]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-http: Changed test-http-server to actually use the http-server API.
authorStephan Bosch <stephan.bosch@dovecot.fi>
Fri, 12 May 2017 02:25:08 +0000 (04:25 +0200)
committerStephan Bosch <stephan.bosch@dovecot.fi>
Thu, 27 Jul 2017 13:04:49 +0000 (15:04 +0200)
This currently serves as a simple demonstration of how to structure an HTTP server.

src/lib-http/test-http-server.c

index 1c8c09fdf512ee10911f40d5838746836f0f2c29..bf9849e1940e8e6daaa5873d39ba6220e869391b 100644 (file)
 /* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
+#include "lib-signals.h"
+#include "llist.h"
 #include "str.h"
 #include "ioloop.h"
 #include "ostream.h"
 #include "connection.h"
-#include "http-date.h"
-#include "http-request-parser.h"
+#include "http-url.h"
+#include "http-server.h"
+
+#include <unistd.h>
 
-static struct connection_list *clients;
 static int fd_listen;
+static struct ioloop *ioloop;
 static struct io *io_listen;
+static struct http_server *http_server;
+static bool shut_down = FALSE;
+static struct client *clients_head = NULL, *clients_tail = NULL;
 
 struct client {
-       struct connection conn;
-       struct http_request_parser *parser;
+       struct client *prev, *next;
+
+       struct ip_addr server_ip, ip;
+       in_port_t server_port, port;
+       struct http_server_connection *http_conn;
 };
 
-static void client_destroy(struct connection *conn)
+static void
+client_destroy(struct client **_client, const char *reason)
 {
-       struct client *client = (struct client *)conn;
+       struct client *client = *_client;
 
-       http_request_parser_deinit(&client->parser);
-       connection_deinit(&client->conn);
+       if (client->http_conn != NULL) {
+               /* We're not in the lib-http/server's connection destroy callback.
+                  If at all possible, avoid destroying client objects directly.
+          */
+               http_server_connection_close(&client->http_conn, reason);
+       }
+       DLLIST2_REMOVE(&clients_head, &clients_tail, client);
        i_free(client);
+
+       if (clients_head == NULL)
+               io_loop_stop(ioloop);
 }
 
