Trash/JWebServer/EventHandler

From J Wiki
Jump to navigation Jump to search

The simple web server functions, more or less. But while it's running the J session is frozen.

To incorporate web serving functionality into J's event handling -- and, thus, allowing continued use of the J session while it's running, we need to restructure the code so that:

a. Every socket is async a. The event handling verb socket_handler is defined, and can handle traffic on each of these sockets.

Here, I've adopted a few conventions: Each socket N has three associated handling sentences which are named inN, outN and failN. These are used when the socket is ready for reading, ready for writing, or ready for exception handling. If the handling sentence is missing when the socket is ready for it, the socket is closed (thus, sockets are closed after encountering an exception if there is no exception handler). Also associated with the socket are buffers, inbN and outbN -- these buffers are initially empty strings.

Whenever a socket is closed, the associated sentences and buffers are erased.

When opened, sockets are immediately available for writing, but the http protocol requires that a request be received before anything be sent. So the httpSend routine ignores empty buffers. When httpSend empties a buffer, I immediately close the socket.

Because socket_handler only runs when the status of a socket changes, httpRecv immediately starts sending text as soon as it's ready. In the typical case, everything happens immediately and the socket winds up being closed twice (once from httpSend from in httpRecv, and again when handling the fact that the socket had been ready for writing. This seems harmless, so I've left it alone.

Finally, it's possible to have this web server serving on multiple ports at the same time. For example: servehttp 80 8000 8080 would start it serving on three ports. This version of the web server does not track which server socket is associated with which port. (This could be added.)

Here's the code:

require 'socket'
coinsert 'jsocket'

servehttp=: verb define"0
 5 servehttp y NB. x is queue length, y is port
:
 server=. {. ; sdcheck sdsocket ''
 ('in',":server)=: 'httpAccept ',":server
 ('out',":server)=: ''
 sdcheck sdbind server; AF_INET; ''; y
 sdcheck sdlisten server, x
 sdcheck sdasync server
)

httpAccept=: verb define
 sdcheck sdasync socket=. >{. sdcheck sdaccept y
 ('in',":socket)=: 'httpRecv ',":socket
 ('out',":socket)=: 'httpSend ',":socket
 ('inb',":socket)=: ('outb',":socket)=: ''
)

httpRecv=: verb define
 request=. ('inb',":y)=:(".'inb',":y),; sdcheck sdrecv y, 65536 0
 if. 1 e. CRLF E. request do.
  if. 1 = +/' '= (i.&LF {. ]) request do.
   (simpleResponse y responseFor request) httpSend y
  end.
 end.
 if. (httpLength <: #) request do.
  (y responseFor request) httpSend y
 end.
)

headerValue=: adverb define
 [: (i.&CR {. ]) ((4+#m.)"_ + 1: i.~ (CRLF,tolower m.,': ')"_ E. tolower) }. ]
)

headersLength=: 4: + 1: i.~ (CRLF,CRLF)"_ E. ]
contentLength=: {.@(0&".)@('Content-Length' headerValue)
httpLength=: headersLength + contentLength

simpleResponse=: headersLength }. ]

httpSend=: verb define
 buf=. 'outb',":y
 if. *#".buf do.
  sent=. >{.sdcheck (".buf) sdsend y,0
  (buf)=: sent }.".buf
  if. 0 = #".buf do.
   close y
  end.
 end.
:
 (buf)=: (".buf=. 'outb',":y),x
 httpSend y
)

responseFor=: dyad define
 'HTTP/1.0 200 OK',CRLF,'Content-Type: text/plain',CRLF,CRLF,y
)

erase=: 4!:55"0

close=: verb define
 erase (;:'in inb out outb fail') ,L:0"0 1/ ":,. y
 sdclose y
)

doEach=: dyad define"1 0 L: 0
 if. *y do.
  name=. x,":y
  if. 0 = nc <name do.
   ".".name NB. run the handler
  else.
   close y
  end.
 end.
 0 0$0
)

socket_handler=: verb define
 (;:'in out fail') doEach sdcheck sdselect ''
 0 0$0 NB. avoid implicit output from event handling
)

reset=: verb def 'close ; sdcheck sdgetsockets i.0'

Note:

This version supports HTTP/0.9 requests and request bodies for POST requests, as required by the HTTP/1.0 standard.

This version includes no support for extracting urls nor any of the other common web server tasks.

This version includes a number of commented out print (1!:2&2) statements -- if these are enabled, the session will display the pattern of calls with each event handler call. This can be useful if you want to understand the behavior of the system.

In unusual circumstances, this code might provide a pop-up indicating an error. This would not be useful in many circumstances. This behavior might be changed by replacing sdcheck with something more useful (perhaps something which logs the errors).

No optimizations have been made for speed. In general, optimizing complicates the architecture of the system and should only be used to alleviate significant bottlenecks. However, if it were worth optimizing, I'd probably have an additional inst variable for each socket, which keeps track of the state of the input process (and, when it's relevant, the target length of the input buffer). This would involve changes to httpAccept, httpRecv and close.

According to the standard, the Content-Length: header is case sensitive. However, some browsers (such as lynx) use Content-length: instead of Content-Length: To accommodate these browsers, the header is treated case-insensitively.

In the general case, request headers might span multiple lines. However, this is extremely rare, and is not a required feature of the web server. Implementing support for this would probably best be accomplished using the ;: dyad.

The verb reset is not used by the server. It's provided for the convenience of the J programmer, so that all active sockets may be closed without having to exit and restart J.

To be useful for something beyond simple diagnostics and education, the responseFor verb would need to be replaced.

Next: HTTP Parser