Skip to content

HTTP services

Ignacio del Valle Alles edited this page Mar 20, 2017 · 25 revisions

Safety

Business is coded in action classes extending either from SafeAction, or UnsafeAction, and using POJOs to define input/output parameters.

According to rfc7231 section 4.2.1:

... Request methods are considered "safe" if their defined semantics are essentially read-only; i.e., the client does not request, and does not expect, any state change on the origin server as a result of applying a safe method to a target resource. Likewise, reasonable use of a safe method is not expected to cause any harm, loss of property, or unusual burden on the origin server...

SafeAction

This base clase is used to implement safe business logic, that is, logic that has no side-effects expected by the user. Results of these actions are cacheable, and both GET and POST request methods are allowed.

Example:

public class HelloWorldAction extends SafeAction<String, String> {
    @Override
    public Cacheable<String> execute(String name) {
        return Cacheable.forForever("Hello " + name + "!");
    }
}

UnsafeAction

On the other hand, this class is used to implement unsafe business logic, that has side-effects expected by the user, like for example, a state change in a business model.

Results of these actions are not cacheable, and only the POST request method is allowed.

Example:

public class CheckoutAction extends UnsafeAction<Void, Void> {
    @Override
    public void execute() throws Exception {
        // get shopping cart from HttpSession
        // start transaction
        // update stock
        // perform payment
        // end transaction
    }
}

Idempotence

An idempotent action is an action that can be executed many times with the same outcome.

Action idempotence is specified by the method HttpAction.isIdempotent().

Action idempotence determines the HTTP methods allowed in the request and is important in building a fault-tolerant API, since gives freedom to intermediary agents in the client-to-server communication, to retry such idempotent requests in case of a communication failure.

  • By definition SafeAction.isIdempotent() always returns true
  • By default UnSafeAction.isIdempotent() returns false, and this can be overridden in action implementation.

Streaming

Services can accept/return binary (non-JSON) payloads. This is possible due to the binary attachments support of JSON SPI.

Request

Services accepting binary payloads define inside their inputs, properties of type InputStream or MetaDataInputStream.

The HTTP request in this case has to be a multipart POST with a part (file upload) per stream. Input JSON data in the request must have a unique (string) value per stream matching the field name of the corresponding part. Parts corresponding to form fields must be present in the request body before file parts.

Implementation note: Given that the number of file parts is known in advance (can be inferred from the input data and schema), the framework only needs to save to disk the first files (see org.brutusin.rpc.upload.folder environment variable), being able to pass the last part stream directly to the action (if the action returns a binary payload all file parts are serialized, since duplex communication is not allowed in HTTP, and response writing must be started after finishing request reading). This way, last file part is recommended to be the largest one.

Request creation is simplified by the usage of the provided [Javascript API](Javascript API).

Click here to see a live example a service supporting binary uploading.

Response

Actions can return arbitrary (non-JSON) payloads to the client if their output class is an instance of StreamResult.

Click here to see a live example a service supporting binary downloading.

HTTP methods

Type SafeAction UnSafeAction
Idempotent GET*, POST, PUT POST, PUT*
Non-idempotent POST*

(*) recommended method

HTTP parameters

Parameter Description
jsonrpc JSON-RPC 2.0 request object

Status codes

Depending on the JSON-RPC response payload, the following status codes are used in the HTTP response:

HTTP response status code Case
200/304 if error is null or error.code equals -32001 (see Caching section for more details)
400 if error.code equals -32700 or -32602
403 if error.code equals -32000)
404 if error.code equals -32601)
405 if error.code equals -32002)
500 any other error

Content-Type header

The following content types can be returned:

  • For JSON payloads: application/json.
  • For non-JSON payloads: StreamResult.getContentType() is used.

Caching

HTTP caching allows to:

  1. Reduce incoming request by allowing client caches to serve cached responses during a specific period ot time.
  2. Provide cache validation mechanisms that allow avoiding re-transmission of unchanged data (conditional requests)

The framework automatically handles HTTP caching depending on these factors:

  • Action being safe.
  • Cacheable.getCachingInfo() value returned by SafeAction.execute()
  • Conditional request header If-None-Match present.

Algorithm:

  • If an error occurred (except -32000) or execution CachingInfo is null or action is unsafe, then the response is considered not cacheable and the following HTTP headers are returned:
Expires:Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control:max-age=0, no-cache, no-store
Pragma:no-cache
  • Else, if the action is safe, then the response is considered cacheable and the following HTTP headers are returned:
Expires:Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control:max-age={$maxAge}, {public|private}, [{no-store}]
Etag: W/"{$etag}

with Cache-Control values obtained from the execution CacheInfo, and etag computed as a MD5 hash of the payload (for JSON responses), or the last modification date (for binary responses). Additionally, if the request is conditional and etags match, then the response status code is set to 304 (NOT MODIFIED) and no payload is returned.

Note on POST requests: When a POST request is received, all responses allowing caching additionally contain a Content-Location header pointing to the url of the GET version, as explained in (rfc7231 4.3.3):

... POST caching is not widely implemented. For cases where an origin server wishes the client to be able to cache the result of a POST in a way that can be reused by a later GET, the origin server MAY send a 200 (OK) response containing the result and a Content-Location header field that has the same value as the POST's effective request URI...

Note on Expires header: An Expires header with an outdated value Thu, 01 Jan 1970 00:00:00 GMT is returned in every response regardless of the case. This action is performed in order to avoid HTTP 1.0 caches contradicting the directives of the Cache-Control header, at the cost of disabling caching for them.

An origin server might wish to use a relatively new HTTP cache control feature, such as the "private" directive, on a network including older caches that do not understand that feature. The origin server will need to combine the new feature with an Expires field whose value is less than or equal to the Date value. This will prevent older caches from improperly caching the response.

See rfc2616 sec14.9.3 for more details.