forked from cory/tildefriends
		
	Work in progress HTTP server in C.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4676 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
		
							
								
								
									
										218
									
								
								src/http.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										218
									
								
								src/http.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,218 @@ | |||||||
|  | #include "http.h" | ||||||
|  |  | ||||||
|  | #include "log.h" | ||||||
|  | #include "mem.h" | ||||||
|  |  | ||||||
|  | #include "picohttpparser.h" | ||||||
|  | #include "uv.h" | ||||||
|  |  | ||||||
|  | #include <string.h> | ||||||
|  |  | ||||||
|  | #if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32) | ||||||
|  | #include <alloca.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | typedef struct _tf_http_connection_t | ||||||
|  | { | ||||||
|  | 	tf_http_t* http; | ||||||
|  | 	uv_tcp_t tcp; | ||||||
|  |  | ||||||
|  | 	char buffer[8192]; | ||||||
|  | 	size_t buffer_length; | ||||||
|  | 	int parsed_length; | ||||||
|  |  | ||||||
|  | 	tf_http_callback_t* callback; | ||||||
|  | 	void* user_data; | ||||||
|  | } tf_http_connection_t; | ||||||
|  |  | ||||||
|  | typedef struct _tf_http_handler_t | ||||||
|  | { | ||||||
|  | 	const char* pattern; | ||||||
|  | 	tf_http_callback_t* callback; | ||||||
|  | 	void* user_data; | ||||||
|  | } tf_http_handler_t; | ||||||
|  |  | ||||||
|  | typedef struct _tf_http_t | ||||||
|  | { | ||||||
|  | 	uv_tcp_t** listeners; | ||||||
|  | 	int listeners_count; | ||||||
|  |  | ||||||
|  | 	tf_http_connection_t** connections; | ||||||
|  | 	int connections_count; | ||||||
|  |  | ||||||
|  | 	tf_http_handler_t* handlers; | ||||||
|  | 	int handlers_count; | ||||||
|  |  | ||||||
|  | 	uv_loop_t* loop; | ||||||
|  | } tf_http_t; | ||||||
|  |  | ||||||
|  | tf_http_t* tf_http_create(uv_loop_t* loop) | ||||||
|  | { | ||||||
|  | 	tf_http_t* http = tf_malloc(sizeof(tf_http_t)); | ||||||
|  | 	*http = (tf_http_t) | ||||||
|  | 	{ | ||||||
|  | 		.loop = loop, | ||||||
|  | 	}; | ||||||
|  | 	return http; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void _http_allocate_buffer(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf) | ||||||
|  | { | ||||||
|  | 	tf_http_connection_t* connection = handle->data; | ||||||
|  | 	*buf = uv_buf_init(connection->buffer + connection->buffer_length, sizeof(connection->buffer) - connection->buffer_length); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool _http_find_handler(tf_http_t* http, const char* path, tf_http_callback_t** out_callback, void** out_user_data) | ||||||
|  | { | ||||||
|  | 	for (int i = 0; i < http->handlers_count; i++) | ||||||
|  | 	{ | ||||||
|  | 		if (!http->handlers[i].pattern) | ||||||
|  | 		{ | ||||||
|  | 			*out_callback = http->handlers[i].callback; | ||||||
|  | 			*out_user_data = http->handlers[i].user_data; | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void _http_on_read(uv_stream_t* stream, ssize_t read_size, const uv_buf_t* buffer) | ||||||
|  | { | ||||||
|  | 	tf_http_connection_t* connection = stream->data; | ||||||
|  | 	if (read_size > 0) | ||||||
|  | 	{ | ||||||
|  | 		connection->buffer_length += read_size; | ||||||
|  |  | ||||||
|  | 		const char* method = NULL; | ||||||
|  | 		size_t method_length = 0; | ||||||
|  | 		const char* path = NULL; | ||||||
|  | 		size_t path_length = 0; | ||||||
|  | 		int minor_version = 0; | ||||||
|  | 		struct phr_header headers[100]; | ||||||
|  | 		size_t header_count = sizeof(headers) / sizeof(*headers); | ||||||
|  |  | ||||||
|  | 		int parse_result = phr_parse_request(connection->buffer, connection->buffer_length, &method, &method_length, &path, &path_length, &minor_version, headers, &header_count, connection->parsed_length); | ||||||
|  | 		connection->parsed_length = connection->buffer_length; | ||||||
|  | 		if (parse_result > 0) | ||||||
|  | 		{ | ||||||
|  | 			if (_http_find_handler(connection->http, path, &connection->callback, &connection->user_data) && connection->callback) | ||||||
|  | 			{ | ||||||
|  | 				char* method_copy = alloca(method_length + 1); | ||||||
|  | 				memcpy(method_copy, method, method_length); | ||||||
|  | 				method_copy[method_length] = '\0'; | ||||||
|  |  | ||||||
|  | 				char* path_copy = alloca(path_length + 1); | ||||||
|  | 				memcpy(path_copy, path, path_length); | ||||||
|  | 				path_copy[path_length] = '\0'; | ||||||
|  |  | ||||||
|  | 				tf_http_request_t request = | ||||||
|  | 				{ | ||||||
|  | 					.connection = connection, | ||||||
|  | 					.phase = k_http_callback_phase_headers_received, | ||||||
|  | 					.method = method_copy, | ||||||
|  | 					.path = path_copy, | ||||||
|  | 					.headers = headers, | ||||||
|  | 					.headers_count = (int)header_count, | ||||||
|  | 					.user_data = connection->user_data, | ||||||
|  | 				}; | ||||||
|  | 				connection->callback(&request); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			tf_printf("phr_parse_request: %d\n", parse_result); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void _http_on_connection(uv_stream_t* stream, int status) | ||||||
|  | { | ||||||
|  | 	tf_http_t* http = stream->data; | ||||||
|  | 	tf_http_connection_t* connection = tf_malloc(sizeof(tf_http_connection_t)); | ||||||
|  | 	*connection = (tf_http_connection_t) { .http = http, .tcp = { .data = connection } }; | ||||||
|  | 	int r = uv_tcp_init(connection->http->loop, &connection->tcp); | ||||||
|  | 	if (r) | ||||||
|  | 	{ | ||||||
|  | 		tf_printf("uv_tcp_init: %s\n", uv_strerror(r)); | ||||||
|  | 		tf_free(connection); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	r = uv_accept(stream, (uv_stream_t*)&connection->tcp); | ||||||
|  | 	if (r) | ||||||
|  | 	{ | ||||||
|  | 		tf_printf("uv_accept: %s\n", uv_strerror(r)); | ||||||
|  | 		uv_close((uv_handle_t*)&connection->tcp, NULL); | ||||||
|  | 		tf_free(connection); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	r = uv_read_start((uv_stream_t*)&connection->tcp, _http_allocate_buffer, _http_on_read); | ||||||
|  | 	if (r) | ||||||
|  | 	{ | ||||||
|  | 		tf_printf("uv_read-start: %s\n", uv_strerror(r)); | ||||||
|  | 		uv_close((uv_handle_t*)&connection->tcp, NULL); | ||||||
|  | 		tf_free(connection); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	http->connections = tf_realloc(http->connections, http->connections_count + 1); | ||||||
|  | 	http->connections[http->connections_count++] = connection; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void tf_http_listen(tf_http_t* http, int port) | ||||||
|  | { | ||||||
|  | 	uv_tcp_t* tcp = tf_malloc(sizeof(uv_tcp_t)); | ||||||
|  | 	*tcp = (uv_tcp_t) { .data = http }; | ||||||
|  | 	int r = uv_tcp_init(http->loop, tcp); | ||||||
|  | 	if (r) | ||||||
|  | 	{ | ||||||
|  | 		tf_printf("uv_tcp_init: %s\n", uv_strerror(r)); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (r == 0) | ||||||
|  | 	{ | ||||||
|  | 		struct sockaddr_in addr = | ||||||
|  | 		{ | ||||||
|  | 			.sin_family = AF_INET, | ||||||
|  | 			.sin_addr = { .s_addr = INADDR_ANY }, | ||||||
|  | 			.sin_port = ntohs(port), | ||||||
|  | 		}; | ||||||
|  | 		r = uv_tcp_bind(tcp, (struct sockaddr*)&addr, 0); | ||||||
|  | 		if (r) | ||||||
|  | 		{ | ||||||
|  | 			tf_printf("uv_tcp_bind: %s\n", uv_strerror(r)); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (r == 0) | ||||||
|  | 	{ | ||||||
|  | 		r = uv_listen((uv_stream_t*)tcp, 16, _http_on_connection); | ||||||
|  | 		if (r) | ||||||
|  | 		{ | ||||||
|  | 			tf_printf("uv_listen: %s\n", uv_strerror(r)); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (r == 0) | ||||||
|  | 	{ | ||||||
|  | 		http->listeners = tf_realloc(http->listeners, sizeof(uv_tcp_t*) * (http->listeners_count + 1)); | ||||||
|  | 		http->listeners[http->listeners_count++] = tcp; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void tf_http_add_handler(tf_http_t* http, const char* pattern, tf_http_callback_t* callback, void* user_data) | ||||||
|  | { | ||||||
|  | 	http->handlers = tf_realloc(http->handlers, sizeof(tf_http_handler_t) * (http->handlers_count + 1)); | ||||||
|  | 	http->handlers[http->handlers_count++] = (tf_http_handler_t) | ||||||
|  | 	{ | ||||||
|  | 		.pattern = tf_strdup(pattern), | ||||||
|  | 		.callback = callback, | ||||||
|  | 		.user_data = user_data, | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void tf_http_destroy(tf_http_t* http) | ||||||
|  | { | ||||||
|  | 	tf_free(http); | ||||||
|  | } | ||||||
							
								
								
									
										29
									
								
								src/http.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/http.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | typedef struct uv_loop_s uv_loop_t; | ||||||
|  | typedef struct _tf_http_t tf_http_t; | ||||||
|  | typedef struct _tf_http_connection_t tf_http_connection_t; | ||||||
|  |  | ||||||
|  | typedef enum _tf_http_callback_phase_t | ||||||
|  | { | ||||||
|  | 	k_http_callback_phase_headers_received, | ||||||
|  | 	k_http_callback_phase_request_done, | ||||||
|  | } tf_http_callback_phase_t; | ||||||
|  |  | ||||||
|  | typedef struct _tf_http_request_t | ||||||
|  | { | ||||||
|  | 	tf_http_callback_phase_t phase; | ||||||
|  | 	tf_http_connection_t* connection; | ||||||
|  | 	const char* method; | ||||||
|  | 	const char* path; | ||||||
|  | 	struct phr_header* headers; | ||||||
|  | 	int headers_count; | ||||||
|  | 	void* user_data; | ||||||
|  | } tf_http_request_t; | ||||||
|  |  | ||||||
|  | typedef void (tf_http_callback_t)(tf_http_request_t* request); | ||||||
|  |  | ||||||
|  | tf_http_t* tf_http_create(uv_loop_t* loop); | ||||||
|  | void tf_http_listen(tf_http_t* http, int port); | ||||||
|  | void tf_http_add_handler(tf_http_t* http, const char* pattern, tf_http_callback_t* callback, void* user_data); | ||||||
|  | void tf_http_destroy(tf_http_t* http); | ||||||
							
								
								
									
										36
									
								
								src/tests.c
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								src/tests.c
									
									
									
									
									
								
							| @@ -1,5 +1,6 @@ | |||||||
| #include "tests.h" | #include "tests.h" | ||||||
|  |  | ||||||
|  | #include "http.h" | ||||||
| #include "log.h" | #include "log.h" | ||||||
| #include "mem.h" | #include "mem.h" | ||||||
| #include "ssb.tests.h" | #include "ssb.tests.h" | ||||||
| @@ -11,6 +12,8 @@ | |||||||
| #include <string.h> | #include <string.h> | ||||||
| #include <unistd.h> | #include <unistd.h> | ||||||
|  |  | ||||||
|  | #include <uv.h> | ||||||
|  |  | ||||||
| #if defined(_WIN32) | #if defined(_WIN32) | ||||||
| #define WIFEXITED(x) 1 | #define WIFEXITED(x) 1 | ||||||
| #define WEXITSTATUS(x) (x) | #define WEXITSTATUS(x) (x) | ||||||
| @@ -667,6 +670,38 @@ static void _test_b64(const tf_test_options_t* options) | |||||||
| 	unlink("out/test.js"); | 	unlink("out/test.js"); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static void _test_http_thread(void* data) | ||||||
|  | { | ||||||
|  | #if defined(__linux__) | ||||||
|  | 	int r = system("curl -v http://localhost:23456/"); | ||||||
|  | 	tf_printf("curl returned %d\n", WEXITSTATUS(r)); | ||||||
|  | 	assert(WEXITSTATUS(r) == 0); | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void _test_http_handler(tf_http_request_t* request) | ||||||
|  | { | ||||||
|  | 	tf_printf("HANDLER %d\n", request->phase); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void _test_http(const tf_test_options_t* options) | ||||||
|  | { | ||||||
|  | 	uv_loop_t loop = { 0 }; | ||||||
|  | 	uv_loop_init(&loop); | ||||||
|  | 	tf_http_t* http = tf_http_create(&loop); | ||||||
|  | 	tf_http_add_handler(http, NULL, _test_http_handler, NULL); | ||||||
|  | 	tf_http_listen(http, 23456); | ||||||
|  |  | ||||||
|  | 	uv_thread_t thread = { 0 }; | ||||||
|  | 	uv_thread_create(&thread, _test_http_thread, NULL); | ||||||
|  | 	uv_run(&loop, UV_RUN_DEFAULT); | ||||||
|  |  | ||||||
|  | 	tf_http_destroy(http); | ||||||
|  | 	uv_loop_close(&loop); | ||||||
|  |  | ||||||
|  | 	uv_thread_join(&thread); | ||||||
|  | } | ||||||
|  |  | ||||||
| static void _tf_test_run(const tf_test_options_t* options, const char* name, void (*test)(const tf_test_options_t* options), bool opt_in) | static void _tf_test_run(const tf_test_options_t* options, const char* name, void (*test)(const tf_test_options_t* options), bool opt_in) | ||||||
| { | { | ||||||
| 	bool specified = false; | 	bool specified = false; | ||||||
| @@ -706,6 +741,7 @@ static void _tf_test_run(const tf_test_options_t* options, const char* name, voi | |||||||
| void tf_tests(const tf_test_options_t* options) | void tf_tests(const tf_test_options_t* options) | ||||||
| { | { | ||||||
| #if !TARGET_OS_IPHONE | #if !TARGET_OS_IPHONE | ||||||
|  | 	_tf_test_run(options, "http", _test_http, false); | ||||||
| 	_tf_test_run(options, "ssb", tf_ssb_test_ssb, false); | 	_tf_test_run(options, "ssb", tf_ssb_test_ssb, false); | ||||||
| 	_tf_test_run(options, "ssb_id", tf_ssb_test_id_conversion, false); | 	_tf_test_run(options, "ssb_id", tf_ssb_test_id_conversion, false); | ||||||
| 	_tf_test_run(options, "ssb_following", tf_ssb_test_following, false); | 	_tf_test_run(options, "ssb_following", tf_ssb_test_following, false); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user