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
Related
i want to send event to socket io server that ran on java spring boot application,
the weird point is that my angular app can connect to server perfectly and i can get connect and disconnect events in java.but when it comes to emit an event to server or get any event from server it is not working
Java Class:
public class SocketIOServerImpl {
public static void main(String[] args) throws InterruptedException {
Configuration config = new Configuration();
config.setHostname("localhost");
config.setPort(8090);
websocket
final SocketIOServer server = new SocketIOServer(config);
server.addConnectListener(socketIOClient -> {
System.out.println("User Connected");
server.getBroadcastOperations().sendEvent("daniyal", "You Connected to Server Successfully");
});
server.addDisconnectListener(client -> {
server.getBroadcastOperations().sendEvent("daniyal", "You Connected to Server Successfully");
});
server.addEventListener("daniyal", String.class, new DataListener<String>() {
#Override
public void onData(SocketIOClient socketIOClient, String s, AckRequest ackRequest) throws Exception {
System.out.println("User Connected");
server.getBroadcastOperations().sendEvent("daniyal", "You Emited STH to Server Successfully");
}
});
server.start();
Thread.sleep(Integer.MAX_VALUE);
server.stop();
}
}
angular servise.ts:
#Injectable({
providedIn: 'root'
})
export class SocketServiceService {
readonly uri: string = 'ws://localhost:8090';
socket: any;
constructor() {
this.socket = io.connect(this.uri,{ transports: [ 'websocket' ] });
}
listen(eventName: string) {
return new Observable((subscriber) => {
this.socket.on(eventName, (data: any) => {
subscriber.next(data);
});
});
}
emit(eventName: string, data: any) {
this.socket.emit(eventName, data);
}
}
app.component.ts:
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
constructor(private socketService: SocketServiceService) {
}
ngOnInit() {
this.socketService.listen('daniyal').subscribe(
(res) => {
console.log('Server Response: ',res);
});
}
emit(){
this.socketService.emit('daniyal','HI SERVER');
}
}
i use angular version 14 and java 11
i expected server can get the event and when send event to client,client get that too
I have a websocket server file called server.js
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const port = 1234;
const server = http.createServer(express);
const wss = new WebSocket.Server({ server });
server.listen(port, function() {
console.log(`Server is listening on port ${port}`);
})
wss.on('connection', function connection(ws, req) {
console.log("connected");
ws.on('message', function incoming(data) {
wss.clients.forEach(function each(client) {
if(client !== ws && client.readyState == WebSocket.OPEN) {
client.send(data.toString());
console.log(client.id + " => " + data.toString());
}
})
})
})
In java i wanted to connect to the websocket with initiateWebSocket() function
private void instantiateWebSocket() {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(WEBSOCKET_URL).build();
SocketListener socketListener = new SocketListener(this);
webSocket = client.newWebSocket(request, socketListener);
}
public class SocketListener extends WebSocketListener {
public MainActivity activity;
public SocketListener (MainActivity activity) {
this.activity = activity;
}
#Override
public void onOpen(#NonNull WebSocket webSocket, #NonNull Response response) {
super.onOpen(webSocket, response);
activity.runOnUiThread(() -> Toast.makeText(activity, "Connection starts", Toast.LENGTH_LONG).show());
webSocket.send("Hello..."); // send message here Hello
}
}
So I wanted to send the word "Hello..." when the connection is connected to the websocket.
On my server.js, it showed connected, which means ws.on("connection") is executed...
But the weird part is ws.on("message") is not executed and Hello... is not received
Also note that I didn't received any errors, everything is working fine, only Hello... is not received.
Any advice? Thank you.
When I google this subject, all I found is websockets-related stuff. I want use the Socket API from Java to send and receive data between a client and a server (the server is always a spring-boot web application, the client could be or not).
I designed the server application to run on port 4444 when I execute java -jar server.war and the client to run on port 3333 when I execute java -jar client.war. The server should listen on port 5555.
What I have so far, for the server:
controller
#Controller
public class Home {
ServerSocket s;
Integer PORT = 5555;
#RequestMapping("/")
public String index() {
return "index";
}
#RequestMapping("/open_connection")
#ResponseBody
public void open_connection() throws Exception {
s = new ServerSocket(PORT);
}
#RequestMapping("/close_connection")
#ResponseBody
public void close_connection() throws Exception {
s.close();
}
#RequestMapping("/listen_connection")
#ResponseBody
public String listen_connection() throws Exception {
Socket socket = s.accept();
DataInputStream dis=new DataInputStream(socket.getInputStream());
String str = (String) dis.readUTF();
socket.close();
return str;
}
}
the methods are called through this javascript code:
var isOpen = false;
function open_connection(e) {
var url = e.dataset.url;
var oReq = new XMLHttpRequest();
oReq.onload = function(ev) {
var responseText = oReq.response;
isOpen = true;
document.querySelector('.btn-success').style.display = 'none';
document.querySelector('.btn-danger').style.display = 'block';
}
oReq.open("GET", url);
oReq.send();
}
function close_connection(e) {
var url = e.dataset.url;
var oReq = new XMLHttpRequest();
oReq.onload = function(ev) {
var responseText = oReq.response;
isOpen = false;
document.querySelector('.btn-danger').style.display = 'none';
document.querySelector('.btn-success').style.display = 'block';
}
oReq.open("GET", url);
oReq.send();
}
function listen_connection(e) {
var url = document.querySelector('.container').dataset.url;
if(isOpen) {
while(true) {
var oReq = new XMLHttpRequest();
oReq.onload = function(ev) {
var responseText = oReq.response;
if(responseText === 'quit') {
break;
} else {
var item = document.createElement('li');
item.setAttribute('class', 'list-group-item');
item.innerText = responseText
document.querySelector('.list-group').addChild(item);
}
}
oReq.open("GET", url);
oReq.send();
}
}
}
When I call this methods from the html view, open connection and close connection give me no erros. I have no idea how start to listen the connection to receive data from clients (I try call listen_connection from open_connection, but this way I crash the browser when I call the open connection method).
In the client, I have this:
controller
#Controller
public class Home {
String HOST = "localhost";
Integer PORT = 5555;
#RequestMapping("/")
public String index() {
return "index";
}
#RequestMapping(value="/send_data", method=RequestMethod.POST)
#ResponseBody
public void send_data(#RequestParam("data") String data) throws Exception {
Socket socket = new Socket(HOST, PORT);
DataOutputStream dout = new DataOutputStream(socket.getOutputStream());
dout.writeUTF(data);
dout.flush();
dout.close();
socket.close();
}
}
this methods are called through this javascript code:
function send(e) {
var url = e.dataset.url;
var oReq = new XMLHttpRequest();
oReq.onload = function(ev) {
var responseText = oReq.response;
document.querySelector('.form-control').value = '';
}
oReq.open("POST", url);
var formData = new FormData();
formData.append("data", document.querySelector('.form-control').value)
oReq.send(formData);
}
the issue here is that when I click to call this method, I got a error 403 (forbidden).
Anyone can tell me what I am doing wrong here?
The goto would be to use SpringIntegration , TCP & UDP documentation can be found here. But in summary you can use integration flows to transform messages/communication from one form to another, and implement a bunch of standard enterprise integration patterns.
Spring boot application after all is just a java application that usually exposes some Http Interface, but not always (think about some application that gets its input from, say, messaging system).
So technically there is nothing that can prevent you from using plain sockets.
So you can create (probably of scope Singleton so that there will be only one bean like this in the application context). This bean during the initialization would open up a server socket and accept connections.
Since spring boot manages the lifecycle of the application, you can even close gracefully the server socket when the application goes down.
All this will work as long the server socket port is available and you implement the communication protocol by yourself. This the basis. Keep in mind that this way of communication is extremely low-level, so you won't have monitoring, will have to deal with thread pooling (what if there are too many requests on server running in parallel), etc.
Spring Integration or frameworks like Camel can probably wrap it all in a way that you'll be able to actually use that in production, but I think its kind of beyond the scope of the question.
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.
I just started with Atmosphere for a simple chat application. I downloaded an example with java. This app is sending messages to all clients how can I send a message to a particular client. i think I am able to get uuid. Please someone guide me in right direction.
#Config
#ManagedService(path = "/chat", atmosphereConfig = MAX_INACTIVE + "=9990000")
public class Chat {
private final Logger logger = LoggerFactory.getLogger(Chat.class);
#Inject
private BroadcasterFactory factory;
#Heartbeat
public void onHeartbeat(final AtmosphereResourceEvent event) {
logger.trace("Heartbeat send by {}", event.getResource());
}
#Ready
public void onReady(final AtmosphereResource r) {
logger.info("Browser {} connected", r.uuid());
if(null!=factory && null!=factory.getClass()){
logger.info("BroadcasterFactory used {}", factory.getClass().getName());
}
}
#Disconnect
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());
}
}
#org.atmosphere.config.service.Message(encoders = {JacksonEncoder.class}, decoders = {JacksonDecoder.class})
#DeliverTo(DeliverTo.DELIVER_TO.BROADCASTER)
public Message onMessage(Message message) throws IOException {
logger.info("{} just send {}", message.getAuthor(), message.getMessage());
return message;
}
}
Your Java class is incomplete.
Firstly, there is a missing variable which identify each chat room in your path :
#ManagedService(path = "/chat/{chatRoomId}", atmosphereConfig = MAX_INACTIVE + "=9990000")
public class Chat {
#PathParam("chatRoomId")
private String chatRoomId;
[...]
}
But, you can send all messages to only one socke connected.
Secondly, where is your script JS file to send and receive websocket message ?
This script JS file must contains these methods :
request.onOpen = function(request, response) {
};
request.onTransportFailure = function(request, response) {
};
request.onMessage = function(request, response) {
};
request.onClose = function(request, response) {
};
request.onError= function(request, response) {
};
request.onReconnect = function(request, response) {
};
The most important is to declare the structure of your request :
var socket = atmosphere;
var subSocket;
var transport = 'websocket';
var request = {
url: document.location.toString() + 'chat' + chatRoomId,
contentType : "application/json",
logLevel : 'debug',
transport : transport ,
trackMessageLength : true,
reconnectInterval : 5000
};