aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/man7/libweb_http.750
-rw-r--r--examples/CMakeLists.txt1
-rw-r--r--examples/Makefile1
-rw-r--r--examples/async/CMakeLists.txt4
-rw-r--r--examples/async/Makefile29
-rw-r--r--examples/async/README.md14
-rw-r--r--examples/async/main.c232
-rw-r--r--handler.c33
-rw-r--r--http.c152
-rw-r--r--include/libweb/http.h10
10 files changed, 470 insertions, 56 deletions
diff --git a/doc/man7/libweb_http.7 b/doc/man7/libweb_http.7
index c3bec8b..a8e315d 100644
--- a/doc/man7/libweb_http.7
+++ b/doc/man7/libweb_http.7
@@ -562,7 +562,13 @@ struct http_response
unsigned long long \fIn\fP;
size_t \fIn_headers\fP;
void (*\fIfree\fP)(void *);
- int (*\fIstep\fP)(const struct http_payload *, struct http_response *, void *);
+ void *\fIstep_args\fP;
+
+ union http_step
+ {
+ int (*\fIlength\fP)(unsigned long long \fIlen\fP, const struct http_cookie *\fIc\fP, struct http_response *\fIr\fP, void *\fIuser\fP, void *\fIstep_args\fP);
+ int (*\fIpayload\fP)(const struct http_payload *\fIp\fP, struct http_response *\fIr\fP, void *\fIuser\fP, void *\fIstep_args\fP);
+ } \fIstep\fP;
};
.EE
.in
@@ -671,22 +677,54 @@ is a valid pointer. Otherwise,
must be a null pointer.
.I step
-allows implementations to generate a response in a non-blocking manner.
-When a response is not immediately available when a payload is received,
+allows implementations to deal with responses asynchronously
+i.e., without blocking other clients.
+For example, this can be useful to
+generate a response in a non-blocking manner.
+In other words, when a response is not immediately available
+when a payload is received,
.I step
must be assigned to a function that can generate it later.
.I libweb
shall then call this function immediately later,
without blocking other clients.
+
Assigning
-.I step
+.I step.length
+or
+.I step.payload
to a null pointer falls back to the default behaviour
i.e., a response is returned immediately.
Note that a non-null
-.I step
-shall always take priority, thus ignoring any other information inside the
+.I step.length
+or
+.I step.payload
+shall always take priority,
+.B thus ignoring any other information inside the
.I "struct http_response"
instance.
+.I step
+can be configured under the following situations:
+
+.IP \(bu 2
+Inside the
+.I length
+callback defined by the
+.I struct http_cfg
+instance. In this case,
+.I step.length
+must be assigned to a function to be later called by
+.IR libweb .
+
+.IP \(bu 2
+Inside the
+.I payload
+callback defined by the
+.I struct http_cfg
+instance. In this case,
+.I step.payload
+must be assigned to a function to be later called by
+.IR libweb .
.SS Transport Layer Security (TLS)
By design,
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 3370acb..b64430c 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -1,4 +1,5 @@
cmake_minimum_required(VERSION 3.13)
+add_subdirectory(async)
add_subdirectory(form)
add_subdirectory(headers)
add_subdirectory(hello)
diff --git a/examples/Makefile b/examples/Makefile
index b06f0a5..688895a 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -1,6 +1,7 @@
.POSIX:
DIRS = \
+ async \
form \
headers \
hello \
diff --git a/examples/async/CMakeLists.txt b/examples/async/CMakeLists.txt
new file mode 100644
index 0000000..4afd5ba
--- /dev/null
+++ b/examples/async/CMakeLists.txt
@@ -0,0 +1,4 @@
+cmake_minimum_required(VERSION 3.13)
+project(async C)
+add_executable(${PROJECT_NAME} main.c)
+target_link_libraries(${PROJECT_NAME} PRIVATE web)
diff --git a/examples/async/Makefile b/examples/async/Makefile
new file mode 100644
index 0000000..d0a42c2
--- /dev/null
+++ b/examples/async/Makefile
@@ -0,0 +1,29 @@
+.POSIX:
+
+PROJECT = async
+DEPS = \
+ main.o
+LIBWEB = ../../libweb.a
+DYNSTR = ../../dynstr/libdynstr.a
+CFLAGS = -I ../../include -I ../../dynstr/include
+LIBWEB_FLAGS = -L ../../ -l web
+DYNSTR_FLAGS = -L ../../dynstr -l dynstr
+
+all: $(PROJECT)
+
+clean:
+ rm -f $(DEPS)
+
+distclean: clean
+ rm -f $(PROJECT)
+
+FORCE:
+
+$(PROJECT): $(DEPS) $(LIBWEB) $(DYNSTR)
+ $(CC) $(LDFLAGS) $(DEPS) $(LIBWEB_FLAGS) $(DYNSTR_FLAGS) -o $@
+
+$(LIBWEB): FORCE
+ +cd ../../ && $(MAKE)
+
+$(DYNSTR): FORCE
+ +cd ../../dynstr && $(MAKE)
diff --git a/examples/async/README.md b/examples/async/README.md
new file mode 100644
index 0000000..ece15e7
--- /dev/null
+++ b/examples/async/README.md
@@ -0,0 +1,14 @@
+# Asynchronous HTTP response example
+
+This example shows how to generate HTTP responses asynchronously.
+
+## How to build
+
+If using `make(1)`, just run `make` from this directory.
+
+If using CMake, examples are built by default when configuring the project
+from [the top-level `CMakeLists.txt`](../../CMakeLists.txt).
+
+## How to run
+
+Run the executable without any command line arguments.
diff --git a/examples/async/main.c b/examples/async/main.c
new file mode 100644
index 0000000..dbf69a8
--- /dev/null
+++ b/examples/async/main.c
@@ -0,0 +1,232 @@
+/* As of FreeBSD 13.2, sigaction(2) still conforms to IEEE Std
+ * 1003.1-1990 (POSIX.1), which did not define SA_RESTART.
+ * FreeBSD supports it as an extension, but then _POSIX_C_SOURCE must
+ * not be defined. */
+#ifndef __FreeBSD__
+#define _POSIX_C_SOURCE 200809L
+#endif
+
+#include <dynstr.h>
+#include <libweb/handler.h>
+#include <libweb/html.h>
+#include <libweb/http.h>
+#include <errno.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static int send_response(const unsigned cnt, struct http_response *const r)
+{
+ int ret = -1;
+
+ struct html_node *html = html_node_alloc("html"), *body, *p;
+ struct dynstr d, text;
+
+ dynstr_init(&d);
+ dynstr_init(&text);
+
+ if (!html)
+ {
+ fprintf(stderr, "%s: html_node_alloc failed\n", __func__);
+ goto end;
+ }
+ else if (!(body = html_node_add_child(html, "body")))
+ {
+ fprintf(stderr, "%s: html_node_add_child body failed\n", __func__);
+ goto end;
+ }
+ else if (!(p = html_node_add_child(body, "p")))
+ {
+ fprintf(stderr, "%s: html_node_add_child p failed\n", __func__);
+ goto end;
+ }
+ else if (dynstr_append(&text, "It took %u iterations to generate "
+ "this response!", cnt))
+ {
+ fprintf(stderr, "%s: dynstr_append failed\n", __func__);
+ goto end;
+ }
+ else if (html_node_set_value(p, text.str))
+ {
+ fprintf(stderr, "%s: html_node_set_value p failed\n", __func__);
+ goto end;
+ }
+ else if (html_serialize(html, &d))
+ {
+ fprintf(stderr, "%s: html_serialize failed\n", __func__);
+ goto end;
+ }
+
+ *r = (const struct http_response)
+ {
+ .status = HTTP_STATUS_OK,
+ .buf.rw = d.str,
+ .n = d.len,
+ .free = free
+ };
+
+ if (http_response_add_header(r, "Content-Type", "text/html"))
+ {
+ fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
+ goto end;
+ }
+
+ ret = 0;
+
+end:
+ dynstr_free(&text);
+ html_node_free(html);
+
+ if (ret)
+ dynstr_free(&d);
+
+ return ret;
+}
+
+static int step(const struct http_payload *const pl,
+ struct http_response *const r, void *const user, void *step_args)
+{
+ unsigned *const cnt = step_args;
+ const unsigned max = 10;
+
+ fprintf(stderr, "%s: step %u\n", __func__, *cnt);
+
+ if (++(*cnt) >= max)
+ return send_response(*cnt, r);
+
+ return 0;
+}
+
+static int hello(const struct http_payload *const pl,
+ struct http_response *const r, void *const user)
+{
+ unsigned *const cnt = malloc(sizeof *cnt);
+
+ if (!cnt)
+ {
+ fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
+ return -1;
+ }
+
+ *r = (const struct http_response)
+ {
+ .step.payload = step,
+ .step_args = cnt
+ };
+
+ *cnt = 0;
+ return 0;
+}
+
+static int on_length(const unsigned long long len,
+ const struct http_cookie *const c, struct http_response *const r,
+ void *const user)
+{
+ *r = (const struct http_response)
+ {
+ .status = HTTP_STATUS_FORBIDDEN
+ };
+
+ return 1;
+}
+
+struct handler *handler;
+
+static void handle_signal(const int signum)
+{
+ switch (signum)
+ {
+ case SIGINT:
+ /* Fall through. */
+ case SIGTERM:
+ handler_notify_close(handler);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static int init_signals(void)
+{
+ struct sigaction sa =
+ {
+ .sa_handler = handle_signal,
+ .sa_flags = SA_RESTART
+ };
+
+ sigemptyset(&sa.sa_mask);
+
+ static const struct signal
+ {
+ int signal;
+ const char *name;
+ } signals[] =
+ {
+ {.signal = SIGINT, .name = "SIGINT"},
+ {.signal = SIGTERM, .name = "SIGTERM"},
+ {.signal = SIGPIPE, .name = "SIGPIPE"}
+ };
+
+ for (size_t i = 0; i < sizeof signals / sizeof *signals; i++)
+ {
+ const struct signal *const s = &signals[i];
+
+ if (sigaction(s->signal, &sa, NULL))
+ {
+ fprintf(stderr, "%s: sigaction(2) %s: %s\n",
+ __func__, s->name, strerror(errno));
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int ret = EXIT_FAILURE;
+ const struct handler_cfg cfg =
+ {
+ .length = on_length
+ };
+
+ static const char *const urls[] = {"/", "/index.html"};
+
+ if (!(handler = handler_alloc(&cfg)))
+ {
+ fprintf(stderr, "%s: handler_alloc failed\n", __func__);
+ goto end;
+ }
+
+ for (size_t i = 0; i < sizeof urls / sizeof *urls; i++)
+ if (handler_add(handler, urls[i], HTTP_OP_GET, hello, NULL))
+ {
+ fprintf(stderr, "%s: handler_add failed\n", __func__);
+ goto end;
+ }
+
+ unsigned short outport;
+
+ if (handler_listen(handler, 0, &outport))
+ {
+ fprintf(stderr, "%s: handler_listen failed\n", __func__);
+ goto end;
+ }
+
+ printf("Listening on port %hu\n", outport);
+
+ if (handler_loop(handler))
+ {
+ fprintf(stderr, "%s: handler_loop failed\n", __func__);
+ goto end;
+ }
+
+ ret = EXIT_SUCCESS;
+
+end:
+ handler_free(handler);
+ return ret;
+}
diff --git a/handler.c b/handler.c
index 8a777fa..98e6118 100644
--- a/handler.c
+++ b/handler.c
@@ -28,7 +28,8 @@ struct handler
struct handler *h;
struct server_client *c;
struct http_ctx *http;
- int (*fn)(const struct http_payload *, struct http_response *, void *);
+ union http_step step;
+ void *step_args;
struct client *next;
} *clients;
@@ -61,15 +62,19 @@ static int on_payload(const struct http_payload *const p,
if (e->op == p->op && !wildcard_cmp(p->resource, e->url, true))
{
+ union http_step *const s = &c->step;
int ret;
- if (c->fn)
- ret = c->fn(p, r, e->user);
+ if (s->payload)
+ ret = s->payload(p, r, e->user, c->step_args);
else
ret = e->f(p, r, e->user);
if (!ret)
- c->fn = r->step;
+ {
+ s->payload = r->step.payload;
+ c->step_args = r->step_args;
+ }
return ret;
}
@@ -92,10 +97,24 @@ static int on_length(const unsigned long long len,
struct client *const cl = user;
struct handler *const h = cl->h;
- if (h->cfg.length)
- return h->cfg.length(len, c, r, h->cfg.user);
+ if (!h->cfg.length)
+ return 0;
- return 0;
+ union http_step *const s = &cl->step;
+ int ret;
+
+ if (s->length)
+ ret = s->length(len, c, r, h->cfg.user, cl->step_args);
+ else
+ ret = h->cfg.length(len, c, r, h->cfg.user);
+
+ if (!ret)
+ {
+ s->length = r->step.length;
+ cl->step_args = r->step_args;
+ }
+
+ return ret;
}
static struct client *find_or_alloc_client(struct handler *const h,
diff --git a/http.c b/http.c
index 67aa62e..c173c3d 100644
--- a/http.c
+++ b/http.c
@@ -103,6 +103,8 @@ struct http_ctx
struct http_header *headers;
bool has_length, expect_continue;
int (*response)(struct http_ctx *);
+ void *buf;
+ size_t buflen;
} ctx;
struct write_ctx
@@ -653,6 +655,7 @@ static void ctx_free(struct ctx *const c)
free(c->headers);
free(c->args);
+ free(c->buf);
*c = (const struct ctx){0};
}
@@ -1220,7 +1223,7 @@ static int process_payload(struct http_ctx *const h)
ctx_free(c);
return ret;
}
- else if (!r->step)
+ else if (!r->step.payload)
{
h->wctx.op = c->op;
ctx_free(c);
@@ -1475,29 +1478,15 @@ static int check_length(struct http_ctx *const h)
return h->cfg.length(c->u2.payload.len, &cookie, &h->wctx.r, h->cfg.user);
}
-static int process_expect(struct http_ctx *const h)
+static int process_payload_expect(struct http_ctx *const h)
{
struct ctx *const c = &h->ctx;
struct write_ctx *const w = &h->wctx;
- const int res = check_length(h);
-
- if (res)
- {
- if (res < 0)
- {
- fprintf(stderr, "%s: check_length failed\n", __func__);
- return res;
- }
-
- w->close = true;
- return start_response(h);
- }
-
+ struct http_response *const r = &h->wctx.r;
struct http_payload p = ctx_to_payload(c);
p.expect_continue = true;
- struct http_response *const r = &h->wctx.r;
const int ret = h->cfg.payload(&p, r, h->cfg.user);
if (ret)
@@ -1505,7 +1494,7 @@ static int process_expect(struct http_ctx *const h)
ctx_free(c);
return ret;
}
- else if (!r->step)
+ else if (!r->step.payload)
{
w->op = c->op;
c->state = BODY_LINE;
@@ -1515,6 +1504,30 @@ static int process_expect(struct http_ctx *const h)
return 0;
}
+static int process_expect(struct http_ctx *const h)
+{
+ struct ctx *const c = &h->ctx;
+ struct write_ctx *const w = &h->wctx;
+ struct http_response *const r = &h->wctx.r;
+ const int res = check_length(h);
+
+ if (res)
+ {
+ if (res < 0)
+ {
+ fprintf(stderr, "%s: check_length failed\n", __func__);
+ return res;
+ }
+
+ w->close = true;
+ return start_response(h);
+ }
+ else if (!r->step.length)
+ c->response = process_payload_expect;
+
+ return 0;
+}
+
static int process_get(struct http_ctx *const h)
{
int ret;
@@ -1530,7 +1543,7 @@ static int process_get(struct http_ctx *const h)
ctx_free(c);
return ret;
}
- else if (!r->step)
+ else if (!r->step.payload)
{
w->op = c->op;
ctx_free(c);
@@ -1544,6 +1557,34 @@ static int process_get(struct http_ctx *const h)
return 0;
}
+static int process_boundary(struct http_ctx *const h)
+{
+ struct ctx *const c = &h->ctx;
+ struct write_ctx *const w = &h->wctx;
+ const int res = check_length(h);
+
+ if (res)
+ {
+ if (res < 0)
+ {
+ fprintf(stderr, "%s: check_length failed\n",
+ __func__);
+ return res;
+ }
+
+ w->close = true;
+ ctx_free(c);
+ return start_response(h);
+ }
+ else if (!w->r.step.length)
+ {
+ c->response = NULL;
+ c->state = BODY_LINE;
+ }
+
+ return 0;
+}
+
static int header_cr_line(struct http_ctx *const h)
{
const char *const line = (const char *)h->line;
@@ -1575,20 +1616,8 @@ static int header_cr_line(struct http_ctx *const h)
}
else if (c->boundary)
{
- const int res = check_length(h);
-
- if (res)
- {
- if (res < 0)
- {
- fprintf(stderr, "%s: check_length failed\n",
- __func__);
- return res;
- }
-
- h->wctx.close = true;
- return start_response(h);
- }
+ c->response = process_boundary;
+ return 0;
}
c->state = BODY_LINE;
@@ -1640,7 +1669,7 @@ static int send_payload(struct http_ctx *const h,
ctx_free(c);
return ret;
}
- else if (!r->step)
+ else if (!r->step.payload)
{
ctx_free(c);
return start_response(h);
@@ -2524,28 +2553,67 @@ static int read_buf(struct http_ctx *const h, const char *const buf,
return -1;
}
+static int storebuf(struct http_ctx *const h, const void *const src,
+ const size_t n)
+{
+ struct ctx *const c = &h->ctx;
+ void *const dst = malloc(n);
+
+ if (!dst)
+ {
+ fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
+ return -1;
+ }
+
+ memcpy((c->buf = dst), src, (c->buflen = n));
+ return 0;
+}
+
+static int loadbuf(struct http_ctx *const h, void *const dst, const size_t n)
+{
+ struct ctx *const c = &h->ctx;
+
+ memcpy(dst, c->buf, c->buflen);
+ free(c->buf);
+ c->buf = NULL;
+ return c->buflen;
+}
+
int http_read(struct http_ctx *const h, bool *const close)
{
+ struct ctx *const c = &h->ctx;
char buf[BUFSIZ];
- const int r = h->cfg.read(buf, sizeof buf, h->cfg.user);
+ int ret, r;
- if (r <= 0)
+ if (c->buf)
+ r = loadbuf(h, buf, sizeof buf);
+ else if ((r = h->cfg.read(buf, sizeof buf, h->cfg.user)) <= 0)
return rw_error(r, close);
size_t rem = r;
while (rem)
{
- const int ret = read_buf(h, &buf[r - rem], &rem);
-
- if (ret)
+ if ((ret = read_buf(h, &buf[r - rem], &rem)))
+ goto failure;
+ else if (c->response && rem)
{
- ctx_free(&h->ctx);
- return ret;
+ if (storebuf(h, &buf[r - rem], rem))
+ {
+ fprintf(stderr, "%s: storebuf failed\n", __func__);
+ ret = -1;
+ goto failure;
+ }
+
+ break;
}
}
return 0;
+
+failure:
+ ctx_free(&h->ctx);
+ return ret;
}
static int append_expire(struct dynstr *const d)
@@ -2615,7 +2683,7 @@ int http_update(struct http_ctx *const h, bool *const write, bool *const close)
else
ret = w->pending ? http_write(h, close) : http_read(h, close);
- *write = c->response || w->pending;
+ *write = c->buf || c->response || w->pending;
return ret;
}
diff --git a/include/libweb/http.h b/include/libweb/http.h
index f18b5c4..87edf8a 100644
--- a/include/libweb/http.h
+++ b/include/libweb/http.h
@@ -109,7 +109,15 @@ struct http_response
unsigned long long n;
size_t n_headers;
void (*free)(void *);
- int (*step)(const struct http_payload *, struct http_response *, void *);
+ void *step_args;
+
+ union http_step
+ {
+ int (*length)(unsigned long long len, const struct http_cookie *c,
+ struct http_response *r, void *user, void *step_args);
+ int (*payload)(const struct http_payload *p, struct http_response *r,
+ void *user, void *step_args);
+ } step;
};
struct http_cfg