REBOL.net

REBOL Async Network Port Examples

REBOL/Core Alpha 2.5.55
Updated 13-Nov-2004 (Rebuilt 10-Mar-2007)

Contents:

1. Async HTTP Request
2. Async Server Example
3. A Client to Access the Above Server
4. Combining Server and Client for Testing

1. Async HTTP Request

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]

Important Note

The server code above is just a simple example to show the concept.

You cannot depend on the read having finished completely at the time that the READ action is processed. In other words the buffer may not contain the entire code to be loaded. Do not use this code for a real server. We will furnish additional examples.

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)

This is quite helpful for seeing what's going on.

Updated 14-Mar-2007, WIP Wiki, REBOL/Core 2.6.2.4.2   -   Copyright 2007 REBOL Technologies   -   WWW.REBOL.COM   -   Edit