REBOL
Docs Blog Get-it

REBOL/Services Tutorial and Guide

Introduction

This document was written for those who want to understand REBOL/Services and how to use them but don't have the time to read dozens of pages of technical documentation. The information included here should be enough to get you started on your own simple scripts and applications, but advanced users will find it beneficial to read some of the other REBOL/Service technical documents.

What are REBOL/Services?

It's easy to just take off and start using REBOL/Services, but before you do, you may want to get a basic understanding of what they are and how they work.

REBOL/Services provides a simple, elegant way to share information between computer programs. They can be used to exchange information between clients and servers that are located thousands of miles apart or simply between applications running on your local computer.

REBOL/Services implements a concept called a service oriented architecture or SOA for short. The basic idea of an SOA is that you send a request message to another program (a "service") that attempts to fulfill the request and return the result.

Benefits of Services

The primary benefits REBOL/Services are:

Types of Services

As an SOA, REBOL/Services gives you an easy way to create a wide variety of applications such as:

There is really no end to this list because it only takes a page or two to implement most services. I'm sure you can imagine many other uses as well.

How Services Work

It's really quite simple, and that's one of the good things about it. To use a service, you send it a request. It processes the request and sends back a result. The service can be anywhere. It might be on a server in some remote location, or on another client in your local network, or in another process on the same machine. It does not matter.

The Request/Result Sequence

Let's take a closer look at the sequence:

RequestThe request is sent to the service. The format of the request is very simple: it is a REBOL block. The block begins with a command word and contains others words and values that are arranged in an order that the service understands. It is a REBOL dialect, and offers all the benefits of that mechanism.
ProcessingThe request is handled by the service. It must parse the request (the command), act on it, and compose a result to be returned. All of this is usually driven with the PARSE function, but other methods are allowed as well.
ResultWhen the processing is complete, the result is sent back to the client. The format of the result is somewhat similar to the request, but not identical. The format allows for the client to quickly determine the success of the request and to obtain the resulting values that may have been returned.

All transfers both to and from the service are always encrypted. A variety of encryption strengths are provided. They are described in a separate document.

Example Requests

What does a request block look like? It depends on the service. For example, the request can be as simple as this block:

[time]

This request asks for the current time from the service. The time is a built-in command that is a default part of all services.

A more detailed request block might look like this:

[file/get %maui.jpg 12-Mar-2005/10:00 %hawaii.jpg 14-Mar-2005/2:20]

This block requests that two files be transferred back to the client if the files are newer than the dates specified. If the files are older, they are not sent back to the client.

A request can also include multiple command blocks. Here are three commands sent in the same request:

[time]
[info title]
[file/put %photo.jpg (read/binary %photo.jpg)]

The result that is returned to the client will contain three result blocks.

Example Results

The results that are returned from a service consist of two parts: a success indicator and a block that holds the resulting values. For example, the time command above might return:

ok [time 25-Mar-2005/12:08:12-8:00]

The ok tells you that the command was processed successfully. If it failed for some reason, the word fail would be returned, and if an error occured the word error would be returned.

The block that follows the success indicator tells you the command and its result. This block is also dialected, so the format of the block depends on the service. However, the first value in the block is always the command. (This makes it easier for advanced clients to generate callback events within their applications.)

For multiple command requests, you will get multiple command results. The example from the prior section might produce these results:

ok [time 25-Mar-2005/12:08:12-8:00]
ok [info "Default REBOL Service"]
ok [file/put %photo.jpg 20456]

There is one more thing that you should know about the result block. It contains a request summary indicator and block that we did not show above (to keep the example simple). An example summary looks like this:

done [reply seq 1 service "Generic Service" commands 1 time 0:00:01]

The done word indicates that no errors occurred and the request was fully processed. This makes it simple to check that your request succeeded, even if it contains multiple commands. The block contains summary information about the processing of the request. Most of the time you can safely ignore this information.

Accessing a Service

Ok, now let's take a look at how to access a REBOL/Service.

Services are implemented in REBOL through a function-based interface. This approach keeps the interface very simple, minimizing what a user has to know to make it work. However, under the hood, REBOL's asynchronous port mechanism is used, allowing advanced developers a greater degree of control.

The Simplest Example

To get started as a beginner, it's best to just look at an example line of code that accesses a service:

result: do-service tcp://server:8000 [time]

This line sends a TIME request to a service, waits for a response, and returns the result. This is called a synchronous request. The do-service function waits until the server has processed the request before the function returns.

