AppSuite:Introduction to the HTTP API

From Open-Xchange

Introduction

This document explains the basics of using the Open-Xchange HTTP API. This article describes general definitions and conventions which apply to all server modules. The (rather large) list of potential calls can be found at AppSuite:HTTP API.

Low level protocol

The client accesses the server through HTTP GET, POST and PUT requests. HTTP cookies are used for authentication and must therefore be processed and sent back by the client as specified by RFC 6265. The HTTP API is accessible at URIs starting with /api. Each server module has a unique name and its own sub-namespace with that name below /api, e. g. all access to the module "tasks" is via URIs starting with /api/tasks. This is different from the previous OX6, where it was /ajax (so you might keep that configuration, too, and run an OX6. Check the compatibility chart before doing so).

Text encoding is always UTF-8. Data is sent from the server to the client as text/javascript and interpreted by the client to obtain an ECMAScript object. The HTTP API uses only a small subset of the ECMAScript syntax. This subset is roughly described by the following BNF:

Value   ::= "null" | Boolean | Number | String | Array | Object
Boolean ::= "true" | "false"
Number  ::= see NumericLiteral in ECMA 262 3rd edition
String  ::= \"([^"\n\\]|\\["\n\\])*\"
Array   ::= "[]" | "[" Value ("," Value)* "]"
Object  ::= "{}" | "{" Name ":" Value ("," Name ":" Value)* "}"
Name    ::= [A-Fa-f][0-9A-Fa-f_]*

Numbers are the standard signed integer and floating point numbers. Strings can contain any character, except double quotes, newlines and backslashes, which must be escaped by a backslash. Control characters in strings (other than newline) are not supported. Whitespace is allowed between any two tokens. See JSON and ECMA 262, 3rd edition for the formal definition.

The response body consists of an object, which contains up to four fields as described in Response body. The field data contains the actual payload which is described in following chapters. The fields timestamp, error and error_params are present when data objects are returned, if an error occurred and if the error message contains conversion specifiers, respectively. Following sections describe the contents of these fields in more detail.

Response body
Name Type Value
data Value Payload of the response.
timestamp Timestamp The latest timestamp of the returned data (see Updates).
error String English error message. Present in case of errors.
error_params Array Replacement parameters for the error message.
error_id String Unique error identifier to help finding this error instance in the server logs.
code String Error code consisting of a three-letter category and a four-digit message number, separated by a dash.
category Number Category to which the error message belongs:
1 An error resulting from wrong or missing input from front-end (e.g. mandatory field missing).
2 An error strictly related to user configuration which denies requested operation.
3 An error related to insufficient permission settings.
4 A requested operation could not be accomplished because a needed resource is temporary down or missing (e.g. imap server rejects connection because of too many established connections).
5 A subsystem or third party service is down and therefore does not respond (e.g. database is down).
6 The underlying socket connection is corrupt, empty or closed. Only a temporary error that does not affect the whole system.
7 An internal java-related (runtime) exception.
8 A programming error which was caused by incorrect programm code.
9 A concurrent modification.
10 Error in system setup detected.
11 The requested operation could not be performed cause an underlying resource is full or busy (e.g. IMAP folder exceeds quota).
12 The given data could not be stored into the database because an attribute contains a too long value.
13 Action was at least partially successful, but a condition occurred that merited a warning

Data from the client to the server can be sent in several formats. Small amounts of data are sent as application/x-www-urlencoded in query parameters in the request URI. For POST requests, some or all parameters may be sent in the request body instead of in the URI using any valid encoding for POST requests. Alternatively, some requests specify that data is sent as text/javascript in the body of a PUT request. The format of the request body for PUT requests is the same as for sending data from the server to the client, except that the payload is sent directly, without being wrapped in another object.

When updating existing data, the client sends only fields that were modified. To explicitly delete a field, the field is sent with the value null. For fields of type String, the empty string "" is equivalent to null.

Error handling

If the session of the user times out, if the client doesn't send a session ID or if the session for the specified session ID can not be found then the server returns the above described response object, that contains an error code and an error message. If the request URI or the request body is malformed or incomplete then the server returns the reponse object with an error message, too. In case of internal server errors, especially Java exceptions, or if the server is down, it returns the HTTP status code 503, Service Unavailable. Other severe errors may return other HTTP status values.

Application errors, which can be caused by a user and are therefore expected during the operation of the groupware, are reported by setting the field error in the returned object, as described in Response body. Since the error messages are translated by the client, they can not be composed of multiple variable parts. Instead, the error message can contain simplified printf()-style conversion specifications, which are replaced by elements from the array in the field error_params. If error_params is not present, no replacement occurs, even if parts of the error message match the syntax of a conversion specification.

A simplified conversion specification, as used for error messages, is either of the form %s or %n$s, where n is a 1-based decimal parameter index. The conversion specifications are replaced from left to right by elements from error_params, starting at the first element. %s is replaced by the current element and the current index is incremented. %n$s is replaced by the nth element and the current index is set to the (n + 1)th element.

Some error message contain data sizes which must be expressed in Bytes or Kilobytes etc., depending on the actual value. Since the unit must be translated, this conversion is performed by the client. Unfortunately, standard printf()-style formatting does not have a specifier for this kind of translation. Therefore, the conversion specification for sizes is the same as for normal strings, and the client has to determine which parameters to translate based on the error code. The current error codes and the corresponding size parameters are listed in Data size parameters

Data size parameters
Error code Parameter indices
CON-0101 2, 3
FLS-0003 1, 2, 3
MSG-0065 1, 3
MSG-0066 1
NON-0005 1, 2


Detailed Exception Data (Preliminary)

Starting with some upcoming version application errors will be returned in the field "exception" in a response. The exception field will contain a JSON array containing objects that have the format specified in the following table:

Exception Object
Name Type Value
code String Error code consisting of a three-letter category and a four-digit message number, separated by a dash.
category Number Category to which the error message belongs:
1 An error resulting from wrong or missing input from front-end (e.g. mandatory field missing).
2 An error strictly related to user configuration which denies requested operation.
3 An error related to insufficient permission settings.
4 A requested operation could not be accomplished because a needed resource is temporary down or missing (e.g. imap server rejects connection because of too many established connections).
5 A subsystem or third party service is down and therefore does not respond (e.g. database is down).
6 The underlying socket connection is corrupt, empty or closed. Only a temporary error that does not affect the whole system.
7 An internal java-related (runtime) exception.
8 A programming error which was caused by incorrect programm code.
9 A concurrent modification.
10 Error in system setup detected.
11 The requested operation could not be performed cause an underlying resource is full or busy (e.g. IMAP folder exceeds quota).
12 The given data could not be stored into the database because an attribute contains a too long value.
13 Action was at least partially successful, but a condition occurred that merited a warning
message String Localized error message.
parameters Array Replacement parameters for the message. What are they good for if the message is already localized? -- Viktor Pracht - 2009-04-24
logMessage String Technical error message useful for debugging the problem.
exceptionId String Unique error identifier to help finding this error instance in the server logs.
help String Localized help text, telling the user how he may resolve the problem.
session String The session id of the session in which this exception was caused. When can this be not the current session? -- Viktor Pracht - 2009-04-24
user Number The id of the user having caused the exception. When can this be not the current user? -- Viktor Pracht - 2009-04-24
context Number The context id of the context in which the exception happened. When can this be not the current context? -- Viktor Pracht - 2009-04-24
details Object A JSON Object containing extra information for debugging purposes.
application String Application ID of the subsystem having thrown this exception.
stacktrace Array An array of Strings containing the error trace. Note: This can be turned off in the backend, in which case an empty array will be returned.

Date and time

Dates without time are transmitted as the number of milliseconds between 00:00 UTC on that date and 1970-01-01 00:00 UTC. Leap seconds are ignored, therefore this number is always an integer multiple of 8.64e7.

Because ECMAScript Date objects have no way to explicitly specify a timezone for calculations, timezone correction must be performed on the server. Dates with time are transmitted as the number of milliseconds since 1970-01-01 00:00 UTC (again, ignoring leap seconds) plus the offset between the user's timezone and UTC at the time in question. (See the Java method java.util.TimeZone.getOffset(long)). Unless optional URL parameter timezone is present. Then dates with time are transmitted as the number of milliseconds since 1970-01-01 00:00 UTC (again, ignoring leap seconds) plus the offset between the specified timezone and UTC at the time in question.

For some date and time values, especially timestamps, monotonicity is more important than the actual value. Such values are transmitted as the number of milliseconds since 1970-01-01 00:00 UTC, ignoring leap seconds and without timezone correction. If possible, a unique strictly monotonic increasing value should be used instead, as it avoids some race conditions described below.

This specification refers to these three interpretations of the type Number as separate data types. The types are described in Date and time types.

Date and time types
Type Time Timezone Comment
Date No UTC Date without time.
Time Yes User Date and time.
Timestamp Yes UTC Timestamp or unique sequence number.

Updates

To allow efficient synchronization of a client with changes made by other clients and to detect conflicts, the server stores a timestamp of the last modification for each object. Whenever the server transmits data objects to the client, the response object described in Response body includes the field timestamp. This field contains a timestamp value which is computed as the maximum of the timestamps of all transmitted objects.

When requesting updates to a previously retrieved set of objects, the client sends the last timestamp which belongs to that set of objects. The response contains all updates with timestamps greater than the one specified by the client. The field timestamp of the response contains the new maximum timestamp value.

If multiple different objects may have the same timestamp values, then a race condition exists when an update is processed between two such objects being modified. The first, already modified object will be included in the update response and its timestamp will be the maximum timestamp value sent in the timestamp field of the response. If the second object is modified later but gets the same timestamp, the client will never see the update to that object because the next update request from the client supplies the same timestamp value, but only modifications with greater timestamp values are returned.

If unique sequence numbers can't be used as timestamps, then the risk of the race condition can be at least minimized by storing timestamps in the most precise format and/or limiting update results to changes with timestamp values which are measurably smaller than the current timestamp value.

Editing

Editing objects is performed one object at a time. There may be multiple objects being edited by the same client simulataneously, but this is achieved by repeating the steps required for editing a single object. There is no batch edit or upload command.

To edit an object, a client first requests the entire object from the server. The server response contains the timestamp field described in the previous section. For in-place editing inside a view of multiple objects, where only already retrieved fields can be changed, retrieving the entire object is not necessary, and the last timestamp of the view is used as the timestamp of each object in it.

When sending the modified object back to the server, only modified fields need to be included in the sent object. The request also includes the timestamp of the edited object. The timestamp is used by the server to ensure that the object was not edited by another client in the meantime. If the current timestamp of the object is greater than the timestamp supplied by the client, then a conflict is detected and the field error is set in the response. Otherwise, the object gets a new timestamp and the response to the client is empty.

If the client displays the edited object in a view together with other objects, then the client will need to perform an update of that view immediately after successfully uploading an edited object.

File uploads

File uploads are made by sending a POST request that submits both the file and the needed fields as parts of a request of content-type “multipart/form-data” or “multipart/mixed”. The file metadata are stored in a form field “file” (much like an <input type=”file” name=”file” /> would do). In general a call that allows file uploads via POST will have a corresponding call using PUT to send object data. The JSON-encoded object-data that is send as the body of a corresponding PUT call is, when performed as a POST with file uploads, put into the request parameter “json”.

Since the upload is performed directly by the browser and is not an Ajax call, the normal callback mechanism for asynchronous Javascript calls cannot be used to obtain the result. For this reason the server responds to these POST calls with a complete HTML page that performs the callback and should not be displayed to the user. The HTML response is functionally equivalent to:

<html>
    <head>
        <script type="text/javascript">
            (owner || parent).callback_action({json});
        </script>
    </head>
</html>

The placeholders {json} is replaced by the response with the timestamp that would be expected from the corresponding PUT method. The placeholder action is replaced by the value of the parameter action of the request (except for the import bundle, which is named "import" instead of the action name for legacy purposes). The content-type of the answer is text/html.

Non-browser clients don't need to interpret HTML or JavaScript. The JSON data can be recognized by the outermost ({ and }), where the inner braces are part of the JSON value. For example, the regular expression \((\{.*\})\) captures the entire JSON value in its first capturing group.