Server-Sent Events

Web Sockets

+Auto Buttons

^{https://software-lab.de/doc/form/refA.html#+Auto +Auto}

+Auto +Button

clock

(de clock () (action (html 0 "Clock" "@lib.css" NIL (form NIL (gui '(+TimeField) 8) (gui '(+Click +Auto +Button) 250 'This 999 '(pop *Throbber) '(set> (field -1) (time)) ) ) ) ) )

+Auto

Server-Sent Events

The serverSentEvent Function

(serverSentEvent "id" 'var . prg)

serverSend

serverSentEvent

var

serverSentEvent

prg

var

var

NIL

The serverSend Function

prg

serverSend

(serverSend 'sock . prg)

prg

sock

An Interactive Test

<div>

*Sse

(<div> '(id . "sseDiv")) (serverSentEvent "sseDiv" '*Sse)

prg

: (serverSend *Sse (<h1> NIL "Hello" (<nbsp>) (<em> "World") "!"))

#!/usr/bin/pil -load "@lib/http.l" "@lib/xhtml.l" # 03aug18abu (de sseTest () (html 0 "SSE" "@lib.css" NIL (<div> '(id . "sseDiv")) (serverSentEvent "sseDiv" '*Sse) ) ) (de work () (app) (timeout) (redirect (baseHRef) *SesId "!sseTest") ) (server 8080 "!work")

pil

#!/usr/bin/pil ...

sseTest

<div>

work

(app)

sseTest

(app)

work

(timeout)

PilBox

$ mkdir sse $ cat >sse/App.l "Server-Sent Events" (on *Repl) (menu "Server-Sent Events" (<h1> NIL "Server-Sent Events") (<div> '(id . "sseDiv")) (serverSentEvent "sseDiv" '*Sse) ) <Ctrl-D> $ zip -rFS sse.zip sse/App.l adding: sse/App.l (deflated 37%) $ termux-share sse.zip

(on *Repl)

(serverSend *Sse ...)

Simple Clock Display

+Auto +Button

(de clock () (html 0 "Clock" "@lib.css" NIL (<div> '(id . "clockDiv")) (serverSentEvent "clockDiv" '*ClockSock (task -960 0 # More than once per second (if *ClockSock (serverSend *ClockSock (<h2> NIL (prin (tim$ (time) T))) ) (task -960) ) ) ) ) )

*ClockSock

clockDiv

*ClockSock

NIL

SSH Terminal

<pre>

*ChatTxt

(<pre> '((id . "msg") (style . "height:48em; font-family: monospace") ) (ht:Prin *ChatTxt) )

serverSentEvent

*ChatSock

ssh

*Ssh

*ChatTxt

<br>

serverSend

(serverSentEvent "msg" '*ChatSock (off *ChatTxt) (zero *ChatLen) (task (setq *Ssh (pipe (exec "ssh" "-tt" *User@Host))) (in @ (if (and *ChatSock (not (eof))) (let? C (char) (unless (= C "r") (queue '*ChatTxt (case C ("n" (if (= *ChatLen 39) (until (= "<br>" (++ *ChatTxt))) (inc '*ChatLen) ) "<br>" ) ("t" " ") (T C) ) ) (serverSend *ChatSock (ht:Prin *ChatTxt) ) ) ) (task (close *Ssh)) (off *Ssh) ) ) ) )

+TextField

(gui '(+Focus +TextField) 80) (gui '(+JS +Button) "Chat" '(when (and *Ssh (val> (field -1))) (out *Ssh (prinl @)) (clr> (field -1)) ) )

Source listings

+Auto Button

#!/usr/bin/pil # 03aug18abu (allowed () "!work" "!clock" "@lib.css" ) (load "@lib/http.l" "@lib/xhtml.l" "@lib/form.l") (de clock () (action (html 0 "Clock" "@lib.css" NIL (form NIL (gui '(+TimeField) 8) (gui '(+Click +Auto +Button) 250 'This 999 '(pop *Throbber) '(set> (field -1) (time)) ) ) ) ) ) (de work () (app) (redirect (baseHRef) *SesId "!clock") ) (server 8080 "!work")

Simple Clock Display

#!/usr/bin/pil # 03aug18abu (allowed () "!work" "!clock" "@lib.css" ) (load "@lib/http.l" "@lib/xhtml.l") (de clock () (html 0 "Clock" "@lib.css" NIL (<div> '(id . "clockDiv")) (serverSentEvent "clockDiv" '*ClockSock (task -960 0 # More than once per second (if *ClockSock (serverSend *ClockSock (<h2> NIL (prin (tim$ (time) T))) ) (task -960) ) ) ) ) ) (de work () (app) (redirect (baseHRef) *SesId "!clock") ) (server 8080 "!work")

SSH Terminal

#!/usr/bin/pil # 03aug18abu ## Usage: ## misc/sshChat user@host (setq *User@Host (opt)) (allowed () "!work" "!chat" "@lib.css" ) (load "@lib/http.l" "@lib/xhtml.l" "@lib/form.l") (de chat () (action (html 0 "Chat" "@lib.css" NIL (form NIL (<pre> '((id . "msg") (style . "height:48em; font-family: monospace") ) (ht:Prin *ChatTxt) ) (serverSentEvent "msg" '*ChatSock (off *ChatTxt) (zero *ChatLen) (task (setq *Ssh (pipe (exec "ssh" "-tt" *User@Host))) (in @ (if (and *ChatSock (not (eof))) (let? C (char) (unless (= C "r") (queue '*ChatTxt (case C ("n" (if (= *ChatLen 39) (until (= "<br>" (++ *ChatTxt))) (inc '*ChatLen) ) "<br>" ) ("t" " ") (T C) ) ) (serverSend *ChatSock (ht:Prin *ChatTxt) ) ) ) (task (close *Ssh)) (off *Ssh) ) ) ) ) (--) (gui '(+Focus +TextField) 80) (gui '(+JS +Button) "Chat" '(when (and *Ssh (val> (field -1))) (out *Ssh (prinl @)) (clr> (field -1)) ) ) ) ) ) ) (de work () (app) (redirect (baseHRef) *SesId "!chat") ) (server 8080 "!work")

The normal PicoLisp Web GUI, running over HTTP, is client-driven: A client (typically a browser) initiates the connection, sends GET or POST requests, and receives responses from the server. HTTP by itself provides no way for the server to send anything to the client on its own.It is often desirable, however, to display something to the user without the need of manually reloading the page.One way to do this is Web Sockets , a full-duplex protocol. It is not trivial, and I cannot tell much about it as I have never used it.But some people did, for example Henrik Sarvell with Websockets with PicoLisp , based on Jose I. Romero's https://bitbucket.org/iromero91/web.l/src/default/web/sockets.l library, so I won't further elaborate on it.The traditional way in the PicoLisp GUI to get some automatism is thebutton prefix class. It is used in PicoLisp applications since many years.Ancauses the current form to auto-update periodically - essentiallythe server at regular intervals. This is not very efficient, but quite smooth as it refreses the form contents at the JavaScript level.The followingfunction shows a page with a time field and an auto button which starts after a quarter second, and then updates the form roughly every second:See below for a complete program listing.is fine if an action needs to be repeated anyway, and it has the advantage of being fully integrated into the form GUI framework, updating the complete state on the server (variables, GUI components, database etc.).And: It also works if JavaScript is not available (e.g. in text browsers or web scrape clients ), if the user presses the button manually, or the script triggers the button explicitly. Server-Sent Events are an elegant, efficient and asynchronous way to send data to clients.They are available in PicoLisp since version 18.5.23.They are a lot simpler than Web Sockets - however only one-way, while Web Sockets are bi-directional. The advantage of the latter does not outweigh the first in my opinion, as the opposite direction (from client to server) is available in HTTP anyway.The API consists of just two functions: 'serverSentEvent' and 'serverSend', usable inside the standard PicoLisp GUI. They don't need a form environment, a plain HTML session is enough.'serverSentEvent' is embedded at an arbitrary place in the page as:The first argument should be the ID of a DOM component in the page. Later calls towill update this component. The ID should be unique within this program session for alluse cases, as it is also used as a key to an internal association table.The second argumentis a variable which is automatically bound to a socket as soon as the client establishes the event stream connection, i.e. shortly after the page with the embedded call tois displayed. In essence, this socket is thento the DOM component "id".When that happened, thebody is executed to set up everything needed for further event sending via the socket inis automatically set to, and the socket closed, when the client closes the connection. Then the server should clean up whatever is necessary.When the event stream connection is established from the client, andis executed,may be called anytime while the connection is open:It sends all output (HTML text) generated byto the inner HTML of the component connected to the socketImportant: This HTML text must be all on a single line (no newlines, use

if necessary).Server-Sent Events can be tested interactively in the PicoLisp REPL.Create an emptyand connect it to a global variableThebody is empty here, no initialization needed.Then send some HTML code from the REPL:The full source listing is:Adjust the first line for the path to your environment'sinterpreter. A typical value on Unix is(the above is for Termux on Android).generates a page with the above emptyis a helper function starting a session with. It redirects immediately to, and is only needed for an application like this, which needs JavaScript already on the very first page (to satisfy the Same-Origin-Policy, becauseallocates a new port for the session).callswithout arguments in the new session, effectively disabling timeouts. This is also normally not needed, but useful here so that the session does not close while idling in the REPL.In PilBox it is even easier. Assuming you have the Android App installed, you do in Termux:PilBox should start up.The lineactivates the REPL in this App, so that it is not necessary to navigate to the REPL page first to click the checkbox.You just swipe to the REPL on the right side and callRevisiting the above clock example with, we can do something similar with Server-Sent Events:Afteris connected, we start a background task every 960 seconds which sends the current time as a string to theelement. Afterbecame, the task stops.Again, see below for the full source code.As a more involved example, let's look at a "chat" with a remote shell via SSH. Not really useful in itself, but it shows how to handle asynchronous messages in a chat-like system.We use aelement to show the text in a global variablein monospaced font:connects it to a global variableand starts a task which opens a pipe to theutility, and stores the file descriptor in a global variableAll incoming characters are appended to, replacing newlines withtags, and tabs with three spaces. The length of the text is limited to 40 lines, and sent to the chat socket withCommands to the remote shell can be entered into a, and sent by pressing Enter or the "Chat" button:Again, the complete source code is appended below.The script expects a single argument of the form "user@host" for the SSH session. The login is best provided for with proper key files on the remote machine, otherwise you need to type the password in the terminal where the script was started.

https://picolisp.com/wiki/?serversentevents