forked from cory/tildefriends
		
	Now we're uploading some data.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4682 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
		
							
								
								
									
										139
									
								
								src/http.c
									
									
									
									
									
								
							
							
						
						
									
										139
									
								
								src/http.c
									
									
									
									
									
								
							| @@ -2,11 +2,13 @@ | |||||||
|  |  | ||||||
| #include "log.h" | #include "log.h" | ||||||
| #include "mem.h" | #include "mem.h" | ||||||
|  | #include "util.js.h" | ||||||
|  |  | ||||||
| #include "picohttpparser.h" | #include "picohttpparser.h" | ||||||
| #include "uv.h" | #include "uv.h" | ||||||
|  |  | ||||||
| #include <assert.h> | #include <assert.h> | ||||||
|  | #include <stdlib.h> | ||||||
| #include <string.h> | #include <string.h> | ||||||
|  |  | ||||||
| #if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32) | #if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32) | ||||||
| @@ -18,12 +20,26 @@ typedef struct _tf_http_connection_t | |||||||
| 	tf_http_t* http; | 	tf_http_t* http; | ||||||
| 	uv_tcp_t tcp; | 	uv_tcp_t tcp; | ||||||
|  |  | ||||||
|  | 	const char* method; | ||||||
|  | 	const char* path; | ||||||
|  |  | ||||||
|  | 	char headers_buffer[8192]; | ||||||
|  | 	size_t headers_buffer_length; | ||||||
|  | 	int parsed_length; | ||||||
|  |  | ||||||
| 	char buffer[8192]; | 	char buffer[8192]; | ||||||
| 	size_t buffer_length; | 	size_t buffer_length; | ||||||
| 	int parsed_length; |  | ||||||
|  | 	struct phr_header headers[32]; | ||||||
|  | 	int headers_length; | ||||||
|  | 	bool headers_done; | ||||||
|  |  | ||||||
| 	tf_http_callback_t* callback; | 	tf_http_callback_t* callback; | ||||||
| 	void* user_data; | 	void* user_data; | ||||||
|  |  | ||||||
|  | 	void* body; | ||||||
|  | 	size_t body_length; | ||||||
|  | 	size_t content_length; | ||||||
| } tf_http_connection_t; | } tf_http_connection_t; | ||||||
|  |  | ||||||
| typedef struct _tf_http_handler_t | typedef struct _tf_http_handler_t | ||||||
| @@ -49,6 +65,7 @@ typedef struct _tf_http_t | |||||||
| } tf_http_t; | } tf_http_t; | ||||||
|  |  | ||||||
| static void _http_connection_destroy(tf_http_connection_t* connection); | static void _http_connection_destroy(tf_http_connection_t* connection); | ||||||
|  | static const char* _http_status_text(int status); | ||||||
|  |  | ||||||
| tf_http_t* tf_http_create(uv_loop_t* loop) | tf_http_t* tf_http_create(uv_loop_t* loop) | ||||||
| { | { | ||||||
| @@ -63,14 +80,21 @@ tf_http_t* tf_http_create(uv_loop_t* loop) | |||||||
| void _http_allocate_buffer(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf) | void _http_allocate_buffer(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf) | ||||||
| { | { | ||||||
| 	tf_http_connection_t* connection = handle->data; | 	tf_http_connection_t* connection = handle->data; | ||||||
|  | 	if (!connection->headers_done) | ||||||
|  | 	{ | ||||||
|  | 		*buf = uv_buf_init(connection->headers_buffer + connection->headers_buffer_length, sizeof(connection->headers_buffer) - connection->headers_buffer_length); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 	{ | ||||||
| 		*buf = uv_buf_init(connection->buffer + connection->buffer_length, sizeof(connection->buffer) - connection->buffer_length); | 		*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) | 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++) | 	for (int i = 0; i < http->handlers_count; i++) | ||||||
| 	{ | 	{ | ||||||
| 		if (!http->handlers[i].pattern) | 		if (!http->handlers[i].pattern || strcmp(path, http->handlers[i].pattern) == 0) | ||||||
| 		{ | 		{ | ||||||
| 			*out_callback = http->handlers[i].callback; | 			*out_callback = http->handlers[i].callback; | ||||||
| 			*out_user_data = http->handlers[i].user_data; | 			*out_user_data = http->handlers[i].user_data; | ||||||
| @@ -108,58 +132,105 @@ static void _http_connection_destroy(tf_http_connection_t* connection) | |||||||
| 				http->connections[i] = http->connections[--http->connections_count]; | 				http->connections[i] = http->connections[--http->connections_count]; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 		if (connection->body) | ||||||
|  | 		{ | ||||||
|  | 			tf_free(connection->body); | ||||||
|  | 			connection->body = NULL; | ||||||
|  | 		} | ||||||
| 		tf_free(connection); | 		tf_free(connection); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void _http_on_read(uv_stream_t* stream, ssize_t read_size, const uv_buf_t* buffer) | static void _http_builtin_404_handler(tf_http_request_t* request) | ||||||
|  | { | ||||||
|  | 	const char* k_payload = _http_status_text(404); | ||||||
|  | 	tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void _http_add_body_bytes(tf_http_connection_t* connection, const void* data, size_t size) | ||||||
|  | { | ||||||
|  | 	size_t fit = tf_min(connection->content_length - connection->body_length, size); | ||||||
|  | 	if (fit > 0) | ||||||
|  | 	{ | ||||||
|  | 		memcpy((char*)connection->body + connection->body_length, data, fit); | ||||||
|  | 		connection->body_length += fit; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (connection->body_length == connection->content_length) | ||||||
|  | 	{ | ||||||
|  | 		tf_http_request_t request = | ||||||
|  | 		{ | ||||||
|  | 			.connection = connection, | ||||||
|  | 			.phase = k_http_callback_phase_headers_received, | ||||||
|  | 			.method = connection->method, | ||||||
|  | 			.path = connection->path, | ||||||
|  | 			.headers = connection->headers, | ||||||
|  | 			.headers_count = connection->headers_length, | ||||||
|  | 			.user_data = connection->user_data, | ||||||
|  | 		}; | ||||||
|  | 		connection->callback(&request); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void _http_on_read(uv_stream_t* stream, ssize_t read_size, const uv_buf_t* buffer) | ||||||
| { | { | ||||||
| 	tf_http_connection_t* connection = stream->data; | 	tf_http_connection_t* connection = stream->data; | ||||||
| 	if (read_size > 0) | 	if (read_size > 0) | ||||||
| 	{ | 	{ | ||||||
| 		connection->buffer_length += read_size; | 		if (!connection->headers_done) | ||||||
|  | 		{ | ||||||
|  | 			connection->headers_buffer_length += read_size; | ||||||
|  |  | ||||||
| 			const char* method = NULL; | 			const char* method = NULL; | ||||||
| 			size_t method_length = 0; | 			size_t method_length = 0; | ||||||
| 			const char* path = NULL; | 			const char* path = NULL; | ||||||
| 			size_t path_length = 0; | 			size_t path_length = 0; | ||||||
| 			int minor_version = 0; | 			int minor_version = 0; | ||||||
| 		struct phr_header headers[100]; | 			size_t header_count = sizeof(connection->headers) / sizeof(*connection->headers); | ||||||
| 		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); | 			int parse_result = phr_parse_request(connection->headers_buffer, connection->headers_buffer_length, &method, &method_length, &path, &path_length, &minor_version, connection->headers, &header_count, connection->parsed_length); | ||||||
| 		connection->parsed_length = connection->buffer_length; | 			connection->parsed_length = connection->headers_buffer_length; | ||||||
| 			if (parse_result > 0) | 			if (parse_result > 0) | ||||||
| 			{ | 			{ | ||||||
| 			if (_http_find_handler(connection->http, path, &connection->callback, &connection->user_data) && connection->callback) | 				connection->headers_done = true; | ||||||
| 			{ | 				connection->method = method; | ||||||
| 				char* method_copy = alloca(method_length + 1); | 				((char*)connection->method)[method_length] = '\0'; | ||||||
| 				memcpy(method_copy, method, method_length); | 				connection->path = path; | ||||||
| 				method_copy[method_length] = '\0'; | 				((char*)connection->path)[path_length] = '\0'; | ||||||
|  |  | ||||||
| 				char* path_copy = alloca(path_length + 1); | 				for (int i = 0; i < (int)header_count; i++) | ||||||
| 				memcpy(path_copy, path, path_length); |  | ||||||
| 				path_copy[path_length] = '\0'; |  | ||||||
|  |  | ||||||
| 				tf_http_request_t request = |  | ||||||
| 				{ | 				{ | ||||||
| 					.connection = connection, | 					if (connection->headers[i].name_len == strlen("content-length") && | ||||||
| 					.phase = k_http_callback_phase_headers_received, | 						strncasecmp(connection->headers[i].name, "content-length", connection->headers[i].name_len) == 0) | ||||||
| 					.method = method_copy, | 					{ | ||||||
| 					.path = path_copy, | 						connection->content_length = strtoull(connection->headers[i].value, NULL, 10); | ||||||
| 					.headers = headers, |  | ||||||
| 					.headers_count = (int)header_count, |  | ||||||
| 					.user_data = connection->user_data, |  | ||||||
| 				}; |  | ||||||
| 				connection->callback(&request); |  | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | 				if (connection->content_length) | ||||||
|  | 				{ | ||||||
|  | 					connection->body = tf_malloc(connection->content_length); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if (!_http_find_handler(connection->http, connection->path, &connection->callback, &connection->user_data) || !connection->callback) | ||||||
|  | 				{ | ||||||
|  | 					connection->callback = _http_builtin_404_handler; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				_http_add_body_bytes(connection, connection->headers_buffer + parse_result, connection->headers_buffer_length - parse_result); | ||||||
|  | 			} | ||||||
| 			else | 			else | ||||||
| 			{ | 			{ | ||||||
| 				tf_printf("phr_parse_request: %d\n", parse_result); | 				tf_printf("phr_parse_request: %d\n", parse_result); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		else | 		else | ||||||
|  | 		{ | ||||||
|  | 			connection->buffer_length += read_size; | ||||||
|  | 			_http_add_body_bytes(connection, connection->buffer, connection->buffer_length); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
| 	{ | 	{ | ||||||
| 		_http_connection_destroy(connection); | 		_http_connection_destroy(connection); | ||||||
| 	} | 	} | ||||||
| @@ -268,6 +339,14 @@ void tf_http_destroy(tf_http_t* http) | |||||||
| 	http->listeners = NULL; | 	http->listeners = NULL; | ||||||
| 	http->listeners_count = 0; | 	http->listeners_count = 0; | ||||||
|  |  | ||||||
|  | 	for (int i = 0; i < http->handlers_count; i++) | ||||||
|  | 	{ | ||||||
|  | 		if (http->handlers[i].pattern) | ||||||
|  | 		{ | ||||||
|  | 			tf_free((void*)http->handlers[i].pattern); | ||||||
|  | 			http->handlers[i].pattern = NULL; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 	tf_free(http->handlers); | 	tf_free(http->handlers); | ||||||
| 	http->handlers_count = 0; | 	http->handlers_count = 0; | ||||||
|  |  | ||||||
| @@ -347,3 +426,9 @@ void tf_http_respond(tf_http_request_t* request, int status, const char** header | |||||||
| 	*shutdown_request = (uv_shutdown_t) { .data = request }; | 	*shutdown_request = (uv_shutdown_t) { .data = request }; | ||||||
| 	uv_shutdown(shutdown_request, (uv_stream_t*)&request->connection->tcp, _http_on_shutdown); | 	uv_shutdown(shutdown_request, (uv_stream_t*)&request->connection->tcp, _http_on_shutdown); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | size_t tf_http_get_body(const tf_http_request_t* request, const void** out_data) | ||||||
|  | { | ||||||
|  | 	*out_data = request->connection->body; | ||||||
|  | 	return request->connection->content_length; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -29,4 +29,5 @@ tf_http_t* tf_http_create(uv_loop_t* loop); | |||||||
| void tf_http_listen(tf_http_t* http, int port); | 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_add_handler(tf_http_t* http, const char* pattern, tf_http_callback_t* callback, void* user_data); | ||||||
| void tf_http_respond(tf_http_request_t* request, int status, const char** headers, int headers_count, const void* body, size_t content_length); | void tf_http_respond(tf_http_request_t* request, int status, const char** headers, int headers_count, const void* body, size_t content_length); | ||||||
|  | size_t tf_http_get_body(const tf_http_request_t* request, const void** out_data); | ||||||
| void tf_http_destroy(tf_http_t* http); | void tf_http_destroy(tf_http_t* http); | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								src/tests.c
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								src/tests.c
									
									
									
									
									
								
							| @@ -685,7 +685,15 @@ static void _test_http_thread(void* data) | |||||||
| { | { | ||||||
| #if defined(__linux__) | #if defined(__linux__) | ||||||
| 	test_http_t* test = data; | 	test_http_t* test = data; | ||||||
| 	int r = system("curl -v http://localhost:23456/"); | 	int r = system("curl -v http://localhost:23456/404"); | ||||||
|  | 	assert(WEXITSTATUS(r) == 0); | ||||||
|  | 	tf_printf("curl returned %d\n", WEXITSTATUS(r)); | ||||||
|  |  | ||||||
|  | 	r = system("curl -v http://localhost:23456/hello"); | ||||||
|  | 	assert(WEXITSTATUS(r) == 0); | ||||||
|  | 	tf_printf("curl returned %d\n", WEXITSTATUS(r)); | ||||||
|  |  | ||||||
|  | 	r = system("curl -v --data 'hello world' http://localhost:23456/post"); | ||||||
| 	assert(WEXITSTATUS(r) == 0); | 	assert(WEXITSTATUS(r) == 0); | ||||||
| 	tf_printf("curl returned %d\n", WEXITSTATUS(r)); | 	tf_printf("curl returned %d\n", WEXITSTATUS(r)); | ||||||
|  |  | ||||||
| @@ -706,12 +714,27 @@ static void _test_http_handler(tf_http_request_t* request) | |||||||
| 	tf_http_respond(request, 200, headers, 1, k_payload, strlen(k_payload)); | 	tf_http_respond(request, 200, headers, 1, k_payload, strlen(k_payload)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static void _test_http_handler_post(tf_http_request_t* request) | ||||||
|  | { | ||||||
|  | 	const void* body = NULL; | ||||||
|  | 	size_t size = tf_http_get_body(request, &body); | ||||||
|  | 	tf_printf("size = %zd body=%.*s\n", size, (int)size, (const char*)body); | ||||||
|  | 	const char* headers[] = | ||||||
|  | 	{ | ||||||
|  | 		"Connection", "close", | ||||||
|  | 	}; | ||||||
|  | 	const char* k_payload = "Hello, world!\n"; | ||||||
|  | 	tf_http_respond(request, 200, headers, 1, k_payload, strlen(k_payload)); | ||||||
|  | } | ||||||
|  |  | ||||||
| static void _test_http(const tf_test_options_t* options) | static void _test_http(const tf_test_options_t* options) | ||||||
| { | { | ||||||
|  | 	tf_printf("Starting http.\n"); | ||||||
| 	uv_loop_t loop = { 0 }; | 	uv_loop_t loop = { 0 }; | ||||||
| 	uv_loop_init(&loop); | 	uv_loop_init(&loop); | ||||||
| 	tf_http_t* http = tf_http_create(&loop); | 	tf_http_t* http = tf_http_create(&loop); | ||||||
| 	tf_http_add_handler(http, NULL, _test_http_handler, NULL); | 	tf_http_add_handler(http, "/hello", _test_http_handler, NULL); | ||||||
|  | 	tf_http_add_handler(http, "/post", _test_http_handler_post, NULL); | ||||||
| 	tf_http_listen(http, 23456); | 	tf_http_listen(http, 23456); | ||||||
|  |  | ||||||
| 	test_http_t test = { .loop = &loop }; | 	test_http_t test = { .loop = &loop }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user