B4J Question Server-Sent Events - is available on jServer ?

amorosik

Expert
Licensed User
I'm reading a w3schools document where they talk about Server-Sent Events technology
It is described as a system that allows you to send web pages from the server to the client
So by reversing the normal data flow, it is no longer the client who has to query the server but it is the latter that takes the initiative to send a new screen to the client (who initially subscribed)
The question is: are the instructions behind this feature also available with a jServer based web server?
 

DonManfred

Expert
Licensed User
Longtime User
I don´t see how HTML SSE is related to jserver.

Non of the Supported Browsers are useable in B4J as the webview-Engine is another Implementation than Firefox, Opera, ect.

I guess NO.
 
Last edited:
Upvote 0

tchart

Well-Known Member
Licensed User
Longtime User
Bit weird that this popped up since it was created in March 2021.

To be honest I'd never heard of this before I read this thread. Did some reading and it actually looks pretty neat. Seems to have been around for ages but not widely used.

I don't see why jServer couldn't do this. It seems to just be the client subscribing to some kind of data stream from the server. Might have a play tomorrow.

@DonManfred those are the minimum versions of the browsers. I just tried the W3 schools demo and it works in modern Chrome and SSE are officially part of HTML5.
 
Upvote 0

tchart

Well-Known Member
Licensed User
Longtime User
@alwaysbusy your example/library is obviously a bit more robust/elegant but for a simplified approach you can use something like the code below. This is based on the W3 page (here)

However this approach doesnt work well (at all?) in debug mode since its single threaded.

Also there are lots of caveats for Server Sent Events, notably;

1. There is a finite number of connections under HTTP 1.1 (ie the browser) - you can demonstrate this by opening more than 6 tabs to the same page with an SSE connection.
2. The persistant connection to the handler will put load on your server

B4X:
Sub Handle(req As ServletRequest, resp As ServletResponse)
    resp.ContentType = "text/event-stream"
    resp.SetHeader("Cache-Control","no-cache")
    resp.setHeader("Connection", "keep-alive")
    resp.CharacterEncoding = "UTF-8"
  
    resp.Status = 200
  
    Dim os As OutputStream = resp.OutputStream
  
    SendMessage(os,"123","event: open") 
    SendMessage(os,"123","retry: 5000")
  
    For n = 0 To 100     
        Dim message As String = "data: The server time is: " & DateTime.Now
        Try
            If req.IsInitialized Then SendMessage(os, "123", message)
        Catch
            Log("Disconnected")
            Exit
        End Try
      
        u.Sleep(5000)
    Next 
End Sub

Sub SendMessage(os As OutputStream, id As String, message As String) 
    Dim FinalMessage As String
  
    If id.Length > 0 Then FinalMessage = $"id: ${id}${Chr(10)}"$
  
    FinalMessage = FinalMessage & message & Chr(10) & Chr(10)
  
    os.WriteBytes(FinalMessage.GetBytes("UTF-8"),0,FinalMessage.Length)
    os.Flush
End Sub
 
Last edited:
Upvote 0

tchart

Well-Known Member
Licensed User
Longtime User
Better example using a timer and message loop.

B4X:
'Handler class
Sub Class_Globals
    Dim t As Timer
    Dim os As OutputStream
End Sub

Public Sub Initialize
    t.Initialize("Timer1",1000)
End Sub

Sub Handle(req As ServletRequest, resp As ServletResponse)
    resp.ContentType = "text/event-stream"
    resp.SetHeader("Cache-Control","no-cache")
    resp.setHeader("Connection", "keep-alive")
    resp.CharacterEncoding = "UTF-8"
    
    resp.Status = 200
    
    os = resp.OutputStream
    
    SendMessage("123","event: open")   
    SendMessage("123","retry: 5000")
    
    t.Enabled = True
    
    StartMessageLoop
End Sub

Sub Timer1_Tick
    Dim message As String = "data: The server time is: " & DateTime.Now
    Try
        SendMessage("123", message)
    Catch
        Log("Disconnected")
        t.Enabled = False
        StopMessageLoop
    End Try
