changeset 22388:df373becc66c

lib-http: Changed test-http-server to actually use the http-server API. This currently serves as a simple demonstration of how to structure an HTTP server.
author Stephan Bosch <stephan.bosch@dovecot.fi>
date Fri, 12 May 2017 04:25:08 +0200
parents 6cbca06276e6
children 36a38929734d
files src/lib-http/test-http-server.c
diffstat 1 files changed, 174 insertions(+), 66 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-http/test-http-server.c	Tue Jul 25 20:53:18 2017 +0300
+++ b/src/lib-http/test-http-server.c	Fri May 12 04:25:08 2017 +0200
@@ -1,84 +1,139 @@
 /* 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;
-	}
-	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)
-{
-	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;
-		}
-	}
-	if (ret < 0) {
-		i_error("Client sent invalid request: %s", error);
-		client_destroy(conn);
+	while (clients_head != NULL) {
+		struct client *client = clients_head;
+		client_destroy(&client, "Shutting down server");
 	}
 }
 
-static struct connection_settings client_set = {
-	.input_max_size = (size_t)-1,
-	.output_max_size = (size_t)-1,
-	.client = FALSE
+static void
+client_http_handle_request(void *context,
+	struct http_server_request *req)
+{
+	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;
+	}
+
+	/* 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 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 http_server_callbacks server_callbacks = {
+	.handle_request = client_http_handle_request,
+	.connection_destroy = client_http_connection_destroy
 };
 
-static const struct connection_vfuncs client_vfuncs = {
-	.destroy = client_destroy,
-	.input = client_input
-};
-
-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 @@
 	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();
 }