Trying to set up undertwo to play with it a bit, i am trying to server a static html file index.html and add my annoted websocket ServerEndpoint pojo so i did the following
final Xnio xnio = Xnio.getInstance("nio", Undertow.class.getClassLoader());
final XnioWorker xnioWorker = xnio.createWorker(OptionMap.builder().getMap());
WebSocketDeploymentInfo webSockets = new WebSocketDeploymentInfo ()
.addEndpoint(Socket.class)
.setWorker(xnioWorker);
final DeploymentManager deployment = defaultContainer()
.addDeployment(deployment()
.setClassLoader(main.class.getClassLoader())
.setContextPath("/websockets")
.setDeploymentName("websockets")
.addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, webSockets));
deployment.deploy();
deployment.start();
Undertow.builder()
.addHttpListener(8000,"localhost")
.setHandler(path().addPrefixPath("/", resource(new ClassPathResourceManager(main.class.getClassLoader(), main.class.getPackage())).addWelcomeFiles("WSGSS/index.html")))
.build()
.start();
the index.html gets served as expected but i cant seem to connect to the websocket, and i am sure i am doing something wrong with the way i deploy the websocket, if i do it as in the examples on github it works but then i cant find the right way to serve the static files
in javascript i get the exception
var ws = new WebSocket("ws://localhost:8000/websockets/socket")
WebSocket connection to 'ws://localhost:8000/websockets/socket' failed: Error during WebSocket handshake: Unexpected response code: 404
also here is my ServerEndpoint
#ServerEndpoint(value = "/socket",
encoders = CommandEncoder.class,
decoders = CommandDecoder.class)
public class Socket{
private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<Session>());
private final Logger log = Logger.getLogger(getClass().getName());
#OnMessage
public void onMessage(Session session,Command command){
log.info("command of type "+ command.getAction() + " recieved");
command.Process();
try{
session.getBasicRemote().sendObject(command);
//Future<Void> future = session.getAsyncRemote().sendText(message);
} catch (IOException | EncodeException ex){
log.info(ex.getMessage() + ex.getCause());
}
}
#OnOpen
public void onOpen(Session session){
sessions.add(session);
log.info("new session created "+ session.getId());
}
#OnClose
public void onClose(Session session){
sessions.remove(session);
}
#OnError void onError(Session session,Throwable thr){
log.info(session.getId() + " error " + thr.getMessage() + thr.getCause());
}
}
The following solves it
Undertow.builder()
.addHttpListener(8000,"localhost")
.setHandler(path()
.addPrefixPath("/", resource(new ClassPathResourceManager(main.class.getClassLoader(), main.class.getPackage())).addWelcomeFiles("WSGSS/index.html"))
.addPrefixPath("/websocket",manager.deployment()))
.build()
.start();
Related
I'm building a react application that uses atmosphere library to listen to a SpringBoot websocket, when the client tries to connect to the server, it throws an error in the console saying Some cookies are misusing the recommended “sameSite“ attribute. I added some attributes to the request object to fix the issue as recommended (SameSite cookies). but I'm still getting the same error.
ReactJS code:
import React from 'react';
import * as atmosphere from 'atmosphere.js';
//import $ from 'jquery';
var transport = 'websocket';
//var req = new atmosphere.AtmosphereRequest();
// We are now ready to cut the request
var request = {
url:'http://localhost:8080/stream',
contentType: "application/json",
trackMessageLength: true,
shared: true,
enableXDR: true,
headers: { 'Access-Control-Allow-Origin': '*',
'sameSite': ' None; Secure'
},
//sameSite: 'None; Secure',
rewriteURL:true,
transport: transport,
fallbackTransport: 'long-polling',
onOpen: function(response:any) {
console.log('Atmosphere connected using ' , response.transport);
transport = response.transport;
},
onTransportFailure: function(errorMsg: Atmosphere.Response, request: Atmosphere.Request) {
console.log('Atmosphere Chat. Default transport is WebSocket, fallback is ' ,request.fallbackTransport );
},
onMessage: function (response:Atmosphere.Response) {
var message = response.responseBody;
try {
console.log('message: ', message);
} catch (e) {
console.log('This doesn\'t look like a valid JSON: ', message);
return;
}
},
onClose : function(response: Atmosphere.Response) {
console.log("Close connection !!!");
}
};
const socket = atmosphere;
// Connect to the server, hook up to the request handler.
console.log('socket : ', socket.subscribe);
socket.subscribe && socket.subscribe(request);
const AtmosphereWebSocket = () => {
return ( <div> </div> );
}
export default AtmosphereWebSocket;
SpringBoot Code:
#Component
#CrossOrigin(origins = "http://localhost:3000")
#WebSocketHandlerService(path = "/stream", broadcaster = SimpleBroadcaster.class,
atmosphereConfig = {"org.atmosphere.websocket.WebSocketProtocol=" +
"org.atmosphere.websocket.protocol.StreamingHttpProtocol"})
public class WebSocketStream extends WebSocketStreamingHandlerAdapter {
private final Logger logger = LoggerFactory.getLogger(WebSocketStream.class);
public WebSocketStream() {
System.out.println(" ** WebSocketStream ** ");
}
// A thread which sends a stream of data out of a websocket. Create when the class
// is instantiated, inject the websocket when open.
private class Stream extends Thread {
protected WebSocket socket;
protected final ObjectMapper mapper = new ObjectMapper();
protected boolean stop = false;
public Stream(WebSocket socket) {
this.socket = socket;
}
public void run() {
int count = 0;
try {
while (!stop) {
Map<String, Object> message = new HashMap<String, Object>();
message.put("time", new Date().toString());
message.put("count", count++);
String string = mapper.writeValueAsString(message);
socket.write(string);
System.out.println("tick: " + string);
Thread.sleep(1000);
}
} catch (Exception x) {
// break.
}
}
}
int clients = 0;
#Override
public void onOpen(WebSocket webSocket) throws IOException {
// Hook up the stream.
final Stream stream = new Stream(webSocket);
stream.start();
logger.info(" on open was called !!!");
webSocket.broadcast("client " + clients++ + " connected");
webSocket.resource().addEventListener(new WebSocketEventListenerAdapter() {
#Override
public void onDisconnect(AtmosphereResourceEvent event) {
if (event.isCancelled()) {
logger.info("Browser {} unexpectedly disconnected", event.getResource().uuid());
} else if (event.isClosedByClient()) {
logger.info("Browser {} closed the connection", event.getResource().uuid());
}
stream.stop = true;
}
});
}
}
Error Message:
Websocket failed on first connection attempt. Downgrading to long- polling and resending 1.chunk.js:3632:18
Atmosphere Chat. Default transport is WebSocket, fallback is long-polling atmosphere.tsx:27
The development server has disconnected.
Refresh the page if necessary. 1.chunk.js:7419:13
Sat Jul 11 2020 15:52:07 GMT-0500 (Central Daylight Time) Atmosphere: unload event 1.chunk.js:3632:18
[HMR] Waiting for update signal from WDS... log.js:24
Download the React DevTools for a better development experience: react-dom.development.js:24994
socket :
function subscribe(url, callback, request)
atmosphere.tsx:47
Firefox can’t establish a connection to the server at ws://localhost:8080/stream?X-Atmosphere-tracking-id=0&X-Atmosphere-Framework=3.0.5-javascript&X-Atmosphere-Transport=websocket&X-Atmosphere-TrackMessageSize=true&Content-Type=application/json&X-atmo-protocol=true&Access-Control-Allow-Origin=*&sameSite=%20None%3B%20Secure. atmosphere.js:1201
Websocket closed, reason: Connection was closed abnormally (that is, with no close frame being sent). - wasClean: false atmosphere.js:3302
Close connection !!! atmosphere.tsx:40
Websocket failed on first connection attempt. Downgrading to long-polling and resending atmosphere.js:3302
Atmosphere Chat. Default transport is WebSocket, fallback is long-polling
Is there a way to enable encoded slashes for websockets in tomcat?
I have set org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH to true and it works as expected for web requests.
Hovewer, there is a problem with WS requests. That setting doesn't seem to work for them.
I have WS endpoint configured as like this:
#ServerEndpoint(value = "/ws/data/{path}", decoders = ChatMessageDecoder.class, encoders = ChatMessageEncoder.class)
It works just fine for URL ws://localhost:8080/app/ws/data/Test but it doesn't work for URL ws://localhost:8080/ws/data/Test%2FSubDir. For the latter URL I receive 404 error.
Is there a way to allow encoded slashes for WS requests?
Maybe you are ignoring the #PathParam.
I tried the following code and works as expected without any additional configuration:
package com.logicbig.example;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import javax.websocket.server.PathParam;
#ServerEndpoint("/hello/{parameter}")
public class HelloWorldEndpointWilcard {
public HelloWorldEndpointWilcard() {
System.out.println("class loaded " + this.getClass());
}
#OnOpen
public void onOpen(Session session, #PathParam("parameter") String parameter) {
System.out.printf("Session opened, id: %s%n", session.getId());
System.out.println("parameter onOpen:"+parameter);
try {
session.getBasicRemote().sendText("Hi there, we are successfully connected.");
} catch (IOException ex) {
ex.printStackTrace();
}
}
#OnMessage
public void onMessage(String message, Session session, #PathParam("parameter") String parameter) {
System.out.printf("Message received. Session id: %s Message: %s%n",
session.getId(), message);
System.out.println("parameter onMessage:"+parameter);
try {
session.getBasicRemote().sendText(String.format("We received your message: %s%n", message));
} catch (IOException ex) {
ex.printStackTrace();
}
}
#OnError
public void onError(Throwable e) {
e.printStackTrace();
}
#OnClose
public void onClose(Session session) {
System.out.printf("Session closed with id: %s%n", session.getId());
}
}
And this javascript client:
var webSocket = new WebSocket("ws://localhost:8080/example/hello/Test%2FSubDir");
Test%2FSubDir value was received successfully on session established and on each message:
Complete java source code here
I couldn't make encoding working, but I have 3 alternative solutions.
Make multiple #ServerEndpoint:
#ServerEndpoint(value = "/ws/data/{path}"
public SingleChatHandler : ChatHandler {
#OnOpen
public void onOpen(
Session session,
#PathParam("path") String path) {
onOpenInternal(session, path);
}
}
#ServerEndpoint(value = "/ws/data/{path1}/{path2}"
public DoubleChatHandler : ChatHandler {
#OnOpen
public void onOpen(
Session session,
#PathParam("path1") String path1,
#PathParam("path2") String path2) {
onOpenInternal(session, path1 + '/' + path2);
}
}
public abstract class ChatHandler {
protected void onOpenInternal(Session session, String path) { [...] }
}
This however has two main disadvantages:
depth of the path must be know beforehand to implement required handlers
there is no way to have more than one parameter with slashes
Use query string instead of #PathParam:
#ServerEndpoint(value = "/ws/data")
public class ChatHandler {
#OnOpen
public void onOpen(Session session) {
Map<String, List<String>> map = session.getRequestParameterMap();
if (!map.containsKey("path")) {
// TODO: throw exception
}
String path = map.get("path").get(0);
}
}
This has 2 disadvantages as well, but less troublesome:
URL is not too nice
there will be no 404 when parameters are not provided (I still see 101)
Use query string instead of #PathParam (use code from 2. as a base) and check query string in modifyHandshake method:
public class ChatHandlerConfigurator extends ServerEndpointConfig.Configurator
{
#Override
public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response)
{
Map<String, List<String>> map = request.getParameterMap();
if (!map.containsKey("path")) {
throw new IllegalArgumentException("path was not provided");
}
// Optionally add values to userProperties
Map<String, Object> userProperties = config.getUserProperties();
userProperties.put("path", map.get("path").get(0));
}
}
This behaves similarly to 2. but since I throw exception on handshake, instead of 101 there will be 500 error.
The Spring framework support tcp connection as well , i wrote code below to setup a simple socket server , i am confused about adding below futures to my socket server :
authorizing clients based on a unique identifier ( for example a client secret received from client, maybe using TCP Connection Events )
send a message directly to specific client (based on identifier)
broadcast a message
UPDATE :
Config.sendMessage added to send message to single client
Config.broadCast added to broadcast message
authorizeIncomingConnection to authorize clients , accept or reject connections
tcpConnections static filed added to keep tcpEvent sources
Questions !
is using tcpConnections HashMap good idea ?!
is the authorization method i implemented a good one ?!
Main.java
#SpringBootApplication
public class Main {
public static void main(final String[] args) {
SpringApplication.run(Main.class, args);
}
}
Config.java
#EnableIntegration
#IntegrationComponentScan
#Configuration
public class Config implements ApplicationListener<TcpConnectionEvent> {
private static final Logger LOGGER = Logger.getLogger(Config.class.getName());
#Bean
public AbstractServerConnectionFactory AbstractServerConnectionFactory() {
return new TcpNetServerConnectionFactory(8181);
}
#Bean
public TcpInboundGateway TcpInboundGateway(AbstractServerConnectionFactory connectionFactory) {
TcpInboundGateway inGate = new TcpInboundGateway();
inGate.setConnectionFactory(connectionFactory);
inGate.setRequestChannel(getMessageChannel());
return inGate;
}
#Bean
public MessageChannel getMessageChannel() {
return new DirectChannel();
}
#MessageEndpoint
public class Echo {
#Transformer(inputChannel = "getMessageChannel")
public String convert(byte[] bytes) throws Exception {
return new String(bytes);
}
}
private static ConcurrentHashMap<String, TcpConnection> tcpConnections = new ConcurrentHashMap<>();
#Override
public void onApplicationEvent(TcpConnectionEvent tcpEvent) {
TcpConnection source = (TcpConnection) tcpEvent.getSource();
if (tcpEvent instanceof TcpConnectionOpenEvent) {
LOGGER.info("Socket Opened " + source.getConnectionId());
tcpConnections.put(tcpEvent.getConnectionId(), source);
if (!authorizeIncomingConnection(source.getSocketInfo())) {
LOGGER.warn("Socket Rejected " + source.getConnectionId());
source.close();
}
} else if (tcpEvent instanceof TcpConnectionCloseEvent) {
LOGGER.info("Socket Closed " + source.getConnectionId());
tcpConnections.remove(source.getConnectionId());
}
}
private boolean authorizeIncomingConnection(SocketInfo socketInfo) {
//Authorization Logic , Like Ip,Mac Address WhiteList or anyThing else !
return (System.currentTimeMillis() / 1000) % 2 == 0;
}
public static String broadCast(String message) {
Set<String> connectionIds = tcpConnections.keySet();
int successCounter = 0;
int FailureCounter = 0;
for (String connectionId : connectionIds) {
try {
sendMessage(connectionId, message);
successCounter++;
} catch (Exception e) {
FailureCounter++;
}
}
return "BroadCast Result , Success : " + successCounter + " Failure : " + FailureCounter;
}
public static void sendMessage(String connectionId, final String message) throws Exception {
tcpConnections.get(connectionId).send(new Message<String>() {
#Override
public String getPayload() {
return message;
}
#Override
public MessageHeaders getHeaders() {
return null;
}
});
}
}
MainController.java
#Controller
public class MainController {
#RequestMapping("/notify/{connectionId}/{message}")
#ResponseBody
public String home(#PathVariable String connectionId, #PathVariable String message) {
try {
Config.sendMessage(connectionId, message);
return "Client Notified !";
} catch (Exception e) {
return "Failed To Notify Client , cause : \n " + e.toString();
}
}
#RequestMapping("/broadCast/{message}")
#ResponseBody
public String home(#PathVariable String message) {
return Config.broadCast(message);
}
}
Usage :
Socket Request/Response Mode
notify single client
http://localhost:8080/notify/{connectionId}/{message}
broadCast
http://localhost:8080/broadCast/{message}
The TcpConnectionOpenEvent contains a connectionId property. Each message coming from that client will have the same property in the IpHeaders.CONNECTION_ID message header.
Add a custom router that keeps track of the logged-on state of each connection.
Lookup the connection id and if not authenticated, route to a challenge/response subflow.
When authenticated, route to the normal flow.
To use arbitrary messaging (rather than request/response) use a TcpReceivingChannelAdapter and TcpSendingMessageHandler instead of an inbound gateway. Both configured to use the same connection factory. For each message sent to the message handler, add the IpHeaders.CONNECTION_ID header to target the specific client.
To broadcast, send a message for each connection id.
Been trying to follow some online examples as I need to do basic authentication on a webservice client.
I generated the stub classes of the project using wsimport and tried passing the authentication credentials using javax.xml.rpc.stub class but casting the proxy class throws a java.lang.ClassCastException:
com.sun.proxy.$Proxy29 cannot be cast to javax.xml.rpc.Stub.
please can anyone review this code and point me in the right direction if am doing something wrong.
public class WebClientTester
{
public static void main(String[] args)
{
doNameEnquiry("XXXXXXXXX");
}
public static void doNameEnquiry(String acct)
{
boolean txnOk = false;
try
{
String str = "http://XXX.XXX.XXX.XXX/GwHolderService.svc?wsdl";
URL url = new URL(str.substring(0, str.indexOf("?")));
QName qname = new QName("http://tempuri.org/", "GwHolderService");
Service service = Service.create(url, qname);
SInfoHolder port = (SInfoHolder) service.getPort(SInfoHolder.class);
((javax.xml.rpc.Stub) port)._setProperty(javax.xml.rpc.Stub.USERNAME_PROPERTY, "myUser");
((javax.xml.rpc.Stub) port)._setProperty(javax.xml.rpc.Stub.PASSWORD_PROPERTY, "myPwd");
InfoHolderRequest request = new InfoHolderRequest();
request.setHolderAccountNumber(acct);
InfoHolderResponse response = port.infoHolder(request);
// System.out.println("authenticated: "+
// response.getRespMessageCode());
System.out.println("******************END RESPONSE***********");
System.out.println("responseCode: " + response.getCoderesp());
System.out.println(processResponseXML(response));
System.out.println("******************LIST DETAILS***********");
listDetails(processResponseXML(response));
}
catch (Exception ex)
{
ex.printStackTrace();
}
// return txnOk;
}
}
I have core java project (swing module) but recently requirement come that to deploy one restful web service on core java without any container.
So Is it possible to deploy restful web service without any container?
I have searched many site using that I have got code as below:
public class JerseyEmbeddedHTTPServerCrunchify {
public static void main(String[] args) throws IOException {
System.out
.println("Starting Crunchify's Embedded Jersey HTTPServer...\n");
HttpServer crunchifyHTTPServer = createHttpServer();
crunchifyHTTPServer.start();
System.out.println(String.format(
"\nJersey Application Server started with WADL available at "
+ "%sapplication.wadl\n", getCrunchifyURI()));
System.out
.println("Started Crunchify's Embedded Jersey HTTPServer Successfully !!!");
}
private static HttpServer createHttpServer() throws IOException {
// ResourceConfig crunchifyResourceConfig = new
// PackagesResourceConfig("com.crunchify.tutorial");
ResourceConfig crunchifyResourceConfig = new ResourceConfig();
// This tutorial required and then enable below line:
// http://crunfy.me/1DZIui5
// crunchifyResourceConfig.getContainerResponseFilters().add(CrunchifyCORSFilter.class);
// return HttpServerFactory.create(getCrunchifyURI(),
// crunchifyResourceConfig);
System.out.println("URI : " + getCrunchifyURI());
return JdkHttpServerFactory.createHttpServer(getCrunchifyURI(),
crunchifyResourceConfig);
}
private static URI getCrunchifyURI() {
// return UriBuilder.fromUri("http://" + crunchifyGetHostName() +
// "/").port(18085).build();
return UriBuilder.fromUri("http://" + "localhost" + "/").port(18085)
.build();
}
private static String crunchifyGetHostName() {
String hostName = "localhost";
try {
hostName = InetAddress.getLocalHost().getCanonicalHostName();
} catch (UnknownHostException e) {
e.printStackTrace();
}
return hostName;
}
Maven Dependency:
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-jdk-http</artifactId>
<version>2.4.1</version>
</dependency>
In above code I got below exception:
Exception in thread "main" java.lang.IllegalStateException: server in wrong state
at sun.net.httpserver.ServerImpl.start(ServerImpl.java:139)
at sun.net.httpserver.HttpServerImpl.start(HttpServerImpl.java:58)
at org.glassfish.jersey.jdkhttp.JdkHttpServerFactory$1.start(JdkHttpServerFactory.java:143)
You can try this
#RestController
public class WebServiceController
{
#RequestMapping(value="/getresultbyNumber/{number1}/{number2}", method={RequestMethod.POST,RequestMethod.GET})
public #ResponseBody Object getData(#PathVariable String number1, #PathVariable String number2 , HttpServletRequest request)
{
String URL="http://192.168.4.218:8081/DemoSpringMVC/getResultbyNumberdata/"+ number1 +"/"+number2;
HttpClient client= HttpClientBuilder.create().build();
HttpGet httpRequest=new HttpGet(URL);
String res =null;
try {
HttpResponse response=client.execute(httpRequest);
res = new BasicResponseHandler().handleResponse(response);
System.out.println("result===>"+res);
} catch (Exception e) {
e.printStackTrace();
}
return res ;
}
}