Disclaimer: These are planning documents. The functionalities described here may not be implemented, or may be only partially implemented.

Griddle Proxy

Summary

Griddle is a client daemon that will allow for requests to be signed, if needed, and proxied to a Grid Daemon.

Motivation

Griddle’s Rest API, including signing utility, will be used to interact with Grid. Griddle can be deployed in a different security zone than the Grid daemon. Read requests are immediately proxied to the grid daemon. Griddle endpoints that accept transactions will use a private key to construct and sign the resulting batch, then proxy the request payload to the grid daemon’s batches endpoint.

Guide-level explanation

As a proxy for the Grid daemon, the Griddle REST API will send all read requests to the Grid daemon. The request is turned into a generic request object that maintains all integral pieces of the initial request. It will then send the request to the corresponding endpoint. The proxy will respond with the data it received from the endpoint. If the proxy encounters an error, it will return the error data as received.

The Griddle rest endpoint that will receive the request needs to copy the request to be sent to the grid daemon. The griddle endpoint will use a client in order to send the request to the grid daemon. The proxy client will send the request to the Grid daemon and return the JSON response as a generic JSON value. The Griddle endpoint will then return this JSON data received by the client.

Reference-level explanation

The proxying for griddle is able to be genericized, as a trait. The trait follows suit of other clients, in that it is able to be sent over threads. The proxy client trait is defined below:

pub trait ProxyClient: Send {
    fn proxy(&self, req: ProxyRequestBuilder) -> ProxyResponse;

    fn cloned_box(&self) -> Box<dyn ProxyClient>;
}

The first method, proxy, is a generic method for sending a request to the proxy client’s internal url. The method takes in a generic ProxyRequestBuilder object, which reflects the original request, excluding the request’s uri. The builder differs from the ProxyRequest in that all fields are optional. The builder has a build method that returns the resulting ProxyRequest or an error if any required fields are not supplied. The ProxyClient provides the request’s uri using its internal url value.

The ProxyRequest object is defined below:

pub struct ProxyRequest {
    headers: HeaderList,
    uri: String,
    path: String,
    query_params: Option<String>,
    body: Option<ProxyRequestBody>,
    method: ProxyMethod,
}

The request headers are represented as bytes. To avoid raw bytes in the public API, the HeaderName and HeaderValue types wrap the header bytes.

pub type HeaderName = Vec<u8>;

pub type HeaderValue = Vec<u8>;

pub type HeaderList = Vec<(HeaderName, HeaderValue)>;

The body of the request is generic, representing the basic structure of an HTTP request body.

pub struct ProxyRequestBody {
    content_type: String,
    content: Vec<u8>,
}

The ProxyMethod struct supports the basic HTTP request methods defined in HTTP RFC 7231. The enum also includes methods, Patch and Custom, to reflect the methods provided by the REST API backend. The REST API is implemented using Actix-Web, which uses the Http crate’s Method implementation. ProxyMethod is defined below:

pub enum ProxyMethod {
    Get,
    Post,
    Put,
    Delete,
    Connect,
    Head,
    Options,
    Trace,
    Patch,
    Custom(Vec<u8>),
}

The ProxyClient responds with a ProxyResponse object. This struct reflects a basic HTTP response, containing a message represented by a String and the status code represented as a u16 value. This allows the message and status code to be converted into any other representation of an HTTP response. This object is defined below:

pub struct ProxyResponse {
    status_code: u16,
    body: ProxyResponseBody,
}

pub struct ProxyResponseBody {
    content: Vec<u8>,
}

The ProxyResponseBody contains the body of the response, represented as bytes. This value may be deserialized as needed by the endpoint receiving this response.

The second method, cloned_box, allows the object to be used as a boxed trait and moved to the REST API thread.

The endpoint that retrieves a GET request to proxy will have a signature similar to the following:

pub async fn proxy_get(
    req: HttpRequest,
    proxy_client: web::Data<Box<dyn ProxyClient>>,
) -> HttpResponse;

This endpoint holds the proxy_client that will be used to send the request to the Grid Daemon backend. The HttpRequest is then converted into a ProxyRequest and sent to the proxy client for handling. This method would be able to be used across multiple GET endpoints, as it returns a generic JSON value as returned by the daemon’s endpoints.

Unresolved questions

Response object is relatively simplified. Is there value for making this object more robust, such as returning headers from the original HTTP response?