Module vibe.web.rest
Automatic high-level RESTful client/server interface generation facilities.
This modules aims to provide a typesafe way to deal with RESTful APIs. D's
interface
s are used to define the behavior of the API, so that they can
be used transparently within the application. This module assumes that
HTTP is used as the underlying transport for the REST API.
While convenient means are provided for generating both, the server and the client side, of the API from a single interface definition, it is also possible to use as a pure client side implementation to target existing web APIs.
The following paragraphs will explain in detail how the interface definition
is mapped to the RESTful API, without going into specifics about the client
or server side. Take a look at registerRestInterface
and
RestInterfaceClient
for more information in those areas.
These are the main adantages of using this module to define RESTful APIs
over defining them manually by registering request handlers in a
URLRouter
:
- Automatic client generation: once the interface is defined, it can be used both by the client side and the server side, which means that there is no way to have a protocol mismatch between the two.
- Automatic route generation for the server: one job of the REST module is to generate the HTTP routes/endpoints for the API.
- Automatic serialization/deserialization: Instead of doing manual
serialization and deserialization, just normal statically typed
member functions are defined and the code generator takes care of
converting to/from wire format. Custom serialization can be achieved
by defining
JSON
orstring
parameters/return values together with the appropriate@bodyParam
annotations. - Higher level representation integrated into D: Some concepts of the
interfaces, such as optional parameters or
in
/out
/ref
parameters, as well asNullable!T
, are translated naturally to the RESTful protocol.
The most basic interface that can be defined is as follows:
@path("/api/")
interface APIRoot {
string get();
}
This defines an API that has a single endpoint, 'GET /api/'. So if the
server is found at http://api.example.com, performing a GET request to
http://api.example.com/api/ will call the get()
method and send
its return value verbatim as the response body.
Endpoint generation
An endpoint is a combination of an HTTP method and a local URI. For each
public method of the interface, one endpoint is registered in the
URLRouter
.
By default, the method and URI parts will be inferred from the method
name by looking for a known prefix. For example, a method called
getFoo
will automatically be mapped to a 'GET /foo' request. The
recognized prefixes are as follows:
Prefix | HTTP verb |
---|---|
get | GET |
query | GET |
set | PUT |
put | PUT |
update | PATCH |
patch | PATCH |
add | POST |
create | POST |
post | POST |
Member functions that have no valid prefix default to 'POST'. Note that
any of the methods defined in HTTPMethod
are
supported through manual endpoint specifications, as described in the
next section.
After determining the HTTP method, the rest of the method's name is
then treated as the local URI of the endpoint. It is expected to be in
standard D camel case style and will be transformed into the style that
is specified in the call to registerRestInterface
, which defaults to
MethodStyle
.
Manual endpoint specification
Endpoints can be controlled manually through the use of @path
and
@method
annotations:
@path("/api/")
interface APIRoot {
// Here we use a POST method
@method(HTTPMethod .POST)
// Our method will located at '/api/foo'
@path("/foo")
void doSomething();
}
Manual path annotations also allows defining custom path placeholders that will be mapped to function parameters. Placeholders are path segments that start with a colon:
@path("/users/")
interface UsersAPI {
@path(":name")
Json getUserByName(string _name);
}
This will cause a request "GET /users/peter" to be mapped to the
getUserByName
method, with the _name
parameter receiving the string
"peter". Note that the matching parameter must have an underscore
prefixed so that it can be distinguished from normal form/query
parameters.
It is possible to partially rely on the default behavior and to only customize either the method or the path of the endpoint:
@method(HTTPMethod .POST)
void getFoo();
In the above case, as 'POST' is set explicitly, the route would be 'POST /foo'. On the other hand, if the declaration had been:
@path("/bar")
void getFoo();
The route generated would be 'GET /bar'.
Properties
@property
functions have a special mapping: property getters (no
parameters and a non-void return value) are mapped as GET functions,
and property setters (a single parameter) are mapped as PUT. No prefix
recognition or trimming will be done for properties.
Method style
Method names will be translated to the given 'MethodStyle'. The default
style is MethodStyle
, so that a function named
getFooBar
will match the route 'GET /foo_bar'. See
MethodStyle
for more information about the available
styles.
Serialization
By default the return values of the interface methods are serialized as a JSON text and sent back to the REST client. To override this, you can use the @resultSerializer attribute
struct TestStruct {int i;}
interface IService {
@safe:
@resultSerializer!(
// output_stream implements OutputRange
function (output_stream, test_struct) {
output_stream ~= serializeToJsonString(test_struct);
},
// input_stream implements InputStream
function (input_stream) {
return deserializeJson!TestStruct(input_stream .readAllUTF8());
},
"application/json")()
@resultSerializer!(
// output_stream implements OutputRange
function (output_stream, test_struct) {
output_stream ~= test_struct .i .to!string();
},
// input_stream implements InputStream
function (input_stream) {
TestStruct test_struct;
test_struct .i = input_stream .readAllUTF8() .to!int();
return test_struct;
},
"plain/text")()
TestStruct getTest();
}
class Service : IService {
@safe:
TestStruct getTest() {
TestStruct test_struct = {42};
return test_struct;
}
}
Serialization policies
You can customize the serialization of any type used by an interface
by using serialization policies. The following example is using
the Base64ArrayPolicy
, which means if X
contains any ubyte arrays,
they will be serialized to their base64 encoding instead of
their normal string representation (e.g. "[1, 2, 255]"
).
@serializationPolicy!(Base64ArrayPolicy)
interface ITestBase64
{
@safe X getTest();
}
Parameters
Function parameters may be populated from the route, query string, request body, or headers. They may optionally affect the route URL itself.
By default, parameters are passed differently depending on the type of
request (i.e., HTTP method). For GET and PUT, parameters are passed
via the query string (<route>?paramA=valueA[?paramB=...]
),
while for POST and PATCH, they are passed via the request body
as a JSON object.
The default behavior can be overridden using one of the following annotations, put as UDA on the relevant parameter:
@viaHeader("field")
: Will source the parameter on which it is applied from the request headers named "field". If the parameter isref
, it will also be set as a response header. Parameters declared asout
will only be set as a response header.@viaQuery("field")
: Will source the parameter on which it is applied from a field named "field" of the query string.@viaBody("field")
: Will source the parameter on which it is applied from a field named "field" of the request body in JSON format, or, if no field is passed, will represent the whole body. Note that in the later case, there can be no otherviaBody
parameters.
@path("/api/")
interface APIRoot {
// GET /api/header with 'Authorization' set
string getHeader(@viaHeader("Authorization") string param);
// GET /api/foo?param=...
string getFoo(@viaQuery("param") int param);
// GET /api/body with body set to { "myFoo": {...} }
string getBody(@viaBody("parameter") FooType myFoo);
// GET /api/full_body with body set to {...}
string getFullBody(@viaBody() FooType myFoo);
}
Further, how function parameters are named may affect the route:
Parameters with leading underscores (e.g.
_slug
) are also interpreted as a route component, but only in the presence of a@path
UDA annotation. See Manual endpoint specification above.Other function parameters do not affect or come from the path portion of the URL, and are are passed according to the default rules above: query string for GET and PUT; request body JSON for POST and PATCH.
Deprecated: If the first parameter is named
id
, this is interpreted as a leading route component. For example,getName(int id)
becomes/:id/name
.Note that this style of parameter-based URL routing is different than in many other web frameworks, where instead this example would be routed as
/name/:id
.See
Collection
for the preferred way to represent object collections in REST interfaces
Default values
Parameters with default values behave as optional parameters. If one is set in the interface declaration of a method, the client can omit a value for the corresponding field in the request and the default value is used instead.
Note that if default parameters are not evaluable by CTFE, compilation may fail due to DMD bug #14369 (Vibe.d tracking issue: #1043).
Aggregates
When passing aggregates as parameters, those are serialized differently depending on the way they are passed, which may be especially important when interfacing with an existing RESTful API:
- If the parameter is passed via the headers or the query, either implicitly or explicitly, the aggregate is serialized to JSON. If the JSON representation is a single string, the string value will be used verbatim. Otherwise the JSON representation will be used
- If the parameter is passed via the body, the datastructure is
serialized to JSON and set as a field of the main JSON object
that is expected in the request body. Its field name equals the
parameter name, unless an explicit
@bodyParam
annotation is used.
See Also
To see how to implement the server side in detail, jump to
registerRestInterface
.
To see how to implement the client side in detail, jump to
the RestInterfaceClient
documentation.
Functions
Name | Description |
---|---|
generateRestJSClient(output, settings)
|
Generates JavaScript code to access a REST interface from the browser. |
registerRestInterface(router, instance, settings)
|
Registers a server matching a certain REST interface. |
serveRestJSClient(settings)
|
Returns a HTTP handler delegate that serves a JavaScript REST client. |
Classes
Name | Description |
---|---|
RestInterfaceClient
|
Implements the given interface by forwarding all public methods to a REST server. |
RestInterfaceSettings
|
Encapsulates settings used to customize the generated REST interface. |
Structs
Name | Description |
---|---|
Collection
|
Models REST collection interfaces using natural D syntax. |
RestErrorInformation
|
Contains detailed informations about the error |
Aliases
Name | Type | Description |
---|---|---|
after
|
vibe
|
Allows processing the return value of a handler method and the request/response objects. |
before
|
vibe
|
Allows processing the server request/response before the handler method is called. |
RestErrorHandler
|
@safe void delegate(HTTPServerRequest, HTTPServerResponse, RestErrorInformation)
|
Type of the optional handler used to render custom replies in case of errors. |