1<!-- 2Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. 3 4SPDX-License-Identifier: curl 5--> 6 7# curl client writers 8 9Client writers is a design in the internals of libcurl, not visible in its public API. They were started 10in curl v8.5.0. This document describes the concepts, its high level implementation and the motivations. 11 12## Naming 13 14`libcurl` operates between clients and servers. A *client* is the application using libcurl, like the command line tool `curl` itself. Data to be uploaded to a server is **read** from the client and **send** to the server, the servers response is **received** by `libcurl` and then **written** to the client. 15 16With this naming established, client writers are concerned with writing responses from the server to the application. Applications register callbacks via `CURLOPT_WRITEFUNCTION` and `CURLOPT_HEADERFUNCTION` to be invoked by `libcurl` when the response is received. 17 18## Invoking 19 20All code in `libcurl` that handles response data is ultimately expected to forward this data via `Curl_client_write()` to the application. The exact prototype of this function is: 21 22``` 23CURLcode Curl_client_write(struct Curl_easy *data, int type, const char *buf, size_t blen); 24``` 25The `type` argument specifies what the bytes in `buf` actually are. The following bits are defined: 26 27``` 28#define CLIENTWRITE_BODY (1<<0) /* non-meta information, BODY */ 29#define CLIENTWRITE_INFO (1<<1) /* meta information, not a HEADER */ 30#define CLIENTWRITE_HEADER (1<<2) /* meta information, HEADER */ 31#define CLIENTWRITE_STATUS (1<<3) /* a special status HEADER */ 32#define CLIENTWRITE_CONNECT (1<<4) /* a CONNECT related HEADER */ 33#define CLIENTWRITE_1XX (1<<5) /* a 1xx response related HEADER */ 34#define CLIENTWRITE_TRAILER (1<<6) /* a trailer HEADER */ 35``` 36 37The main types here are `CLIENTWRITE_BODY` and `CLIENTWRITE_HEADER`. They are 38mutually exclusive. The other bits are enhancements to `CLIENTWRITE_HEADER` to 39specify what the header is about. They are only used in HTTP and related 40protocols (RTSP and WebSocket). 41 42The implementation of `Curl_client_write()` uses a chain of *client writer* instances to process the call and make sure that the bytes reach the proper application callbacks. This is similar to the design of connection filters: client writers can be chained to process the bytes written through them. The definition is: 43 44``` 45struct Curl_cwtype { 46 const char *name; 47 CURLcode (*do_init)(struct Curl_easy *data, 48 struct Curl_cwriter *writer); 49 CURLcode (*do_write)(struct Curl_easy *data, 50 struct Curl_cwriter *writer, int type, 51 const char *buf, size_t nbytes); 52 void (*do_close)(struct Curl_easy *data, 53 struct Curl_cwriter *writer); 54}; 55 56struct Curl_cwriter { 57 const struct Curl_cwtype *cwt; /* type implementation */ 58 struct Curl_cwriter *next; /* Downstream writer. */ 59 Curl_cwriter_phase phase; /* phase at which it operates */ 60}; 61``` 62 63`Curl_cwriter` is a writer instance with a `next` pointer to form the chain. It has a type `cwt` which provides the implementation. The main callback is `do_write()` that processes the data and calls then the `next` writer. The others are for setup and tear down. 64 65## Phases and Ordering 66 67Since client writers may transform the bytes written through them, the order in which the are called is relevant for the outcome. When a writer is created, one property it gets is the `phase` in which it operates. Writer phases are defined like: 68 69``` 70typedef enum { 71 CURL_CW_RAW, /* raw data written, before any decoding */ 72 CURL_CW_TRANSFER_DECODE, /* remove transfer-encodings */ 73 CURL_CW_PROTOCOL, /* after transfer, but before content decoding */ 74 CURL_CW_CONTENT_DECODE, /* remove content-encodings */ 75 CURL_CW_CLIENT /* data written to client */ 76} Curl_cwriter_phase; 77``` 78 79If a writer for phase `PROTOCOL` is added to the chain, it is always added *after* any `RAW` or `TRANSFER_DECODE` and *before* any `CONTENT_DECODE` and `CLIENT` phase writer. If there is already a writer for the same phase present, the new writer is inserted just before that one. 80 81All transfers have a chain of 3 writers by default. A specific protocol handler may alter that by adding additional writers. The 3 standard writers are (name, phase): 82 831. `"raw", CURL_CW_RAW `: if the transfer is verbose, it forwards the body data to the debug function. 841. `"download", CURL_CW_PROTOCOL`: checks that protocol limits are kept and updates progress counters. When a download has a known length, it checks that it is not exceeded and errors otherwise. 851. `"client", CURL_CW_CLIENT`: the main work horse. It invokes the application callbacks or writes to the configured file handles. It chops large writes into smaller parts, as documented for `CURLOPT_WRITEFUNCTION`. If also handles *pausing* of transfers when the application callback returns `CURL_WRITEFUNC_PAUSE`. 86 87With these writers always in place, libcurl's protocol handlers automatically have these implemented. 88 89## Enhanced Use 90 91HTTP is the protocol in curl that makes use of the client writer chain by 92adding writers to it. When the `libcurl` application set 93`CURLOPT_ACCEPT_ENCODING` (as `curl` does with `--compressed`), the server is 94offered an `Accept-Encoding` header with the algorithms supported. The server 95then may choose to send the response body compressed. For example using `gzip` 96or `brotli` or even both. 97 98In the server's response, if there is a `Content-Encoding` header listing the 99encoding applied. If supported by `libcurl` it then decompresses the content 100before writing it out to the client. How does it do that? 101 102The HTTP protocol adds client writers in phase `CURL_CW_CONTENT_DECODE` on 103seeing such a header. For each encoding listed, it adds the corresponding 104writer. The response from the server is then passed through 105`Curl_client_write()` to the writers that decode it. If several encodings had 106been applied the writer chain decodes them in the proper order. 107 108When the server provides a `Content-Length` header, that value applies to the 109*compressed* content. Length checks on the response bytes must happen *before* 110it gets decoded. That is why this check happens in phase `CURL_CW_PROTOCOL` 111which always is ordered before writers in phase `CURL_CW_CONTENT_DECODE`. 112 113What else? 114 115Well, HTTP servers may also apply a `Transfer-Encoding` to the body of a response. The most well-known one is `chunked`, but algorithms like `gzip` and friends could also be applied. The difference to content encodings is that decoding needs to happen *before* protocol checks, for example on length, are done. 116 117That is why transfer decoding writers are added for phase `CURL_CW_TRANSFER_DECODE`. Which makes their operation happen *before* phase `CURL_CW_PROTOCOL` where length may be checked. 118 119## Summary 120 121By adding the common behavior of all protocols into `Curl_client_write()` we make sure that they do apply everywhere. Protocol handler have less to worry about. Changes to default behavior can be done without affecting handler implementations. 122 123Having a writer chain as implementation allows protocol handlers with extra needs, like HTTP, to add to this for special behavior. The common way of writing the actual response data stays the same. 124