Skip to content

Web hosting reference

A web-hosted service is any program that serves HTTP, run beside the platform's proxy in the same pod. This page is the whole of what the proxy gives the program, which the platform calls the process, and what it asks of it. It grows only by addition: a new header or variable never changes the meaning of an old one.

What the image must do

The image must serve HTTP/1.1 on the port named by PORT, and nothing else. It needs no certificate, no library of ankka's and no knowledge of the platform. It may listen on every network address: the network admits no connection to that port from outside the pod.

Environment

The process is given every variable its descriptor declares, and these two:

Variable Value
PORT the port to listen on: the descriptor's processPort, or 8080
ANKKA_SERVICES_URL where to call other services: http://127.0.0.1:7630 in a cluster

A descriptor may not declare either; the platform sets them.

Every request the process receives

The proxy passes the method, the path, the query, the body and the headers as they arrived, with these headers set by the proxy, and any copy the request carried removed first:

Header Value
X-Ankka-Caller internet, service <project>/<service>, or local on a developer's machine
X-Forwarded-Proto https from the internet or a service; http on a developer's machine
X-Forwarded-Host the authority the request was addressed to, with its port when it is not the scheme's own. Under another web-hosted service's mount, the authority that service's proxy stated
X-Forwarded-Port that port
Host the same authority

The caller is read from the certificate of whoever connected, never from the request, so a request cannot say that a service sent it. From the internet, the address is the hostname the gateway routed the request at: the service's derived hostname, or one of its custom hostnames, since the gateway sends a hostname only to the service that holds it. A forwarded header the request carries is never believed, so a request cannot say it was sent somewhere else. From another service, the address is the web-hosted service's in-cluster address, <service>.ankka-<project>.svc.cluster.local:<port>.

Removed from every request, whoever sent it: any header whose name starts X-Ankka-, Forwarded, and the hop-by-hop headers (Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, TE, Trailer, Transfer-Encoding, Upgrade). X-Forwarded-For is passed on from the internet, where its last entry is the gateway's and the ones before it are whatever the client sent, and removed from a service's request.

A trace context passes as it arrived, on a request to the process, on a request under a mount and on a call made at the calling address: traceparent and tracestate are neither removed nor changed, and the proxy records no span of its own. A trace crosses a web-hosted service unbroken, and a process that wants a span in it exports one itself.

When the installation has no base domain, the three X-Forwarded- headers are left out of a request from the internet and Host is passed as it arrived.

A request body is passed on as it arrives. A response is delivered as the process writes it, status and headers as given; the proxy holds none of it back, so a stream of events reaches the browser event by event.

The proxy does not upgrade a connection. A request asking for one is passed on without the Upgrade and Connection headers, and what the process answers is returned. A socket route of a mounted service is therefore not reached under a mount; a browser opens the socket at the mounted service's own hostname.

Requests the process never sees

The process never sees two kinds of request:

  • One from a service the descriptor does not admit: the proxy answers 403. The internet and the web-hosted service itself are always admitted.
  • One whose path is a mount's, or under it: the proxy passes it to the mounted service.

Calling another service

The process calls another service at the calling address, ANKKA_SERVICES_URL. The first path segment names the service, and the rest, with the query, is the path the service receives:

GET  $ANKKA_SERVICES_URL/cart/carts/c1            # the service cart, in this project
POST $ANKKA_SERVICES_URL/invoices.billing/issue   # the service invoices, in the project billing

The method, the headers and the body are sent as the process gave them, except that headers starting X-Ankka- and the hop-by-hop headers are removed and Host is the service's. The answer is returned as the service gave it, whatever its status, a refusal included. No redirect is followed. A call the service answered is never sent again, and neither is any other call whose connection closed. The one exception is a GET or HEAD whose connection closed before any answer began: it is sent once more on a new connection, because the closed connection may be one the service had already let go idle. The same holds for a request passed to the process or under a mount.

The service is told the caller Service(<this project>, <this service>), so its access rule can admit the web-hosted service by name. The proxy also checks that whoever answers holds the identity of the service asked for, so a call is never sent to another workload. Only the process can use the calling address: it is bound to the pod's loopback interface.

A request under a mount

A mount passes every request under its path to a service of the same project, with the path removed. For a descriptor with { "path": "/api/cart", "service": "cart" }:

GET /api/cart/carts/c1?x=1   →   cart receives   GET /carts/c1?x=1
GET /api/cart                →   cart receives   GET /
GET /api/cartoons            →   the process     (a mount matches whole segments)

The headers and body are passed on as for a call, with the X-Forwarded-* headers set as the process would have been given them. The mounted service is told the caller Gateway: the internet, whoever sent the request to the proxy. It serves the request only if its access rule admits the internet. A service in another project refuses it, and so does a service whose runtime predates web hosting.

What the proxy answers by itself

Every answer the proxy gives without the process or a service having answered carries X-Ankka-Answered-By: proxy and the body {"error": "<reason>"}. An answer without that header came from the process or from a service.

Status When Reason
400 a call names no service a call names a service: <url>/<service>/<path>, or '<segment>' does not name a service: …
403 the caller is not admitted the caller is not admitted: service <project>/<service>
403 a request under another project's mount a request under a mount of another project
403 a certificate that names no caller unrecognised caller certificate
502 the process or a service closed the connection before answering <who> closed the connection before answering
502 the service that answered is not the one called '<service>' is not the service that answered: …
503 the process is not listening the process is not listening
503 a call names a service that cannot be found no service '<service>', with in the project '<project>' for another project's
503 a called service is found and not listening the service <project>/<service> is not listening
503 a mount's service cannot be found or reached '<service>' cannot be reached
504 the process or a service did not begin to answer in time <who> did not answer within 60 seconds

The bound of 60 seconds is on the start of an answer. A response that has begun is never cut.

Readiness

The pod is ready while a connection to PORT succeeds, and not otherwise. The proxy answers the platform's probe on its own port, 7627, by trying that connection. No route of the process's is called. When a rollout's deadline passes with the process never listening, the service's status says the process is not listening on port <n> and quotes the kubelet's words.

Stopping

SIGTERM reaches both containers five seconds after the platform starts removing the pod from its address. The proxy stops accepting, lets requests in flight finish for up to ten seconds, and exits. A process should do the same.

Ports in a web-hosted pod

Port Who listens Who reaches it
the descriptor's port, 9000 by default the proxy, mutual TLS the gateway, and services of the installation
7627 the proxy, plain anyone: it says only whether the pod is ready
processPort, 8080 by default the process the proxy, on the pod's loopback
7630 the proxy, on loopback the process

On a developer's machine

ankka local web gives the same contract with four differences: there is no TLS, X-Ankka-Caller is always local, ANKKA_SERVICES_URL names a port chosen when it starts, and when it runs the process, PORT is a free port it chose rather than the descriptor's.