libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

commit 3b384315bcb2710490bfff676544b5872957f739
parent 32d7b92ba70509405f78b80711ef142391b63bd6
Author: Christian Grothoff <christian@grothoff.org>
Date:   Fri, 19 Jul 2013 09:19:48 +0000

-importing Andrey Uzunov's mhd2spdy code

Diffstat:
Msrc/examples/Makefile.am | 15++++++++++++++-
Asrc/examples/mhd2spdy.c | 323+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/examples/mhd2spdy_http.c | 450+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/examples/mhd2spdy_http.h | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/examples/mhd2spdy_spdy.c | 908+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/examples/mhd2spdy_spdy.h | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/examples/mhd2spdy_structures.c | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/examples/mhd2spdy_structures.h | 265+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/microhttpd/connection.c | 2+-
Msrc/microspdy/io_raw.c | 4++++
10 files changed, 2224 insertions(+), 2 deletions(-)

diff --git a/src/examples/Makefile.am b/src/examples/Makefile.am @@ -19,7 +19,11 @@ if ENABLE_SPDY spdyex = \ spdy_event_loop \ spdy_fileserver \ - spdy_response_with_callback + spdy_response_with_callback + +if HAVE_SPDYLAY +spdyex += mhd2spdy +endif endif @@ -74,6 +78,15 @@ demo_LDADD = \ $(top_builddir)/src/microhttpd/libmicrohttpd.la \ -lmagic +mhd2spdy_SOURCES = \ + mhd2spdy.c \ + mhd2spdy_spdy.c mhd2spdy_spdy.h \ + mhd2spdy_http.c mhd2spdy_http.h \ + mhd2spdy_structures.c mhd2spdy_structures.h +mhd2spdy_LDADD = \ + $(top_builddir)/src/microhttpd/libmicrohttpd.la \ + -lssl -lcrypto -lspdylay + benchmark_SOURCES = \ benchmark.c benchmark_LDADD = \ diff --git a/src/examples/mhd2spdy.c b/src/examples/mhd2spdy.c @@ -0,0 +1,323 @@ +/* + Copyright (C) 2013 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file structures.h + * @author Andrey Uzunov + */ + + /* + * TODOs + * non blocking SSL connect + * check certificate + * + * + * + * + + + */ + +#include "mhd2spdy_structures.h" +#include "mhd2spdy_spdy.h" +#include "mhd2spdy_http.h" + +static int run = 1; +//static int spdy_close = 0; + +static void catch_signal(int signal) +{ + //spdy_close = 1; + run = 0; +} + +int +run_everything () +{ + unsigned long long timeoutlong=0; + struct timeval timeout; + int ret; + fd_set rs; + fd_set ws; + fd_set es; + int maxfd = -1; + int maxfd_s = -1; + struct MHD_Daemon *daemon; + nfds_t spdy_npollfds = 1; + //struct pollfd spdy_pollfds[MAX_SPDY_CONNECTIONS]; + struct URI * spdy2http_uri = NULL; + //int spdy_nfds; + //int spdylay_timeout = 0; + struct SPDY_Connection *connection; + struct SPDY_Connection *connections[MAX_SPDY_CONNECTIONS]; + struct SPDY_Connection *connection_for_delete; + + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) + PRINT_INFO("signal failed"); + + if (signal(SIGINT, catch_signal) == SIG_ERR) + PRINT_INFO("signal failed"); + + //spdy2http_url = argv[2]; + glob_opt.streams_opened = 0; + glob_opt.responses_pending = 0; + glob_opt.global_memory = 0; + //spdy_proto_version = 0; + + srand(time(NULL)); + + if(init_parse_uri(&glob_opt.uri_preg)) + DIE("Regexp compilation failed"); + + + if(NULL != glob_opt.spdy2http_str) + { + ret = parse_uri(&glob_opt.uri_preg, glob_opt.spdy2http_str, &spdy2http_uri); + if(ret != 0) + DIE("spdy_parse_uri failed"); + } + + SSL_load_error_strings(); + SSL_library_init(); + glob_opt.ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + if(glob_opt.ssl_ctx == NULL) { + PRINT_INFO2("SSL_CTX_new %s", ERR_error_string(ERR_get_error(), NULL)); + abort(); + } + spdy_ssl_init_ssl_ctx(glob_opt.ssl_ctx, &glob_opt.spdy_proto_version); + + daemon = MHD_start_daemon ( + MHD_SUPPRESS_DATE_NO_CLOCK, + glob_opt.listen_port, + NULL, NULL, &http_cb_request, NULL, + MHD_OPTION_URI_LOG_CALLBACK, &http_log_cb, NULL, + MHD_OPTION_END); + if(NULL==daemon) + DIE("MHD_start_daemon failed"); + + + do + { + + timeout.tv_sec = 0; + timeout.tv_usec = 0; + + if(NULL == glob_opt.spdy_connection && NULL != glob_opt.spdy2http_str) + { + glob_opt.spdy_connection = spdy_connect(spdy2http_uri, spdy2http_uri->port, strcmp("https", spdy2http_uri->scheme)==0); + if(NULL == glob_opt.spdy_connection && glob_opt.only_proxy) + PRINT_INFO("cannot connect to the proxy"); + } + + //PRINT_INFO("while1"); + FD_ZERO(&rs); + FD_ZERO(&ws); + FD_ZERO(&es); + + /*if(glob_opt.spdy_data_received) + { + timeout.tv_sec = 0; + timeout.tv_usec = 0; + glob_opt.spdy_data_received = false; +} +else{*/ + /*if(glob_opt.responses_pending || glob_opt.streams_opened)// TODO only streams_opened true? + timeout.tv_usec = 0; //return immediately + else + {*/ + ret = MHD_get_timeout(daemon, &timeoutlong); + if(MHD_NO == ret || timeoutlong > 5000) + timeout.tv_sec = 5; + else + { + timeout.tv_sec = timeoutlong / 1000; + timeout.tv_usec = (timeoutlong % 1000) * 1000; + } + //} +//} + if(MHD_NO == MHD_get_fdset (daemon, + &rs, + &ws, + &es, + &maxfd)) + { + PRINT_INFO("MHD_get_fdset error"); + } + assert(-1 != maxfd); + + maxfd_s = spdy_get_selectfdset( + &rs, + &ws, + &es, + connections, MAX_SPDY_CONNECTIONS, &spdy_npollfds); + if(maxfd_s > maxfd) maxfd = maxfd_s; + + PRINT_INFO2("MHD timeout %i %i", timeout.tv_sec, timeout.tv_usec); + //TODO + //timeout.tv_sec = 0; + //timeout.tv_usec = 0; + + glob_opt.spdy_data_received = false; + + ret = select(maxfd+1, &rs, &ws, &es, &timeout); + PRINT_INFO2("timeout now %i %i", timeout.tv_sec, timeout.tv_usec); + + switch(ret) { + case -1: + PRINT_INFO2("select error: %i", errno); + break; + case 0: + break; + default: + PRINT_INFO("run"); + MHD_run_from_select(daemon,&rs, &ws, &es); + spdy_run_select(&rs, &ws, &es, connections, spdy_npollfds); + if(glob_opt.spdy_data_received) + { + PRINT_INFO("MHD run again"); + MHD_run_from_select(daemon,&rs, &ws, &es); + } + break; + } + + /* + //if(glob_opt.streams_opened) spdylay_timeout = 500; + //if(glob_opt.responses_pending || glob_opt.streams_opened) spdylay_timeout = 0; + //else spdylay_timeout = 0; + //else spdylay_timeout = 0; + + spdy_get_pollfdset(spdy_pollfds, connections, MAX_SPDY_CONNECTIONS, &spdy_npollfds); + + //TODO + //spdylay_timeout = 0; + + PRINT_INFO2("spdylay timeout %i", spdylay_timeout); + ret = poll(spdy_pollfds, spdy_npollfds, spdylay_timeout); + if(ret == -1) + DIE("poll"); + if(ret > 0){ + PRINT_INFO("spdy_run"); + spdy_run(spdy_pollfds, connections, spdy_npollfds); + }*/ + } + while(run); + + //TODO exit from loop and clean + + MHD_stop_daemon (daemon); + + //TODO SSL_free brakes + spdy_free_connection(glob_opt.spdy_connection); + + connection = glob_opt.spdy_connections_head; + while(NULL != connection) + { + connection_for_delete = connection; + connection = connection_for_delete->next; + glob_opt.streams_opened -= connection_for_delete->streams_opened; + DLL_remove(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connection_for_delete); + spdy_free_connection(connection_for_delete); + } + + free_uri(spdy2http_uri); + + deinit_parse_uri(&glob_opt.uri_preg); + + SSL_CTX_free(glob_opt.ssl_ctx); + ERR_free_strings(); + EVP_cleanup(); + + PRINT_INFO2("spdy streams: %i; http requests: %i", glob_opt.streams_opened, glob_opt.responses_pending); + PRINT_INFO2("memory allocated %zu bytes", glob_opt.global_memory); + + return 0; +} + +void +display_usage() +{ + printf( + "Usage: http2spdy [-vo] [-b <SPDY2HTTP-PROXY>] -p <PORT>\n" + "TODO\n" + ); +} + + +int +main (int argc, + char *const *argv) +{ + int getopt_ret; + int option_index; + struct option long_options[] = { + {"port", required_argument, 0, 'p'}, + {"backend-proxy", required_argument, 0, 'b'}, + {"verbose", no_argument, 0, 'v'}, + {"only-proxy", no_argument, 0, 'o'}, + {0, 0, 0, 0} + }; + + while (1) + { + getopt_ret = getopt_long( argc, argv, "p:b:vo", long_options, &option_index); + if (getopt_ret == -1) + break; + + switch(getopt_ret) + { + case 'p': + glob_opt.listen_port = atoi(optarg); + break; + + case 'b': + glob_opt.spdy2http_str = strdup(optarg); + if(NULL == glob_opt.spdy2http_str) + return 1; + break; + + case 'v': + glob_opt.verbose = true; + break; + + case 'o': + glob_opt.only_proxy = true; + break; + + case 0: + PRINT_INFO("0 from getopt"); + break; + + case '?': + display_usage(); + return 1; + + default: + DIE("default from getopt"); + } + } + + if( + 0 == glob_opt.listen_port + || (glob_opt.only_proxy && NULL == glob_opt.spdy2http_str) + ) + { + display_usage(); + return 1; + } + + return run_everything(); +} diff --git a/src/examples/mhd2spdy_http.c b/src/examples/mhd2spdy_http.c @@ -0,0 +1,450 @@ +/* + Copyright (C) 2013 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file structures.h + * @author Andrey Uzunov + */ + +#include "mhd2spdy_structures.h" +#include "mhd2spdy_http.h" +#include "mhd2spdy_spdy.h" + + +void * http_log_cb(void * cls, const char * uri) +{ + struct HTTP_URI * http_uri; + + PRINT_INFO2("log uri '%s'\n", uri); + + if(NULL == (http_uri = au_malloc(sizeof(struct HTTP_URI )))) + DIE("no memory"); + //memset(http_uri, 0 , sizeof(struct HTTP_URI)); + http_uri->uri = strdup(uri); + return http_uri; +} + + +/* +static int +http_query_iterate_cb(void *cls, + enum MHD_ValueKind kind, + const char *name, const char *value) +{ + +}*/ + + +static int +http_iterate_cb(void *cls, + enum MHD_ValueKind kind, + const char *name, const char *value) +{ + static char * const forbidden[] = {"Transfer-Encoding", + "Proxy-Connection", + "Keep-Alive", + "Connection"}; + static int forbidden_size = 4; + int i; + struct SPDY_Headers *spdy_headers = (struct SPDY_Headers *)cls; + + if(0 == strcasecmp(name, "Host")) + spdy_headers->nv[9] = (char *)value; + else + { + for(i=0; i<forbidden_size; ++i) + if(0 == strcasecmp(forbidden[i], name)) + return MHD_YES; + spdy_headers->nv[spdy_headers->cnt++] = (char *)name; + spdy_headers->nv[spdy_headers->cnt++] = (char *)value; + } + + return MHD_YES; +} + + +static ssize_t +http_response_callback (void *cls, + uint64_t pos, + char *buffer, + size_t max) +{ + int ret; + struct Proxy *proxy = (struct Proxy *)cls; + void *newbody; + const union MHD_ConnectionInfo *info; + int val = 1; + + //max=16; + + //PRINT_INFO2("response_callback, pos: %i, max is %i, len is %i",pos,max,proxy->length); + + //assert(0 != proxy->length); + + //if(MHD_CONTENT_READER_END_OF_STREAM == proxy->length) + // return MHD_CONTENT_READER_END_OF_STREAM; + + PRINT_INFO2("http_response_callback for %s", proxy->url); + + if(0 == proxy->http_body_size &&( proxy->done || !proxy->spdy_active)){ + PRINT_INFO("sent end of stream"); + return MHD_CONTENT_READER_END_OF_STREAM; + } + + //*more = true; + if(!proxy->http_body_size)//nothing to write now + { + //flush data + info = MHD_get_connection_info (proxy->http_connection, + MHD_CONNECTION_INFO_CONNECTION_FD); + ret = setsockopt(info->connect_fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val)); + if(ret == -1) { + DIE("setsockopt"); + } + + PRINT_INFO("FLUSH data"); + return 0; + } + + if(max >= proxy->http_body_size) + { + ret = proxy->http_body_size; + newbody = NULL; + } + else + { + ret = max; + if(NULL == (newbody = au_malloc(proxy->http_body_size - max))) + { + PRINT_INFO("no memory"); + return -2; + } + memcpy(newbody, proxy->http_body + max, proxy->http_body_size - max); + } + memcpy(buffer, proxy->http_body, ret); + free(proxy->http_body); + proxy->http_body = newbody; + proxy->http_body_size -= ret; + + if(proxy->length >= 0) + { + proxy->length -= ret; + //printf("pr len %i", proxy->length); + /*if(proxy->length <= 0) + { + // *more = false; + //last frame + proxy->length = MHD_CONTENT_READER_END_OF_STREAM; + }*/ + } + + PRINT_INFO2("response_callback, size: %i",ret); + + return ret; +} + + +static void +http_response_done_callback(void *cls) +{ + struct Proxy *proxy = (struct Proxy *)cls; + + PRINT_INFO2("http_response_done_callback for %s", proxy->url); + //int ret; + + //printf("response_done_callback\n"); + + //printf("answer for %s was sent\n", (char *)cls); + + /*if(SPDY_RESPONSE_RESULT_SUCCESS != status) + { + printf("answer was NOT sent, %i\n",status); + }*/ + /*if(CURLM_OK != (ret = curl_multi_remove_handle(multi_handle, proxy->curl_handle))) + { + PRINT_INFO2("curl_multi_remove_handle failed (%i)", ret); + } + curl_slist_free_all(proxy->curl_headers); + curl_easy_cleanup(proxy->curl_handle); + */ + //SPDY_destroy_request(request); + //SPDY_destroy_response(response); + MHD_destroy_response (proxy->http_response); + //if(!strcmp("/close",proxy->path)) run = 0; + //free(proxy->path); + if(proxy->spdy_active) + proxy->http_active = false; + else + free_proxy(proxy); + + --glob_opt.responses_pending; +} + +int +http_cb_request (void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **ptr) +{ + //struct MHD_Response *response; + int ret; + struct Proxy *proxy; + //struct URI *spdy_uri; + //char **nv; + //int num_headers; + struct SPDY_Headers spdy_headers; + + //PRINT_INFO2("request cb %i; %s", *ptr,url); + + if (NULL == *ptr) + DIE("ptr is null"); + struct HTTP_URI *http_uri = (struct HTTP_URI *)*ptr; + + if(NULL == http_uri->proxy) + { + if (0 != strcmp (method, MHD_HTTP_METHOD_GET)) + { + free(http_uri->uri); + free(http_uri); + PRINT_INFO2("unexpected method %s", method); + return MHD_NO; /* unexpected method */ + } + + if(NULL == (proxy = au_malloc(sizeof(struct Proxy)))) + { + PRINT_INFO("No memory"); + return MHD_NO; + } + + ++glob_opt.responses_pending; + //memset(proxy, 0, sizeof(struct Proxy)); + proxy->id = rand(); + proxy->http_active = true; + //PRINT_INFO2("proxy obj with id %i created (%i)", proxy->id, proxy); + proxy->http_connection = connection; + http_uri->proxy = proxy; + return MHD_YES; + } + + proxy = http_uri->proxy; + //*ptr = NULL; /* reset when done */ + + if(proxy->spdy_active) + { + //already handled + PRINT_INFO("unnecessary call to http_cb_request"); + return MHD_YES; + } + + PRINT_INFO2("received request for '%s %s %s'\n", method, http_uri->uri, version); + /* + proxy->http_response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN, + 8096, + &http_response_callback, + proxy, + &http_response_done_callback); + + if (proxy->http_response == NULL) + DIE("no response"); + */ + + proxy->url = http_uri->uri; + //if(NULL == (proxy->url = strdup(http_uri->uri))) + // DIE("no memory"); + +//TODO HTTP headers + /*MHD_get_connection_values (connection, + MHD_HEADER_KIND, + &http_iterate_cb, + proxy); + */ + //proxy->url = strdup(url); + //if(NULL == (spdy_uri = au_malloc(sizeof(struct URI)))) + // DIE("no memory"); + + ret = parse_uri(&glob_opt.uri_preg, proxy->url, &proxy->uri); + if(ret != 0) + DIE("parse_uri failed"); + //proxy->uri = spdy_uri; + proxy->http_uri = http_uri; + proxy->spdy_active = true; + + //proxy->spdy_request = au_malloc(sizeof(struct SPDY_Request)); + //if(NULL == proxy->spdy_request) + // DIE("no memory"); + //memset(proxy->spdy_request,0,sizeof(struct SPDY_Request)); + //spdy_request_init(proxy->spdy_request, &spdy_uri); + //spdy_submit_request(spdy_connection, proxy); + + spdy_headers.num = MHD_get_connection_values (connection, + MHD_HEADER_KIND, + NULL, + NULL); + if(NULL == (spdy_headers.nv = au_malloc(((spdy_headers.num + 5) * 2 + 1) * sizeof(char *)))) + DIE("no memory"); + spdy_headers.nv[0] = ":method"; spdy_headers.nv[1] = "GET"; + spdy_headers.nv[2] = ":path"; spdy_headers.nv[3] = proxy->uri->path_and_more; + spdy_headers.nv[4] = ":version"; spdy_headers.nv[5] = (char *)version; + spdy_headers.nv[6] = ":scheme"; spdy_headers.nv[7] = proxy->uri->scheme; + spdy_headers.nv[8] = ":host"; spdy_headers.nv[9] = NULL; + //nv[14] = NULL; + spdy_headers.cnt = 10; + MHD_get_connection_values (connection, + MHD_HEADER_KIND, + &http_iterate_cb, + &spdy_headers); + + spdy_headers.nv[spdy_headers.cnt] = NULL; + if(NULL == spdy_headers.nv[9]) + spdy_headers.nv[9] = proxy->uri->host_and_port; + + /*int i; + for(i=0; i<spdy_headers.cnt; i+=2) + printf("%s: %s\n", spdy_headers.nv[i], spdy_headers.nv[i+1]); + */ + if(0 != spdy_request(spdy_headers.nv, proxy)) + { + //--glob_opt.responses_pending; + free(spdy_headers.nv); + //MHD_destroy_response (proxy->http_response); + free_proxy(proxy);//TODO call it here or in done_callback + + return MHD_NO; + } + free(spdy_headers.nv); + + proxy->http_response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN, + 4096, + &http_response_callback, + proxy, + &http_response_done_callback); + + if (proxy->http_response == NULL) + DIE("no response"); + + if(MHD_NO == MHD_add_response_header (proxy->http_response, + "Proxy-Connection", "keep-alive")) + PRINT_INFO("SPDY_name_value_add failed: "); + if(MHD_NO == MHD_add_response_header (proxy->http_response, + "Connection", "Keep-Alive")) + PRINT_INFO("SPDY_name_value_add failed: "); + if(MHD_NO == MHD_add_response_header (proxy->http_response, + "Keep-Alive", "timeout=5, max=100")) + PRINT_INFO("SPDY_name_value_add failed: "); + + /* + const union MHD_ConnectionInfo *info; + info = MHD_get_connection_info (connection, + MHD_CONNECTION_INFO_CONNECTION_FD); + int val = 1; + int rv; + rv = setsockopt(info->connect_fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val)); + if(rv == -1) { + DIE("setsockopt"); + }*/ + return MHD_YES; +} + +void +http_create_response(struct Proxy* proxy, char **nv) +{ + size_t i; + //uint64_t response_size=MHD_SIZE_UNKNOWN; + + /*for(i = 0; nv[i]; i += 2) { + if(0 == strcmp("content-length", nv[i])) + { + response_size = atoi(nv[i+1]); + break; + } + }*/ + /* + proxy->http_response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN, + 4096, + &http_response_callback, + proxy, + &http_response_done_callback); + + if (proxy->http_response == NULL) + DIE("no response"); + + if(MHD_NO == MHD_add_response_header (proxy->http_response, + "Proxy-Connection", "keep-alive")) + PRINT_INFO("SPDY_name_value_add failed: "); + if(MHD_NO == MHD_add_response_header (proxy->http_response, + "Connection", "Keep-Alive")) + PRINT_INFO("SPDY_name_value_add failed: "); + if(MHD_NO == MHD_add_response_header (proxy->http_response, + "Keep-Alive", "timeout=5, max=100")) + PRINT_INFO("SPDY_name_value_add failed: "); + */ + for(i = 0; nv[i]; i += 2) { + //printf(" %s: %s\n", nv[i], nv[i+1]); + //int j; + + if(0 == strcmp(":status", nv[i])) + { + //raise(SIGINT); + //proxy->status_msg = nv[i+1]; + char tmp[4]; + memcpy(&tmp,nv[i+1],3); + tmp[3]=0; + proxy->status = atoi(tmp); + continue; + } + else if(0 == strcmp(":version", nv[i])) + { + proxy->version = nv[i+1]; + continue; + } + else if(0 == strcmp("content-length", nv[i])) + { + //proxy->length = atoi(nv[i+1]); + //response_size = atoi(nv[i+1]); + continue; + } + + //for(j=0; j<strlen(nv[i]) && ':'==nv[i][j]; ++j); + + char *header = *(nv+i); + //header[0] = toupper(header[0]); + if(MHD_NO == MHD_add_response_header (proxy->http_response, + header, nv[i+1])) + { + PRINT_INFO2("SPDY_name_value_add failed: '%s' '%s'", header, nv[i+1]); + //abort(); + } + PRINT_INFO2("adding '%s: %s'",header, nv[i+1]); + } + + //PRINT_INFO2("%i", MHD_get_response_headers(proxy->http_response, NULL, NULL)); + //PRINT_INFO2("state before %i", proxy->http_connection->state); + //PRINT_INFO2("loop before %i", proxy->http_connection->event_loop_info); + if(MHD_NO == MHD_queue_response (proxy->http_connection, proxy->status, proxy->http_response)){ + PRINT_INFO("No queue"); + abort(); + } + //PRINT_INFO2("state after %i", proxy->http_connection->state); + //PRINT_INFO2("loop after %i", proxy->http_connection->event_loop_info); + //MHD_destroy_response (proxy->http_response); + //PRINT_INFO2("state after %i", proxy->http_connection->state); + //PRINT_INFO2("loop after %i", proxy->http_connection->event_loop_info); +} diff --git a/src/examples/mhd2spdy_http.h b/src/examples/mhd2spdy_http.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2013 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file structures.h + * @author Andrey Uzunov + */ + +#ifndef HTTP_H +#define HTTP_H + +#include "mhd2spdy_structures.h" + +int +http_cb_request (void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **ptr); + +void * http_log_cb(void * cls, const char * uri); + +void +http_create_response(struct Proxy* proxy, char **nv); + +#endif diff --git a/src/examples/mhd2spdy_spdy.c b/src/examples/mhd2spdy_spdy.c @@ -0,0 +1,908 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file spdy.c + * @author Tatsuhiro Tsujikawa + * @author Andrey Uzunov + */ + +#include "mhd2spdy_structures.h" +#include "mhd2spdy_spdy.h" +#include "mhd2spdy_http.h" + +enum +{ + IO_NONE, + WANT_READ, + WANT_WRITE +}; + + +/* + * Prints error containing the function name |func| and message |msg| + * and exit. + */ +static void spdy_dief(const char *func, const char *msg) +{ + fprintf(stderr, "FATAL: %s: %s\n", func, msg); + exit(EXIT_FAILURE); +} + +/* + * Prints error containing the function name |func| and error code + * |error_code| and exit. + */ +void spdy_diec(const char *func, int error_code) +{ + fprintf(stderr, "FATAL: %s: error_code=%d, msg=%s\n", func, error_code, + spdylay_strerror(error_code)); + exit(EXIT_FAILURE); +} + + +/* + * The implementation of spdylay_send_callback type. Here we write + * |data| with size |length| to the network and return the number of + * bytes actually written. See the documentation of + * spdylay_send_callback for the details. + */ +static ssize_t spdy_cb_send(spdylay_session *session, + const uint8_t *data, size_t length, int flags, + void *user_data) +{ + //PRINT_INFO("spdy_cb_send called"); + struct SPDY_Connection *connection; + ssize_t rv; + connection = (struct SPDY_Connection*)user_data; + connection->want_io = IO_NONE; + if(connection->is_tls) + { + ERR_clear_error(); + rv = SSL_write(connection->ssl, data, length); + if(rv < 0) { + int err = SSL_get_error(connection->ssl, rv); + if(err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) { + connection->want_io = (err == SSL_ERROR_WANT_READ ? + WANT_READ : WANT_WRITE); + rv = SPDYLAY_ERR_WOULDBLOCK; + } else { + rv = SPDYLAY_ERR_CALLBACK_FAILURE; + } + } + } + else + { + rv = write(connection->fd, + data, + length); + + if (rv < 0) + { + switch(errno) + { + case EAGAIN: + #if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: + #endif + connection->want_io = WANT_WRITE; + rv = SPDYLAY_ERR_WOULDBLOCK; + break; + + default: + rv = SPDYLAY_ERR_CALLBACK_FAILURE; + } + } + } + return rv; +} + +/* + * The implementation of spdylay_recv_callback type. Here we read data + * from the network and write them in |buf|. The capacity of |buf| is + * |length| bytes. Returns the number of bytes stored in |buf|. See + * the documentation of spdylay_recv_callback for the details. + */ +static ssize_t spdy_cb_recv(spdylay_session *session, + uint8_t *buf, size_t length, int flags, + void *user_data) +{ + struct SPDY_Connection *connection; + ssize_t rv; + + connection = (struct SPDY_Connection*)user_data; + //prevent monopolizing everything + if(!(++connection->counter % 10)) return SPDYLAY_ERR_WOULDBLOCK; + connection->want_io = IO_NONE; + if(connection->is_tls) + { + ERR_clear_error(); + rv = SSL_read(connection->ssl, buf, length); + if(rv < 0) { + int err = SSL_get_error(connection->ssl, rv); + if(err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) { + connection->want_io = (err == SSL_ERROR_WANT_READ ? + WANT_READ : WANT_WRITE); + rv = SPDYLAY_ERR_WOULDBLOCK; + } else { + rv = SPDYLAY_ERR_CALLBACK_FAILURE; + } + } else if(rv == 0) { + rv = SPDYLAY_ERR_EOF; + } + } + else + { + rv = read(connection->fd, + buf, + length); + + if (rv < 0) + { + switch(errno) + { + case EAGAIN: + #if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: + #endif + connection->want_io = WANT_READ; + rv = SPDYLAY_ERR_WOULDBLOCK; + break; + + default: + rv = SPDYLAY_ERR_CALLBACK_FAILURE; + } + } + else if(rv == 0) + rv = SPDYLAY_ERR_EOF; + } + return rv; +} + +/* + * The implementation of spdylay_before_ctrl_send_callback type. We + * use this function to get stream ID of the request. This is because + * stream ID is not known when we submit the request + * (spdylay_spdy_submit_request). + */ +/*static void spdy_cb_before_ctrl_send(spdylay_session *session, + spdylay_frame_type type, + spdylay_frame *frame, + void *user_data) +{ +}*/ + + +static void spdy_cb_on_ctrl_send(spdylay_session *session, + spdylay_frame_type type, + spdylay_frame *frame, void *user_data) +{ + //char **nv; + //const char *name = NULL; + int32_t stream_id; + //size_t i; + struct Proxy *proxy; + + switch(type) { + case SPDYLAY_SYN_STREAM: + //nv = frame->syn_stream.nv; + //name = "SYN_STREAM"; + stream_id = frame->syn_stream.stream_id; + proxy = spdylay_session_get_stream_user_data(session, stream_id); + ++glob_opt.streams_opened; + ++proxy->spdy_connection->streams_opened; + PRINT_INFO2("opening stream: str open %i; %s", glob_opt.streams_opened, proxy->url); + break; + default: + break; + } +} + +void spdy_cb_on_ctrl_recv(spdylay_session *session, + spdylay_frame_type type, + spdylay_frame *frame, void *user_data) +{ + //struct SPDY_Request *req; + char **nv; + //const char *name = NULL; + int32_t stream_id; + struct Proxy * proxy; + + switch(type) { + case SPDYLAY_SYN_REPLY: + nv = frame->syn_reply.nv; + //name = "SYN_REPLY"; + stream_id = frame->syn_reply.stream_id; + break; + case SPDYLAY_HEADERS: + nv = frame->headers.nv; + //name = "HEADERS"; + stream_id = frame->headers.stream_id; + break; + default: + return; + break; + } + + proxy = spdylay_session_get_stream_user_data(session, stream_id); + PRINT_INFO2("received headers for %s", proxy->url); + + http_create_response(proxy, nv); + glob_opt.spdy_data_received = true; +} + +/* + * The implementation of spdylay_on_stream_close_callback type. We use + * this function to know the response is fully received. Since we just + * fetch 1 resource in this program, after reception of the response, + * we submit GOAWAY and close the session. + */ +static void spdy_cb_on_stream_close(spdylay_session *session, + int32_t stream_id, + spdylay_status_code status_code, + void *user_data) +{ + struct Proxy * proxy = spdylay_session_get_stream_user_data(session, stream_id); + + assert(NULL != proxy); + + --glob_opt.streams_opened; + --proxy->spdy_connection->streams_opened; + PRINT_INFO2("closing stream: str opened %i", glob_opt.streams_opened); + + DLL_remove(proxy->spdy_connection->proxies_head, proxy->spdy_connection->proxies_tail, proxy); + + if(proxy->http_active) + proxy->spdy_active = false; + else + free_proxy(proxy); + return; +} + +#define SPDY_MAX_OUTLEN 4096 + +/* + * The implementation of spdylay_on_data_chunk_recv_callback type. We + * use this function to print the received response body. + */ +static void spdy_cb_on_data_chunk_recv(spdylay_session *session, uint8_t flags, + int32_t stream_id, + const uint8_t *data, size_t len, + void *user_data) +{ + //struct SPDY_Request *req; + struct Proxy *proxy; + proxy = spdylay_session_get_stream_user_data(session, stream_id); + + if(NULL == proxy->http_body) + proxy->http_body = au_malloc(len); + else + proxy->http_body = realloc(proxy->http_body, proxy->http_body_size + len); + if(NULL == proxy->http_body) + { + PRINT_INFO("not enough memory (realloc returned NULL)"); + return ; + } + + memcpy(proxy->http_body + proxy->http_body_size, data, len); + proxy->http_body_size += len; + PRINT_INFO2("received data for %s; %zu bytes", proxy->url, len); + glob_opt.spdy_data_received = true; +} + +static void spdy_cb_on_data_recv(spdylay_session *session, + uint8_t flags, int32_t stream_id, int32_t length, void *user_data) +{ + if(flags & SPDYLAY_DATA_FLAG_FIN) + { + struct Proxy *proxy; + proxy = spdylay_session_get_stream_user_data(session, stream_id); + proxy->done = true; + PRINT_INFO2("last data frame received for %s", proxy->url); + } +} + +/* + * Setup callback functions. Spdylay API offers many callback + * functions, but most of them are optional. The send_callback is + * always required. Since we use spdylay_session_recv(), the + * recv_callback is also required. + */ +static void spdy_setup_spdylay_callbacks(spdylay_session_callbacks *callbacks) +{ + memset(callbacks, 0, sizeof(spdylay_session_callbacks)); + callbacks->send_callback = spdy_cb_send; + callbacks->recv_callback = spdy_cb_recv; + //callbacks->before_ctrl_send_callback = spdy_cb_before_ctrl_send; + callbacks->on_ctrl_send_callback = spdy_cb_on_ctrl_send; + callbacks->on_ctrl_recv_callback = spdy_cb_on_ctrl_recv; + callbacks->on_stream_close_callback = spdy_cb_on_stream_close; + callbacks->on_data_chunk_recv_callback = spdy_cb_on_data_chunk_recv; + callbacks->on_data_recv_callback = spdy_cb_on_data_recv; +} + +/* + * Callback function for SSL/TLS NPN. Since this program only supports + * SPDY protocol, if server does not offer SPDY protocol the Spdylay + * library supports, we terminate program. + */ +static int spdy_cb_ssl_select_next_proto(SSL* ssl, + unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + void *arg) +{ + //PRINT_INFO("spdy_cb_ssl_select_next_proto"); + int rv; + uint16_t *spdy_proto_version; + /* spdylay_select_next_protocol() selects SPDY protocol version the + Spdylay library supports. */ + rv = spdylay_select_next_protocol(out, outlen, in, inlen); + if(rv <= 0) { + PRINT_INFO("Server did not advertise spdy/2 or spdy/3 protocol."); + return rv; + } + spdy_proto_version = (uint16_t*)arg; + *spdy_proto_version = rv; + return SSL_TLSEXT_ERR_OK; +} + +/* + * Setup SSL context. We pass |spdy_proto_version| to get negotiated + * SPDY protocol version in NPN callback. + */ +void spdy_ssl_init_ssl_ctx(SSL_CTX *ssl_ctx, uint16_t *spdy_proto_version) +{ + /* Disable SSLv2 and enable all workarounds for buggy servers */ + SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2 | SSL_OP_NO_COMPRESSION); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + /* Set NPN callback */ + SSL_CTX_set_next_proto_select_cb(ssl_ctx, spdy_cb_ssl_select_next_proto, + spdy_proto_version); +} + +static int spdy_ssl_handshake(SSL *ssl, int fd) +{ + int rv; + if(SSL_set_fd(ssl, fd) == 0) { + spdy_dief("SSL_set_fd", ERR_error_string(ERR_get_error(), NULL)); + } + ERR_clear_error(); + rv = SSL_connect(ssl); + if(rv <= 0) { + PRINT_INFO2("SSL_connect %s", ERR_error_string(ERR_get_error(), NULL)); + } + + return rv; +} + +/* + * Connects to the host |host| and port |port|. This function returns + * the file descriptor of the client socket. + */ +static int spdy_socket_connect_to(const char *host, uint16_t port) +{ + struct addrinfo hints; + int fd = -1; + int rv; + char service[NI_MAXSERV]; + struct addrinfo *res, *rp; + snprintf(service, sizeof(service), "%u", port); + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + rv = getaddrinfo(host, service, &hints, &res); + if(rv != 0) { + printf("%s\n",host); + spdy_dief("getaddrinfo", gai_strerror(rv)); + } + for(rp = res; rp; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if(fd == -1) { + continue; + } + while((rv = connect(fd, rp->ai_addr, rp->ai_addrlen)) == -1 && + errno == EINTR); + if(rv == 0) { + break; + } + close(fd); + fd = -1; + } + freeaddrinfo(res); + return fd; +} + +static void spdy_socket_make_non_block(int fd) +{ + int flags, rv; + while((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR); + if(flags == -1) { + spdy_dief("fcntl", strerror(errno)); + } + while((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR); + if(rv == -1) { + spdy_dief("fcntl", strerror(errno)); + } +} + +/* + * Setting TCP_NODELAY is not mandatory for the SPDY protocol. + */ +static void spdy_socket_set_tcp_nodelay(int fd) +{ + int val = 1; + int rv; + rv = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val)); + if(rv == -1) { + spdy_dief("setsockopt", strerror(errno)); + } +} + +/* + * Update |pollfd| based on the state of |connection|. + */ +void spdy_ctl_poll(struct pollfd *pollfd, struct SPDY_Connection *connection) +{ + pollfd->events = 0; + if(spdylay_session_want_read(connection->session) || + connection->want_io == WANT_READ) { + pollfd->events |= POLLIN; + } + if(spdylay_session_want_write(connection->session) || + connection->want_io == WANT_WRITE) { + pollfd->events |= POLLOUT; + } +} + +/* + * Update |selectfd| based on the state of |connection|. + */ +bool spdy_ctl_select(fd_set * read_fd_set, + fd_set * write_fd_set, + fd_set * except_fd_set, + struct SPDY_Connection *connection) +{ + bool ret = false; + + if(spdylay_session_want_read(connection->session) || + connection->want_io == WANT_READ) { + FD_SET(connection->fd, read_fd_set); + ret = true; + } + if(spdylay_session_want_write(connection->session) || + connection->want_io == WANT_WRITE) { + FD_SET(connection->fd, write_fd_set); + ret = true; + } + return ret; +} + +/* + * Performs the network I/O. + */ +int spdy_exec_io(struct SPDY_Connection *connection) +{ + int rv; + rv = spdylay_session_recv(connection->session); + if(rv != 0) { + PRINT_INFO2("spdylay_session_recv %i", rv); + return rv; + } + rv = spdylay_session_send(connection->session); + if(rv != 0) { + PRINT_INFO2("spdylay_session_send %i", rv); + } + return rv; +} + +/* + * Fetches the resource denoted by |uri|. + */ +struct SPDY_Connection * spdy_connect(const struct URI *uri, uint16_t port, bool is_tls) +{ + spdylay_session_callbacks callbacks; + int fd; + //SSL_CTX *ssl_ctx; + SSL *ssl=NULL; + //struct SPDY_Request req; + struct SPDY_Connection * connection; + int rv; + + spdy_setup_spdylay_callbacks(&callbacks); + + /* Establish connection and setup SSL */ + PRINT_INFO2("connecting to %s:%i", uri->host, port); + fd = spdy_socket_connect_to(uri->host, port); + if(fd == -1) { + PRINT_INFO("Could not open file descriptor"); + return NULL;//glob_opt.spdy_connection; + } + + if(is_tls) + { + /*ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + if(ssl_ctx == NULL) { + spdy_dief("SSL_CTX_new", ERR_error_string(ERR_get_error(), NULL)); + } + spdy_ssl_init_ssl_ctx(ssl_ctx, &spdy_proto_version); + */ + ssl = SSL_new(glob_opt.ssl_ctx); + if(ssl == NULL) { + spdy_dief("SSL_new", ERR_error_string(ERR_get_error(), NULL)); + } + + //TODO non-blocking + /* To simplify the program, we perform SSL/TLS handshake in blocking + I/O. */ + glob_opt.spdy_proto_version = 0; + rv = spdy_ssl_handshake(ssl, fd); + if(rv <= 0 || (glob_opt.spdy_proto_version != 3 && glob_opt.spdy_proto_version != 2)) + { + PRINT_INFO("Closing SSL"); + //no spdy on the other side + SSL_shutdown(ssl); + close(fd); + SSL_free(ssl); + + return NULL; + } + } + else + { + glob_opt.spdy_proto_version = 3; + } + + if(NULL == (connection = au_malloc(sizeof(struct SPDY_Connection)))) + return NULL; + //memset(connection, 0 , sizeof(struct SPDY_Connection)); + + connection->is_tls = is_tls; + connection->ssl = ssl; + connection->want_io = IO_NONE; + connection->host = strdup(uri->host); + + /* Here make file descriptor non-block */ + spdy_socket_make_non_block(fd); + spdy_socket_set_tcp_nodelay(fd); + + PRINT_INFO2("[INFO] SPDY protocol version = %d\n", glob_opt.spdy_proto_version); + rv = spdylay_session_client_new(&(connection->session), glob_opt.spdy_proto_version, + &callbacks, connection); + if(rv != 0) { + spdy_diec("spdylay_session_client_new", rv); + } + + connection->fd = fd; + + return connection; +} + +void +spdy_free_connection(struct SPDY_Connection * connection) +{ + if(NULL != connection) + { + spdylay_session_del(connection->session); + SSL_free(connection->ssl); + free(connection->host); + free(connection); + } +} + +int +spdy_request(const char **nv, struct Proxy *proxy) +{ + int ret; + uint16_t port; + struct SPDY_Connection *connection; + + if(glob_opt.only_proxy) + { + connection = glob_opt.spdy_connection; + } + else + { + connection = glob_opt.spdy_connections_head; + while(NULL != connection) + { + if(0 == strcasecmp(proxy->uri->host, connection->host)) + break; + connection = connection->next; + } + + if(NULL == connection) + { + //connect to host + port = proxy->uri->port; + if(0 == port) port = 443; + connection = spdy_connect(proxy->uri, port, true); + if(NULL != connection) + { + DLL_insert(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connection); + glob_opt.total_spdy_connections++; + } + else + connection = glob_opt.spdy_connection; + } + } + + if(NULL == connection) + { + PRINT_INFO("there is no proxy!"); + return -1; + } + + proxy->spdy_connection = connection; + ret = spdylay_submit_request(connection->session, 0, nv, NULL, proxy); + if(ret != 0) { + spdy_diec("spdylay_spdy_submit_request", ret); + } + DLL_insert(connection->proxies_head, connection->proxies_tail, proxy); + + return ret; +} + + +void +spdy_get_pollfdset(struct pollfd fds[], struct SPDY_Connection *connections[], int max_size, nfds_t *real_size) +{ + struct SPDY_Connection *connection; + struct Proxy *proxy; + + *real_size = 0; + if(max_size<1) return; + if(NULL != glob_opt.spdy_connection) + { + spdy_ctl_poll(&(fds[*real_size]), glob_opt.spdy_connection); + if(!fds[*real_size].events) + { + //PRINT_INFO("TODO drop connection"); + glob_opt.streams_opened -= glob_opt.spdy_connection->streams_opened; + + for(proxy = glob_opt.spdy_connection->proxies_head; NULL != proxy; proxy=proxy->next) + { + DLL_remove(glob_opt.spdy_connection->proxies_head, glob_opt.spdy_connection->proxies_tail, proxy); + proxy->spdy_active = false; + } + spdy_free_connection(glob_opt.spdy_connection); + glob_opt.spdy_connection = NULL; + } + else + { + fds[*real_size].fd = glob_opt.spdy_connection->fd; + connections[*real_size] = glob_opt.spdy_connection; + ++(*real_size); + } + } + + connection = glob_opt.spdy_connections_head; + + while(NULL != connection && *real_size < max_size) + { + assert(!glob_opt.only_proxy); + spdy_ctl_poll(&(fds[*real_size]), connection); + if(!fds[*real_size].events) + { + //PRINT_INFO("TODO drop connection"); + glob_opt.streams_opened -= connection->streams_opened; + DLL_remove(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connection); + glob_opt.total_spdy_connections--; + + for(proxy = connection->proxies_head; NULL != proxy; proxy=proxy->next) + { + DLL_remove(connection->proxies_head, connection->proxies_tail, proxy); + proxy->spdy_active = false; + } + spdy_free_connection(connection); + } + else + { + fds[*real_size].fd = connection->fd; + connections[*real_size] = connection; + ++(*real_size); + } + connection = connection->next; + } + + //, "TODO max num of conn reached; close something" + assert(NULL == connection); +} + + +int +spdy_get_selectfdset(fd_set * read_fd_set, + fd_set * write_fd_set, + fd_set * except_fd_set, + struct SPDY_Connection *connections[], int max_size, nfds_t *real_size) +{ + struct SPDY_Connection *connection; + struct Proxy *proxy; + bool ret; + int maxfd = 0; + + *real_size = 0; + if(max_size<1) return 0; + if(NULL != glob_opt.spdy_connection) + { + ret = spdy_ctl_select(read_fd_set, + write_fd_set, + except_fd_set, glob_opt.spdy_connection); + if(!ret) + { + //PRINT_INFO("TODO drop connection"); + glob_opt.streams_opened -= glob_opt.spdy_connection->streams_opened; + + for(proxy = glob_opt.spdy_connection->proxies_head; NULL != proxy; proxy=proxy->next) + { + DLL_remove(glob_opt.spdy_connection->proxies_head, glob_opt.spdy_connection->proxies_tail, proxy); + proxy->spdy_active = false; + } + spdy_free_connection(glob_opt.spdy_connection); + glob_opt.spdy_connection = NULL; + } + else + { + connections[*real_size] = glob_opt.spdy_connection; + ++(*real_size); + if(maxfd < glob_opt.spdy_connection->fd) maxfd = glob_opt.spdy_connection->fd; + } + } + + connection = glob_opt.spdy_connections_head; + + while(NULL != connection && *real_size < max_size) + { + assert(!glob_opt.only_proxy); + ret = spdy_ctl_select(read_fd_set, + write_fd_set, + except_fd_set, connection); + if(!ret) + { + //PRINT_INFO("TODO drop connection"); + glob_opt.streams_opened -= connection->streams_opened; + DLL_remove(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connection); + glob_opt.total_spdy_connections--; + + for(proxy = connection->proxies_head; NULL != proxy; proxy=proxy->next) + { + DLL_remove(connection->proxies_head, connection->proxies_tail, proxy); + proxy->spdy_active = false; + } + spdy_free_connection(connection); + } + else + { + connections[*real_size] = connection; + ++(*real_size); + if(maxfd < connection->fd) maxfd = connection->fd; + } + connection = connection->next; + } + + //, "TODO max num of conn reached; close something" + assert(NULL == connection); + + return maxfd; +} + + +void +spdy_run(struct pollfd fds[], struct SPDY_Connection *connections[], int size) +{ + int i; + int ret; + struct Proxy *proxy; + //PRINT_INFO2("size is %i", size); + + for(i=0; i<size; ++i) + { + // PRINT_INFO2("exec about to be called for %s", connections[i]->host); + if(fds[i].revents & (POLLIN | POLLOUT)) + { + ret = spdy_exec_io(connections[i]); + //PRINT_INFO2("%i",ret); + //if((spdy_pollfds[i].revents & POLLHUP) || (spdy_pollfds[0].revents & POLLERR)) + // PRINT_INFO("SPDY SPDY_Connection error"); + + //TODO POLLRDHUP + // always close on ret != 0? + + if(0 != ret) + { + glob_opt.streams_opened -= connections[i]->streams_opened; + if(connections[i] == glob_opt.spdy_connection) + { + glob_opt.spdy_connection = NULL; + } + else + { + DLL_remove(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connections[i]); + glob_opt.total_spdy_connections--; + } + for(proxy = connections[i]->proxies_head; NULL != proxy; proxy=proxy->next) + { + DLL_remove(connections[i]->proxies_head, connections[i]->proxies_tail, proxy); + proxy->spdy_active = false; + } + spdy_free_connection(connections[i]); + } + } + else + { + PRINT_INFO("not called"); + } + } +} + +void +spdy_run_select(fd_set * read_fd_set, + fd_set * write_fd_set, + fd_set * except_fd_set, struct SPDY_Connection *connections[], int size) +{ + int i; + int ret; + struct Proxy *proxy; + //PRINT_INFO2("size is %i", size); + + for(i=0; i<size; ++i) + { + // PRINT_INFO2("exec about to be called for %s", connections[i]->host); + if(FD_ISSET(connections[i]->fd, read_fd_set) || FD_ISSET(connections[i]->fd, write_fd_set) || FD_ISSET(connections[i]->fd, except_fd_set)) + { + ret = spdy_exec_io(connections[i]); + //PRINT_INFO2("%i",ret); + //if((spdy_pollfds[i].revents & POLLHUP) || (spdy_pollfds[0].revents & POLLERR)) + // PRINT_INFO("SPDY SPDY_Connection error"); + + //TODO POLLRDHUP + // always close on ret != 0? + + if(0 != ret) + { + glob_opt.streams_opened -= connections[i]->streams_opened; + if(connections[i] == glob_opt.spdy_connection) + { + glob_opt.spdy_connection = NULL; + } + else + { + DLL_remove(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connections[i]); + glob_opt.total_spdy_connections--; + } + for(proxy = connections[i]->proxies_head; NULL != proxy; proxy=proxy->next) + { + DLL_remove(connections[i]->proxies_head, connections[i]->proxies_tail, proxy); + proxy->spdy_active = false; + } + spdy_free_connection(connections[i]); + } + } + else + { + PRINT_INFO("not called"); + } + } +} diff --git a/src/examples/mhd2spdy_spdy.h b/src/examples/mhd2spdy_spdy.h @@ -0,0 +1,67 @@ +/* + Copyright (C) 2013 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file spdy.h + * @author Andrey Uzunov + */ + +#ifndef SPDY_H +#define SPDY_H + +#include "mhd2spdy_structures.h" + +struct SPDY_Connection * spdy_connect(const struct URI *uri, uint16_t port, bool is_tls); + +void spdy_ctl_poll(struct pollfd *pollfd, struct SPDY_Connection *connection); + +bool spdy_ctl_select(fd_set * read_fd_set, + fd_set * write_fd_set, + fd_set * except_fd_set, + struct SPDY_Connection *connection); + +int spdy_exec_io(struct SPDY_Connection *connection); + +void spdy_diec(const char *func, int error_code); + +int +spdy_request(const char **nv, struct Proxy *proxy); + +void spdy_ssl_init_ssl_ctx(SSL_CTX *ssl_ctx, uint16_t *spdy_proto_version); + +void +spdy_free_connection(struct SPDY_Connection * connection); + +void +spdy_get_pollfdset(struct pollfd fds[], struct SPDY_Connection *connections[], int max_size, nfds_t *real_size); + + +int +spdy_get_selectfdset(fd_set * read_fd_set, + fd_set * write_fd_set, + fd_set * except_fd_set, + struct SPDY_Connection *connections[], int max_size, nfds_t *real_size); + +void +spdy_run(struct pollfd fds[], struct SPDY_Connection *connections[], int size); + +void +spdy_run_select(fd_set * read_fd_set, + fd_set * write_fd_set, + fd_set * except_fd_set, struct SPDY_Connection *connections[], int size); + +#endif diff --git a/src/examples/mhd2spdy_structures.c b/src/examples/mhd2spdy_structures.c @@ -0,0 +1,149 @@ +/* + Copyright (C) 2013 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file structures.h + * @author Andrey Uzunov + */ + +#include "mhd2spdy_structures.h" + + +void +free_uri(struct URI * uri) +{ + if(NULL != uri) + { + free(uri->full_uri); + free(uri->scheme); + free(uri->host_and_port); + //free(uri->host_and_port_for_connecting); + free(uri->host); + free(uri->path); + free(uri->path_and_more); + free(uri->query); + free(uri->fragment); + uri->port = 0; + free(uri); + } +} + +int +init_parse_uri(regex_t * preg) +{ + // RFC 2396 + // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? + /* + scheme = $2 + authority = $4 + path = $5 + query = $7 + fragment = $9 + */ + + return regcomp(preg, "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?", REG_EXTENDED); +} + +void +deinit_parse_uri(regex_t * preg) +{ + regfree(preg); +} + +int +parse_uri(regex_t * preg, char * full_uri, struct URI ** uri) +{ + int ret; + char *colon; + long long port; + size_t nmatch = 10; + regmatch_t pmatch[10]; + + if (0 != (ret = regexec(preg, full_uri, nmatch, pmatch, 0))) + return ret; + + *uri = au_malloc(sizeof(struct URI)); + if(NULL == *uri) + return -200; + + (*uri)->full_uri = strdup(full_uri); + + asprintf(&((*uri)->scheme), "%.*s",pmatch[2].rm_eo - pmatch[2].rm_so, &full_uri[pmatch[2].rm_so]); + asprintf(&((*uri)->host_and_port), "%.*s",pmatch[4].rm_eo - pmatch[4].rm_so, &full_uri[pmatch[4].rm_so]); + asprintf(&((*uri)->path), "%.*s",pmatch[5].rm_eo - pmatch[5].rm_so, &full_uri[pmatch[5].rm_so]); + asprintf(&((*uri)->path_and_more), "%.*s",pmatch[9].rm_eo - pmatch[5].rm_so, &full_uri[pmatch[5].rm_so]); + asprintf(&((*uri)->query), "%.*s",pmatch[7].rm_eo - pmatch[7].rm_so, &full_uri[pmatch[7].rm_so]); + asprintf(&((*uri)->fragment), "%.*s",pmatch[9].rm_eo - pmatch[9].rm_so, &full_uri[pmatch[9].rm_so]); + + colon = strrchr((*uri)->host_and_port, ':'); + if(NULL == colon) + { + (*uri)->host = strdup((*uri)->host_and_port); + /*if(0 == strcasecmp("http", uri->scheme)) + { + uri->port = 80; + asprintf(&(uri->host_and_port_for_connecting), "%s:80", uri->host_and_port); + } + else if(0 == strcasecmp("https", uri->scheme)) + { + uri->port = 443; + asprintf(&(uri->host_and_port_for_connecting), "%s:443", uri->host_and_port); + } + else + { + PRINT_INFO("no standard scheme!"); + */(*uri)->port = 0; + /*uri->host_and_port_for_connecting = strdup(uri->host_and_port); + }*/ + return 0; + } + + port = atoi(colon + 1); + if(port<1 || port >= 256 * 256) + { + free_uri(*uri); + return -100; + } + (*uri)->port = port; + asprintf(&((*uri)->host), "%.*s", (int)(colon - (*uri)->host_and_port), (*uri)->host_and_port); + + return 0; +} + +void +free_proxy(struct Proxy *proxy) +{ + //PRINT_INFO("free proxy called"); + free(proxy->http_body); + free_uri(proxy->uri); + free(proxy->url); + free(proxy->http_uri); + free(proxy); +} + +void *au_malloc(size_t size) +{ + void *new_memory; + + new_memory = malloc(size); + if(NULL != new_memory) + { + glob_opt.global_memory += size; + memset(new_memory, 0, size); + } + return new_memory; +} diff --git a/src/examples/mhd2spdy_structures.h b/src/examples/mhd2spdy_structures.h @@ -0,0 +1,265 @@ +/* + Copyright (C) 2013 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file structures.h + * @author Andrey Uzunov + */ +#ifndef STRUCTURES_H +#define STRUCTURES_H + +#define _GNU_SOURCE + +#include <unistd.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include <assert.h> +#include <microhttpd.h> +#include <signal.h> +#include <poll.h> +#include <fcntl.h> +#include <regex.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <spdylay/spdylay.h> +#include <getopt.h> + +struct Proxy; + +struct SPDY_Connection { + SSL *ssl; + //SSL_CTX *ssl_ctx; + spdylay_session *session; + struct SPDY_Connection *prev; + struct SPDY_Connection *next; + struct Proxy *proxies_head; + struct Proxy *proxies_tail; + char *host; + /* WANT_READ if SSL connection needs more input; or WANT_WRITE if it + needs more output; or IO_NONE. This is necessary because SSL/TLS + re-negotiation is possible at any time. Spdylay API offers + similar functions like spdylay_session_want_read() and + spdylay_session_want_write() but they do not take into account + SSL connection. */ + int fd; + int want_io; + uint counter; + uint streams_opened; + bool is_tls; +}; + + +struct URI +{ + char * full_uri; + char * scheme; + char * host_and_port; + //char * host_and_port_for_connecting; + char * host; + char * path; + char * path_and_more; + char * query; + char * fragment; + uint16_t port; +}; + +struct HTTP_URI; + +struct Proxy +{ + struct MHD_Connection *http_connection; + struct MHD_Response *http_response; + struct URI *uri; + struct HTTP_URI *http_uri; //TODO remove me + struct SPDY_Connection *spdy_connection; + struct Proxy *next; + struct Proxy *prev; + //char *path; + char *url; + //struct SPDY_Request *request; + //struct SPDY_Response *response; + //CURL *curl_handle; + //struct curl_slist *curl_headers; + //struct SPDY_NameValue *headers; + char *version; + //char *status_msg; + void *http_body; + size_t http_body_size; + ssize_t length; + int status; + int id; + bool done; + bool http_active; + bool spdy_active; +}; + +struct HTTP_URI +{ + char * uri; + struct Proxy * proxy; +}; + +struct SPDY_Headers +{ + const char **nv; + int num; + int cnt; +}; + +struct global_options +{ + char *spdy2http_str; + struct SPDY_Connection *spdy_connection; + struct SPDY_Connection *spdy_connections_head; + struct SPDY_Connection *spdy_connections_tail; + int streams_opened; + int responses_pending; + regex_t uri_preg; + size_t global_memory; + SSL_CTX *ssl_ctx; + uint32_t total_spdy_connections; + uint16_t spdy_proto_version; + uint16_t listen_port; + bool verbose; + bool only_proxy; + bool spdy_data_received; +} glob_opt; + +/* + +#define SOCK_ADDR_IN_PTR(sa) ((struct sockaddr_in *)(sa)) +#define SOCK_ADDR_IN_FAMILY(sa) SOCK_ADDR_IN_PTR(sa)->sin_family +#define SOCK_ADDR_IN_PORT(sa) SOCK_ADDR_IN_PTR(sa)->sin_port +#define SOCK_ADDR_IN_ADDR(sa) SOCK_ADDR_IN_PTR(sa)->sin_addr + +#ifdef HAS_IPV6 + +#define SOCK_ADDR_IN6_PTR(sa) ((struct sockaddr_in6 *)(sa)) +#define SOCK_ADDR_IN6_FAMILY(sa) SOCK_ADDR_IN6_PTR(sa)->sin6_family +#define SOCK_ADDR_IN6_PORT(sa) SOCK_ADDR_IN6_PTR(sa)->sin6_port +#define SOCK_ADDR_IN6_ADDR(sa) SOCK_ADDR_IN6_PTR(sa)->sin6_addr + +#endif +*/ + +//forbidden headers +#define SPDY_HTTP_HEADER_TRANSFER_ENCODING "transfer-encoding" +#define SPDY_HTTP_HEADER_PROXY_CONNECTION "proxy-connection" +#define SPDY_HTTP_HEADER_KEEP_ALIVE "keep-alive" +#define SPDY_HTTP_HEADER_CONNECTION "connection" + +#define MAX_SPDY_CONNECTIONS 100 + + +/** + * Insert an element at the head of a DLL. Assumes that head, tail and + * element are structs with prev and next fields. + * + * @param head pointer to the head of the DLL (struct ? *) + * @param tail pointer to the tail of the DLL (struct ? *) + * @param element element to insert (struct ? *) + */ +#define DLL_insert(head,tail,element) do { \ + (element)->next = (head); \ + (element)->prev = NULL; \ + if ((tail) == NULL) \ + (tail) = element; \ + else \ + (head)->prev = element; \ + (head) = (element); } while (0) + + +/** + * Remove an element from a DLL. Assumes + * that head, tail and element are structs + * with prev and next fields. + * + * @param head pointer to the head of the DLL (struct ? *) + * @param tail pointer to the tail of the DLL (struct ? *) + * @param element element to remove (struct ? *) + */ +#define DLL_remove(head,tail,element) do { \ + if ((element)->prev == NULL) \ + (head) = (element)->next; \ + else \ + (element)->prev->next = (element)->next; \ + if ((element)->next == NULL) \ + (tail) = (element)->prev; \ + else \ + (element)->next->prev = (element)->prev; \ + (element)->next = NULL; \ + (element)->prev = NULL; } while (0) + + +#define PRINT_INFO(msg) do{\ + if(glob_opt.verbose){\ + printf("%i:%s\n", __LINE__, msg);\ + fflush(stdout);\ + }\ + }\ + while(0) + + +#define PRINT_INFO2(fmt, ...) do{\ + if(glob_opt.verbose){\ + printf("%i\n", __LINE__);\ + printf(fmt,##__VA_ARGS__);\ + printf("\n");\ + fflush(stdout);\ + }\ + }\ + while(0) + + +#define DIE(msg) do{\ + printf("FATAL ERROR (line %i): %s\n", __LINE__, msg);\ + fflush(stdout);\ + exit(EXIT_FAILURE);\ + }\ + while(0) + + + + +void +free_uri(struct URI * uri); + +int +init_parse_uri(regex_t * preg); + +void +deinit_parse_uri(regex_t * preg); + +int +parse_uri(regex_t * preg, char * full_uri, struct URI ** uri); + +void +free_proxy(struct Proxy *proxy); + +void *au_malloc(size_t size); + +#endif diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c @@ -2312,7 +2312,7 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) case MHD_CONNECTION_FOOTERS_SENT: #if HAVE_DECL_TCP_CORK /* done sending, uncork */ - { + if (0) { const int val = 0; setsockopt (connection->socket_fd, IPPROTO_TCP, TCP_CORK, &val, sizeof (val)); diff --git a/src/microspdy/io_raw.c b/src/microspdy/io_raw.c @@ -159,6 +159,7 @@ SPDYF_raw_is_pending(struct SPDY_Session *session) int SPDYF_raw_before_write(struct SPDY_Session *session) { +#if HAVE_DECL_TCP_CORK if(0 == (SPDY_DAEMON_FLAG_NO_DELAY & session->daemon->flags)) { int val = 1; @@ -168,6 +169,7 @@ SPDYF_raw_before_write(struct SPDY_Session *session) if(-1 == ret) SPDYF_DEBUG("WARNING: Couldn't set the new connection to TCP_CORK"); } +#endif return SPDY_YES; } @@ -176,6 +178,7 @@ SPDYF_raw_before_write(struct SPDY_Session *session) int SPDYF_raw_after_write(struct SPDY_Session *session, int was_written) { +#if HAVE_DECL_TCP_CORK if(SPDY_YES == was_written && 0 == (SPDY_DAEMON_FLAG_NO_DELAY & session->daemon->flags)) { int val = 0; @@ -186,5 +189,6 @@ SPDYF_raw_after_write(struct SPDY_Session *session, int was_written) SPDYF_DEBUG("WARNING: Couldn't unset the new connection to TCP_CORK"); } +#endif return was_written; }