mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-09-25 04:09:31 +03:00
282 lines
8.1 KiB
C
282 lines
8.1 KiB
C
/* Feel free to use this example code in any way
|
|
you see fit (Public Domain) */
|
|
|
|
/**
|
|
* @file callbackresponse.c
|
|
* @brief Example for generating a response on the fly with
|
|
* MHD_create_response_from_callback(). The body is not a
|
|
* file and is never assembled in memory as a whole; it is
|
|
* produced block by block into a single heap buffer that is
|
|
* refilled every time libmicrohttpd asks for more data.
|
|
* "GET /" streams the document with an unknown size (and thus
|
|
* with chunked transfer encoding), "GET /sized" streams the
|
|
* very same document with a "Content-Length" announced up
|
|
* front, and "GET /error" aborts the stream in the middle to
|
|
* show MHD_CONTENT_READER_END_WITH_ERROR.
|
|
* @author Christian Grothoff
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#ifndef _WIN32
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
#else
|
|
#include <winsock2.h>
|
|
#endif
|
|
#include <microhttpd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define PORT 8888
|
|
|
|
/**
|
|
* Length of one generated line, including the newline character.
|
|
*/
|
|
#define LINE_SIZE 64
|
|
|
|
/**
|
|
* Size of the heap buffer that is refilled over and over again. This
|
|
* is the amount of data we produce in one go; it has nothing to do
|
|
* with the block size we pass to MHD.
|
|
*/
|
|
#define BUFFER_SIZE (64 * LINE_SIZE)
|
|
|
|
/**
|
|
* Number of times the buffer is refilled before the stream ends. The
|
|
* complete document is BLOCK_COUNT * BUFFER_SIZE bytes long.
|
|
*/
|
|
#define BLOCK_COUNT 64
|
|
|
|
/**
|
|
* Block size we ask MHD to use when it queries our callback. MHD
|
|
* allocates a buffer of this size together with the response; it is an
|
|
* advisory value and MHD may ask for less.
|
|
*/
|
|
#define IO_BLOCK_SIZE 1024
|
|
|
|
/**
|
|
* For the "/error" URL: number of blocks to deliver before the stream
|
|
* is aborted with MHD_CONTENT_READER_END_WITH_ERROR.
|
|
*/
|
|
#define ABORT_AFTER 3
|
|
|
|
|
|
/**
|
|
* State we keep for one response that is being streamed. One such
|
|
* structure is allocated per request and released by the free callback
|
|
* that we hand to MHD_create_response_from_callback().
|
|
*/
|
|
struct ResponseContext
|
|
{
|
|
/**
|
|
* Heap buffer holding the block that is currently being delivered.
|
|
* Refilled by generate_block() whenever it has been drained.
|
|
*/
|
|
char *buf;
|
|
|
|
/**
|
|
* Number of valid bytes in @e buf.
|
|
*/
|
|
size_t fill;
|
|
|
|
/**
|
|
* Number of bytes of @e buf that were already handed to MHD.
|
|
*/
|
|
size_t off;
|
|
|
|
/**
|
|
* Number of blocks generated so far, and thus the number of the
|
|
* block that generate_block() will produce next.
|
|
*/
|
|
unsigned int block;
|
|
|
|
/**
|
|
* Non-zero if this stream is supposed to fail in the middle.
|
|
*/
|
|
int fail;
|
|
};
|
|
|
|
|
|
/**
|
|
* Produce the next block of the document into @a ctx->buf. This
|
|
* stands in for whatever expensive computation, database query or
|
|
* network fetch a real application would perform here. Note that the
|
|
* same buffer is reused for every block, so the memory needed by the
|
|
* server does not grow with the size of the document.
|
|
*
|
|
* Every line is exactly LINE_SIZE bytes long, which is what allows us
|
|
* to announce an exact "Content-Length" for the "/sized" URL.
|
|
*
|
|
* @param ctx the per-response state to refill
|
|
*/
|
|
static void
|
|
generate_block (struct ResponseContext *ctx)
|
|
{
|
|
unsigned int i;
|
|
char *line;
|
|
char label[32];
|
|
int len;
|
|
|
|
for (i = 0; i < BUFFER_SIZE / LINE_SIZE; i++)
|
|
{
|
|
line = &ctx->buf[i * LINE_SIZE];
|
|
memset (line, '.', LINE_SIZE);
|
|
len = snprintf (label,
|
|
sizeof (label),
|
|
"block %3u line %3u ",
|
|
ctx->block,
|
|
i);
|
|
if ((0 < len) && (LINE_SIZE > (size_t) len))
|
|
memcpy (line, label, (size_t) len);
|
|
line[LINE_SIZE - 1] = '\n';
|
|
}
|
|
ctx->fill = BUFFER_SIZE;
|
|
ctx->off = 0;
|
|
ctx->block++;
|
|
}
|
|
|
|
|
|
/**
|
|
* Callback used by MHD to obtain the next piece of the response body.
|
|
*
|
|
* @param cls our `struct ResponseContext`
|
|
* @param pos number of bytes already returned for this response
|
|
* @param buf where to copy the data
|
|
* @param max maximum number of bytes to copy to @a buf
|
|
* @return number of bytes written to @a buf,
|
|
* MHD_CONTENT_READER_END_OF_STREAM for the regular end,
|
|
* MHD_CONTENT_READER_END_WITH_ERROR to abort the transfer
|
|
*/
|
|
static ssize_t
|
|
content_reader (void *cls,
|
|
uint64_t pos,
|
|
char *buf,
|
|
size_t max)
|
|
{
|
|
struct ResponseContext *ctx = cls;
|
|
size_t ready;
|
|
|
|
/* We generate a pure stream and never look back, so the position is
|
|
of no interest to us. MHD guarantees that it is the sum of all
|
|
non-negative values we returned so far. */
|
|
(void) pos; /* Unused. Silent compiler warning. */
|
|
|
|
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;
|
|
if ((0 != ctx->fail) && (ABORT_AFTER == ctx->block))
|
|
return MHD_CONTENT_READER_END_WITH_ERROR;
|
|
generate_block (ctx);
|
|
}
|
|
/* Hand over as much of the current block as MHD is willing to take;
|
|
the rest follows on the next invocation. */
|
|
ready = ctx->fill - ctx->off;
|
|
if (ready > max)
|
|
ready = max;
|
|
memcpy (buf, &ctx->buf[ctx->off], ready);
|
|
ctx->off += ready;
|
|
return (ssize_t) ready;
|
|
}
|
|
|
|
|
|
/**
|
|
* Release the resources of a `struct ResponseContext`. MHD calls this
|
|
* once the response is destroyed, which happens no matter whether the
|
|
* body was transmitted completely or the client went away early.
|
|
*
|
|
* @param cls our `struct ResponseContext`
|
|
*/
|
|
static void
|
|
free_context (void *cls)
|
|
{
|
|
struct ResponseContext *ctx = cls;
|
|
|
|
free (ctx->buf);
|
|
free (ctx);
|
|
}
|
|
|
|
|
|
static enum MHD_Result
|
|
answer_to_connection (void *cls, struct MHD_Connection *connection,
|
|
const char *url, const char *method,
|
|
const char *version, const char *upload_data,
|
|
size_t *upload_data_size, void **req_cls)
|
|
{
|
|
struct ResponseContext *ctx;
|
|
struct MHD_Response *response;
|
|
enum MHD_Result ret;
|
|
uint64_t size;
|
|
(void) cls; /* Unused. Silent compiler warning. */
|
|
(void) version; /* Unused. Silent compiler warning. */
|
|
(void) upload_data; /* Unused. Silent compiler warning. */
|
|
(void) upload_data_size; /* Unused. Silent compiler warning. */
|
|
(void) req_cls; /* Unused. Silent compiler warning. */
|
|
|
|
if (0 != strcmp (method, MHD_HTTP_METHOD_GET))
|
|
return MHD_NO;
|
|
|
|
/* Allocate the state of this particular response before the response
|
|
object exists; from now on the free callback owns it. */
|
|
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;
|
|
ctx->fail = (0 == strcmp (url, "/error"));
|
|
|
|
/* "/sized" announces the length in advance, everything else is sent
|
|
with an unknown size and thus with chunked transfer encoding. */
|
|
if (0 == strcmp (url, "/sized"))
|
|
size = (uint64_t) BLOCK_COUNT * BUFFER_SIZE;
|
|
else
|
|
size = MHD_SIZE_UNKNOWN;
|
|
|
|
response = MHD_create_response_from_callback (size,
|
|
IO_BLOCK_SIZE,
|
|
&content_reader,
|
|
ctx,
|
|
&free_context);
|
|
if (NULL == response)
|
|
{
|
|
/* The response was never created, so nobody will call the free
|
|
callback for us. */
|
|
free_context (ctx);
|
|
return MHD_NO;
|
|
}
|
|
(void) MHD_add_response_header (response,
|
|
MHD_HTTP_HEADER_CONTENT_TYPE,
|
|
"text/plain");
|
|
ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
|
|
MHD_destroy_response (response);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int
|
|
main (void)
|
|
{
|
|
struct MHD_Daemon *daemon;
|
|
|
|
daemon = MHD_start_daemon (MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD,
|
|
PORT, NULL, NULL,
|
|
&answer_to_connection, NULL, MHD_OPTION_END);
|
|
if (NULL == daemon)
|
|
return 1;
|
|
|
|
(void) getchar ();
|
|
|
|
MHD_stop_daemon (daemon);
|
|
return 0;
|
|
}
|