/
githubmirror
/
libmicrohttpd
Обзор
Документация
Войти
/
githubmirror
/
libmicrohttpd
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
doc/chapters/callbackresponse.inc
476 строк
18 KB
Christian Grothoff
add tutorial for #5657
28 июл 2026, 23:47
Не верифицирован
28 июл 2026, 23:47
8dbbdd7
Код
Авторство
О чём код?
All responses we have built so far were complete before they were queued. @code{MHD_create_response_from_buffer()} needs the whole answer to be sitting in memory already, and @code{MHD_create_response_from_fd()} needs it to be a file that the operating system can read for us. Neither fits an answer that is computed while it is being sent: the output of a long-running report, a log file that is filtered on the fly, the result of a database cursor, or simply a document that is far too large to be worth assembling in a buffer first. For those cases @emph{libmicrohttpd} lets the application hand over a callback instead of the data. Whenever @emph{MHD} has room on the socket, it calls that function and asks for the next few bytes. The application only ever needs one modest buffer, and the client starts receiving the answer long before the last byte has been produced. @heading Creating the response The response object is created with @verbatim struct MHD_Response * MHD_create_response_from_callback (uint64_t size, size_t block_size, MHD_ContentReaderCallback crc, void *crc_cls, MHD_ContentReaderFreeCallback crfc); @end verbatim @noindent The five arguments are: @table @code @item size The total length of the body in bytes, or the constant @code{MHD_SIZE_UNKNOWN} if the application does not know it in advance. If a concrete number is given, @emph{MHD} announces it as @code{Content-Length} and expects the callback to deliver exactly that many bytes. @item block_size The buffer size @emph{MHD} should use when it queries the callback. The value must not be zero---@code{MHD_create_response_from_callback()} returns @code{NULL} otherwise. @emph{MHD} allocates a buffer of that size together with the response object. Note that this buffer is only used for responses of known size; when chunked transfer encoding is in use (see below) the data is collected in the connection's own memory pool instead, and @code{block_size} has no influence on how much @emph{MHD} asks for. In either case the value is advisory: the callback must always look at the @code{max} argument it is passed and never write more than that. @item crc The content reader callback itself, described in the next section. @item crc_cls An arbitrary pointer that is passed back to @code{crc} on every invocation. This is where the per-request state goes. @item crfc A callback that is invoked once, when the response object is destroyed, so that @code{crc_cls} can be released. May be @code{NULL} if there is nothing to clean up. @end table @heading The content reader callback The callback has the following signature: @verbatim typedef ssize_t (*MHD_ContentReaderCallback) (void *cls, uint64_t pos, char *buf, size_t max); @end verbatim @noindent @code{cls} is the @code{crc_cls} pointer given at creation time. @code{buf} points to the memory @emph{MHD} wants filled and @code{max} is the number of bytes that may be written there. The @code{pos} argument deserves a closer look, because its meaning depends on how the response is used. For a response created from a file descriptor, @emph{MHD} uses it as a file offset and reads with @code{pread()}; that is what makes such a response object reusable for many connections. For a response that is created fresh for each request---which is what we do here---@emph{MHD} guarantees that @code{pos} is simply the sum of all non-negative values the callback has returned so far. In other words: for a stream, @code{pos} is a monotonically increasing count of the bytes already produced, and the callback will never be asked to go back. A generator that has no use for random access can ignore @code{pos} completely. Do note that the guarantee only holds as long as the response object is used for one request; if the same object is queued for several connections, the same content reader may be asked for the same data more than once, and sharing one mutable buffer between them would be wrong. The return value tells @emph{MHD} what happened: @table @asis @item a positive number That many bytes were written to @code{buf}. Returning more than @code{max} is a fatal application error and makes @emph{MHD} close the connection. @item @code{MHD_CONTENT_READER_END_OF_STREAM} The body is complete. With chunked encoding @emph{MHD} terminates the chunk sequence properly and appends any footers; without chunked encoding and an unknown size it simply closes the connection. If a concrete @code{size} was announced, ending the stream early is not legal, and @emph{MHD} treats it essentially like an error---the client receives fewer bytes than the @code{Content-Length} promised. @item @code{MHD_CONTENT_READER_END_WITH_ERROR} Something went wrong while generating the data; see below. @item @code{0} No data is available @emph{right now}, but the stream is not over. @end table @noindent That last case is the dangerous one. The header file states that returning zero is legal only when @emph{MHD} is @emph{not} using an internal sockets polling mode, and this is not a formality: @emph{MHD} does not diagnose the situation, it simply arranges for the next poll to time out immediately and calls the callback again. A callback that returns zero while a request is stalled for a third of a second is called several million times in that period and pins a CPU core at 100%. In external polling mode the effect is milder---the callback is retried whenever the application calls one of the @code{MHD_run*()} functions---but it is still a spin. The proper answer for "no data yet" is to suspend the connection, which is covered further down. @heading Keeping state per request Because the callback is a plain C function, everything it needs to know has to travel through @code{crc_cls}. The natural shape of a streaming handler is therefore: allocate a context on the heap, allocate its data buffer, create the response with that context as @code{crc_cls}, and let the free callback dispose of both. @verbatim struct ResponseContext { char *buf; /* heap buffer, refilled again and again */ size_t fill; /* number of valid bytes in buf */ size_t off; /* number of bytes of buf given to MHD */ unsigned int block; /* number of blocks produced so far */ }; @end verbatim @noindent The two offsets are what make the buffer independent of @emph{MHD}'s appetite. A block is generated once and may then be handed out over several invocations, because @code{max} can be smaller than what we have ready: @verbatim static ssize_t content_reader (void *cls, uint64_t pos, char *buf, size_t max) { struct ResponseContext *ctx = cls; size_t ready; (void) pos; if (ctx->off == ctx->fill) { /* Everything we had was passed on, produce the next block. */ if (BLOCK_COUNT == ctx->block) return MHD_CONTENT_READER_END_OF_STREAM; generate_block (ctx); } ready = ctx->fill - ctx->off; if (ready > max) ready = max; memcpy (buf, &ctx->buf[ctx->off], ready); ctx->off += ready; return (ssize_t) ready; } @end verbatim @noindent @code{generate_block()} is the place where a real application would run its expensive computation, query, or network fetch. The important property is that the same buffer is reused every time, so the memory the server needs does not grow with the size of the document. The handler assembles all of this and, from that moment on, hands ownership of the context to the response object: @verbatim ctx = malloc (sizeof (struct ResponseContext)); if (NULL == ctx) return MHD_NO; ctx->buf = malloc (BUFFER_SIZE); if (NULL == ctx->buf) { free (ctx); return MHD_NO; } ctx->fill = 0; ctx->off = 0; ctx->block = 0; response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN, IO_BLOCK_SIZE, &content_reader, ctx, &free_context); if (NULL == response) { /* No response object exists, so nobody will call the free callback for us. */ free_context (ctx); return MHD_NO; } ret = MHD_queue_response (connection, MHD_HTTP_OK, response); MHD_destroy_response (response); @end verbatim @noindent Note the error path: as long as @code{MHD_create_response_from_callback()} has not returned a response, the free callback will never run and the application must clean up itself. @heading Releasing the state The free callback is trivial: @verbatim static void free_context (void *cls) { struct ResponseContext *ctx = cls; free (ctx->buf); free (ctx); } @end verbatim @noindent @emph{MHD} invokes it when the last reference to the response object goes away. The call to @code{MHD_destroy_response()} in the handler does not trigger it, because queuing the response took a reference; the callback runs when the connection is finished with the response. That happens after a completed body, but equally after a client that closed the socket in the middle of the download, after a timeout, and when the daemon is stopped. This is precisely what makes the free callback the right place for the cleanup: there is no other hook that covers all of these cases. @heading Unknown size and chunked encoding Passing @code{MHD_SIZE_UNKNOWN} as the size leaves @emph{MHD} with the problem of telling the client where the body ends. For an HTTP/1.1 client it solves it by switching to chunked transfer encoding: it adds a @code{Transfer-Encoding: chunked} header and frames every piece the callback returns as one chunk, terminating the body with a zero-length chunk when the callback reports the end of the stream. Nothing has to be done for this; it is a consequence of the unknown size. @verbatim < HTTP/1.1 200 OK < Content-Type: text/plain < Transfer-Encoding: chunked @end verbatim @noindent An HTTP/1.0 client cannot be sent chunks. In that case @emph{MHD} falls back to the only other framing the protocol offers and marks the connection as one that must be closed: the body is sent raw, without @code{Content-Length}, and its end is the end of the connection. The data arrives intact, but the client has no way to distinguish a complete answer from one that was cut short. If the size @emph{is} known---and in the example program the URL @code{/sized} shows this variant---it should be announced, simply by passing the number instead of @code{MHD_SIZE_UNKNOWN}. @emph{MHD} then emits a normal @code{Content-Length} header, no chunk framing is added, and the callback is queried with @code{max} limited by both @code{block_size} and the number of bytes still outstanding. The callback must produce exactly the announced number of bytes. @heading Aborting a response Generating data can fail halfway through---the database connection drops, a file disappears, an allocation fails. Since the response headers, and probably a good part of the body, have already been sent, there is no way to turn the answer into an error page any more. Returning @code{MHD_CONTENT_READER_END_WITH_ERROR} is the way to give up: @emph{MHD} logs the condition and closes the connection immediately, without writing the terminating chunk. What the client sees depends on the framing. With chunked encoding it is an abnormally terminated chunk stream, and a well-behaved client reports it as such---@code{curl} for instance fails with "transfer closed with outstanding read data remaining" and a non-zero exit status. With an announced @code{Content-Length} the client notices that the body is shorter than promised. Only in the HTTP/1.0 case discussed above, where the end of the body is the end of the connection, is the failure indistinguishable from a normal ending; that is a property of the protocol and not something @emph{MHD} can repair. The URL @code{/error} of the example program aborts the stream after a few blocks so that this can be observed. @heading When the data is not ready yet So far the generator could always produce the next block on the spot. If it cannot---because the data is coming from another thread, another process, or the network---then, as explained above, returning @code{0} in an internal polling mode turns the daemon into a busy loop. The supported answer is to take the connection out of the event loop until the data has arrived, with the pair @verbatim void MHD_suspend_connection (struct MHD_Connection *connection); void MHD_resume_connection (struct MHD_Connection *connection); @end verbatim @noindent The daemon has to be started with the @code{MHD_ALLOW_SUSPEND_RESUME} flag; both functions call @code{MHD_PANIC()} and abort the process otherwise. The flag implies @code{MHD_USE_ITC}, so that a resumed connection wakes the polling thread up instead of waiting for the next timeout. Suspending is not available for daemons running with @code{MHD_USE_THREAD_PER_CONNECTION}, and the daemon must not be stopped while connections are still suspended. @code{MHD_suspend_connection()} may only be called from the access handler or from the content reader callback, which means the connection handle has to be stored in the context struct so that the callback can reach it. The pattern is then: @verbatim if (! ctx->data_available) { MHD_suspend_connection (ctx->connection); return 0; } @end verbatim @noindent @code{MHD_resume_connection()}, in contrast, may be called from any thread and at any time. In particular it is explicitly allowed to call it before the suspension has taken effect: the producer may already be done by the time the callback gets around to suspending. @emph{MHD} handles this by remembering the resume request and, when the suspension is finally processed, cancelling it instead of parking the connection, so the wakeup can never be lost. This is what makes the "check, suspend, return 0" sequence above safe without a lock of its own. The difference is dramatic in practice. A callback that stalls for three hundred milliseconds and returns @code{0} is entered several million times and burns a full core; the same callback suspending the connection is entered exactly once more and the daemon uses no measurable CPU time at all. Suspending brings requirements of its own---on who may call what from which thread, on how a worker thread wakes up a blocked event loop, and on who releases the shared state when a client disappears halfway through. @ref{Answering from another thread} works all of that out on a complete program. @heading Trailers Chunked responses may carry trailers, that is header fields that are transmitted after the body. They are added with @verbatim MHD_add_response_footer (response, "X-Checksum", "deadbeef"); @end verbatim @noindent @emph{MHD} writes them out after the terminating zero-length chunk: @verbatim 0 X-Checksum: deadbeef @end verbatim @noindent Footers may still be added while the body is being generated---even from inside the content reader callback, immediately before returning @code{MHD_CONTENT_READER_END_OF_STREAM}---which is what makes them useful in the first place: a checksum or a row count over data that has only just been produced cannot be put into the headers, because those are long gone. This is safe as long as the response object belongs to a single request, as it does in the pattern used here. @emph{MHD} does not generate a @code{Trailer} response header by itself; if the client should be told in advance which fields to expect, add it with @code{MHD_add_response_header()}. Be aware that many clients discard trailers silently. @heading Example code The complete program is available as @code{callbackresponse.c}. It serves a document of sixty-four blocks that are produced one at a time into a single heap buffer. @code{GET /} streams it with @code{MHD_SIZE_UNKNOWN} and thus with chunked encoding, @code{GET /sized} streams the same bytes with a @code{Content-Length} announced up front, and @code{GET /error} aborts in the middle. @heading Remarks Calls into the content reader of one response object are serialized by @emph{MHD}, so a per-request context needs no locking of its own as long as no other thread touches it. As soon as a producer thread is involved---the suspend/resume case---the shared fields do need protection. The chunk boundaries are not under the application's control and carry no meaning: a client may receive the bytes of one callback invocation spread over several chunks or, in other configurations, several invocations combined. Chunked encoding is a transport detail, and any record structure the application needs must be part of the body itself. Finally, returning very small amounts of data per invocation is correct but wasteful, since every invocation may become its own chunk with its own framing overhead. If the producer has more data ready, it should fill as much of @code{buf} as @code{max} permits. @heading Exercises @itemize @bullet @item Add a URL that reports how far the transfer has progressed by writing the value of @code{pos} into every line, and confirm with a slow client (@code{curl --limit-rate}) that it really is the number of bytes already sent, and not a block counter. @item Register a request completion callback with @code{MHD_OPTION_NOTIFY_COMPLETED} and print the termination code. Then download the document, interrupt the client in the middle, and watch in which order the completion callback and the content reader's free callback are invoked, and that the free callback runs in both cases. @item Turn the generator into an asynchronous one. Start a thread that appends a line to a shared buffer once per second, keep the connection handle in the context struct, and suspend the connection whenever the buffer is empty. Do not forget @code{MHD_ALLOW_SUSPEND_RESUME} when starting the daemon. Then remove the call to @code{MHD_suspend_connection()}, leaving only the @code{return 0}, and compare the CPU usage of the server process. A complete solution, together with the ownership rules that a shared buffer between a worker and the event loop needs, is the subject of @ref{Answering from another thread}. @item Compute a checksum over the generated document while it is being sent and append it as a trailer. Watch it on the wire---most command line clients will not show it to you, so a raw @code{nc} or a small script speaking HTTP by hand is the easier tool here. @end itemize