dox_http_native 3.0.4
dox_http_native: ^3.0.4 copied to clipboard
Fast native HTTP/1.1 server for Dart and the Dox framework. Rust parses HTTP, your handlers stay in Dart. Implements the dart:io HttpServer API.
3.0.4 #
- Fix: the build hook now tries the download of the native library up to 3 times (waits 1 s, then 3 s). Before, one bad answer from GitHub (eg. 503 or 504) meant no native library, and the app stopped at start. It tries again on a 5xx or 429 status, a connection error or timeout, and a wrong sha256. A 404 is not tried again. If all 3 tries fail, nothing changes: the hook falls back to cargo or adds no library.
3.0.3 #
- New:
NativeHttpServer.bind(idleTimeout: ...). A keep-alive connection with no new request for this time closes. A request head must also come whole in this time. Default: 30 seconds, as before. Behind a load balancer, set it longer than the idle timeout of the balancer (eg. 65 seconds for AWS ALB, which keeps connections for 60 seconds). Else the server may close a connection the balancer still uses, and clients get 502. - The
idleTimeoutfield now shows the value given tobind. Setting it later still has no effect, and it stays deprecated. - Fix: with
autoCompress, answers that could be gzipped now sendVary: Accept-Encoding, so a shared cache does not give a gzip body to a client that can not read it. - Fix:
Accept-Encoding: gzip;q=0means "no gzip", and is now read that way. - Fix: bodies that are compressed already (images, audio, video, zip, gz, woff and so on) are not gzipped again.
image/svg+xmlis still gzipped. - Native ABI 9 (
dox_bindtakes the idle timeout). The build hook gets the new native library.
3.0.2 #
- New:
AesGcm.sealandAesGcm.open, AES-256-GCM in Rust. It uses the AES and carry-less multiply instructions of the CPU. dox_core uses it for encrypted cookies andAESEncryptor. One cookie took about 100 µs in pure Dart (pointycastle), and now takes under a few µs. The layout (nonce + ciphertext + tag) is the same, so values made before still open. - Native ABI 8 (two new FFI calls). The build hook gets the new native library.
3.0.1 #
- Fix (Linux): a client that sent its request and then closed its sending side never had its connection closed. The close came in the same event as the request, and the server waited for a new event that never came. Now the server keeps the close from the event and reads on until it sees it. Test: a client that stops sending still gets its answer, and the connection closes.
- Fix: an answer that comes later (eg. after a database query) could wait up to seconds. The idle helper thread held the sockets, and Dart had to wait until the helper gave them back. Now the helper only watches the poll file (epoll or kqueue) and never holds the sockets, so Dart never waits for it. Tests: answers after a reply from another isolate, after timers of different lengths, a long async task next to fast requests, and a long synchronous task.
- Perf: one thread per isolate. The socket work (accept, read, parse, write) now runs on the isolate's own thread, in a loop of FFI calls (
dox_run). Before, each isolate had its own Rust network thread, and every request and answer went from one thread to the other. A small helper thread now only watches the sockets while the isolate is idle, and wakes it when a request comes. - All features stay the same: WebSocket (now the sync
tungstenitecrate), streamed answers, big bodies without a copy, gzip (on a helper thread),Expect: 100-continue,400/413/431/408, lingering close, gracefulclose()andclose(force: true). - New: at most 64 requests of one connection wait for an answer. Then the server stops reading that connection until answers go out, so one client can not fill the memory with pipelined requests.
- New: a request head must arrive within 30 seconds, and a connection with no request for 30 seconds is closed (like hyper before).
- New: a request with both
Content-LengthandTransfer-Encoding: chunkedgets400. ATransfer-Encodingother thanchunkedgets501. - Removed: the hyper and tokio server and the
DOX_HTTP_THREAD=sharedsetting. - New:
NativeHttpServer.bind(maxConnections: ...). At the limit the server stops accepting until a connection closes. Default: no limit. - Breaking: native ABI 7 (new FFI calls, the big bodies come as native memory with a finalizer,
dox_bindtakes the connection limit). The build hook gets the new native library.
Security #
- A WebSocket client that sent ping frames without end held the isolate, so other requests waited for seconds. Now one call reads at most 256 KB of one WebSocket, and each connection gets one turn per call.
- A WebSocket client that sent pings and never read the pongs made memory grow without limit. Now the server stops reading its frames while more than 256 KB waits to be written.
- A client that pipelined requests and never read the answers made memory grow without limit. Now the server stops reading its requests while 1 MB of answers waits.
- A client that never read its HTTP answer kept the connection open forever. Now a write with no progress for 30 seconds closes it.
- A streamed answer with a wrong
contentLengthcould look like two answers to the client (response desync). Now Rust counts the bytes and closes the connection on a mismatch. In Dart, likedart:io,add()throws when the body passes the length, andclose()fails when it is shorter. - When the isolate died while it owned the sockets, the port stayed open and clients waited forever. Now the helper thread checks every 100 ms that the isolate lives, and closes the server when it is gone.
headers.contentTypewith a line break in a part could add a header. Now it throws aFormatException.Content-Lengthtook+5and other white space. Now only ASCII digits pass, else400.- An idle connection no longer holds a 16 KB read buffer.
- The idle time of a keep-alive connection now counts from its last answer, not its last request.
- A huge
bodyTimeoutno longer overflows the clock (it is cut to one year). A port out of range throws anArgumentError. - A raw UTF-8 byte in the request target now reaches
req.urias%XXof that byte, not as Latin-1 text.
3.0.0 #
- README: streamed bodies (over 64 KB with
addStream) are supported. The build hook has 4 steps, not 3.
3.0.0-alpha.8 #
- Perf: pipelined requests are faster. Before, hyper served the requests of one connection one at a time. So 16 pipelined requests needed 16 trips to Dart, one after the other, and 16 writes. Now Rust reads the pipelined GET and HEAD requests ahead and sends them to Dart in the same batch. The answers still go out in order, and the ones that are ready go out in one write. A request with a body, a chunked body, an upgrade or
Connection: closestops the read ahead, and hyper serves the rest alone like before. - Fix:
headers.set()andheaders.add()with a bad value (eg. a line break) threw, butset()had already removed the old value. Now all values are checked first, so a bad value leaves the headers unchanged.
3.0.0-alpha.7 #
-
The package moved to its own repository: https://github.com/dartondox/dox-http-native. The ready-built libraries are on its releases (tag
v<version>). -
New: a streamed response (
addStream, eg. a file or a download) over 64 KB goes out in parts. Before, the whole body was kept in memory untilclose(), and the client got nothing until then. Now the client gets the first bytes early, and at most 1 MB waits in Rust. Dart reads more only when the client has read what was sent. When the client leaves,addStreamstops reading the source. A small response still goes out in one piece. A streamed response withcontentLengthset sendsContent-Length, else it is chunked. -
Perf: a request body of 64 KB or more goes to Dart without a copy (external typed data). Before, it was copied 3 times.
-
Perf:
compress: truegzips on a Rust thread, not on the isolate. Same output (gzip, default level). A streamed response is not compressed. -
Perf: a response with the default headers and one content type (eg. JSON) copies no headers, and its header bytes are cached.
-
Perf: request headers are parsed into a map on the second lookup, not scanned again and again.
-
Perf: a request path with no query skips the full
Uri.parse. Rust takes the lock of the answers once per batch. -
Breaking: native ABI 6 (streamed body records, gzip flag, big bodies as their own part of the message). The build hook gets the new native library.
-
New: WebSocket upgrade on the native server.
NativeWebSocket.upgrade(request, protocol: ..., maxMessageSize: ...)accepts it and gives adart:ioWebSocket. Rust (tokio-tungstenite) speaks the protocol: ping and pong, the close handshake, the UTF-8 check and the size limit (a bigger message closes with1009). Compression is never agreed.NativeWebSocket.isUpgradeRequest(request)tells if a request asks for one. -
On a graceful
close(), open WebSockets get close code1001. -
WebSocket limits, so a slow client or a busy isolate can not make memory grow forever:
- Messages to a client wait in a queue. Over 4 times
maxMessageSize(at least 1 MiB) the socket closes with1008(policy violation). - A message that can not be sent in 30 seconds drops the socket (
1006). The close handshake (our close frame and the answer) has 5 seconds in all, then the socket is dropped (1006). - When Dart has more than 8 MiB of client messages not yet handled, Rust stops reading from WebSocket clients until it catches up. No message is lost. HTTP is not paused.
- Messages to a client wait in a queue. Over 4 times
-
New:
NativeHttpServer.ensureAvailable()throws an error that says why the library did not load and how to fix it.bindthrows the same error. -
Breaking: native ABI 5 (new WebSocket records, and an ack record from Dart). The build hook gets the new native library.
-
Breaking: Dox has no
dart:ioserver any more. When this library does not load, Dox stops at start with the reason. -
Security: a multipart form may be up to
maxMultipartSizebytes (newbindoption, defaultmaxBodySize). Any other body, eg. JSON, raw or text, is limited tomaxBodySize. Dox passes its own two limits, so a JSON body over 10 MB now gets413from Rust. -
Security: a body with a too big
Content-LengthandExpect: 100-continuegets413at once. WithoutExpect, Rust reads and drops the body (up to 64 MB more, no memory kept), then sends413, so the client sees the413and not a connection reset. -
Security: Rust no longer reserves memory from the client
Content-Length(at most 64 KB up front). -
Security: new
bindoptionbodyTimeout(default 120 seconds). It is the idle time with no body bytes: a client that sends nothing for that long gets408. A slow but steady upload is fine. -
Native ABI 4 (
dox_starttakes the multipart limit and the body timeout). Needs a new native library build. -
Security: response headers now refuse bad input with a
FormatException, likedart:io. A header name must be a token. A header value must not hold a line break or other control byte. Before, a\nin a value (or a char like\u010A, written as its low byte) added one more header, eg.Set-Cookie, when an app put request input in a header. The same check now runs onserverHeaderand on each cookie when the response closes, becauseCookiedoes not check its domain.
3.0.0-alpha.6 #
- Version bump only, to match the other packages.
3.0.0-alpha.5 #
- New:
NativeHttpServer.bind(port, address: ...)listens on one host, likeHttpServer.bind. AnInternetAddressor a host name. With no address it listens on all interfaces, as before.server.addressis the real address. - Fix:
close()never finished when nobody calledlisten()on the server. - Native ABI 3 (
dox_starttakes the host). Needs a new native library build. Until the next release, the build hook builds it with cargo. - New:
close()is graceful, likedart:io. The server stops taking connections at once, each open connection closes after its current answer, and the future completes when all are closed.close(force: true)closes every connection at once.
3.0.0-alpha.4 #
- Version bump only, to match the other packages.
3.0.0-alpha.3 #
DOX_HTTP_THREAD=sharedruns onedox-httpthread for all isolates. Default is still one thread per isolate, which measured faster.- Less work per request: request headers are read one name at a time, answers are not copied again in Rust, one wake per answer batch.
- Native ABI 2 (new header layout in the request record). Needs a new native library build.
3.0.0-alpha.2 #
- First release.
- Native HTTP/1.1 server: Rust (hyper, tokio) owns the sockets and parses HTTP. Handlers stay in Dart.
- Implements the
dart:ioHttpServer,HttpRequestandHttpResponseAPI, so code written fordart:ioworks unchanged. - One server per isolate on the same port (
SO_REUSEPORTon Linux and macOS). - 400 for bad requests, 431 for big headers, 413 for big bodies.
- The build hook downloads a ready-built library. No Rust needed.