+ * Copyright (c) 2015 - 2018, Přemysl Janouch
*
* Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
+ * purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
--
cgit v1.2.3-70-g09d2
From b312c022ae28e0852cba7430465971a2a391f016 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Sun, 24 Jun 2018 00:21:32 +0200
Subject: Update README
---
README.adoc | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/README.adoc b/README.adoc
index 2f80d73..80d708f 100644
--- a/README.adoc
+++ b/README.adoc
@@ -29,7 +29,7 @@ Build dependencies: CMake, pkg-config, help2man, libmagic,
liberty (included), http-parser (included) +
Runtime dependencies: libev, Jansson
- $ git clone --recursive https://github.com/pjanouch/acid.git
+ $ git clone --recursive https://git.janouch.name/p/acid.git
$ mkdir acid/build
$ cd acid/build
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug
@@ -53,11 +53,11 @@ TODO. The main application hasn't been written yet.
Contributing and Support
------------------------
-Use this project's GitHub to report any bugs, request features, or submit pull
-requests. If you want to discuss this project, or maybe just hang out with
-the developer, feel free to join me at irc://irc.janouch.name, channel #dev.
+Use https://git.janouch.name/p/acid to report any bugs, request features,
+or submit pull requests. `git send-email` is tolerated. If you want to discuss
+the project, feel free to join me at ircs://irc.janouch.name, channel #dev.
-Bitcoin donations: 12r5uEWEgcHC46xd64tt3hHt9EUvYYDHe9
+Bitcoin donations are accepted at: 12r5uEWEgcHC46xd64tt3hHt9EUvYYDHe9
License
-------
--
cgit v1.2.3-70-g09d2
From 131debe985199e3d261a985b2055a86fc4bd3fdd Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Sun, 24 Jun 2018 00:40:10 +0200
Subject: Bump liberty
---
.gitmodules | 2 +-
demo-json-rpc-server.c | 106 ++++++++++++++++++++-----------------------------
liberty | 2 +-
3 files changed, 44 insertions(+), 66 deletions(-)
diff --git a/.gitmodules b/.gitmodules
index c8d5acf..1a41faf 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,6 @@
[submodule "liberty"]
path = liberty
- url = git://github.com/pjanouch/liberty.git
+ url = https://git.janouch.name/p/liberty.git
[submodule "http-parser"]
path = http-parser
url = https://github.com/joyent/http-parser.git
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 10c808e..0cfb13b 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -144,8 +144,7 @@ fcgi_muxer_send (struct fcgi_muxer *self,
{
hard_assert (len <= UINT16_MAX);
- struct str message;
- str_init (&message);
+ struct str message = str_make ();
str_pack_u8 (&message, FCGI_VERSION_1);
str_pack_u8 (&message, type);
@@ -177,10 +176,9 @@ fcgi_request_init (struct fcgi_request *self)
{
memset (self, 0, sizeof *self);
- str_map_init (&self->headers);
- self->headers.free = free;
+ self->headers = str_map_make (free);
- fcgi_nv_parser_init (&self->hdr_parser);
+ self->hdr_parser = fcgi_nv_parser_make ();
self->hdr_parser.output = &self->headers;
}
@@ -275,17 +273,15 @@ static void
fcgi_muxer_on_get_values
(struct fcgi_muxer *self, const struct fcgi_parser *parser)
{
- struct str_map values; str_map_init (&values); values.free = free;
- struct str_map response; str_map_init (&response); response.free = free;
+ struct str_map values = str_map_make (free);
+ struct str_map response = str_map_make (free);
- struct fcgi_nv_parser nv_parser;
- fcgi_nv_parser_init (&nv_parser);
+ struct fcgi_nv_parser nv_parser = fcgi_nv_parser_make ();
nv_parser.output = &values;
fcgi_nv_parser_push (&nv_parser, parser->content.str, parser->content.len);
- struct str_map_iter iter;
- str_map_iter_init (&iter, &values);
+ struct str_map_iter iter = str_map_iter_make (&values);
while (str_map_iter_next (&iter))
{
const char *key = iter.link->key;
@@ -297,8 +293,7 @@ fcgi_muxer_on_get_values
str_map_set (&response, key, xstrdup ("1"));
}
- struct str content;
- str_init (&content);
+ struct str content = str_make ();
fcgi_nv_convert (&response, &content);
fcgi_muxer_send (self, FCGI_GET_VALUES_RESULT, parser->request_id,
content.str, content.len);
@@ -312,8 +307,8 @@ static void
fcgi_muxer_on_begin_request
(struct fcgi_muxer *self, const struct fcgi_parser *parser)
{
- struct msg_unpacker unpacker;
- msg_unpacker_init (&unpacker, parser->content.str, parser->content.len);
+ struct msg_unpacker unpacker =
+ msg_unpacker_make (parser->content.str, parser->content.len);
uint16_t role;
uint8_t flags;
@@ -435,7 +430,7 @@ fcgi_muxer_on_message (const struct fcgi_parser *parser, void *user_data)
static void
fcgi_muxer_init (struct fcgi_muxer *self)
{
- fcgi_parser_init (&self->parser);
+ self->parser = fcgi_parser_make ();
self->parser.on_message = fcgi_muxer_on_message;
self->parser.user_data = self;
}
@@ -554,8 +549,7 @@ static void
ws_handler_close (struct ws_handler *self,
enum ws_status close_code, const char *reason, size_t len)
{
- struct str payload;
- str_init (&payload);
+ struct str payload = str_make ();
str_pack_u16 (&payload, close_code);
// XXX: maybe accept a null-terminated string on input? Has to be UTF-8 a/w
str_append_data (&payload, reason, len);
@@ -590,8 +584,7 @@ ws_handler_send (struct ws_handler *self,
if (!soft_assert (self->state == WS_HANDLER_OPEN))
return;
- struct str header;
- str_init (&header);
+ struct str header = str_make ();
str_pack_u8 (&header, 0x80 | (opcode & 0x0F));
if (len > UINT16_MAX)
@@ -639,8 +632,8 @@ static bool
ws_handler_on_protocol_close
(struct ws_handler *self, const struct ws_parser *parser)
{
- struct msg_unpacker unpacker;
- msg_unpacker_init (&unpacker, parser->input.str, parser->payload_len);
+ struct msg_unpacker unpacker =
+ msg_unpacker_make (parser->input.str, parser->payload_len);
char *reason = NULL;
uint16_t close_code = WS_STATUS_NO_STATUS_RECEIVED;
@@ -775,21 +768,20 @@ ws_handler_init (struct ws_handler *self)
http_parser_init (&self->hp, HTTP_REQUEST);
self->hp.data = self;
- str_init (&self->field);
- str_init (&self->value);
- str_map_init (&self->headers);
- self->headers.free = free;
+ self->field = str_make ();
+ self->value = str_make ();
+ self->headers = str_map_make (free);
self->headers.key_xfrm = tolower_ascii_strxfrm;
- str_init (&self->url);
+ self->url = str_make ();
ev_timer_init (&self->handshake_timeout_watcher,
ws_handler_on_handshake_timeout, 0., 0.);
self->handshake_timeout_watcher.data = self;
- ws_parser_init (&self->parser);
+ self->parser = ws_parser_make ();
self->parser.on_frame_header = ws_handler_on_frame_header;
self->parser.on_frame = ws_handler_on_frame;
self->parser.user_data = self;
- str_init (&self->message_data);
+ self->message_data = str_make ();
ev_timer_init (&self->ping_timer,
ws_handler_on_ping_timer, 0., 0.);
@@ -927,8 +919,7 @@ ws_handler_http_responsev (struct ws_handler *self,
{
hard_assert (status != NULL);
- struct str response;
- str_init (&response);
+ struct str response = str_make ();
str_append_printf (&response, "HTTP/1.1 %s\r\n", status);
while (*fields)
@@ -943,8 +934,7 @@ ws_handler_http_responsev (struct ws_handler *self,
static void
ws_handler_http_response (struct ws_handler *self, const char *status, ...)
{
- struct strv v;
- strv_init (&v);
+ struct strv v = strv_make ();
va_list ap;
va_start (ap, status);
@@ -1015,8 +1005,7 @@ ws_handler_finish_handshake (struct ws_handler *self)
if (!key)
FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL);
- struct str tmp;
- str_init (&tmp);
+ struct str tmp = str_make ();
bool key_is_valid = base64_decode (key, false, &tmp) && tmp.len == 16;
str_free (&tmp);
if (!key_is_valid)
@@ -1027,9 +1016,7 @@ ws_handler_finish_handshake (struct ws_handler *self)
if (strcmp (version, "13"))
FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, SEC_WS_VERSION ": 13", NULL);
- struct strv fields;
- strv_init (&fields);
-
+ struct strv fields = strv_make ();
strv_append_args (&fields,
"Upgrade: websocket",
"Connection: Upgrade",
@@ -1181,7 +1168,7 @@ server_context_init (struct server_context *self)
{
memset (self, 0, sizeof *self);
- str_map_init (&self->config);
+ self->config = str_map_make (NULL);
simple_config_load_defaults (&self->config, g_config_table);
ev_timer_init (&self->quit_timeout_watcher, on_quit_timeout, 3., 0.);
self->quit_timeout_watcher.data = self;
@@ -1260,9 +1247,7 @@ validate_json_rpc_content_type (const char *content_type)
char *type = NULL;
char *subtype = NULL;
- struct str_map parameters;
- str_map_init (¶meters);
- parameters.free = free;
+ struct str_map parameters = str_map_make (free);
parameters.key_xfrm = tolower_ascii_strxfrm;
bool result = http_parse_media_type
@@ -1503,8 +1488,7 @@ request_start (struct request *self, struct str_map *headers)
}
// Unable to serve the request
- struct str response;
- str_init (&response);
+ struct str response = str_make ();
str_append (&response, "Status: 404 Not Found\n");
str_append (&response, "Content-Type: text/plain\n\n");
self->write_cb (self->user_data, response.str, response.len);
@@ -1536,7 +1520,7 @@ request_handler_json_rpc_try_handle
return false;
struct str *buf = xcalloc (1, sizeof *buf);
- str_init (buf);
+ *buf = str_make ();
request->handler_data = buf;
*continue_ = true;
@@ -1554,8 +1538,7 @@ request_handler_json_rpc_push
return true;
}
- struct str response;
- str_init (&response);
+ struct str response = str_make ();
str_append (&response, "Status: 200 OK\n");
str_append_printf (&response, "Content-Type: %s\n\n", "application/json");
process_json_rpc (request->ctx, buf->str, buf->len, &response);
@@ -1587,12 +1570,10 @@ static char *
canonicalize_url_path (const char *path)
{
// XXX: this strips any slashes at the end
- struct strv v;
- strv_init (&v);
+ struct strv v = strv_make ();
cstr_split (path, "/", true, &v);
- struct strv canonical;
- strv_init (&canonical);
+ struct strv canonical = strv_make ();
// So that the joined path always begins with a slash
strv_append (&canonical, "");
@@ -1672,8 +1653,7 @@ request_handler_static_try_handle
FILE *fp = fopen (path, "rb");
if (!fp)
{
- struct str response;
- str_init (&response);
+ struct str response = str_make ();
str_append (&response, "Status: 404 Not Found\n");
str_append (&response, "Content-Type: text/plain\n\n");
str_append_printf (&response,
@@ -1699,8 +1679,7 @@ request_handler_static_try_handle
if (!mime_type)
mime_type = xstrdup ("application/octet_stream");
- struct str response;
- str_init (&response);
+ struct str response = str_make ();
str_append (&response, "Status: 200 OK\n");
str_append_printf (&response, "Content-Type: %s\n\n", mime_type);
request->write_cb (request->user_data, response.str, response.len);
@@ -1878,7 +1857,7 @@ client_init (EV_P_ struct client *self, int sock_fd)
memset (self, 0, sizeof *self);
self->ctx = ctx;
- write_queue_init (&self->write_queue);
+ self->write_queue = write_queue_make ();
set_blocking (sock_fd, false);
self->socket_fd = sock_fd;
@@ -2119,7 +2098,7 @@ client_scgi_create (EV_P_ int sock_fd)
self->request.close_cb = client_scgi_close_cb;
self->request.user_data = self;
- scgi_parser_init (&self->parser);
+ self->parser = scgi_parser_make ();
self->parser.on_headers_read = client_scgi_on_headers_read;
self->parser.on_content = client_scgi_on_content;
self->parser.user_data = self;
@@ -2145,8 +2124,7 @@ client_ws_on_message (void *user_data,
return false;
}
- struct str response;
- str_init (&response);
+ struct str response = str_make ();
process_json_rpc (self->client.ctx, data, len, &response);
if (response.len)
ws_handler_send (&self->handler,
@@ -2410,9 +2388,9 @@ setup_listen_fds (struct server_context *ctx, struct error **e)
.ai_flags = AI_PASSIVE,
};
- struct strv ports_fcgi; strv_init (&ports_fcgi);
- struct strv ports_scgi; strv_init (&ports_scgi);
- struct strv ports_ws; strv_init (&ports_ws);
+ struct strv ports_fcgi = strv_make ();
+ struct strv ports_scgi = strv_make ();
+ struct strv ports_ws = strv_make ();
get_ports_from_config (ctx, "port_fastcgi", &ports_fcgi);
get_ports_from_config (ctx, "port_scgi", &ports_scgi);
@@ -2588,8 +2566,8 @@ parse_program_arguments (int argc, char **argv)
{ 0, NULL, NULL, 0, NULL }
};
- struct opt_handler oh;
- opt_handler_init (&oh, argc, argv, opts, NULL, "JSON-RPC 2.0 demo server.");
+ struct opt_handler oh =
+ opt_handler_make (argc, argv, opts, NULL, "JSON-RPC 2.0 demo server.");
int c;
while ((c = opt_handler_get (&oh)) != -1)
diff --git a/liberty b/liberty
index 1dcd259..8ffe20c 160000
--- a/liberty
+++ b/liberty
@@ -1 +1 @@
-Subproject commit 1dcd259d0506b9e2de3715bdf07144b22f57903a
+Subproject commit 8ffe20c0e83b52db1344fe91f57236be4c4cb504
--
cgit v1.2.3-70-g09d2
From 8e986a604031aa6fce636a5e1cbb7b1933375406 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Sun, 24 Jun 2018 03:20:55 +0200
Subject: Remove .travis.yml
We don't depend on any proprietary services no longer. I'll have to
make my own replacements with blackjack and hookers. Until then,
the file stays in the commit log as an example.
---
.travis.yml | 27 ---------------------------
1 file changed, 27 deletions(-)
delete mode 100644 .travis.yml
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 7395b90..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-language: c
-notifications:
- irc:
- channels: "irc.janouch.name#dev"
- use_notice: true
- skip_join: true
- template:
- - "%{repository_name}#%{build_number} on %{branch}: %{message}"
- - " %{compare_url}"
- - " %{build_url}"
- on_success: change
- on_failure: always
-compiler:
- - clang
- - gcc
-before_install:
- - sudo add-apt-repository ppa:ukplc-team/ppa -y
- - sudo apt-get update -qq
-install:
- - sudo apt-get install -y help2man libjansson-dev libev-dev
-before_script:
- - mkdir build
- - cd build
-script:
- - cmake .. -DCMAKE_INSTALL_PREFIX=/usr
- - make
- - cpack -G DEB
--
cgit v1.2.3-70-g09d2
From df340c13ed99bead31c54bbac003b06af82e7dd1 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Sun, 24 Jun 2018 05:59:55 +0200
Subject: Add the missing Date header
---
demo-json-rpc-server.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 0cfb13b..cf4d374 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -925,6 +925,19 @@ ws_handler_http_responsev (struct ws_handler *self,
while (*fields)
str_append_printf (&response, "%s\r\n", *fields++);
+ time_t now = time (NULL);
+ struct tm ts;
+ gmtime_r (&now, &ts);
+
+ // See RFC 7231, 7.1.1.2. Date
+ const char *dow[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+ const char *moy[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+ str_append_printf (&response,
+ "Date: %s, %02d %s %04d %02d:%02d:%02d GMT\r\n",
+ dow[ts.tm_wday], ts.tm_mday, moy[ts.tm_mon], ts.tm_year + 1900,
+ ts.tm_hour, ts.tm_min, ts.tm_sec);
+
str_append (&response, "Server: "
PROGRAM_NAME "/" PROGRAM_VERSION "\r\n\r\n");
self->write_cb (self->user_data, response.str, response.len);
--
cgit v1.2.3-70-g09d2
From 711d73f481e7f0254acc3bca3e9dcf9764aaf583 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Sun, 24 Jun 2018 06:02:02 +0200
Subject: Fix text message UTF-8 validation
---
demo-json-rpc-server.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index cf4d374..6ae1742 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -711,7 +711,7 @@ ws_handler_on_frame (void *user_data, const struct ws_parser *parser)
return true;
if (self->message_opcode == WS_OPCODE_TEXT
- && !utf8_validate (self->parser.input.str, self->parser.input.len))
+ && !utf8_validate (self->message_data.str, self->message_data.len))
{
ws_handler_fail (self, WS_STATUS_INVALID_PAYLOAD_DATA);
return false;
--
cgit v1.2.3-70-g09d2
From 329fc9b88fa419ef2c3a32ccc4414dec223bd69d Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Thu, 11 Oct 2018 21:03:34 +0200
Subject: Bump liberty
Eliminates some fall-through warnings.
---
liberty | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/liberty b/liberty
index 8ffe20c..9494e8e 160000
--- a/liberty
+++ b/liberty
@@ -1 +1 @@
-Subproject commit 8ffe20c0e83b52db1344fe91f57236be4c4cb504
+Subproject commit 9494e8e2affcd08b791d3ecf68985a8a3a310e55
--
cgit v1.2.3-70-g09d2
From d182bcef3bbba9107cb808134d79ffe930be3e14 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Fri, 12 Oct 2018 19:59:17 +0200
Subject: More transient errors
---
demo-json-rpc-server.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 6ae1742..56b931d 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -2285,7 +2285,8 @@ on_client_available (EV_P_ ev_io *watcher, int revents)
listener->create (EV_A_ sock_fd);
else if (errno == EAGAIN)
return;
- else if (errno != EINTR && errno != ECONNABORTED)
+ else if (errno != EINTR && errno != EMFILE
+ && errno != ECONNRESET && errno != ECONNABORTED)
break;
}
--
cgit v1.2.3-70-g09d2
From fd17b4e5046b98b7be7bbc9133140cec2e24777d Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Fri, 12 Oct 2018 23:07:06 +0200
Subject: Update code comments
---
demo-json-rpc-server.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 56b931d..995e7e2 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -418,6 +418,7 @@ fcgi_muxer_on_message (const struct fcgi_parser *parser, void *user_data)
if (parser->type >= N_ELEMENTS (handlers)
|| !(handler = handlers[parser->type]))
{
+ // Responding in this way even to application records, unspecified
uint8_t content[8] = { parser->type };
fcgi_muxer_send (self, FCGI_UNKNOWN_TYPE, parser->request_id,
content, sizeof content);
@@ -1303,8 +1304,9 @@ json_rpc_handler_info_cmp (const void *first, const void *second)
((struct json_rpc_handler_info *) second)->method_name);
}
-// TODO: a method that queues up a ping over IRC: this has to be owned by the
-// server context as a background job that removes itself upon completion.
+// TODO: a method that sends a response after a certain number of seconds.
+// This has to be owned by the server context as a background job that
+// removes itself upon completion.
static json_t *
json_rpc_ping (struct server_context *ctx, json_t *params)
@@ -1479,6 +1481,8 @@ request_finish (struct request *self)
self->close_cb (self->user_data);
}
+/// Starts processing a request. Returns false if no further action is to be
+/// done and the request should be finished.
static bool
request_start (struct request *self, struct str_map *headers)
{
@@ -1489,7 +1493,7 @@ request_start (struct request *self, struct str_map *headers)
//
// However that might cause some stuff to be done twice.
//
- // Another way we could get rid off the continue_ argument is via adding
+ // Another way we could get rid of the continue_ argument is via adding
// some way of marking the request as finished from within the handler.
bool continue_ = true;
@@ -1551,6 +1555,9 @@ request_handler_json_rpc_push
return true;
}
+ // TODO: check buf.len against CONTENT_LENGTH; if it's less, then the
+ // client hasn't been successful in transferring all of its data
+
struct str response = str_make ();
str_append (&response, "Status: 200 OK\n");
str_append_printf (&response, "Content-Type: %s\n\n", "application/json");
@@ -1845,6 +1852,8 @@ static void
on_client_ready (EV_P_ ev_io *watcher, int revents)
{
struct client *client = watcher->data;
+ // XXX: although read and write are in a sequence, if we create response
+ // data, we'll still likely need to go back to the event loop.
if (revents & EV_READ)
if (!client_read_loop (EV_A_ client, watcher))
@@ -2488,6 +2497,9 @@ on_termination_signal (EV_P_ ev_signal *handle, int revents)
(void) handle;
(void) revents;
+ // TODO: consider quitting right away if already quitting;
+ // considering that this may already happen in timeout, it should be OK;
+ // see on_quit_timeout, just destroy all clients
initiate_quit (ctx);
}
--
cgit v1.2.3-70-g09d2
From 8b334e9c9187f4eb8bdbfde7644f82a66d30af00 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Sat, 13 Oct 2018 04:08:09 +0200
Subject: Fix fcgi_muxer_send()
Outgoing records were missing padding and the reserved field.
---
demo-json-rpc-server.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 995e7e2..fa7f833 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -145,14 +145,18 @@ fcgi_muxer_send (struct fcgi_muxer *self,
hard_assert (len <= UINT16_MAX);
struct str message = str_make ();
+ static char zeroes[8];
+ size_t padding = -len & 7;
str_pack_u8 (&message, FCGI_VERSION_1);
str_pack_u8 (&message, type);
str_pack_u16 (&message, request_id);
- str_pack_u16 (&message, len); // content length
- str_pack_u8 (&message, 0); // padding length
+ str_pack_u16 (&message, len); // content length
+ str_pack_u8 (&message, padding); // padding length
+ str_pack_u8 (&message, 0); // reserved
str_append_data (&message, data, len);
+ str_append_data (&message, zeroes, padding);
// XXX: we should probably have another write_cb that assumes ownership
self->write_cb (self->user_data, message.str, message.len);
--
cgit v1.2.3-70-g09d2
From 14ded260a0d8e364f2edb895e915c623e1ab7ce7 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Mon, 15 Oct 2018 03:02:49 +0200
Subject: Clarify and degrade FastCGI multiplexing
No need to support more than 255 concurrent requests on one connection.
---
demo-json-rpc-server.c | 28 +++++++++++++++-------------
1 file changed, 15 insertions(+), 13 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index fa7f833..aa56563 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -124,10 +124,6 @@ struct fcgi_muxer
// TODO: bool quitting; that causes us to reject all requests?
- /// Requests assigned to request IDs
- // TODO: allocate this dynamically
- struct fcgi_request *requests[1 << 16];
-
void (*write_cb) (void *user_data, const void *data, size_t len);
void (*close_cb) (void *user_data);
@@ -136,6 +132,9 @@ struct fcgi_muxer
void (*request_destroy_cb) (void *handler_data);
void *user_data; ///< User data for callbacks
+
+ /// Requests assigned to request IDs (may not be FCGI_NULL_REQUEST_ID)
+ struct fcgi_request *requests[1 << 8];
};
static void
@@ -284,18 +283,21 @@ fcgi_muxer_on_get_values
nv_parser.output = &values;
fcgi_nv_parser_push (&nv_parser, parser->content.str, parser->content.len);
+ const char *key = NULL;
- struct str_map_iter iter = str_map_iter_make (&values);
- while (str_map_iter_next (&iter))
- {
- const char *key = iter.link->key;
+ // No real-world servers seem to actually use multiplexing
+ // or even issue this request, but we will implement it anyway
+ if (str_map_find (&values, (key = FCGI_MPXS_CONNS)))
+ str_map_set (&response, key, xstrdup ("1"));
- // TODO: if (!strcmp (key, FCGI_MAX_CONNS))
- // TODO: if (!strcmp (key, FCGI_MAX_REQS))
+ // It's not clear whether FCGI_MAX_REQS means concurrently over all
+ // connections or over just a single connection (multiplexed), though
+ // supposedly it's actually per /web server/. Supply the strictest limit.
+ if (str_map_find (&values, (key = FCGI_MAX_REQS)))
+ str_map_set (&response, key,
+ xstrdup_printf ("%zu", N_ELEMENTS (self->requests) - 1));
- if (!strcmp (key, FCGI_MPXS_CONNS))
- str_map_set (&response, key, xstrdup ("1"));
- }
+ // FCGI_MAX_CONNS would be basically infinity. We don't limit connections.
struct str content = str_make ();
fcgi_nv_convert (&response, &content);
--
cgit v1.2.3-70-g09d2
From 441c89f654d2f5d8d36c10a948d2ab5db87ffa12 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Mon, 15 Oct 2018 03:04:39 +0200
Subject: Handle FastCGI null request IDs better
---
demo-json-rpc-server.c | 32 ++++++++++++++++++++++++--------
1 file changed, 24 insertions(+), 8 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index aa56563..096d2a5 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -276,7 +276,14 @@ static void
fcgi_muxer_on_get_values
(struct fcgi_muxer *self, const struct fcgi_parser *parser)
{
- struct str_map values = str_map_make (free);
+ if (parser->request_id != FCGI_NULL_REQUEST_ID)
+ {
+ print_debug ("FastCGI: ignoring invalid %s message",
+ STRINGIFY (FCGI_GET_VALUES));
+ return;
+ }
+
+ struct str_map values = str_map_make (free);
struct str_map response = str_map_make (free);
struct fcgi_nv_parser nv_parser = fcgi_nv_parser_make ();
@@ -330,6 +337,13 @@ fcgi_muxer_on_begin_request
return;
}
+ struct fcgi_request *request = self->requests[parser->request_id];
+ if (parser->request_id == FCGI_NULL_REQUEST_ID || request)
+ {
+ // TODO: fail
+ return;
+ }
+
// We can only act as a responder, reject everything else up front
if (role != FCGI_RESPONDER)
{
@@ -338,10 +352,11 @@ fcgi_muxer_on_begin_request
return;
}
- struct fcgi_request *request = self->requests[parser->request_id];
- if (request)
+ // TODO: also send OVERLOADED when shutting down?
+ if (parser->request_id >= N_ELEMENTS (self->requests))
{
- // TODO: fail
+ fcgi_muxer_send_end_request (self,
+ parser->request_id, 0, FCGI_OVERLOADED);
return;
}
@@ -359,21 +374,22 @@ fcgi_muxer_on_abort_request
(struct fcgi_muxer *self, const struct fcgi_parser *parser)
{
struct fcgi_request *request = self->requests[parser->request_id];
- if (!request)
+ if (parser->request_id == FCGI_NULL_REQUEST_ID || !request)
{
print_debug ("FastCGI: received %s for an unknown request",
STRINGIFY (FCGI_ABORT_REQUEST));
return;
}
- // TODO: abort the request: let it somehow produce FCGI_END_REQUEST
+ // TODO: abort the request: let it somehow produce FCGI_END_REQUEST,
+ // make sure to send an stdout EOF record
}
static void
fcgi_muxer_on_params (struct fcgi_muxer *self, const struct fcgi_parser *parser)
{
struct fcgi_request *request = self->requests[parser->request_id];
- if (!request)
+ if (parser->request_id == FCGI_NULL_REQUEST_ID || !request)
{
print_debug ("FastCGI: received %s for an unknown request",
STRINGIFY (FCGI_PARAMS));
@@ -388,7 +404,7 @@ static void
fcgi_muxer_on_stdin (struct fcgi_muxer *self, const struct fcgi_parser *parser)
{
struct fcgi_request *request = self->requests[parser->request_id];
- if (!request)
+ if (parser->request_id == FCGI_NULL_REQUEST_ID || !request)
{
print_debug ("FastCGI: received %s for an unknown request",
STRINGIFY (FCGI_STDIN));
--
cgit v1.2.3-70-g09d2
From 267a9a561bdc252736a022f241b9de5458d66ceb Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Mon, 15 Oct 2018 03:28:09 +0200
Subject: Eliminate some warnings
---
demo-json-rpc-server.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 096d2a5..3698ade 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -769,7 +769,9 @@ ws_handler_on_ping_timer (EV_P_ ev_timer *watcher, int revents)
static void
ws_handler_on_close_timeout (EV_P_ ev_timer *watcher, int revents)
{
+ (void) revents;
struct ws_handler *self = watcher->data;
+
// TODO: anything else to do here? Invalidate our state?
if (self->close_cb)
self->close_cb (self->user_data);
@@ -778,6 +780,7 @@ ws_handler_on_close_timeout (EV_P_ ev_timer *watcher, int revents)
static void
ws_handler_on_handshake_timeout (EV_P_ ev_timer *watcher, int revents)
{
+ (void) revents;
struct ws_handler *self = watcher->data;
// TODO
}
@@ -1034,9 +1037,12 @@ ws_handler_finish_handshake (struct ws_handler *self)
FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL);
// Okay, we're finally past the basic HTTP/1.1 stuff
- const char *key = str_map_find (&self->headers, SEC_WS_KEY);
- const char *version = str_map_find (&self->headers, SEC_WS_VERSION);
- const char *protocol = str_map_find (&self->headers, SEC_WS_PROTOCOL);
+ const char *key = str_map_find (&self->headers, SEC_WS_KEY);
+ const char *version = str_map_find (&self->headers, SEC_WS_VERSION);
+/*
+ const char *protocol = str_map_find (&self->headers, SEC_WS_PROTOCOL);
+ const char *extensions = str_map_find (&self->headers, SEC_WS_EXTENSIONS);
+*/
if (!key)
FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL);
@@ -1063,7 +1069,7 @@ ws_handler_finish_handshake (struct ws_handler *self)
xstrdup_printf (SEC_WS_ACCEPT ": %s", response_key));
free (response_key);
- // TODO: check and set Sec-Websocket-{Extensions,Protocol}
+ // TODO: make it possible to choose Sec-Websocket-{Extensions,Protocol}
ws_handler_http_responsev (self,
HTTP_101_SWITCHING_PROTOCOLS, fields.vector);
--
cgit v1.2.3-70-g09d2
From a14edb72e97084d8c5c16b7c5f37396091464103 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Mon, 15 Oct 2018 05:07:57 +0200
Subject: Make Doxygen a bit more useful
---
demo-json-rpc-server.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 3698ade..476187c 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -96,6 +96,8 @@ log_message_syslog (void *user_data, const char *quote, const char *fmt,
}
// --- FastCGI -----------------------------------------------------------------
+/// @defgroup FastCGI
+/// @{
enum fcgi_request_state
{
@@ -470,7 +472,10 @@ fcgi_muxer_push (struct fcgi_muxer *self, const void *data, size_t len)
fcgi_parser_push (&self->parser, data, len);
}
+/// @}
// --- WebSockets --------------------------------------------------------------
+/// @defgroup WebSockets
+/// @{
// WebSockets aren't CGI-compatible, therefore we must handle the initial HTTP
// handshake ourselves. Luckily it's not too much of a bother with http-parser.
@@ -1169,6 +1174,7 @@ ws_handler_push (struct ws_handler *self, const void *data, size_t len)
return true;
}
+/// @}
// --- Server ------------------------------------------------------------------
static struct simple_config_item g_config_table[] =
@@ -1229,6 +1235,8 @@ server_context_free (struct server_context *self)
}
// --- JSON-RPC ----------------------------------------------------------------
+/// @defgroup JSON-RPC
+/// @{
#define JSON_RPC_ERROR_TABLE(XX) \
XX (-32700, PARSE_ERROR, "Parse error") \
@@ -1451,7 +1459,10 @@ process_json_rpc (struct server_context *ctx,
}
}
+/// @}
// --- Requests ----------------------------------------------------------------
+/// @defgroup Requests
+/// @{
struct request
{
@@ -1551,6 +1562,7 @@ request_push (struct request *self, const void *data, size_t len)
return self->handler->push_cb (self, data, len);
}
+/// @}
// --- Requests handlers -------------------------------------------------------
static bool
--
cgit v1.2.3-70-g09d2
From 7aff9c3475989278bf0c3bc69e6be499e78d59e4 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Mon, 15 Oct 2018 19:29:17 +0200
Subject: Improve documentation
---
demo-json-rpc-server.c | 21 +++++++++++++++------
1 file changed, 15 insertions(+), 6 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 476187c..9658e91 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -546,7 +546,7 @@ struct ws_handler
// Actually, calling push() could work pretty fine for this.
void (*on_close) (void *user_data, int close_code, const char *reason);
- // Method callbacks:
+ // Virtual method callbacks:
/// Write a chunk of data to the stream
void (*write_cb) (void *user_data, const void *data, size_t len);
@@ -1464,11 +1464,12 @@ process_json_rpc (struct server_context *ctx,
/// @defgroup Requests
/// @{
+/// A generic CGI request abstraction, writing data indirectly through callbacks
struct request
{
struct server_context *ctx; ///< Server context
- struct request_handler *handler; ///< Current request handler
+ struct request_handler *handler; ///< Assigned request handler
void *handler_data; ///< User data for the handler
/// Callback to write some CGI response data to the output
@@ -1481,20 +1482,25 @@ struct request
void *user_data; ///< User data argument for callbacks
};
+/// An interface to detect and handle specific kinds of CGI requests.
+/// The server walks through a list of them until it finds one that can serve
+/// a particular request. If unsuccessful, the remote client gets a 404
+/// (the default handling).
struct request_handler
{
LIST_HEADER (struct request_handler)
- /// Install ourselves as the handler for the request if applicable.
- /// Set @a continue_ to false if further processing should be stopped.
+ /// Install ourselves as the handler for the request, if applicable.
+ /// Sets @a continue_ to false if further processing should be stopped,
+ /// meaning the request has already been handled.
bool (*try_handle) (struct request *request,
struct str_map *headers, bool *continue_);
/// Handle incoming data.
- /// Return false if further processing should be stopped.
+ /// Returns false if there is no more processing to be done.
bool (*push_cb) (struct request *request, const void *data, size_t len);
- /// Destroy the handler
+ /// Destroy the handler's data stored in the request object
void (*destroy_cb) (struct request *request);
};
@@ -1792,6 +1798,8 @@ struct request_handler g_request_handler_static =
// --- Client communication handlers -------------------------------------------
+/// A virtual class for client connections coming either from the web server
+/// or directly from the end-client, depending on the protocol in use
struct client
{
LIST_HEADER (struct client)
@@ -1808,6 +1816,7 @@ struct client
struct client_vtable *vtable; ///< Client behaviour
};
+/// The concrete behaviour to serve a particular client's requests
struct client_vtable
{
/// Attempt a graceful shutdown
--
cgit v1.2.3-70-g09d2
From dda22c2cd552d113db6afd789e38c5d436156d7c Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Mon, 15 Oct 2018 20:38:48 +0200
Subject: Eliminate unnecessary user_data pointers
The CONTAINER_OF macro can find the parent structure just as well.
---
demo-json-rpc-server.c | 122 +++++++++++++++++++++++--------------------------
1 file changed, 57 insertions(+), 65 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 9658e91..baed884 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -44,6 +44,9 @@
enum { PIPE_READ, PIPE_WRITE };
+#define FIND_CONTAINER(name, pointer, type, member) \
+ type *name = CONTAINER_OF (pointer, type, member)
+
// --- libev helpers -----------------------------------------------------------
static bool
@@ -126,15 +129,13 @@ struct fcgi_muxer
// TODO: bool quitting; that causes us to reject all requests?
- void (*write_cb) (void *user_data, const void *data, size_t len);
- void (*close_cb) (void *user_data);
+ void (*write_cb) (struct fcgi_muxer *, const void *data, size_t len);
+ void (*close_cb) (struct fcgi_muxer *);
- void *(*request_start_cb) (void *user_data, struct fcgi_request *request);
+ void *(*request_start_cb) (struct fcgi_muxer *, struct fcgi_request *);
void (*request_push_cb) (void *handler_data, const void *data, size_t len);
void (*request_destroy_cb) (void *handler_data);
- void *user_data; ///< User data for callbacks
-
/// Requests assigned to request IDs (may not be FCGI_NULL_REQUEST_ID)
struct fcgi_request *requests[1 << 8];
};
@@ -160,7 +161,7 @@ fcgi_muxer_send (struct fcgi_muxer *self,
str_append_data (&message, zeroes, padding);
// XXX: we should probably have another write_cb that assumes ownership
- self->write_cb (self->user_data, message.str, message.len);
+ self->write_cb (self, message.str, message.len);
str_free (&message);
}
@@ -210,8 +211,7 @@ fcgi_request_push_params
{
// TODO: probably check the state of the header parser
// TODO: request_start() can return false, end the request here?
- self->handler_data = self->muxer->request_start_cb
- (self->muxer->user_data, self);
+ self->handler_data = self->muxer->request_start_cb (self->muxer, self);
self->state = FCGI_REQUEST_STDIN;
}
}
@@ -532,10 +532,10 @@ struct ws_handler
// This may render "on_connected" unnecessary.
/// Called after successfuly connecting (handshake complete)
- bool (*on_connected) (void *user_data);
+ bool (*on_connected) (struct ws_handler *);
/// Called upon reception of a single full message
- bool (*on_message) (void *user_data,
+ bool (*on_message) (struct ws_handler *,
enum ws_opcode type, const void *data, size_t len);
/// The connection is about to close. @a close_code may, or may not, be one
@@ -544,17 +544,15 @@ struct ws_handler
// receive a notification about the connection being closed because of
// an error (recv()) returns -1, and call on_close() in reaction.
// Actually, calling push() could work pretty fine for this.
- void (*on_close) (void *user_data, int close_code, const char *reason);
+ void (*on_close) (struct ws_handler *, int close_code, const char *reason);
// Virtual method callbacks:
/// Write a chunk of data to the stream
- void (*write_cb) (void *user_data, const void *data, size_t len);
+ void (*write_cb) (struct ws_handler *, const void *data, size_t len);
/// Close the connection
- void (*close_cb) (void *user_data);
-
- void *user_data; ///< User data for callbacks
+ void (*close_cb) (struct ws_handler *);
};
static void
@@ -569,8 +567,8 @@ ws_handler_send_control (struct ws_handler *self,
}
uint8_t header[2] = { 0x80 | (opcode & 0x0F), len };
- self->write_cb (self->user_data, header, sizeof header);
- self->write_cb (self->user_data, data, len);
+ self->write_cb (self, header, sizeof header);
+ self->write_cb (self, data, len);
}
static void
@@ -585,7 +583,7 @@ ws_handler_close (struct ws_handler *self,
// Close initiated by us; the reason is null-terminated within `payload'
if (self->on_close)
- self->on_close (self->user_data, close_code, payload.str + 2);
+ self->on_close (self, close_code, payload.str + 2);
self->state = WS_HANDLER_CLOSING;
str_free (&payload);
@@ -628,8 +626,8 @@ ws_handler_send (struct ws_handler *self,
else
str_pack_u8 (&header, len);
- self->write_cb (self->user_data, header.str, header.len);
- self->write_cb (self->user_data, data, len);
+ self->write_cb (self, header.str, header.len);
+ self->write_cb (self, data, len);
str_free (&header);
}
@@ -680,7 +678,7 @@ ws_handler_on_protocol_close
ws_handler_send_control (self, WS_OPCODE_CLOSE,
parser->input.str, parser->payload_len);
if (self->on_close)
- self->on_close (self->user_data, close_code, reason);
+ self->on_close (self, close_code, reason);
}
free (reason);
@@ -747,7 +745,7 @@ ws_handler_on_frame (void *user_data, const struct ws_parser *parser)
bool result = true;
if (self->on_message)
- result = self->on_message (self->user_data, self->message_opcode,
+ result = self->on_message (self, self->message_opcode,
self->message_data.str, self->message_data.len);
str_reset (&self->message_data);
return result;
@@ -779,7 +777,7 @@ ws_handler_on_close_timeout (EV_P_ ev_timer *watcher, int revents)
// TODO: anything else to do here? Invalidate our state?
if (self->close_cb)
- self->close_cb (self->user_data);
+ self->close_cb (self);
}
static void
@@ -971,7 +969,7 @@ ws_handler_http_responsev (struct ws_handler *self,
str_append (&response, "Server: "
PROGRAM_NAME "/" PROGRAM_VERSION "\r\n\r\n");
- self->write_cb (self->user_data, response.str, response.len);
+ self->write_cb (self, response.str, response.len);
str_free (&response);
}
@@ -1110,8 +1108,7 @@ ws_handler_push (struct ws_handler *self, const void *data, size_t len)
if (self->state == WS_HANDLER_OPEN)
{
if (self->on_close)
- self->on_close (self->user_data,
- WS_STATUS_ABNORMAL_CLOSURE, "");
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "");
}
else
{
@@ -1154,7 +1151,7 @@ ws_handler_push (struct ws_handler *self, const void *data, size_t len)
self->state = WS_HANDLER_OPEN;
if (self->on_connected)
- return self->on_connected (self->user_data);
+ return self->on_connected (self);
return true;
}
@@ -1473,13 +1470,11 @@ struct request
void *handler_data; ///< User data for the handler
/// Callback to write some CGI response data to the output
- void (*write_cb) (void *user_data, const void *data, size_t len);
+ void (*write_cb) (struct request *, const void *data, size_t len);
/// Callback to close the connection.
/// CALLING THIS MAY CAUSE THE REQUEST TO BE DESTROYED.
- void (*close_cb) (void *user_data);
-
- void *user_data; ///< User data argument for callbacks
+ void (*close_cb) (struct request *);
};
/// An interface to detect and handle specific kinds of CGI requests.
@@ -1523,7 +1518,7 @@ request_free (struct request *self)
static void
request_finish (struct request *self)
{
- self->close_cb (self->user_data);
+ self->close_cb (self);
}
/// Starts processing a request. Returns false if no further action is to be
@@ -1553,7 +1548,7 @@ request_start (struct request *self, struct str_map *headers)
struct str response = str_make ();
str_append (&response, "Status: 404 Not Found\n");
str_append (&response, "Content-Type: text/plain\n\n");
- self->write_cb (self->user_data, response.str, response.len);
+ self->write_cb (self, response.str, response.len);
str_free (&response);
return false;
}
@@ -1608,7 +1603,7 @@ request_handler_json_rpc_push
str_append (&response, "Status: 200 OK\n");
str_append_printf (&response, "Content-Type: %s\n\n", "application/json");
process_json_rpc (request->ctx, buf->str, buf->len, &response);
- request->write_cb (request->user_data, response.str, response.len);
+ request->write_cb (request, response.str, response.len);
str_free (&response);
return false;
}
@@ -1724,7 +1719,7 @@ request_handler_static_try_handle
str_append (&response, "Content-Type: text/plain\n\n");
str_append_printf (&response,
"File %s was not found on this server\n", suffix);
- request->write_cb (request->user_data, response.str, response.len);
+ request->write_cb (request, response.str, response.len);
str_free (&response);
free (suffix);
@@ -1748,17 +1743,17 @@ request_handler_static_try_handle
struct str response = str_make ();
str_append (&response, "Status: 200 OK\n");
str_append_printf (&response, "Content-Type: %s\n\n", mime_type);
- request->write_cb (request->user_data, response.str, response.len);
+ request->write_cb (request, response.str, response.len);
str_free (&response);
free (mime_type);
// Write the chunk we've used to help us with magic detection;
// obviously we have to do it after we've written the headers
if (len)
- request->write_cb (request->user_data, buf, len);
+ request->write_cb (request, buf, len);
while ((len = fread (buf, 1, sizeof buf, fp)))
- request->write_cb (request->user_data, buf, len);
+ request->write_cb (request, buf, len);
fclose (fp);
// TODO: this should rather not be returned all at once but in chunks;
@@ -1962,28 +1957,29 @@ struct client_fcgi_request
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
-client_fcgi_request_write_cb (void *user_data, const void *data, size_t len)
+client_fcgi_request_write_cb (struct request *req, const void *data, size_t len)
{
- struct client_fcgi_request *request = user_data;
- fcgi_request_write (request->fcgi_request, data, len);
+ FIND_CONTAINER (self, req, struct client_fcgi_request, request);
+ fcgi_request_write (self->fcgi_request, data, len);
}
static void
-client_fcgi_request_close_cb (void *user_data)
+client_fcgi_request_close_cb (struct request *req)
{
- struct client_fcgi_request *request = user_data;
+ FIND_CONTAINER (self, req, struct client_fcgi_request, request);
// No more data to send, terminate the substream/request
// XXX: this will most probably end up with client_fcgi_request_destroy(),
// we might or might not need to defer this action
- fcgi_request_finish (request->fcgi_request);
+ fcgi_request_finish (self->fcgi_request);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void *
-client_fcgi_request_start (void *user_data, struct fcgi_request *fcgi_request)
+client_fcgi_request_start
+ (struct fcgi_muxer *mux, struct fcgi_request *fcgi_request)
{
- struct client_fcgi *self = user_data;
+ FIND_CONTAINER (self, mux, struct client_fcgi, muxer);
// TODO: what if the request is aborted by ;
struct client_fcgi_request *request = xcalloc (1, sizeof *request);
@@ -1992,7 +1988,6 @@ client_fcgi_request_start (void *user_data, struct fcgi_request *fcgi_request)
request->request.ctx = self->client.ctx;
request->request.write_cb = client_fcgi_request_write_cb;
request->request.close_cb = client_fcgi_request_close_cb;
- request->request.user_data = request;
return request;
}
@@ -2014,16 +2009,16 @@ client_fcgi_request_destroy (void *handler_data)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
-client_fcgi_write_cb (void *user_data, const void *data, size_t len)
+client_fcgi_write_cb (struct fcgi_muxer *mux, const void *data, size_t len)
{
- struct client_fcgi *self = user_data;
+ FIND_CONTAINER (self, mux, struct client_fcgi, muxer);
client_write (&self->client, data, len);
}
static void
-client_fcgi_close_cb (void *user_data)
+client_fcgi_close_cb (struct fcgi_muxer *mux)
{
- struct client_fcgi *self = user_data;
+ FIND_CONTAINER (self, mux, struct client_fcgi, muxer);
// FIXME: we should probably call something like client_shutdown(),
// which may have an argument whether we should really use close()
client_destroy (&self->client);
@@ -2076,7 +2071,6 @@ client_fcgi_create (EV_P_ int sock_fd)
self->muxer.request_start_cb = client_fcgi_request_start;
self->muxer.request_push_cb = client_fcgi_request_push;
self->muxer.request_destroy_cb = client_fcgi_request_destroy;
- self->muxer.user_data = self;
return &self->client;
}
@@ -2090,17 +2084,17 @@ struct client_scgi
};
static void
-client_scgi_write_cb (void *user_data, const void *data, size_t len)
+client_scgi_write_cb (struct request *req, const void *data, size_t len)
{
- struct client_scgi *self = user_data;
+ FIND_CONTAINER (self, req, struct client_scgi, request);
client_write (&self->client, data, len);
}
static void
-client_scgi_close_cb (void *user_data)
+client_scgi_close_cb (struct request *req)
{
// NOTE: this rather really means "close me [the request]"
- struct client_scgi *self = user_data;
+ FIND_CONTAINER (self, req, struct client_scgi, request);
// FIXME: we should probably call something like client_shutdown(),
// which may have an argument whether we should really use close()
client_destroy (&self->client);
@@ -2167,7 +2161,6 @@ client_scgi_create (EV_P_ int sock_fd)
self->request.ctx = self->client.ctx;
self->request.write_cb = client_scgi_write_cb;
self->request.close_cb = client_scgi_close_cb;
- self->request.user_data = self;
self->parser = scgi_parser_make ();
self->parser.on_headers_read = client_scgi_on_headers_read;
@@ -2185,10 +2178,10 @@ struct client_ws
};
static bool
-client_ws_on_message (void *user_data,
+client_ws_on_message (struct ws_handler *handler,
enum ws_opcode type, const void *data, size_t len)
{
- struct client_ws *self = user_data;
+ FIND_CONTAINER (self, handler, struct client_ws, handler);
if (type != WS_OPCODE_TEXT)
{
ws_handler_fail (&self->handler, WS_STATUS_UNSUPPORTED_DATA);
@@ -2205,16 +2198,16 @@ client_ws_on_message (void *user_data,
}
static void
-client_ws_write_cb (void *user_data, const void *data, size_t len)
+client_ws_write_cb (struct ws_handler *handler, const void *data, size_t len)
{
- struct client *client = user_data;
- client_write (client, data, len);
+ FIND_CONTAINER (self, handler, struct client_ws, handler);
+ client_write (&self->client, data, len);
}
static void
-client_ws_close_cb (void *user_data)
+client_ws_close_cb (struct ws_handler *handler)
{
- struct client_ws *self = user_data;
+ FIND_CONTAINER (self, handler, struct client_ws, handler);
// FIXME: we should probably call something like client_shutdown(),
// which may have an argument whether we should really use close()
client_destroy (&self->client);
@@ -2261,7 +2254,6 @@ client_ws_create (EV_P_ int sock_fd)
self->handler.on_message = client_ws_on_message;
self->handler.write_cb = client_ws_write_cb;
self->handler.close_cb = client_ws_close_cb;
- self->handler.user_data = self;
// One mebibyte seems to be a reasonable value
self->handler.max_payload_len = 1 << 10;
--
cgit v1.2.3-70-g09d2
From e9530f450e64947850da0ed7596c8611cf445efe Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Tue, 16 Oct 2018 00:18:13 +0200
Subject: Call ws_handler_start()
---
demo-json-rpc-server.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index baed884..2c487f5 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -2257,6 +2257,8 @@ client_ws_create (EV_P_ int sock_fd)
// One mebibyte seems to be a reasonable value
self->handler.max_payload_len = 1 << 10;
+
+ ws_handler_start (&self->handler);
return &self->client;
}
--
cgit v1.2.3-70-g09d2
From 7cefdd496f8e45a5d4ee54b7c6cfc5283ae32d51 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Tue, 16 Oct 2018 04:05:42 +0200
Subject: Cleanup
---
demo-json-rpc-server.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 2c487f5..4ec7e4c 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -2218,21 +2218,21 @@ client_ws_close_cb (struct ws_handler *handler)
static void
client_ws_shutdown (struct client *client)
{
- struct client_ws *self = (struct client_ws *) client;
+ FIND_CONTAINER (self, client, struct client_ws, client);
ws_handler_close (&self->handler, WS_STATUS_GOING_AWAY, NULL, 0);
}
static void
client_ws_destroy (struct client *client)
{
- struct client_ws *self = (struct client_ws *) client;
+ FIND_CONTAINER (self, client, struct client_ws, client);
ws_handler_free (&self->handler);
}
static bool
client_ws_push (struct client *client, const void *data, size_t len)
{
- struct client_ws *self = (struct client_ws *) client;
+ FIND_CONTAINER (self, client, struct client_ws, client);
return ws_handler_push (&self->handler, data, len);
}
--
cgit v1.2.3-70-g09d2
From 7f6db9d39f7a65243992b3a01eacedb4b2be279e Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Tue, 16 Oct 2018 04:09:59 +0200
Subject: Improve WebSocket shutdown
---
demo-json-rpc-server.c | 25 +++++++++++++------------
1 file changed, 13 insertions(+), 12 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 4ec7e4c..4bf1fcc 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -540,10 +540,6 @@ struct ws_handler
/// The connection is about to close. @a close_code may, or may not, be one
/// of enum ws_status. The @a reason is never NULL.
- // TODO; also note that ideally, the handler should (be able to) first
- // receive a notification about the connection being closed because of
- // an error (recv()) returns -1, and call on_close() in reaction.
- // Actually, calling push() could work pretty fine for this.
void (*on_close) (struct ws_handler *, int close_code, const char *reason);
// Virtual method callbacks:
@@ -551,8 +547,9 @@ struct ws_handler
/// Write a chunk of data to the stream
void (*write_cb) (struct ws_handler *, const void *data, size_t len);
- /// Close the connection
- void (*close_cb) (struct ws_handler *);
+ /// Close the connection. If @a half_close is false, you are allowed to
+ /// destroy the handler directly from within the callback.
+ void (*close_cb) (struct ws_handler *, bool half_close);
};
static void
@@ -777,7 +774,7 @@ ws_handler_on_close_timeout (EV_P_ ev_timer *watcher, int revents)
// TODO: anything else to do here? Invalidate our state?
if (self->close_cb)
- self->close_cb (self);
+ self->close_cb (self, false /* half_close */);
}
static void
@@ -2205,12 +2202,13 @@ client_ws_write_cb (struct ws_handler *handler, const void *data, size_t len)
}
static void
-client_ws_close_cb (struct ws_handler *handler)
+client_ws_close_cb (struct ws_handler *handler, bool half_close)
{
FIND_CONTAINER (self, handler, struct client_ws, handler);
- // FIXME: we should probably call something like client_shutdown(),
- // which may have an argument whether we should really use close()
- client_destroy (&self->client);
+ if (half_close)
+ ; // FIXME: we should probably call something like client_shutdown()
+ else
+ client_destroy (&self->client);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -2219,7 +2217,10 @@ static void
client_ws_shutdown (struct client *client)
{
FIND_CONTAINER (self, client, struct client_ws, client);
- ws_handler_close (&self->handler, WS_STATUS_GOING_AWAY, NULL, 0);
+ if (self->handler.state == WS_HANDLER_CONNECTING)
+ ; // TODO: abort the connection immediately
+ else if (self->handler.state == WS_HANDLER_OPEN)
+ ws_handler_close (&self->handler, WS_STATUS_GOING_AWAY, NULL, 0);
}
static void
--
cgit v1.2.3-70-g09d2
From 7d922352ead78b92247d0848572a7e9c50ae1075 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Tue, 16 Oct 2018 04:12:37 +0200
Subject: Rename client_vtable::destroy to finalize
Matches a similar concept from garbage-collected languages.
---
demo-json-rpc-server.c | 21 ++++++++++-----------
1 file changed, 10 insertions(+), 11 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 4bf1fcc..f241030 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -1814,9 +1814,8 @@ struct client_vtable
/// Attempt a graceful shutdown
void (*shutdown) (struct client *client);
- /// Do any additional cleanup
- // TODO: rename to "finalize" or "cleanup"?
- void (*destroy) (struct client *client);
+ /// Do any additional cleanup for the concrete class before destruction
+ void (*finalize) (struct client *client);
/// Process incoming data; "len == 0" means EOF
bool (*push) (struct client *client, const void *data, size_t len);
@@ -1848,7 +1847,7 @@ client_destroy (struct client *self)
ctx->n_clients--;
// First uninitialize the higher-level implementation
- self->vtable->destroy (self);
+ self->vtable->finalize (self);
ev_io_stop (EV_DEFAULT_ &self->read_watcher);
ev_io_stop (EV_DEFAULT_ &self->write_watcher);
@@ -2034,7 +2033,7 @@ client_fcgi_shutdown (struct client *client)
}
static void
-client_fcgi_destroy (struct client *client)
+client_fcgi_finalize (struct client *client)
{
struct client_fcgi *self = (struct client_fcgi *) client;
fcgi_muxer_free (&self->muxer);
@@ -2051,7 +2050,7 @@ client_fcgi_push (struct client *client, const void *data, size_t len)
static struct client_vtable client_fcgi_vtable =
{
.shutdown = client_fcgi_shutdown,
- .destroy = client_fcgi_destroy,
+ .finalize = client_fcgi_finalize,
.push = client_fcgi_push,
};
@@ -2118,7 +2117,7 @@ client_scgi_on_content (void *user_data, const void *data, size_t len)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
-client_scgi_destroy (struct client *client)
+client_scgi_finalize (struct client *client)
{
struct client_scgi *self = (struct client_scgi *) client;
request_free (&self->request);
@@ -2143,8 +2142,8 @@ client_scgi_push (struct client *client, const void *data, size_t len)
static struct client_vtable client_scgi_vtable =
{
- .destroy = client_scgi_destroy,
- .push = client_scgi_push,
+ .finalize = client_scgi_finalize,
+ .push = client_scgi_push,
};
static struct client *
@@ -2224,7 +2223,7 @@ client_ws_shutdown (struct client *client)
}
static void
-client_ws_destroy (struct client *client)
+client_ws_finalize (struct client *client)
{
FIND_CONTAINER (self, client, struct client_ws, client);
ws_handler_free (&self->handler);
@@ -2240,7 +2239,7 @@ client_ws_push (struct client *client, const void *data, size_t len)
static struct client_vtable client_ws_vtable =
{
.shutdown = client_ws_shutdown,
- .destroy = client_ws_destroy,
+ .finalize = client_ws_finalize,
.push = client_ws_push,
};
--
cgit v1.2.3-70-g09d2
From 4c54bc42b9fdb83d439dc974953f06f5def3ae5b Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Tue, 16 Oct 2018 04:33:33 +0200
Subject: Clean up and better document client_vtable
---
demo-json-rpc-server.c | 65 +++++++++++++++++++++++++++-----------------------
1 file changed, 35 insertions(+), 30 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index f241030..d461691 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -1811,14 +1811,19 @@ struct client
/// The concrete behaviour to serve a particular client's requests
struct client_vtable
{
- /// Attempt a graceful shutdown
+ /// Process incoming data; "len == 0" means EOF.
+ /// If the method returns false, the client is destroyed by caller.
+ bool (*push) (struct client *client, const void *data, size_t len);
+
+ // TODO: optional push_error() to inform about network I/O errors
+
+ /// Attempt a graceful shutdown: make any appropriate steps before
+ /// the client connection times out and gets torn down by force.
+ /// The client is allowed to destroy itself immediately.
void (*shutdown) (struct client *client);
/// Do any additional cleanup for the concrete class before destruction
void (*finalize) (struct client *client);
-
- /// Process incoming data; "len == 0" means EOF
- bool (*push) (struct client *client, const void *data, size_t len);
};
static void
@@ -2022,6 +2027,14 @@ client_fcgi_close_cb (struct fcgi_muxer *mux)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+static bool
+client_fcgi_push (struct client *client, const void *data, size_t len)
+{
+ struct client_fcgi *self = (struct client_fcgi *) client;
+ fcgi_muxer_push (&self->muxer, data, len);
+ return true;
+}
+
static void
client_fcgi_shutdown (struct client *client)
{
@@ -2039,19 +2052,11 @@ client_fcgi_finalize (struct client *client)
fcgi_muxer_free (&self->muxer);
}
-static bool
-client_fcgi_push (struct client *client, const void *data, size_t len)
-{
- struct client_fcgi *self = (struct client_fcgi *) client;
- fcgi_muxer_push (&self->muxer, data, len);
- return true;
-}
-
static struct client_vtable client_fcgi_vtable =
{
+ .push = client_fcgi_push,
.shutdown = client_fcgi_shutdown,
.finalize = client_fcgi_finalize,
- .push = client_fcgi_push,
};
static struct client *
@@ -2116,14 +2121,6 @@ client_scgi_on_content (void *user_data, const void *data, size_t len)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-static void
-client_scgi_finalize (struct client *client)
-{
- struct client_scgi *self = (struct client_scgi *) client;
- request_free (&self->request);
- scgi_parser_free (&self->parser);
-}
-
static bool
client_scgi_push (struct client *client, const void *data, size_t len)
{
@@ -2140,10 +2137,18 @@ client_scgi_push (struct client *client, const void *data, size_t len)
return false;
}
+static void
+client_scgi_finalize (struct client *client)
+{
+ struct client_scgi *self = (struct client_scgi *) client;
+ request_free (&self->request);
+ scgi_parser_free (&self->parser);
+}
+
static struct client_vtable client_scgi_vtable =
{
- .finalize = client_scgi_finalize,
.push = client_scgi_push,
+ .finalize = client_scgi_finalize,
};
static struct client *
@@ -2212,6 +2217,13 @@ client_ws_close_cb (struct ws_handler *handler, bool half_close)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+static bool
+client_ws_push (struct client *client, const void *data, size_t len)
+{
+ FIND_CONTAINER (self, client, struct client_ws, client);
+ return ws_handler_push (&self->handler, data, len);
+}
+
static void
client_ws_shutdown (struct client *client)
{
@@ -2229,18 +2241,11 @@ client_ws_finalize (struct client *client)
ws_handler_free (&self->handler);
}
-static bool
-client_ws_push (struct client *client, const void *data, size_t len)
-{
- FIND_CONTAINER (self, client, struct client_ws, client);
- return ws_handler_push (&self->handler, data, len);
-}
-
static struct client_vtable client_ws_vtable =
{
+ .push = client_ws_push,
.shutdown = client_ws_shutdown,
.finalize = client_ws_finalize,
- .push = client_ws_push,
};
static struct client *
--
cgit v1.2.3-70-g09d2
From 272145ace2d7d4bd0342cce1890410d20d861e37 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Wed, 17 Oct 2018 02:21:19 +0200
Subject: Clarify EOF behaviour
---
demo-json-rpc-server.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index d461691..c886364 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -413,6 +413,7 @@ fcgi_muxer_on_stdin (struct fcgi_muxer *self, const struct fcgi_parser *parser)
return;
}
+ // At the end of the stream, a zero-length record is received
fcgi_request_push_stdin (request,
parser->content.str, parser->content.len);
}
@@ -1092,7 +1093,7 @@ ws_handler_start (struct ws_handler *self)
ev_timer_start (EV_DEFAULT_ &self->handshake_timeout_watcher);
}
-/// Push data to the WebSocket handler; "len == 0" means EOF
+/// Push data to the WebSocket handler. "len == 0" means EOF.
static bool
ws_handler_push (struct ws_handler *self, const void *data, size_t len)
{
@@ -1488,7 +1489,7 @@ struct request_handler
bool (*try_handle) (struct request *request,
struct str_map *headers, bool *continue_);
- /// Handle incoming data.
+ /// Handle incoming data. "len == 0" means EOF.
/// Returns false if there is no more processing to be done.
bool (*push_cb) (struct request *request, const void *data, size_t len);
@@ -1768,10 +1769,10 @@ request_handler_static_push
{
(void) request;
(void) data;
- (void) len;
- // Ignoring all content; we shouldn't receive any (GET)
- return false;
+ // Aborting on content; we shouldn't receive any (GET)
+ // FIXME: there should at least be some indication of this happening
+ return len == 0;
}
static void
--
cgit v1.2.3-70-g09d2
From 83363e6383a7589d2a691ca1f988acb38ccea5b3 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Wed, 17 Oct 2018 02:57:08 +0200
Subject: FastCGI: make it work at least in theory
---
demo-json-rpc-server.c | 104 +++++++++++++++++++++++++++++++++++--------------
1 file changed, 75 insertions(+), 29 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index c886364..a915b3d 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -123,18 +123,32 @@ struct fcgi_request
void *handler_data; ///< Handler data
};
+/// Handles a single FastCGI connection, de/multiplexing requests and responses
struct fcgi_muxer
{
struct fcgi_parser parser; ///< FastCGI message parser
+ uint32_t active_requests; ///< The number of active requests
+ bool in_shutdown; ///< Rejecting new requests
- // TODO: bool quitting; that causes us to reject all requests?
+ // Virtual method callbacks:
+ /// Write data to the underlying transport
void (*write_cb) (struct fcgi_muxer *, const void *data, size_t len);
+
+ /// Close the underlying transport
+ // TODO: consider half-close and the subsequent handling
void (*close_cb) (struct fcgi_muxer *);
- void *(*request_start_cb) (struct fcgi_muxer *, struct fcgi_request *);
- void (*request_push_cb) (void *handler_data, const void *data, size_t len);
- void (*request_destroy_cb) (void *handler_data);
+ /// Start processing a request. Return false if no further action is
+ /// to be done and the request should be finished.
+ bool (*request_start_cb) (struct fcgi_muxer *, struct fcgi_request *);
+
+ /// Handle incoming data. "len == 0" means EOF.
+ void (*request_push_cb)
+ (struct fcgi_request *, const void *data, size_t len);
+
+ /// Destroy the handler's data stored in the request object
+ void (*request_destroy_cb) (struct fcgi_request *);
/// Requests assigned to request IDs (may not be FCGI_NULL_REQUEST_ID)
struct fcgi_request *requests[1 << 8];
@@ -177,22 +191,27 @@ fcgi_muxer_send_end_request (struct fcgi_muxer *self, uint16_t request_id,
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-static void
-fcgi_request_init (struct fcgi_request *self)
+static struct fcgi_request *
+fcgi_request_new (void)
{
- memset (self, 0, sizeof *self);
+ struct fcgi_request *self = xcalloc (1, sizeof *self);
self->headers = str_map_make (free);
self->hdr_parser = fcgi_nv_parser_make ();
self->hdr_parser.output = &self->headers;
+ return self;
}
static void
-fcgi_request_free (struct fcgi_request *self)
+fcgi_request_destroy (struct fcgi_request *self)
{
+ // TODO: consider the case where it hasn't been started yet
+ self->muxer->request_destroy_cb (self);
+
str_map_free (&self->headers);
fcgi_nv_parser_free (&self->hdr_parser);
+ free (self);
}
static void
@@ -211,7 +230,7 @@ fcgi_request_push_params
{
// TODO: probably check the state of the header parser
// TODO: request_start() can return false, end the request here?
- self->handler_data = self->muxer->request_start_cb (self->muxer, self);
+ (void) self->muxer->request_start_cb (self->muxer, self);
self->state = FCGI_REQUEST_STDIN;
}
}
@@ -226,7 +245,7 @@ fcgi_request_push_stdin
return;
}
- self->muxer->request_push_cb (self->handler_data, data, len);
+ self->muxer->request_push_cb (self, data, len);
}
static void
@@ -266,7 +285,21 @@ fcgi_request_write (struct fcgi_request *self, const void *data, size_t len)
static void
fcgi_request_finish (struct fcgi_request *self)
{
- // TODO: flush(), end_request(), delete self, muxer->request_destroy_cb()?
+ fcgi_request_flush (self);
+ fcgi_muxer_send (self->muxer, FCGI_STDOUT, self->request_id, NULL, 0);
+
+ fcgi_muxer_send_end_request (self->muxer, self->request_id,
+ 0 /* TODO app_status, although ignored */,
+ FCGI_REQUEST_COMPLETE /* TODO protocol_status, may be different */);
+
+ if (!(self->flags & FCGI_KEEP_CONN))
+ {
+ // TODO: tear down (shut down) the connection
+ }
+
+ self->muxer->active_requests--;
+ self->muxer->requests[self->request_id] = NULL;
+ fcgi_request_destroy (self);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -354,21 +387,21 @@ fcgi_muxer_on_begin_request
return;
}
- // TODO: also send OVERLOADED when shutting down?
- if (parser->request_id >= N_ELEMENTS (self->requests))
+ if (parser->request_id >= N_ELEMENTS (self->requests)
+ || self->in_shutdown)
{
fcgi_muxer_send_end_request (self,
parser->request_id, 0, FCGI_OVERLOADED);
return;
}
- request = xcalloc (1, sizeof *request);
- fcgi_request_init (request);
+ request = fcgi_request_new ();
request->muxer = self;
request->request_id = parser->request_id;
request->flags = flags;
self->requests[parser->request_id] = request;
+ self->active_requests++;
}
static void
@@ -464,6 +497,17 @@ fcgi_muxer_init (struct fcgi_muxer *self)
static void
fcgi_muxer_free (struct fcgi_muxer *self)
{
+ for (size_t i = 0; i < N_ELEMENTS (self->requests); i++)
+ {
+ if (!self->active_requests)
+ break;
+ if (self->requests[i])
+ {
+ fcgi_request_destroy (self->requests[i]);
+ self->active_requests--;
+ }
+ }
+
fcgi_parser_free (&self->parser);
}
@@ -1977,33 +2021,35 @@ client_fcgi_request_close_cb (struct request *req)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-static void *
+static bool
client_fcgi_request_start
(struct fcgi_muxer *mux, struct fcgi_request *fcgi_request)
{
FIND_CONTAINER (self, mux, struct client_fcgi, muxer);
- // TODO: what if the request is aborted by ;
- struct client_fcgi_request *request = xcalloc (1, sizeof *request);
+ struct client_fcgi_request *request =
+ fcgi_request->handler_data = xcalloc (1, sizeof *request);
request->fcgi_request = fcgi_request;
request_init (&request->request);
request->request.ctx = self->client.ctx;
request->request.write_cb = client_fcgi_request_write_cb;
request->request.close_cb = client_fcgi_request_close_cb;
- return request;
+
+ return request_start (&request->request, &fcgi_request->headers);
}
static void
-client_fcgi_request_push (void *handler_data, const void *data, size_t len)
+client_fcgi_request_push
+ (struct fcgi_request *req, const void *data, size_t len)
{
- struct client_fcgi_request *request = handler_data;
+ struct client_fcgi_request *request = req->handler_data;
request_push (&request->request, data, len);
}
static void
-client_fcgi_request_destroy (void *handler_data)
+client_fcgi_request_destroy (struct fcgi_request *req)
{
- struct client_fcgi_request *request = handler_data;
+ struct client_fcgi_request *request = req->handler_data;
request_free (&request->request);
free (request);
}
@@ -2031,7 +2077,7 @@ client_fcgi_close_cb (struct fcgi_muxer *mux)
static bool
client_fcgi_push (struct client *client, const void *data, size_t len)
{
- struct client_fcgi *self = (struct client_fcgi *) client;
+ FIND_CONTAINER (self, client, struct client_fcgi, client);
fcgi_muxer_push (&self->muxer, data, len);
return true;
}
@@ -2039,17 +2085,17 @@ client_fcgi_push (struct client *client, const void *data, size_t len)
static void
client_fcgi_shutdown (struct client *client)
{
- struct client_fcgi *self = (struct client_fcgi *) client;
+ FIND_CONTAINER (self, client, struct client_fcgi, client);
+ self->muxer.in_shutdown = true;
- // TODO: respond with FCGI_END_REQUEST: FCGI_REQUEST_COMPLETE to everything,
- // and start sending out FCGI_OVERLOADED to all incoming requests. The
- // FastCGI specification isn't very clear about what we should do.
+ // TODO: respond with FCGI_END_REQUEST: FCGI_REQUEST_COMPLETE to everything?
+ // The FastCGI specification isn't very clear about what we should do.
}
static void
client_fcgi_finalize (struct client *client)
{
- struct client_fcgi *self = (struct client_fcgi *) client;
+ FIND_CONTAINER (self, client, struct client_fcgi, client);
fcgi_muxer_free (&self->muxer);
}
--
cgit v1.2.3-70-g09d2
From 1d638c9170c2f785000aa1e7fff30974ae9be3d6 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Wed, 17 Oct 2018 03:53:07 +0200
Subject: Say "finalize" instead of "destroy" where appropriate
- _make() returns a struct directly
- _init() initializes over a pointer
- _free() deinitializes over a pointer
- _new() is like _init() but also allocates
- _destroy() is like _free() but also deallocates
Finalization is a matching concept in garbage-collected languages.
---
demo-json-rpc-server.c | 36 ++++++++++++++++++------------------
1 file changed, 18 insertions(+), 18 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index a915b3d..8ad1c36 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -148,7 +148,7 @@ struct fcgi_muxer
(struct fcgi_request *, const void *data, size_t len);
/// Destroy the handler's data stored in the request object
- void (*request_destroy_cb) (struct fcgi_request *);
+ void (*request_finalize_cb) (struct fcgi_request *);
/// Requests assigned to request IDs (may not be FCGI_NULL_REQUEST_ID)
struct fcgi_request *requests[1 << 8];
@@ -207,7 +207,7 @@ static void
fcgi_request_destroy (struct fcgi_request *self)
{
// TODO: consider the case where it hasn't been started yet
- self->muxer->request_destroy_cb (self);
+ self->muxer->request_finalize_cb (self);
str_map_free (&self->headers);
fcgi_nv_parser_free (&self->hdr_parser);
@@ -1538,7 +1538,7 @@ struct request_handler
bool (*push_cb) (struct request *request, const void *data, size_t len);
/// Destroy the handler's data stored in the request object
- void (*destroy_cb) (struct request *request);
+ void (*finalize_cb) (struct request *request);
};
static void
@@ -1551,7 +1551,7 @@ static void
request_free (struct request *self)
{
if (self->handler)
- self->handler->destroy_cb (self);
+ self->handler->finalize_cb (self);
}
/// This function is only intended to be run from asynchronous event handlers
@@ -1651,7 +1651,7 @@ request_handler_json_rpc_push
}
static void
-request_handler_json_rpc_destroy (struct request *request)
+request_handler_json_rpc_finalize (struct request *request)
{
struct str *buf = request->handler_data;
str_free (buf);
@@ -1662,9 +1662,9 @@ request_handler_json_rpc_destroy (struct request *request)
struct request_handler g_request_handler_json_rpc =
{
- .try_handle = request_handler_json_rpc_try_handle,
- .push_cb = request_handler_json_rpc_push,
- .destroy_cb = request_handler_json_rpc_destroy,
+ .try_handle = request_handler_json_rpc_try_handle,
+ .push_cb = request_handler_json_rpc_push,
+ .finalize_cb = request_handler_json_rpc_finalize,
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -1820,7 +1820,7 @@ request_handler_static_push
}
static void
-request_handler_static_destroy (struct request *request)
+request_handler_static_finalize (struct request *request)
{
(void) request;
// Nothing to dispose of this far
@@ -1828,9 +1828,9 @@ request_handler_static_destroy (struct request *request)
struct request_handler g_request_handler_static =
{
- .try_handle = request_handler_static_try_handle,
- .push_cb = request_handler_static_push,
- .destroy_cb = request_handler_static_destroy,
+ .try_handle = request_handler_static_try_handle,
+ .push_cb = request_handler_static_push,
+ .finalize_cb = request_handler_static_finalize,
};
// --- Client communication handlers -------------------------------------------
@@ -2047,7 +2047,7 @@ client_fcgi_request_push
}
static void
-client_fcgi_request_destroy (struct fcgi_request *req)
+client_fcgi_request_finalize (struct fcgi_request *req)
{
struct client_fcgi_request *request = req->handler_data;
request_free (&request->request);
@@ -2114,11 +2114,11 @@ client_fcgi_create (EV_P_ int sock_fd)
self->client.vtable = &client_fcgi_vtable;
fcgi_muxer_init (&self->muxer);
- self->muxer.write_cb = client_fcgi_write_cb;
- self->muxer.close_cb = client_fcgi_close_cb;
- self->muxer.request_start_cb = client_fcgi_request_start;
- self->muxer.request_push_cb = client_fcgi_request_push;
- self->muxer.request_destroy_cb = client_fcgi_request_destroy;
+ self->muxer.write_cb = client_fcgi_write_cb;
+ self->muxer.close_cb = client_fcgi_close_cb;
+ self->muxer.request_start_cb = client_fcgi_request_start;
+ self->muxer.request_push_cb = client_fcgi_request_push;
+ self->muxer.request_finalize_cb = client_fcgi_request_finalize;
return &self->client;
}
--
cgit v1.2.3-70-g09d2
From 13892fcd0e2f95b8033b92838996b0b700905652 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Wed, 17 Oct 2018 05:16:17 +0200
Subject: Clean up client de/allocation
---
demo-json-rpc-server.c | 26 ++++++++++----------------
1 file changed, 10 insertions(+), 16 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 8ad1c36..ce50c0f 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -1871,12 +1871,6 @@ struct client_vtable
void (*finalize) (struct client *client);
};
-static void
-client_free (struct client *self)
-{
- write_queue_free (&self->write_queue);
-}
-
static void
client_write (struct client *self, const void *data, size_t len)
{
@@ -1902,7 +1896,7 @@ client_destroy (struct client *self)
ev_io_stop (EV_DEFAULT_ &self->read_watcher);
ev_io_stop (EV_DEFAULT_ &self->write_watcher);
xclose (self->socket_fd);
- client_free (self);
+ write_queue_free (&self->write_queue);
free (self);
try_finish_quit (ctx);
@@ -1962,12 +1956,14 @@ close:
client_destroy (client);
}
-static void
-client_init (EV_P_ struct client *self, int sock_fd)
+/// Create a new instance of a subclass with the given size.
+/// The superclass is assumed to be the first member of the structure.
+static void *
+client_new (EV_P_ size_t size, int sock_fd)
{
struct server_context *ctx = ev_userdata (loop);
+ struct client *self = xcalloc (1, size);
- memset (self, 0, sizeof *self);
self->ctx = ctx;
self->write_queue = write_queue_make ();
@@ -1984,6 +1980,7 @@ client_init (EV_P_ struct client *self, int sock_fd)
LIST_PREPEND (ctx->clients, self);
ctx->n_clients++;
+ return self;
}
// --- FastCGI client handler --------------------------------------------------
@@ -2109,8 +2106,7 @@ static struct client_vtable client_fcgi_vtable =
static struct client *
client_fcgi_create (EV_P_ int sock_fd)
{
- struct client_fcgi *self = xcalloc (1, sizeof *self);
- client_init (EV_A_ &self->client, sock_fd);
+ struct client_fcgi *self = client_new (EV_A_ sizeof *self, sock_fd);
self->client.vtable = &client_fcgi_vtable;
fcgi_muxer_init (&self->muxer);
@@ -2201,8 +2197,7 @@ static struct client_vtable client_scgi_vtable =
static struct client *
client_scgi_create (EV_P_ int sock_fd)
{
- struct client_scgi *self = xcalloc (1, sizeof *self);
- client_init (EV_A_ &self->client, sock_fd);
+ struct client_scgi *self = client_new (EV_A_ sizeof *self, sock_fd);
self->client.vtable = &client_scgi_vtable;
request_init (&self->request);
@@ -2298,8 +2293,7 @@ static struct client_vtable client_ws_vtable =
static struct client *
client_ws_create (EV_P_ int sock_fd)
{
- struct client_ws *self = xcalloc (1, sizeof *self);
- client_init (EV_A_ &self->client, sock_fd);
+ struct client_ws *self = client_new (EV_A_ sizeof *self, sock_fd);
self->client.vtable = &client_ws_vtable;
ws_handler_init (&self->handler);
--
cgit v1.2.3-70-g09d2
From efd500ca3cf4dfdc635ccd71ea8baf5a0d3c5bbd Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Wed, 17 Oct 2018 06:08:11 +0200
Subject: Accelerated daemon quitting
---
demo-json-rpc-server.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index ce50c0f..cde6ad0 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -2586,10 +2586,14 @@ on_termination_signal (EV_P_ ev_signal *handle, int revents)
(void) handle;
(void) revents;
- // TODO: consider quitting right away if already quitting;
- // considering that this may already happen in timeout, it should be OK;
- // see on_quit_timeout, just destroy all clients
- initiate_quit (ctx);
+ if (ctx->quitting)
+ {
+ // Double C-c from the terminal accelerates the process
+ LIST_FOR_EACH (struct client, iter, ctx->clients)
+ client_destroy (iter);
+ }
+ else
+ initiate_quit (ctx);
}
static void
--
cgit v1.2.3-70-g09d2
From a3ec0942f8063509a00695d6e702a7f914caa7e9 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Wed, 17 Oct 2018 08:40:18 +0200
Subject: Implement basic connection teardown
I finally understand the codebase again. It's rather complicated.
---
demo-json-rpc-server.c | 240 ++++++++++++++++++++++++++++++-------------------
1 file changed, 147 insertions(+), 93 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index cde6ad0..563d11c 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -47,10 +47,10 @@ enum { PIPE_READ, PIPE_WRITE };
#define FIND_CONTAINER(name, pointer, type, member) \
type *name = CONTAINER_OF (pointer, type, member)
-// --- libev helpers -----------------------------------------------------------
+// --- Utilities ---------------------------------------------------------------
static bool
-flush_queue (struct write_queue *queue, ev_io *watcher)
+flush_queue (struct write_queue *queue, int fd)
{
struct iovec vec[queue->len], *vec_iter = vec;
LIST_FOR_EACH (struct write_req, iter, queue->head)
@@ -58,24 +58,17 @@ flush_queue (struct write_queue *queue, ev_io *watcher)
ssize_t written;
again:
- written = writev (watcher->fd, vec, N_ELEMENTS (vec));
- if (written < 0)
+ if ((written = writev (fd, vec, N_ELEMENTS (vec))) >= 0)
{
- if (errno == EAGAIN)
- goto skip;
- if (errno == EINTR)
- goto again;
- return false;
+ write_queue_processed (queue, written);
+ return true;
}
+ if (errno == EINTR)
+ goto again;
+ if (errno == EAGAIN)
+ return true;
- write_queue_processed (queue, written);
-
-skip:
- if (write_queue_is_empty (queue))
- ev_io_stop (EV_DEFAULT_ watcher);
- else
- ev_io_start (EV_DEFAULT_ watcher);
- return true;
+ return false;
}
// --- Logging -----------------------------------------------------------------
@@ -135,16 +128,17 @@ struct fcgi_muxer
/// Write data to the underlying transport
void (*write_cb) (struct fcgi_muxer *, const void *data, size_t len);
- /// Close the underlying transport
- // TODO: consider half-close and the subsequent handling
+ /// Close the underlying transport. You are allowed to destroy the muxer
+ /// directly from within the callback.
void (*close_cb) (struct fcgi_muxer *);
/// Start processing a request. Return false if no further action is
/// to be done and the request should be finished.
- bool (*request_start_cb) (struct fcgi_muxer *, struct fcgi_request *);
+ bool (*request_start_cb) (struct fcgi_request *);
- /// Handle incoming data. "len == 0" means EOF.
- void (*request_push_cb)
+ /// Handle incoming data. "len == 0" means EOF. Returns false if
+ /// the underlying transport should be closed, this being the last request.
+ bool (*request_push_cb)
(struct fcgi_request *, const void *data, size_t len);
/// Destroy the handler's data stored in the request object
@@ -230,7 +224,7 @@ fcgi_request_push_params
{
// TODO: probably check the state of the header parser
// TODO: request_start() can return false, end the request here?
- (void) self->muxer->request_start_cb (self->muxer, self);
+ (void) self->muxer->request_start_cb (self);
self->state = FCGI_REQUEST_STDIN;
}
}
@@ -282,7 +276,9 @@ fcgi_request_write (struct fcgi_request *self, const void *data, size_t len)
}
}
-static void
+/// Mark the request as done. Returns false if the underlying transport
+/// should be closed, this being the last request.
+static bool
fcgi_request_finish (struct fcgi_request *self)
{
fcgi_request_flush (self);
@@ -292,14 +288,26 @@ fcgi_request_finish (struct fcgi_request *self)
0 /* TODO app_status, although ignored */,
FCGI_REQUEST_COMPLETE /* TODO protocol_status, may be different */);
- if (!(self->flags & FCGI_KEEP_CONN))
- {
- // TODO: tear down (shut down) the connection
- }
+ bool should_close = !(self->flags & FCGI_KEEP_CONN);
self->muxer->active_requests--;
self->muxer->requests[self->request_id] = NULL;
fcgi_request_destroy (self);
+
+ // TODO: tear down (shut down) the connection. This is called from:
+ //
+ // 1. client_fcgi_request_push <- request_push_cb
+ // <- fcgi_request_push_stdin <- fcgi_muxer_on_stdin
+ // <- fcgi_muxer_on_message <- fcgi_parser_push <- fcgi_muxer_push
+ // <- client_fcgi_push <- client_read_loop
+ // => in this case no close_cb may be called
+ // -> need to pass a false boolean aaall the way up,
+ // then client_fcgi_finalize eventually cleans up the rest
+ //
+ // 2. client_fcgi_request_close_cb <- request_finish
+ // => our direct caller must call fcgi_muxer::close_cb
+ // -> not very nice to delegate it there
+ return !should_close;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -418,6 +426,7 @@ fcgi_muxer_on_abort_request
// TODO: abort the request: let it somehow produce FCGI_END_REQUEST,
// make sure to send an stdout EOF record
+ // TODO: and if that was not a FCGI_KEEP_CONN request, close the transport
}
static void
@@ -1841,23 +1850,24 @@ struct client
{
LIST_HEADER (struct client)
- // XXX: do we really need this here?
- struct server_context *ctx; ///< Server context
+ struct client_vtable *vtable; ///< Client behaviour
- int socket_fd; ///< The TCP socket
+ int socket_fd; ///< The network socket
+ bool received_eof; ///< Whether EOF has been received yet
+ bool closing; ///< Whether we're just flushing buffers
+ bool half_closed; ///< Transport half-closed while closing
struct write_queue write_queue; ///< Write queue
+ ev_timer flush_timeout_watcher; ///< Write queue flush timer
ev_io read_watcher; ///< The socket can be read from
ev_io write_watcher; ///< The socket can be written to
-
- struct client_vtable *vtable; ///< Client behaviour
};
/// The concrete behaviour to serve a particular client's requests
struct client_vtable
{
/// Process incoming data; "len == 0" means EOF.
- /// If the method returns false, the client is destroyed by caller.
+ /// If the method returns false, client_close() is called by the caller.
bool (*push) (struct client *client, const void *data, size_t len);
// TODO: optional push_error() to inform about network I/O errors
@@ -1885,8 +1895,8 @@ client_write (struct client *self, const void *data, size_t len)
static void
client_destroy (struct client *self)
{
- struct server_context *ctx = self->ctx;
-
+ // XXX: this codebase halfway pretends there could be other contexts
+ struct server_context *ctx = ev_userdata (EV_DEFAULT);
LIST_UNLINK (ctx->clients, self);
ctx->n_clients--;
@@ -1897,63 +1907,111 @@ client_destroy (struct client *self)
ev_io_stop (EV_DEFAULT_ &self->write_watcher);
xclose (self->socket_fd);
write_queue_free (&self->write_queue);
+ ev_timer_stop (EV_DEFAULT_ &self->flush_timeout_watcher);
free (self);
try_finish_quit (ctx);
}
+/// Try to cleanly close the connection, waiting for the remote client to close
+/// its own side of the connection as a sign that it has processed all the data
+/// it wanted to. The client implementation will not receive any further data.
+/// May directly call client_destroy().
+static void
+client_close (struct client *self)
+{
+ if (self->closing)
+ return;
+
+ self->closing = true;
+ ev_timer_start (EV_DEFAULT_ &self->flush_timeout_watcher);
+ ev_feed_event (EV_DEFAULT_ &self->write_watcher, EV_WRITE);
+
+ // We assume the remote client doesn't want our data if it half-closes
+ if (self->received_eof)
+ client_destroy (self);
+}
+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static bool
client_read_loop (EV_P_ struct client *client, ev_io *watcher)
{
char buf[8192];
- while (true)
+ ssize_t n_read;
+again:
+ while ((n_read = recv (watcher->fd, buf, sizeof buf, 0)) >= 0)
{
- ssize_t n_read = recv (watcher->fd, buf, sizeof buf, 0);
- if (n_read >= 0)
+ if (!n_read)
{
- if (!client->vtable->push (client, buf, n_read))
- return false;
- if (!n_read)
- break;
+ // Don't deliver the EOF condition repeatedly
+ ev_io_stop (EV_A_ watcher);
+ client->received_eof = true;
}
- else if (errno == EAGAIN)
- return true;
- else if (errno != EINTR)
+ if (!client->closing
+ && !client->vtable->push (client, buf, n_read))
+ {
+ client_close (client);
return false;
+ }
+ if (!n_read)
+ return true;
}
+ if (errno == EINTR)
+ goto again;
+ if (errno == EAGAIN)
+ return true;
- // Don't receive the EOF condition repeatedly
- ev_io_stop (EV_A_ watcher);
+ client_destroy (client);
+ return false;
+}
- // We can probably still write, so let's just return
- // XXX: if there's nothing to be written, shouldn't we close the connection?
- return true;
+static void
+on_client_readable (EV_P_ ev_io *watcher, int revents)
+{
+ struct client *client = watcher->data;
+ (void) revents;
+
+ if (client_read_loop (EV_A_ client, watcher)
+ && client->closing && client->received_eof)
+ client_destroy (client);
}
static void
-on_client_ready (EV_P_ ev_io *watcher, int revents)
+on_client_writable (EV_P_ ev_io *watcher, int revents)
{
struct client *client = watcher->data;
- // XXX: although read and write are in a sequence, if we create response
- // data, we'll still likely need to go back to the event loop.
-
- if (revents & EV_READ)
- if (!client_read_loop (EV_A_ client, watcher))
- goto close;
- if (revents & EV_WRITE)
- // TODO: add "closing link" functionality -> automatic shutdown
- // (half-close) once we manage to flush the write buffer,
- // which is logically followed by waiting for an EOF from the client
- // TODO: some sort of "on_buffers_flushed" callback for streaming huge
- // chunks of external (or generated) data.
- if (!flush_queue (&client->write_queue, watcher))
- goto close;
- return;
-
-close:
- client_destroy (client);
+ (void) loop;
+ (void) revents;
+
+ // TODO: some sort of "on_buffers_flushed" callback for streaming huge
+ // chunks of external (or generated) data. That will need to be
+ // forwarded to "struct request_handler".
+ if (!flush_queue (&client->write_queue, watcher->fd))
+ {
+ client_destroy (client);
+ return;
+ }
+ if (!write_queue_is_empty (&client->write_queue))
+ return;
+
+ ev_io_stop (EV_A_ watcher);
+ if (client->closing && !client->half_closed)
+ {
+ if (!shutdown (client->socket_fd, SHUT_WR))
+ client->half_closed = true;
+ else
+ client_destroy (client);
+ }
+}
+
+static void
+on_client_timeout (EV_P_ ev_timer *watcher, int revents)
+{
+ (void) loop;
+ (void) revents;
+
+ client_destroy (watcher->data);
}
/// Create a new instance of a subclass with the given size.
@@ -1964,14 +2022,15 @@ client_new (EV_P_ size_t size, int sock_fd)
struct server_context *ctx = ev_userdata (loop);
struct client *self = xcalloc (1, size);
- self->ctx = ctx;
self->write_queue = write_queue_make ();
+ ev_timer_init (&self->flush_timeout_watcher, on_client_timeout, 5., 0.);
+ self->flush_timeout_watcher.data = self;
set_blocking (sock_fd, false);
self->socket_fd = sock_fd;
- ev_io_init (&self->read_watcher, on_client_ready, sock_fd, EV_READ);
- ev_io_init (&self->write_watcher, on_client_ready, sock_fd, EV_WRITE);
+ ev_io_init (&self->read_watcher, on_client_readable, sock_fd, EV_READ);
+ ev_io_init (&self->write_watcher, on_client_writable, sock_fd, EV_WRITE);
self->read_watcher.data = self;
self->write_watcher.data = self;
@@ -2010,37 +2069,36 @@ static void
client_fcgi_request_close_cb (struct request *req)
{
FIND_CONTAINER (self, req, struct client_fcgi_request, request);
- // No more data to send, terminate the substream/request
- // XXX: this will most probably end up with client_fcgi_request_destroy(),
- // we might or might not need to defer this action
- fcgi_request_finish (self->fcgi_request);
+ struct fcgi_muxer *muxer = self->fcgi_request->muxer;
+ // No more data to send, terminate the substream/request,
+ // and also the transport if the client didn't specifically ask to keep it
+ if (!fcgi_request_finish (self->fcgi_request))
+ muxer->close_cb (muxer);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static bool
-client_fcgi_request_start
- (struct fcgi_muxer *mux, struct fcgi_request *fcgi_request)
+client_fcgi_request_start (struct fcgi_request *fcgi_request)
{
- FIND_CONTAINER (self, mux, struct client_fcgi, muxer);
-
struct client_fcgi_request *request =
fcgi_request->handler_data = xcalloc (1, sizeof *request);
request->fcgi_request = fcgi_request;
request_init (&request->request);
- request->request.ctx = self->client.ctx;
+ request->request.ctx = ev_userdata (EV_DEFAULT);
request->request.write_cb = client_fcgi_request_write_cb;
request->request.close_cb = client_fcgi_request_close_cb;
return request_start (&request->request, &fcgi_request->headers);
}
-static void
+static bool
client_fcgi_request_push
(struct fcgi_request *req, const void *data, size_t len)
{
struct client_fcgi_request *request = req->handler_data;
- request_push (&request->request, data, len);
+ return request_push (&request->request, data, len)
+ || fcgi_request_finish (req);
}
static void
@@ -2064,9 +2122,7 @@ static void
client_fcgi_close_cb (struct fcgi_muxer *mux)
{
FIND_CONTAINER (self, mux, struct client_fcgi, muxer);
- // FIXME: we should probably call something like client_shutdown(),
- // which may have an argument whether we should really use close()
- client_destroy (&self->client);
+ client_close (&self->client);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -2137,11 +2193,9 @@ client_scgi_write_cb (struct request *req, const void *data, size_t len)
static void
client_scgi_close_cb (struct request *req)
{
- // NOTE: this rather really means "close me [the request]"
FIND_CONTAINER (self, req, struct client_scgi, request);
- // FIXME: we should probably call something like client_shutdown(),
- // which may have an argument whether we should really use close()
- client_destroy (&self->client);
+ // NOTE: this rather really means "close me [the request]"
+ client_close (&self->client);
}
static bool
@@ -2201,7 +2255,6 @@ client_scgi_create (EV_P_ int sock_fd)
self->client.vtable = &client_scgi_vtable;
request_init (&self->request);
- self->request.ctx = self->client.ctx;
self->request.write_cb = client_scgi_write_cb;
self->request.close_cb = client_scgi_close_cb;
@@ -2231,8 +2284,9 @@ client_ws_on_message (struct ws_handler *handler,
return false;
}
+ struct server_context *ctx = ev_userdata (EV_DEFAULT);
struct str response = str_make ();
- process_json_rpc (self->client.ctx, data, len, &response);
+ process_json_rpc (ctx, data, len, &response);
if (response.len)
ws_handler_send (&self->handler,
WS_OPCODE_TEXT, response.str, response.len);
--
cgit v1.2.3-70-g09d2
From cf56921c4e1959bb3962b4f99ee4a1e7665c57d4 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Wed, 17 Oct 2018 23:26:11 +0200
Subject: Allow WebSockets to micromanage shutdowns
They have their reasons, mostly event-related.
---
demo-json-rpc-server.c | 59 ++++++++++++++++++++++++++++++--------------------
1 file changed, 35 insertions(+), 24 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 563d11c..2b99419 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -1854,10 +1854,11 @@ struct client
int socket_fd; ///< The network socket
bool received_eof; ///< Whether EOF has been received yet
- bool closing; ///< Whether we're just flushing buffers
- bool half_closed; ///< Transport half-closed while closing
+ bool flushing; ///< No more data to write, send FIN
+ bool closing; ///< No more data to read or write
+ bool half_closed; ///< Conn. half-closed while flushing
struct write_queue write_queue; ///< Write queue
- ev_timer flush_timeout_watcher; ///< Write queue flush timer
+ ev_timer close_timeout_watcher; ///< Write queue flush timer
ev_io read_watcher; ///< The socket can be read from
ev_io write_watcher; ///< The socket can be written to
@@ -1881,17 +1882,6 @@ struct client_vtable
void (*finalize) (struct client *client);
};
-static void
-client_write (struct client *self, const void *data, size_t len)
-{
- struct write_req *req = xcalloc (1, sizeof *req);
- req->data.iov_base = memcpy (xmalloc (len), data, len);
- req->data.iov_len = len;
-
- write_queue_add (&self->write_queue, req);
- ev_io_start (EV_DEFAULT_ &self->write_watcher);
-}
-
static void
client_destroy (struct client *self)
{
@@ -1907,12 +1897,36 @@ client_destroy (struct client *self)
ev_io_stop (EV_DEFAULT_ &self->write_watcher);
xclose (self->socket_fd);
write_queue_free (&self->write_queue);
- ev_timer_stop (EV_DEFAULT_ &self->flush_timeout_watcher);
+ ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher);
free (self);
try_finish_quit (ctx);
}
+static void
+client_write (struct client *self, const void *data, size_t len)
+{
+ if (!soft_assert (!self->flushing))
+ return;
+
+ struct write_req *req = xcalloc (1, sizeof *req);
+ req->data.iov_base = memcpy (xmalloc (len), data, len);
+ req->data.iov_len = len;
+
+ write_queue_add (&self->write_queue, req);
+ ev_io_start (EV_DEFAULT_ &self->write_watcher);
+}
+
+/// Half-close the connection from our side once the write_queue is flushed.
+/// It is the caller's responsibility to destroy the connection upon EOF.
+// XXX: or we might change on_client_readable to do it anyway, seems safe
+static void
+client_shutdown (struct client *self)
+{
+ self->flushing = true;
+ ev_feed_event (EV_DEFAULT_ &self->write_watcher, EV_WRITE);
+}
+
/// Try to cleanly close the connection, waiting for the remote client to close
/// its own side of the connection as a sign that it has processed all the data
/// it wanted to. The client implementation will not receive any further data.
@@ -1924,8 +1938,8 @@ client_close (struct client *self)
return;
self->closing = true;
- ev_timer_start (EV_DEFAULT_ &self->flush_timeout_watcher);
- ev_feed_event (EV_DEFAULT_ &self->write_watcher, EV_WRITE);
+ ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher);
+ client_shutdown (self);
// We assume the remote client doesn't want our data if it half-closes
if (self->received_eof)
@@ -1996,7 +2010,7 @@ on_client_writable (EV_P_ ev_io *watcher, int revents)
return;
ev_io_stop (EV_A_ watcher);
- if (client->closing && !client->half_closed)
+ if (client->flushing && !client->half_closed)
{
if (!shutdown (client->socket_fd, SHUT_WR))
client->half_closed = true;
@@ -2023,8 +2037,8 @@ client_new (EV_P_ size_t size, int sock_fd)
struct client *self = xcalloc (1, size);
self->write_queue = write_queue_make ();
- ev_timer_init (&self->flush_timeout_watcher, on_client_timeout, 5., 0.);
- self->flush_timeout_watcher.data = self;
+ ev_timer_init (&self->close_timeout_watcher, on_client_timeout, 5., 0.);
+ self->close_timeout_watcher.data = self;
set_blocking (sock_fd, false);
self->socket_fd = sock_fd;
@@ -2305,10 +2319,7 @@ static void
client_ws_close_cb (struct ws_handler *handler, bool half_close)
{
FIND_CONTAINER (self, handler, struct client_ws, handler);
- if (half_close)
- ; // FIXME: we should probably call something like client_shutdown()
- else
- client_destroy (&self->client);
+ (half_close ? client_shutdown : client_destroy) (&self->client);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
cgit v1.2.3-70-g09d2
From 253e35e1e43b8c7f8ea8de7b94556a4ef6cf0d95 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Wed, 17 Oct 2018 23:34:59 +0200
Subject: Wrap request::write_cb in a function
---
demo-json-rpc-server.c | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 2b99419..9552146 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -1563,6 +1563,13 @@ request_free (struct request *self)
self->handler->finalize_cb (self);
}
+/// Write request CGI response data, intended for use by request handlers
+static void
+request_write (struct request *self, const void *data, size_t len)
+{
+ self->write_cb (self, data, len);
+}
+
/// This function is only intended to be run from asynchronous event handlers
/// such as timers, not as a direct result of starting the request or receiving
/// request data. CALLING THIS MAY CAUSE THE REQUEST TO BE DESTROYED.
@@ -1599,7 +1606,7 @@ request_start (struct request *self, struct str_map *headers)
struct str response = str_make ();
str_append (&response, "Status: 404 Not Found\n");
str_append (&response, "Content-Type: text/plain\n\n");
- self->write_cb (self, response.str, response.len);
+ request_write (self, response.str, response.len);
str_free (&response);
return false;
}
@@ -1654,7 +1661,7 @@ request_handler_json_rpc_push
str_append (&response, "Status: 200 OK\n");
str_append_printf (&response, "Content-Type: %s\n\n", "application/json");
process_json_rpc (request->ctx, buf->str, buf->len, &response);
- request->write_cb (request, response.str, response.len);
+ request_write (request, response.str, response.len);
str_free (&response);
return false;
}
@@ -1770,7 +1777,7 @@ request_handler_static_try_handle
str_append (&response, "Content-Type: text/plain\n\n");
str_append_printf (&response,
"File %s was not found on this server\n", suffix);
- request->write_cb (request, response.str, response.len);
+ request_write (request, response.str, response.len);
str_free (&response);
free (suffix);
@@ -1794,17 +1801,17 @@ request_handler_static_try_handle
struct str response = str_make ();
str_append (&response, "Status: 200 OK\n");
str_append_printf (&response, "Content-Type: %s\n\n", mime_type);
- request->write_cb (request, response.str, response.len);
+ request_write (request, response.str, response.len);
str_free (&response);
free (mime_type);
// Write the chunk we've used to help us with magic detection;
// obviously we have to do it after we've written the headers
if (len)
- request->write_cb (request, buf, len);
+ request_write (request, buf, len);
while ((len = fread (buf, 1, sizeof buf, fp)))
- request->write_cb (request, buf, len);
+ request_write (request, buf, len);
fclose (fp);
// TODO: this should rather not be returned all at once but in chunks;
--
cgit v1.2.3-70-g09d2
From 580f0a0c5960d830f49c4cddbfdf869c1d262a0c Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Wed, 17 Oct 2018 23:56:11 +0200
Subject: Synthesize EOF events in SCGI
---
demo-json-rpc-server.c | 28 +++++++++++++++++++++++-----
1 file changed, 23 insertions(+), 5 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 9552146..1ae1bad 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -1544,6 +1544,8 @@ struct request_handler
/// Handle incoming data. "len == 0" means EOF.
/// Returns false if there is no more processing to be done.
+ // FIXME: the EOF may or may not be delivered when request is cut short,
+ // we should fix FastCGI not to deliver it on CONTENT_LENGTH mismatch
bool (*push_cb) (struct request *request, const void *data, size_t len);
/// Destroy the handler's data stored in the request object
@@ -1655,7 +1657,8 @@ request_handler_json_rpc_push
}
// TODO: check buf.len against CONTENT_LENGTH; if it's less, then the
- // client hasn't been successful in transferring all of its data
+ // client hasn't been successful in transferring all of its data.
+ // See also comment on request_handler::push_cb.
struct str response = str_make ();
str_append (&response, "Status: 200 OK\n");
@@ -2202,6 +2205,7 @@ struct client_scgi
struct client client; ///< Parent class
struct scgi_parser parser; ///< SCGI stream parser
struct request request; ///< Request (only one per connection)
+ unsigned long remaining_content; ///< Length of input data to be seen
};
static void
@@ -2223,6 +2227,12 @@ static bool
client_scgi_on_headers_read (void *user_data)
{
struct client_scgi *self = user_data;
+ const char *cl = str_map_find (&self->parser.headers, "CONTENT_LENGTH");
+ if (!cl || !xstrtoul (&self->remaining_content, cl, 10))
+ {
+ print_debug ("SCGI request with invalid or missing CONTENT_LENGTH");
+ return false;
+ }
return request_start (&self->request, &self->parser.headers);
}
@@ -2230,11 +2240,19 @@ static bool
client_scgi_on_content (void *user_data, const void *data, size_t len)
{
struct client_scgi *self = user_data;
+ if (len > self->remaining_content)
+ {
+ print_debug ("SCGI request got more data than CONTENT_LENGTH");
+ return false;
+ }
+ // We're in a slight disagreement with the specification since
+ // this tries to write output before it has read all the input
+ if (!request_push (&self->request, data, len))
+ return false;
- // XXX: do we have to count CONTENT_LENGTH and supply our own EOF?
- // If we do produce our own EOF, we should probably make sure we don't
- // send it twice in a row.
- return request_push (&self->request, data, len);
+ // Signalise end of input to the request handler
+ return (self->remaining_content -= len) != 0
+ || request_push (&self->request, NULL, 0);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
cgit v1.2.3-70-g09d2
From 62945cceb3b2f9fae0fd39842448ebfeba378fa9 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Thu, 18 Oct 2018 02:55:55 +0200
Subject: Finish the WebSocket backend
Of course, everything so far hasn't been tested much.
---
demo-json-rpc-server.c | 271 +++++++++++++++++++++++++++++--------------------
1 file changed, 162 insertions(+), 109 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 1ae1bad..60a7173 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -540,9 +540,9 @@ enum ws_handler_state
{
WS_HANDLER_CONNECTING, ///< Parsing HTTP
WS_HANDLER_OPEN, ///< Parsing WebSockets frames
- WS_HANDLER_CLOSING, ///< Closing the connection
- WS_HANDLER_ALMOST_DEAD, ///< Closing connection after failure
- WS_HANDLER_CLOSED ///< Dead
+ WS_HANDLER_CLOSING, ///< Partial closure by us
+ WS_HANDLER_FLUSHING, ///< Just waiting for client EOF
+ WS_HANDLER_CLOSED ///< Dead, both sides closed
};
struct ws_handler
@@ -584,6 +584,7 @@ struct ws_handler
// TODO: void (*on_handshake) (protocols) that will allow the user
// to choose any sub-protocol, if the client has provided any.
// This may render "on_connected" unnecessary.
+ // Should also enable failing the handshake.
/// Called after successfuly connecting (handshake complete)
bool (*on_connected) (struct ws_handler *);
@@ -626,36 +627,41 @@ static void
ws_handler_close (struct ws_handler *self,
enum ws_status close_code, const char *reason, size_t len)
{
+ hard_assert (self->state == WS_HANDLER_OPEN);
+
struct str payload = str_make ();
str_pack_u16 (&payload, close_code);
// XXX: maybe accept a null-terminated string on input? Has to be UTF-8 a/w
str_append_data (&payload, reason, len);
ws_handler_send_control (self, WS_OPCODE_CLOSE, payload.str, payload.len);
-
- // Close initiated by us; the reason is null-terminated within `payload'
- if (self->on_close)
- self->on_close (self, close_code, payload.str + 2);
+ self->close_cb (self, true /* half_close */);
self->state = WS_HANDLER_CLOSING;
str_free (&payload);
}
-static void
-ws_handler_fail (struct ws_handler *self, enum ws_status close_code)
+static bool
+ws_handler_fail_connection (struct ws_handler *self, enum ws_status close_code)
{
- ws_handler_close (self, close_code, NULL, 0);
- self->state = WS_HANDLER_ALMOST_DEAD;
+ hard_assert (self->state == WS_HANDLER_OPEN
+ || self->state == WS_HANDLER_CLOSING);
+
+ if (self->state == WS_HANDLER_OPEN)
+ ws_handler_close (self, close_code, NULL, 0);
- // TODO: set the close timer, ignore all further incoming input (either set
- // some flag for the case that we're in the middle of ws_handler_push(),
- // and/or add a mechanism to stop the caller from polling the socket for
- // reads).
- // TODO: make sure we don't send pings after the close
+ self->state = WS_HANDLER_FLUSHING;
+ if (self->on_close)
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "");
+
+ ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
+ ev_timer_set (&self->close_timeout_watcher, self->close_timeout, 0.);
+ ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher);
+ return false;
}
// TODO: add support for fragmented responses
static void
-ws_handler_send (struct ws_handler *self,
+ws_handler_send_frame (struct ws_handler *self,
enum ws_opcode opcode, const void *data, size_t len)
{
if (!soft_assert (self->state == WS_HANDLER_OPEN))
@@ -697,24 +703,26 @@ ws_handler_on_frame_header (void *user_data, const struct ws_parser *parser)
|| (!ws_is_control_frame (parser->opcode) &&
(self->expecting_continuation && parser->opcode != WS_OPCODE_CONT))
|| parser->payload_len >= 0x8000000000000000ULL)
- ws_handler_fail (self, WS_STATUS_PROTOCOL_ERROR);
- else if (parser->payload_len > self->max_payload_len)
- ws_handler_fail (self, WS_STATUS_MESSAGE_TOO_BIG);
- else
- return true;
- return false;
+ return ws_handler_fail_connection (self, WS_STATUS_PROTOCOL_ERROR);
+
+ if (parser->payload_len > self->max_payload_len
+ || (self->expecting_continuation &&
+ self->message_data.len + parser->payload_len > self->max_payload_len))
+ return ws_handler_fail_connection (self, WS_STATUS_MESSAGE_TOO_BIG);
+ return true;
}
static bool
-ws_handler_on_protocol_close
+ws_handler_on_control_close
(struct ws_handler *self, const struct ws_parser *parser)
{
+ hard_assert (self->state == WS_HANDLER_OPEN
+ || self->state == WS_HANDLER_CLOSING);
struct msg_unpacker unpacker =
msg_unpacker_make (parser->input.str, parser->payload_len);
char *reason = NULL;
uint16_t close_code = WS_STATUS_NO_STATUS_RECEIVED;
-
if (parser->payload_len >= 2)
{
(void) msg_unpacker_u16 (&unpacker, &close_code);
@@ -723,17 +731,29 @@ ws_handler_on_protocol_close
else
reason = xstrdup ("");
- if (self->state != WS_HANDLER_CLOSING)
+ if (close_code < 1000 || close_code > 4999)
+ // XXX: invalid close code: maybe we should fail the connection instead
+ close_code = WS_STATUS_PROTOCOL_ERROR;
+
+ if (self->state == WS_HANDLER_OPEN)
{
// Close initiated by the client
+ // FIXME: not sending the potentially different close_code
ws_handler_send_control (self, WS_OPCODE_CLOSE,
parser->input.str, parser->payload_len);
+
+ self->state = WS_HANDLER_FLUSHING;
if (self->on_close)
self->on_close (self, close_code, reason);
}
+ else
+ self->state = WS_HANDLER_FLUSHING;
free (reason);
- self->state = WS_HANDLER_ALMOST_DEAD;
+
+ ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
+ ev_timer_set (&self->close_timeout_watcher, self->close_timeout, 0.);
+ ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher);
return true;
}
@@ -744,21 +764,18 @@ ws_handler_on_control_frame
switch (parser->opcode)
{
case WS_OPCODE_CLOSE:
- return ws_handler_on_protocol_close (self, parser);
+ return ws_handler_on_control_close (self, parser);
case WS_OPCODE_PING:
ws_handler_send_control (self, WS_OPCODE_PONG,
parser->input.str, parser->payload_len);
break;
case WS_OPCODE_PONG:
- // XXX: maybe we should check the payload
+ // TODO: check the payload
self->received_pong = true;
break;
default:
// Unknown control frame
- ws_handler_fail (self, WS_STATUS_PROTOCOL_ERROR);
- // FIXME: we shouldn't close the connection right away;
- // also check other places
- return false;
+ return ws_handler_fail_connection (self, WS_STATUS_PROTOCOL_ERROR);
}
return true;
}
@@ -769,29 +786,19 @@ ws_handler_on_frame (void *user_data, const struct ws_parser *parser)
struct ws_handler *self = user_data;
if (ws_is_control_frame (parser->opcode))
return ws_handler_on_control_frame (self, parser);
-
- // TODO: do this rather in "on_frame_header"
- if (self->message_data.len + parser->payload_len > self->max_payload_len)
- {
- ws_handler_fail (self, WS_STATUS_MESSAGE_TOO_BIG);
- return false;
- }
-
if (!self->expecting_continuation)
self->message_opcode = parser->opcode;
str_append_data (&self->message_data,
parser->input.str, parser->payload_len);
- self->expecting_continuation = !parser->is_fin;
-
- if (!parser->is_fin)
+ if ((self->expecting_continuation = !parser->is_fin))
return true;
if (self->message_opcode == WS_OPCODE_TEXT
&& !utf8_validate (self->message_data.str, self->message_data.len))
{
- ws_handler_fail (self, WS_STATUS_INVALID_PAYLOAD_DATA);
- return false;
+ return ws_handler_fail_connection
+ (self, WS_STATUS_INVALID_PAYLOAD_DATA);
}
bool result = true;
@@ -799,6 +806,8 @@ ws_handler_on_frame (void *user_data, const struct ws_parser *parser)
result = self->on_message (self, self->message_opcode,
self->message_data.str, self->message_data.len);
str_reset (&self->message_data);
+ // TODO: if (!result), either replace this with a state check,
+ // or make sure to change the state
return result;
}
@@ -810,11 +819,10 @@ ws_handler_on_ping_timer (EV_P_ ev_timer *watcher, int revents)
struct ws_handler *self = watcher->data;
if (!self->received_pong)
- {
- // TODO: close/fail the connection?
- }
+ ws_handler_fail_connection (self, 4000);
else
{
+ // TODO: be an annoying server and send a nonce in the data
ws_handler_send_control (self, WS_OPCODE_PING, NULL, 0);
ev_timer_again (EV_A_ watcher);
}
@@ -823,20 +831,38 @@ ws_handler_on_ping_timer (EV_P_ ev_timer *watcher, int revents)
static void
ws_handler_on_close_timeout (EV_P_ ev_timer *watcher, int revents)
{
+ (void) loop;
(void) revents;
struct ws_handler *self = watcher->data;
- // TODO: anything else to do here? Invalidate our state?
- if (self->close_cb)
- self->close_cb (self, false /* half_close */);
+ hard_assert (self->state == WS_HANDLER_OPEN
+ || self->state == WS_HANDLER_CLOSING);
+
+ if (self->state == WS_HANDLER_CLOSING
+ && self->on_close)
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "close timeout");
+
+ self->state = WS_HANDLER_CLOSED;
+ self->close_cb (self, false /* half_close */);
}
static void
ws_handler_on_handshake_timeout (EV_P_ ev_timer *watcher, int revents)
{
+ (void) loop;
(void) revents;
struct ws_handler *self = watcher->data;
- // TODO
+
+ // XXX: this is a no-op, since this currently doesn't even call shutdown
+ // immediately but postpones it until later
+ self->close_cb (self, true /* half_close */);
+ self->state = WS_HANDLER_FLUSHING;
+
+ if (self->on_close)
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "handshake timeout");
+
+ self->state = WS_HANDLER_CLOSED;
+ self->close_cb (self, false /* half_close */);
}
static void
@@ -991,6 +1017,7 @@ ws_handler_on_url (http_parser *parser, const char *at, size_t len)
#define HTTP_400_BAD_REQUEST "400 Bad Request"
#define HTTP_405_METHOD_NOT_ALLOWED "405 Method Not Allowed"
#define HTTP_417_EXPECTATION_FAILED "407 Expectation Failed"
+#define HTTP_426_UPGRADE_REQUIRED "426 Upgrade Required"
#define HTTP_505_VERSION_NOT_SUPPORTED "505 HTTP Version Not Supported"
static void
@@ -1024,43 +1051,47 @@ ws_handler_http_responsev (struct ws_handler *self,
str_free (&response);
}
-static void
-ws_handler_http_response (struct ws_handler *self, const char *status, ...)
+static bool
+ws_handler_fail_handshake (struct ws_handler *self, const char *status, ...)
{
- struct strv v = strv_make ();
-
va_list ap;
va_start (ap, status);
const char *s;
+ struct strv v = strv_make ();
while ((s = va_arg (ap, const char *)))
strv_append (&v, s);
va_end (ap);
-
ws_handler_http_responsev (self, status, v.vector);
strv_free (&v);
+
+ self->close_cb (self, true /* half_close */);
+ self->state = WS_HANDLER_FLUSHING;
+
+ if (self->on_close)
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, status);
+ return false;
}
-#define FAIL_HANDSHAKE(status, ...) \
- BLOCK_START \
- self->state = WS_HANDLER_ALMOST_DEAD; \
- ws_handler_http_response (self, (status), __VA_ARGS__); \
- return false; \
- BLOCK_END
+#define FAIL_HANDSHAKE(...) \
+ return ws_handler_fail_handshake (self, __VA_ARGS__, NULL)
static bool
ws_handler_finish_handshake (struct ws_handler *self)
{
- // XXX: we probably shouldn't use 505 to reject the minor version but w/e
- if (self->hp.http_major != 1 || self->hp.http_minor < 1)
- FAIL_HANDSHAKE (HTTP_505_VERSION_NOT_SUPPORTED, NULL);
if (self->hp.method != HTTP_GET)
- FAIL_HANDSHAKE (HTTP_405_METHOD_NOT_ALLOWED, "Allow: GET", NULL);
+ FAIL_HANDSHAKE (HTTP_405_METHOD_NOT_ALLOWED, "Allow: GET");
+
+ // Technically, it must be /at least/ 1.1 but no other 1.x version of HTTP
+ // is going to happen and 2.x is entirely incompatible
+ // XXX: we probably shouldn't use 505 to reject the minor version but w/e
+ if (self->hp.http_major != 1 || self->hp.http_minor != 1)
+ FAIL_HANDSHAKE (HTTP_505_VERSION_NOT_SUPPORTED);
// Your expectations are way too high
if (str_map_find (&self->headers, "Expect"))
- FAIL_HANDSHAKE (HTTP_417_EXPECTATION_FAILED, NULL);
+ FAIL_HANDSHAKE (HTTP_417_EXPECTATION_FAILED);
// Reject URLs specifying the schema and host; we're not parsing that
// TODO: actually do parse this and let our user decide if it matches
@@ -1068,11 +1099,11 @@ ws_handler_finish_handshake (struct ws_handler *self)
if (http_parser_parse_url (self->url.str, self->url.len, false, &url)
|| (url.field_set & (1 << UF_SCHEMA | 1 << UF_HOST | 1 << UF_PORT))
|| !str_map_find (&self->headers, "Host"))
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL);
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
const char *connection = str_map_find (&self->headers, "Connection");
if (!connection || strcasecmp_ascii (connection, "Upgrade"))
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL);
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
// Check if we can actually upgrade the protocol to WebSockets
const char *upgrade = str_map_find (&self->headers, "Upgrade");
@@ -1088,7 +1119,8 @@ ws_handler_finish_handshake (struct ws_handler *self)
http_protocol_destroy (iter);
}
if (!can_upgrade)
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL);
+ FAIL_HANDSHAKE (HTTP_426_UPGRADE_REQUIRED,
+ "Upgrade: websocket", SEC_WS_VERSION ": 13");
// Okay, we're finally past the basic HTTP/1.1 stuff
const char *key = str_map_find (&self->headers, SEC_WS_KEY);
@@ -1098,19 +1130,17 @@ ws_handler_finish_handshake (struct ws_handler *self)
const char *extensions = str_map_find (&self->headers, SEC_WS_EXTENSIONS);
*/
- if (!key)
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL);
+ if (!version)
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
+ if (strcmp (version, "13"))
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, SEC_WS_VERSION ": 13");
struct str tmp = str_make ();
- bool key_is_valid = base64_decode (key, false, &tmp) && tmp.len == 16;
+ bool key_is_valid = key
+ && base64_decode (key, false, &tmp) && tmp.len == 16;
str_free (&tmp);
if (!key_is_valid)
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL);
-
- if (!version)
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL);
- if (strcmp (version, "13"))
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, SEC_WS_VERSION ": 13", NULL);
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
struct strv fields = strv_make ();
strv_append_args (&fields,
@@ -1130,6 +1160,7 @@ ws_handler_finish_handshake (struct ws_handler *self)
strv_free (&fields);
+ self->state = WS_HANDLER_OPEN;
ev_timer_init (&self->ping_timer, ws_handler_on_ping_timer,
self->ping_interval, 0);
ev_timer_start (EV_DEFAULT_ &self->ping_timer);
@@ -1141,40 +1172,62 @@ ws_handler_finish_handshake (struct ws_handler *self)
static void
ws_handler_start (struct ws_handler *self)
{
+ hard_assert (self->state == WS_HANDLER_CONNECTING);
+
ev_timer_set (&self->handshake_timeout_watcher,
self->handshake_timeout, 0.);
ev_timer_start (EV_DEFAULT_ &self->handshake_timeout_watcher);
}
-/// Push data to the WebSocket handler. "len == 0" means EOF.
+// The client should normally never close the connection, assume that it's
+// either received an EOF from our side, or that it doesn't care about our data
+// anymore, having called close() already
static bool
-ws_handler_push (struct ws_handler *self, const void *data, size_t len)
+ws_handler_push_eof (struct ws_handler *self)
{
- // TODO: make sure all timers are stopped appropriately
-
- if (!len)
+ switch (self->state)
{
+ case WS_HANDLER_CONNECTING:
ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
- if (self->state == WS_HANDLER_OPEN)
- {
- if (self->on_close)
- self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "");
- }
- else
- {
- // TODO: anything to do besides just closing the connection?
- }
-
+ self->state = WS_HANDLER_FLUSHING;
+ if (self->on_close)
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "unexpected EOF");
+ break;
+ case WS_HANDLER_OPEN:
+ ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
+ // Fall-through
+ case WS_HANDLER_CLOSING:
self->state = WS_HANDLER_CLOSED;
- return false;
+ if (self->on_close)
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "");
+ // Fall-through
+ case WS_HANDLER_FLUSHING:
+ ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher);
+ break;
+ default:
+ soft_assert(self->state != WS_HANDLER_CLOSED);
}
+ self->state = WS_HANDLER_CLOSED;
+ return false;
+}
+
+/// Push data to the WebSocket handler. "len == 0" means EOF.
+/// You are expected to close the connection and dispose of the handler
+/// when the function returns false.
+static bool
+ws_handler_push (struct ws_handler *self, const void *data, size_t len)
+{
+ if (!len)
+ return ws_handler_push_eof (self);
- if (self->state == WS_HANDLER_ALMOST_DEAD)
+ if (self->state == WS_HANDLER_FLUSHING)
// We're waiting for an EOF from the client, must not process data
return true;
+
if (self->state != WS_HANDLER_CONNECTING)
- return ws_parser_push (&self->parser, data, len);
+ return soft_assert (self->state != WS_HANDLER_CLOSED)
+ && ws_parser_push (&self->parser, data, len);
// The handshake hasn't been done yet, process HTTP headers
static const http_parser_settings http_settings =
@@ -1185,8 +1238,8 @@ ws_handler_push (struct ws_handler *self, const void *data, size_t len)
.on_url = ws_handler_on_url,
};
- size_t n_parsed = http_parser_execute (&self->hp,
- &http_settings, data, len);
+ size_t n_parsed =
+ http_parser_execute (&self->hp, &http_settings, data, len);
if (self->hp.upgrade)
{
@@ -1195,12 +1248,10 @@ ws_handler_push (struct ws_handler *self, const void *data, size_t len)
// The handshake hasn't been finished, yet there is more data
// to be processed after the headers already
if (len - n_parsed)
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL);
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
if (!ws_handler_finish_handshake (self))
return false;
-
- self->state = WS_HANDLER_OPEN;
if (self->on_connected)
return self->on_connected (self);
return true;
@@ -1217,7 +1268,7 @@ ws_handler_push (struct ws_handler *self, const void *data, size_t len)
print_debug ("WS handshake failed: %s",
http_errno_description (err));
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, NULL);
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
}
return true;
}
@@ -2319,15 +2370,15 @@ client_ws_on_message (struct ws_handler *handler,
FIND_CONTAINER (self, handler, struct client_ws, handler);
if (type != WS_OPCODE_TEXT)
{
- ws_handler_fail (&self->handler, WS_STATUS_UNSUPPORTED_DATA);
- return false;
+ return ws_handler_fail_connection
+ (&self->handler, WS_STATUS_UNSUPPORTED_DATA);
}
struct server_context *ctx = ev_userdata (EV_DEFAULT);
struct str response = str_make ();
process_json_rpc (ctx, data, len, &response);
if (response.len)
- ws_handler_send (&self->handler,
+ ws_handler_send_frame (&self->handler,
WS_OPCODE_TEXT, response.str, response.len);
str_free (&response);
return true;
@@ -2353,6 +2404,7 @@ static bool
client_ws_push (struct client *client, const void *data, size_t len)
{
FIND_CONTAINER (self, client, struct client_ws, client);
+ // client_close() will correctly destroy the client on EOF
return ws_handler_push (&self->handler, data, len);
}
@@ -2361,7 +2413,8 @@ client_ws_shutdown (struct client *client)
{
FIND_CONTAINER (self, client, struct client_ws, client);
if (self->handler.state == WS_HANDLER_CONNECTING)
- ; // TODO: abort the connection immediately
+ // No on_close, no problem
+ client_destroy (&self->client);
else if (self->handler.state == WS_HANDLER_OPEN)
ws_handler_close (&self->handler, WS_STATUS_GOING_AWAY, NULL, 0);
}
--
cgit v1.2.3-70-g09d2
From d883f4cc71a1f9db5cc4cec2bc60df6cba1f8833 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Thu, 18 Oct 2018 04:13:59 +0200
Subject: Finish the FastCGI backend
Bump liberty, also fixing SCGI.
---
demo-json-rpc-server.c | 154 ++++++++++++++++++++++++-------------------------
liberty | 2 +-
2 files changed, 75 insertions(+), 81 deletions(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 60a7173..50e203f 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -194,6 +194,8 @@ fcgi_request_new (void)
self->hdr_parser = fcgi_nv_parser_make ();
self->hdr_parser.output = &self->headers;
+
+ self->output_buffer = str_make ();
return self;
}
@@ -208,40 +210,6 @@ fcgi_request_destroy (struct fcgi_request *self)
free (self);
}
-static void
-fcgi_request_push_params
- (struct fcgi_request *self, const void *data, size_t len)
-{
- if (self->state != FCGI_REQUEST_PARAMS)
- {
- // TODO: probably reject the request
- return;
- }
-
- if (len)
- fcgi_nv_parser_push (&self->hdr_parser, data, len);
- else
- {
- // TODO: probably check the state of the header parser
- // TODO: request_start() can return false, end the request here?
- (void) self->muxer->request_start_cb (self);
- self->state = FCGI_REQUEST_STDIN;
- }
-}
-
-static void
-fcgi_request_push_stdin
- (struct fcgi_request *self, const void *data, size_t len)
-{
- if (self->state != FCGI_REQUEST_STDIN)
- {
- // TODO: probably reject the request
- return;
- }
-
- self->muxer->request_push_cb (self, data, len);
-}
-
static void
fcgi_request_flush (struct fcgi_request *self)
{
@@ -294,36 +262,62 @@ fcgi_request_finish (struct fcgi_request *self)
self->muxer->requests[self->request_id] = NULL;
fcgi_request_destroy (self);
- // TODO: tear down (shut down) the connection. This is called from:
- //
- // 1. client_fcgi_request_push <- request_push_cb
- // <- fcgi_request_push_stdin <- fcgi_muxer_on_stdin
- // <- fcgi_muxer_on_message <- fcgi_parser_push <- fcgi_muxer_push
- // <- client_fcgi_push <- client_read_loop
- // => in this case no close_cb may be called
- // -> need to pass a false boolean aaall the way up,
- // then client_fcgi_finalize eventually cleans up the rest
- //
- // 2. client_fcgi_request_close_cb <- request_finish
- // => our direct caller must call fcgi_muxer::close_cb
- // -> not very nice to delegate it there
return !should_close;
}
+static bool
+fcgi_request_push_params
+ (struct fcgi_request *self, const void *data, size_t len)
+{
+ if (self->state != FCGI_REQUEST_PARAMS)
+ {
+ print_debug ("FastCGI: expected %s, got %s",
+ STRINGIFY (FCGI_STDIN), STRINGIFY (FCGI_PARAMS));
+ return false;
+ }
+
+ if (len)
+ fcgi_nv_parser_push (&self->hdr_parser, data, len);
+ else
+ {
+ if (self->hdr_parser.state != FCGI_NV_PARSER_NAME_LEN)
+ print_debug ("FastCGI: request headers seem to be cut off");
+
+ self->state = FCGI_REQUEST_STDIN;
+ if (!self->muxer->request_start_cb (self))
+ return fcgi_request_finish (self);
+ }
+ return true;
+}
+
+static bool
+fcgi_request_push_stdin
+ (struct fcgi_request *self, const void *data, size_t len)
+{
+ if (self->state != FCGI_REQUEST_STDIN)
+ {
+ print_debug ("FastCGI: expected %s, got %s",
+ STRINGIFY (FCGI_PARAMS), STRINGIFY (FCGI_STDIN));
+ return false;
+ }
+
+ return self->muxer->request_push_cb (self, data, len);
+}
+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-typedef void (*fcgi_muxer_handler_fn)
+typedef bool (*fcgi_muxer_handler_fn)
(struct fcgi_muxer *, const struct fcgi_parser *);
-static void
+static bool
fcgi_muxer_on_get_values
(struct fcgi_muxer *self, const struct fcgi_parser *parser)
{
if (parser->request_id != FCGI_NULL_REQUEST_ID)
{
- print_debug ("FastCGI: ignoring invalid %s message",
+ print_debug ("FastCGI: invalid %s message",
STRINGIFY (FCGI_GET_VALUES));
- return;
+ return false;
}
struct str_map values = str_map_make (free);
@@ -357,9 +351,10 @@ fcgi_muxer_on_get_values
str_map_free (&values);
str_map_free (&response);
+ return true;
}
-static void
+static bool
fcgi_muxer_on_begin_request
(struct fcgi_muxer *self, const struct fcgi_parser *parser)
{
@@ -375,16 +370,17 @@ fcgi_muxer_on_begin_request
if (!success)
{
- print_debug ("FastCGI: ignoring invalid %s message",
+ print_debug ("FastCGI: invalid %s message",
STRINGIFY (FCGI_BEGIN_REQUEST));
- return;
+ return false;
}
struct fcgi_request *request = self->requests[parser->request_id];
if (parser->request_id == FCGI_NULL_REQUEST_ID || request)
{
- // TODO: fail
- return;
+ print_debug ("FastCGI: unusable request ID in %s message",
+ STRINGIFY (FCGI_BEGIN_REQUEST));
+ return false;
}
// We can only act as a responder, reject everything else up front
@@ -392,7 +388,7 @@ fcgi_muxer_on_begin_request
{
fcgi_muxer_send_end_request (self,
parser->request_id, 0, FCGI_UNKNOWN_ROLE);
- return;
+ return true;
}
if (parser->request_id >= N_ELEMENTS (self->requests)
@@ -400,7 +396,7 @@ fcgi_muxer_on_begin_request
{
fcgi_muxer_send_end_request (self,
parser->request_id, 0, FCGI_OVERLOADED);
- return;
+ return true;
}
request = fcgi_request_new ();
@@ -410,9 +406,10 @@ fcgi_muxer_on_begin_request
self->requests[parser->request_id] = request;
self->active_requests++;
+ return true;
}
-static void
+static bool
fcgi_muxer_on_abort_request
(struct fcgi_muxer *self, const struct fcgi_parser *parser)
{
@@ -421,15 +418,13 @@ fcgi_muxer_on_abort_request
{
print_debug ("FastCGI: received %s for an unknown request",
STRINGIFY (FCGI_ABORT_REQUEST));
- return;
+ return true; // We might have just rejected it
}
- // TODO: abort the request: let it somehow produce FCGI_END_REQUEST,
- // make sure to send an stdout EOF record
- // TODO: and if that was not a FCGI_KEEP_CONN request, close the transport
+ return fcgi_request_finish (request);
}
-static void
+static bool
fcgi_muxer_on_params (struct fcgi_muxer *self, const struct fcgi_parser *parser)
{
struct fcgi_request *request = self->requests[parser->request_id];
@@ -437,14 +432,15 @@ fcgi_muxer_on_params (struct fcgi_muxer *self, const struct fcgi_parser *parser)
{
print_debug ("FastCGI: received %s for an unknown request",
STRINGIFY (FCGI_PARAMS));
- return;
+ return true; // We might have just rejected it
}
- fcgi_request_push_params (request,
+ // This may immediately finish and delete the request, but that's fine
+ return fcgi_request_push_params (request,
parser->content.str, parser->content.len);
}
-static void
+static bool
fcgi_muxer_on_stdin (struct fcgi_muxer *self, const struct fcgi_parser *parser)
{
struct fcgi_request *request = self->requests[parser->request_id];
@@ -452,15 +448,15 @@ fcgi_muxer_on_stdin (struct fcgi_muxer *self, const struct fcgi_parser *parser)
{
print_debug ("FastCGI: received %s for an unknown request",
STRINGIFY (FCGI_STDIN));
- return;
+ return true; // We might have just rejected it
}
// At the end of the stream, a zero-length record is received
- fcgi_request_push_stdin (request,
+ return fcgi_request_push_stdin (request,
parser->content.str, parser->content.len);
}
-static void
+static bool
fcgi_muxer_on_message (const struct fcgi_parser *parser, void *user_data)
{
struct fcgi_muxer *self = user_data;
@@ -468,8 +464,7 @@ fcgi_muxer_on_message (const struct fcgi_parser *parser, void *user_data)
if (parser->version != FCGI_VERSION_1)
{
print_debug ("FastCGI: unsupported version %d", parser->version);
- // TODO: also return false to stop processing on protocol error?
- return;
+ return false;
}
static const fcgi_muxer_handler_fn handlers[] =
@@ -489,10 +484,10 @@ fcgi_muxer_on_message (const struct fcgi_parser *parser, void *user_data)
uint8_t content[8] = { parser->type };
fcgi_muxer_send (self, FCGI_UNKNOWN_TYPE, parser->request_id,
content, sizeof content);
- return;
+ return true;
}
- handler (self, parser);
+ return handler (self, parser);
}
static void
@@ -520,10 +515,10 @@ fcgi_muxer_free (struct fcgi_muxer *self)
fcgi_parser_free (&self->parser);
}
-static void
+static bool
fcgi_muxer_push (struct fcgi_muxer *self, const void *data, size_t len)
{
- fcgi_parser_push (&self->parser, data, len);
+ return fcgi_parser_push (&self->parser, data, len);
}
/// @}
@@ -1574,7 +1569,7 @@ struct request
/// Callback to write some CGI response data to the output
void (*write_cb) (struct request *, const void *data, size_t len);
- /// Callback to close the connection.
+ /// Callback to close the CGI response, simulates end of program execution.
/// CALLING THIS MAY CAUSE THE REQUEST TO BE DESTROYED.
void (*close_cb) (struct request *);
};
@@ -1967,7 +1962,7 @@ client_destroy (struct client *self)
static void
client_write (struct client *self, const void *data, size_t len)
{
- if (!soft_assert (!self->flushing))
+ if (!soft_assert (!self->flushing) || len == 0)
return;
struct write_req *req = xcalloc (1, sizeof *req);
@@ -2206,8 +2201,7 @@ static bool
client_fcgi_push (struct client *client, const void *data, size_t len)
{
FIND_CONTAINER (self, client, struct client_fcgi, client);
- fcgi_muxer_push (&self->muxer, data, len);
- return true;
+ return fcgi_muxer_push (&self->muxer, data, len);
}
static void
diff --git a/liberty b/liberty
index 9494e8e..bca7167 160000
--- a/liberty
+++ b/liberty
@@ -1 +1 @@
-Subproject commit 9494e8e2affcd08b791d3ecf68985a8a3a310e55
+Subproject commit bca7167d037d857448cb18243425d7c61de3bdd5
--
cgit v1.2.3-70-g09d2
From 8d664355681302a08b671723699e3b3720a7703a Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Thu, 18 Oct 2018 06:41:12 +0200
Subject: Remember to set the server context in SCGI requests
---
demo-json-rpc-server.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index 50e203f..e210788 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -2339,6 +2339,7 @@ client_scgi_create (EV_P_ int sock_fd)
self->client.vtable = &client_scgi_vtable;
request_init (&self->request);
+ self->request.ctx = ev_userdata (EV_DEFAULT);
self->request.write_cb = client_scgi_write_cb;
self->request.close_cb = client_scgi_close_cb;
--
cgit v1.2.3-70-g09d2
From 6e152ae37c4075ff2a39298d450bf58258ceffe1 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Thu, 18 Oct 2018 07:17:06 +0200
Subject: More debugging information for static file serving
---
demo-json-rpc-server.c | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
index e210788..34cd378 100644
--- a/demo-json-rpc-server.c
+++ b/demo-json-rpc-server.c
@@ -1642,6 +1642,15 @@ request_start (struct request *self, struct str_map *headers)
// Another way we could get rid of the continue_ argument is via adding
// some way of marking the request as finished from within the handler.
+ if (g_debug_mode)
+ {
+ struct str_map_iter iter = str_map_iter_make (headers);
+ const char *value;
+ while ((value = str_map_iter_next (&iter)))
+ print_debug ("%s: %s", iter.link->key, value);
+ print_debug ("--");
+ }
+
bool continue_ = true;
LIST_FOR_EACH (struct request_handler, handler, self->ctx->handlers)
if (handler->try_handle (self, headers, &continue_))
@@ -1800,15 +1809,19 @@ request_handler_static_try_handle
return false;
}
+ // TODO: implement HEAD, we don't get that for free;
+ // probably implies adding Content-Length
const char *method = str_map_find (headers, "REQUEST_METHOD");
if (!method || strcmp (method, "GET"))
return false;
// TODO: look at , REQUEST_URI in the headers
const char *path_info = str_map_find (headers, "PATH_INFO");
+ if (!path_info)
+ path_info = str_map_find (headers, "REQUEST_URI");
if (!path_info)
{
- print_debug ("PATH_INFO not defined");
+ print_debug ("neither PATH_INFO nor REQUEST_URI was defined");
return false;
}
@@ -1816,6 +1829,7 @@ request_handler_static_try_handle
// Being able to read /etc/passwd would be rather embarrasing
char *suffix = canonicalize_url_path (path_info);
char *path = xstrdup_printf ("%s%s", root, suffix);
+ print_debug ("trying to statically serve %s", path);
// TODO: check that this is a regular file
FILE *fp = fopen (path, "rb");
--
cgit v1.2.3-70-g09d2
From 1c52f9e37ed07e93c21f84779d209ee196360006 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Thu, 18 Oct 2018 07:27:55 +0200
Subject: demo-json-rpc-server -> json-rpc-test-server
---
CMakeLists.txt | 6 +-
demo-json-rpc-server.c | 2925 ------------------------------------------------
json-rpc-test-server.c | 2925 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 2928 insertions(+), 2928 deletions(-)
delete mode 100644 demo-json-rpc-server.c
create mode 100644 json-rpc-test-server.c
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c78736a..f6d9b00 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -36,9 +36,9 @@ configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h
include_directories (${PROJECT_BINARY_DIR})
# Build the executables
-add_executable (demo-json-rpc-server
- demo-json-rpc-server.c http-parser/http_parser.c)
-target_link_libraries (demo-json-rpc-server ${project_libraries})
+add_executable (json-rpc-test-server
+ json-rpc-test-server.c http-parser/http_parser.c)
+target_link_libraries (json-rpc-test-server ${project_libraries})
# The files to be installed
include (GNUInstallDirs)
diff --git a/demo-json-rpc-server.c b/demo-json-rpc-server.c
deleted file mode 100644
index 34cd378..0000000
--- a/demo-json-rpc-server.c
+++ /dev/null
@@ -1,2925 +0,0 @@
-/*
- * demo-json-rpc-server.c: JSON-RPC 2.0 demo server
- *
- * Copyright (c) 2015 - 2018, Přemysl Janouch
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- *
- */
-
-#define print_fatal_data ((void *) LOG_ERR)
-#define print_error_data ((void *) LOG_ERR)
-#define print_warning_data ((void *) LOG_WARNING)
-#define print_status_data ((void *) LOG_INFO)
-#define print_debug_data ((void *) LOG_DEBUG)
-
-#define LIBERTY_WANT_SSL
-#define LIBERTY_WANT_PROTO_HTTP
-#define LIBERTY_WANT_PROTO_WS
-#define LIBERTY_WANT_PROTO_SCGI
-#define LIBERTY_WANT_PROTO_FASTCGI
-
-#include "config.h"
-#include "liberty/liberty.c"
-
-#include
-#include
-#include
-#include
-
-#include
-#include
-#include
-
-#include "http-parser/http_parser.h"
-
-enum { PIPE_READ, PIPE_WRITE };
-
-#define FIND_CONTAINER(name, pointer, type, member) \
- type *name = CONTAINER_OF (pointer, type, member)
-
-// --- Utilities ---------------------------------------------------------------
-
-static bool
-flush_queue (struct write_queue *queue, int fd)
-{
- struct iovec vec[queue->len], *vec_iter = vec;
- LIST_FOR_EACH (struct write_req, iter, queue->head)
- *vec_iter++ = iter->data;
-
- ssize_t written;
-again:
- if ((written = writev (fd, vec, N_ELEMENTS (vec))) >= 0)
- {
- write_queue_processed (queue, written);
- return true;
- }
- if (errno == EINTR)
- goto again;
- if (errno == EAGAIN)
- return true;
-
- return false;
-}
-
-// --- Logging -----------------------------------------------------------------
-
-static void
-log_message_syslog (void *user_data, const char *quote, const char *fmt,
- va_list ap)
-{
- int prio = (int) (intptr_t) user_data;
-
- va_list va;
- va_copy (va, ap);
- int size = vsnprintf (NULL, 0, fmt, va);
- va_end (va);
- if (size < 0)
- return;
-
- char buf[size + 1];
- if (vsnprintf (buf, sizeof buf, fmt, ap) >= 0)
- syslog (prio, "%s%s", quote, buf);
-}
-
-// --- FastCGI -----------------------------------------------------------------
-/// @defgroup FastCGI
-/// @{
-
-enum fcgi_request_state
-{
- FCGI_REQUEST_PARAMS, ///< Reading headers
- FCGI_REQUEST_STDIN ///< Reading input
-};
-
-struct fcgi_request
-{
- struct fcgi_muxer *muxer; ///< The parent muxer
- uint16_t request_id; ///< The ID of this request
- uint8_t flags; ///< Request flags
-
- enum fcgi_request_state state; ///< Parsing state
- struct str_map headers; ///< Headers
- struct fcgi_nv_parser hdr_parser; ///< Header parser
-
- struct str output_buffer; ///< Output buffer
-
- void *handler_data; ///< Handler data
-};
-
-/// Handles a single FastCGI connection, de/multiplexing requests and responses
-struct fcgi_muxer
-{
- struct fcgi_parser parser; ///< FastCGI message parser
- uint32_t active_requests; ///< The number of active requests
- bool in_shutdown; ///< Rejecting new requests
-
- // Virtual method callbacks:
-
- /// Write data to the underlying transport
- void (*write_cb) (struct fcgi_muxer *, const void *data, size_t len);
-
- /// Close the underlying transport. You are allowed to destroy the muxer
- /// directly from within the callback.
- void (*close_cb) (struct fcgi_muxer *);
-
- /// Start processing a request. Return false if no further action is
- /// to be done and the request should be finished.
- bool (*request_start_cb) (struct fcgi_request *);
-
- /// Handle incoming data. "len == 0" means EOF. Returns false if
- /// the underlying transport should be closed, this being the last request.
- bool (*request_push_cb)
- (struct fcgi_request *, const void *data, size_t len);
-
- /// Destroy the handler's data stored in the request object
- void (*request_finalize_cb) (struct fcgi_request *);
-
- /// Requests assigned to request IDs (may not be FCGI_NULL_REQUEST_ID)
- struct fcgi_request *requests[1 << 8];
-};
-
-static void
-fcgi_muxer_send (struct fcgi_muxer *self,
- enum fcgi_type type, uint16_t request_id, const void *data, size_t len)
-{
- hard_assert (len <= UINT16_MAX);
-
- struct str message = str_make ();
- static char zeroes[8];
- size_t padding = -len & 7;
-
- str_pack_u8 (&message, FCGI_VERSION_1);
- str_pack_u8 (&message, type);
- str_pack_u16 (&message, request_id);
- str_pack_u16 (&message, len); // content length
- str_pack_u8 (&message, padding); // padding length
- str_pack_u8 (&message, 0); // reserved
-
- str_append_data (&message, data, len);
- str_append_data (&message, zeroes, padding);
-
- // XXX: we should probably have another write_cb that assumes ownership
- self->write_cb (self, message.str, message.len);
- str_free (&message);
-}
-
-static void
-fcgi_muxer_send_end_request (struct fcgi_muxer *self, uint16_t request_id,
- uint32_t app_status, enum fcgi_protocol_status protocol_status)
-{
- uint8_t content[8] = { app_status >> 24, app_status >> 16,
- app_status << 8, app_status, protocol_status };
- fcgi_muxer_send (self, FCGI_END_REQUEST, request_id,
- content, sizeof content);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static struct fcgi_request *
-fcgi_request_new (void)
-{
- struct fcgi_request *self = xcalloc (1, sizeof *self);
-
- self->headers = str_map_make (free);
-
- self->hdr_parser = fcgi_nv_parser_make ();
- self->hdr_parser.output = &self->headers;
-
- self->output_buffer = str_make ();
- return self;
-}
-
-static void
-fcgi_request_destroy (struct fcgi_request *self)
-{
- // TODO: consider the case where it hasn't been started yet
- self->muxer->request_finalize_cb (self);
-
- str_map_free (&self->headers);
- fcgi_nv_parser_free (&self->hdr_parser);
- free (self);
-}
-
-static void
-fcgi_request_flush (struct fcgi_request *self)
-{
- if (!self->output_buffer.len)
- return;
-
- fcgi_muxer_send (self->muxer, FCGI_STDOUT, self->request_id,
- self->output_buffer.str, self->output_buffer.len);
- str_reset (&self->output_buffer);
-}
-
-static void
-fcgi_request_write (struct fcgi_request *self, const void *data, size_t len)
-{
- // We're buffering the output and splitting it into messages
- bool need_flush = true;
- while (len)
- {
- size_t to_write = UINT16_MAX - self->output_buffer.len;
- if (to_write > len)
- {
- to_write = len;
- need_flush = false;
- }
-
- str_append_data (&self->output_buffer, data, to_write);
- data = (uint8_t *) data + to_write;
- len -= to_write;
-
- if (need_flush)
- fcgi_request_flush (self);
- }
-}
-
-/// Mark the request as done. Returns false if the underlying transport
-/// should be closed, this being the last request.
-static bool
-fcgi_request_finish (struct fcgi_request *self)
-{
- fcgi_request_flush (self);
- fcgi_muxer_send (self->muxer, FCGI_STDOUT, self->request_id, NULL, 0);
-
- fcgi_muxer_send_end_request (self->muxer, self->request_id,
- 0 /* TODO app_status, although ignored */,
- FCGI_REQUEST_COMPLETE /* TODO protocol_status, may be different */);
-
- bool should_close = !(self->flags & FCGI_KEEP_CONN);
-
- self->muxer->active_requests--;
- self->muxer->requests[self->request_id] = NULL;
- fcgi_request_destroy (self);
-
- return !should_close;
-}
-
-static bool
-fcgi_request_push_params
- (struct fcgi_request *self, const void *data, size_t len)
-{
- if (self->state != FCGI_REQUEST_PARAMS)
- {
- print_debug ("FastCGI: expected %s, got %s",
- STRINGIFY (FCGI_STDIN), STRINGIFY (FCGI_PARAMS));
- return false;
- }
-
- if (len)
- fcgi_nv_parser_push (&self->hdr_parser, data, len);
- else
- {
- if (self->hdr_parser.state != FCGI_NV_PARSER_NAME_LEN)
- print_debug ("FastCGI: request headers seem to be cut off");
-
- self->state = FCGI_REQUEST_STDIN;
- if (!self->muxer->request_start_cb (self))
- return fcgi_request_finish (self);
- }
- return true;
-}
-
-static bool
-fcgi_request_push_stdin
- (struct fcgi_request *self, const void *data, size_t len)
-{
- if (self->state != FCGI_REQUEST_STDIN)
- {
- print_debug ("FastCGI: expected %s, got %s",
- STRINGIFY (FCGI_PARAMS), STRINGIFY (FCGI_STDIN));
- return false;
- }
-
- return self->muxer->request_push_cb (self, data, len);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-typedef bool (*fcgi_muxer_handler_fn)
- (struct fcgi_muxer *, const struct fcgi_parser *);
-
-static bool
-fcgi_muxer_on_get_values
- (struct fcgi_muxer *self, const struct fcgi_parser *parser)
-{
- if (parser->request_id != FCGI_NULL_REQUEST_ID)
- {
- print_debug ("FastCGI: invalid %s message",
- STRINGIFY (FCGI_GET_VALUES));
- return false;
- }
-
- struct str_map values = str_map_make (free);
- struct str_map response = str_map_make (free);
-
- struct fcgi_nv_parser nv_parser = fcgi_nv_parser_make ();
- nv_parser.output = &values;
-
- fcgi_nv_parser_push (&nv_parser, parser->content.str, parser->content.len);
- const char *key = NULL;
-
- // No real-world servers seem to actually use multiplexing
- // or even issue this request, but we will implement it anyway
- if (str_map_find (&values, (key = FCGI_MPXS_CONNS)))
- str_map_set (&response, key, xstrdup ("1"));
-
- // It's not clear whether FCGI_MAX_REQS means concurrently over all
- // connections or over just a single connection (multiplexed), though
- // supposedly it's actually per /web server/. Supply the strictest limit.
- if (str_map_find (&values, (key = FCGI_MAX_REQS)))
- str_map_set (&response, key,
- xstrdup_printf ("%zu", N_ELEMENTS (self->requests) - 1));
-
- // FCGI_MAX_CONNS would be basically infinity. We don't limit connections.
-
- struct str content = str_make ();
- fcgi_nv_convert (&response, &content);
- fcgi_muxer_send (self, FCGI_GET_VALUES_RESULT, parser->request_id,
- content.str, content.len);
- str_free (&content);
-
- str_map_free (&values);
- str_map_free (&response);
- return true;
-}
-
-static bool
-fcgi_muxer_on_begin_request
- (struct fcgi_muxer *self, const struct fcgi_parser *parser)
-{
- struct msg_unpacker unpacker =
- msg_unpacker_make (parser->content.str, parser->content.len);
-
- uint16_t role;
- uint8_t flags;
- bool success = true;
- success &= msg_unpacker_u16 (&unpacker, &role);
- success &= msg_unpacker_u8 (&unpacker, &flags);
- // Ignoring 5 reserved bytes
-
- if (!success)
- {
- print_debug ("FastCGI: invalid %s message",
- STRINGIFY (FCGI_BEGIN_REQUEST));
- return false;
- }
-
- struct fcgi_request *request = self->requests[parser->request_id];
- if (parser->request_id == FCGI_NULL_REQUEST_ID || request)
- {
- print_debug ("FastCGI: unusable request ID in %s message",
- STRINGIFY (FCGI_BEGIN_REQUEST));
- return false;
- }
-
- // We can only act as a responder, reject everything else up front
- if (role != FCGI_RESPONDER)
- {
- fcgi_muxer_send_end_request (self,
- parser->request_id, 0, FCGI_UNKNOWN_ROLE);
- return true;
- }
-
- if (parser->request_id >= N_ELEMENTS (self->requests)
- || self->in_shutdown)
- {
- fcgi_muxer_send_end_request (self,
- parser->request_id, 0, FCGI_OVERLOADED);
- return true;
- }
-
- request = fcgi_request_new ();
- request->muxer = self;
- request->request_id = parser->request_id;
- request->flags = flags;
-
- self->requests[parser->request_id] = request;
- self->active_requests++;
- return true;
-}
-
-static bool
-fcgi_muxer_on_abort_request
- (struct fcgi_muxer *self, const struct fcgi_parser *parser)
-{
- struct fcgi_request *request = self->requests[parser->request_id];
- if (parser->request_id == FCGI_NULL_REQUEST_ID || !request)
- {
- print_debug ("FastCGI: received %s for an unknown request",
- STRINGIFY (FCGI_ABORT_REQUEST));
- return true; // We might have just rejected it
- }
-
- return fcgi_request_finish (request);
-}
-
-static bool
-fcgi_muxer_on_params (struct fcgi_muxer *self, const struct fcgi_parser *parser)
-{
- struct fcgi_request *request = self->requests[parser->request_id];
- if (parser->request_id == FCGI_NULL_REQUEST_ID || !request)
- {
- print_debug ("FastCGI: received %s for an unknown request",
- STRINGIFY (FCGI_PARAMS));
- return true; // We might have just rejected it
- }
-
- // This may immediately finish and delete the request, but that's fine
- return fcgi_request_push_params (request,
- parser->content.str, parser->content.len);
-}
-
-static bool
-fcgi_muxer_on_stdin (struct fcgi_muxer *self, const struct fcgi_parser *parser)
-{
- struct fcgi_request *request = self->requests[parser->request_id];
- if (parser->request_id == FCGI_NULL_REQUEST_ID || !request)
- {
- print_debug ("FastCGI: received %s for an unknown request",
- STRINGIFY (FCGI_STDIN));
- return true; // We might have just rejected it
- }
-
- // At the end of the stream, a zero-length record is received
- return fcgi_request_push_stdin (request,
- parser->content.str, parser->content.len);
-}
-
-static bool
-fcgi_muxer_on_message (const struct fcgi_parser *parser, void *user_data)
-{
- struct fcgi_muxer *self = user_data;
-
- if (parser->version != FCGI_VERSION_1)
- {
- print_debug ("FastCGI: unsupported version %d", parser->version);
- return false;
- }
-
- static const fcgi_muxer_handler_fn handlers[] =
- {
- [FCGI_GET_VALUES] = fcgi_muxer_on_get_values,
- [FCGI_BEGIN_REQUEST] = fcgi_muxer_on_begin_request,
- [FCGI_ABORT_REQUEST] = fcgi_muxer_on_abort_request,
- [FCGI_PARAMS] = fcgi_muxer_on_params,
- [FCGI_STDIN] = fcgi_muxer_on_stdin,
- };
-
- fcgi_muxer_handler_fn handler;
- if (parser->type >= N_ELEMENTS (handlers)
- || !(handler = handlers[parser->type]))
- {
- // Responding in this way even to application records, unspecified
- uint8_t content[8] = { parser->type };
- fcgi_muxer_send (self, FCGI_UNKNOWN_TYPE, parser->request_id,
- content, sizeof content);
- return true;
- }
-
- return handler (self, parser);
-}
-
-static void
-fcgi_muxer_init (struct fcgi_muxer *self)
-{
- self->parser = fcgi_parser_make ();
- self->parser.on_message = fcgi_muxer_on_message;
- self->parser.user_data = self;
-}
-
-static void
-fcgi_muxer_free (struct fcgi_muxer *self)
-{
- for (size_t i = 0; i < N_ELEMENTS (self->requests); i++)
- {
- if (!self->active_requests)
- break;
- if (self->requests[i])
- {
- fcgi_request_destroy (self->requests[i]);
- self->active_requests--;
- }
- }
-
- fcgi_parser_free (&self->parser);
-}
-
-static bool
-fcgi_muxer_push (struct fcgi_muxer *self, const void *data, size_t len)
-{
- return fcgi_parser_push (&self->parser, data, len);
-}
-
-/// @}
-// --- WebSockets --------------------------------------------------------------
-/// @defgroup WebSockets
-/// @{
-
-// WebSockets aren't CGI-compatible, therefore we must handle the initial HTTP
-// handshake ourselves. Luckily it's not too much of a bother with http-parser.
-// Typically there will be a normal HTTP server in front of us, proxying the
-// requests based on the URI.
-
-enum ws_handler_state
-{
- WS_HANDLER_CONNECTING, ///< Parsing HTTP
- WS_HANDLER_OPEN, ///< Parsing WebSockets frames
- WS_HANDLER_CLOSING, ///< Partial closure by us
- WS_HANDLER_FLUSHING, ///< Just waiting for client EOF
- WS_HANDLER_CLOSED ///< Dead, both sides closed
-};
-
-struct ws_handler
-{
- enum ws_handler_state state; ///< State
-
- // HTTP handshake:
-
- http_parser hp; ///< HTTP parser
- bool have_header_value; ///< Parsing header value or field?
- struct str field; ///< Field part buffer
- struct str value; ///< Value part buffer
- struct str_map headers; ///< HTTP Headers
- struct str url; ///< Request URL
- ev_timer handshake_timeout_watcher; ///< Handshake timeout watcher
-
- // WebSocket frame protocol:
-
- struct ws_parser parser; ///< Protocol frame parser
- bool expecting_continuation; ///< For non-control traffic
-
- enum ws_opcode message_opcode; ///< Opcode for the current message
- struct str message_data; ///< Concatenated message data
-
- ev_timer ping_timer; ///< Ping timer
- bool received_pong; ///< Received PONG since the last PING
-
- ev_timer close_timeout_watcher; ///< Close timeout watcher
-
- // Configuration:
-
- unsigned handshake_timeout; ///< How long to wait for the handshake
- unsigned close_timeout; ///< How long to wait for TCP close
- unsigned ping_interval; ///< Ping interval in seconds
- uint64_t max_payload_len; ///< Maximum length of any message
-
- // Event callbacks:
-
- // TODO: void (*on_handshake) (protocols) that will allow the user
- // to choose any sub-protocol, if the client has provided any.
- // This may render "on_connected" unnecessary.
- // Should also enable failing the handshake.
-
- /// Called after successfuly connecting (handshake complete)
- bool (*on_connected) (struct ws_handler *);
-
- /// Called upon reception of a single full message
- bool (*on_message) (struct ws_handler *,
- enum ws_opcode type, const void *data, size_t len);
-
- /// The connection is about to close. @a close_code may, or may not, be one
- /// of enum ws_status. The @a reason is never NULL.
- void (*on_close) (struct ws_handler *, int close_code, const char *reason);
-
- // Virtual method callbacks:
-
- /// Write a chunk of data to the stream
- void (*write_cb) (struct ws_handler *, const void *data, size_t len);
-
- /// Close the connection. If @a half_close is false, you are allowed to
- /// destroy the handler directly from within the callback.
- void (*close_cb) (struct ws_handler *, bool half_close);
-};
-
-static void
-ws_handler_send_control (struct ws_handler *self,
- enum ws_opcode opcode, const void *data, size_t len)
-{
- if (len > WS_MAX_CONTROL_PAYLOAD_LEN)
- {
- print_debug ("truncating output control frame payload"
- " from %zu to %zu bytes", len, (size_t) WS_MAX_CONTROL_PAYLOAD_LEN);
- len = WS_MAX_CONTROL_PAYLOAD_LEN;
- }
-
- uint8_t header[2] = { 0x80 | (opcode & 0x0F), len };
- self->write_cb (self, header, sizeof header);
- self->write_cb (self, data, len);
-}
-
-static void
-ws_handler_close (struct ws_handler *self,
- enum ws_status close_code, const char *reason, size_t len)
-{
- hard_assert (self->state == WS_HANDLER_OPEN);
-
- struct str payload = str_make ();
- str_pack_u16 (&payload, close_code);
- // XXX: maybe accept a null-terminated string on input? Has to be UTF-8 a/w
- str_append_data (&payload, reason, len);
- ws_handler_send_control (self, WS_OPCODE_CLOSE, payload.str, payload.len);
- self->close_cb (self, true /* half_close */);
-
- self->state = WS_HANDLER_CLOSING;
- str_free (&payload);
-}
-
-static bool
-ws_handler_fail_connection (struct ws_handler *self, enum ws_status close_code)
-{
- hard_assert (self->state == WS_HANDLER_OPEN
- || self->state == WS_HANDLER_CLOSING);
-
- if (self->state == WS_HANDLER_OPEN)
- ws_handler_close (self, close_code, NULL, 0);
-
- self->state = WS_HANDLER_FLUSHING;
- if (self->on_close)
- self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "");
-
- ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
- ev_timer_set (&self->close_timeout_watcher, self->close_timeout, 0.);
- ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher);
- return false;
-}
-
-// TODO: add support for fragmented responses
-static void
-ws_handler_send_frame (struct ws_handler *self,
- enum ws_opcode opcode, const void *data, size_t len)
-{
- if (!soft_assert (self->state == WS_HANDLER_OPEN))
- return;
-
- struct str header = str_make ();
- str_pack_u8 (&header, 0x80 | (opcode & 0x0F));
-
- if (len > UINT16_MAX)
- {
- str_pack_u8 (&header, 127);
- str_pack_u64 (&header, len);
- }
- else if (len > 125)
- {
- str_pack_u8 (&header, 126);
- str_pack_u16 (&header, len);
- }
- else
- str_pack_u8 (&header, len);
-
- self->write_cb (self, header.str, header.len);
- self->write_cb (self, data, len);
- str_free (&header);
-}
-
-static bool
-ws_handler_on_frame_header (void *user_data, const struct ws_parser *parser)
-{
- struct ws_handler *self = user_data;
-
- // Note that we aren't expected to send any close frame before closing the
- // connection when the frame is unmasked
-
- if (parser->reserved_1 || parser->reserved_2 || parser->reserved_3
- || !parser->is_masked // client -> server payload must be masked
- || (ws_is_control_frame (parser->opcode) &&
- (!parser->is_fin || parser->payload_len > WS_MAX_CONTROL_PAYLOAD_LEN))
- || (!ws_is_control_frame (parser->opcode) &&
- (self->expecting_continuation && parser->opcode != WS_OPCODE_CONT))
- || parser->payload_len >= 0x8000000000000000ULL)
- return ws_handler_fail_connection (self, WS_STATUS_PROTOCOL_ERROR);
-
- if (parser->payload_len > self->max_payload_len
- || (self->expecting_continuation &&
- self->message_data.len + parser->payload_len > self->max_payload_len))
- return ws_handler_fail_connection (self, WS_STATUS_MESSAGE_TOO_BIG);
- return true;
-}
-
-static bool
-ws_handler_on_control_close
- (struct ws_handler *self, const struct ws_parser *parser)
-{
- hard_assert (self->state == WS_HANDLER_OPEN
- || self->state == WS_HANDLER_CLOSING);
- struct msg_unpacker unpacker =
- msg_unpacker_make (parser->input.str, parser->payload_len);
-
- char *reason = NULL;
- uint16_t close_code = WS_STATUS_NO_STATUS_RECEIVED;
- if (parser->payload_len >= 2)
- {
- (void) msg_unpacker_u16 (&unpacker, &close_code);
- reason = xstrndup (parser->input.str + 2, parser->payload_len - 2);
- }
- else
- reason = xstrdup ("");
-
- if (close_code < 1000 || close_code > 4999)
- // XXX: invalid close code: maybe we should fail the connection instead
- close_code = WS_STATUS_PROTOCOL_ERROR;
-
- if (self->state == WS_HANDLER_OPEN)
- {
- // Close initiated by the client
- // FIXME: not sending the potentially different close_code
- ws_handler_send_control (self, WS_OPCODE_CLOSE,
- parser->input.str, parser->payload_len);
-
- self->state = WS_HANDLER_FLUSHING;
- if (self->on_close)
- self->on_close (self, close_code, reason);
- }
- else
- self->state = WS_HANDLER_FLUSHING;
-
- free (reason);
-
- ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
- ev_timer_set (&self->close_timeout_watcher, self->close_timeout, 0.);
- ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher);
- return true;
-}
-
-static bool
-ws_handler_on_control_frame
- (struct ws_handler *self, const struct ws_parser *parser)
-{
- switch (parser->opcode)
- {
- case WS_OPCODE_CLOSE:
- return ws_handler_on_control_close (self, parser);
- case WS_OPCODE_PING:
- ws_handler_send_control (self, WS_OPCODE_PONG,
- parser->input.str, parser->payload_len);
- break;
- case WS_OPCODE_PONG:
- // TODO: check the payload
- self->received_pong = true;
- break;
- default:
- // Unknown control frame
- return ws_handler_fail_connection (self, WS_STATUS_PROTOCOL_ERROR);
- }
- return true;
-}
-
-static bool
-ws_handler_on_frame (void *user_data, const struct ws_parser *parser)
-{
- struct ws_handler *self = user_data;
- if (ws_is_control_frame (parser->opcode))
- return ws_handler_on_control_frame (self, parser);
- if (!self->expecting_continuation)
- self->message_opcode = parser->opcode;
-
- str_append_data (&self->message_data,
- parser->input.str, parser->payload_len);
- if ((self->expecting_continuation = !parser->is_fin))
- return true;
-
- if (self->message_opcode == WS_OPCODE_TEXT
- && !utf8_validate (self->message_data.str, self->message_data.len))
- {
- return ws_handler_fail_connection
- (self, WS_STATUS_INVALID_PAYLOAD_DATA);
- }
-
- bool result = true;
- if (self->on_message)
- result = self->on_message (self, self->message_opcode,
- self->message_data.str, self->message_data.len);
- str_reset (&self->message_data);
- // TODO: if (!result), either replace this with a state check,
- // or make sure to change the state
- return result;
-}
-
-static void
-ws_handler_on_ping_timer (EV_P_ ev_timer *watcher, int revents)
-{
- (void) loop;
- (void) revents;
-
- struct ws_handler *self = watcher->data;
- if (!self->received_pong)
- ws_handler_fail_connection (self, 4000);
- else
- {
- // TODO: be an annoying server and send a nonce in the data
- ws_handler_send_control (self, WS_OPCODE_PING, NULL, 0);
- ev_timer_again (EV_A_ watcher);
- }
-}
-
-static void
-ws_handler_on_close_timeout (EV_P_ ev_timer *watcher, int revents)
-{
- (void) loop;
- (void) revents;
- struct ws_handler *self = watcher->data;
-
- hard_assert (self->state == WS_HANDLER_OPEN
- || self->state == WS_HANDLER_CLOSING);
-
- if (self->state == WS_HANDLER_CLOSING
- && self->on_close)
- self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "close timeout");
-
- self->state = WS_HANDLER_CLOSED;
- self->close_cb (self, false /* half_close */);
-}
-
-static void
-ws_handler_on_handshake_timeout (EV_P_ ev_timer *watcher, int revents)
-{
- (void) loop;
- (void) revents;
- struct ws_handler *self = watcher->data;
-
- // XXX: this is a no-op, since this currently doesn't even call shutdown
- // immediately but postpones it until later
- self->close_cb (self, true /* half_close */);
- self->state = WS_HANDLER_FLUSHING;
-
- if (self->on_close)
- self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "handshake timeout");
-
- self->state = WS_HANDLER_CLOSED;
- self->close_cb (self, false /* half_close */);
-}
-
-static void
-ws_handler_init (struct ws_handler *self)
-{
- memset (self, 0, sizeof *self);
-
- self->state = WS_HANDLER_CONNECTING;
-
- http_parser_init (&self->hp, HTTP_REQUEST);
- self->hp.data = self;
- self->field = str_make ();
- self->value = str_make ();
- self->headers = str_map_make (free);
- self->headers.key_xfrm = tolower_ascii_strxfrm;
- self->url = str_make ();
- ev_timer_init (&self->handshake_timeout_watcher,
- ws_handler_on_handshake_timeout, 0., 0.);
- self->handshake_timeout_watcher.data = self;
-
- self->parser = ws_parser_make ();
- self->parser.on_frame_header = ws_handler_on_frame_header;
- self->parser.on_frame = ws_handler_on_frame;
- self->parser.user_data = self;
- self->message_data = str_make ();
-
- ev_timer_init (&self->ping_timer,
- ws_handler_on_ping_timer, 0., 0.);
- self->ping_timer.data = self;
- ev_timer_init (&self->close_timeout_watcher,
- ws_handler_on_close_timeout, 0., 0.);
- self->ping_timer.data = self;
- // So that the first ping timer doesn't timeout the connection
- self->received_pong = true;
-
- self->handshake_timeout = self->close_timeout = self->ping_interval = 60;
- // This is still ridiculously high. Note that the most significant bit
- // must always be zero, i.e. the protocol maximum is 0x7FFF FFFF FFFF FFFF.
- self->max_payload_len = UINT32_MAX;
-}
-
-/// Stop all timers, not going to use the handler anymore
-static void
-ws_handler_stop (struct ws_handler *self)
-{
- ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
- ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
- ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher);
-}
-
-static void
-ws_handler_free (struct ws_handler *self)
-{
- ws_handler_stop (self);
-
- str_free (&self->field);
- str_free (&self->value);
- str_map_free (&self->headers);
- str_free (&self->url);
-
- ws_parser_free (&self->parser);
- str_free (&self->message_data);
-}
-
-static bool
-ws_handler_header_field_is_a_list (const char *name)
-{
- // This must contain all header fields we use for anything
- static const char *concatenable[] =
- { SEC_WS_PROTOCOL, SEC_WS_EXTENSIONS, "Connection", "Upgrade" };
-
- for (size_t i = 0; i < N_ELEMENTS (concatenable); i++)
- if (!strcasecmp_ascii (name, concatenable[i]))
- return true;
- return false;
-}
-
-static void
-ws_handler_on_header_read (struct ws_handler *self)
-{
- // The HTTP parser unfolds values and removes preceding whitespace, but
- // otherwise doesn't touch the values or the following whitespace.
-
- // RFC 7230 states that trailing whitespace is not part of a field value
- char *value = self->field.str;
- size_t len = self->field.len;
- while (len--)
- if (value[len] == '\t' || value[len] == ' ')
- value[len] = '\0';
- else
- break;
- self->field.len = len;
-
- const char *field = self->field.str;
- const char *current = str_map_find (&self->headers, field);
- if (ws_handler_header_field_is_a_list (field) && current)
- str_map_set (&self->headers, field,
- xstrdup_printf ("%s, %s", current, self->value.str));
- else
- // If the field cannot be concatenated, just overwrite the last value.
- // Maybe we should issue a warning or something.
- str_map_set (&self->headers, field, xstrdup (self->value.str));
-}
-
-static int
-ws_handler_on_header_field (http_parser *parser, const char *at, size_t len)
-{
- struct ws_handler *self = parser->data;
- if (self->have_header_value)
- {
- ws_handler_on_header_read (self);
- str_reset (&self->field);
- str_reset (&self->value);
- }
- str_append_data (&self->field, at, len);
- self->have_header_value = false;
- return 0;
-}
-
-static int
-ws_handler_on_header_value (http_parser *parser, const char *at, size_t len)
-{
- struct ws_handler *self = parser->data;
- str_append_data (&self->value, at, len);
- self->have_header_value = true;
- return 0;
-}
-
-static int
-ws_handler_on_headers_complete (http_parser *parser)
-{
- struct ws_handler *self = parser->data;
- if (self->have_header_value)
- ws_handler_on_header_read (self);
-
- // We strictly require a protocol upgrade
- if (!parser->upgrade)
- return 2;
-
- return 0;
-}
-
-static int
-ws_handler_on_url (http_parser *parser, const char *at, size_t len)
-{
- struct ws_handler *self = parser->data;
- str_append_data (&self->value, at, len);
- return 0;
-}
-
-#define HTTP_101_SWITCHING_PROTOCOLS "101 Switching Protocols"
-#define HTTP_400_BAD_REQUEST "400 Bad Request"
-#define HTTP_405_METHOD_NOT_ALLOWED "405 Method Not Allowed"
-#define HTTP_417_EXPECTATION_FAILED "407 Expectation Failed"
-#define HTTP_426_UPGRADE_REQUIRED "426 Upgrade Required"
-#define HTTP_505_VERSION_NOT_SUPPORTED "505 HTTP Version Not Supported"
-
-static void
-ws_handler_http_responsev (struct ws_handler *self,
- const char *status, char *const *fields)
-{
- hard_assert (status != NULL);
-
- struct str response = str_make ();
- str_append_printf (&response, "HTTP/1.1 %s\r\n", status);
-
- while (*fields)
- str_append_printf (&response, "%s\r\n", *fields++);
-
- time_t now = time (NULL);
- struct tm ts;
- gmtime_r (&now, &ts);
-
- // See RFC 7231, 7.1.1.2. Date
- const char *dow[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
- const char *moy[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
- "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
- str_append_printf (&response,
- "Date: %s, %02d %s %04d %02d:%02d:%02d GMT\r\n",
- dow[ts.tm_wday], ts.tm_mday, moy[ts.tm_mon], ts.tm_year + 1900,
- ts.tm_hour, ts.tm_min, ts.tm_sec);
-
- str_append (&response, "Server: "
- PROGRAM_NAME "/" PROGRAM_VERSION "\r\n\r\n");
- self->write_cb (self, response.str, response.len);
- str_free (&response);
-}
-
-static bool
-ws_handler_fail_handshake (struct ws_handler *self, const char *status, ...)
-{
- va_list ap;
- va_start (ap, status);
-
- const char *s;
- struct strv v = strv_make ();
- while ((s = va_arg (ap, const char *)))
- strv_append (&v, s);
-
- va_end (ap);
- ws_handler_http_responsev (self, status, v.vector);
- strv_free (&v);
-
- self->close_cb (self, true /* half_close */);
- self->state = WS_HANDLER_FLUSHING;
-
- if (self->on_close)
- self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, status);
- return false;
-}
-
-#define FAIL_HANDSHAKE(...) \
- return ws_handler_fail_handshake (self, __VA_ARGS__, NULL)
-
-static bool
-ws_handler_finish_handshake (struct ws_handler *self)
-{
- if (self->hp.method != HTTP_GET)
- FAIL_HANDSHAKE (HTTP_405_METHOD_NOT_ALLOWED, "Allow: GET");
-
- // Technically, it must be /at least/ 1.1 but no other 1.x version of HTTP
- // is going to happen and 2.x is entirely incompatible
- // XXX: we probably shouldn't use 505 to reject the minor version but w/e
- if (self->hp.http_major != 1 || self->hp.http_minor != 1)
- FAIL_HANDSHAKE (HTTP_505_VERSION_NOT_SUPPORTED);
-
- // Your expectations are way too high
- if (str_map_find (&self->headers, "Expect"))
- FAIL_HANDSHAKE (HTTP_417_EXPECTATION_FAILED);
-
- // Reject URLs specifying the schema and host; we're not parsing that
- // TODO: actually do parse this and let our user decide if it matches
- struct http_parser_url url;
- if (http_parser_parse_url (self->url.str, self->url.len, false, &url)
- || (url.field_set & (1 << UF_SCHEMA | 1 << UF_HOST | 1 << UF_PORT))
- || !str_map_find (&self->headers, "Host"))
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
-
- const char *connection = str_map_find (&self->headers, "Connection");
- if (!connection || strcasecmp_ascii (connection, "Upgrade"))
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
-
- // Check if we can actually upgrade the protocol to WebSockets
- const char *upgrade = str_map_find (&self->headers, "Upgrade");
- struct http_protocol *offered_upgrades = NULL;
- bool can_upgrade = false;
- if (upgrade && http_parse_upgrade (upgrade, &offered_upgrades))
- // Case-insensitive according to RFC 6455; neither RFC 2616 nor 7230
- // say anything at all about case-sensitivity for this field
- LIST_FOR_EACH (struct http_protocol, iter, offered_upgrades)
- {
- if (!iter->version && !strcasecmp_ascii (iter->name, "websocket"))
- can_upgrade = true;
- http_protocol_destroy (iter);
- }
- if (!can_upgrade)
- FAIL_HANDSHAKE (HTTP_426_UPGRADE_REQUIRED,
- "Upgrade: websocket", SEC_WS_VERSION ": 13");
-
- // Okay, we're finally past the basic HTTP/1.1 stuff
- const char *key = str_map_find (&self->headers, SEC_WS_KEY);
- const char *version = str_map_find (&self->headers, SEC_WS_VERSION);
-/*
- const char *protocol = str_map_find (&self->headers, SEC_WS_PROTOCOL);
- const char *extensions = str_map_find (&self->headers, SEC_WS_EXTENSIONS);
-*/
-
- if (!version)
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
- if (strcmp (version, "13"))
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, SEC_WS_VERSION ": 13");
-
- struct str tmp = str_make ();
- bool key_is_valid = key
- && base64_decode (key, false, &tmp) && tmp.len == 16;
- str_free (&tmp);
- if (!key_is_valid)
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
-
- struct strv fields = strv_make ();
- strv_append_args (&fields,
- "Upgrade: websocket",
- "Connection: Upgrade",
- NULL);
-
- char *response_key = ws_encode_response_key (key);
- strv_append_owned (&fields,
- xstrdup_printf (SEC_WS_ACCEPT ": %s", response_key));
- free (response_key);
-
- // TODO: make it possible to choose Sec-Websocket-{Extensions,Protocol}
-
- ws_handler_http_responsev (self,
- HTTP_101_SWITCHING_PROTOCOLS, fields.vector);
-
- strv_free (&fields);
-
- self->state = WS_HANDLER_OPEN;
- ev_timer_init (&self->ping_timer, ws_handler_on_ping_timer,
- self->ping_interval, 0);
- ev_timer_start (EV_DEFAULT_ &self->ping_timer);
- return true;
-}
-
-/// Tells the handler that the TCP connection has been established so it can
-/// timeout when the client handshake doesn't arrive soon enough
-static void
-ws_handler_start (struct ws_handler *self)
-{
- hard_assert (self->state == WS_HANDLER_CONNECTING);
-
- ev_timer_set (&self->handshake_timeout_watcher,
- self->handshake_timeout, 0.);
- ev_timer_start (EV_DEFAULT_ &self->handshake_timeout_watcher);
-}
-
-// The client should normally never close the connection, assume that it's
-// either received an EOF from our side, or that it doesn't care about our data
-// anymore, having called close() already
-static bool
-ws_handler_push_eof (struct ws_handler *self)
-{
- switch (self->state)
- {
- case WS_HANDLER_CONNECTING:
- ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
-
- self->state = WS_HANDLER_FLUSHING;
- if (self->on_close)
- self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "unexpected EOF");
- break;
- case WS_HANDLER_OPEN:
- ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
- // Fall-through
- case WS_HANDLER_CLOSING:
- self->state = WS_HANDLER_CLOSED;
- if (self->on_close)
- self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "");
- // Fall-through
- case WS_HANDLER_FLUSHING:
- ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher);
- break;
- default:
- soft_assert(self->state != WS_HANDLER_CLOSED);
- }
- self->state = WS_HANDLER_CLOSED;
- return false;
-}
-
-/// Push data to the WebSocket handler. "len == 0" means EOF.
-/// You are expected to close the connection and dispose of the handler
-/// when the function returns false.
-static bool
-ws_handler_push (struct ws_handler *self, const void *data, size_t len)
-{
- if (!len)
- return ws_handler_push_eof (self);
-
- if (self->state == WS_HANDLER_FLUSHING)
- // We're waiting for an EOF from the client, must not process data
- return true;
-
- if (self->state != WS_HANDLER_CONNECTING)
- return soft_assert (self->state != WS_HANDLER_CLOSED)
- && ws_parser_push (&self->parser, data, len);
-
- // The handshake hasn't been done yet, process HTTP headers
- static const http_parser_settings http_settings =
- {
- .on_header_field = ws_handler_on_header_field,
- .on_header_value = ws_handler_on_header_value,
- .on_headers_complete = ws_handler_on_headers_complete,
- .on_url = ws_handler_on_url,
- };
-
- size_t n_parsed =
- http_parser_execute (&self->hp, &http_settings, data, len);
-
- if (self->hp.upgrade)
- {
- ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
-
- // The handshake hasn't been finished, yet there is more data
- // to be processed after the headers already
- if (len - n_parsed)
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
-
- if (!ws_handler_finish_handshake (self))
- return false;
- if (self->on_connected)
- return self->on_connected (self);
- return true;
- }
-
- enum http_errno err = HTTP_PARSER_ERRNO (&self->hp);
- if (n_parsed != len || err != HPE_OK)
- {
- ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
-
- if (err == HPE_CB_headers_complete)
- print_debug ("WS handshake failed: %s", "missing `Upgrade' field");
- else
- print_debug ("WS handshake failed: %s",
- http_errno_description (err));
-
- FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
- }
- return true;
-}
-
-/// @}
-// --- Server ------------------------------------------------------------------
-
-static struct simple_config_item g_config_table[] =
-{
- { "bind_host", NULL, "Address of the server" },
- { "port_fastcgi", "9000", "Port to bind for FastCGI" },
- { "port_scgi", NULL, "Port to bind for SCGI" },
- { "port_ws", NULL, "Port to bind for WebSockets" },
- { "pid_file", NULL, "Full path for the PID file" },
- // XXX: here belongs something like a web SPA that interfaces with us
- { "static_root", NULL, "The root for static content" },
- { NULL, NULL, NULL }
-};
-
-struct server_context
-{
- ev_signal sigterm_watcher; ///< Got SIGTERM
- ev_signal sigint_watcher; ///< Got SIGINT
- ev_timer quit_timeout_watcher; ///< Quit timeout watcher
- bool quitting; ///< User requested quitting
-
- struct listener *listeners; ///< Listeners
- size_t n_listeners; ///< Number of listening sockets
-
- struct client *clients; ///< Clients
- unsigned n_clients; ///< Current number of connections
-
- struct request_handler *handlers; ///< Request handlers
- struct str_map config; ///< Server configuration
-};
-
-static void initiate_quit (struct server_context *self);
-static void try_finish_quit (struct server_context *self);
-static void on_quit_timeout (EV_P_ ev_timer *watcher, int revents);
-static void close_listeners (struct server_context *self);
-
-static void
-server_context_init (struct server_context *self)
-{
- memset (self, 0, sizeof *self);
-
- self->config = str_map_make (NULL);
- simple_config_load_defaults (&self->config, g_config_table);
- ev_timer_init (&self->quit_timeout_watcher, on_quit_timeout, 3., 0.);
- self->quit_timeout_watcher.data = self;
-}
-
-static void
-server_context_free (struct server_context *self)
-{
- // We really shouldn't attempt a quit without closing the clients first
- soft_assert (!self->clients);
-
- close_listeners (self);
- free (self->listeners);
-
- str_map_free (&self->config);
-}
-
-// --- JSON-RPC ----------------------------------------------------------------
-/// @defgroup JSON-RPC
-/// @{
-
-#define JSON_RPC_ERROR_TABLE(XX) \
- XX (-32700, PARSE_ERROR, "Parse error") \
- XX (-32600, INVALID_REQUEST, "Invalid Request") \
- XX (-32601, METHOD_NOT_FOUND, "Method not found") \
- XX (-32602, INVALID_PARAMS, "Invalid params") \
- XX (-32603, INTERNAL_ERROR, "Internal error")
-
-enum json_rpc_error
-{
-#define XX(code, name, message) JSON_RPC_ERROR_ ## name,
- JSON_RPC_ERROR_TABLE (XX)
-#undef XX
- JSON_RPC_ERROR_COUNT
-};
-
-static json_t *
-json_rpc_error (enum json_rpc_error id, json_t *data)
-{
-#define XX(code, name, message) { code, message },
- static const struct json_rpc_error
- {
- int code;
- const char *message;
- }
- errors[JSON_RPC_ERROR_COUNT] =
- {
- JSON_RPC_ERROR_TABLE (XX)
- };
-#undef XX
-
- json_t *error = json_object ();
- json_object_set_new (error, "code", json_integer (errors[id].code));
- json_object_set_new (error, "message", json_string (errors[id].message));
-
- if (data)
- json_object_set_new (error, "data", data);
-
- return error;
-}
-
-static json_t *
-json_rpc_response (json_t *id, json_t *result, json_t *error)
-{
- json_t *x = json_object ();
- json_object_set_new (x, "jsonrpc", json_string ("2.0"));
- json_object_set_new (x, "id", id ? id : json_null ());
- if (result) json_object_set_new (x, "result", result);
- if (error) json_object_set_new (x, "error", error);
- return x;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static bool
-validate_json_rpc_content_type (const char *content_type)
-{
- char *type = NULL;
- char *subtype = NULL;
-
- struct str_map parameters = str_map_make (free);
- parameters.key_xfrm = tolower_ascii_strxfrm;
-
- bool result = http_parse_media_type
- (content_type, &type, &subtype, ¶meters);
- if (!result)
- goto end;
-
- if (strcasecmp_ascii (type, "application")
- || (strcasecmp_ascii (subtype, "json") &&
- strcasecmp_ascii (subtype, "json-rpc" /* obsolete */)))
- result = false;
-
- const char *charset = str_map_find (¶meters, "charset");
- if (charset && strcasecmp_ascii (charset, "UTF-8"))
- result = false;
-
- // Currently ignoring all unknown parametrs
-
-end:
- free (type);
- free (subtype);
- str_map_free (¶meters);
- return result;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-typedef json_t *(*json_rpc_handler_fn) (struct server_context *, json_t *);
-
-struct json_rpc_handler_info
-{
- const char *method_name; ///< JSON-RPC method name
- json_rpc_handler_fn handler; ///< Method handler
-};
-
-static int
-json_rpc_handler_info_cmp (const void *first, const void *second)
-{
- return strcmp (((struct json_rpc_handler_info *) first)->method_name,
- ((struct json_rpc_handler_info *) second)->method_name);
-}
-
-// TODO: a method that sends a response after a certain number of seconds.
-// This has to be owned by the server context as a background job that
-// removes itself upon completion.
-
-static json_t *
-json_rpc_ping (struct server_context *ctx, json_t *params)
-{
- (void) ctx;
- (void) params;
-
- return json_rpc_response (NULL, json_string ("pong"), NULL);
-}
-
-static json_t *
-process_json_rpc_request (struct server_context *ctx, json_t *request)
-{
- // A list of all available methods; this list has to be ordered.
- // Eventually it might be better to move this into a map in the context.
- static struct json_rpc_handler_info handlers[] =
- {
- { "ping", json_rpc_ping },
- };
-
- if (!json_is_object (request))
- return json_rpc_response (NULL, NULL,
- json_rpc_error (JSON_RPC_ERROR_INVALID_REQUEST, NULL));
-
- json_t *v = json_object_get (request, "jsonrpc");
- json_t *m = json_object_get (request, "method");
- json_t *params = json_object_get (request, "params");
- json_t *id = json_object_get (request, "id");
-
- const char *version;
- const char *method;
-
- bool ok = true;
- ok &= v && (version = json_string_value (v)) && !strcmp (version, "2.0");
- ok &= m && (method = json_string_value (m));
- ok &= !params || json_is_array (params) || json_is_object (params);
- ok &= !id || json_is_null (id) ||
- json_is_string (id) || json_is_number (id);
- if (!ok)
- return json_rpc_response (id, NULL,
- json_rpc_error (JSON_RPC_ERROR_INVALID_REQUEST, NULL));
-
- struct json_rpc_handler_info key = { .method_name = method };
- struct json_rpc_handler_info *handler = bsearch (&key, handlers,
- N_ELEMENTS (handlers), sizeof key, json_rpc_handler_info_cmp);
- if (!handler)
- return json_rpc_response (id, NULL,
- json_rpc_error (JSON_RPC_ERROR_METHOD_NOT_FOUND, NULL));
-
- json_t *response = handler->handler (ctx, params);
- if (id)
- return response;
-
- // Notifications don't get responses
- json_decref (response);
- return NULL;
-}
-
-static void
-flush_json (json_t *json, struct str *output)
-{
- char *utf8 = json_dumps (json, JSON_ENCODE_ANY);
- str_append (output, utf8);
- free (utf8);
- json_decref (json);
-}
-
-static void
-process_json_rpc (struct server_context *ctx,
- const void *data, size_t len, struct str *output)
-{
-
- json_error_t e;
- json_t *request;
- if (!(request = json_loadb (data, len, JSON_DECODE_ANY, &e)))
- {
- flush_json (json_rpc_response (NULL, NULL,
- json_rpc_error (JSON_RPC_ERROR_PARSE_ERROR, NULL)),
- output);
- return;
- }
-
- if (json_is_array (request))
- {
- if (!json_array_size (request))
- {
- flush_json (json_rpc_response (NULL, NULL,
- json_rpc_error (JSON_RPC_ERROR_INVALID_REQUEST, NULL)),
- output);
- return;
- }
-
- json_t *response = json_array ();
- json_t *iter;
- size_t i;
-
- json_array_foreach (request, i, iter)
- {
- json_t *result = process_json_rpc_request (ctx, iter);
- if (result)
- json_array_append_new (response, result);
- }
-
- if (json_array_size (response))
- flush_json (response, output);
- else
- json_decref (response);
- }
- else
- {
- json_t *result = process_json_rpc_request (ctx, request);
- if (result)
- flush_json (result, output);
- }
-}
-
-/// @}
-// --- Requests ----------------------------------------------------------------
-/// @defgroup Requests
-/// @{
-
-/// A generic CGI request abstraction, writing data indirectly through callbacks
-struct request
-{
- struct server_context *ctx; ///< Server context
-
- struct request_handler *handler; ///< Assigned request handler
- void *handler_data; ///< User data for the handler
-
- /// Callback to write some CGI response data to the output
- void (*write_cb) (struct request *, const void *data, size_t len);
-
- /// Callback to close the CGI response, simulates end of program execution.
- /// CALLING THIS MAY CAUSE THE REQUEST TO BE DESTROYED.
- void (*close_cb) (struct request *);
-};
-
-/// An interface to detect and handle specific kinds of CGI requests.
-/// The server walks through a list of them until it finds one that can serve
-/// a particular request. If unsuccessful, the remote client gets a 404
-/// (the default handling).
-struct request_handler
-{
- LIST_HEADER (struct request_handler)
-
- /// Install ourselves as the handler for the request, if applicable.
- /// Sets @a continue_ to false if further processing should be stopped,
- /// meaning the request has already been handled.
- bool (*try_handle) (struct request *request,
- struct str_map *headers, bool *continue_);
-
- /// Handle incoming data. "len == 0" means EOF.
- /// Returns false if there is no more processing to be done.
- // FIXME: the EOF may or may not be delivered when request is cut short,
- // we should fix FastCGI not to deliver it on CONTENT_LENGTH mismatch
- bool (*push_cb) (struct request *request, const void *data, size_t len);
-
- /// Destroy the handler's data stored in the request object
- void (*finalize_cb) (struct request *request);
-};
-
-static void
-request_init (struct request *self)
-{
- memset (self, 0, sizeof *self);
-}
-
-static void
-request_free (struct request *self)
-{
- if (self->handler)
- self->handler->finalize_cb (self);
-}
-
-/// Write request CGI response data, intended for use by request handlers
-static void
-request_write (struct request *self, const void *data, size_t len)
-{
- self->write_cb (self, data, len);
-}
-
-/// This function is only intended to be run from asynchronous event handlers
-/// such as timers, not as a direct result of starting the request or receiving
-/// request data. CALLING THIS MAY CAUSE THE REQUEST TO BE DESTROYED.
-static void
-request_finish (struct request *self)
-{
- self->close_cb (self);
-}
-
-/// Starts processing a request. Returns false if no further action is to be
-/// done and the request should be finished.
-static bool
-request_start (struct request *self, struct str_map *headers)
-{
- // XXX: it feels like this should rather be two steps:
- // bool (*can_handle) (request *, headers)
- // ... install the handler ...
- // bool (*handle) (request *)
- //
- // However that might cause some stuff to be done twice.
- //
- // Another way we could get rid of the continue_ argument is via adding
- // some way of marking the request as finished from within the handler.
-
- if (g_debug_mode)
- {
- struct str_map_iter iter = str_map_iter_make (headers);
- const char *value;
- while ((value = str_map_iter_next (&iter)))
- print_debug ("%s: %s", iter.link->key, value);
- print_debug ("--");
- }
-
- bool continue_ = true;
- LIST_FOR_EACH (struct request_handler, handler, self->ctx->handlers)
- if (handler->try_handle (self, headers, &continue_))
- {
- self->handler = handler;
- return continue_;
- }
-
- // Unable to serve the request
- struct str response = str_make ();
- str_append (&response, "Status: 404 Not Found\n");
- str_append (&response, "Content-Type: text/plain\n\n");
- request_write (self, response.str, response.len);
- str_free (&response);
- return false;
-}
-
-static bool
-request_push (struct request *self, const void *data, size_t len)
-{
- if (!soft_assert (self->handler))
- // No handler, nothing to do with any data
- return false;
-
- return self->handler->push_cb (self, data, len);
-}
-
-/// @}
-// --- Requests handlers -------------------------------------------------------
-
-static bool
-request_handler_json_rpc_try_handle
- (struct request *request, struct str_map *headers, bool *continue_)
-{
- const char *content_type = str_map_find (headers, "CONTENT_TYPE");
- const char *method = str_map_find (headers, "REQUEST_METHOD");
-
- if (!method || strcmp (method, "POST")
- || !content_type || !validate_json_rpc_content_type (content_type))
- return false;
-
- struct str *buf = xcalloc (1, sizeof *buf);
- *buf = str_make ();
-
- request->handler_data = buf;
- *continue_ = true;
- return true;
-}
-
-static bool
-request_handler_json_rpc_push
- (struct request *request, const void *data, size_t len)
-{
- struct str *buf = request->handler_data;
- if (len)
- {
- str_append_data (buf, data, len);
- return true;
- }
-
- // TODO: check buf.len against CONTENT_LENGTH; if it's less, then the
- // client hasn't been successful in transferring all of its data.
- // See also comment on request_handler::push_cb.
-
- struct str response = str_make ();
- str_append (&response, "Status: 200 OK\n");
- str_append_printf (&response, "Content-Type: %s\n\n", "application/json");
- process_json_rpc (request->ctx, buf->str, buf->len, &response);
- request_write (request, response.str, response.len);
- str_free (&response);
- return false;
-}
-
-static void
-request_handler_json_rpc_finalize (struct request *request)
-{
- struct str *buf = request->handler_data;
- str_free (buf);
- free (buf);
-
- request->handler_data = NULL;
-}
-
-struct request_handler g_request_handler_json_rpc =
-{
- .try_handle = request_handler_json_rpc_try_handle,
- .push_cb = request_handler_json_rpc_push,
- .finalize_cb = request_handler_json_rpc_finalize,
-};
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static char *
-canonicalize_url_path (const char *path)
-{
- // XXX: this strips any slashes at the end
- struct strv v = strv_make ();
- cstr_split (path, "/", true, &v);
-
- struct strv canonical = strv_make ();
-
- // So that the joined path always begins with a slash
- strv_append (&canonical, "");
-
- for (size_t i = 0; i < v.len; i++)
- {
- const char *dir = v.vector[i];
- if (!strcmp (dir, "."))
- continue;
-
- if (strcmp (dir, ".."))
- strv_append (&canonical, dir);
- else if (canonical.len > 1)
- // ".." never goes above the root
- strv_remove (&canonical, canonical.len - 1);
- }
- strv_free (&v);
-
- char *joined = strv_join (&canonical, "/");
- strv_free (&canonical);
- return joined;
-}
-
-static char *
-detect_magic (const void *data, size_t len)
-{
- magic_t cookie;
- char *mime_type = NULL;
-
- if (!(cookie = magic_open (MAGIC_MIME)))
- return NULL;
-
- const char *magic = NULL;
- if (!magic_load (cookie, NULL)
- && (magic = magic_buffer (cookie, data, len)))
- mime_type = xstrdup (magic);
- else
- print_debug ("MIME type detection failed: %s", magic_error (cookie));
-
- magic_close (cookie);
- return mime_type;
-}
-
-static bool
-request_handler_static_try_handle
- (struct request *request, struct str_map *headers, bool *continue_)
-{
- // Serving static files is actually quite complicated as it turns out;
- // but this is only meant to serve a few tiny text files
-
- struct server_context *ctx = request->ctx;
- const char *root = str_map_find (&ctx->config, "static_root");
- if (!root)
- {
- print_debug ("static document root not configured");
- return false;
- }
-
- // TODO: implement HEAD, we don't get that for free;
- // probably implies adding Content-Length
- const char *method = str_map_find (headers, "REQUEST_METHOD");
- if (!method || strcmp (method, "GET"))
- return false;
-
- // TODO: look at , REQUEST_URI in the headers
- const char *path_info = str_map_find (headers, "PATH_INFO");
- if (!path_info)
- path_info = str_map_find (headers, "REQUEST_URI");
- if (!path_info)
- {
- print_debug ("neither PATH_INFO nor REQUEST_URI was defined");
- return false;
- }
-
- // We need to filter the path to stay in our root
- // Being able to read /etc/passwd would be rather embarrasing
- char *suffix = canonicalize_url_path (path_info);
- char *path = xstrdup_printf ("%s%s", root, suffix);
- print_debug ("trying to statically serve %s", path);
-
- // TODO: check that this is a regular file
- FILE *fp = fopen (path, "rb");
- if (!fp)
- {
- struct str response = str_make ();
- str_append (&response, "Status: 404 Not Found\n");
- str_append (&response, "Content-Type: text/plain\n\n");
- str_append_printf (&response,
- "File %s was not found on this server\n", suffix);
- request_write (request, response.str, response.len);
- str_free (&response);
-
- free (suffix);
- free (path);
- return false;
- }
-
- free (suffix);
- free (path);
-
- uint8_t buf[8192];
- size_t len;
-
- // Try to detect the Content-Type from the actual contents
- char *mime_type = NULL;
- if ((len = fread (buf, 1, sizeof buf, fp)))
- mime_type = detect_magic (buf, len);
- if (!mime_type)
- mime_type = xstrdup ("application/octet_stream");
-
- struct str response = str_make ();
- str_append (&response, "Status: 200 OK\n");
- str_append_printf (&response, "Content-Type: %s\n\n", mime_type);
- request_write (request, response.str, response.len);
- str_free (&response);
- free (mime_type);
-
- // Write the chunk we've used to help us with magic detection;
- // obviously we have to do it after we've written the headers
- if (len)
- request_write (request, buf, len);
-
- while ((len = fread (buf, 1, sizeof buf, fp)))
- request_write (request, buf, len);
- fclose (fp);
-
- // TODO: this should rather not be returned all at once but in chunks;
- // file read requests never return EAGAIN
- // TODO: actual file data should really be returned by a callback when
- // the socket is writable with nothing to be sent (pumping the entire
- // file all at once won't really work if it's huge).
- *continue_ = false;
- return true;
-}
-
-static bool
-request_handler_static_push
- (struct request *request, const void *data, size_t len)
-{
- (void) request;
- (void) data;
-
- // Aborting on content; we shouldn't receive any (GET)
- // FIXME: there should at least be some indication of this happening
- return len == 0;
-}
-
-static void
-request_handler_static_finalize (struct request *request)
-{
- (void) request;
- // Nothing to dispose of this far
-}
-
-struct request_handler g_request_handler_static =
-{
- .try_handle = request_handler_static_try_handle,
- .push_cb = request_handler_static_push,
- .finalize_cb = request_handler_static_finalize,
-};
-
-// --- Client communication handlers -------------------------------------------
-
-/// A virtual class for client connections coming either from the web server
-/// or directly from the end-client, depending on the protocol in use
-struct client
-{
- LIST_HEADER (struct client)
-
- struct client_vtable *vtable; ///< Client behaviour
-
- int socket_fd; ///< The network socket
- bool received_eof; ///< Whether EOF has been received yet
- bool flushing; ///< No more data to write, send FIN
- bool closing; ///< No more data to read or write
- bool half_closed; ///< Conn. half-closed while flushing
- struct write_queue write_queue; ///< Write queue
- ev_timer close_timeout_watcher; ///< Write queue flush timer
-
- ev_io read_watcher; ///< The socket can be read from
- ev_io write_watcher; ///< The socket can be written to
-};
-
-/// The concrete behaviour to serve a particular client's requests
-struct client_vtable
-{
- /// Process incoming data; "len == 0" means EOF.
- /// If the method returns false, client_close() is called by the caller.
- bool (*push) (struct client *client, const void *data, size_t len);
-
- // TODO: optional push_error() to inform about network I/O errors
-
- /// Attempt a graceful shutdown: make any appropriate steps before
- /// the client connection times out and gets torn down by force.
- /// The client is allowed to destroy itself immediately.
- void (*shutdown) (struct client *client);
-
- /// Do any additional cleanup for the concrete class before destruction
- void (*finalize) (struct client *client);
-};
-
-static void
-client_destroy (struct client *self)
-{
- // XXX: this codebase halfway pretends there could be other contexts
- struct server_context *ctx = ev_userdata (EV_DEFAULT);
- LIST_UNLINK (ctx->clients, self);
- ctx->n_clients--;
-
- // First uninitialize the higher-level implementation
- self->vtable->finalize (self);
-
- ev_io_stop (EV_DEFAULT_ &self->read_watcher);
- ev_io_stop (EV_DEFAULT_ &self->write_watcher);
- xclose (self->socket_fd);
- write_queue_free (&self->write_queue);
- ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher);
- free (self);
-
- try_finish_quit (ctx);
-}
-
-static void
-client_write (struct client *self, const void *data, size_t len)
-{
- if (!soft_assert (!self->flushing) || len == 0)
- return;
-
- struct write_req *req = xcalloc (1, sizeof *req);
- req->data.iov_base = memcpy (xmalloc (len), data, len);
- req->data.iov_len = len;
-
- write_queue_add (&self->write_queue, req);
- ev_io_start (EV_DEFAULT_ &self->write_watcher);
-}
-
-/// Half-close the connection from our side once the write_queue is flushed.
-/// It is the caller's responsibility to destroy the connection upon EOF.
-// XXX: or we might change on_client_readable to do it anyway, seems safe
-static void
-client_shutdown (struct client *self)
-{
- self->flushing = true;
- ev_feed_event (EV_DEFAULT_ &self->write_watcher, EV_WRITE);
-}
-
-/// Try to cleanly close the connection, waiting for the remote client to close
-/// its own side of the connection as a sign that it has processed all the data
-/// it wanted to. The client implementation will not receive any further data.
-/// May directly call client_destroy().
-static void
-client_close (struct client *self)
-{
- if (self->closing)
- return;
-
- self->closing = true;
- ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher);
- client_shutdown (self);
-
- // We assume the remote client doesn't want our data if it half-closes
- if (self->received_eof)
- client_destroy (self);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static bool
-client_read_loop (EV_P_ struct client *client, ev_io *watcher)
-{
- char buf[8192];
- ssize_t n_read;
-again:
- while ((n_read = recv (watcher->fd, buf, sizeof buf, 0)) >= 0)
- {
- if (!n_read)
- {
- // Don't deliver the EOF condition repeatedly
- ev_io_stop (EV_A_ watcher);
- client->received_eof = true;
- }
- if (!client->closing
- && !client->vtable->push (client, buf, n_read))
- {
- client_close (client);
- return false;
- }
- if (!n_read)
- return true;
- }
- if (errno == EINTR)
- goto again;
- if (errno == EAGAIN)
- return true;
-
- client_destroy (client);
- return false;
-}
-
-static void
-on_client_readable (EV_P_ ev_io *watcher, int revents)
-{
- struct client *client = watcher->data;
- (void) revents;
-
- if (client_read_loop (EV_A_ client, watcher)
- && client->closing && client->received_eof)
- client_destroy (client);
-}
-
-static void
-on_client_writable (EV_P_ ev_io *watcher, int revents)
-{
- struct client *client = watcher->data;
- (void) loop;
- (void) revents;
-
- // TODO: some sort of "on_buffers_flushed" callback for streaming huge
- // chunks of external (or generated) data. That will need to be
- // forwarded to "struct request_handler".
- if (!flush_queue (&client->write_queue, watcher->fd))
- {
- client_destroy (client);
- return;
- }
- if (!write_queue_is_empty (&client->write_queue))
- return;
-
- ev_io_stop (EV_A_ watcher);
- if (client->flushing && !client->half_closed)
- {
- if (!shutdown (client->socket_fd, SHUT_WR))
- client->half_closed = true;
- else
- client_destroy (client);
- }
-}
-
-static void
-on_client_timeout (EV_P_ ev_timer *watcher, int revents)
-{
- (void) loop;
- (void) revents;
-
- client_destroy (watcher->data);
-}
-
-/// Create a new instance of a subclass with the given size.
-/// The superclass is assumed to be the first member of the structure.
-static void *
-client_new (EV_P_ size_t size, int sock_fd)
-{
- struct server_context *ctx = ev_userdata (loop);
- struct client *self = xcalloc (1, size);
-
- self->write_queue = write_queue_make ();
- ev_timer_init (&self->close_timeout_watcher, on_client_timeout, 5., 0.);
- self->close_timeout_watcher.data = self;
-
- set_blocking (sock_fd, false);
- self->socket_fd = sock_fd;
-
- ev_io_init (&self->read_watcher, on_client_readable, sock_fd, EV_READ);
- ev_io_init (&self->write_watcher, on_client_writable, sock_fd, EV_WRITE);
- self->read_watcher.data = self;
- self->write_watcher.data = self;
-
- // We're only interested in reading as the write queue is empty now
- ev_io_start (EV_A_ &self->read_watcher);
-
- LIST_PREPEND (ctx->clients, self);
- ctx->n_clients++;
- return self;
-}
-
-// --- FastCGI client handler --------------------------------------------------
-
-struct client_fcgi
-{
- struct client client; ///< Parent class
- struct fcgi_muxer muxer; ///< FastCGI de/multiplexer
-};
-
-struct client_fcgi_request
-{
- struct fcgi_request *fcgi_request; ///< FastCGI request
- struct request request; ///< Request
-};
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static void
-client_fcgi_request_write_cb (struct request *req, const void *data, size_t len)
-{
- FIND_CONTAINER (self, req, struct client_fcgi_request, request);
- fcgi_request_write (self->fcgi_request, data, len);
-}
-
-static void
-client_fcgi_request_close_cb (struct request *req)
-{
- FIND_CONTAINER (self, req, struct client_fcgi_request, request);
- struct fcgi_muxer *muxer = self->fcgi_request->muxer;
- // No more data to send, terminate the substream/request,
- // and also the transport if the client didn't specifically ask to keep it
- if (!fcgi_request_finish (self->fcgi_request))
- muxer->close_cb (muxer);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static bool
-client_fcgi_request_start (struct fcgi_request *fcgi_request)
-{
- struct client_fcgi_request *request =
- fcgi_request->handler_data = xcalloc (1, sizeof *request);
- request->fcgi_request = fcgi_request;
- request_init (&request->request);
- request->request.ctx = ev_userdata (EV_DEFAULT);
- request->request.write_cb = client_fcgi_request_write_cb;
- request->request.close_cb = client_fcgi_request_close_cb;
-
- return request_start (&request->request, &fcgi_request->headers);
-}
-
-static bool
-client_fcgi_request_push
- (struct fcgi_request *req, const void *data, size_t len)
-{
- struct client_fcgi_request *request = req->handler_data;
- return request_push (&request->request, data, len)
- || fcgi_request_finish (req);
-}
-
-static void
-client_fcgi_request_finalize (struct fcgi_request *req)
-{
- struct client_fcgi_request *request = req->handler_data;
- request_free (&request->request);
- free (request);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static void
-client_fcgi_write_cb (struct fcgi_muxer *mux, const void *data, size_t len)
-{
- FIND_CONTAINER (self, mux, struct client_fcgi, muxer);
- client_write (&self->client, data, len);
-}
-
-static void
-client_fcgi_close_cb (struct fcgi_muxer *mux)
-{
- FIND_CONTAINER (self, mux, struct client_fcgi, muxer);
- client_close (&self->client);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static bool
-client_fcgi_push (struct client *client, const void *data, size_t len)
-{
- FIND_CONTAINER (self, client, struct client_fcgi, client);
- return fcgi_muxer_push (&self->muxer, data, len);
-}
-
-static void
-client_fcgi_shutdown (struct client *client)
-{
- FIND_CONTAINER (self, client, struct client_fcgi, client);
- self->muxer.in_shutdown = true;
-
- // TODO: respond with FCGI_END_REQUEST: FCGI_REQUEST_COMPLETE to everything?
- // The FastCGI specification isn't very clear about what we should do.
-}
-
-static void
-client_fcgi_finalize (struct client *client)
-{
- FIND_CONTAINER (self, client, struct client_fcgi, client);
- fcgi_muxer_free (&self->muxer);
-}
-
-static struct client_vtable client_fcgi_vtable =
-{
- .push = client_fcgi_push,
- .shutdown = client_fcgi_shutdown,
- .finalize = client_fcgi_finalize,
-};
-
-static struct client *
-client_fcgi_create (EV_P_ int sock_fd)
-{
- struct client_fcgi *self = client_new (EV_A_ sizeof *self, sock_fd);
- self->client.vtable = &client_fcgi_vtable;
-
- fcgi_muxer_init (&self->muxer);
- self->muxer.write_cb = client_fcgi_write_cb;
- self->muxer.close_cb = client_fcgi_close_cb;
- self->muxer.request_start_cb = client_fcgi_request_start;
- self->muxer.request_push_cb = client_fcgi_request_push;
- self->muxer.request_finalize_cb = client_fcgi_request_finalize;
- return &self->client;
-}
-
-// --- SCGI client handler -----------------------------------------------------
-
-struct client_scgi
-{
- struct client client; ///< Parent class
- struct scgi_parser parser; ///< SCGI stream parser
- struct request request; ///< Request (only one per connection)
- unsigned long remaining_content; ///< Length of input data to be seen
-};
-
-static void
-client_scgi_write_cb (struct request *req, const void *data, size_t len)
-{
- FIND_CONTAINER (self, req, struct client_scgi, request);
- client_write (&self->client, data, len);
-}
-
-static void
-client_scgi_close_cb (struct request *req)
-{
- FIND_CONTAINER (self, req, struct client_scgi, request);
- // NOTE: this rather really means "close me [the request]"
- client_close (&self->client);
-}
-
-static bool
-client_scgi_on_headers_read (void *user_data)
-{
- struct client_scgi *self = user_data;
- const char *cl = str_map_find (&self->parser.headers, "CONTENT_LENGTH");
- if (!cl || !xstrtoul (&self->remaining_content, cl, 10))
- {
- print_debug ("SCGI request with invalid or missing CONTENT_LENGTH");
- return false;
- }
- return request_start (&self->request, &self->parser.headers);
-}
-
-static bool
-client_scgi_on_content (void *user_data, const void *data, size_t len)
-{
- struct client_scgi *self = user_data;
- if (len > self->remaining_content)
- {
- print_debug ("SCGI request got more data than CONTENT_LENGTH");
- return false;
- }
- // We're in a slight disagreement with the specification since
- // this tries to write output before it has read all the input
- if (!request_push (&self->request, data, len))
- return false;
-
- // Signalise end of input to the request handler
- return (self->remaining_content -= len) != 0
- || request_push (&self->request, NULL, 0);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static bool
-client_scgi_push (struct client *client, const void *data, size_t len)
-{
- struct client_scgi *self = (struct client_scgi *) client;
- struct error *e = NULL;
- if (scgi_parser_push (&self->parser, data, len, &e))
- return true;
-
- if (e != NULL)
- {
- print_debug ("SCGI parser failed: %s", e->message);
- error_free (e);
- }
- return false;
-}
-
-static void
-client_scgi_finalize (struct client *client)
-{
- struct client_scgi *self = (struct client_scgi *) client;
- request_free (&self->request);
- scgi_parser_free (&self->parser);
-}
-
-static struct client_vtable client_scgi_vtable =
-{
- .push = client_scgi_push,
- .finalize = client_scgi_finalize,
-};
-
-static struct client *
-client_scgi_create (EV_P_ int sock_fd)
-{
- struct client_scgi *self = client_new (EV_A_ sizeof *self, sock_fd);
- self->client.vtable = &client_scgi_vtable;
-
- request_init (&self->request);
- self->request.ctx = ev_userdata (EV_DEFAULT);
- self->request.write_cb = client_scgi_write_cb;
- self->request.close_cb = client_scgi_close_cb;
-
- self->parser = scgi_parser_make ();
- self->parser.on_headers_read = client_scgi_on_headers_read;
- self->parser.on_content = client_scgi_on_content;
- self->parser.user_data = self;
- return &self->client;
-}
-
-// --- WebSockets client handler -----------------------------------------------
-
-struct client_ws
-{
- struct client client; ///< Parent class
- struct ws_handler handler; ///< WebSockets connection handler
-};
-
-static bool
-client_ws_on_message (struct ws_handler *handler,
- enum ws_opcode type, const void *data, size_t len)
-{
- FIND_CONTAINER (self, handler, struct client_ws, handler);
- if (type != WS_OPCODE_TEXT)
- {
- return ws_handler_fail_connection
- (&self->handler, WS_STATUS_UNSUPPORTED_DATA);
- }
-
- struct server_context *ctx = ev_userdata (EV_DEFAULT);
- struct str response = str_make ();
- process_json_rpc (ctx, data, len, &response);
- if (response.len)
- ws_handler_send_frame (&self->handler,
- WS_OPCODE_TEXT, response.str, response.len);
- str_free (&response);
- return true;
-}
-
-static void
-client_ws_write_cb (struct ws_handler *handler, const void *data, size_t len)
-{
- FIND_CONTAINER (self, handler, struct client_ws, handler);
- client_write (&self->client, data, len);
-}
-
-static void
-client_ws_close_cb (struct ws_handler *handler, bool half_close)
-{
- FIND_CONTAINER (self, handler, struct client_ws, handler);
- (half_close ? client_shutdown : client_destroy) (&self->client);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static bool
-client_ws_push (struct client *client, const void *data, size_t len)
-{
- FIND_CONTAINER (self, client, struct client_ws, client);
- // client_close() will correctly destroy the client on EOF
- return ws_handler_push (&self->handler, data, len);
-}
-
-static void
-client_ws_shutdown (struct client *client)
-{
- FIND_CONTAINER (self, client, struct client_ws, client);
- if (self->handler.state == WS_HANDLER_CONNECTING)
- // No on_close, no problem
- client_destroy (&self->client);
- else if (self->handler.state == WS_HANDLER_OPEN)
- ws_handler_close (&self->handler, WS_STATUS_GOING_AWAY, NULL, 0);
-}
-
-static void
-client_ws_finalize (struct client *client)
-{
- FIND_CONTAINER (self, client, struct client_ws, client);
- ws_handler_free (&self->handler);
-}
-
-static struct client_vtable client_ws_vtable =
-{
- .push = client_ws_push,
- .shutdown = client_ws_shutdown,
- .finalize = client_ws_finalize,
-};
-
-static struct client *
-client_ws_create (EV_P_ int sock_fd)
-{
- struct client_ws *self = client_new (EV_A_ sizeof *self, sock_fd);
- self->client.vtable = &client_ws_vtable;
-
- ws_handler_init (&self->handler);
- self->handler.on_message = client_ws_on_message;
- self->handler.write_cb = client_ws_write_cb;
- self->handler.close_cb = client_ws_close_cb;
-
- // One mebibyte seems to be a reasonable value
- self->handler.max_payload_len = 1 << 10;
-
- ws_handler_start (&self->handler);
- return &self->client;
-}
-
-// --- Basic server stuff ------------------------------------------------------
-
-typedef struct client *(*client_create_fn) (EV_P_ int sock_fd);
-
-struct listener
-{
- int fd; ///< Listening socket FD
- ev_io watcher; ///< New connection available
- client_create_fn create; ///< Client constructor
-};
-
-static void
-close_listeners (struct server_context *self)
-{
- for (size_t i = 0; i < self->n_listeners; i++)
- {
- struct listener *listener = &self->listeners[i];
- if (listener->fd == -1)
- continue;
-
- ev_io_stop (EV_DEFAULT_ &listener->watcher);
- xclose (listener->fd);
- listener->fd = -1;
- }
-}
-
-static void
-try_finish_quit (struct server_context *self)
-{
- if (!self->quitting || self->clients)
- return;
-
- ev_timer_stop (EV_DEFAULT_ &self->quit_timeout_watcher);
- ev_break (EV_DEFAULT_ EVBREAK_ALL);
-}
-
-static void
-on_quit_timeout (EV_P_ ev_timer *watcher, int revents)
-{
- struct server_context *self = watcher->data;
- (void) loop;
- (void) revents;
-
- LIST_FOR_EACH (struct client, iter, self->clients)
- client_destroy (iter);
-}
-
-static void
-initiate_quit (struct server_context *self)
-{
- self->quitting = true;
- close_listeners (self);
-
- // Wait a little while for all clients to clean up, if necessary
- LIST_FOR_EACH (struct client, iter, self->clients)
- if (iter->vtable->shutdown)
- iter->vtable->shutdown (iter);
- ev_timer_start (EV_DEFAULT_ &self->quit_timeout_watcher);
- try_finish_quit (self);
-}
-
-static void
-on_client_available (EV_P_ ev_io *watcher, int revents)
-{
- struct server_context *ctx = ev_userdata (loop);
- struct listener *listener = watcher->data;
- (void) revents;
-
- while (true)
- {
- int sock_fd = accept (watcher->fd, NULL, NULL);
- if (sock_fd != -1)
- listener->create (EV_A_ sock_fd);
- else if (errno == EAGAIN)
- return;
- else if (errno != EINTR && errno != EMFILE
- && errno != ECONNRESET && errno != ECONNABORTED)
- break;
- }
-
- // Stop accepting connections to prevent busy looping
- ev_io_stop (EV_A_ watcher);
-
- print_fatal ("%s: %s", "accept", strerror (errno));
- initiate_quit (ctx);
-}
-
-// --- Application setup -------------------------------------------------------
-
-/// This function handles values that require validation before their first use,
-/// or some kind of a transformation (such as conversion to an integer) needs
-/// to be done before they can be used directly.
-static bool
-parse_config (struct server_context *ctx, struct error **e)
-{
- (void) ctx;
- (void) e;
-
- return true;
-}
-
-static int
-listener_bind (struct addrinfo *gai_iter)
-{
- int fd = socket (gai_iter->ai_family,
- gai_iter->ai_socktype, gai_iter->ai_protocol);
- if (fd == -1)
- return -1;
- set_cloexec (fd);
-
- int yes = 1;
- soft_assert (setsockopt (fd, SOL_SOCKET, SO_KEEPALIVE,
- &yes, sizeof yes) != -1);
- soft_assert (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR,
- &yes, sizeof yes) != -1);
-
- char host[NI_MAXHOST], port[NI_MAXSERV];
- host[0] = port[0] = '\0';
- int err = getnameinfo (gai_iter->ai_addr, gai_iter->ai_addrlen,
- host, sizeof host, port, sizeof port,
- NI_NUMERICHOST | NI_NUMERICSERV);
- if (err)
- print_debug ("%s: %s", "getnameinfo", gai_strerror (err));
-
- char *address = format_host_port_pair (host, port);
- if (bind (fd, gai_iter->ai_addr, gai_iter->ai_addrlen))
- print_error ("bind to %s failed: %s", address, strerror (errno));
- else if (listen (fd, 16 /* arbitrary number */))
- print_error ("listen on %s failed: %s", address, strerror (errno));
- else
- {
- print_status ("listening on %s", address);
- free (address);
- return fd;
- }
-
- free (address);
- xclose (fd);
- return -1;
-}
-
-static void
-listener_add (struct server_context *ctx, const char *host, const char *port,
- const struct addrinfo *gai_hints, client_create_fn create)
-{
- struct addrinfo *gai_result, *gai_iter;
- int err = getaddrinfo (host, port, gai_hints, &gai_result);
- if (err)
- {
- char *address = format_host_port_pair (host, port);
- print_error ("bind to %s failed: %s: %s",
- address, "getaddrinfo", gai_strerror (err));
- free (address);
- return;
- }
-
- int fd;
- for (gai_iter = gai_result; gai_iter; gai_iter = gai_iter->ai_next)
- {
- if ((fd = listener_bind (gai_iter)) == -1)
- continue;
- set_blocking (fd, false);
-
- struct listener *listener = &ctx->listeners[ctx->n_listeners++];
- ev_io_init (&listener->watcher, on_client_available, fd, EV_READ);
- ev_io_start (EV_DEFAULT_ &listener->watcher);
- listener->watcher.data = listener;
- listener->create = create;
- listener->fd = fd;
- break;
- }
- freeaddrinfo (gai_result);
-}
-
-static void
-get_ports_from_config (struct server_context *ctx,
- const char *key, struct strv *out)
-{
- const char *ports;
- if ((ports = str_map_find (&ctx->config, key)))
- cstr_split (ports, ",", true, out);
-}
-
-static bool
-setup_listen_fds (struct server_context *ctx, struct error **e)
-{
- static const struct addrinfo gai_hints =
- {
- .ai_socktype = SOCK_STREAM,
- .ai_flags = AI_PASSIVE,
- };
-
- struct strv ports_fcgi = strv_make ();
- struct strv ports_scgi = strv_make ();
- struct strv ports_ws = strv_make ();
-
- get_ports_from_config (ctx, "port_fastcgi", &ports_fcgi);
- get_ports_from_config (ctx, "port_scgi", &ports_scgi);
- get_ports_from_config (ctx, "port_ws", &ports_ws);
-
- const char *bind_host = str_map_find (&ctx->config, "bind_host");
- size_t n_ports = ports_fcgi.len + ports_scgi.len + ports_ws.len;
- ctx->listeners = xcalloc (n_ports, sizeof *ctx->listeners);
-
- for (size_t i = 0; i < ports_fcgi.len; i++)
- listener_add (ctx, bind_host, ports_fcgi.vector[i],
- &gai_hints, client_fcgi_create);
- for (size_t i = 0; i < ports_scgi.len; i++)
- listener_add (ctx, bind_host, ports_scgi.vector[i],
- &gai_hints, client_scgi_create);
- for (size_t i = 0; i < ports_ws.len; i++)
- listener_add (ctx, bind_host, ports_ws.vector[i],
- &gai_hints, client_ws_create);
-
- strv_free (&ports_fcgi);
- strv_free (&ports_scgi);
- strv_free (&ports_ws);
-
- if (!ctx->n_listeners)
- {
- error_set (e, "%s: %s",
- "network setup failed", "no ports to listen on");
- return false;
- }
- return true;
-}
-
-static bool
-app_lock_pid_file (struct server_context *ctx, struct error **e)
-{
- const char *path = str_map_find (&ctx->config, "pid_file");
- if (!path)
- return true;
-
- char *resolved = resolve_filename (path, resolve_relative_runtime_filename);
- bool result = lock_pid_file (resolved, e) != -1;
- free (resolved);
- return result;
-}
-
-// --- Tests -------------------------------------------------------------------
-
-static void
-test_misc (void)
-{
- soft_assert ( validate_json_rpc_content_type
- ("application/JSON; charset=\"utf-8\""));
- soft_assert (!validate_json_rpc_content_type
- ("text/html; charset=\"utf-8\""));
-
- char *canon = canonicalize_url_path ("///../../../etc/./passwd");
- soft_assert (!strcmp (canon, "/etc/passwd"));
- free (canon);
-}
-
-int
-test_main (int argc, char *argv[])
-{
- struct test test;
- test_init (&test, argc, argv);
-
- test_add_simple (&test, "/misc", NULL, test_misc);
-
- // TODO: write more tests
- // TODO: test the server handler (happy path)
-
- return test_run (&test);
-}
-
-// --- Main program ------------------------------------------------------------
-
-static void
-on_termination_signal (EV_P_ ev_signal *handle, int revents)
-{
- struct server_context *ctx = ev_userdata (loop);
- (void) handle;
- (void) revents;
-
- if (ctx->quitting)
- {
- // Double C-c from the terminal accelerates the process
- LIST_FOR_EACH (struct client, iter, ctx->clients)
- client_destroy (iter);
- }
- else
- initiate_quit (ctx);
-}
-
-static void
-setup_signal_handlers (struct server_context *ctx)
-{
- ev_signal_init (&ctx->sigterm_watcher, on_termination_signal, SIGTERM);
- ev_signal_start (EV_DEFAULT_ &ctx->sigterm_watcher);
-
- ev_signal_init (&ctx->sigint_watcher, on_termination_signal, SIGINT);
- ev_signal_start (EV_DEFAULT_ &ctx->sigint_watcher);
-
- (void) signal (SIGPIPE, SIG_IGN);
-}
-
-static void
-daemonize (struct server_context *ctx)
-{
- print_status ("daemonizing...");
-
- if (chdir ("/"))
- exit_fatal ("%s: %s", "chdir", strerror (errno));
-
- // Because of systemd, we need to exit the parent process _after_ writing
- // a PID file, otherwise our grandchild would receive a SIGTERM
- int sync_pipe[2];
- if (pipe (sync_pipe))
- exit_fatal ("%s: %s", "pipe", strerror (errno));
-
- pid_t pid;
- if ((pid = fork ()) < 0)
- exit_fatal ("%s: %s", "fork", strerror (errno));
- else if (pid)
- {
- // Wait until all write ends of the pipe are closed, which can mean
- // either success or failure, we don't need to care
- xclose (sync_pipe[PIPE_WRITE]);
-
- char dummy;
- if (read (sync_pipe[PIPE_READ], &dummy, 1) < 0)
- exit_fatal ("%s: %s", "read", strerror (errno));
-
- exit (EXIT_SUCCESS);
- }
-
- setsid ();
- signal (SIGHUP, SIG_IGN);
-
- if ((pid = fork ()) < 0)
- exit_fatal ("%s: %s", "fork", strerror (errno));
- else if (pid)
- exit (EXIT_SUCCESS);
-
- openlog (PROGRAM_NAME, LOG_NDELAY | LOG_NOWAIT | LOG_PID, 0);
- g_log_message_real = log_message_syslog;
-
- // Write the PID file (if so configured) and get rid of the pipe, so that
- // the read() in our grandparent finally returns zero (no write ends)
- struct error *e = NULL;
- if (!app_lock_pid_file (ctx, &e))
- exit_fatal ("%s", e->message);
-
- xclose (sync_pipe[PIPE_READ]);
- xclose (sync_pipe[PIPE_WRITE]);
-
- // XXX: we may close our own descriptors this way, crippling ourselves;
- // there is no real guarantee that we will start with all three
- // descriptors open. In theory we could try to enumerate the descriptors
- // at the start of main().
- for (int i = 0; i < 3; i++)
- xclose (i);
-
- int tty = open ("/dev/null", O_RDWR);
- if (tty != 0 || dup (0) != 1 || dup (0) != 2)
- exit_fatal ("failed to reopen FD's: %s", strerror (errno));
-}
-
-static void
-parse_program_arguments (int argc, char **argv)
-{
- static const struct opt opts[] =
- {
- { 't', "test", NULL, 0, "self-test" },
- { 'd', "debug", NULL, 0, "run in debug mode" },
- { 'h', "help", NULL, 0, "display this help and exit" },
- { 'V', "version", NULL, 0, "output version information and exit" },
- { 'w', "write-default-cfg", "FILENAME",
- OPT_OPTIONAL_ARG | OPT_LONG_ONLY,
- "write a default configuration file and exit" },
- { 0, NULL, NULL, 0, NULL }
- };
-
- struct opt_handler oh =
- opt_handler_make (argc, argv, opts, NULL, "JSON-RPC 2.0 demo server.");
-
- int c;
- while ((c = opt_handler_get (&oh)) != -1)
- switch (c)
- {
- case 't':
- test_main (argc, argv);
- exit (EXIT_SUCCESS);
- case 'd':
- g_debug_mode = true;
- break;
- case 'h':
- opt_handler_usage (&oh, stdout);
- exit (EXIT_SUCCESS);
- case 'V':
- printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
- exit (EXIT_SUCCESS);
- case 'w':
- call_simple_config_write_default (optarg, g_config_table);
- exit (EXIT_SUCCESS);
- default:
- print_error ("wrong options");
- opt_handler_usage (&oh, stderr);
- exit (EXIT_FAILURE);
- }
-
- argc -= optind;
- argv += optind;
-
- if (argc)
- {
- opt_handler_usage (&oh, stderr);
- exit (EXIT_FAILURE);
- }
- opt_handler_free (&oh);
-}
-
-int
-main (int argc, char *argv[])
-{
- parse_program_arguments (argc, argv);
-
- print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting");
-
- struct server_context ctx;
- server_context_init (&ctx);
-
- struct error *e = NULL;
- if (!simple_config_update_from_file (&ctx.config, &e))
- {
- print_error ("error loading configuration: %s", e->message);
- error_free (e);
- exit (EXIT_FAILURE);
- }
-
- struct ev_loop *loop;
- if (!(loop = EV_DEFAULT))
- exit_fatal ("libev initialization failed");
-
- ev_set_userdata (loop, &ctx);
- setup_signal_handlers (&ctx);
-
- LIST_PREPEND (ctx.handlers, &g_request_handler_static);
- LIST_PREPEND (ctx.handlers, &g_request_handler_json_rpc);
-
- if (!parse_config (&ctx, &e)
- || !setup_listen_fds (&ctx, &e))
- {
- print_error ("%s", e->message);
- error_free (e);
- exit (EXIT_FAILURE);
- }
-
- if (!g_debug_mode)
- daemonize (&ctx);
- else if (!app_lock_pid_file (&ctx, &e))
- exit_fatal ("%s", e->message);
-
- ev_run (loop, 0);
- ev_loop_destroy (loop);
-
- server_context_free (&ctx);
- return EXIT_SUCCESS;
-}
diff --git a/json-rpc-test-server.c b/json-rpc-test-server.c
new file mode 100644
index 0000000..ac15f0a
--- /dev/null
+++ b/json-rpc-test-server.c
@@ -0,0 +1,2925 @@
+/*
+ * json-rpc-test-server.c: JSON-RPC 2.0 demo server
+ *
+ * Copyright (c) 2015 - 2018, Přemysl Janouch
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#define print_fatal_data ((void *) LOG_ERR)
+#define print_error_data ((void *) LOG_ERR)
+#define print_warning_data ((void *) LOG_WARNING)
+#define print_status_data ((void *) LOG_INFO)
+#define print_debug_data ((void *) LOG_DEBUG)
+
+#define LIBERTY_WANT_SSL
+#define LIBERTY_WANT_PROTO_HTTP
+#define LIBERTY_WANT_PROTO_WS
+#define LIBERTY_WANT_PROTO_SCGI
+#define LIBERTY_WANT_PROTO_FASTCGI
+
+#include "config.h"
+#include "liberty/liberty.c"
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "http-parser/http_parser.h"
+
+enum { PIPE_READ, PIPE_WRITE };
+
+#define FIND_CONTAINER(name, pointer, type, member) \
+ type *name = CONTAINER_OF (pointer, type, member)
+
+// --- Utilities ---------------------------------------------------------------
+
+static bool
+flush_queue (struct write_queue *queue, int fd)
+{
+ struct iovec vec[queue->len], *vec_iter = vec;
+ LIST_FOR_EACH (struct write_req, iter, queue->head)
+ *vec_iter++ = iter->data;
+
+ ssize_t written;
+again:
+ if ((written = writev (fd, vec, N_ELEMENTS (vec))) >= 0)
+ {
+ write_queue_processed (queue, written);
+ return true;
+ }
+ if (errno == EINTR)
+ goto again;
+ if (errno == EAGAIN)
+ return true;
+
+ return false;
+}
+
+// --- Logging -----------------------------------------------------------------
+
+static void
+log_message_syslog (void *user_data, const char *quote, const char *fmt,
+ va_list ap)
+{
+ int prio = (int) (intptr_t) user_data;
+
+ va_list va;
+ va_copy (va, ap);
+ int size = vsnprintf (NULL, 0, fmt, va);
+ va_end (va);
+ if (size < 0)
+ return;
+
+ char buf[size + 1];
+ if (vsnprintf (buf, sizeof buf, fmt, ap) >= 0)
+ syslog (prio, "%s%s", quote, buf);
+}
+
+// --- FastCGI -----------------------------------------------------------------
+/// @defgroup FastCGI
+/// @{
+
+enum fcgi_request_state
+{
+ FCGI_REQUEST_PARAMS, ///< Reading headers
+ FCGI_REQUEST_STDIN ///< Reading input
+};
+
+struct fcgi_request
+{
+ struct fcgi_muxer *muxer; ///< The parent muxer
+ uint16_t request_id; ///< The ID of this request
+ uint8_t flags; ///< Request flags
+
+ enum fcgi_request_state state; ///< Parsing state
+ struct str_map headers; ///< Headers
+ struct fcgi_nv_parser hdr_parser; ///< Header parser
+
+ struct str output_buffer; ///< Output buffer
+
+ void *handler_data; ///< Handler data
+};
+
+/// Handles a single FastCGI connection, de/multiplexing requests and responses
+struct fcgi_muxer
+{
+ struct fcgi_parser parser; ///< FastCGI message parser
+ uint32_t active_requests; ///< The number of active requests
+ bool in_shutdown; ///< Rejecting new requests
+
+ // Virtual method callbacks:
+
+ /// Write data to the underlying transport
+ void (*write_cb) (struct fcgi_muxer *, const void *data, size_t len);
+
+ /// Close the underlying transport. You are allowed to destroy the muxer
+ /// directly from within the callback.
+ void (*close_cb) (struct fcgi_muxer *);
+
+ /// Start processing a request. Return false if no further action is
+ /// to be done and the request should be finished.
+ bool (*request_start_cb) (struct fcgi_request *);
+
+ /// Handle incoming data. "len == 0" means EOF. Returns false if
+ /// the underlying transport should be closed, this being the last request.
+ bool (*request_push_cb)
+ (struct fcgi_request *, const void *data, size_t len);
+
+ /// Destroy the handler's data stored in the request object
+ void (*request_finalize_cb) (struct fcgi_request *);
+
+ /// Requests assigned to request IDs (may not be FCGI_NULL_REQUEST_ID)
+ struct fcgi_request *requests[1 << 8];
+};
+
+static void
+fcgi_muxer_send (struct fcgi_muxer *self,
+ enum fcgi_type type, uint16_t request_id, const void *data, size_t len)
+{
+ hard_assert (len <= UINT16_MAX);
+
+ struct str message = str_make ();
+ static char zeroes[8];
+ size_t padding = -len & 7;
+
+ str_pack_u8 (&message, FCGI_VERSION_1);
+ str_pack_u8 (&message, type);
+ str_pack_u16 (&message, request_id);
+ str_pack_u16 (&message, len); // content length
+ str_pack_u8 (&message, padding); // padding length
+ str_pack_u8 (&message, 0); // reserved
+
+ str_append_data (&message, data, len);
+ str_append_data (&message, zeroes, padding);
+
+ // XXX: we should probably have another write_cb that assumes ownership
+ self->write_cb (self, message.str, message.len);
+ str_free (&message);
+}
+
+static void
+fcgi_muxer_send_end_request (struct fcgi_muxer *self, uint16_t request_id,
+ uint32_t app_status, enum fcgi_protocol_status protocol_status)
+{
+ uint8_t content[8] = { app_status >> 24, app_status >> 16,
+ app_status << 8, app_status, protocol_status };
+ fcgi_muxer_send (self, FCGI_END_REQUEST, request_id,
+ content, sizeof content);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static struct fcgi_request *
+fcgi_request_new (void)
+{
+ struct fcgi_request *self = xcalloc (1, sizeof *self);
+
+ self->headers = str_map_make (free);
+
+ self->hdr_parser = fcgi_nv_parser_make ();
+ self->hdr_parser.output = &self->headers;
+
+ self->output_buffer = str_make ();
+ return self;
+}
+
+static void
+fcgi_request_destroy (struct fcgi_request *self)
+{
+ // TODO: consider the case where it hasn't been started yet
+ self->muxer->request_finalize_cb (self);
+
+ str_map_free (&self->headers);
+ fcgi_nv_parser_free (&self->hdr_parser);
+ free (self);
+}
+
+static void
+fcgi_request_flush (struct fcgi_request *self)
+{
+ if (!self->output_buffer.len)
+ return;
+
+ fcgi_muxer_send (self->muxer, FCGI_STDOUT, self->request_id,
+ self->output_buffer.str, self->output_buffer.len);
+ str_reset (&self->output_buffer);
+}
+
+static void
+fcgi_request_write (struct fcgi_request *self, const void *data, size_t len)
+{
+ // We're buffering the output and splitting it into messages
+ bool need_flush = true;
+ while (len)
+ {
+ size_t to_write = UINT16_MAX - self->output_buffer.len;
+ if (to_write > len)
+ {
+ to_write = len;
+ need_flush = false;
+ }
+
+ str_append_data (&self->output_buffer, data, to_write);
+ data = (uint8_t *) data + to_write;
+ len -= to_write;
+
+ if (need_flush)
+ fcgi_request_flush (self);
+ }
+}
+
+/// Mark the request as done. Returns false if the underlying transport
+/// should be closed, this being the last request.
+static bool
+fcgi_request_finish (struct fcgi_request *self)
+{
+ fcgi_request_flush (self);
+ fcgi_muxer_send (self->muxer, FCGI_STDOUT, self->request_id, NULL, 0);
+
+ fcgi_muxer_send_end_request (self->muxer, self->request_id,
+ 0 /* TODO app_status, although ignored */,
+ FCGI_REQUEST_COMPLETE /* TODO protocol_status, may be different */);
+
+ bool should_close = !(self->flags & FCGI_KEEP_CONN);
+
+ self->muxer->active_requests--;
+ self->muxer->requests[self->request_id] = NULL;
+ fcgi_request_destroy (self);
+
+ return !should_close;
+}
+
+static bool
+fcgi_request_push_params
+ (struct fcgi_request *self, const void *data, size_t len)
+{
+ if (self->state != FCGI_REQUEST_PARAMS)
+ {
+ print_debug ("FastCGI: expected %s, got %s",
+ STRINGIFY (FCGI_STDIN), STRINGIFY (FCGI_PARAMS));
+ return false;
+ }
+
+ if (len)
+ fcgi_nv_parser_push (&self->hdr_parser, data, len);
+ else
+ {
+ if (self->hdr_parser.state != FCGI_NV_PARSER_NAME_LEN)
+ print_debug ("FastCGI: request headers seem to be cut off");
+
+ self->state = FCGI_REQUEST_STDIN;
+ if (!self->muxer->request_start_cb (self))
+ return fcgi_request_finish (self);
+ }
+ return true;
+}
+
+static bool
+fcgi_request_push_stdin
+ (struct fcgi_request *self, const void *data, size_t len)
+{
+ if (self->state != FCGI_REQUEST_STDIN)
+ {
+ print_debug ("FastCGI: expected %s, got %s",
+ STRINGIFY (FCGI_PARAMS), STRINGIFY (FCGI_STDIN));
+ return false;
+ }
+
+ return self->muxer->request_push_cb (self, data, len);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+typedef bool (*fcgi_muxer_handler_fn)
+ (struct fcgi_muxer *, const struct fcgi_parser *);
+
+static bool
+fcgi_muxer_on_get_values
+ (struct fcgi_muxer *self, const struct fcgi_parser *parser)
+{
+ if (parser->request_id != FCGI_NULL_REQUEST_ID)
+ {
+ print_debug ("FastCGI: invalid %s message",
+ STRINGIFY (FCGI_GET_VALUES));
+ return false;
+ }
+
+ struct str_map values = str_map_make (free);
+ struct str_map response = str_map_make (free);
+
+ struct fcgi_nv_parser nv_parser = fcgi_nv_parser_make ();
+ nv_parser.output = &values;
+
+ fcgi_nv_parser_push (&nv_parser, parser->content.str, parser->content.len);
+ const char *key = NULL;
+
+ // No real-world servers seem to actually use multiplexing
+ // or even issue this request, but we will implement it anyway
+ if (str_map_find (&values, (key = FCGI_MPXS_CONNS)))
+ str_map_set (&response, key, xstrdup ("1"));
+
+ // It's not clear whether FCGI_MAX_REQS means concurrently over all
+ // connections or over just a single connection (multiplexed), though
+ // supposedly it's actually per /web server/. Supply the strictest limit.
+ if (str_map_find (&values, (key = FCGI_MAX_REQS)))
+ str_map_set (&response, key,
+ xstrdup_printf ("%zu", N_ELEMENTS (self->requests) - 1));
+
+ // FCGI_MAX_CONNS would be basically infinity. We don't limit connections.
+
+ struct str content = str_make ();
+ fcgi_nv_convert (&response, &content);
+ fcgi_muxer_send (self, FCGI_GET_VALUES_RESULT, parser->request_id,
+ content.str, content.len);
+ str_free (&content);
+
+ str_map_free (&values);
+ str_map_free (&response);
+ return true;
+}
+
+static bool
+fcgi_muxer_on_begin_request
+ (struct fcgi_muxer *self, const struct fcgi_parser *parser)
+{
+ struct msg_unpacker unpacker =
+ msg_unpacker_make (parser->content.str, parser->content.len);
+
+ uint16_t role;
+ uint8_t flags;
+ bool success = true;
+ success &= msg_unpacker_u16 (&unpacker, &role);
+ success &= msg_unpacker_u8 (&unpacker, &flags);
+ // Ignoring 5 reserved bytes
+
+ if (!success)
+ {
+ print_debug ("FastCGI: invalid %s message",
+ STRINGIFY (FCGI_BEGIN_REQUEST));
+ return false;
+ }
+
+ struct fcgi_request *request = self->requests[parser->request_id];
+ if (parser->request_id == FCGI_NULL_REQUEST_ID || request)
+ {
+ print_debug ("FastCGI: unusable request ID in %s message",
+ STRINGIFY (FCGI_BEGIN_REQUEST));
+ return false;
+ }
+
+ // We can only act as a responder, reject everything else up front
+ if (role != FCGI_RESPONDER)
+ {
+ fcgi_muxer_send_end_request (self,
+ parser->request_id, 0, FCGI_UNKNOWN_ROLE);
+ return true;
+ }
+
+ if (parser->request_id >= N_ELEMENTS (self->requests)
+ || self->in_shutdown)
+ {
+ fcgi_muxer_send_end_request (self,
+ parser->request_id, 0, FCGI_OVERLOADED);
+ return true;
+ }
+
+ request = fcgi_request_new ();
+ request->muxer = self;
+ request->request_id = parser->request_id;
+ request->flags = flags;
+
+ self->requests[parser->request_id] = request;
+ self->active_requests++;
+ return true;
+}
+
+static bool
+fcgi_muxer_on_abort_request
+ (struct fcgi_muxer *self, const struct fcgi_parser *parser)
+{
+ struct fcgi_request *request = self->requests[parser->request_id];
+ if (parser->request_id == FCGI_NULL_REQUEST_ID || !request)
+ {
+ print_debug ("FastCGI: received %s for an unknown request",
+ STRINGIFY (FCGI_ABORT_REQUEST));
+ return true; // We might have just rejected it
+ }
+
+ return fcgi_request_finish (request);
+}
+
+static bool
+fcgi_muxer_on_params (struct fcgi_muxer *self, const struct fcgi_parser *parser)
+{
+ struct fcgi_request *request = self->requests[parser->request_id];
+ if (parser->request_id == FCGI_NULL_REQUEST_ID || !request)
+ {
+ print_debug ("FastCGI: received %s for an unknown request",
+ STRINGIFY (FCGI_PARAMS));
+ return true; // We might have just rejected it
+ }
+
+ // This may immediately finish and delete the request, but that's fine
+ return fcgi_request_push_params (request,
+ parser->content.str, parser->content.len);
+}
+
+static bool
+fcgi_muxer_on_stdin (struct fcgi_muxer *self, const struct fcgi_parser *parser)
+{
+ struct fcgi_request *request = self->requests[parser->request_id];
+ if (parser->request_id == FCGI_NULL_REQUEST_ID || !request)
+ {
+ print_debug ("FastCGI: received %s for an unknown request",
+ STRINGIFY (FCGI_STDIN));
+ return true; // We might have just rejected it
+ }
+
+ // At the end of the stream, a zero-length record is received
+ return fcgi_request_push_stdin (request,
+ parser->content.str, parser->content.len);
+}
+
+static bool
+fcgi_muxer_on_message (const struct fcgi_parser *parser, void *user_data)
+{
+ struct fcgi_muxer *self = user_data;
+
+ if (parser->version != FCGI_VERSION_1)
+ {
+ print_debug ("FastCGI: unsupported version %d", parser->version);
+ return false;
+ }
+
+ static const fcgi_muxer_handler_fn handlers[] =
+ {
+ [FCGI_GET_VALUES] = fcgi_muxer_on_get_values,
+ [FCGI_BEGIN_REQUEST] = fcgi_muxer_on_begin_request,
+ [FCGI_ABORT_REQUEST] = fcgi_muxer_on_abort_request,
+ [FCGI_PARAMS] = fcgi_muxer_on_params,
+ [FCGI_STDIN] = fcgi_muxer_on_stdin,
+ };
+
+ fcgi_muxer_handler_fn handler;
+ if (parser->type >= N_ELEMENTS (handlers)
+ || !(handler = handlers[parser->type]))
+ {
+ // Responding in this way even to application records, unspecified
+ uint8_t content[8] = { parser->type };
+ fcgi_muxer_send (self, FCGI_UNKNOWN_TYPE, parser->request_id,
+ content, sizeof content);
+ return true;
+ }
+
+ return handler (self, parser);
+}
+
+static void
+fcgi_muxer_init (struct fcgi_muxer *self)
+{
+ self->parser = fcgi_parser_make ();
+ self->parser.on_message = fcgi_muxer_on_message;
+ self->parser.user_data = self;
+}
+
+static void
+fcgi_muxer_free (struct fcgi_muxer *self)
+{
+ for (size_t i = 0; i < N_ELEMENTS (self->requests); i++)
+ {
+ if (!self->active_requests)
+ break;
+ if (self->requests[i])
+ {
+ fcgi_request_destroy (self->requests[i]);
+ self->active_requests--;
+ }
+ }
+
+ fcgi_parser_free (&self->parser);
+}
+
+static bool
+fcgi_muxer_push (struct fcgi_muxer *self, const void *data, size_t len)
+{
+ return fcgi_parser_push (&self->parser, data, len);
+}
+
+/// @}
+// --- WebSockets --------------------------------------------------------------
+/// @defgroup WebSockets
+/// @{
+
+// WebSockets aren't CGI-compatible, therefore we must handle the initial HTTP
+// handshake ourselves. Luckily it's not too much of a bother with http-parser.
+// Typically there will be a normal HTTP server in front of us, proxying the
+// requests based on the URI.
+
+enum ws_handler_state
+{
+ WS_HANDLER_CONNECTING, ///< Parsing HTTP
+ WS_HANDLER_OPEN, ///< Parsing WebSockets frames
+ WS_HANDLER_CLOSING, ///< Partial closure by us
+ WS_HANDLER_FLUSHING, ///< Just waiting for client EOF
+ WS_HANDLER_CLOSED ///< Dead, both sides closed
+};
+
+struct ws_handler
+{
+ enum ws_handler_state state; ///< State
+
+ // HTTP handshake:
+
+ http_parser hp; ///< HTTP parser
+ bool have_header_value; ///< Parsing header value or field?
+ struct str field; ///< Field part buffer
+ struct str value; ///< Value part buffer
+ struct str_map headers; ///< HTTP Headers
+ struct str url; ///< Request URL
+ ev_timer handshake_timeout_watcher; ///< Handshake timeout watcher
+
+ // WebSocket frame protocol:
+
+ struct ws_parser parser; ///< Protocol frame parser
+ bool expecting_continuation; ///< For non-control traffic
+
+ enum ws_opcode message_opcode; ///< Opcode for the current message
+ struct str message_data; ///< Concatenated message data
+
+ ev_timer ping_timer; ///< Ping timer
+ bool received_pong; ///< Received PONG since the last PING
+
+ ev_timer close_timeout_watcher; ///< Close timeout watcher
+
+ // Configuration:
+
+ unsigned handshake_timeout; ///< How long to wait for the handshake
+ unsigned close_timeout; ///< How long to wait for TCP close
+ unsigned ping_interval; ///< Ping interval in seconds
+ uint64_t max_payload_len; ///< Maximum length of any message
+
+ // Event callbacks:
+
+ // TODO: void (*on_handshake) (protocols) that will allow the user
+ // to choose any sub-protocol, if the client has provided any.
+ // This may render "on_connected" unnecessary.
+ // Should also enable failing the handshake.
+
+ /// Called after successfuly connecting (handshake complete)
+ bool (*on_connected) (struct ws_handler *);
+
+ /// Called upon reception of a single full message
+ bool (*on_message) (struct ws_handler *,
+ enum ws_opcode type, const void *data, size_t len);
+
+ /// The connection is about to close. @a close_code may, or may not, be one
+ /// of enum ws_status. The @a reason is never NULL.
+ void (*on_close) (struct ws_handler *, int close_code, const char *reason);
+
+ // Virtual method callbacks:
+
+ /// Write a chunk of data to the stream
+ void (*write_cb) (struct ws_handler *, const void *data, size_t len);
+
+ /// Close the connection. If @a half_close is false, you are allowed to
+ /// destroy the handler directly from within the callback.
+ void (*close_cb) (struct ws_handler *, bool half_close);
+};
+
+static void
+ws_handler_send_control (struct ws_handler *self,
+ enum ws_opcode opcode, const void *data, size_t len)
+{
+ if (len > WS_MAX_CONTROL_PAYLOAD_LEN)
+ {
+ print_debug ("truncating output control frame payload"
+ " from %zu to %zu bytes", len, (size_t) WS_MAX_CONTROL_PAYLOAD_LEN);
+ len = WS_MAX_CONTROL_PAYLOAD_LEN;
+ }
+
+ uint8_t header[2] = { 0x80 | (opcode & 0x0F), len };
+ self->write_cb (self, header, sizeof header);
+ self->write_cb (self, data, len);
+}
+
+static void
+ws_handler_close (struct ws_handler *self,
+ enum ws_status close_code, const char *reason, size_t len)
+{
+ hard_assert (self->state == WS_HANDLER_OPEN);
+
+ struct str payload = str_make ();
+ str_pack_u16 (&payload, close_code);
+ // XXX: maybe accept a null-terminated string on input? Has to be UTF-8 a/w
+ str_append_data (&payload, reason, len);
+ ws_handler_send_control (self, WS_OPCODE_CLOSE, payload.str, payload.len);
+ self->close_cb (self, true /* half_close */);
+
+ self->state = WS_HANDLER_CLOSING;
+ str_free (&payload);
+}
+
+static bool
+ws_handler_fail_connection (struct ws_handler *self, enum ws_status close_code)
+{
+ hard_assert (self->state == WS_HANDLER_OPEN
+ || self->state == WS_HANDLER_CLOSING);
+
+ if (self->state == WS_HANDLER_OPEN)
+ ws_handler_close (self, close_code, NULL, 0);
+
+ self->state = WS_HANDLER_FLUSHING;
+ if (self->on_close)
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "");
+
+ ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
+ ev_timer_set (&self->close_timeout_watcher, self->close_timeout, 0.);
+ ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher);
+ return false;
+}
+
+// TODO: add support for fragmented responses
+static void
+ws_handler_send_frame (struct ws_handler *self,
+ enum ws_opcode opcode, const void *data, size_t len)
+{
+ if (!soft_assert (self->state == WS_HANDLER_OPEN))
+ return;
+
+ struct str header = str_make ();
+ str_pack_u8 (&header, 0x80 | (opcode & 0x0F));
+
+ if (len > UINT16_MAX)
+ {
+ str_pack_u8 (&header, 127);
+ str_pack_u64 (&header, len);
+ }
+ else if (len > 125)
+ {
+ str_pack_u8 (&header, 126);
+ str_pack_u16 (&header, len);
+ }
+ else
+ str_pack_u8 (&header, len);
+
+ self->write_cb (self, header.str, header.len);
+ self->write_cb (self, data, len);
+ str_free (&header);
+}
+
+static bool
+ws_handler_on_frame_header (void *user_data, const struct ws_parser *parser)
+{
+ struct ws_handler *self = user_data;
+
+ // Note that we aren't expected to send any close frame before closing the
+ // connection when the frame is unmasked
+
+ if (parser->reserved_1 || parser->reserved_2 || parser->reserved_3
+ || !parser->is_masked // client -> server payload must be masked
+ || (ws_is_control_frame (parser->opcode) &&
+ (!parser->is_fin || parser->payload_len > WS_MAX_CONTROL_PAYLOAD_LEN))
+ || (!ws_is_control_frame (parser->opcode) &&
+ (self->expecting_continuation && parser->opcode != WS_OPCODE_CONT))
+ || parser->payload_len >= 0x8000000000000000ULL)
+ return ws_handler_fail_connection (self, WS_STATUS_PROTOCOL_ERROR);
+
+ if (parser->payload_len > self->max_payload_len
+ || (self->expecting_continuation &&
+ self->message_data.len + parser->payload_len > self->max_payload_len))
+ return ws_handler_fail_connection (self, WS_STATUS_MESSAGE_TOO_BIG);
+ return true;
+}
+
+static bool
+ws_handler_on_control_close
+ (struct ws_handler *self, const struct ws_parser *parser)
+{
+ hard_assert (self->state == WS_HANDLER_OPEN
+ || self->state == WS_HANDLER_CLOSING);
+ struct msg_unpacker unpacker =
+ msg_unpacker_make (parser->input.str, parser->payload_len);
+
+ char *reason = NULL;
+ uint16_t close_code = WS_STATUS_NO_STATUS_RECEIVED;
+ if (parser->payload_len >= 2)
+ {
+ (void) msg_unpacker_u16 (&unpacker, &close_code);
+ reason = xstrndup (parser->input.str + 2, parser->payload_len - 2);
+ }
+ else
+ reason = xstrdup ("");
+
+ if (close_code < 1000 || close_code > 4999)
+ // XXX: invalid close code: maybe we should fail the connection instead
+ close_code = WS_STATUS_PROTOCOL_ERROR;
+
+ if (self->state == WS_HANDLER_OPEN)
+ {
+ // Close initiated by the client
+ // FIXME: not sending the potentially different close_code
+ ws_handler_send_control (self, WS_OPCODE_CLOSE,
+ parser->input.str, parser->payload_len);
+
+ self->state = WS_HANDLER_FLUSHING;
+ if (self->on_close)
+ self->on_close (self, close_code, reason);
+ }
+ else
+ self->state = WS_HANDLER_FLUSHING;
+
+ free (reason);
+
+ ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
+ ev_timer_set (&self->close_timeout_watcher, self->close_timeout, 0.);
+ ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher);
+ return true;
+}
+
+static bool
+ws_handler_on_control_frame
+ (struct ws_handler *self, const struct ws_parser *parser)
+{
+ switch (parser->opcode)
+ {
+ case WS_OPCODE_CLOSE:
+ return ws_handler_on_control_close (self, parser);
+ case WS_OPCODE_PING:
+ ws_handler_send_control (self, WS_OPCODE_PONG,
+ parser->input.str, parser->payload_len);
+ break;
+ case WS_OPCODE_PONG:
+ // TODO: check the payload
+ self->received_pong = true;
+ break;
+ default:
+ // Unknown control frame
+ return ws_handler_fail_connection (self, WS_STATUS_PROTOCOL_ERROR);
+ }
+ return true;
+}
+
+static bool
+ws_handler_on_frame (void *user_data, const struct ws_parser *parser)
+{
+ struct ws_handler *self = user_data;
+ if (ws_is_control_frame (parser->opcode))
+ return ws_handler_on_control_frame (self, parser);
+ if (!self->expecting_continuation)
+ self->message_opcode = parser->opcode;
+
+ str_append_data (&self->message_data,
+ parser->input.str, parser->payload_len);
+ if ((self->expecting_continuation = !parser->is_fin))
+ return true;
+
+ if (self->message_opcode == WS_OPCODE_TEXT
+ && !utf8_validate (self->message_data.str, self->message_data.len))
+ {
+ return ws_handler_fail_connection
+ (self, WS_STATUS_INVALID_PAYLOAD_DATA);
+ }
+
+ bool result = true;
+ if (self->on_message)
+ result = self->on_message (self, self->message_opcode,
+ self->message_data.str, self->message_data.len);
+ str_reset (&self->message_data);
+ // TODO: if (!result), either replace this with a state check,
+ // or make sure to change the state
+ return result;
+}
+
+static void
+ws_handler_on_ping_timer (EV_P_ ev_timer *watcher, int revents)
+{
+ (void) loop;
+ (void) revents;
+
+ struct ws_handler *self = watcher->data;
+ if (!self->received_pong)
+ ws_handler_fail_connection (self, 4000);
+ else
+ {
+ // TODO: be an annoying server and send a nonce in the data
+ ws_handler_send_control (self, WS_OPCODE_PING, NULL, 0);
+ ev_timer_again (EV_A_ watcher);
+ }
+}
+
+static void
+ws_handler_on_close_timeout (EV_P_ ev_timer *watcher, int revents)
+{
+ (void) loop;
+ (void) revents;
+ struct ws_handler *self = watcher->data;
+
+ hard_assert (self->state == WS_HANDLER_OPEN
+ || self->state == WS_HANDLER_CLOSING);
+
+ if (self->state == WS_HANDLER_CLOSING
+ && self->on_close)
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "close timeout");
+
+ self->state = WS_HANDLER_CLOSED;
+ self->close_cb (self, false /* half_close */);
+}
+
+static void
+ws_handler_on_handshake_timeout (EV_P_ ev_timer *watcher, int revents)
+{
+ (void) loop;
+ (void) revents;
+ struct ws_handler *self = watcher->data;
+
+ // XXX: this is a no-op, since this currently doesn't even call shutdown
+ // immediately but postpones it until later
+ self->close_cb (self, true /* half_close */);
+ self->state = WS_HANDLER_FLUSHING;
+
+ if (self->on_close)
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "handshake timeout");
+
+ self->state = WS_HANDLER_CLOSED;
+ self->close_cb (self, false /* half_close */);
+}
+
+static void
+ws_handler_init (struct ws_handler *self)
+{
+ memset (self, 0, sizeof *self);
+
+ self->state = WS_HANDLER_CONNECTING;
+
+ http_parser_init (&self->hp, HTTP_REQUEST);
+ self->hp.data = self;
+ self->field = str_make ();
+ self->value = str_make ();
+ self->headers = str_map_make (free);
+ self->headers.key_xfrm = tolower_ascii_strxfrm;
+ self->url = str_make ();
+ ev_timer_init (&self->handshake_timeout_watcher,
+ ws_handler_on_handshake_timeout, 0., 0.);
+ self->handshake_timeout_watcher.data = self;
+
+ self->parser = ws_parser_make ();
+ self->parser.on_frame_header = ws_handler_on_frame_header;
+ self->parser.on_frame = ws_handler_on_frame;
+ self->parser.user_data = self;
+ self->message_data = str_make ();
+
+ ev_timer_init (&self->ping_timer,
+ ws_handler_on_ping_timer, 0., 0.);
+ self->ping_timer.data = self;
+ ev_timer_init (&self->close_timeout_watcher,
+ ws_handler_on_close_timeout, 0., 0.);
+ self->ping_timer.data = self;
+ // So that the first ping timer doesn't timeout the connection
+ self->received_pong = true;
+
+ self->handshake_timeout = self->close_timeout = self->ping_interval = 60;
+ // This is still ridiculously high. Note that the most significant bit
+ // must always be zero, i.e. the protocol maximum is 0x7FFF FFFF FFFF FFFF.
+ self->max_payload_len = UINT32_MAX;
+}
+
+/// Stop all timers, not going to use the handler anymore
+static void
+ws_handler_stop (struct ws_handler *self)
+{
+ ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
+ ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
+ ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher);
+}
+
+static void
+ws_handler_free (struct ws_handler *self)
+{
+ ws_handler_stop (self);
+
+ str_free (&self->field);
+ str_free (&self->value);
+ str_map_free (&self->headers);
+ str_free (&self->url);
+
+ ws_parser_free (&self->parser);
+ str_free (&self->message_data);
+}
+
+static bool
+ws_handler_header_field_is_a_list (const char *name)
+{
+ // This must contain all header fields we use for anything
+ static const char *concatenable[] =
+ { SEC_WS_PROTOCOL, SEC_WS_EXTENSIONS, "Connection", "Upgrade" };
+
+ for (size_t i = 0; i < N_ELEMENTS (concatenable); i++)
+ if (!strcasecmp_ascii (name, concatenable[i]))
+ return true;
+ return false;
+}
+
+static void
+ws_handler_on_header_read (struct ws_handler *self)
+{
+ // The HTTP parser unfolds values and removes preceding whitespace, but
+ // otherwise doesn't touch the values or the following whitespace.
+
+ // RFC 7230 states that trailing whitespace is not part of a field value
+ char *value = self->field.str;
+ size_t len = self->field.len;
+ while (len--)
+ if (value[len] == '\t' || value[len] == ' ')
+ value[len] = '\0';
+ else
+ break;
+ self->field.len = len;
+
+ const char *field = self->field.str;
+ const char *current = str_map_find (&self->headers, field);
+ if (ws_handler_header_field_is_a_list (field) && current)
+ str_map_set (&self->headers, field,
+ xstrdup_printf ("%s, %s", current, self->value.str));
+ else
+ // If the field cannot be concatenated, just overwrite the last value.
+ // Maybe we should issue a warning or something.
+ str_map_set (&self->headers, field, xstrdup (self->value.str));
+}
+
+static int
+ws_handler_on_header_field (http_parser *parser, const char *at, size_t len)
+{
+ struct ws_handler *self = parser->data;
+ if (self->have_header_value)
+ {
+ ws_handler_on_header_read (self);
+ str_reset (&self->field);
+ str_reset (&self->value);
+ }
+ str_append_data (&self->field, at, len);
+ self->have_header_value = false;
+ return 0;
+}
+
+static int
+ws_handler_on_header_value (http_parser *parser, const char *at, size_t len)
+{
+ struct ws_handler *self = parser->data;
+ str_append_data (&self->value, at, len);
+ self->have_header_value = true;
+ return 0;
+}
+
+static int
+ws_handler_on_headers_complete (http_parser *parser)
+{
+ struct ws_handler *self = parser->data;
+ if (self->have_header_value)
+ ws_handler_on_header_read (self);
+
+ // We strictly require a protocol upgrade
+ if (!parser->upgrade)
+ return 2;
+
+ return 0;
+}
+
+static int
+ws_handler_on_url (http_parser *parser, const char *at, size_t len)
+{
+ struct ws_handler *self = parser->data;
+ str_append_data (&self->value, at, len);
+ return 0;
+}
+
+#define HTTP_101_SWITCHING_PROTOCOLS "101 Switching Protocols"
+#define HTTP_400_BAD_REQUEST "400 Bad Request"
+#define HTTP_405_METHOD_NOT_ALLOWED "405 Method Not Allowed"
+#define HTTP_417_EXPECTATION_FAILED "407 Expectation Failed"
+#define HTTP_426_UPGRADE_REQUIRED "426 Upgrade Required"
+#define HTTP_505_VERSION_NOT_SUPPORTED "505 HTTP Version Not Supported"
+
+static void
+ws_handler_http_responsev (struct ws_handler *self,
+ const char *status, char *const *fields)
+{
+ hard_assert (status != NULL);
+
+ struct str response = str_make ();
+ str_append_printf (&response, "HTTP/1.1 %s\r\n", status);
+
+ while (*fields)
+ str_append_printf (&response, "%s\r\n", *fields++);
+
+ time_t now = time (NULL);
+ struct tm ts;
+ gmtime_r (&now, &ts);
+
+ // See RFC 7231, 7.1.1.2. Date
+ const char *dow[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+ const char *moy[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+ str_append_printf (&response,
+ "Date: %s, %02d %s %04d %02d:%02d:%02d GMT\r\n",
+ dow[ts.tm_wday], ts.tm_mday, moy[ts.tm_mon], ts.tm_year + 1900,
+ ts.tm_hour, ts.tm_min, ts.tm_sec);
+
+ str_append (&response, "Server: "
+ PROGRAM_NAME "/" PROGRAM_VERSION "\r\n\r\n");
+ self->write_cb (self, response.str, response.len);
+ str_free (&response);
+}
+
+static bool
+ws_handler_fail_handshake (struct ws_handler *self, const char *status, ...)
+{
+ va_list ap;
+ va_start (ap, status);
+
+ const char *s;
+ struct strv v = strv_make ();
+ while ((s = va_arg (ap, const char *)))
+ strv_append (&v, s);
+
+ va_end (ap);
+ ws_handler_http_responsev (self, status, v.vector);
+ strv_free (&v);
+
+ self->close_cb (self, true /* half_close */);
+ self->state = WS_HANDLER_FLUSHING;
+
+ if (self->on_close)
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, status);
+ return false;
+}
+
+#define FAIL_HANDSHAKE(...) \
+ return ws_handler_fail_handshake (self, __VA_ARGS__, NULL)
+
+static bool
+ws_handler_finish_handshake (struct ws_handler *self)
+{
+ if (self->hp.method != HTTP_GET)
+ FAIL_HANDSHAKE (HTTP_405_METHOD_NOT_ALLOWED, "Allow: GET");
+
+ // Technically, it must be /at least/ 1.1 but no other 1.x version of HTTP
+ // is going to happen and 2.x is entirely incompatible
+ // XXX: we probably shouldn't use 505 to reject the minor version but w/e
+ if (self->hp.http_major != 1 || self->hp.http_minor != 1)
+ FAIL_HANDSHAKE (HTTP_505_VERSION_NOT_SUPPORTED);
+
+ // Your expectations are way too high
+ if (str_map_find (&self->headers, "Expect"))
+ FAIL_HANDSHAKE (HTTP_417_EXPECTATION_FAILED);
+
+ // Reject URLs specifying the schema and host; we're not parsing that
+ // TODO: actually do parse this and let our user decide if it matches
+ struct http_parser_url url;
+ if (http_parser_parse_url (self->url.str, self->url.len, false, &url)
+ || (url.field_set & (1 << UF_SCHEMA | 1 << UF_HOST | 1 << UF_PORT))
+ || !str_map_find (&self->headers, "Host"))
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
+
+ const char *connection = str_map_find (&self->headers, "Connection");
+ if (!connection || strcasecmp_ascii (connection, "Upgrade"))
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
+
+ // Check if we can actually upgrade the protocol to WebSockets
+ const char *upgrade = str_map_find (&self->headers, "Upgrade");
+ struct http_protocol *offered_upgrades = NULL;
+ bool can_upgrade = false;
+ if (upgrade && http_parse_upgrade (upgrade, &offered_upgrades))
+ // Case-insensitive according to RFC 6455; neither RFC 2616 nor 7230
+ // say anything at all about case-sensitivity for this field
+ LIST_FOR_EACH (struct http_protocol, iter, offered_upgrades)
+ {
+ if (!iter->version && !strcasecmp_ascii (iter->name, "websocket"))
+ can_upgrade = true;
+ http_protocol_destroy (iter);
+ }
+ if (!can_upgrade)
+ FAIL_HANDSHAKE (HTTP_426_UPGRADE_REQUIRED,
+ "Upgrade: websocket", SEC_WS_VERSION ": 13");
+
+ // Okay, we're finally past the basic HTTP/1.1 stuff
+ const char *key = str_map_find (&self->headers, SEC_WS_KEY);
+ const char *version = str_map_find (&self->headers, SEC_WS_VERSION);
+/*
+ const char *protocol = str_map_find (&self->headers, SEC_WS_PROTOCOL);
+ const char *extensions = str_map_find (&self->headers, SEC_WS_EXTENSIONS);
+*/
+
+ if (!version)
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
+ if (strcmp (version, "13"))
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, SEC_WS_VERSION ": 13");
+
+ struct str tmp = str_make ();
+ bool key_is_valid = key
+ && base64_decode (key, false, &tmp) && tmp.len == 16;
+ str_free (&tmp);
+ if (!key_is_valid)
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
+
+ struct strv fields = strv_make ();
+ strv_append_args (&fields,
+ "Upgrade: websocket",
+ "Connection: Upgrade",
+ NULL);
+
+ char *response_key = ws_encode_response_key (key);
+ strv_append_owned (&fields,
+ xstrdup_printf (SEC_WS_ACCEPT ": %s", response_key));
+ free (response_key);
+
+ // TODO: make it possible to choose Sec-Websocket-{Extensions,Protocol}
+
+ ws_handler_http_responsev (self,
+ HTTP_101_SWITCHING_PROTOCOLS, fields.vector);
+
+ strv_free (&fields);
+
+ self->state = WS_HANDLER_OPEN;
+ ev_timer_init (&self->ping_timer, ws_handler_on_ping_timer,
+ self->ping_interval, 0);
+ ev_timer_start (EV_DEFAULT_ &self->ping_timer);
+ return true;
+}
+
+/// Tells the handler that the TCP connection has been established so it can
+/// timeout when the client handshake doesn't arrive soon enough
+static void
+ws_handler_start (struct ws_handler *self)
+{
+ hard_assert (self->state == WS_HANDLER_CONNECTING);
+
+ ev_timer_set (&self->handshake_timeout_watcher,
+ self->handshake_timeout, 0.);
+ ev_timer_start (EV_DEFAULT_ &self->handshake_timeout_watcher);
+}
+
+// The client should normally never close the connection, assume that it's
+// either received an EOF from our side, or that it doesn't care about our data
+// anymore, having called close() already
+static bool
+ws_handler_push_eof (struct ws_handler *self)
+{
+ switch (self->state)
+ {
+ case WS_HANDLER_CONNECTING:
+ ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
+
+ self->state = WS_HANDLER_FLUSHING;
+ if (self->on_close)
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "unexpected EOF");
+ break;
+ case WS_HANDLER_OPEN:
+ ev_timer_stop (EV_DEFAULT_ &self->ping_timer);
+ // Fall-through
+ case WS_HANDLER_CLOSING:
+ self->state = WS_HANDLER_CLOSED;
+ if (self->on_close)
+ self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "");
+ // Fall-through
+ case WS_HANDLER_FLUSHING:
+ ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher);
+ break;
+ default:
+ soft_assert(self->state != WS_HANDLER_CLOSED);
+ }
+ self->state = WS_HANDLER_CLOSED;
+ return false;
+}
+
+/// Push data to the WebSocket handler. "len == 0" means EOF.
+/// You are expected to close the connection and dispose of the handler
+/// when the function returns false.
+static bool
+ws_handler_push (struct ws_handler *self, const void *data, size_t len)
+{
+ if (!len)
+ return ws_handler_push_eof (self);
+
+ if (self->state == WS_HANDLER_FLUSHING)
+ // We're waiting for an EOF from the client, must not process data
+ return true;
+
+ if (self->state != WS_HANDLER_CONNECTING)
+ return soft_assert (self->state != WS_HANDLER_CLOSED)
+ && ws_parser_push (&self->parser, data, len);
+
+ // The handshake hasn't been done yet, process HTTP headers
+ static const http_parser_settings http_settings =
+ {
+ .on_header_field = ws_handler_on_header_field,
+ .on_header_value = ws_handler_on_header_value,
+ .on_headers_complete = ws_handler_on_headers_complete,
+ .on_url = ws_handler_on_url,
+ };
+
+ size_t n_parsed =
+ http_parser_execute (&self->hp, &http_settings, data, len);
+
+ if (self->hp.upgrade)
+ {
+ ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
+
+ // The handshake hasn't been finished, yet there is more data
+ // to be processed after the headers already
+ if (len - n_parsed)
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
+
+ if (!ws_handler_finish_handshake (self))
+ return false;
+ if (self->on_connected)
+ return self->on_connected (self);
+ return true;
+ }
+
+ enum http_errno err = HTTP_PARSER_ERRNO (&self->hp);
+ if (n_parsed != len || err != HPE_OK)
+ {
+ ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
+
+ if (err == HPE_CB_headers_complete)
+ print_debug ("WS handshake failed: %s", "missing `Upgrade' field");
+ else
+ print_debug ("WS handshake failed: %s",
+ http_errno_description (err));
+
+ FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
+ }
+ return true;
+}
+
+/// @}
+// --- Server ------------------------------------------------------------------
+
+static struct simple_config_item g_config_table[] =
+{
+ { "bind_host", NULL, "Address of the server" },
+ { "port_fastcgi", "9000", "Port to bind for FastCGI" },
+ { "port_scgi", NULL, "Port to bind for SCGI" },
+ { "port_ws", NULL, "Port to bind for WebSockets" },
+ { "pid_file", NULL, "Full path for the PID file" },
+ // XXX: here belongs something like a web SPA that interfaces with us
+ { "static_root", NULL, "The root for static content" },
+ { NULL, NULL, NULL }
+};
+
+struct server_context
+{
+ ev_signal sigterm_watcher; ///< Got SIGTERM
+ ev_signal sigint_watcher; ///< Got SIGINT
+ ev_timer quit_timeout_watcher; ///< Quit timeout watcher
+ bool quitting; ///< User requested quitting
+
+ struct listener *listeners; ///< Listeners
+ size_t n_listeners; ///< Number of listening sockets
+
+ struct client *clients; ///< Clients
+ unsigned n_clients; ///< Current number of connections
+
+ struct request_handler *handlers; ///< Request handlers
+ struct str_map config; ///< Server configuration
+};
+
+static void initiate_quit (struct server_context *self);
+static void try_finish_quit (struct server_context *self);
+static void on_quit_timeout (EV_P_ ev_timer *watcher, int revents);
+static void close_listeners (struct server_context *self);
+
+static void
+server_context_init (struct server_context *self)
+{
+ memset (self, 0, sizeof *self);
+
+ self->config = str_map_make (NULL);
+ simple_config_load_defaults (&self->config, g_config_table);
+ ev_timer_init (&self->quit_timeout_watcher, on_quit_timeout, 3., 0.);
+ self->quit_timeout_watcher.data = self;
+}
+
+static void
+server_context_free (struct server_context *self)
+{
+ // We really shouldn't attempt a quit without closing the clients first
+ soft_assert (!self->clients);
+
+ close_listeners (self);
+ free (self->listeners);
+
+ str_map_free (&self->config);
+}
+
+// --- JSON-RPC ----------------------------------------------------------------
+/// @defgroup JSON-RPC
+/// @{
+
+#define JSON_RPC_ERROR_TABLE(XX) \
+ XX (-32700, PARSE_ERROR, "Parse error") \
+ XX (-32600, INVALID_REQUEST, "Invalid Request") \
+ XX (-32601, METHOD_NOT_FOUND, "Method not found") \
+ XX (-32602, INVALID_PARAMS, "Invalid params") \
+ XX (-32603, INTERNAL_ERROR, "Internal error")
+
+enum json_rpc_error
+{
+#define XX(code, name, message) JSON_RPC_ERROR_ ## name,
+ JSON_RPC_ERROR_TABLE (XX)
+#undef XX
+ JSON_RPC_ERROR_COUNT
+};
+
+static json_t *
+json_rpc_error (enum json_rpc_error id, json_t *data)
+{
+#define XX(code, name, message) { code, message },
+ static const struct json_rpc_error
+ {
+ int code;
+ const char *message;
+ }
+ errors[JSON_RPC_ERROR_COUNT] =
+ {
+ JSON_RPC_ERROR_TABLE (XX)
+ };
+#undef XX
+
+ json_t *error = json_object ();
+ json_object_set_new (error, "code", json_integer (errors[id].code));
+ json_object_set_new (error, "message", json_string (errors[id].message));
+
+ if (data)
+ json_object_set_new (error, "data", data);
+
+ return error;
+}
+
+static json_t *
+json_rpc_response (json_t *id, json_t *result, json_t *error)
+{
+ json_t *x = json_object ();
+ json_object_set_new (x, "jsonrpc", json_string ("2.0"));
+ json_object_set_new (x, "id", id ? id : json_null ());
+ if (result) json_object_set_new (x, "result", result);
+ if (error) json_object_set_new (x, "error", error);
+ return x;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static bool
+validate_json_rpc_content_type (const char *content_type)
+{
+ char *type = NULL;
+ char *subtype = NULL;
+
+ struct str_map parameters = str_map_make (free);
+ parameters.key_xfrm = tolower_ascii_strxfrm;
+
+ bool result = http_parse_media_type
+ (content_type, &type, &subtype, ¶meters);
+ if (!result)
+ goto end;
+
+ if (strcasecmp_ascii (type, "application")
+ || (strcasecmp_ascii (subtype, "json") &&
+ strcasecmp_ascii (subtype, "json-rpc" /* obsolete */)))
+ result = false;
+
+ const char *charset = str_map_find (¶meters, "charset");
+ if (charset && strcasecmp_ascii (charset, "UTF-8"))
+ result = false;
+
+ // Currently ignoring all unknown parametrs
+
+end:
+ free (type);
+ free (subtype);
+ str_map_free (¶meters);
+ return result;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+typedef json_t *(*json_rpc_handler_fn) (struct server_context *, json_t *);
+
+struct json_rpc_handler_info
+{
+ const char *method_name; ///< JSON-RPC method name
+ json_rpc_handler_fn handler; ///< Method handler
+};
+
+static int
+json_rpc_handler_info_cmp (const void *first, const void *second)
+{
+ return strcmp (((struct json_rpc_handler_info *) first)->method_name,
+ ((struct json_rpc_handler_info *) second)->method_name);
+}
+
+// TODO: a method that sends a response after a certain number of seconds.
+// This has to be owned by the server context as a background job that
+// removes itself upon completion.
+
+static json_t *
+json_rpc_ping (struct server_context *ctx, json_t *params)
+{
+ (void) ctx;
+ (void) params;
+
+ return json_rpc_response (NULL, json_string ("pong"), NULL);
+}
+
+static json_t *
+process_json_rpc_request (struct server_context *ctx, json_t *request)
+{
+ // A list of all available methods; this list has to be ordered.
+ // Eventually it might be better to move this into a map in the context.
+ static struct json_rpc_handler_info handlers[] =
+ {
+ { "ping", json_rpc_ping },
+ };
+
+ if (!json_is_object (request))
+ return json_rpc_response (NULL, NULL,
+ json_rpc_error (JSON_RPC_ERROR_INVALID_REQUEST, NULL));
+
+ json_t *v = json_object_get (request, "jsonrpc");
+ json_t *m = json_object_get (request, "method");
+ json_t *params = json_object_get (request, "params");
+ json_t *id = json_object_get (request, "id");
+
+ const char *version;
+ const char *method;
+
+ bool ok = true;
+ ok &= v && (version = json_string_value (v)) && !strcmp (version, "2.0");
+ ok &= m && (method = json_string_value (m));
+ ok &= !params || json_is_array (params) || json_is_object (params);
+ ok &= !id || json_is_null (id) ||
+ json_is_string (id) || json_is_number (id);
+ if (!ok)
+ return json_rpc_response (id, NULL,
+ json_rpc_error (JSON_RPC_ERROR_INVALID_REQUEST, NULL));
+
+ struct json_rpc_handler_info key = { .method_name = method };
+ struct json_rpc_handler_info *handler = bsearch (&key, handlers,
+ N_ELEMENTS (handlers), sizeof key, json_rpc_handler_info_cmp);
+ if (!handler)
+ return json_rpc_response (id, NULL,
+ json_rpc_error (JSON_RPC_ERROR_METHOD_NOT_FOUND, NULL));
+
+ json_t *response = handler->handler (ctx, params);
+ if (id)
+ return response;
+
+ // Notifications don't get responses
+ json_decref (response);
+ return NULL;
+}
+
+static void
+flush_json (json_t *json, struct str *output)
+{
+ char *utf8 = json_dumps (json, JSON_ENCODE_ANY);
+ str_append (output, utf8);
+ free (utf8);
+ json_decref (json);
+}
+
+static void
+process_json_rpc (struct server_context *ctx,
+ const void *data, size_t len, struct str *output)
+{
+
+ json_error_t e;
+ json_t *request;
+ if (!(request = json_loadb (data, len, JSON_DECODE_ANY, &e)))
+ {
+ flush_json (json_rpc_response (NULL, NULL,
+ json_rpc_error (JSON_RPC_ERROR_PARSE_ERROR, NULL)),
+ output);
+ return;
+ }
+
+ if (json_is_array (request))
+ {
+ if (!json_array_size (request))
+ {
+ flush_json (json_rpc_response (NULL, NULL,
+ json_rpc_error (JSON_RPC_ERROR_INVALID_REQUEST, NULL)),
+ output);
+ return;
+ }
+
+ json_t *response = json_array ();
+ json_t *iter;
+ size_t i;
+
+ json_array_foreach (request, i, iter)
+ {
+ json_t *result = process_json_rpc_request (ctx, iter);
+ if (result)
+ json_array_append_new (response, result);
+ }
+
+ if (json_array_size (response))
+ flush_json (response, output);
+ else
+ json_decref (response);
+ }
+ else
+ {
+ json_t *result = process_json_rpc_request (ctx, request);
+ if (result)
+ flush_json (result, output);
+ }
+}
+
+/// @}
+// --- Requests ----------------------------------------------------------------
+/// @defgroup Requests
+/// @{
+
+/// A generic CGI request abstraction, writing data indirectly through callbacks
+struct request
+{
+ struct server_context *ctx; ///< Server context
+
+ struct request_handler *handler; ///< Assigned request handler
+ void *handler_data; ///< User data for the handler
+
+ /// Callback to write some CGI response data to the output
+ void (*write_cb) (struct request *, const void *data, size_t len);
+
+ /// Callback to close the CGI response, simulates end of program execution.
+ /// CALLING THIS MAY CAUSE THE REQUEST TO BE DESTROYED.
+ void (*close_cb) (struct request *);
+};
+
+/// An interface to detect and handle specific kinds of CGI requests.
+/// The server walks through a list of them until it finds one that can serve
+/// a particular request. If unsuccessful, the remote client gets a 404
+/// (the default handling).
+struct request_handler
+{
+ LIST_HEADER (struct request_handler)
+
+ /// Install ourselves as the handler for the request, if applicable.
+ /// Sets @a continue_ to false if further processing should be stopped,
+ /// meaning the request has already been handled.
+ bool (*try_handle) (struct request *request,
+ struct str_map *headers, bool *continue_);
+
+ /// Handle incoming data. "len == 0" means EOF.
+ /// Returns false if there is no more processing to be done.
+ // FIXME: the EOF may or may not be delivered when request is cut short,
+ // we should fix FastCGI not to deliver it on CONTENT_LENGTH mismatch
+ bool (*push_cb) (struct request *request, const void *data, size_t len);
+
+ /// Destroy the handler's data stored in the request object
+ void (*finalize_cb) (struct request *request);
+};
+
+static void
+request_init (struct request *self)
+{
+ memset (self, 0, sizeof *self);
+}
+
+static void
+request_free (struct request *self)
+{
+ if (self->handler)
+ self->handler->finalize_cb (self);
+}
+
+/// Write request CGI response data, intended for use by request handlers
+static void
+request_write (struct request *self, const void *data, size_t len)
+{
+ self->write_cb (self, data, len);
+}
+
+/// This function is only intended to be run from asynchronous event handlers
+/// such as timers, not as a direct result of starting the request or receiving
+/// request data. CALLING THIS MAY CAUSE THE REQUEST TO BE DESTROYED.
+static void
+request_finish (struct request *self)
+{
+ self->close_cb (self);
+}
+
+/// Starts processing a request. Returns false if no further action is to be
+/// done and the request should be finished.
+static bool
+request_start (struct request *self, struct str_map *headers)
+{
+ // XXX: it feels like this should rather be two steps:
+ // bool (*can_handle) (request *, headers)
+ // ... install the handler ...
+ // bool (*handle) (request *)
+ //
+ // However that might cause some stuff to be done twice.
+ //
+ // Another way we could get rid of the continue_ argument is via adding
+ // some way of marking the request as finished from within the handler.
+
+ if (g_debug_mode)
+ {
+ struct str_map_iter iter = str_map_iter_make (headers);
+ const char *value;
+ while ((value = str_map_iter_next (&iter)))
+ print_debug ("%s: %s", iter.link->key, value);
+ print_debug ("--");
+ }
+
+ bool continue_ = true;
+ LIST_FOR_EACH (struct request_handler, handler, self->ctx->handlers)
+ if (handler->try_handle (self, headers, &continue_))
+ {
+ self->handler = handler;
+ return continue_;
+ }
+
+ // Unable to serve the request
+ struct str response = str_make ();
+ str_append (&response, "Status: 404 Not Found\n");
+ str_append (&response, "Content-Type: text/plain\n\n");
+ request_write (self, response.str, response.len);
+ str_free (&response);
+ return false;
+}
+
+static bool
+request_push (struct request *self, const void *data, size_t len)
+{
+ if (!soft_assert (self->handler))
+ // No handler, nothing to do with any data
+ return false;
+
+ return self->handler->push_cb (self, data, len);
+}
+
+/// @}
+// --- Requests handlers -------------------------------------------------------
+
+static bool
+request_handler_json_rpc_try_handle
+ (struct request *request, struct str_map *headers, bool *continue_)
+{
+ const char *content_type = str_map_find (headers, "CONTENT_TYPE");
+ const char *method = str_map_find (headers, "REQUEST_METHOD");
+
+ if (!method || strcmp (method, "POST")
+ || !content_type || !validate_json_rpc_content_type (content_type))
+ return false;
+
+ struct str *buf = xcalloc (1, sizeof *buf);
+ *buf = str_make ();
+
+ request->handler_data = buf;
+ *continue_ = true;
+ return true;
+}
+
+static bool
+request_handler_json_rpc_push
+ (struct request *request, const void *data, size_t len)
+{
+ struct str *buf = request->handler_data;
+ if (len)
+ {
+ str_append_data (buf, data, len);
+ return true;
+ }
+
+ // TODO: check buf.len against CONTENT_LENGTH; if it's less, then the
+ // client hasn't been successful in transferring all of its data.
+ // See also comment on request_handler::push_cb.
+
+ struct str response = str_make ();
+ str_append (&response, "Status: 200 OK\n");
+ str_append_printf (&response, "Content-Type: %s\n\n", "application/json");
+ process_json_rpc (request->ctx, buf->str, buf->len, &response);
+ request_write (request, response.str, response.len);
+ str_free (&response);
+ return false;
+}
+
+static void
+request_handler_json_rpc_finalize (struct request *request)
+{
+ struct str *buf = request->handler_data;
+ str_free (buf);
+ free (buf);
+
+ request->handler_data = NULL;
+}
+
+struct request_handler g_request_handler_json_rpc =
+{
+ .try_handle = request_handler_json_rpc_try_handle,
+ .push_cb = request_handler_json_rpc_push,
+ .finalize_cb = request_handler_json_rpc_finalize,
+};
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static char *
+canonicalize_url_path (const char *path)
+{
+ // XXX: this strips any slashes at the end
+ struct strv v = strv_make ();
+ cstr_split (path, "/", true, &v);
+
+ struct strv canonical = strv_make ();
+
+ // So that the joined path always begins with a slash
+ strv_append (&canonical, "");
+
+ for (size_t i = 0; i < v.len; i++)
+ {
+ const char *dir = v.vector[i];
+ if (!strcmp (dir, "."))
+ continue;
+
+ if (strcmp (dir, ".."))
+ strv_append (&canonical, dir);
+ else if (canonical.len > 1)
+ // ".." never goes above the root
+ strv_remove (&canonical, canonical.len - 1);
+ }
+ strv_free (&v);
+
+ char *joined = strv_join (&canonical, "/");
+ strv_free (&canonical);
+ return joined;
+}
+
+static char *
+detect_magic (const void *data, size_t len)
+{
+ magic_t cookie;
+ char *mime_type = NULL;
+
+ if (!(cookie = magic_open (MAGIC_MIME)))
+ return NULL;
+
+ const char *magic = NULL;
+ if (!magic_load (cookie, NULL)
+ && (magic = magic_buffer (cookie, data, len)))
+ mime_type = xstrdup (magic);
+ else
+ print_debug ("MIME type detection failed: %s", magic_error (cookie));
+
+ magic_close (cookie);
+ return mime_type;
+}
+
+static bool
+request_handler_static_try_handle
+ (struct request *request, struct str_map *headers, bool *continue_)
+{
+ // Serving static files is actually quite complicated as it turns out;
+ // but this is only meant to serve a few tiny text files
+
+ struct server_context *ctx = request->ctx;
+ const char *root = str_map_find (&ctx->config, "static_root");
+ if (!root)
+ {
+ print_debug ("static document root not configured");
+ return false;
+ }
+
+ // TODO: implement HEAD, we don't get that for free;
+ // probably implies adding Content-Length
+ const char *method = str_map_find (headers, "REQUEST_METHOD");
+ if (!method || strcmp (method, "GET"))
+ return false;
+
+ // TODO: look at , REQUEST_URI in the headers
+ const char *path_info = str_map_find (headers, "PATH_INFO");
+ if (!path_info)
+ path_info = str_map_find (headers, "REQUEST_URI");
+ if (!path_info)
+ {
+ print_debug ("neither PATH_INFO nor REQUEST_URI was defined");
+ return false;
+ }
+
+ // We need to filter the path to stay in our root
+ // Being able to read /etc/passwd would be rather embarrasing
+ char *suffix = canonicalize_url_path (path_info);
+ char *path = xstrdup_printf ("%s%s", root, suffix);
+ print_debug ("trying to statically serve %s", path);
+
+ // TODO: check that this is a regular file
+ FILE *fp = fopen (path, "rb");
+ if (!fp)
+ {
+ struct str response = str_make ();
+ str_append (&response, "Status: 404 Not Found\n");
+ str_append (&response, "Content-Type: text/plain\n\n");
+ str_append_printf (&response,
+ "File %s was not found on this server\n", suffix);
+ request_write (request, response.str, response.len);
+ str_free (&response);
+
+ free (suffix);
+ free (path);
+ return false;
+ }
+
+ free (suffix);
+ free (path);
+
+ uint8_t buf[8192];
+ size_t len;
+
+ // Try to detect the Content-Type from the actual contents
+ char *mime_type = NULL;
+ if ((len = fread (buf, 1, sizeof buf, fp)))
+ mime_type = detect_magic (buf, len);
+ if (!mime_type)
+ mime_type = xstrdup ("application/octet_stream");
+
+ struct str response = str_make ();
+ str_append (&response, "Status: 200 OK\n");
+ str_append_printf (&response, "Content-Type: %s\n\n", mime_type);
+ request_write (request, response.str, response.len);
+ str_free (&response);
+ free (mime_type);
+
+ // Write the chunk we've used to help us with magic detection;
+ // obviously we have to do it after we've written the headers
+ if (len)
+ request_write (request, buf, len);
+
+ while ((len = fread (buf, 1, sizeof buf, fp)))
+ request_write (request, buf, len);
+ fclose (fp);
+
+ // TODO: this should rather not be returned all at once but in chunks;
+ // file read requests never return EAGAIN
+ // TODO: actual file data should really be returned by a callback when
+ // the socket is writable with nothing to be sent (pumping the entire
+ // file all at once won't really work if it's huge).
+ *continue_ = false;
+ return true;
+}
+
+static bool
+request_handler_static_push
+ (struct request *request, const void *data, size_t len)
+{
+ (void) request;
+ (void) data;
+
+ // Aborting on content; we shouldn't receive any (GET)
+ // FIXME: there should at least be some indication of this happening
+ return len == 0;
+}
+
+static void
+request_handler_static_finalize (struct request *request)
+{
+ (void) request;
+ // Nothing to dispose of this far
+}
+
+struct request_handler g_request_handler_static =
+{
+ .try_handle = request_handler_static_try_handle,
+ .push_cb = request_handler_static_push,
+ .finalize_cb = request_handler_static_finalize,
+};
+
+// --- Client communication handlers -------------------------------------------
+
+/// A virtual class for client connections coming either from the web server
+/// or directly from the end-client, depending on the protocol in use
+struct client
+{
+ LIST_HEADER (struct client)
+
+ struct client_vtable *vtable; ///< Client behaviour
+
+ int socket_fd; ///< The network socket
+ bool received_eof; ///< Whether EOF has been received yet
+ bool flushing; ///< No more data to write, send FIN
+ bool closing; ///< No more data to read or write
+ bool half_closed; ///< Conn. half-closed while flushing
+ struct write_queue write_queue; ///< Write queue
+ ev_timer close_timeout_watcher; ///< Write queue flush timer
+
+ ev_io read_watcher; ///< The socket can be read from
+ ev_io write_watcher; ///< The socket can be written to
+};
+
+/// The concrete behaviour to serve a particular client's requests
+struct client_vtable
+{
+ /// Process incoming data; "len == 0" means EOF.
+ /// If the method returns false, client_close() is called by the caller.
+ bool (*push) (struct client *client, const void *data, size_t len);
+
+ // TODO: optional push_error() to inform about network I/O errors
+
+ /// Attempt a graceful shutdown: make any appropriate steps before
+ /// the client connection times out and gets torn down by force.
+ /// The client is allowed to destroy itself immediately.
+ void (*shutdown) (struct client *client);
+
+ /// Do any additional cleanup for the concrete class before destruction
+ void (*finalize) (struct client *client);
+};
+
+static void
+client_destroy (struct client *self)
+{
+ // XXX: this codebase halfway pretends there could be other contexts
+ struct server_context *ctx = ev_userdata (EV_DEFAULT);
+ LIST_UNLINK (ctx->clients, self);
+ ctx->n_clients--;
+
+ // First uninitialize the higher-level implementation
+ self->vtable->finalize (self);
+
+ ev_io_stop (EV_DEFAULT_ &self->read_watcher);
+ ev_io_stop (EV_DEFAULT_ &self->write_watcher);
+ xclose (self->socket_fd);
+ write_queue_free (&self->write_queue);
+ ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher);
+ free (self);
+
+ try_finish_quit (ctx);
+}
+
+static void
+client_write (struct client *self, const void *data, size_t len)
+{
+ if (!soft_assert (!self->flushing) || len == 0)
+ return;
+
+ struct write_req *req = xcalloc (1, sizeof *req);
+ req->data.iov_base = memcpy (xmalloc (len), data, len);
+ req->data.iov_len = len;
+
+ write_queue_add (&self->write_queue, req);
+ ev_io_start (EV_DEFAULT_ &self->write_watcher);
+}
+
+/// Half-close the connection from our side once the write_queue is flushed.
+/// It is the caller's responsibility to destroy the connection upon EOF.
+// XXX: or we might change on_client_readable to do it anyway, seems safe
+static void
+client_shutdown (struct client *self)
+{
+ self->flushing = true;
+ ev_feed_event (EV_DEFAULT_ &self->write_watcher, EV_WRITE);
+}
+
+/// Try to cleanly close the connection, waiting for the remote client to close
+/// its own side of the connection as a sign that it has processed all the data
+/// it wanted to. The client implementation will not receive any further data.
+/// May directly call client_destroy().
+static void
+client_close (struct client *self)
+{
+ if (self->closing)
+ return;
+
+ self->closing = true;
+ ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher);
+ client_shutdown (self);
+
+ // We assume the remote client doesn't want our data if it half-closes
+ if (self->received_eof)
+ client_destroy (self);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static bool
+client_read_loop (EV_P_ struct client *client, ev_io *watcher)
+{
+ char buf[8192];
+ ssize_t n_read;
+again:
+ while ((n_read = recv (watcher->fd, buf, sizeof buf, 0)) >= 0)
+ {
+ if (!n_read)
+ {
+ // Don't deliver the EOF condition repeatedly
+ ev_io_stop (EV_A_ watcher);
+ client->received_eof = true;
+ }
+ if (!client->closing
+ && !client->vtable->push (client, buf, n_read))
+ {
+ client_close (client);
+ return false;
+ }
+ if (!n_read)
+ return true;
+ }
+ if (errno == EINTR)
+ goto again;
+ if (errno == EAGAIN)
+ return true;
+
+ client_destroy (client);
+ return false;
+}
+
+static void
+on_client_readable (EV_P_ ev_io *watcher, int revents)
+{
+ struct client *client = watcher->data;
+ (void) revents;
+
+ if (client_read_loop (EV_A_ client, watcher)
+ && client->closing && client->received_eof)
+ client_destroy (client);
+}
+
+static void
+on_client_writable (EV_P_ ev_io *watcher, int revents)
+{
+ struct client *client = watcher->data;
+ (void) loop;
+ (void) revents;
+
+ // TODO: some sort of "on_buffers_flushed" callback for streaming huge
+ // chunks of external (or generated) data. That will need to be
+ // forwarded to "struct request_handler".
+ if (!flush_queue (&client->write_queue, watcher->fd))
+ {
+ client_destroy (client);
+ return;
+ }
+ if (!write_queue_is_empty (&client->write_queue))
+ return;
+
+ ev_io_stop (EV_A_ watcher);
+ if (client->flushing && !client->half_closed)
+ {
+ if (!shutdown (client->socket_fd, SHUT_WR))
+ client->half_closed = true;
+ else
+ client_destroy (client);
+ }
+}
+
+static void
+on_client_timeout (EV_P_ ev_timer *watcher, int revents)
+{
+ (void) loop;
+ (void) revents;
+
+ client_destroy (watcher->data);
+}
+
+/// Create a new instance of a subclass with the given size.
+/// The superclass is assumed to be the first member of the structure.
+static void *
+client_new (EV_P_ size_t size, int sock_fd)
+{
+ struct server_context *ctx = ev_userdata (loop);
+ struct client *self = xcalloc (1, size);
+
+ self->write_queue = write_queue_make ();
+ ev_timer_init (&self->close_timeout_watcher, on_client_timeout, 5., 0.);
+ self->close_timeout_watcher.data = self;
+
+ set_blocking (sock_fd, false);
+ self->socket_fd = sock_fd;
+
+ ev_io_init (&self->read_watcher, on_client_readable, sock_fd, EV_READ);
+ ev_io_init (&self->write_watcher, on_client_writable, sock_fd, EV_WRITE);
+ self->read_watcher.data = self;
+ self->write_watcher.data = self;
+
+ // We're only interested in reading as the write queue is empty now
+ ev_io_start (EV_A_ &self->read_watcher);
+
+ LIST_PREPEND (ctx->clients, self);
+ ctx->n_clients++;
+ return self;
+}
+
+// --- FastCGI client handler --------------------------------------------------
+
+struct client_fcgi
+{
+ struct client client; ///< Parent class
+ struct fcgi_muxer muxer; ///< FastCGI de/multiplexer
+};
+
+struct client_fcgi_request
+{
+ struct fcgi_request *fcgi_request; ///< FastCGI request
+ struct request request; ///< Request
+};
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+client_fcgi_request_write_cb (struct request *req, const void *data, size_t len)
+{
+ FIND_CONTAINER (self, req, struct client_fcgi_request, request);
+ fcgi_request_write (self->fcgi_request, data, len);
+}
+
+static void
+client_fcgi_request_close_cb (struct request *req)
+{
+ FIND_CONTAINER (self, req, struct client_fcgi_request, request);
+ struct fcgi_muxer *muxer = self->fcgi_request->muxer;
+ // No more data to send, terminate the substream/request,
+ // and also the transport if the client didn't specifically ask to keep it
+ if (!fcgi_request_finish (self->fcgi_request))
+ muxer->close_cb (muxer);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static bool
+client_fcgi_request_start (struct fcgi_request *fcgi_request)
+{
+ struct client_fcgi_request *request =
+ fcgi_request->handler_data = xcalloc (1, sizeof *request);
+ request->fcgi_request = fcgi_request;
+ request_init (&request->request);
+ request->request.ctx = ev_userdata (EV_DEFAULT);
+ request->request.write_cb = client_fcgi_request_write_cb;
+ request->request.close_cb = client_fcgi_request_close_cb;
+
+ return request_start (&request->request, &fcgi_request->headers);
+}
+
+static bool
+client_fcgi_request_push
+ (struct fcgi_request *req, const void *data, size_t len)
+{
+ struct client_fcgi_request *request = req->handler_data;
+ return request_push (&request->request, data, len)
+ || fcgi_request_finish (req);
+}
+
+static void
+client_fcgi_request_finalize (struct fcgi_request *req)
+{
+ struct client_fcgi_request *request = req->handler_data;
+ request_free (&request->request);
+ free (request);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+client_fcgi_write_cb (struct fcgi_muxer *mux, const void *data, size_t len)
+{
+ FIND_CONTAINER (self, mux, struct client_fcgi, muxer);
+ client_write (&self->client, data, len);
+}
+
+static void
+client_fcgi_close_cb (struct fcgi_muxer *mux)
+{
+ FIND_CONTAINER (self, mux, struct client_fcgi, muxer);
+ client_close (&self->client);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static bool
+client_fcgi_push (struct client *client, const void *data, size_t len)
+{
+ FIND_CONTAINER (self, client, struct client_fcgi, client);
+ return fcgi_muxer_push (&self->muxer, data, len);
+}
+
+static void
+client_fcgi_shutdown (struct client *client)
+{
+ FIND_CONTAINER (self, client, struct client_fcgi, client);
+ self->muxer.in_shutdown = true;
+
+ // TODO: respond with FCGI_END_REQUEST: FCGI_REQUEST_COMPLETE to everything?
+ // The FastCGI specification isn't very clear about what we should do.
+}
+
+static void
+client_fcgi_finalize (struct client *client)
+{
+ FIND_CONTAINER (self, client, struct client_fcgi, client);
+ fcgi_muxer_free (&self->muxer);
+}
+
+static struct client_vtable client_fcgi_vtable =
+{
+ .push = client_fcgi_push,
+ .shutdown = client_fcgi_shutdown,
+ .finalize = client_fcgi_finalize,
+};
+
+static struct client *
+client_fcgi_create (EV_P_ int sock_fd)
+{
+ struct client_fcgi *self = client_new (EV_A_ sizeof *self, sock_fd);
+ self->client.vtable = &client_fcgi_vtable;
+
+ fcgi_muxer_init (&self->muxer);
+ self->muxer.write_cb = client_fcgi_write_cb;
+ self->muxer.close_cb = client_fcgi_close_cb;
+ self->muxer.request_start_cb = client_fcgi_request_start;
+ self->muxer.request_push_cb = client_fcgi_request_push;
+ self->muxer.request_finalize_cb = client_fcgi_request_finalize;
+ return &self->client;
+}
+
+// --- SCGI client handler -----------------------------------------------------
+
+struct client_scgi
+{
+ struct client client; ///< Parent class
+ struct scgi_parser parser; ///< SCGI stream parser
+ struct request request; ///< Request (only one per connection)
+ unsigned long remaining_content; ///< Length of input data to be seen
+};
+
+static void
+client_scgi_write_cb (struct request *req, const void *data, size_t len)
+{
+ FIND_CONTAINER (self, req, struct client_scgi, request);
+ client_write (&self->client, data, len);
+}
+
+static void
+client_scgi_close_cb (struct request *req)
+{
+ FIND_CONTAINER (self, req, struct client_scgi, request);
+ // NOTE: this rather really means "close me [the request]"
+ client_close (&self->client);
+}
+
+static bool
+client_scgi_on_headers_read (void *user_data)
+{
+ struct client_scgi *self = user_data;
+ const char *cl = str_map_find (&self->parser.headers, "CONTENT_LENGTH");
+ if (!cl || !xstrtoul (&self->remaining_content, cl, 10))
+ {
+ print_debug ("SCGI request with invalid or missing CONTENT_LENGTH");
+ return false;
+ }
+ return request_start (&self->request, &self->parser.headers);
+}
+
+static bool
+client_scgi_on_content (void *user_data, const void *data, size_t len)
+{
+ struct client_scgi *self = user_data;
+ if (len > self->remaining_content)
+ {
+ print_debug ("SCGI request got more data than CONTENT_LENGTH");
+ return false;
+ }
+ // We're in a slight disagreement with the specification since
+ // this tries to write output before it has read all the input
+ if (!request_push (&self->request, data, len))
+ return false;
+
+ // Signalise end of input to the request handler
+ return (self->remaining_content -= len) != 0
+ || request_push (&self->request, NULL, 0);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static bool
+client_scgi_push (struct client *client, const void *data, size_t len)
+{
+ struct client_scgi *self = (struct client_scgi *) client;
+ struct error *e = NULL;
+ if (scgi_parser_push (&self->parser, data, len, &e))
+ return true;
+
+ if (e != NULL)
+ {
+ print_debug ("SCGI parser failed: %s", e->message);
+ error_free (e);
+ }
+ return false;
+}
+
+static void
+client_scgi_finalize (struct client *client)
+{
+ struct client_scgi *self = (struct client_scgi *) client;
+ request_free (&self->request);
+ scgi_parser_free (&self->parser);
+}
+
+static struct client_vtable client_scgi_vtable =
+{
+ .push = client_scgi_push,
+ .finalize = client_scgi_finalize,
+};
+
+static struct client *
+client_scgi_create (EV_P_ int sock_fd)
+{
+ struct client_scgi *self = client_new (EV_A_ sizeof *self, sock_fd);
+ self->client.vtable = &client_scgi_vtable;
+
+ request_init (&self->request);
+ self->request.ctx = ev_userdata (EV_DEFAULT);
+ self->request.write_cb = client_scgi_write_cb;
+ self->request.close_cb = client_scgi_close_cb;
+
+ self->parser = scgi_parser_make ();
+ self->parser.on_headers_read = client_scgi_on_headers_read;
+ self->parser.on_content = client_scgi_on_content;
+ self->parser.user_data = self;
+ return &self->client;
+}
+
+// --- WebSockets client handler -----------------------------------------------
+
+struct client_ws
+{
+ struct client client; ///< Parent class
+ struct ws_handler handler; ///< WebSockets connection handler
+};
+
+static bool
+client_ws_on_message (struct ws_handler *handler,
+ enum ws_opcode type, const void *data, size_t len)
+{
+ FIND_CONTAINER (self, handler, struct client_ws, handler);
+ if (type != WS_OPCODE_TEXT)
+ {
+ return ws_handler_fail_connection
+ (&self->handler, WS_STATUS_UNSUPPORTED_DATA);
+ }
+
+ struct server_context *ctx = ev_userdata (EV_DEFAULT);
+ struct str response = str_make ();
+ process_json_rpc (ctx, data, len, &response);
+ if (response.len)
+ ws_handler_send_frame (&self->handler,
+ WS_OPCODE_TEXT, response.str, response.len);
+ str_free (&response);
+ return true;
+}
+
+static void
+client_ws_write_cb (struct ws_handler *handler, const void *data, size_t len)
+{
+ FIND_CONTAINER (self, handler, struct client_ws, handler);
+ client_write (&self->client, data, len);
+}
+
+static void
+client_ws_close_cb (struct ws_handler *handler, bool half_close)
+{
+ FIND_CONTAINER (self, handler, struct client_ws, handler);
+ (half_close ? client_shutdown : client_destroy) (&self->client);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static bool
+client_ws_push (struct client *client, const void *data, size_t len)
+{
+ FIND_CONTAINER (self, client, struct client_ws, client);
+ // client_close() will correctly destroy the client on EOF
+ return ws_handler_push (&self->handler, data, len);
+}
+
+static void
+client_ws_shutdown (struct client *client)
+{
+ FIND_CONTAINER (self, client, struct client_ws, client);
+ if (self->handler.state == WS_HANDLER_CONNECTING)
+ // No on_close, no problem
+ client_destroy (&self->client);
+ else if (self->handler.state == WS_HANDLER_OPEN)
+ ws_handler_close (&self->handler, WS_STATUS_GOING_AWAY, NULL, 0);
+}
+
+static void
+client_ws_finalize (struct client *client)
+{
+ FIND_CONTAINER (self, client, struct client_ws, client);
+ ws_handler_free (&self->handler);
+}
+
+static struct client_vtable client_ws_vtable =
+{
+ .push = client_ws_push,
+ .shutdown = client_ws_shutdown,
+ .finalize = client_ws_finalize,
+};
+
+static struct client *
+client_ws_create (EV_P_ int sock_fd)
+{
+ struct client_ws *self = client_new (EV_A_ sizeof *self, sock_fd);
+ self->client.vtable = &client_ws_vtable;
+
+ ws_handler_init (&self->handler);
+ self->handler.on_message = client_ws_on_message;
+ self->handler.write_cb = client_ws_write_cb;
+ self->handler.close_cb = client_ws_close_cb;
+
+ // One mebibyte seems to be a reasonable value
+ self->handler.max_payload_len = 1 << 10;
+
+ ws_handler_start (&self->handler);
+ return &self->client;
+}
+
+// --- Basic server stuff ------------------------------------------------------
+
+typedef struct client *(*client_create_fn) (EV_P_ int sock_fd);
+
+struct listener
+{
+ int fd; ///< Listening socket FD
+ ev_io watcher; ///< New connection available
+ client_create_fn create; ///< Client constructor
+};
+
+static void
+close_listeners (struct server_context *self)
+{
+ for (size_t i = 0; i < self->n_listeners; i++)
+ {
+ struct listener *listener = &self->listeners[i];
+ if (listener->fd == -1)
+ continue;
+
+ ev_io_stop (EV_DEFAULT_ &listener->watcher);
+ xclose (listener->fd);
+ listener->fd = -1;
+ }
+}
+
+static void
+try_finish_quit (struct server_context *self)
+{
+ if (!self->quitting || self->clients)
+ return;
+
+ ev_timer_stop (EV_DEFAULT_ &self->quit_timeout_watcher);
+ ev_break (EV_DEFAULT_ EVBREAK_ALL);
+}
+
+static void
+on_quit_timeout (EV_P_ ev_timer *watcher, int revents)
+{
+ struct server_context *self = watcher->data;
+ (void) loop;
+ (void) revents;
+
+ LIST_FOR_EACH (struct client, iter, self->clients)
+ client_destroy (iter);
+}
+
+static void
+initiate_quit (struct server_context *self)
+{
+ self->quitting = true;
+ close_listeners (self);
+
+ // Wait a little while for all clients to clean up, if necessary
+ LIST_FOR_EACH (struct client, iter, self->clients)
+ if (iter->vtable->shutdown)
+ iter->vtable->shutdown (iter);
+ ev_timer_start (EV_DEFAULT_ &self->quit_timeout_watcher);
+ try_finish_quit (self);
+}
+
+static void
+on_client_available (EV_P_ ev_io *watcher, int revents)
+{
+ struct server_context *ctx = ev_userdata (loop);
+ struct listener *listener = watcher->data;
+ (void) revents;
+
+ while (true)
+ {
+ int sock_fd = accept (watcher->fd, NULL, NULL);
+ if (sock_fd != -1)
+ listener->create (EV_A_ sock_fd);
+ else if (errno == EAGAIN)
+ return;
+ else if (errno != EINTR && errno != EMFILE
+ && errno != ECONNRESET && errno != ECONNABORTED)
+ break;
+ }
+
+ // Stop accepting connections to prevent busy looping
+ ev_io_stop (EV_A_ watcher);
+
+ print_fatal ("%s: %s", "accept", strerror (errno));
+ initiate_quit (ctx);
+}
+
+// --- Application setup -------------------------------------------------------
+
+/// This function handles values that require validation before their first use,
+/// or some kind of a transformation (such as conversion to an integer) needs
+/// to be done before they can be used directly.
+static bool
+parse_config (struct server_context *ctx, struct error **e)
+{
+ (void) ctx;
+ (void) e;
+
+ return true;
+}
+
+static int
+listener_bind (struct addrinfo *gai_iter)
+{
+ int fd = socket (gai_iter->ai_family,
+ gai_iter->ai_socktype, gai_iter->ai_protocol);
+ if (fd == -1)
+ return -1;
+ set_cloexec (fd);
+
+ int yes = 1;
+ soft_assert (setsockopt (fd, SOL_SOCKET, SO_KEEPALIVE,
+ &yes, sizeof yes) != -1);
+ soft_assert (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR,
+ &yes, sizeof yes) != -1);
+
+ char host[NI_MAXHOST], port[NI_MAXSERV];
+ host[0] = port[0] = '\0';
+ int err = getnameinfo (gai_iter->ai_addr, gai_iter->ai_addrlen,
+ host, sizeof host, port, sizeof port,
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ if (err)
+ print_debug ("%s: %s", "getnameinfo", gai_strerror (err));
+
+ char *address = format_host_port_pair (host, port);
+ if (bind (fd, gai_iter->ai_addr, gai_iter->ai_addrlen))
+ print_error ("bind to %s failed: %s", address, strerror (errno));
+ else if (listen (fd, 16 /* arbitrary number */))
+ print_error ("listen on %s failed: %s", address, strerror (errno));
+ else
+ {
+ print_status ("listening on %s", address);
+ free (address);
+ return fd;
+ }
+
+ free (address);
+ xclose (fd);
+ return -1;
+}
+
+static void
+listener_add (struct server_context *ctx, const char *host, const char *port,
+ const struct addrinfo *gai_hints, client_create_fn create)
+{
+ struct addrinfo *gai_result, *gai_iter;
+ int err = getaddrinfo (host, port, gai_hints, &gai_result);
+ if (err)
+ {
+ char *address = format_host_port_pair (host, port);
+ print_error ("bind to %s failed: %s: %s",
+ address, "getaddrinfo", gai_strerror (err));
+ free (address);
+ return;
+ }
+
+ int fd;
+ for (gai_iter = gai_result; gai_iter; gai_iter = gai_iter->ai_next)
+ {
+ if ((fd = listener_bind (gai_iter)) == -1)
+ continue;
+ set_blocking (fd, false);
+
+ struct listener *listener = &ctx->listeners[ctx->n_listeners++];
+ ev_io_init (&listener->watcher, on_client_available, fd, EV_READ);
+ ev_io_start (EV_DEFAULT_ &listener->watcher);
+ listener->watcher.data = listener;
+ listener->create = create;
+ listener->fd = fd;
+ break;
+ }
+ freeaddrinfo (gai_result);
+}
+
+static void
+get_ports_from_config (struct server_context *ctx,
+ const char *key, struct strv *out)
+{
+ const char *ports;
+ if ((ports = str_map_find (&ctx->config, key)))
+ cstr_split (ports, ",", true, out);
+}
+
+static bool
+setup_listen_fds (struct server_context *ctx, struct error **e)
+{
+ static const struct addrinfo gai_hints =
+ {
+ .ai_socktype = SOCK_STREAM,
+ .ai_flags = AI_PASSIVE,
+ };
+
+ struct strv ports_fcgi = strv_make ();
+ struct strv ports_scgi = strv_make ();
+ struct strv ports_ws = strv_make ();
+
+ get_ports_from_config (ctx, "port_fastcgi", &ports_fcgi);
+ get_ports_from_config (ctx, "port_scgi", &ports_scgi);
+ get_ports_from_config (ctx, "port_ws", &ports_ws);
+
+ const char *bind_host = str_map_find (&ctx->config, "bind_host");
+ size_t n_ports = ports_fcgi.len + ports_scgi.len + ports_ws.len;
+ ctx->listeners = xcalloc (n_ports, sizeof *ctx->listeners);
+
+ for (size_t i = 0; i < ports_fcgi.len; i++)
+ listener_add (ctx, bind_host, ports_fcgi.vector[i],
+ &gai_hints, client_fcgi_create);
+ for (size_t i = 0; i < ports_scgi.len; i++)
+ listener_add (ctx, bind_host, ports_scgi.vector[i],
+ &gai_hints, client_scgi_create);
+ for (size_t i = 0; i < ports_ws.len; i++)
+ listener_add (ctx, bind_host, ports_ws.vector[i],
+ &gai_hints, client_ws_create);
+
+ strv_free (&ports_fcgi);
+ strv_free (&ports_scgi);
+ strv_free (&ports_ws);
+
+ if (!ctx->n_listeners)
+ {
+ error_set (e, "%s: %s",
+ "network setup failed", "no ports to listen on");
+ return false;
+ }
+ return true;
+}
+
+static bool
+app_lock_pid_file (struct server_context *ctx, struct error **e)
+{
+ const char *path = str_map_find (&ctx->config, "pid_file");
+ if (!path)
+ return true;
+
+ char *resolved = resolve_filename (path, resolve_relative_runtime_filename);
+ bool result = lock_pid_file (resolved, e) != -1;
+ free (resolved);
+ return result;
+}
+
+// --- Tests -------------------------------------------------------------------
+
+static void
+test_misc (void)
+{
+ soft_assert ( validate_json_rpc_content_type
+ ("application/JSON; charset=\"utf-8\""));
+ soft_assert (!validate_json_rpc_content_type
+ ("text/html; charset=\"utf-8\""));
+
+ char *canon = canonicalize_url_path ("///../../../etc/./passwd");
+ soft_assert (!strcmp (canon, "/etc/passwd"));
+ free (canon);
+}
+
+int
+test_main (int argc, char *argv[])
+{
+ struct test test;
+ test_init (&test, argc, argv);
+
+ test_add_simple (&test, "/misc", NULL, test_misc);
+
+ // TODO: write more tests
+ // TODO: test the server handler (happy path)
+
+ return test_run (&test);
+}
+
+// --- Main program ------------------------------------------------------------
+
+static void
+on_termination_signal (EV_P_ ev_signal *handle, int revents)
+{
+ struct server_context *ctx = ev_userdata (loop);
+ (void) handle;
+ (void) revents;
+
+ if (ctx->quitting)
+ {
+ // Double C-c from the terminal accelerates the process
+ LIST_FOR_EACH (struct client, iter, ctx->clients)
+ client_destroy (iter);
+ }
+ else
+ initiate_quit (ctx);
+}
+
+static void
+setup_signal_handlers (struct server_context *ctx)
+{
+ ev_signal_init (&ctx->sigterm_watcher, on_termination_signal, SIGTERM);
+ ev_signal_start (EV_DEFAULT_ &ctx->sigterm_watcher);
+
+ ev_signal_init (&ctx->sigint_watcher, on_termination_signal, SIGINT);
+ ev_signal_start (EV_DEFAULT_ &ctx->sigint_watcher);
+
+ (void) signal (SIGPIPE, SIG_IGN);
+}
+
+static void
+daemonize (struct server_context *ctx)
+{
+ print_status ("daemonizing...");
+
+ if (chdir ("/"))
+ exit_fatal ("%s: %s", "chdir", strerror (errno));
+
+ // Because of systemd, we need to exit the parent process _after_ writing
+ // a PID file, otherwise our grandchild would receive a SIGTERM
+ int sync_pipe[2];
+ if (pipe (sync_pipe))
+ exit_fatal ("%s: %s", "pipe", strerror (errno));
+
+ pid_t pid;
+ if ((pid = fork ()) < 0)
+ exit_fatal ("%s: %s", "fork", strerror (errno));
+ else if (pid)
+ {
+ // Wait until all write ends of the pipe are closed, which can mean
+ // either success or failure, we don't need to care
+ xclose (sync_pipe[PIPE_WRITE]);
+
+ char dummy;
+ if (read (sync_pipe[PIPE_READ], &dummy, 1) < 0)
+ exit_fatal ("%s: %s", "read", strerror (errno));
+
+ exit (EXIT_SUCCESS);
+ }
+
+ setsid ();
+ signal (SIGHUP, SIG_IGN);
+
+ if ((pid = fork ()) < 0)
+ exit_fatal ("%s: %s", "fork", strerror (errno));
+ else if (pid)
+ exit (EXIT_SUCCESS);
+
+ openlog (PROGRAM_NAME, LOG_NDELAY | LOG_NOWAIT | LOG_PID, 0);
+ g_log_message_real = log_message_syslog;
+
+ // Write the PID file (if so configured) and get rid of the pipe, so that
+ // the read() in our grandparent finally returns zero (no write ends)
+ struct error *e = NULL;
+ if (!app_lock_pid_file (ctx, &e))
+ exit_fatal ("%s", e->message);
+
+ xclose (sync_pipe[PIPE_READ]);
+ xclose (sync_pipe[PIPE_WRITE]);
+
+ // XXX: we may close our own descriptors this way, crippling ourselves;
+ // there is no real guarantee that we will start with all three
+ // descriptors open. In theory we could try to enumerate the descriptors
+ // at the start of main().
+ for (int i = 0; i < 3; i++)
+ xclose (i);
+
+ int tty = open ("/dev/null", O_RDWR);
+ if (tty != 0 || dup (0) != 1 || dup (0) != 2)
+ exit_fatal ("failed to reopen FD's: %s", strerror (errno));
+}
+
+static void
+parse_program_arguments (int argc, char **argv)
+{
+ static const struct opt opts[] =
+ {
+ { 't', "test", NULL, 0, "self-test" },
+ { 'd', "debug", NULL, 0, "run in debug mode" },
+ { 'h', "help", NULL, 0, "display this help and exit" },
+ { 'V', "version", NULL, 0, "output version information and exit" },
+ { 'w', "write-default-cfg", "FILENAME",
+ OPT_OPTIONAL_ARG | OPT_LONG_ONLY,
+ "write a default configuration file and exit" },
+ { 0, NULL, NULL, 0, NULL }
+ };
+
+ struct opt_handler oh =
+ opt_handler_make (argc, argv, opts, NULL, "JSON-RPC 2.0 demo server.");
+
+ int c;
+ while ((c = opt_handler_get (&oh)) != -1)
+ switch (c)
+ {
+ case 't':
+ test_main (argc, argv);
+ exit (EXIT_SUCCESS);
+ case 'd':
+ g_debug_mode = true;
+ break;
+ case 'h':
+ opt_handler_usage (&oh, stdout);
+ exit (EXIT_SUCCESS);
+ case 'V':
+ printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
+ exit (EXIT_SUCCESS);
+ case 'w':
+ call_simple_config_write_default (optarg, g_config_table);
+ exit (EXIT_SUCCESS);
+ default:
+ print_error ("wrong options");
+ opt_handler_usage (&oh, stderr);
+ exit (EXIT_FAILURE);
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc)
+ {
+ opt_handler_usage (&oh, stderr);
+ exit (EXIT_FAILURE);
+ }
+ opt_handler_free (&oh);
+}
+
+int
+main (int argc, char *argv[])
+{
+ parse_program_arguments (argc, argv);
+
+ print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting");
+
+ struct server_context ctx;
+ server_context_init (&ctx);
+
+ struct error *e = NULL;
+ if (!simple_config_update_from_file (&ctx.config, &e))
+ {
+ print_error ("error loading configuration: %s", e->message);
+ error_free (e);
+ exit (EXIT_FAILURE);
+ }
+
+ struct ev_loop *loop;
+ if (!(loop = EV_DEFAULT))
+ exit_fatal ("libev initialization failed");
+
+ ev_set_userdata (loop, &ctx);
+ setup_signal_handlers (&ctx);
+
+ LIST_PREPEND (ctx.handlers, &g_request_handler_static);
+ LIST_PREPEND (ctx.handlers, &g_request_handler_json_rpc);
+
+ if (!parse_config (&ctx, &e)
+ || !setup_listen_fds (&ctx, &e))
+ {
+ print_error ("%s", e->message);
+ error_free (e);
+ exit (EXIT_FAILURE);
+ }
+
+ if (!g_debug_mode)
+ daemonize (&ctx);
+ else if (!app_lock_pid_file (&ctx, &e))
+ exit_fatal ("%s", e->message);
+
+ ev_run (loop, 0);
+ ev_loop_destroy (loop);
+
+ server_context_free (&ctx);
+ return EXIT_SUCCESS;
+}
--
cgit v1.2.3-70-g09d2