forked from cory/tildefriends
		
	Some quick http refactors to make websockets less magic.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4705 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
		| @@ -954,6 +954,7 @@ function stringResponse(response, data) { | |||||||
| loadSettings().then(function() { | loadSettings().then(function() { | ||||||
| 	let httpd_impl = (tildefriends.args.httpdc ? httpdc : httpd); | 	let httpd_impl = (tildefriends.args.httpdc ? httpdc : httpd); | ||||||
| 	httpd_impl.all("/login", auth.handler); | 	httpd_impl.all("/login", auth.handler); | ||||||
|  | 	httpd_impl.registerSocketHandler("/app/socket", app.socket); | ||||||
| 	httpd_impl.all("", function(request, response) { | 	httpd_impl.all("", function(request, response) { | ||||||
| 		let match; | 		let match; | ||||||
| 		if (request.uri === "/" || request.uri === "") { | 		if (request.uri === "/" || request.uri === "") { | ||||||
| @@ -1004,7 +1005,6 @@ loadSettings().then(function() { | |||||||
| 			return response.end(data); | 			return response.end(data); | ||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
| 	httpd_impl.registerSocketHandler("/app/socket", app.socket); |  | ||||||
| 	httpd_impl.start(tildefriends.http_port); | 	httpd_impl.start(tildefriends.http_port); | ||||||
| }).catch(function(error) { | }).catch(function(error) { | ||||||
| 	print('Failed to load settings.'); | 	print('Failed to load settings.'); | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								src/http.c
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								src/http.c
									
									
									
									
									
								
							| @@ -40,7 +40,6 @@ typedef struct _tf_http_connection_t | |||||||
| 	int headers_length; | 	int headers_length; | ||||||
| 	bool headers_done; | 	bool headers_done; | ||||||
|  |  | ||||||
| 	int flags; |  | ||||||
| 	tf_http_callback_t* callback; | 	tf_http_callback_t* callback; | ||||||
| 	tf_http_request_t* request; | 	tf_http_request_t* request; | ||||||
| 	void* user_data; | 	void* user_data; | ||||||
| @@ -56,7 +55,6 @@ typedef struct _tf_http_connection_t | |||||||
| typedef struct _tf_http_handler_t | typedef struct _tf_http_handler_t | ||||||
| { | { | ||||||
| 	const char* pattern; | 	const char* pattern; | ||||||
| 	int flags; |  | ||||||
| 	tf_http_callback_t* callback; | 	tf_http_callback_t* callback; | ||||||
| 	void* user_data; | 	void* user_data; | ||||||
| } tf_http_handler_t; | } tf_http_handler_t; | ||||||
| @@ -103,14 +101,13 @@ void _http_allocate_buffer(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| bool _http_find_handler(tf_http_t* http, const char* path, int flags, 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].flags == flags && | 		if (!http->handlers[i].pattern || | ||||||
| 			(!http->handlers[i].pattern || |  | ||||||
| 			strcmp(path, http->handlers[i].pattern) == 0 || | 			strcmp(path, http->handlers[i].pattern) == 0 || | ||||||
| 			(strncmp(path, http->handlers[i].pattern, strlen(http->handlers[i].pattern)) == 0 && path[strlen(http->handlers[i].pattern)] == '/'))) | 			(strncmp(path, http->handlers[i].pattern, strlen(http->handlers[i].pattern)) == 0 && path[strlen(http->handlers[i].pattern)] == '/')) | ||||||
| 		{ | 		{ | ||||||
| 			*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; | ||||||
| @@ -271,10 +268,6 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d | |||||||
|  |  | ||||||
| 		if (connection->body_length == connection->content_length) | 		if (connection->body_length == connection->content_length) | ||||||
| 		{ | 		{ | ||||||
| 			if (connection->flags & k_tf_http_handler_flag_websocket) |  | ||||||
| 			{ |  | ||||||
| 				connection->is_websocket = true; |  | ||||||
| 			} |  | ||||||
| 			tf_http_request_t* request = tf_malloc(sizeof(tf_http_request_t)); | 			tf_http_request_t* request = tf_malloc(sizeof(tf_http_request_t)); | ||||||
| 			*request = (tf_http_request_t) | 			*request = (tf_http_request_t) | ||||||
| 			{ | 			{ | ||||||
| @@ -282,7 +275,6 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d | |||||||
| 				.phase = k_http_callback_phase_headers_received, | 				.phase = k_http_callback_phase_headers_received, | ||||||
| 				.method = connection->method, | 				.method = connection->method, | ||||||
| 				.path = connection->path, | 				.path = connection->path, | ||||||
| 				.flags = connection->flags, |  | ||||||
| 				.query = connection->query, | 				.query = connection->query, | ||||||
| 				.body = connection->body, | 				.body = connection->body, | ||||||
| 				.content_length = connection->content_length, | 				.content_length = connection->content_length, | ||||||
| @@ -367,9 +359,7 @@ static void _http_on_read(uv_stream_t* stream, ssize_t read_size, const uv_buf_t | |||||||
| 					connection->body = tf_malloc(connection->content_length); | 					connection->body = tf_malloc(connection->content_length); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				int flags = _http_connection_get_header(connection, "upgrade") ? k_tf_http_handler_flag_websocket : 0; | 				if (!_http_find_handler(connection->http, connection->path, &connection->callback, &connection->user_data) || !connection->callback) | ||||||
| 				connection->flags = flags; |  | ||||||
| 				if (!_http_find_handler(connection->http, connection->path, flags, &connection->callback, &connection->user_data) || !connection->callback) |  | ||||||
| 				{ | 				{ | ||||||
| 					connection->callback = _http_builtin_404_handler; | 					connection->callback = _http_builtin_404_handler; | ||||||
| 				} | 				} | ||||||
| @@ -475,13 +465,12 @@ void tf_http_listen(tf_http_t* http, int port) | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void tf_http_add_handler(tf_http_t* http, const char* pattern, int flags, 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) | ||||||
| { | { | ||||||
| 	http->handlers = tf_realloc(http->handlers, sizeof(tf_http_handler_t) * (http->handlers_count + 1)); | 	http->handlers = tf_realloc(http->handlers, sizeof(tf_http_handler_t) * (http->handlers_count + 1)); | ||||||
| 	http->handlers[http->handlers_count++] = (tf_http_handler_t) | 	http->handlers[http->handlers_count++] = (tf_http_handler_t) | ||||||
| 	{ | 	{ | ||||||
| 		.pattern = tf_strdup(pattern), | 		.pattern = tf_strdup(pattern), | ||||||
| 		.flags = flags, |  | ||||||
| 		.callback = callback, | 		.callback = callback, | ||||||
| 		.user_data = user_data, | 		.user_data = user_data, | ||||||
| 	}; | 	}; | ||||||
| @@ -664,3 +653,8 @@ const char* tf_http_request_get_header(tf_http_request_t* request, const char* n | |||||||
| { | { | ||||||
| 	return _http_connection_get_header(request->connection, name); | 	return _http_connection_get_header(request->connection, name); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void tf_http_request_websocket_upgrade(tf_http_request_t* request) | ||||||
|  | { | ||||||
|  | 	request->connection->is_websocket = true; | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								src/http.h
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/http.h
									
									
									
									
									
								
							| @@ -21,7 +21,6 @@ typedef struct _tf_http_request_t | |||||||
| 	tf_http_connection_t* connection; | 	tf_http_connection_t* connection; | ||||||
| 	const char* method; | 	const char* method; | ||||||
| 	const char* path; | 	const char* path; | ||||||
| 	int flags; |  | ||||||
| 	const char* query; | 	const char* query; | ||||||
| 	void* body; | 	void* body; | ||||||
| 	size_t content_length; | 	size_t content_length; | ||||||
| @@ -33,17 +32,11 @@ typedef struct _tf_http_request_t | |||||||
| 	int ref_count; | 	int ref_count; | ||||||
| } tf_http_request_t; | } tf_http_request_t; | ||||||
|  |  | ||||||
| typedef enum _tf_http_handler_flags_t |  | ||||||
| { |  | ||||||
| 	k_tf_http_handler_flag_none = 0, |  | ||||||
| 	k_tf_http_handler_flag_websocket = 1, |  | ||||||
| } tf_http_handler_flags_t; |  | ||||||
|  |  | ||||||
| typedef void (tf_http_callback_t)(tf_http_request_t* request); | typedef void (tf_http_callback_t)(tf_http_request_t* request); | ||||||
|  |  | ||||||
| tf_http_t* tf_http_create(uv_loop_t* loop); | 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, int flags, 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); | 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); | ||||||
| @@ -52,3 +45,4 @@ void tf_http_request_ref(tf_http_request_t* request); | |||||||
| void tf_http_request_release(tf_http_request_t* request); | void tf_http_request_release(tf_http_request_t* request); | ||||||
| const char* tf_http_request_get_header(tf_http_request_t* request, const char* name); | const char* tf_http_request_get_header(tf_http_request_t* request, const char* name); | ||||||
| void tf_http_request_send(tf_http_request_t* request, const void* data, size_t size); | void tf_http_request_send(tf_http_request_t* request, const void* data, size_t size); | ||||||
|  | void tf_http_request_websocket_upgrade(tf_http_request_t* request); | ||||||
|   | |||||||
| @@ -162,44 +162,8 @@ static void _httpd_message_callback(tf_http_request_t* request, int op_code, con | |||||||
| 	JS_FreeValue(context, on_message); | 	JS_FreeValue(context, on_message); | ||||||
| } | } | ||||||
|  |  | ||||||
| static void _httpd_callback(tf_http_request_t* request) | static void _httpd_callback_internal(tf_http_request_t* request, bool is_websocket) | ||||||
| { | { | ||||||
| 	if (request->flags & k_tf_http_handler_flag_websocket) |  | ||||||
| 	{ |  | ||||||
| 		const char* header_connection = tf_http_request_get_header(request, "connection"); |  | ||||||
| 		const char* header_upgrade = tf_http_request_get_header(request, "upgrade"); |  | ||||||
| 		const char* header_sec_websocket_key = tf_http_request_get_header(request, "sec-websocket-key"); |  | ||||||
| 		if (header_connection && |  | ||||||
| 			header_upgrade && |  | ||||||
| 			header_sec_websocket_key && |  | ||||||
| 			strstr(header_connection, "Upgrade") && |  | ||||||
| 			strcasecmp(header_upgrade, "websocket") == 0) |  | ||||||
| 		{ |  | ||||||
| 			static const char* k_magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; |  | ||||||
| 			size_t key_length = strlen(header_sec_websocket_key); |  | ||||||
| 			size_t size = key_length + 36; |  | ||||||
| 			uint8_t* key_magic = alloca(size); |  | ||||||
| 			memcpy(key_magic, header_sec_websocket_key, key_length); |  | ||||||
| 			memcpy(key_magic + key_length, k_magic, 36); |  | ||||||
| 			uint8_t digest[20]; |  | ||||||
| 			SHA1(key_magic, size, digest); |  | ||||||
| 			char key[41] = { 0 }; |  | ||||||
| 			tf_base64_encode(digest, sizeof(digest), key, sizeof(key)); |  | ||||||
| 			enum { k_headers_count = 4 }; |  | ||||||
| 			const char* headers[k_headers_count * 2] = |  | ||||||
| 			{ |  | ||||||
| 				"Upgrade", "websocket", |  | ||||||
| 				"Connection", "Upgrade", |  | ||||||
| 				"Sec-WebSocket-Accept", key, |  | ||||||
| 				"Sec-WebSocket-Version", "13", |  | ||||||
| 			}; |  | ||||||
| 			bool send_version = |  | ||||||
| 				!tf_http_request_get_header(request, "sec-websocket-version") || |  | ||||||
| 				strcmp(tf_http_request_get_header(request, "sec-websocket-version"), "13") != 0; |  | ||||||
| 			tf_http_respond(request, 101, headers, send_version ? k_headers_count : (k_headers_count - 1), NULL, 0); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	http_handler_data_t* data = request->user_data; | 	http_handler_data_t* data = request->user_data; | ||||||
| 	JSContext* context = data->context; | 	JSContext* context = data->context; | ||||||
| 	JSValue request_object = JS_NewObject(context); | 	JSValue request_object = JS_NewObject(context); | ||||||
| @@ -238,12 +202,67 @@ static void _httpd_callback(tf_http_request_t* request) | |||||||
| 	JSValue response = JS_Call(context, data->callback, JS_UNDEFINED, 2, args); | 	JSValue response = JS_Call(context, data->callback, JS_UNDEFINED, 2, args); | ||||||
| 	tf_util_report_error(context, response); | 	tf_util_report_error(context, response); | ||||||
| 	JS_FreeValue(context, request_object); | 	JS_FreeValue(context, request_object); | ||||||
| 	//JS_FreeValue(context, response_object); |  | ||||||
| 	JS_FreeValue(context, response); | 	JS_FreeValue(context, response); | ||||||
|  |  | ||||||
|  | 	if (is_websocket) | ||||||
|  | 	{ | ||||||
| 		request->on_message = _httpd_message_callback; | 		request->on_message = _httpd_message_callback; | ||||||
| 		request->context = context; | 		request->context = context; | ||||||
| 		request->user_data = JS_VALUE_GET_PTR(response_object); | 		request->user_data = JS_VALUE_GET_PTR(response_object); | ||||||
| 	} | 	} | ||||||
|  | 	else | ||||||
|  | 	{ | ||||||
|  | 		JS_FreeValue(context, response_object); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void _httpd_callback(tf_http_request_t* request) | ||||||
|  | { | ||||||
|  | 	_httpd_callback_internal(request, false); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void _httpd_websocket_callback(tf_http_request_t* request) | ||||||
|  | { | ||||||
|  | 	const char* header_connection = tf_http_request_get_header(request, "connection"); | ||||||
|  | 	const char* header_upgrade = tf_http_request_get_header(request, "upgrade"); | ||||||
|  | 	const char* header_sec_websocket_key = tf_http_request_get_header(request, "sec-websocket-key"); | ||||||
|  | 	if (header_connection && | ||||||
|  | 		header_upgrade && | ||||||
|  | 		header_sec_websocket_key && | ||||||
|  | 		strstr(header_connection, "Upgrade") && | ||||||
|  | 		strcasecmp(header_upgrade, "websocket") == 0) | ||||||
|  | 	{ | ||||||
|  | 		static const char* k_magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; | ||||||
|  | 		size_t key_length = strlen(header_sec_websocket_key); | ||||||
|  | 		size_t size = key_length + 36; | ||||||
|  | 		uint8_t* key_magic = alloca(size); | ||||||
|  | 		memcpy(key_magic, header_sec_websocket_key, key_length); | ||||||
|  | 		memcpy(key_magic + key_length, k_magic, 36); | ||||||
|  | 		uint8_t digest[20]; | ||||||
|  | 		SHA1(key_magic, size, digest); | ||||||
|  | 		char key[41] = { 0 }; | ||||||
|  | 		tf_base64_encode(digest, sizeof(digest), key, sizeof(key)); | ||||||
|  | 		enum { k_headers_count = 4 }; | ||||||
|  | 		const char* headers[k_headers_count * 2] = | ||||||
|  | 		{ | ||||||
|  | 			"Upgrade", "websocket", | ||||||
|  | 			"Connection", "Upgrade", | ||||||
|  | 			"Sec-WebSocket-Accept", key, | ||||||
|  | 			"Sec-WebSocket-Version", "13", | ||||||
|  | 		}; | ||||||
|  | 		bool send_version = | ||||||
|  | 			!tf_http_request_get_header(request, "sec-websocket-version") || | ||||||
|  | 			strcmp(tf_http_request_get_header(request, "sec-websocket-version"), "13") != 0; | ||||||
|  | 		tf_http_request_websocket_upgrade(request); | ||||||
|  | 		tf_http_respond(request, 101, headers, send_version ? k_headers_count : (k_headers_count - 1), NULL, 0); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 	{ | ||||||
|  | 		tf_http_respond(request, 400, NULL, 0, NULL, 0); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_httpd_callback_internal(request, true); | ||||||
|  | } | ||||||
|  |  | ||||||
| static JSValue _httpd_all(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) | static JSValue _httpd_all(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) | ||||||
| { | { | ||||||
| @@ -251,7 +270,7 @@ static JSValue _httpd_all(JSContext* context, JSValueConst this_val, int argc, J | |||||||
| 	const char* pattern = JS_ToCString(context, argv[0]); | 	const char* pattern = JS_ToCString(context, argv[0]); | ||||||
| 	http_handler_data_t* data = tf_malloc(sizeof(http_handler_data_t)); | 	http_handler_data_t* data = tf_malloc(sizeof(http_handler_data_t)); | ||||||
| 	*data = (http_handler_data_t) { .context = context, .callback = JS_DupValue(context, argv[1]) }; | 	*data = (http_handler_data_t) { .context = context, .callback = JS_DupValue(context, argv[1]) }; | ||||||
| 	tf_http_add_handler(http, pattern, k_tf_http_handler_flag_none, _httpd_callback, data); | 	tf_http_add_handler(http, pattern, _httpd_callback, data); | ||||||
| 	JS_FreeCString(context, pattern); | 	JS_FreeCString(context, pattern); | ||||||
| 	return JS_UNDEFINED; | 	return JS_UNDEFINED; | ||||||
| } | } | ||||||
| @@ -262,7 +281,7 @@ static JSValue _httpd_register_socket_handler(JSContext* context, JSValueConst t | |||||||
| 	const char* pattern = JS_ToCString(context, argv[0]); | 	const char* pattern = JS_ToCString(context, argv[0]); | ||||||
| 	http_handler_data_t* data = tf_malloc(sizeof(http_handler_data_t)); | 	http_handler_data_t* data = tf_malloc(sizeof(http_handler_data_t)); | ||||||
| 	*data = (http_handler_data_t) { .context = context, .callback = JS_DupValue(context, argv[1]) }; | 	*data = (http_handler_data_t) { .context = context, .callback = JS_DupValue(context, argv[1]) }; | ||||||
| 	tf_http_add_handler(http, pattern, k_tf_http_handler_flag_websocket, _httpd_callback, data); | 	tf_http_add_handler(http, pattern, _httpd_websocket_callback, data); | ||||||
| 	JS_FreeCString(context, pattern); | 	JS_FreeCString(context, pattern); | ||||||
| 	return JS_UNDEFINED; | 	return JS_UNDEFINED; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -737,8 +737,8 @@ static void _test_http(const tf_test_options_t* options) | |||||||
| 	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, "/hello", k_tf_http_handler_flag_none, _test_http_handler, NULL); | 	tf_http_add_handler(http, "/hello", _test_http_handler, NULL); | ||||||
| 	tf_http_add_handler(http, "/post", k_tf_http_handler_flag_none, _test_http_handler_post, 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