End Sub

Sub SendMessage(id As String, message As String)   
    Dim FinalMessage As String
    
    If id.Length > 0 Then FinalMessage = $"id: ${id}${Chr(10)}"$
    
    FinalMessage = FinalMessage & message & Chr(10) & Chr(10)
    
    os.WriteBytes(FinalMessage.GetBytes("UTF-8"),0,FinalMessage.Length)
    os.Flush
End Sub
 
Upvote 0

alwaysbusy

Expert
Licensed User
Longtime User
@tchart I think what you wrote is a 'similar' system, but you do not use the HTML5 EventSource capabilities that need to be at the browser side.

Attached is a pure B4J (no libraries) implementation. I find it an even more elegant solution than my library one.

Still, I think WebSockets are a much better solution.

Alwaysbusy
 

Attachments

  • SSEPureB4J.zip
    3.5 KB · Views: 130
Last edited:
Upvote 0

tchart

Well-Known Member
Licensed User
Longtime User
I think what you wrote is a 'similar' system, but you do not use the HTML5 EventSource capabilities
I didn’t include any HTML as it’s on the W3 page. It does indeed use EventSource. I just replaced the W3 php backend with a b4j handler. There is no magic in Server Sent Events apart from the headers and the persistent connection.
Still, I think WebSockets are a much better solution.

Alwaysbusy
Agree. I think the use case for SSE is very specific.
 
Upvote 0

Jmu5667

Well-Known Member
Licensed User
Longtime User
Here is the exported project for my SSE test. Run under release mode and broswe to http://127.0.0.1:51042/

You should get a page like this and the server times will update.

View attachment 126181
This is excellent. I have just encountered a situation where I am forced to use HTTP to ping the server see to if the app needs to collect messages. The battery usage is an issue. The server code you have done is exactly what I need. The client app in done is B4A. What method would I use to communicate with the server to keep the connection alive so I can push to the client. I was using httpjob, I am now playing with OkHttpClient and OkHttpRequest to use with your code. What do you suggest ?
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
This client code seems to work:
B4X:
'Non-UI application (console / server application)
#Region Project Attributes
    #CommandLineArgs:
    #MergeLibraries: True
#End Region

Sub Process_Globals
   
End Sub

Sub AppStart (Args() As String)
    Log("Hello world!!!")
    communicate
    StartMessageLoop
End Sub

Sub communicate
    Dim sock As Socket
    sock.Initialize("sock")
    sock.Connect("127.0.0.1", 51042, 30000)
    wait for sock_Connected (Successful As Boolean)
    If Successful Then
        Log("Have socket connection")
        'https://www.b4x.com/android/forum/threads/downloading-a-web-page-with-esp8266.70570/#content
        Dim a As AsyncStreams
        a.Initialize(sock.InputStream, sock.OutputStream, "sse")
        Dim eol() As Byte = Array As Byte(13, 10)
        a.Write("GET /sse HTTP/1.1".GetBytes("UTF8"))
        a.Write(eol)
        a.Write("Host: localhost".GetBytes("UTF8"))
        a.Write(eol)
        a.Write("Connection: keep-alive".GetBytes("UTF8"))
        a.Write(eol)
        a.Write(eol)
    Else
        Log("No socket connection")
        StopMessageLoop
    End If
   
End Sub

Sub sse_NewData(Buffer() As Byte)
    Log(BytesToString(Buffer, 0 , Buffer.Length, "UTF8"))
End Sub

Sub sse_Error
    Log("Error")
    StopMessageLoop
End Sub

Sub sse_Terminated
    Log("Terminated")
    StopMessageLoop
End Sub
Note: With this code, I'm going directly to the /sse URL, not /
Note 2: This is not a proper implementation of SSE on the client per se. More code would be needed. This code just allows you to get the "raw" HTTP feed from the server in case of a keep-alive connection.
Note 3: Larger messages may be broken up into several packets by TCP, so one may need to account for that and write additional code to detect such a situation and re-combine the fragments to re-create the message.
 
Last edited:
Upvote 1
Top