Version 2 of the HTTP API ("Hrana over HTTP") exposes stateful streams from Hrana over HTTP. It provides functionality equivalent to Hrana and it is useful for environments with missing or incomplete support for WebSockets.
This version deprecates version 1 of the HTTP API. Both clients and servers should move to version 2 as soon as possible.
The HTTP API uses data structures and semantics from the Hrana 2 protocol.
Individual requests on the same stream are tied together by the use of a baton. The server returns a baton in every response to a request on the stream, and the client then needs to include the baton in the subsequent request. The client must serialize the requests: it must wait for a response to the previous request before sending next request.
The server can also optionally specify a different URL that the client should use for the requests on the stream. This can be used to ensure that stream requests are "sticky" and reach the same server.
The server will close streams after a short period of inactivity, to make sure that abandoned streams don't accumulate on the server.
GET /v2
If the server supports this version of the HTTP API, it should return a 2xx
response for a GET request on /v2
. This can be used as a crude version
negotiation mechanism by the client.
POST /v2/pipeline
-> {
"baton": string | null,
"requests": Array<StreamRequest>,
}
<- {
"baton": string | null,
"base_url": string | null,
"results": Array<StreamResult>
}
type StreamResult =
| StreamResultOk
| StreamResultError
type StreamResultOk = {
"type": "ok",
"response": StreamResponse,
}
type StreamResultError = {
"type": "error",
"error": Error,
}
The pipeline
endpoint is used to execute a pipeline of requests on a stream.
baton
in the request specifies the stream. If the client sets baton
to
null
, the server should create a new stream.
Server responds with another baton
value in the response. If the baton
value
in the response is null
, it means that the server has closed the stream. The
client must use this value to refer to this stream in the next request (the
baton
in the response should be different from the baton
in the request).
This forces the client to issue the requests serially: it must wait for the
response from a previous pipeline
request before issuing another request on
the same stream.
The server should ensure that the baton
values are unpredictable and
unforgeable, for example by cryptographically signing them.
If the base_url
in the response is not null
, the client should use this URL
when sending further requests on this stream. If it is null
, the client should
use the same URL that it has used for the previous request. The base_url
must be an absolute URL with "http" or "https" scheme.
The requests
array in the request specifies a sequence of stream requests that
should be executed on the stream. The server executes them in order and returns
the results in the results
array in the response. Result is either a success
(type
set to "ok"
) or an error (type
set to "error"
). The server always
executes all requests, even if some of them return errors.
If the client receives an HTTP error (4xx or 5xx response) in response to the
pipeline
endpoint, it means that the server encountered an internal error and
the stream is no longer valid.
Requests in the HTTP API closely mirror stream requests in Hrana:
type StreamRequest =
| CloseStreamReq
| ExecuteStreamReq
| BatchStreamReq
| SequenceStreamReq
| DescribeStreamReq
| StoreSqlStreamReq
| CloseSqlStreamReq
type StreamResponse =
| CloseStreamResp
| ExecuteStreamResp
| BatchStreamResp
| SequenceStreamResp
| DescribeStreamResp
| StoreSqlStreamResp
| CloseSqlStreamResp
type CloseStreamReq = {
"type": "close",
}
type CloseStreamResp = {
"type": "close",
}
The close
request closes the stream. It is an error if the client tries to
execute more requests on the same stream.
type ExecuteStreamReq = {
"type": "execute",
"stmt": Stmt,
}
type ExecuteStreamResp = {
"type": "execute",
"result": StmtResult,
}
The execute
request has the same semantics as the execute
request in Hrana.
type BatchStreamReq = {
"type": "batch",
"batch": Batch,
}
type BatchStreamResp = {
"type": "batch",
"result": BatchResult,
}
The batch
request has the same semantics as the batch
request in Hrana.
type SequenceStreamReq = {
"type": "sequence",
"sql"?: string | null,
"sql_id"?: int32 | null,
}
type SequenceStreamResp = {
"type": "sequence",
}
The sequence
request has the same semantics as the sequence
request in
Hrana.
type DescribeStreamReq = {
"type": "describe",
"sql"?: string | null,
"sql_id"?: int32 | null,
}
type DescribeStreamResp = {
"type": "describe",
"result": DescribeResult,
}
The describe
request has the same semantics as the describe
request in
Hrana.
type StoreSqlStreamReq = {
"type": "store_sql",
"sql_id": int32,
"sql": string,
}
type StoreSqlStreamResp = {
"type": "store_sql",
}
The store_sql
request has the same semantics as the store_sql
request in
Hrana, except that the scope of the SQL texts is just a single stream (in Hrana,
it is the whole connection).
type CloseSqlStreamReq = {
"type": "close_sql",
"sql_id": int32,
}
type CloseSqlStreamResp = {
"type": "close_sql",
}
The close_sql
request has the same semantics as the close_sql
request in
Hrana, except that the scope of the SQL texts is just a single stream.