Getting Started with Restless

Restless is a super lightweight HTTP server and client API. One of the main aims of Restless is to allow easy exposure of software components as HTTP resources at runtime. Restless is very simple to embed into other applications. Just add the restless jar to your class path and you're good to go.

Core API

The HttpPeer class is tha main work horse of Restless and the way into deploying objects and using the client side.

Server Side API

On the server side HttpPeer runs a server thread when you call the open() method. For example:
HttpPeer peer = new HttpPeer();
peer.open();
    
A HttpPeer can also be supplied with a PeerProperties object that allows you to set the port number (defaults to 8080), set a security context, set the number of threads to recive client connections and set the number of times a client connection is reused, among other things.
PeerProperties props = new PeerProperties();
props.setPort(1234);
HttpPeer peer = new HttpPeer(props);
peer.open();
    
If the port is already taken, then Restless will try a further 10 ports, incrementing the port number on each attempt. So if you can't find your server, make sure the port you gave it was not already taken. It may have started up on a higher port number!

To make an object available over HTTP requires creating an implementation of a Target object. A Target is an entity that can return Resource objects on request, typically by matching a resource against an HTTP request path. Both Resource objects and Target objects extend the Locatable interface:

public interface Locatable {
    public Path getPath();
}
    

A Path object encapsulates a request path. It represents a forward-slash delimited set of components. You can give a target anything to be its path but if you include a query string (string starting with '?') or a fragment (string starting with '#') then these will be discarded. If you create a path object with illegal URL characters, then you should be sure to encode the path before exposing it as part of a URL, for example as a link in an HTML page. You can use the static methods of the UrlUtils class to perform this, for example UrlUtils.encodePath(getPath()) and UrlUtils.encodeEndpoint(String endpoint) for a complete HTTP URL. Equivalent decode methods also exist.

Equality between paths is determined by the list of path components alone. Therefore a path defined using the string '/mypath', 'mypath/' and 'mypath' are all considered equal.

A Target has methods that are called depending on the HTTP method being used by the client. It also contains the getResource(RequestContext) method that is called before most processing happens. If a Target returns null to this method, Restless will issue a 404 not found response to the client. Therefore a Target is responsible for managing Resources.

public interface Target extends Locatable {


    /**
     * get the resource that is the target of a request.
     *
     * @param context
     * @return
     */
    public Resource getResource(RequestContext context) throws RequestProcessException;

    /**
     * called after preprocessing a HTTP GET request
     *
     * @param context
     */
    public void onGet(RequestContext context) throws RequestProcessException;

    /**
     * called after preprocessing a HTTP PUT request
     *
     * @param context
     */
    public void onPut(RequestContext context) throws RequestProcessException;

    /**
     * called after preprocessing a HTTP POST request
     *
     * @param context
     */
    public void onPost(RequestContext context) throws RequestProcessException;

    /**
     * called after preprocessing a HTTP DELETE request
     *
     * @param context
     */
    public void onDelete(RequestContext context) throws RequestProcessException;


    /**
     * called after preprocessing a HTTP OPTIONS request
     *
     * @param context
     */
    public void onOptions(RequestContext context) throws RequestProcessException;

    /**
     * get the target properties associated with this target.
     * @return
     */
    public TargetProperties getTargetProperties();
}
    

A Target is registered with the peer using the addTarget(Target) method.

Resource objects provide data in the form of Streamable objects. There are various implementations of Streamable, for example to wrap bytes, URLs, XML Documents, Java Serialized Objects, Files and Strings. A resource has a default Streamable and can have a list of other Streamables mapped to different mime types. In REST speak, these are the resource 'representations'. A resource also has a set of Allowed HTTP methods associated with it, a last modified date and several other properties useful at botht the server and client side.

The RequestContext class is the object that is passed to Targets in order to evaluate which resource if any, to return to the client. The RequestContext has a number of fields to maintain the state of a particular client request. The context is passed through the processor chain in the HttpPeer during request execution. Specifically, when a request arrives at the server, first the relevant Target must be found. This is done by finding the registered target with the longest matching path to the request path. Once the Target has been located, the peer calls the getResource(RequestContext) on the Target. The target must now determine which resource to return. Typically this is done using the RequestContext.getRequestTarget() method. This returns a Path object against which the paths of known resources can be evaluated.

