This example performs a simple async read from an HTTP server and tells you what
the handler is doing on each action.
REBOL [Title: "Simple Async HTTP"]
port-spec: tcp://www.rebol.net:80
http-request: {GET /
User-Agent: REBOL/Core
Connection: close
}
client: context [
data: make binary! 10000
handler: func [port action arg] [
switch action [
read [
append data copy/part port arg
print ["-- read" arg "bytes" length? data "total"]
]
write [print "-- writing (sending)"]
write-done [print "-- done with write"]
close [
print ["-- done with read" length? data]
close port
print ["-- closed port, press RETURN to quit"]
]
init [print "-- port initialized"]
open [print "-- opened" insert port http-request]
address [print ["-- address lookup:" arg]]
error [print ["-- error:" mold disarm :arg] close port]
]
]
]
p: open/direct/binary/async port-spec get in client 'handler
input ; (wait for user console input before closing)
attempt [close p]
In this example, the client object holds the context for the handler.
This is not required, but it is a handy way to isolate the data and functions of the connection (in case you want to open multiple connections at the same time).
Note that many of the actions are printed in the above example simply for educational purposes. You can remove write, write-done, init, and address. The error case has
been provided here only to show how it can be handled.
The call to INPUT above is only needed to prevent the CLOSE function from happening
immediately. I will wait until you type a line at the console before closing. In
your actual application, you do not need to do that.
To see how the handler does for large reads, change the http-request to use
this for the first line:
GET /builds/031/rebol2552031.exe
It's about 300 KB.
2. Async Server Example
Here is the code for an async server. Note that server commands are dialected (as blocks) rather than being string-oriented. This is the preferred method of
communication REBOL.
REBOL [Title: "Async Server"]
user-id: 0
debug: none :print
server: context [
user: "server"
buffer: make string! 1000
handler: func [port action arg /local obj] [
debug ["SERVER:" action user arg]
switch action [
read [
append buffer copy/part port arg
debug ["-- Server read" arg "bytes" length? buffer "total"]
print ["-- Client said:" as-string buffer]
cmd: load/all as-string buffer
clear buffer
parse cmd [
'who-am-I? (
insert port mold/only join [you-are] user
)
| 'thanks! (insert port 'bye!)
| none (insert port 'huh?)
]
]
close [print "-- Server asks: Anyone else? (or press RETURN)^/"]
listen [first port] ; clone port, process immediately
accept [
obj: make server [ ; Create a new "session":
user: join "User-" [user-id: user-id + 1]
]
port/state/async-context: obj
port/state/async-handler: get in obj 'handler
print ["-- Accept" obj/user
"from" port/remote-ip "on port" port/remote-port ]
insert port 'Welcome
]
error [print ["-- Error:" mold disarm :arg] close port]
]
]
]
To watch what's going on, you can remove the NONE from the debug line at the top (which makes DEBUG the same as PRINT.)
To start the server code:
server-port: open/direct/binary/async tcp://:8000 get in server 'handler
input ; wait for RETURN key
attempt [close server-port]
3. A Client to Access the Above Server
Here is example client code to access the above server.
To run it, first start the server code from the prior section in one instance of
REBOL, then in a separate instance of REBOL, run this code:
REBOL [Title: "Async Client"]
debug: none :print
client: context [
port: none
user: none
buffer: make binary! 1000
handler: func [port action arg] [
debug ["CLIENT:" action arg]
switch action [
read [
append buffer copy/part port arg
debug ["== Client read" arg "bytes" length? buffer "total"]
print ["== Server said:" as-string buffer]
cmd: load/all as-string buffer
clear buffer
parse cmd [
'Welcome (insert port 'who-am-I?)
| 'you-are set user string! (
insert port 'thanks!
print ["== Client thinks: Wow! I am" user]
)
| 'bye! (close port)
| none (print ["== Unknown reply:" cmd] close port)
]
]
close [close port]
error [print ["== Error:" mold disarm :arg] close port]
]
]
]
client-port: open/direct/binary/async tcp://localhost:8000
get in client 'handler
input ; wait for RETURN key
attempt [close client-port]
If all works well, you should see the client output this:
[wait]
== Server said: Welcome
== Server said: you-are "User-1"
== Client thinks: Wow! I am User-1
== Server said: bye!
If you go look at the server console, you will see:
[wait]
-- Accept User-1 from 127.0.0.1 on port 1093
-- Client said: who-am-I?
-- Client said: thanks!
-- Server asks: Anyone else? (or press RETURN)
To see complete details of each action, remove the NONE from the debug line.
To try multiple async requests at the same time, use code such as this:
clients: []
loop 4 [
append clients c: make client []
c/port: open/direct/binary/async tcp://localhost:8000 get in c 'handler
]
input ; wait for RETURN key
foreach c clients [attempt [close c/port]]
4. Combining Server and Client for Testing
A useful feature while debugging is to run the server and client within the same instance of REBOL. Because the server and client are both asynchronous, they can be run at the "same" time.
server-port: open/direct/binary/async tcp://:8000 get in server 'handler
client-port: open/direct/binary/async tcp://localhost:8000 get in client 'handler
input ; wait for RETURN key
attempt [close server-port]
attempt [close client-port]
When you run the above, you will see the output from the server and the client mixed together:
-- Accept User-1 from 127.0.0.1 on port 1133
== Server said: Welcome
-- Client said: who-am-I?
== Server said: you-are "User-1"
== Client thinks: Wow! I am User-1
-- Client said: thanks!
== Server said: bye!
-- Server asks: Anyone else? (or press RETURN)