mirror of
https://github.com/servalproject/serval-dna.git
synced 2024-12-23 15:02:27 +00:00
34e2e8d4bc
The REST Rhizome import request now requires the 'id' and 'version' query parameters to either both be supplied or neither, and fails if they do not match the manifest that is supplied in the request body. Added a test case for this. Added a test case to ensure that if the 'id' and 'version' query parameters cause a hit (already in store) then the response is sent immediately without reading the request body. Improve the documentation for the REST Rhizome import request.
651 lines
26 KiB
Markdown
651 lines
26 KiB
Markdown
REST API
|
|
========
|
|
[Serval Project][], October 2017
|
|
|
|
Introduction
|
|
------------
|
|
|
|
The [Serval DNA][] daemon that runs on every node in a [Serval Mesh network][]
|
|
gives applications access to the network through two main classes of [API][]:
|
|
|
|
* the [MDP API][MDP] and [MSP API][MSP] provide "traditional" packet and
|
|
stream transport, allowing applications to send and receive Serval network
|
|
packets to and from nearby nodes with latencies of up to several seconds;
|
|
|
|
* the various [HTTP REST][] APIs provide applications with access to Serval
|
|
services:
|
|
|
|
- [Keyring REST API][] -- local identity management by querying and
|
|
modifying the [Keyring][]
|
|
|
|
- [Rhizome REST API][] -- store-and-forward (high latency) content
|
|
distribution by extracting and inserting content in the local [Rhizome][]
|
|
store
|
|
|
|
- [MeshMS REST API][] -- secure one-to-one messaging by reading and writing
|
|
the local cache of [MeshMS][] messages
|
|
|
|
This document describes the features in common to all the [HTTP REST][] APIs.
|
|
|
|
Protocol and port
|
|
-----------------
|
|
|
|
The Serval DNA [HTTP REST][] API is an [HTTP 1.0][] server that only accepts
|
|
requests on the loopback interface (IPv4 address 127.0.0.1), TCP port 4110. It
|
|
rejects requests that do not originate on the local host, by replying
|
|
[403 Forbidden](#403-forbidden).
|
|
|
|
Security
|
|
--------
|
|
|
|
The REST API uses plain HTTP *without* encryption. REST requests and responses
|
|
are not carried over any physical network link, only local (“logical”) links
|
|
between processes, so there is no risk of remote eavesdropping. The only
|
|
potential threat comes from hostile local processes.
|
|
|
|
Operating system kernels such as Linux (Android, Ubuntu) and Darwin (Apple)
|
|
prevent normal processes from accessing the traffic on local sockets between
|
|
other processes. To attack Serval DNA and its clients, a local process on the
|
|
local host would have to gain super-user privilege (eg, through a privilege
|
|
escalation vulnerability). A super-user process would have many ways to attack
|
|
Serval DNA and its clients, much more effective than intercepting their
|
|
communications, so encrypting client-server communications would offer no
|
|
protection whatsoever.
|
|
|
|
Authentication
|
|
--------------
|
|
|
|
Clients of the HTTP REST API must authenticate themselves using [Basic
|
|
Authentication][]. This narrows the window for opportunistic attacks on the
|
|
server's HTTP port by malicious applications that scan for open local ports to
|
|
exploit. Any process wishing to use the REST API must supply valid
|
|
authentication credentials (name/password), or will receive a [401
|
|
Unauthorized](#401-unauthorized) response.
|
|
|
|
Client applications obtain their REST API credentials via a back channel
|
|
specific to their particular platform. This delegates the exercise of handing
|
|
out credentials to the application layer, where users can (usually) exercise
|
|
their own discretion. For example, on Android, a client app sends an
|
|
[Intent][] to the [Serval Mesh][] app requesting a Serval REST credential, and
|
|
will receive a reply only if it possesses the right Android [Permission][].
|
|
When users install or run the client app, Android informs them that the app
|
|
requests the "Serval Network" permission, and users may allow or deny it.
|
|
|
|
As a fall-back mechanism, created primarily to facilitate testing, HTTP REST
|
|
API credentials can be [configured][] using configuration options of the form:
|
|
|
|
api.restful.users.USERNAME.password=PASSWORD
|
|
|
|
PASSWORD is a cleartext secret, so the Serval DNA configuration file must be
|
|
protected from unauthorised access or modification by other apps. That makes
|
|
this mechanism unsuitable for general use.
|
|
|
|
Request
|
|
-------
|
|
|
|
An HTTP REST request is a normal [HTTP 1.0][] [GET](#get) or [POST](#post):
|
|
|
|
### GET
|
|
|
|
A **GET** request consists of an initial "GET" line containing the *path* and
|
|
*HTTP version*, followed by zero or more header lines, followed by a blank
|
|
line. As usual for HTTP, all lines are terminated by an ASCII CR-LF sequence.
|
|
|
|
For example:
|
|
|
|
GET /restful/keyring/identities.json?pin=1234 HTTP/1.0
|
|
Authorization: Basic aGFycnk6cG90dGVy
|
|
Accept: */*
|
|
|
|
|
|
GET requests only accept parameters as [percent encoded][] [query parameters][]
|
|
in the *path*.
|
|
|
|
### POST
|
|
|
|
A **POST** request has a similar structure to a GET request except that the
|
|
first word of the first line is "POST", and there may be a body of content
|
|
following the blank line that ends the header.
|
|
|
|
POST requests accept parameters as [percent encoded][] [query parameters][] in
|
|
the *path* and also as a request body with a [Content-Type](#content-type-header)
|
|
of [multipart/form-data][]. These two kinds of parameters are not exclusive; a
|
|
POST request may contain a mixture of both.
|
|
|
|
A POST request may also include the following headers as described below:
|
|
* [Content-Length](#content-length-header)
|
|
* [Content-Type](#content-type-header)
|
|
* [Range](#range-header)
|
|
* [Transfer-Encoding](#transfer-encoding-header)
|
|
* [Expect](#expect-header)
|
|
|
|
### Content-Length header
|
|
|
|
In a [POST](#post) request, the **Content-Length** header gives the exact
|
|
number of bytes (octets) in the request's body, which must be correct. Serval
|
|
DNA will not process a request until it receives Content-Length bytes, so if
|
|
Content-Length is too large, the request will suspend and eventually time out.
|
|
Serval DNA will ignore any bytes received after it has read Content-Length
|
|
bytes, so if Content-Length is too small, the request body will be malformed.
|
|
|
|
Serval DNA treats a missing Content-Length header the same as a Content-Length
|
|
of zero; it will not attempt to read the request body so any body content will
|
|
be ignored if sent.
|
|
|
|
### Content-Type header
|
|
|
|
In a [POST](#post) request, the **Content-Type** header gives the [Internet
|
|
Media Type][] of the body.
|
|
|
|
A missing Content-Type header in a [POST](#post) request will cause a [400 Bad
|
|
Request](#400-bad-request) response. An unsupported content type will cause a
|
|
[415 Unsupported Media Type](#415-unsupported-media-type) response.
|
|
|
|
#### multipart/form-data
|
|
|
|
Serval DNA [POST](#post) requests that take their parameters in the request
|
|
body accept the **[multipart/form-data][]** content type.
|
|
|
|
The multipart **boundary** must specify a string consisting of at most 70 ASCII
|
|
characters that does not occur anywhere within the content of any part, and is
|
|
used to delimit the parts of the request body. Typically this string is
|
|
generated using a large random number encoded into printable ASCII, eg:
|
|
|
|
Content-Type: multipart/form-data;boundary=081d31d4c3d23014
|
|
|
|
Each form part introduces one named parameter, and consists of its own header
|
|
section, followed by a blank line (a CR-LF immediately following the CR-LF that
|
|
terminates the last header), followed by a body that contains the parameter's
|
|
content, followed by a CR-LF. Every part must have a [Content-Disposition][]
|
|
header that gives the parameter's name, and may have an optional
|
|
[Content-Type][] header.
|
|
|
|
The following example passes two parameters to a request, called `first` and
|
|
`second`, the first is a single line of plain text terminated by a LF, the
|
|
second is an HTML document with no terminating LF:
|
|
|
|
POST /an/example/request HTTP/1.0
|
|
Content-Type: multipart/form-data;boundary=4aceafdc5cc7295d
|
|
|
|
--4aceafdc5cc7295d
|
|
Content-Disposition: form-data; name=first
|
|
Content-Type: text/plain; charset=utf-8
|
|
|
|
Hello, world!
|
|
|
|
--4aceafdc5cc7295d
|
|
Content-Disposition: form-data; name=second; filename="hello_world.html"
|
|
Content-Type: text/html; charset=utf-8
|
|
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>The second parameter</title>
|
|
</head>
|
|
<body>
|
|
<h1>Hello world!</h1>
|
|
</body>
|
|
</html>
|
|
--4aceafdc5cc7295d--
|
|
|
|
#### application/x-www-form-urlencoded
|
|
|
|
Serval DNA [POST](#post) requests *do not support* the
|
|
[application/x-www-form-urlencoded][] content type, although some may do so in
|
|
future.
|
|
|
|
[application/x-www-form-urlencoded][] is the predecessor web standard to
|
|
[multipart/form-data](#multipart-form-data). It has the benefit of being
|
|
simpler for requests that take short, mainly textual parameters, but is very
|
|
inefficient for encoding large binary values and does not provide any means to
|
|
associate metadata such as content-type, encoding and file name with individual
|
|
parameters.
|
|
|
|
#### text/plain
|
|
|
|
Serval DNA [POST](#post) requests that take plain text parameters use the
|
|
**text/plain** content type in the parameter's [form part](#multipart-form-data).
|
|
See the [MeshMS send request][] for an example.
|
|
|
|
The only supported charset is **utf-8**; a missing or different charset will
|
|
cause a [415 Unsupported Media Type](#415-unsupported-media-type) response.
|
|
The correct [Content-Type](#content-type-header) header is:
|
|
|
|
Content-Type: text/plain; charset=utf-8
|
|
|
|
#### serval/sid
|
|
|
|
Serval DNA [POST](#post) requests that take [SID][] parameters use the
|
|
non-standard **serval/sid** content type within the parameter's [form
|
|
part](#multipart-form-data). See the [Rhizome insert request][] for an
|
|
example.
|
|
|
|
At present only the **hex** format is supported, and must be explicitly
|
|
specified. A missing or different format will cause a [415 Unsupported Media
|
|
Type](#415-unsupported-media-type). The correct [Content-Type](#content-type-header)
|
|
header is:
|
|
|
|
Content-Type: serval/sid; format=hex
|
|
|
|
Hex format parameter values may only contain ASCII characters from the set
|
|
`0123456789ABCDEFabcdef`; any other character (such as a trailing newline) will
|
|
cause a [400 Bad Request](#400-bad-request) response.
|
|
|
|
In future other formats may be supported, such as Base-64, 7-bit binary, or
|
|
8-bit binary.
|
|
|
|
### Range header
|
|
|
|
[HTTP 1.1 Range][] retrieval is partially supported. In a [POST](#post)
|
|
request, the **Range** header gives the start and end, in byte offsets, of the
|
|
resource to be returned. The server may respond with exactly the range
|
|
requested, in which case the response status code will be [206 Partial
|
|
Content](#206-partial-content), or it may ignore the Range header and respond
|
|
with the entire requested resource.
|
|
|
|
For example, the following header asks that the server omit the first 64 bytes
|
|
and send only the next 64 bytes (note that ranges are inclusive of their end
|
|
byte number):
|
|
|
|
Range: bytes=64-127
|
|
|
|
The [specification][HTTP 1.1 Range] allows for more than one start-end range to
|
|
be supplied, separated by commas, however not all REST API operations support
|
|
multi ranges. If a multi-range header is used in such a request, then the
|
|
response may be the entire content or [501 Not Implemented](#501-not-implemented).
|
|
|
|
[HTTP 1.1 Range]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
|
|
|
|
### Transfer-Encoding header
|
|
|
|
In a [POST](#post) request, a **Transfer-Encoding** header of "chunked",
|
|
indicates that the client can generate and transmit the request body without
|
|
pre-calculating the final length. No other transfer encodings are currently
|
|
supported.
|
|
|
|
Each chunk is generated as the size of the chunk in hex, followed by CR-LF,
|
|
followed by the request bytes, followed by another CR-LF.
|
|
|
|
The end of the input is indicated with a chunk of zero length.
|
|
|
|
### Expect header
|
|
|
|
In a [POST](#post) request, the presense of an **Expect** header of
|
|
"100-Continue" indicates that the server should respond with an intermediate
|
|
response of "HTTP/1.1 100 Continue" before the client begins to transmit a
|
|
request body.
|
|
|
|
If for any reason the server determines that the request body is not needed, or
|
|
the request is invalid, the server will generate the response immediately without
|
|
reading the contents of the request body.
|
|
|
|
Responses
|
|
---------
|
|
|
|
An HTTP REST *response* is a normal [HTTP 1.0][] response consisting of a header
|
|
block, a blank line, and an optional body, for example: As usual, all lines are
|
|
terminated by an ASCII CR-LF sequence. For example:
|
|
|
|
HTTP/1.0 200 OK
|
|
Content-Type: application/json
|
|
Content-Length: 78
|
|
|
|
{
|
|
"http_status_code": 200,
|
|
"http_status_message": "OK"
|
|
}
|
|
|
|
The lingua franca of the HTTP REST API is [JSON][] in [UTF-8][] encoding. All
|
|
Serval DNA HTTP REST responses have a Content-Type of **[application/json][]**
|
|
unless otherwise documented.
|
|
|
|
Some responses contain non-standard HTTP headers as part of the result they
|
|
return to the client; for example, [Rhizome response headers](#rhizome-response-headers).
|
|
|
|
[application/json]: https://tools.ietf.org/html/rfc4627
|
|
|
|
Response status code
|
|
--------------------
|
|
|
|
The HTTP REST API response uses the [HTTP status code][] to indicate the
|
|
outcome of the request as follows:
|
|
|
|
[HTTP status code]: http://www.w3.org/Protocols/HTTP/1.0/spec.html#Status-Codes
|
|
|
|
### 200 OK
|
|
|
|
The operation was successful and no new entity was created. Most requests
|
|
return this code to indicate success. Requests that create a new entity only
|
|
return this code if the entity already existed, meaning that the creation was
|
|
not performed but the request can be considered a success since the desired
|
|
outcome was achieved: namely, the existence of the entity. (If the entity was
|
|
created, then these requests return [201 Created](#201-created) instead.)
|
|
|
|
(Serval APIs are all [idempotent][] with respect to creation: creating the same
|
|
entity twice yields the same state as creating it once. This is an important
|
|
property for a purely distributed network that has no central arbiter to
|
|
enforce sequencing of operations.)
|
|
|
|
### 201 Created
|
|
|
|
The operation was successful and the entity was created. This code is only
|
|
returned by requests that create new entities, in the case that the entity did
|
|
not exist beforehand and has been created successfully.
|
|
|
|
### 202 Accepted
|
|
|
|
The operation was successful but the entity was not created. This code is only
|
|
returned by requests that create new entities, in the case that the request was
|
|
valid but the entity was not created because other existing entities take
|
|
precedence. For example, the [Rhizome REST API](#rhizome-rest-api) returns
|
|
this code when inserting a bundle to a full Rhizome store if the new bundle's
|
|
rank falls below all other bundles, so the new bundle itself would be evicted
|
|
to make room.
|
|
|
|
### 206 Partial Content
|
|
|
|
The operation was successful and the response contains part of the requested
|
|
content. This code is only returned by requests that fetch an entity (the
|
|
fetched entity forms the body of the response) if the request supplied a
|
|
[Range](#range-header) header that specified less than the entire entity.
|
|
|
|
### 400 Bad Request
|
|
|
|
The HTTP request was malformed, and should not be repeated without
|
|
modifications. This could be for several reasons:
|
|
- invalid [query parameter][] in the *path*
|
|
- invalid syntax in the request header block
|
|
- a [POST](#post) request parameter is missing, duplicated or out of order
|
|
- a [POST](#post) request was given an unsupported parameter
|
|
- a [POST](#post) request parameter has missing or malformed content
|
|
|
|
### 401 Unauthorized
|
|
|
|
The request did not supply an "Authorization" header with a recognised
|
|
credential. This response contains a "WWW-Authenticate" header that describes
|
|
the missing credential:
|
|
|
|
HTTP/1.0 401 Unauthorized
|
|
Content-Type: application/json
|
|
Content-Length: 88
|
|
WWW-Authenticate: Basic "Serval RESTful API"
|
|
|
|
{
|
|
"http_status_code": 401
|
|
"http_status_message": "Unauthorized"
|
|
}
|
|
|
|
### 403 Forbidden
|
|
|
|
The request failed because the server does not accept requests from the
|
|
originating host.
|
|
|
|
### 404 Not Found
|
|
|
|
The request failed because the [HTTP request URI][] does not exist. This could
|
|
be for several reasons:
|
|
- the request specified an incorrect path (typographic mistake)
|
|
- the path is unavailable because the API in question is unavailable (eg, the
|
|
[Rhizome REST API](#rhizome-rest-api)) is currently [configured][] as
|
|
disabled
|
|
- the path contains a reference to an entity (eg, [SID][], [Bundle ID][]) that
|
|
does not exist
|
|
|
|
[HTTP request URI]: http://www.w3.org/Protocols/HTTP/1.0/spec.html#Request-URI
|
|
|
|
### 405 Method Not Allowed
|
|
|
|
The request failed because the [HTTP request method][] is not supported for the
|
|
given path. Usually this means that a [GET](#get) request was attempted on a
|
|
path that only supports [POST](#post), or vice versa.
|
|
|
|
[HTTP request method]: http://www.w3.org/Protocols/HTTP/1.0/spec.html#Method
|
|
|
|
### 411 Length Required
|
|
|
|
A [POST](#post) request did not supply either a [Content-Length](#content-length-header)
|
|
or [Transfer-Encoding](#transfer-encoding-header) header.
|
|
|
|
### 414 Request-URI Too Long
|
|
|
|
The request failed because the [HTTP request URI][] was too long. The server
|
|
persists the path and a few other pieces of the request in a fixed size request
|
|
buffer, and this response is triggered if the collective size of these does not
|
|
leave enough buffer for receiving the remainder of the request.
|
|
|
|
### 415 Unsupported Media Type
|
|
|
|
A [POST](#post) request failed because of an unsupported content type, which
|
|
could be for several reasons:
|
|
- the request's [Content-Type](#content-type-header) header specified an
|
|
unsupported media type
|
|
- a part of a [multipart/form-data][] request body has:
|
|
- a missing `Content-Disposition` header, or
|
|
- a `Content-Disposition` header that is not of type `form-data`, or
|
|
- a missing or unsupported `Content-Type` header (including a missing or
|
|
unsupported `charset` parameter)
|
|
|
|
### 416 Requested Range Not Satisfiable
|
|
|
|
The [Range](#range-header) header specified a range whose start position falls
|
|
outside the size of the requested entity.
|
|
|
|
### 419 Authentication Timeout
|
|
|
|
The request failed because the server does not possess and cannot derive the
|
|
necessary cryptographic secret or credential. For example, updating a [Rhizome
|
|
bundle][] without providing the [bundle secret][]. This code is not part of
|
|
the HTTP standard.
|
|
|
|
### 422 Unprocessable Entity
|
|
|
|
A [POST](#post) request supplied data that was inconsistent or violates semantic
|
|
constraints, so cannot be processed. For example, the [Rhizome
|
|
insert](./REST-API-Rhizome.md#post-restfulrhizomeinsert) operation responds
|
|
with 422 if the manifest *filesize* and *filehash* fields do not match the
|
|
supplied payload.
|
|
|
|
### 423 Locked
|
|
|
|
The request cannot be performed because a necessary resource is busy for
|
|
reasons outside the control of the requester and server.
|
|
|
|
This code is returned by Rhizome requests if the Rhizome store database is
|
|
currently locked by another process. The architecture of [Serval DNA][] is
|
|
being improved to prevent any process other than the Serval DNA daemon itself
|
|
from directly accessing the Rhizome database. Once these improvements are
|
|
done, this code should no longer occur except during unusual testing and
|
|
development situations.
|
|
|
|
### 429 Too Many Requests
|
|
|
|
The request cannot be performed because a necessary resource is temporarily
|
|
unavailable due to a high volume of concurrent requests.
|
|
|
|
The original use of this code was for Rhizome operations if the server's
|
|
manifest table ran out of free manifests, which would only happen if there were
|
|
many concurrent Rhizome requests holding manifest structures open in server
|
|
memory.
|
|
|
|
This code may also be used to indicate temporary exhaustion of other finite
|
|
resources. For example, if [Serval DNA][] is ever limited to service only a
|
|
few HTTP requests at a time, then this code will be returned to new requests
|
|
that would exceed the limit.
|
|
|
|
### 431 Request Header Fields Too Large
|
|
|
|
The request header block was too long.
|
|
|
|
Initial implementations of [Serval DNA][] allocated approximately 8 KiB of
|
|
buffer memory for each [request](#request), and the HTTP server read each
|
|
header line entirely into that buffer before parsing it. If a single header
|
|
exceeded the size of this buffer, then the 431 response was returned.
|
|
|
|
### 500 Internal Server Error
|
|
|
|
The request failed because of an internal error in [Serval DNA][], not an error
|
|
in the request itself. This could be for several reasons:
|
|
- software defect (bug)
|
|
- unavailable system resource (eg, memory, disk space)
|
|
- corrupted environment (eg, bad configuration, database inconsistency)
|
|
|
|
Internal errors of this kind may persist or may resolve if the request is
|
|
re-tried, but in general they will persist because the cause is not transient.
|
|
Temporary failures that can be resolved by re-trying the request are generally
|
|
indicated by other status codes, such as [423 Locked](#423-locked).
|
|
|
|
### 501 Not Implemented
|
|
|
|
The requested operation is valid but not yet implemented. This is used for the
|
|
following cases:
|
|
|
|
- a [POST](#post) request [Range](#range-header) header specifies a multi range
|
|
|
|
Cross-Origin Resource Sharing (CORS)
|
|
------------------------------------
|
|
|
|
To support client-side JavaScript applications, Serval DNA has a limited
|
|
implementation of [Cross-Origin Resource Sharing][CORS]. If a request contains
|
|
an **Origin** header with either “null” or a single URI with scheme “http” or
|
|
“https” or “file”, hostname “localhost” or “127.0.0.1” (or empty in the case of
|
|
a “file” scheme), and optionally any port number, then the response will
|
|
contain three **Access-Control** headers granting permission for other pages on
|
|
the same site to access resources in the returned response.
|
|
|
|
For example, given the request:
|
|
|
|
GET /restful/keyring/identities.json HTTP/1.0
|
|
Origin: http://localhost:8080/
|
|
...
|
|
|
|
Serval DNA will respond:
|
|
|
|
HTTP/1.0 200 OK
|
|
Access-Control-Allow-Origin: http://localhost:8080
|
|
Access-Control-Allow-Methods: GET, POST, OPTIONS
|
|
Access-Control-Allow-Headers: Authorization
|
|
...
|
|
|
|
[CORS]: http://www.w3.org/TR/cors/
|
|
|
|
JSON result
|
|
-----------
|
|
|
|
All responses that convey no special content return the following *JSON result*
|
|
object:
|
|
|
|
{
|
|
"http_status_code": ...,
|
|
"http_status_message": "..."
|
|
}
|
|
|
|
The `http_status_code` field is an integer equal to the [status
|
|
code](#response-status-code) that follows the `HTTP/1.0` token in the first
|
|
line of the response.
|
|
|
|
The `http_status_message` field is usually the same as the *reason phrase* text
|
|
that follows the code in the first line of the HTTP response. This reason
|
|
phrase may be a [standard phrase][status code], or it may be more explanatory;
|
|
for example, some *404* responses from Rhizome have phrases like, “Bundle not
|
|
found”, “Payload not found”, etc.
|
|
|
|
Some responses augment the *JSON result* object with extra fields; for example,
|
|
[Rhizome JSON result][] and [Keyring JSON result][].
|
|
|
|
JSON table
|
|
----------
|
|
|
|
Many HTTP REST responses that return a list of regular objects (eg, [GET
|
|
/restful/rhizome/bundlelist.json](./REST-API-Rhizome.md#get-restfulrhizomebundlelistjson))
|
|
use the following *JSON table* format:
|
|
|
|
{
|
|
"header":["fieldname1","fieldname2","fieldname3", ... ],
|
|
"rows":[
|
|
[field1, field2, field3, ... ],
|
|
[field1, field2, field3, ... ],
|
|
...
|
|
]
|
|
}
|
|
|
|
The JSON table format is more compact than the most straightforward JSON
|
|
representation, an array of JSON objects, which has the overhead of redundantly
|
|
repeating all field labels in every single object:
|
|
|
|
[
|
|
{
|
|
"fieldname1: field1,
|
|
"fieldname2: field2,
|
|
"fieldname3: field3,
|
|
...
|
|
},
|
|
{
|
|
"fieldname1: field1,
|
|
"fieldname2: field2,
|
|
"fieldname3: field3,
|
|
...
|
|
},
|
|
...
|
|
]
|
|
|
|
A JSON table can easily be transformed into its equivalent array of JSON
|
|
objects. The [test scripts](../testdefs_json.sh) use the following [jq(1)][]
|
|
expression to perform the transformation:
|
|
|
|
[
|
|
.header as $header |
|
|
.rows as $rows |
|
|
$rows | keys | .[] as $index |
|
|
[ $rows[$index] as $d | $d | keys | .[] as $i | {key:$header[$i], value:$d[$i]} ] |
|
|
from_entries |
|
|
.["__index"] = $index
|
|
]
|
|
|
|
-----
|
|
**Copyright 2015-2017 Serval Project Inc.**
|
|
![CC-BY-4.0](./cc-by-4.0.png)
|
|
Available under the [Creative Commons Attribution 4.0 International licence][CC BY 4.0].
|
|
|
|
|
|
[Serval Project]: http://www.servalproject.org/
|
|
[CC BY 4.0]: ../LICENSE-DOCUMENTATION.md
|
|
[API]: https://en.wikipedia.org/wiki/Application_programming_interface
|
|
[Serval DNA]: ../README.md
|
|
[Serval Mesh network]: http://developer.servalproject.org/dokuwiki/doku.php?id=content:tech:mesh_network
|
|
[HTTP REST]: https://en.wikipedia.org/wiki/Representational_state_transfer
|
|
[HTTP 1.0]: http://www.w3.org/Protocols/HTTP/1.0/spec.html
|
|
[MDP]: ./Mesh-Datagram-Protocol.md
|
|
[MSP]: ./Mesh-Stream-Protocol.md
|
|
[Keyring REST API]: ./REST-API-Keyring.md
|
|
[Keyring]: http://developer.servalproject.org/dokuwiki/doku.php?id=content:tech:keyring
|
|
[Rhizome REST API]: ./REST-API-Rhizome.md
|
|
[Rhizome]: http://developer.servalproject.org/dokuwiki/doku.php?id=content:tech:rhizome
|
|
[MeshMS REST API]: ./REST-API-MeshMS.md
|
|
[MeshMS]: http://developer.servalproject.org/dokuwiki/doku.php?id=content:tech:meshms
|
|
[Basic Authentication]: https://en.wikipedia.org/wiki/Basic_access_authentication
|
|
[Serval Mesh]: http://developer.servalproject.org/dokuwiki/doku.php?id=content:servalmesh:development
|
|
[Intent]: http://developer.android.com/reference/android/content/Intent.html
|
|
[Permission]: https://developer.android.com/preview/features/runtime-permissions.html
|
|
[configured]: ./Servald-Configuration.md
|
|
[Internet Media Type]: https://www.iana.org/assignments/media-types/media-types.xhtml
|
|
[Rhizome bundle]: ./REST-API-Rhizome.md#bundle
|
|
[Rhizome manifest]: ./REST-API-Rhizome.md#manifest
|
|
[Rhizome JSON result]: ./REST-API-Rhizome.md#rhizome-json-result
|
|
[Rhizome insert request]: ./REST-API-Rhizome.md#post-restfulrhizomeinsert
|
|
[MeshMS send request]: ./REST-API-MeshMS.md#post-restfulmeshmssendersidrecipientsidsendmessage
|
|
[Keyring JSON result]: ./REST-API-Keyring.md#keyring-json-result
|
|
[bundle secret]: ./REST-API-Rhizome.md#bundle-secret
|
|
[multipart/form-data]: https://www.ietf.org/rfc/rfc7578
|
|
[Content-Disposition]: https://tools.ietf.org/html/rfc7578#section-4.2
|
|
[Content-Type]: https://tools.ietf.org/html/rfc7578#section-4.4
|
|
[application/x-www-form-urlencoded]: https://tools.ietf.org/html/rfc1866#section-8.2.1
|
|
[percent encoded]: https://en.wikipedia.org/wiki/Percent-encoding
|
|
[query parameter]: https://en.wikipedia.org/wiki/Query_string
|
|
[JSON]: https://en.wikipedia.org/wiki/JSON
|
|
[UTF-8]: https://en.wikipedia.org/wiki/UTF-8
|
|
[jq(1)]: https://stedolan.github.io/jq/
|
|
[idempotent]: https://en.wikipedia.org/wiki/Idempotence
|
|
[SID]: ./REST-API-Keyring.md#serval-id
|
|
[Bundle ID]: ./REST-API-Rhizome.md#bundle-id
|