For a full list of the properties and associated methods of a RequestContext please refer to the source code. The most important ones for the server are as follows. Each of these properties has associated getter and setter methods.

There are several basic implementats of Target availabale:

Server Examples

Below is a very simple server example that shows a number of features.

public class SimpleServer extends MemoryTarget {

    public SimpleServer() {
        super("simple");
    }

    public void run() throws IOException, RequestProcessException {
        HttpPeer peer = new HttpPeer();
        RequestPreprocessor rp = getTargetProperties().getPreprocessor();
        rp.addDirective(new PreprocessDirective("**",
                                RequestPreprocessor.Directive.APPEND_SLASH,
                                RequestPreprocessor.Directive.APPEND_INDEX));
        peer.addTarget(this);
        StreamableString ss = new StreamableHtml("<html><body><p>HELLO</p></body></html>");
        store.put(new Resource(getPath().append("index.html"), ss));
        peer.open();
    }

    public static void main(String[] args) throws IOException, RequestProcessException {
        new SimpleServer().run();
    }
}
    

The server extends the MemoryTarget class. This gives it access to the MapStore object that stores Resource objects in memory. In the run method, it creates a new HttpPeer object. Then it gets hold of its TargetProperties. These contain a RequestPreprocessor object which allows some basic URL manipulation to be done before the Target's getResource() method is called. Currently it supports appending slashes to request paths and appending index.html to request paths. The former results in a 301 permanently moved redirect to the client. The second does not change the client's view of the endpoint, but internally adds the index.html string to the path. The '**' string is the request paths to match to. This uses ant style matching, so '**' matches all directory structures. The '?' - any single character, and '*' - zero or more characters is also supported.

The class then creates a simple Streamable object from an HTML string and stores this with the path of the server class appended by the String 'index.html'. The path has been set to 'simple' in its constructor. The result is that the client can access the Resource without typing in 'index.html', rather the request will directed to 'index.html' if it is not there in the request path.

Finally, the class calls the open() method on the peer to start the server.

public class UrlTargetServer extends UrlTarget {

    public UrlTargetServer() {
        super("simple", UrlTarget.class.getResource("/favicon.ico"));
    }

    public void run() throws IOException, RequestProcessException {
        HttpPeer peer = new HttpPeer();
        peer.addTarget(this);
        peer.open();
    }

    public static void main(String[] args) throws IOException, RequestProcessException {
        new UrlTargetServer().run();
    }
}

In the above example, the class extends the UrlTarget class. This class looks for resources in file and jar URLs passed to it. The constructor of the example class passes the URL of a known resource on the class path. The super class will extract either parent directory, if the URL represents a file, or the containing jar file if the URL is a jar URL. This extracted URL then becomes the search path for resources. This URL modification can be switched off using the UrlTarget constructors that take a boolean.

So in the above exammple, any resource contained as a subset of the passed in URL will be returned to the client if the paths match.

public class PostServer extends MemoryTarget {

    public PostServer() {
        super("simple");
    }

    public void run() throws IOException, RequestProcessException {
        HttpPeer peer = new HttpPeer();
        peer.addTarget(this);
        // If this is not specified, then files will go into a temp folder.
        TargetProperties props = getTargetProperties();
        File out = new File("files");
        out.mkdirs();
        props.setOutputDirectory(out);
        store.put(new Resource(getPath(), Http.Method.POST));
        peer.open();
    }

    public void onPost(RequestContext context) throws RequestProcessException {
        Streamable s = context.getRequestEntity();
        try {
            s.writeTo(System.out);
        } catch (IOException e) {
            e.printStackTrace();
        }
        context.setResponseEntity(new StreamableHtml("<html><body><p>Cheers</p></body></html>"));
    }

    public static void main(String[] args) throws IOException, RequestProcessException {
        new PostServer().run();
    }
}

The above example shows a server that responds to POST methods. It overrides the onPost method of MemoryTarget class, which by default does nothing. First it creates a peer, and adds itself as a target to it. Then is uses its TargetProperties to set an output file for uploaded data. It then stores a new resource at its own path which only allows POST operations.

