I'm building a rudimentary web server in Rebol 2. I'm trying in particular to understand the flow and logic behind the asynchronous aspects of the TCP scheme.
As far as I can understand it, you create a listening port, place it on the wait list. As requests come in, clones of the listening port is created which are then added to the wait list until their function is complete.
In the meantime, the state of all ports concerned need to be managed (via their ASYNC-MODES) in order for the correct flow of data.
Things I'm unclear on:
My port logic is as minimal as I could make it—is this sufficient to be somewhat reliable?
ASYNC-MODES are undocumented and apparently a temporary measure that calcified. Does any explanation exist as to how they should be set over the course of operations? (the only usage examples I've been able to locate are the UniServe Engine and the Async Protocol, however both are intended as base protocols and have incorporate layers of code management that obscure the elementary network operations)
When reading the request, the only way to ensure the entire request is read is to push READ-IO until it returns a 'WOULD-BLOCK error. Is this the only way to do this?
The client handler reads the request, sends the response and closes the client port. In HTTP, there is more dialog that this simple request->response operation. Is it enough to keep the client port open in order to be able to continue, e.g. in response to a
Expect: 100-continue
header?client-awake: func [client-port][ ... read client port ... ... send response ... ... if decisive response, close client port ... false ]
Minimal Async Web Server
#!/usr/local/bin/rebol -cs
Rebol [
Title: "Minimal Web Server for Rebol 2"
Author: "Christopher Ross-Gill"
Date: 15-Jan-2017
File: %mini-webserver.r
Version: 0.1.0
Purpose: "An elementary Web Server"
Rights: http://opensource.org/licenses/Apache-2.0
]
buffer: make binary! 2 + buffer-size: 1024
start: func [port [port!]][
append system/ports/wait-list port
]
stop: func [port [port!]][
remove find system/ports/wait-list port
close port
]
wake-server: func [server-port [port!] /local client-port][
client-port: first server-port
net-utils/net-log reform ["HTTP request from" client-port/host]
; set-modes client-port [no-wait: true]
client-port/async-modes: 'read
client-port/awake: :wake-client
client-port/locals: make object! [
request: make binary! 1024
response: "HTTP/1.0 200 OK^/Content-type: text/html^/^/<pre>I'm Alive!^/^/"
]
start client-port
false ; returns false to perpetuate the WAIT function
]
wake-client: func [client-port [port!] /local state request response][
request: client-port/locals/request
response: client-port/locals/response
while [
case [
error? state: try [
read-io client-port clear buffer buffer-size
][
state: disarm :state
either state/code = 517 [ ; would-block
; end of input?
net-utils/net-log "End of Stream"
][
net-utils/net-log "Read Error"
? state
]
false
]
state <= 0 [
net-utils/net-log join "Client closed connection: " state
false
]
state [true]
]
][
net-utils/net-log "Reading stream..."
append request buffer
]
client-port/async-modes: 'write
write-io client-port response: join response request length? response
stop client-port
false ; returns false to perpetuate the WAIT function
]
server-port: make port! [
scheme: 'tcp
port-id: 8888
async-modes: 'connect
awake: :wake-server
]
trace/net on
net-utils/net-log "Opening HTTP listening port 8888"
start server-port
open/direct/binary server-port
browse http://127.0.0.1:8888/test
wait []
net-utils/net-log "Closing HTTP listening port 8888"
stop server-port