Asynchronous Access (No-wait)

For some types of programs you will want to use an asynchronous request. This method does not wait for the result. You can either provide a function that is called when the result is received (a callback) or wait for the result.

Here is an example of an asynchronous request.

send-service/action tcp://server:8000 [time] [print result]

Here a TIME request is sent to the service, but the code does not wait for a result. When the result is received, the second block is evaluated, printing the result. This approach is very common within graphical user interfaces, that are by their nature asynchronous (more than one thing going on at a time).

If you need to wait for the result to a request you made earlier, you can use the WAIT-SERVICE function. Here is an example:

req: send-service tcp://server:8000 [time]
do-some-other-stuff
result: wait-service req

The request is sent and while it is being processed you can do other things. Then, when you need the result, you can wait for it.

Issuing Multiple Requests

The above examples provide the shortcut of allowing a URL to be provided for the service. However, most of the time in real applications you will not provide a URL each time. Instead, you will establish a connection that will remain open for multiple requests. This can be done with the open-service function:

port: open-service tcp://server:8000

This returns a port that can be supplied as an argument to the other functions:

result: do-service/action port [time]

send-service/action port [time] [print result]

When you are finished, you will want to provide a call to close the connection:

close-service port

It also turns out that you can use the send-service and login-service functions to open the port and keep it open. It is not necessary to use open-service only; however, open-service does allow the specification of special options to be used for the connection.

Service Request Functions

Here is a summary of the functions that are used for handling requests that are sent to a server. This implements the client API (application programming interface).

Function Description
do-service Sends a request to a service and waits for the result. Returns the result. The result will be simplified if possible (not all fields of the service response will be returned. More below).
send-service Sends a request to a service but does not wait for the result. Returns a req-message object that can be used by the other functions listed below. An optional callback block or function can also be specified.
wait-service Waits for the result from a prior send-service request and returns the result. The result is not simplified (contains all fields of the service response.)
query-service Returns the result from a prior send-service request, if it has been received. Otherwise, returns NONE. This function lets you poll for a result without waiting for it.
open-service Opens a service connection and returns a REBOL port that can be used with all of the other API functions. Allows the specification of additional service-related options, such as timeout durations or encryption methods and keys.
close-service Closes a service port opened earlier. No error will occur if the service has already closed.
abort-service Aborts a prior send-service request, if possible. Returns TRUE if it was successful. Returns FALSE if the request has already been sent to the service and cannot be aborted.
login-service This is a helper function that authenticates access to a service and begins a fully encrypted session. This function will not return until the login is complete (implemented as a synchronous function). Asynchronous login is implemented by sending a LOGIN request to the service with the send-service function.
logout-service Concludes the authenticated and encrypted session.

Service Requests

A service request is a "command" block that is sent to the service by the do-service and send-service functions.

Single Requests

To keep the above examples simple, only a TIME request was used.

result: do-service tcp://server:8000 [time]

Single requests can be sent like this within a block. Here the TIME word is the request command, and does not require any other arguments.

Some commands may require additional arguments. Those can also be provided within the block:

result: do-service tcp://server:8000 [info title]

result: do-service tcp://server:8000 [login "carl"]

The service request functions perform a COMPOSE/deep on the block. This allows you to insert evaluated terms within the block. For example:

result: do-service tcp://server:8000 [
    file/put %photo.jpg (read/binary %photo.jpg)
]

In this example, the photo.jpg is read as a binary file and is inserted into the request before it is sent.

Multiple Requests

The single request format is provided to keep simple code simple. The more general form allows multiple commands to be sent to a service within a single request. To do so, wrap each command in a block:

result: do-service tcp://server:8000 [
    [time]
    [info title]
    [file/put %photo.jpg (read/binary %photo.jpg)]
    [file/get %manual.txt]
]

This example sends the four commands to the service with a single request. The results will be returned in a similar format, see the "Service Results" section below.

The paths uses for the file/put and file/get commands indicates that they are found in the file service context, not in the default home context. See the "Service Contexts" section below.

Requests are Dialected

It is important to understand that service requests are REBOL dialects. That is, they do not call REBOL functions directly within the service, but are interpreted as a domain-specific sub-language.

This approach:

Service Results

A service result is one or more values returned as the result of a service request.

Single, Simple Results

The do-service function provides a simplification of results for singular requests. This can be seen in the following examples:

print do-service tcp://server:8000 [time]

==25-Mar-2005/12:08:12-8:00

print do-service tcp://server:8000 [info title]

