How to implements Simple SSE Server using Java? - javascript

I want to implement Server Sent Events (SSE) using simple Java Server Socket (instead with php and/or asp).
This server will do only simple things: listening for incoming request from browser, creates a handler thread, reply the request (sending header) without closing the connection, and then, wait for user input (System.in) that will forward the messages to the client.
Of course it will be an overkill if I use J2EE library (eg. Jersey) just for doing task like that.
The source is as follow:
import java.net.*;
import java.io.*;
import java.util.Scanner;
public class Loc {
public static void main ( String[] args ) throws IOException{
ServerSocket ss = new ServerSocket(8888);
//listen to only one connection.
System.out.print("Waiting for connection...");
Socket hp = ss.accept();
System.out.println("Connected!");
BufferedReader br = new BufferedReader(new InputStreamReader(hp.getInputStream()));
String line = "";
while( br.ready() && (line=br.readLine()) != null) { //dumps request header.
System.out.println(line);
}
Thread handler = new Thread(new ClientHandler(hp));
handler.start();
try {
handler.join();
}catch(InterruptedException e){
}
System.out.println("Server stopped.");
}
}
class ClientHandler implements Runnable{
Socket toClient;
public ClientHandler(Socket s){
this.toClient = s;
}
public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); //server input (also forwards it to client)
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(toClient.getOutputStream()));
// response header.
String response = "HTTP/1.1 200 OK\n"+
"Server: Maknyos-Server/1.0\n"+
"Cache-Control: no-cache,public\n"+
"Content-Type: text/event-stream\n"+
"Connection: keep-alive\n"+
"Language: en-US\n"+
"Charset: UTF-8\n"+
"\n"; //two line break as header ending.
bw.write(response);
bw.flush();
String input = "";
while ( (input = br.readLine()) != null ) { //one directional message to client (browser), via cmd.
bw.write(input+'\n');
bw.flush();
if("close".equalsIgnoreCase(input)){
break;
}
}
toClient.close();
}catch(IOException e){
System.out.println("IOEXception! "+e);
}
}
This is the client.
<h1>Hullo World!</h1>
<script type="text/javascript">
var es = new EventSource("http://localhost:8888");
es.onmessage = function (event) {
alert(event.data);
};
</script>
When I open the html page, I can see the http request header on my server std out (command line).
My server then reply it with
HTTP/1.1 200 OK
Server: Maknyos-Server/1.0
Cache-Control: no-cache,public
Content-Type: text/event-stream
Connection: keep-alive
Language: en-US
Charset: UTF-8
...
I'll try to describe what I do and what is the problems, between those two client and server. It maybe related to the protocol, but I'm not sure.
First, If I put only two (new line) at the end of the header to finish the response header (as the example above), Firefox console will tell me that the browser can't connect to the server. But, in the NET console, it receives the response request.
Second, I test without finishing the response header (without double \n character).
Neither of them succeeded in sending SSE to the browser.
Edit, when I see again the browser's console windows, it appears that It cannot make a request to another domain!
this program runs on localhost:8888
but the webserver I use (for the html page) is on localhost:80

I suggest you write your application using Spring Boot and write a controller which will return an instance of SseEmitter. Spring Boot is lightweight and fits your requirements.
http://docs.spring.io/autorepo/docs/spring-framework/4.3.0.RC2/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.html
Remember to use the send() method in another thread.

It turns out that I'm missing one important header in my server header string.
"Access-Control-Allow-Origin:*" + "\r\n" + ...

Related

How can i use socket communication between java server and javascript client?

