doubango/tinyHTTP/test/test_transport.h
c732d49e
 #ifndef _TEST_HTTP_TRANSPORT_H_
 #define _TEST_HTTP_TRANSPORT_H_
 
 #define TEST_HTTP_CAT_(A, B) A ## B
 #define TEST_HTTP_CAT(A, B) TEST_HTTP_CAT_(A, B)
 #define TEST_HTTP_STRING_(A) #A
 #define TEST_HTTP_STRING(A) TEST_HTTP_STRING_(A)
 
 #define TEST_HTTP_LOCAL_IP				"0.0.0.0"
 #define TEST_HTTP_LOCAL_PORT			8080
 #define TEST_HTTP_REMOTE_IP				"google.com"
 #define TEST_HTTP_REMOTE_PORT			80
 #define TEST_HTTP_MIN_STREAM_CHUNCK_SIZE	0x32
 #define TEST_HTTP_GET "GET / HTTP/1.1\r\n" \
 	"Host: " TEST_HTTP_REMOTE_IP ":" TEST_HTTP_STRING(TEST_HTTP_REMOTE_PORT) "\r\n" \
 	"Connection: keep-alive\r\n" \
 	"Cache-Control: max-age=0\r\n" \
 	"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" \
 	"User-Agent: Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko)\r\n" \
 	"Doubango 2.0\r\n" \
 	"Accept-Encoding: gzip,deflate,sdch\r\n" \
 	"Accept-Language: en-US,en;q=0.8\r\n" \
 	"\r\n" \
 
 static int test_http_transport_callback(const tnet_transport_event_t* e);
 
 /************************************************
 * test_http_peer_t
 ************************************************/
 typedef struct test_http_peer_s
 {
 	TSK_DECLARE_OBJECT;
 	tnet_fd_t fd;
 	tsk_buffer_t* buff;
 }
 test_http_peer_t;
 typedef tsk_list_t test_http_peers_L_t;
 
 static tsk_object_t* test_http_peer_ctor(tsk_object_t * self, va_list * app) { return self; }
 static tsk_object_t* test_http_peer_dtor(tsk_object_t * self)
 { 
 	test_http_peer_t *peer = self;
 	if (peer) {
 		TSK_OBJECT_SAFE_FREE(peer->buff);
 	}
 
 	return self;
 }
 static const tsk_object_def_t test_http_peer_def_s = 
 {
 	sizeof(test_http_peer_t),
 	test_http_peer_ctor, 
 	test_http_peer_dtor,
 	tsk_null, 
 };
 
 static test_http_peer_t* test_http_peer_create(tnet_fd_t fd)
 {
 	test_http_peer_t* peer = tsk_object_new(&test_http_peer_def_s);
 	peer->fd = fd;
 	peer->buff = tsk_buffer_create_null();
 	return peer;
 }
 
 static int test_http_peer_pred_fd_cmp(const tsk_list_item_t* item, const void* data)
 {
 	return ((test_http_peer_t*)item->data)->fd - *((const tnet_fd_t*)data);
 }
 
 
 /************************************************
 * test_http_transport_t
 ************************************************/
 typedef struct test_http_transport_s
 {
 	TSK_DECLARE_OBJECT;
 
 	test_http_peers_L_t* peers;
 	tnet_transport_handle_t* handle;
 }
 test_http_transport_t;
 
 static tsk_object_t* test_http_transport_ctor(tsk_object_t * self, va_list * app)
 { 
 	test_http_transport_t *transport = self;
 	if (transport) {
 	}
 	return self; 
 }
 static tsk_object_t* test_http_transport_dtor(tsk_object_t * self)
 { 
 	test_http_transport_t *transport = self;
 	if(transport){
 		TSK_OBJECT_SAFE_FREE(transport->handle);
 		TSK_OBJECT_SAFE_FREE(transport->peers);
 	}
 
 	return self;
 }
 static const tsk_object_def_t test_http_transport_def_s = 
 {
 	sizeof(test_http_transport_t),
 	test_http_transport_ctor, 
 	test_http_transport_dtor,
 	tsk_null, 
 };
 
 static test_http_transport_t* test_http_transport_create(const char* local_ip, tnet_port_t local_port, enum tnet_socket_type_e socket_type)
 {
 	test_http_transport_t *transport = tsk_object_new(&test_http_transport_def_s);
 	if (transport) {
 		transport->peers = tsk_list_create();
 		transport->handle = tnet_transport_create(local_ip, local_port, socket_type, "HTTP Transport");
 		if (!transport->handle) {
 			TSK_OBJECT_SAFE_FREE(transport);
 			return tsk_null;
 		}
 		tnet_transport_set_callback(transport->handle, test_http_transport_callback, transport);
 	}
 	return transport;
 }
 
 static int test_http_transport_start(test_http_transport_t* self)
 {
 	if (self) {
 		return tnet_transport_start(self->handle);
 	}
 	return -1;
 }
 
 // Up to the caller to free the returned object using TSK_OBJECT_SAFREE(peer)
 static test_http_peer_t* test_http_transport_connect_to(test_http_transport_t* self, const char* dst_host, tnet_port_t dst_port)
 {
 	tnet_fd_t fd = tnet_transport_connectto_2(self->handle, dst_host, dst_port);
 	if (fd > 0) {
 		return test_http_peer_create(fd);
 	}
 	return tsk_null;
 }
 
 static tsk_size_t test_http_transport_send_data(test_http_transport_t* self, tnet_fd_t fd, const void* data_ptr, tsk_size_t data_size)
 {
 	if (self && self->handle && data_ptr && data_size) {
 		return tnet_transport_send(self->handle, fd, data_ptr, data_size);
 	}
 	return 0;
 }
 
 static void test_http_transport_add_peer(test_http_transport_t* self, test_http_peer_t* peer)
 {
 	tsk_list_lock(self->peers);
 	tsk_list_push_back_data(self->peers, &peer);
 	tsk_list_unlock(self->peers);
 }
 
 static void test_http_transport_remove_peer_by_fd(test_http_transport_t* self, tnet_fd_t fd)
 {
 	tsk_list_lock(self->peers);
 	tsk_list_remove_item_by_pred(self->peers, test_http_peer_pred_fd_cmp, &fd);
 	tsk_list_unlock(self->peers);
 }
 
 static const test_http_peer_t* test_http_transport_find_peer_by_fd(test_http_transport_t* self, tnet_fd_t fd)
 {
 	const tsk_list_item_t* item;
 	tsk_list_lock(self->peers);
 	item = tsk_list_find_item_by_pred(self->peers, test_http_peer_pred_fd_cmp, &fd);
 	tsk_list_unlock(self->peers);
 	if (item) {
 		return (const test_http_peer_t*)item->data;
 	}
 	return tsk_null;
 }
 
 static void test_http_transport_process_incoming_msg(test_http_transport_t* self, const thttp_message_t* msg, tnet_fd_t fd)
 {
 	if (THTTP_MESSAGE_IS_REQUEST(msg)) {
 		if (tsk_striequals(msg->line.request.method, "GET")) {
 			char* result = tsk_null;
 			const char* content = "<html><body>Hello world!</body></html>";
 			int len = tsk_sprintf(
 			(char**)&result, 
 			"HTTP/1.1 %u %s\r\n"
 			"Server: My test server \r\n"
 			"Access-Control-Allow-Origin: *\r\n"
 			"Content-Length: %u\r\n"
 			"Content-Type: text/html\r\n"
 			"Connection: Close\r\n"
 			"\r\n"
 			"%s", 200, "OK", tsk_strlen(content), content);
 			tnet_transport_send(self, fd, result, len);
 			TSK_FREE(result);
 		}
 	}
 }
 
 static int test_http_transport_callback(const tnet_transport_event_t* e)
 {
 	test_http_transport_t* transport = (test_http_transport_t*)e->callback_data;
 	const test_http_peer_t* _peer;
 	thttp_message_t *message = tsk_null;
 	int endOfheaders = -1;
 	tsk_ragel_state_t state;
 	tsk_bool_t have_all_content = tsk_false;
 	int ret;
 	switch (e->type)
 	{
 		case event_closed:
 			{
 				test_http_transport_remove_peer_by_fd(transport, e->local_fd);
 				return 0;
 			}
 
 		case event_connected:
 		case event_accepted:
 			{
 				_peer = test_http_transport_find_peer_by_fd(transport, e->local_fd);
 				if (!_peer) {
 					_peer = test_http_peer_create(e->local_fd);
 					test_http_transport_add_peer(transport, (test_http_peer_t*)_peer);
 				}
 				return 0;
 			}
 
 		case event_data:
 			{
 				TSK_DEBUG_INFO("\n\nRECV: %.*s\n\n", e->size, (const char*)e->data);
 				break;
 			}
 		default:
 			return 0;
 	}
 
 
 	_peer = test_http_transport_find_peer_by_fd(transport, e->local_fd);
 	if(!_peer) {
 		TSK_DEBUG_ERROR("Data event but no peer found!");
 		return -1;
 	}
 
 	/* Append new content. */
 	tsk_buffer_append(_peer->buff, e->data, e->size);
 
 	/* Check if we have all HTTP headers. */
 parse_buffer:
 	if ((endOfheaders = tsk_strindexOf(TSK_BUFFER_DATA(_peer->buff), TSK_BUFFER_SIZE(_peer->buff), "\r\n\r\n"/*2CRLF*/)) < 0) {
 		TSK_DEBUG_INFO("No all HTTP headers in the TCP buffer.");
 		goto bail;
 	}
 	
 	/* If we are here this mean that we have all HTTP headers.
 	*	==> Parse the HTTP message without the content.
 	*/
 	tsk_ragel_state_init(&state, TSK_BUFFER_DATA(_peer->buff), endOfheaders + 4/*2CRLF*/);
 	if (!(ret = thttp_message_parse(&state, &message, tsk_false/* do not extract the content */))) {
 		const thttp_header_Transfer_Encoding_t* transfer_Encoding;
 
 		/* chunked? */
 		if((transfer_Encoding = (const thttp_header_Transfer_Encoding_t*)thttp_message_get_header(message, thttp_htype_Transfer_Encoding)) && tsk_striequals(transfer_Encoding->encoding, "chunked")){
 			const char* start = (const char*)(TSK_BUFFER_TO_U8(_peer->buff) + (endOfheaders + 4/*2CRLF*/));
 			const char* end = (const char*)(TSK_BUFFER_TO_U8(_peer->buff) + TSK_BUFFER_SIZE(_peer->buff));
 			int index;
 
 			TSK_DEBUG_INFO("CHUNKED transfer.");
 			while(start < end){
 				/* RFC 2616 - 19.4.6 Introduction of Transfer-Encoding */
 				// read chunk-size, chunk-extension (if any) and CRLF
 				tsk_size_t chunk_size = (tsk_size_t)tsk_atox(start);
 				if((index = tsk_strindexOf(start, (end-start), "\r\n")) >=0){
 					start += index + 2/*CRLF*/;
 				}
 				else{
 					TSK_DEBUG_INFO("Parsing chunked data has failed.");
 					break;
 				}
 
 				if(chunk_size == 0 && ((start + 2) <= end) && *start == '\r' && *(start+ 1) == '\n'){
 					int parsed_len = (start - (const char*)(TSK_BUFFER_TO_U8(_peer->buff))) + 2/*CRLF*/;
 					tsk_buffer_remove(_peer->buff, 0, parsed_len);
 					have_all_content = tsk_true;
 					break;
 				}
 					
 				thttp_message_append_content(message, start, chunk_size);
 				start += chunk_size + 2/*CRLF*/;
 			}
 		}
 		else{
 			tsk_size_t clen = THTTP_MESSAGE_CONTENT_LENGTH(message); /* MUST have content-length header. */
 			if(clen == 0){ /* No content */
 				tsk_buffer_remove(_peer->buff, 0, (endOfheaders + 4/*2CRLF*/)); /* Remove HTTP headers and CRLF ==> must never happen */
 				have_all_content = tsk_true;
 			}
 			else{ /* There is a content */
 				if((endOfheaders + 4/*2CRLF*/ + clen) > TSK_BUFFER_SIZE(_peer->buff)){ /* There is content but not all the content. */
 					TSK_DEBUG_INFO("No all HTTP content in the TCP buffer.");
 					goto bail;
 				}
 				else{
 					/* Add the content to the message. */
 					thttp_message_add_content(message, tsk_null, TSK_BUFFER_TO_U8(_peer->buff) + endOfheaders + 4/*2CRLF*/, clen);
 					/* Remove HTTP headers, CRLF and the content. */
 					tsk_buffer_remove(_peer->buff, 0, (endOfheaders + 4/*2CRLF*/ + clen));
 					have_all_content = tsk_true;
 				}
 			}
 		}
 	}
 	
 	/* Alert the dialog (FSM) */
 	if(message){
 		if (have_all_content) { /* only if we have all data */
 			test_http_transport_process_incoming_msg(transport, message, e->local_fd);
 			/* Parse next chunck */
 			if (TSK_BUFFER_SIZE(_peer->buff) >= TEST_HTTP_MIN_STREAM_CHUNCK_SIZE) {
 				TSK_OBJECT_SAFE_FREE(message);
 				goto parse_buffer;
 			}
 		}
 	}
 
 bail:
 	TSK_OBJECT_SAFE_FREE(message);
 
 	return 0;
 }
 
 
 /************************************************
 * Main
 ************************************************/
 static void test_transport()
 {
 	test_http_transport_t* p_transport = tsk_null;
 	test_http_peer_t* peer = tsk_null;
 	int ret;
 	static const char* __get_msg = TEST_HTTP_GET;
 
 	p_transport = test_http_transport_create(TEST_HTTP_LOCAL_IP, TEST_HTTP_LOCAL_PORT, tnet_socket_type_tcp_ipv4);
 	if (!p_transport) {
 		TSK_DEBUG_ERROR("Failed to HTTP transport");
 		goto bail;
 	}
 
 	ret = test_http_transport_start(p_transport);
 	if (ret) {
 		TSK_DEBUG_ERROR("Failed to start HTTP transport with error code = %d", ret);
 		goto bail;
 	}
 	
 	getchar();
 
 	// Send data to google.com
 	peer = test_http_transport_connect_to(p_transport, TEST_HTTP_REMOTE_IP, TEST_HTTP_REMOTE_PORT);
 	ret = tnet_sockfd_waitUntilWritable(peer->fd, 1000); // you should use the callback function instead of blocking the process
 	if (ret) {
 		TSK_DEBUG_ERROR("Failed to connect to(%s,%d) with error code = %d", TEST_HTTP_REMOTE_IP, TEST_HTTP_REMOTE_PORT, ret);
 		goto bail;
 	}
 	ret = test_http_transport_send_data(p_transport, peer->fd, __get_msg, tsk_strlen(__get_msg));
 	TSK_DEBUG_INFO("Sent %d bytes", ret);
 
 	getchar();
 
 
 bail:
 	TSK_OBJECT_SAFE_FREE(p_transport); // stop server and free memory
 	TSK_OBJECT_SAFE_FREE(peer);
 }
 
 #endif /* _TEST_HTTP_TRANSPORT_H_ */