The onPost method extracts the Streamable request entity and writes it to system out. It then returns a simple HTML string to the client.

Very small data may not get saved to the output directory. Whether this happens is dependent on the buffer size of the request context. The default buffer size is 32Kb. To ensure all data, no matter how small is written to file you can call the setForceWriteToFile(true) on the target properties object.

NOTE: To force writing out to file on the client side, you can set the buffer size of the request context directly: context.setBufferSixe(0);

Client Side API

The Client side uses the same basic API as the server side. You create an HttpPeer object and ask it to execute an HTTP method. Unlike the server side, you do not need to open() the peer. Instead you call one of get, put, post, delete or options methods. Each one of these takes a RequestContext object.

On the server a RequestContext is created for you. On the client side, you must create one explicitly. The easiest way to do this is to use the constructor that takes the full URL of your request:

RequestContext context = new RequestContext("http://localhost:8080/simple");
    
If you are performing a GET, or a DELETE then nothing else needs to be added because you are not sending any data. If you are performing a POST or a PUT, then you need to add a payload in the form of a Streamable object. You can use the Resource class to do this, or set the request entity directly on the context:
StreamableObject so = new StreamableObject(new Date());
context.setRequestEntity(so);

    OR

StreamableObject so = new StreamableObject(new Date());
context.setResource(new Resource(so));
If you do not use a Resource then one will be created for you and set its content based on the request entity Streamable you provided. Restless will insert any relevant data into the resource during the request process, for example if the server inserts a last modified header, then this will become the last modified value of the resource. Likewise if a redirect occurs, then the resource's location field will be set to the redirect value.

Using the resource method enables you to reference existing resources that you may have stored locally. For example, if you previously downloaded a resource with a last modified date, then Restless will use the If-Modified-Since header to perform a conditional get.

The result an HTTP request is a Response object. This contains the Resource associated with the request as well as the status code outcome, and the response entity form the server.

Client Examples

public class SimpleClient {

    public void run() throws IOException {
        HttpPeer peer = new HttpPeer();
        RequestContext c = new RequestContext("http://localhost:8080/simple");
        c.setKeepAlive(true);

        Response resp = peer.get(c);
        Streamable s = resp.getResponseEntity();
        if (s == null) {
            System.out.println("Client.run streamable is null!!");
        } else {
            s.writeTo(System.out);
        }
    }

    public static void main(String[] args) throws IOException {
        new SimpleClient().run();
    }
}

The above example shows a simple GET request. The only thing of note here, is that the client sets the keep alive status of the request context. This means it will ask the server to keep the socket open for futher requests. Whether this is honoured is up to the server. A Restless server will keep the socket open for up to 10 seconds by default and reuse a connection for up to 20 requests.

public class RangeClient {

    public void run() throws IOException {
        HttpPeer peer = new HttpPeer();
        RequestContext c = new RequestContext("http://localhost:8080/simple/simon/chunks.txt");
        List<ByteRange> ranges = new ArrayList<ByteRange>();
        ranges.add(new ByteRange(16, 31));
        c.setRequestRanges(ranges);
        Response resp = peer.get(c);
        Streamable s = resp.getResponseEntity();
        if (s == null) {
            System.out.println("Client.run streamable is null!!");
        } else {
            s.writeTo(System.out);
        }
    }

    public static void main(String[] args) throws IOException {
        new RangeClient().run();
    }
}

The above example shows a client requesting a only a range of bytes from the server. NOTE: multiple range requests are currently not supported at the server side.

public class PostClient {

    public void run() throws IOException, ClassNotFoundException {
        HttpPeer peer = new HttpPeer();
        RequestContext c = new RequestContext("http://localhost:8080/simple");
        StreamableObject so = new StreamableObject(new Date());
        c.setRequestEntity(so);
        Response resp = peer.post(c);
        if (resp.getResponseEntity() != null) {
            resp.getResponseEntity().writeTo(System.out);
        }
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        new PostClient().run();
    }
}

The above example shows a client POSTing a java.util.Date object to a server. It uses the request entity of the context directly rather than using a resource object to send the payload.

Hopefull this has given you a flavour of Restless. More documentation will appear soon, describing further features of Restless.

Thanks and have fun.


Powered by wine