I'm trying to connect java Server and Javascript client with socket.io. When i see the debugger at browser, it looks like the data is being received, but i'm getting this error: "Reason: CORS header 'Access-Control-Allow-Origin' missing" and i am not being able to print data at client-side.
import...
public class MeuServerSocket {
//initialize socket and input stream
private Socket socket = null;
private ServerSocket server = null;
private DataInputStream in = null;
public MeuServerSocket(int port) {
// starts server and waits for a connection
try {
while(true){
server = new ServerSocket(port);
System.out.println("Server started");
System.out.println("Waiting for a client ...");
socket = server.accept();
System.out.println("Client accepted");
ObjectOutputStream saida = new ObjectOutputStream(socket.getOutputStream());
saida.flush();
// send available data from server to client
saida.writeObject("Texto enviado 123...");
// takes input from the client socket
in = new DataInputStream(
new BufferedInputStream(socket.getInputStream()));
String line = "";
// reads message from client until "Over" is sent
boolean fim = false;
while (!line.equals("Over") && !fim)
{
try
{
line = in.readUTF();
System.out.println(line);
}
catch(IOException i)
{
fim = true;
System.out.println(i.toString());
}
}
System.out.println("Closing connection");
// close connection
socket.close();
saida.close();
in.close();
}
} catch (IOException i) {
System.out.println(i);
}catch(Exception e){
System.out.println(e.toString());
}
}
public static void main(String[] args) {
MeuServerSocket server = new MeuServerSocket(5000);
}
}
var socket = io('http://localhost:5000');
socket.on('connect', function () {
socket.send('hi \nOver');
socket.on('get', function (msg) {
// my msg
console.log('msg: '+msg)
})
socket.on('disconnect',()=>{
console.log('disconnected')
})
})
When i look at Firefox network, i see that the data was sent inside one of the packages...
https://imgur.com/vDAS00B
The biggest issue I'm seeing here is a misunderstanding of socket.io. Socket.io for javascript is not compatible with the Socket library in java. The naming conventions can be confusing for sure.
socket.io is a library that is related to web sockets (ws://). It implements all the basic websocket features plus some bonuses.
What you have for your java code is a TCP socket server. While websockets and socket.io are built on TCP socket, you can not connect a socket.io client to a "naked" socket server.
SOLUTION:
If your javascript is running from nodejs, you can use their net library found here. If you are running javascript from a webbrowser, than you are limited to websockets, which means you're going to change your java code to a websocket server. You can find a library for that somewhere online.
TLDR: Use ws://... instead of http://....
Details:
https is used for HTTP protocol. In such case it is correct that browser first asks your server if CORS is allowed. You have not enabled CORS. That's why it is normal that browser refuses to send CORS request.
But you say you want to use Web Sockets. Then you should use ws://, not http://. For Web Sockets there is no CORS policy and browser will send your request without CORS restrictions.

Javascript - Arduino - POST request - WiFi101 - Connection issue

In my code I am trying to send a POST request to the IFTTT service webhooks (maker).
I'm using a couple of libraries, mainly WiFi101
I am using an Arduino MKR1000.
I have updated the firmware, and added a certificate for https://maker.ifttt.com:443.
When in the following code I call sslClient.connect(host, 443); It fails to make the connection. I have tried bypassing this and just trying to print data to the host, however this also didn't work.
It takes about 10-20 seconds for the function to return as false, if I change the host to an incorrect variable, then it returns as false immediately. I'm assuming this is a good sign since the arduino is trying to connect?
wifiSetup() Runs well, connection is established reasonably quickly.
The code I am refering to is below:
Globally defined
//WiFi router setup
char ssid[] = "-----"; //network SSID (aka WiFi name)
char pass[] = "-----"; //network password
int status = WL_IDLE_STATUS;
const char* host = "https://maker.ifttt.com";
WiFiSSLClient sslClient;
Wifi setup procedure: This runs without problems
void wifiSetup() {
// Check for the presence of the shield
Serial.print("WiFi101 shield: ");
if (WiFi.status() == WL_NO_SHIELD) {
Serial.println("NOT PRESENT");
return; // don't continue
}
Serial.println("DETECTED");
// attempt to connect to Wifi network:
while ( status != WL_CONNECTED) {
Serial.print("Attempting to connect to Network named: ");
Serial.println(ssid); // print the network name (SSID);
// Connect to WPA/WPA2 network. Change this line if using open or WEP network:
status = WiFi.begin(ssid, pass);
// wait 10 seconds for connection:
delay(10000);
}
printWifiStatus(); // you're connected now, so print out the status
}
The code below is the one causing problems
void sendMessage() {
if (sslClient.connect(host, 443)) {
//change this to your Maker setting from https://ifttt.com/services/maker/settings
String data = "randomdata";
sslClient.println("POST /trigger/tank_empty/with/key/bxa");
sslClient.println("Host: https://maker.ifttt.com");
sslClient.println("Content-Type: application/json");
sslClient.print("Content-Length: ");
sslClient.println(data.length());
sslClient.println();
sslClient.print(data);
sslClient.stop();
Serial.println("IFTTT request Sucessful");
}
else {
Serial.println("IFTTT request failed");
}
delay(20000000);
}
Does anyone have any solutions, or things to troubleshoot?
Thanks for your help all,
Let me know if you need any extra information.
https://maker.ifttt.com is not a valid host. A valid host is either an IP address or a domain. https:// is not a part of the domain, but an URL.
You are also missing the HTTP protocol version (HTTP/1.1), which could potentially cause problems.
const char* host = "maker.ifttt.com";
sslClient.println("POST /trigger/tank_empty/with/key/bxa HTTP/1.1");
sslClient.print("Host: ");
sslClient.println(host); // non hardcoded host header
sslClient.println("Content-Type: application/json");
sslClient.print("Content-Length: ");
sslClient.println(data.length());
sslClient.println();
sslClient.print(data);
sslClient.stop();

python socket - cant get sent data from javascript

I'm trying to make a javascript program send data to a python socket but it doesn't receive the right data.
I want python to print 'aaaa'.
Here is my javascript code:
function createCORSRequest(method, url) {
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined") {
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
xhr = null;
}
return xhr;
}
var xhr = createCORSRequest('GET', "http://192.168.1.10:12345");
xhr.send("aaaa");
Here is my python code:
import socket
s = socket.socket()
host = socket.gethostname()
port = 12345
BUFFER_SIZE = 1024
s.bind(('', port))
s.listen(5)
while True:
c, addr = s.accept()
print ('Got connection from', addr)
c.send(bytes('Thank you for connecting','UTF-8'))
data = c.recv(BUFFER_SIZE)
print(data)
c.close()
You are doing an XMLHttpRequest, which is a HTTP request. But your python server does not handle the HTTP protocol at all. Handling HTTP would mean to read the HTTP request header, read the body based on the information in the header and return a proper HTTP response.
You are doing a HTTP GET requests. A GET requests takes no payload so any body data you add (i.e. the "aaaa" in your xhr.send("aaaa")) will be ignored (means: not send). To send a HTTP body use request types like POST.
Steffen's answer is correct (at least in general - can't comment on the JS specifics). In addition, it's always a good idea to independently verify the moving parts of your application, so that you can narrow down where the problem is.
Here's how you can verify that your python server works from the command line:
Start the server
In another terminal window, connect to it using telnet
telnet localhost 12345
(It will first try connecting using IPv6, fail, and fall back to IPv4)
You will see your welcome message returned to the client. Enter some text and press Enter.
The server will print your message and close the connection to the client.
Using your code, here's how it will look for the client. I'm sending the text meow to the server:
margold#home-macbook ~ $ telnet 127.0.0.1 12345
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Thank you for connectingmeow
Connection closed by foreign host.
And for the server:
margold#home-macbook ~ $ ./server.py
('Got connection from', ('127.0.0.1', 61148))
meow

