Rust Web Services

Routing

flowchart TD
   200[200 OK]
   404[404 Not Found]

   A[HTTP Request]
   B[psinode]
   C[http-server service]
   sites[sites service's serveSys action]
   serveSys{{serveSys handles request?}}

   A --> B --> C
   sites --> E{{was site data found?}} -->|yes| 200
   E -->|no| 404
   C --> G{{target begins with '/common/'?}}
   G -->|yes| common['common-api' service's serveSys action] --> serveSys
   G -->|no| I{{on a subdomain?}} -->|no| home['homepage' service's serveSys action] --> serveSys
   I -->|yes| J{{Has registered server?}}
   J -->|yes| L[registered server's serveSys action] --> serveSys
   J -->|no| sites
   serveSys -->|yes| 200
   serveSys -->|no| sites

psinode passes most HTTP requests to the SystemService::HttpServer service, which then routes requests to the appropriate service's serveSys action (see diagram). The services run in RPC mode; this prevents them from writing to the database, but allows them to read data they normally can't. See psibase::DbId.

SystemService::CommonApi provides services common to all domains under the /common/ tree.

SystemService::Sites provides web hosting for non-service accounts or service accounts that did not register for HTTP handling.

psinode directly handles requests which start with /native, e.g. /native/p2p. Services don't serve these.

Registration

Services which wish to serve HTTP requests need to register using the SystemService::HttpServer service's SystemService::HttpServer::registerServer action. This is usually done by setting the package.metadata.psibase.server field in Cargo.toml to add this action to the package installation process.

A service doesn't have to serve HTTP requests itself; it may delegate this to another service during registration.

HTTP Interfaces

Services which serve HTTP implement these interfaces:

Helpers

These help implement basic functionality:

Here's a common pattern for using these functions. #[psibase::service] defines Wrapper; the serve_* functions fetch action definitions from Wrapper.

#[psibase::service]
#[allow(non_snake_case)]
mod service {
    use psibase::*;

    #[action]
    fn serveSys(request: HttpRequest) -> Option<HttpReply> {
        if request.method == "GET"
        && (request.target == "/" || request.target == "/index.html")
        {
            return Some(HttpReply {
                contentType: "text/html".into(),
                body: "<b>This is my UI</b>".into(),
                headers: vec![],
            });
        }

        None.or_else(|| serve_action_templates::<Wrapper>(&request))
            .or_else(|| serve_pack_action::<Wrapper>(&request))
    }
}