-static int
-client_handle_request(struct client *client, struct http_request *request)
+/* This function just serves as an illustration of what to do when client
+   objects are destroyed by some actor other than lib-http/server. The best way
+   to close all clients is to drop the whole http-server, which will close all
+   connections, which in turn calls the connection_destroy() callbacks. Using a
+   function like this just complicates matters. */
+static void
+clients_destroy_all(void)
 {
-       string_t *str = t_str_new(128);
-
-       if (strcmp(request->method, "GET") != 0) {
-               o_stream_send_str(client->conn.output, "HTTP/1.1 501 Not Implemented\r\nAllow: GET\r\n\r\n");
-               return 0;
+       while (clients_head != NULL) {
+               struct client *client = clients_head;
+               client_destroy(&client, "Shutting down server");
        }
-       str_append(str, "HTTP/1.1 200 OK\r\n");
-       str_printfa(str, "Date: %s\r\n", http_date_create(ioloop_time));
-       str_printfa(str, "Content-Length: %d\r\n", (int)strlen(request->target_raw));
-       str_append(str, "Content-Type: text/plain\r\n");
-       str_append(str, "\r\n");
-       str_append(str, request->target_raw);
-       o_stream_nsend(client->conn.output, str_data(str), str_len(str));
-       return 0;
 }
 
-static void client_input(struct connection *conn)
+static void
+client_http_handle_request(void *context,
+       struct http_server_request *req)
 {
-       struct client *client = (struct client *)conn;
-       struct http_request request;
-       enum http_request_parse_error error_code;
-       const char *error;
-       int ret;
-
-       while ((ret = http_request_parse_next
-               (client->parser, NULL, &request, &error_code, &error)) > 0) {
-               if (client_handle_request(client, &request) < 0 ||
-                   request.connection_close) {
-                       client_destroy(conn);
-                       return;
-               }
+       struct client *client = (struct client *)context;
+       const struct http_request *http_req = http_server_request_get(req);
+       struct http_server_response *http_resp;
+       const char *ipport;
+       string_t *content;
+
+       if (strcmp(http_req->method, "GET") != 0) {
+               /* Unsupported method */
+               http_resp = http_server_response_create(req, 501, "Not Implemented");
+               http_server_response_add_header(http_resp, "Allow", "GET");
+               http_server_response_submit(http_resp);
+               return;
        }
-       if (ret < 0) {
-               i_error("Client sent invalid request: %s", error);
-               client_destroy(conn);
+
+       /* Compose response payload */
+       content = t_str_new(1024);
+       (void)net_ipport2str(&client->server_ip, client->server_port, &ipport);
+       str_printfa(content, "Server: %s\r\n", ipport);
+       (void)net_ipport2str(&client->ip, client->port, &ipport);
+       str_printfa(content, "Client: %s\r\n", ipport);
+       str_printfa(content, "Host: %s", http_req->target.url->host_name);
+       if (http_req->target.url->port != 0)
+               str_printfa(content, ":%u", http_req->target.url->port);
+       str_append(content, "\r\n");
+       switch (http_req->target.format) {
+       case HTTP_REQUEST_TARGET_FORMAT_ORIGIN:
+       case HTTP_REQUEST_TARGET_FORMAT_ABSOLUTE:
+               str_printfa(content, "Target: %s\r\n",
+                       http_url_create(http_req->target.url));
+               break;
+       case HTTP_REQUEST_TARGET_FORMAT_AUTHORITY:
+               str_printfa(content, "Target: %s\r\n",
+                       http_url_create_authority(http_req->target.url));
+               break;
+       case HTTP_REQUEST_TARGET_FORMAT_ASTERISK:
+               str_append(content, "Target: *\r\n");
+               break;
        }
+
+       /* Just respond with the request target */
+       http_resp = http_server_response_create(req, 200, "OK");
+       http_server_response_add_header(http_resp, "Content-Type", "text/plain");
+       http_server_response_set_payload_data(http_resp,
+               str_data(content), str_len(content));
+       http_server_response_submit(http_resp);
 }
 
-static struct connection_settings client_set = {
-       .input_max_size = (size_t)-1,
-       .output_max_size = (size_t)-1,
-       .client = FALSE
-};
+static void
+client_http_connection_destroy(void *context, const char *reason)
+{
+       struct client *client = (struct client *)context;
+
+       if (client->http_conn == NULL) {
+               /* already destroying client directly */
+               return;
+       }
+
+       /* HTTP connection is destroyed already now */
+       client->http_conn = NULL;
+
+       /* destroy the client itself */
+       client_destroy(&client, reason);
+}
 
-static const struct connection_vfuncs client_vfuncs = {
-       .destroy = client_destroy,
-       .input = client_input
+static const struct http_server_callbacks server_callbacks = {
+       .handle_request = client_http_handle_request,
+       .connection_destroy = client_http_connection_destroy
 };
 
-static void client_init(int fd)
+static void
+client_init(int fd, const struct ip_addr *ip, in_port_t port)
 {
        struct client *client;
        struct http_request_limits req_limits;
@@ -87,51 +142,104 @@ static void client_init(int fd)
        req_limits.max_target_length = 4096;
 
        client = i_new(struct client, 1);
-       connection_init_server(clients, &client->conn,
-                              "(http client)", fd, fd);
-       client->parser = http_request_parser_init(client->conn.input, &req_limits);
+       client->ip = *ip;
+       client->port = port;
+       (void)net_getsockname(fd, &client->server_ip, &client->server_port);
+       client->http_conn = http_server_connection_create(http_server,
+               fd, fd, FALSE, &server_callbacks, client);
+
+       DLLIST2_APPEND(&clients_head, &clients_tail, client);
 }
 
 static void client_accept(void *context ATTR_UNUSED)
 {
+       struct ip_addr client_ip;
+       in_port_t client_port;
        int fd;
 
-       fd = net_accept(fd_listen, NULL, NULL);
+       fd = net_accept(fd_listen, &client_ip, &client_port);
        if (fd == -1)
                return;
        if (fd == -2)
                i_fatal("accept() failed: %m");
 
-       client_init(fd);
+       client_init(fd, &client_ip, client_port);
+}
+
+static void
+sig_die(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED)
+{
+       if (shut_down) {
+               i_info("Received SIGINT again - stopping immediately");
+               io_loop_stop(current_ioloop);
+               return;
+       }
+
+       i_info("Received SIGINT - shutting down gracefully");
+       shut_down = TRUE;
+       http_server_shut_down(http_server);
+       if (clients_head == NULL)
+               io_loop_stop(ioloop);
 }
 
 int main(int argc, char *argv[])
 {
+       struct http_server_settings http_set;
+       bool debug = FALSE;
        struct ip_addr my_ip;
-       struct ioloop *ioloop;
        in_port_t port;
+       int c;
 
        lib_init();
-       if (argc < 2 || net_str2port(argv[1], &port) < 0)
+
+  while ((c = getopt(argc, argv, "D")) > 0) {
+               switch (c) {
+               case 'D':
+                       debug = TRUE;
+                       break;
+               default:
+                       i_fatal("Usage: %s [-D] <port> [<IP>]", argv[0]);
+               }
+  }
+       argc -= optind;
+       argv += optind;
+
+       if (argc < 1 || net_str2port(argv[0], &port) < 0)
                i_fatal("Port parameter missing");
-       if (argc < 3)
+       if (argc < 2)
                net_get_ip_any4(&my_ip);
        else if (net_addr2ip(argv[2], &my_ip) < 0)
                i_fatal("Invalid IP parameter");
 
+       i_zero(&http_set);
+       http_set.max_client_idle_time_msecs = 20*1000; /* defaults to indefinite! */
+       http_set.max_pipelined_requests = 4;
+       http_set.debug = debug;
+
        ioloop = io_loop_create();
-       clients = connection_list_init(&client_set, &client_vfuncs);
+
+       http_server = http_server_init(&http_set);
+
+       lib_signals_init();
+       lib_signals_ignore(SIGPIPE, TRUE);
+       lib_signals_set_handler(SIGTERM, LIBSIG_FLAG_DELAYED, sig_die, NULL);
+       lib_signals_set_handler(SIGINT, LIBSIG_FLAG_DELAYED, sig_die, NULL);
 
        fd_listen = net_listen(&my_ip, &port, 128);
        if (fd_listen == -1)
                i_fatal("listen(port=%u) failed: %m", port);
+
        io_listen = io_add(fd_listen, IO_READ, client_accept, (void *)NULL);
 
        io_loop_run(ioloop);
 
        io_remove(&io_listen);
        i_close_fd(&fd_listen);
-       connection_list_deinit(&clients);
+
+       clients_destroy_all(); /* just an example; avoid doing this */
+
+       http_server_deinit(&http_server);
+       lib_signals_deinit();
        io_loop_destroy(&ioloop);
        lib_deinit();
 }