=="Default REBOL Service"

In both cases, the result is returned as a single value. This is only true for the case when do-service is used with a single command request. Multiple command requests will return multiple result blocks, as described below.

We may want to re-evaluate whether we really want this mode of operation. It is similar in behavior to the LOAD function, which if not known to the user, can lead to errors. Contact me if you have an opinion on this issue.

Multiple Results

When multiple commands are sent to a service within a single request, their results are returned as multiple blocks. Here is an example of the result coming from a multiple command request:

probe do-service tcp://server:8000 [
    [time]
    [info title]
    [file/put %photo.jpg (read/binary %photo.jpg)]
    [file/get %manual.txt]
]

==[
    ok [time 25-Mar-2005/12:08:12-8:00]
    ok [info "Default REBOL Service"]
    ok [file/put %photo.jpg 20456]
    ok [file/get %manual.txt 25-Mar-2005 #{...}]
]

Each result begins with a success indicator (ok), followed by the result content. To make it easy to identify the results, they are always returned in the same order as the request, and each result includes the actual command as its first value.

It is possible, depending on special request options selected, for you to get mixed results that include both successful and failed results:

==[
    ok [time 25-Mar-2005/12:08:12-8:00]
    ok [info "Default REBOL Service"]
    fail [file/put not-allowed]
    fail [file/get not-exists]
]

In addition, the result block may include an optional header that summarizes the request results, including other details that go beyond the scope of this document.

Service Command Contexts

A server can incorporate multiple service command contexts. Each context provides a command set for a particular service. For example, the default (built-in) server provides these contexts:

The commands within a request can explicitly specify a context by using a path notation:

[admin/reset]
[file/get %photo.jpg]

Or, you can select a different context with the SERVICE command:

[service admin]
[add-user "Bob" "Robert Smith" ...]
[change-user 47 user "Jenny"]

Here the add-user and change-user commands are part of the admin context.

You can change the command context as many times as you want within the same request. In addition, the commands of the home context are always available within any context, as long as they have not been overwritten by commands in the current context. For example, this is valid:

[service admin]
[add-user "Bob" "Robert Smith" ...]
[change-user 47 user "Jenny"]
[time]

Here the TIME command is part of the home service, not the admin service.

Creating a Service

Now that you know how to access REBOL/Services, here are the basics for creating a service.

The Simplest Server

This example starts a server, but does not specify any of its own custom services. It will use the built-in services (home, admin, and file as described above.)

service: start-service tcp://:8000

The server uses direct TCP on port 8000. It will immediately begin processing requests.

Here is an actual example that you can type into a script and try:

REBOL [Title: "Example Server"]
service: start-service tcp://:8000
ask "PRESS ENTER TO QUIT"
stop-service service

The server will continue running until you press the enter key. When you press the enter key, it will shutdown the service.

Creating Your Own Service

To create your own service, all you need to do is create a service context with a command dialect.

A service context is a REBOL object that you put into a file that is loaded when your server starts. Here is an example service that implements a little bulletin board:

name: 'bbs-example
title: "Micro-BBS Service"
description: "A very simple BBS."

; Message format: [date author message]
messages: any [attempt [load %messages.r] copy []]

commands: [
    'put "Store a new message"
        arg: string! "Author" string! "Message" (
            write/append messages mold reduce [now arg/1 arg/2]
            result: true
        )
    | 'get "Get a messages by number"
        (result: copy [])
        some [
            arg: integer!
            (repend result [arg/1 pick messages arg/1])
        ]
    | 'list "List new message numbers since a given date or number"
        (result: copy []) [
            arg: date! (
                num: 1
                foreach msg messages [
                    if msg/1 >= arg/1 [append result num]
                    num: num + 1
                ]
            )
            | arg: integer! [
                repeat n length? skip messages arg/1 [
                    append result n
                ]
            ]
        ]
    ]
]

Note that the strings within the command dialect are used for documentation, and they are extracted when the service is initialized. The actual rule block passed to PARSE does not contain literal strings. There is also a special notation that can be used to include strings for international languages. More on this later.

The result and arg variables are defined locally within the function where the command block is evaluated. The result variable contains the value or block that is returned from the command and passed back to the client.

To put your new service online, you must specify it in the services block that is provided as an option to the start-service function.

service: start-service/options tcp://:8000 [
    services: [%bbs-example.r]
]

It is also possible to add the service to the server configuration by using the handle-service function, but this feature will be covered in a separate document.

About | Contact | PrivacyREBOL Technologies 2024