@@ -254,6 +254,48 @@ for any other body. A body that isn't text -- an upload, an image,
254254form, after checking that it's valid UTF-8, and refuses a binary body rather
255255than hand the handler garbled characters.
256256
257+ ==== Large uploads and downloads
258+
259+ A binary body can be larger than 8 MB when `CN1_HTTP_MAX_SPOOLED_BODY_MB` raises
260+ its limit. Such a body is never held in memory whole. Up to
261+ `CN1_HTTP_SPOOL_THRESHOLD_KB` (1024 by default) it stays on the heap, and past
262+ that the server writes it to a temporary file as it arrives. The file is deleted
263+ from the directory as soon as it's created, so a crash leaves nothing behind.
264+ `CN1_HTTP_SPOOL_DIR` chooses the directory, the system temporary directory
265+ otherwise. A body over the limit is answered 413, over HTTP/1.1 and HTTP/2
266+ alike. JSON, forms, XML and compressed bodies keep the 8 MB limit, because a
267+ handler reads those whole anyway.
268+
269+ Read such a body with `getBodyStream()`, which reads the file or the memory
270+ without the handler knowing which. `getBodyLength()` gives its size without
271+ reading it. A multipart upload is spooled the same way, and each
272+ `HttpServer.Part` has `getInputStream()` and `getLength()` for it:
273+
274+ [source,java,role=cn1-no-try]
275+ ----
276+ include::../demos/backend/src/main/java/com/codenameone/developerguide/backend/Streaming.java[tag=backend-web-upload-stream,indent=0]
277+ ----
278+
279+ The body is valid until the request ends. A handler that needs it later copies
280+ it somewhere of its own.
281+
282+ A response is streamed with `HttpServer.Response.stream`. The writer runs after
283+ the headers are sent and writes as much as it likes: chunked on HTTP/1.1, DATA
284+ frames on HTTP/2. A write blocks while the client is slow, so a slow client
285+ slows the writer rather than filling memory, and a client that goes away makes
286+ the next write throw.
287+
288+ [source,java,role=cn1-no-try]
289+ ----
290+ include::../demos/backend/src/main/java/com/codenameone/developerguide/backend/Streaming.java[tag=backend-web-download-stream,indent=0]
291+ ----
292+
293+ The server doesn't run the writer for a `HEAD` request, a status without a
294+ body, or a client that left before the headers went out. A writer that holds
295+ something open until it runs, such as a connection or a file, should also
296+ implement `java.io.Closeable`. In those cases the server calls `close()`
297+ instead.
298+
257299==== Compression
258300
259301`cn1.server.compression.enabled=true` turns on gzip for responses, with the two
@@ -765,6 +807,63 @@ HTTP/2 fails every request with an exception that says so. It reads PKCS#12
765807archives, and PEM keys in unencrypted PKCS#8 or RSA form. The packaged server also
766808reads encrypted PEM keys. A PKCS#12 archive works the same in both.
767809
810+ ==== Streaming a request
811+
812+ `Web.request` holds the whole response in memory. `Web.open` (or
813+ `WebClient.open`) returns a `Web.Exchange` instead, which sends the request body
814+ as it's written and reads the response as it arrives. The length is the body's
815+ size, `Web.UNKNOWN_LENGTH` for a body of unknown size, or `Web.NO_BODY`:
816+
817+ [source,java,role=cn1-no-try]
818+ ----
819+ include::../demos/backend/src/main/java/com/codenameone/developerguide/backend/Streaming.java[tag=backend-web-exchange,indent=0]
820+ ----
821+
822+ Write and close the request body first, then read the status, the headers and
823+ the response body, all from one thread. Each direction buffers a fixed window,
824+ so a slow peer slows its producer. `close()` ends the exchange in any state.
825+ Before the response has been read to its end it cancels the exchange, closing
826+ the connection instead of draining it, so always close it in a `finally`. An
827+ exchange never follows a redirect, because a streamed body can't be sent twice.
828+
829+ ==== Forwarding to another service
830+
831+ `HttpProxy` forwards a request to another server and streams the answer back,
832+ the way a gateway in front of an internal service would:
833+
834+ [source,java,role=cn1-no-try]
835+ ----
836+ include::../demos/backend/src/main/java/com/codenameone/developerguide/backend/Streaming.java[tag=backend-web-proxy-create,indent=0]
837+ ----
838+
839+ [source,java,role=cn1-no-try]
840+ ----
841+ include::../demos/backend/src/main/java/com/codenameone/developerguide/backend/Streaming.java[tag=backend-web-proxy-forward,indent=0]
842+ ----
843+
844+ The response body is streamed back as it arrives, and a binary upload is
845+ streamed up from wherever it lives, a spooled one included. A text or compressed
846+ request body is read whole on arrival, under the 8 MB limit, and forwarded
847+ decompressed. Every end-to-end header goes
848+ through, a repeated one such as `Set-Cookie` line by line. The hop-by-hop
849+ headers (`Connection`, `Keep-Alive`, `Proxy-*`, `TE`, `Trailer`,
850+ `Transfer-Encoding`, `Upgrade`) and any header a `Connection` field names are
851+ removed in both directions. `X-Forwarded-For`, `X-Forwarded-Proto` and
852+ `X-Forwarded-Host` are added unless `setForwardedHeaders(false)` turns them off.
853+
854+ Only the target's host is reached, plus the hosts named with `allowHost`, over
855+ `http` and `https`. Redirects are passed back to the client.
856+ `setFollowRedirects(n)` follows up to `n` hops itself, for a request without a
857+ body and only to allowed hosts. A hop to another host doesn't carry the
858+ client's `Authorization` and `Cookie` headers.
859+
860+ Failures go both ways. A client that disconnects cancels the upstream exchange.
861+ An upstream that fails before answering is a `502`. Once the status has been
862+ sent, a later upstream failure aborts the client's response, closing the
863+ HTTP/1.1 connection or resetting the HTTP/2 stream, so the client sees a
864+ truncated response and never one that looks complete. `setClient` gives the
865+ proxy a `WebClient` for its timeouts and client certificate.
866+
768867A static binary in a `scratch` image has no certificate store. Copy a
769868`ca-certificates.crt` into the image and name it in `CURL_CA_BUNDLE` or
770869`SSL_CERT_FILE`.
0 commit comments