AuthorizedHandler Blocked wrong request! url: /socket.io/

I'm using mrniko/netty-socketio (Java) to start a websocket server like this:
config = new Configuration();
config.setHostname("localhost");
config.setPort(8001);
server = new SocketIOServer(config);
server.addListeners(serviceClass);
server.start();
Then I'm using (the recommended) socketio/socket.io-client (JavaScript) to try to connect to the websocket server like this (all on the same server):
var socket = io("http://localhost:8001");
The connection is "blocked" at the server with the server printing:
8239 [nioEventLoopGroup-5-1] WARN com.corundumstudio.socketio.handler.AuthorizeHandler - Blocked wrong request! url: /socket.io/, ip: /127.0.0.1:48915
28889 [nioEventLoopGroup-5-2] WARN com.corundumstudio.socketio.handler.AuthorizeHandler - Blocked wrong request! url: /socket.io/, ip: /127.0.0.1:48916
Which occurs endlessly, as the client continues to retry the connection.
I can't seem to get the server to accept the connection. I've tried:
var socket = io("ws://localhost:8001");
But that gives the same outcome. I've also tried putting a trailing slash after the URL for both cases - makes no difference. I've also tried all combinations of using "localhost" or "127.0.0.1" at both the server and client, and so on.
The JavaScript page itself is being served up from a http server on localhost:8000. This does not appear to be a cross site issue as that gives an entirely different error at the browser.
Does anyone know what is going wrong and how to fix it?
In my case network monitoring accesses that port every 10 seconds. I had temporarily changed log4j.properties to ERROR level logging, but wanted to provide networking a path to use that would not cause excessive warn logging. Not sure if this was the best approach, but this is what I ended up doing.
config.setAllowCustomRequests(true);
By allowing custom requests the piece of code displaying the warning was bypassed in Authorizehandler.
I created a custom pipeline, that allowed me to switch out the wrongUrlHandler with a custom one to allow a safe path to use for monitoring.
public class CustomSocketIOChannelInitializer extends SocketIOChannelInitializer {
CustomWrongUrlHandler customWrongUrlHandler = null;
public CustomSocketIOChannelInitializer(Configuration configuration) {
customWrongUrlHandler = new CustomWrongUrlHandler(configuration);
}
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
addSslHandler(pipeline);
addSocketioHandlers(pipeline);
// Replace wrong url handler with our custom one to allow network monitoring without logging warnings.
pipeline.replace(SocketIOChannelInitializer.WRONG_URL_HANDLER, "CUSTOM_WRONG_URL_HANDLER", customWrongUrlHandler);
}
This is my custom handler:
#Sharable
public class CustomWrongUrlHandler extends ChannelInboundHandlerAdapter {
private final Logger log = LoggerFactory.getLogger(getClass());
Configuration configuration = null;
/**
* #param configuration
*/
public CustomWrongUrlHandler(Configuration configuration) {
this.configuration = configuration;
}
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof FullHttpRequest) {
FullHttpRequest req = (FullHttpRequest) msg;
Channel channel = ctx.channel();
QueryStringDecoder queryDecoder = new QueryStringDecoder(req.getUri());
// Don't log when port is pinged for monitoring. Must use context that starts with /ping.
if (configuration.isAllowCustomRequests() && queryDecoder.path().startsWith("/ping")) {
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
channel.writeAndFlush(res).addListener(ChannelFutureListener.CLOSE);
req.release();
//log.info("Blocked wrong request! url: {}, ip: {}", queryDecoder.path(), channel.remoteAddress());
return;
}
// This is the last channel handler in the pipe so if it is not ping then log warning.
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
ChannelFuture f = channel.writeAndFlush(res);
f.addListener(ChannelFutureListener.CLOSE);
req.release();
log.warn("Blocked wrong socket.io-context request! url: {}, params: {}, ip: {}", channel.remoteAddress() + " " + queryDecoder.path(), queryDecoder.parameters());
}
}
}
CustomSocketIOChannelInitializer customSocketIOChannelInitializer = new CustomSocketIOChannelInitializer(config);
server.setPipelineFactory(customSocketIOChannelInitializer);

