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.
HttpPeer
class is tha main work horse of Restless and the way into deploying objects and using
the client 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.
context.setResponseEntity(resource.getStreamable())
.
Another variation on this method, is one that takes a list of mime types specified by the client. The
list is ordered according to the client's preferred mime type: context.setResponseEntity(resource.getStreamable(),
context.getAcceptedMedia())
There are several basic implementats of Target
availabale:
MemoryTarget
- this stores Resource objects in a Map in memory.DirectoryTarget
- this takes a directory and looks for matching files in the directory.UrlTarget
- this takes a list of file or jar URLs and looks for matching resources in these.TargetResource
- this is a Target that is also a Resource.
This stores resources in a PathTree
object. This allows hierarchical searching resources. And of
course
the tree can also contain further TargetResource
objects allowing for quite complex tree structures
and
delegation
of resource resolution.
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);
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.
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.