0
0
mirror of https://github.com/tursodatabase/libsql.git synced 2024-12-15 02:49:41 +00:00
libsql/docs/HTTP_V2_SPEC.md
2023-11-14 10:28:20 -05:00

6.0 KiB

The sqld HTTP API v2 specification ("Hrana over HTTP")

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.

Overview

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.

Check support for version 2

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.

Execute requests on a stream

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

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

Close stream

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.

Execute a statement

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.

Execute a batch

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.

Execute a sequence of SQL statements

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.

Describe a statement

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.

Store an SQL text on the server

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).

Close a stored SQL text

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.