Connecting to websocket using C# (I can connect using JavaScript, but C# gives Status code 200 error)

I am new in the area of websocket.
I can connect to websocket server using JavaScript using this code:
var webSocket = new WebSocket(url);
But for my application, I need to connect to the same server using c#. The code I am using is:
ClientWebSocket webSocket = null;
webSocket = new ClientWebSocket();
await webSocket.ConnectAsync(new Uri(url), CancellationToken.None);
3rd line of the code results following error:
"Server returned status code 200 when status code 101 was expected"
After little bit of survey, I realised that somehow server can't switch http protocol to websocket protocol during connection process.
Am I doing anything stupid in my C# code or there is something going wrong with the server. I don't have any access to the server, as the url I am using is a third party one .
Could you please give me any suggestion regarding the issue?
TL; DR:
Use ReceiveAsync() in loop until Close frame is received or CancellationToken is canceled. That's how you get your messages. Sending is straightworward, just SendAsync(). Do not use CloseAsync() before CloseOutputAsync() - because you want to stop your receiving loop first. Otherwise - either the CloseAsync() would hang, or if you use CancellationToken to quit ReceiveAsync() - the CloseAsync() would throw.
I learned a lot from https://mcguirev10.com/2019/08/17/how-to-close-websocket-correctly.html .
Full answer:
Use Dotnet client, here, have an example cut out from my real life code, that illustrate how the handshaking is made. The most important thing most people don't understand about how the thing operates is that there is no magic event when a message is received. You create it yourself. How?
You just perform ReceiveAsync() in a loop that ends, when a special Close frame is received. So when you want to disconnect you have to tell the server you close with CloseOutputAsync, so it would reply with a similar Close frame to your client, so it would be able to end receiving.
My code example illustrates only the most basic, outer transmission mechanism. So you send and receive raw binary messages. At this point you cannot tell the specific server response is related to the specific request you've sent. You have to match them yourself after coding / decoding messages. Use any serialization tool for that, but many crypto currency markets use Protocol Buffers from Google. The name says it all ;)
For matching any unique random data can be used. You need tokens, in C# I use Guid class for that.
Then I use request / response matching to make request work without dependency on events. The SendRequest() methods awaits until matching response arrives, or... the connection is closed. Very handy and allows to make way more readable code than in event-based approach. Of course you can still invoke events on messages received, just make sure they are not matched to any requests that require response.
Oh, and for waiting in my async method I use SemaphoreSlim. Each request puts its own semaphore in a special dictionary, when I get the response, I find the entry by the response token, release the semaphore, dispose it, remove from the dictionary. Seems complicated, but it's actually pretty simple.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
namespace Example {
public class WsClient : IDisposable {
public int ReceiveBufferSize { get; set; } = 8192;
public async Task ConnectAsync(string url) {
if (WS != null) {
if (WS.State == WebSocketState.Open) return;
else WS.Dispose();
}
WS = new ClientWebSocket();
if (CTS != null) CTS.Dispose();
CTS = new CancellationTokenSource();
await WS.ConnectAsync(new Uri(url), CTS.Token);
await Task.Factory.StartNew(ReceiveLoop, CTS.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
public async Task DisconnectAsync() {
if (WS is null) return;
// TODO: requests cleanup code, sub-protocol dependent.
if (WS.State == WebSocketState.Open) {
CTS.CancelAfter(TimeSpan.FromSeconds(2));
await WS.CloseOutputAsync(WebSocketCloseStatus.Empty, "", CancellationToken.None);
await WS.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
}
WS.Dispose();
WS = null;
CTS.Dispose();
CTS = null;
}
private async Task ReceiveLoop() {
var loopToken = CTS.Token;
MemoryStream outputStream = null;
WebSocketReceiveResult receiveResult = null;
var buffer = new byte[ReceiveBufferSize];
try {
while (!loopToken.IsCancellationRequested) {
outputStream = new MemoryStream(ReceiveBufferSize);
do {
receiveResult = await WS.ReceiveAsync(buffer, CTS.Token);
if (receiveResult.MessageType != WebSocketMessageType.Close)
outputStream.Write(buffer, 0, receiveResult.Count);
}
while (!receiveResult.EndOfMessage);
if (receiveResult.MessageType == WebSocketMessageType.Close) break;
outputStream.Position = 0;
ResponseReceived(outputStream);
}
}
catch (TaskCanceledException) { }
finally {
outputStream?.Dispose();
}
}
private async Task<ResponseType> SendMessageAsync<RequestType>(RequestType message) {
// TODO: handle serializing requests and deserializing responses, handle matching responses to the requests.
}
private void ResponseReceived(Stream inputStream) {
// TODO: handle deserializing responses and matching them to the requests.
// IMPORTANT: DON'T FORGET TO DISPOSE THE inputStream!
}
public void Dispose() => DisconnectAsync().Wait();
private ClientWebSocket WS;
private CancellationTokenSource CTS;
}
}
BTW, why use other libraries than the .NET built in? I can't find any reason other than maybe poor documentation of the Microsoft's classes. Maybe - if for some really weird reason you would want to use modern WebSocket transport with an ancient .NET Framework ;)
Oh, and I haven't tested the example. It's taken from the tested code, but all inner protocol parts were removed to leave only the transport part.
Since WebsocketSharp is not .NET Core compatible I suggest using websocket-client instead.
Here's some sample code
static async Task Main(string[] args)
{
var url = new Uri("wss://echo.websocket.org");
var exitEvent = new ManualResetEvent(false);
using (var client = new WebsocketClient(url))
{
client.MessageReceived.Subscribe(msg => Console.WriteLine($"Message: {msg}"));
await client.Start();
await client.Send("Echo");
exitEvent.WaitOne();
}
Console.ReadLine();
}
Be sure to use ManualResetEvent. Otherwise it doesn't work.
If you connect with a WebSocket client and you get an HTTP 200 as response, means that probably you are connecting to the wrong place (host, path and/or port).
Basically, you are connecting to a normal HTTP endpoint that is not understanding your WebSocket requirement, and it is just returning the "OK" response (HTTP 200). Probably the WebSocket server runs in another port or path in the same server.
Check your URL.
Not quite sure what happened to WebSocketSharp nuget package, however I noticed that now WebSocket# is showing up as most relevant result in nuget repo. It took me some time before I realized that Connect() is now returning Task, hopefully this example will be useful to someone:
using System;
using System.Threading.Tasks;
using WebSocketSharp;
namespace Example
{
class Program
{
private static void Main(string[] args)
{
using (var ws = new WebSocket(url: "ws://localhost:1337", onMessage: OnMessage, onError: OnError))
{
ws.Connect().Wait();
ws.Send("Hey, Server!").Wait();
Console.ReadKey(true);
}
}
private static Task OnError(ErrorEventArgs errorEventArgs)
{
Console.Write("Error: {0}, Exception: {1}", errorEventArgs.Message, errorEventArgs.Exception);
return Task.FromResult(0);
}
private static Task OnMessage(MessageEventArgs messageEventArgs)
{
Console.Write("Message received: {0}", messageEventArgs.Text.ReadToEnd());
return Task.FromResult(0);
}
}
}
All the libraries mentioned above are Wrappers. The .Net Frameworks class doing this is System.Net.WebSockets.ClientWebSocket
Websocket URLs should start with ws:// or wss:// where the latter is secure websocket.

Categories

Resources