HTTP versions
The API port serves HTTP/1.1 and HTTP/2 on the same TCP listener, and each connection uses whichever one the client speaks, with nothing to configure. HTTP/3 runs over QUIC on a UDP listener of its own. It is off by default, and needs a certificate even when a proxy terminates TLS for TCP.
| Version | Transport | How a connection gets it | To turn it on |
|---|---|---|---|
| HTTP/1.1 | TCP | Anything that isn't HTTP/2 | Nothing |
| HTTP/2 | TCP | Prior knowledge, or ALPN with TLS | Nothing |
| HTTP/3 | QUIC over UDP | The Alt-Svc advertisement | server.http3.mode and a certificate |
How a connection picks its version
Over plaintext, the broker reads the first bytes of each connection. A client that starts with the HTTP/2 connection preface gets HTTP/2 straight away. This is HTTP/2 over cleartext with prior knowledge, as curl --http2-prior-knowledge sends it. Everything else is served as HTTP/1.1.
With server.transport = "tls", the version is agreed in the TLS handshake through ALPN. The broker offers h2 and http/1.1, in that order, and the client chooses, as TLS describes.
Behind a proxy
A proxy holds two connections, one with the client and one with the broker, and each gets a version of its own. A proxy can speak HTTP/2 to clients and HTTP/1.1 to the broker, or the other way round, and the broker answers in whatever its side of the connection speaks.
For HTTP/2 between the proxy and a plaintext broker, the proxy has to send it with prior knowledge, since the broker never upgrades an HTTP/1.1 connection. Whether your proxy can speak HTTP/2 to an upstream at all is in its own documentation.
The broker closes a connection once it has been idle for server.idle_timeout, 3 minutes by default. Keep that above the time your proxy keeps an idle upstream connection open, so the proxy is always the one that closes it. Most proxies keep one for 60 to 120 seconds, and some for much longer. If the broker closes first, the proxy can send a request down the connection just as it closes, and that request fails.
Connections that carry no request
Two settings close a TCP connection that isn't carrying a request. Both apply to the API listener and to the management listener, and both take effect after a restart.
| Setting | Default | Counts from |
|---|---|---|
server.request_head_timeout | 30 seconds | The connection opening, and on HTTP/1.1 the first byte of each later request |
server.idle_timeout | 3 minutes | The end of the last response |
A new connection has server.request_head_timeout to send a complete request head. On HTTP/1.1, every later request on the connection gets the same time again, from its first byte. If part of an HTTP/1.1 head has arrived when the time runs out, the broker answers 408 Request Timeout with no body and Connection: close, then closes the connection. A connection that sent nothing, or only part of the HTTP/2 preface, is closed without an answer. request_limits.timeout starts only once the head has arrived, so it never covers this wait.
Between requests, a connection may sit idle for server.idle_timeout. A request that runs longer is never cut off, because the time starts only when the last response has gone out. An idle HTTP/1.1 connection is closed. An idle HTTP/2 connection is sent a GOAWAY first and then closed.
What is the same on every version
The version changes how the bytes travel, not what a request may hold.
- A request with a body needs
Content-Length. Without one, the broker answers411with no body, as Strictness explains. request_limits.max_header_sectionlimits the request head, measured the same way on every version, and a head over it gets431. Over HTTP/3, the limit is also sent to clients asSETTINGS_MAX_FIELD_SECTION_SIZE, so a client that has read it doesn't send larger headers.request_limits.timeoutandrequest_limits.max_bodyapply to every request.- Responses carry the same headers and bodies.
Closing connections
When the broker closes an HTTP/1.1 connection after a response marked Connection: close, it waits server.announced_close_timeout, 1 second by default, so the client can read the response first.
On HTTP/1.1, a refusal that comes before the body is answered without reading the body. That covers 404, 405, 411, 413, 415 and any other answer the broker can give from the request head alone, and each of them carries Connection: close. The broker then leaves the connection open, unread, for server.announced_close_timeout before it closes it. A client still sending its body reads the answer instead of a connection reset, and a proxy streaming the body passes the answer on instead of turning it into a 502.
On shutdown, the broker gives open connections server.graceful_drain_timeout, 30 seconds by default, to finish their requests. Listeners and TLS has these settings and the timeouts above.
HTTP/3
Andromeda can serve the API over HTTP/3 as well. HTTP/3 runs over QUIC on UDP and always uses TLS 1.3, so the broker needs a certificate and key of its own to serve it, even when a proxy terminates TLS for TCP.
HTTP/3 support is experimental. It builds on QUIC and TLS 1.3 libraries that are still maturing, so its behaviour may change between releases, and it may not yet hold up under production load. Try it in a test environment before you rely on it.
Turning it on
[server.http3]
mode = "enabled"
[tls]
certificate = "/etc/andromeda/tls/tls.crt"
private_key = "/etc/andromeda/tls/tls.key"
HTTP/3 listens on server.host and server.port, over UDP. TCP 1056 and UDP 1056 are separate bindings, so no other port is needed, but both have to be reachable. In Docker, publish both, for example 1056:1056/tcp and 1056:1056/udp, and let inbound UDP through any firewall in front.
The certificate is the broker's one [tls] identity, the same one the TCP listener presents when it terminates TLS. Without tls.certificate and tls.private_key, andromeda configuration validate refuses the configuration and the broker doesn't start.
HTTP/3 always negotiates TLS 1.3 and the h3 ALPN token, so tls.minimum_protocol_version doesn't apply to it. Rotation and tls.client_authentication.mode work exactly as they do over TCP, as TLS describes.
How clients find it
While HTTP/3 is up, every TCP response carries an advertisement like this one.
Alt-Svc: h3=":1056"; ma=86400
The empty host means the host the client already reached. ma is server.http3.advertisement_lifetime in seconds, a day by default, and says how long a client may remember the advertisement. The header is sent only while the HTTP/3 listener is up, so a client never tries QUIC against a port nobody listens on.
The advertisement names the broker's own port. If a proxy terminates TCP in front of the broker, clients can use HTTP/3 only when the same host also reaches that port over UDP.
Checking it works
Ask for HTTP/1.1 and for HTTP/2, and look at the status line.
curl -sI --http1.1 http://localhost:1056/ngsi-ld/v1/version
curl -sI --http2-prior-knowledge http://localhost:1056/ngsi-ld/v1/version
The first answers HTTP/1.1 200 OK and the second HTTP/2 200. With TLS, use https://, and --http2 for the second.
For HTTP/3, a certificate that a client trusts directly must not be marked as a CA, or the client refuses it. For a test, make one like this and start the broker with it.
openssl req -x509 -newkey rsa:2048 -nodes -days 365 \
-keyout broker.key -out broker.crt \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1" \
-addext "basicConstraints=critical,CA:FALSE"
ANDROMEDA__SERVER__HTTP3__MODE=enabled \
ANDROMEDA__TLS__CERTIFICATE=$PWD/broker.crt \
ANDROMEDA__TLS__PRIVATE_KEY=$PWD/broker.key \
andromeda
A TCP response should now carry Alt-Svc, and a curl built with HTTP/3 support should get an answer over QUIC alone.
curl -si http://localhost:1056/ngsi-ld/v1/info/sourceIdentity | grep -i alt-svc
curl --http3-only --cacert broker.crt https://localhost:1056/ngsi-ld/v1/info/sourceIdentity