| /* |
| Copyright 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 mhd2spdy_http.c |
| * @brief HTTP part of the proxy. libmicrohttpd is used for the server side. |
| * @author Andrey Uzunov |
| */ |
| |
| #include "mhd2spdy_structures.h" |
| #include "mhd2spdy_http.h" |
| #include "mhd2spdy_spdy.h" |
| |
| |
| void * |
| http_cb_log(void * cls, |
| const char * uri) |
| { |
| (void)cls; |
| |
| struct HTTP_URI * http_uri; |
| |
| PRINT_INFO2("log uri '%s'\n", uri); |
| |
| //TODO not freed once in a while |
| if(NULL == (http_uri = au_malloc(sizeof(struct HTTP_URI )))) |
| return NULL; |
| http_uri->uri = strdup(uri); |
| return http_uri; |
| } |
| |
| |
| static int |
| http_cb_iterate(void *cls, |
| enum MHD_ValueKind kind, |
| const char *name, |
| const char *value) |
| { |
| (void)kind; |
| |
| 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_cb_response (void *cls, |
| uint64_t pos, |
| char *buffer, |
| size_t max) |
| { |
| (void)pos; |
| |
| int ret; |
| struct Proxy *proxy = (struct Proxy *)cls; |
| void *newbody; |
| const union MHD_ConnectionInfo *info; |
| int val = 1; |
| |
| PRINT_INFO2("http_cb_response for %s", proxy->url); |
| |
| if(proxy->spdy_error) |
| return MHD_CONTENT_READER_END_WITH_ERROR; |
| |
| if(0 == proxy->http_body_size && (proxy->done || !proxy->spdy_active)) |
| { |
| PRINT_INFO("sent end of stream"); |
| return MHD_CONTENT_READER_END_OF_STREAM; |
| } |
| |
| 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 MHD_CONTENT_READER_END_WITH_ERROR; |
| } |
| 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; |
| } |
| |
| PRINT_INFO2("response_callback, size: %i",ret); |
| |
| return ret; |
| } |
| |
| |
| static void |
| http_cb_response_done(void *cls) |
| { |
| (void)cls; |
| //TODO remove |
| } |
| |
| 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)cls; |
| (void)url; |
| (void)upload_data; |
| (void)upload_data_size; |
| |
| int ret; |
| struct Proxy *proxy; |
| struct SPDY_Headers spdy_headers; |
| bool with_body = false; |
| struct HTTP_URI *http_uri; |
| const char *header_value; |
| |
| if (NULL == ptr || NULL == *ptr) |
| return MHD_NO; |
| |
| http_uri = (struct HTTP_URI *)*ptr; |
| |
| if(NULL == http_uri->proxy) |
| { |
| //first call for this request |
| if (0 != strcmp (method, MHD_HTTP_METHOD_GET) && 0 != strcmp (method, MHD_HTTP_METHOD_POST)) |
| { |
| free(http_uri->uri); |
| free(http_uri); |
| PRINT_INFO2("unexpected method %s", method); |
| return MHD_NO; |
| } |
| |
| if(NULL == (proxy = au_malloc(sizeof(struct Proxy)))) |
| { |
| free(http_uri->uri); |
| free(http_uri); |
| PRINT_INFO("No memory"); |
| return MHD_NO; |
| } |
| |
| ++glob_opt.responses_pending; |
| proxy->id = rand(); |
| proxy->http_active = true; |
| proxy->http_connection = connection; |
| http_uri->proxy = proxy; |
| return MHD_YES; |
| } |
| |
| proxy = http_uri->proxy; |
| |
| if(proxy->spdy_error || proxy->http_error) |
| return MHD_NO; // handled at different place TODO? leaks? |
| |
| if(proxy->spdy_active) |
| { |
| if(0 == strcmp (method, MHD_HTTP_METHOD_POST)) |
| { |
| PRINT_INFO("POST processing"); |
| |
| int rc= spdylay_session_resume_data(proxy->spdy_connection->session, proxy->stream_id); |
| PRINT_INFO2("rc is %i stream is %i", rc, proxy->stream_id); |
| proxy->spdy_connection->want_io |= WANT_WRITE; |
| |
| if(0 == *upload_data_size) |
| { |
| PRINT_INFO("POST http EOF"); |
| proxy->receiving_done = true; |
| return MHD_YES; |
| } |
| |
| if(!copy_buffer(upload_data, *upload_data_size, &proxy->received_body, &proxy->received_body_size)) |
| { |
| //TODO handle it better? |
| PRINT_INFO("not enough memory (malloc/realloc returned NULL)"); |
| return MHD_NO; |
| } |
| |
| *upload_data_size = 0; |
| |
| return MHD_YES; |
| } |
| |
| //already handled |
| PRINT_INFO("unnecessary call to http_cb_request"); |
| return MHD_YES; |
| } |
| |
| //second call for this request |
| |
| PRINT_INFO2("received request for '%s %s %s'", method, http_uri->uri, version); |
| |
| proxy->url = http_uri->uri; |
| |
| header_value = MHD_lookup_connection_value(connection, |
| MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH); |
| |
| with_body = 0 == strcmp (method, MHD_HTTP_METHOD_POST) |
| && (NULL == header_value || 0 != strcmp ("0", header_value)); |
| |
| PRINT_INFO2("body will be sent %i", with_body); |
| |
| ret = parse_uri(&glob_opt.uri_preg, proxy->url, &proxy->uri); |
| if(ret != 0) |
| DIE("parse_uri failed"); |
| proxy->http_uri = http_uri; |
| |
| 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] = method; |
| 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_cb_iterate, |
| &spdy_headers); |
| |
| spdy_headers.nv[spdy_headers.cnt] = NULL; |
| if(NULL == spdy_headers.nv[9]) |
| spdy_headers.nv[9] = proxy->uri->host_and_port; |
| |
| if(0 != spdy_request(spdy_headers.nv, proxy, with_body)) |
| { |
| free(spdy_headers.nv); |
| //free_proxy(proxy); |
| |
| return MHD_NO; |
| } |
| free(spdy_headers.nv); |
| |
| proxy->http_response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN, |
| 4096, |
| &http_cb_response, |
| proxy, |
| &http_cb_response_done); |
| |
| if (NULL == proxy->http_response) |
| 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: "); |
| |
| proxy->spdy_active = true; |
| |
| return MHD_YES; |
| } |
| |
| |
| void |
| http_create_response(struct Proxy* proxy, |
| char **nv) |
| { |
| size_t i; |
| |
| if(!proxy->http_active) |
| return; |
| |
| for(i = 0; nv[i]; i += 2) { |
| if(0 == strcmp(":status", nv[i])) |
| { |
| 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])) |
| { |
| continue; |
| } |
| |
| char *header = *(nv+i); |
| 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]); |
| } |
| PRINT_INFO2("adding '%s: %s'",header, nv[i+1]); |
| } |
| |
| if(MHD_NO == MHD_queue_response (proxy->http_connection, proxy->status, proxy->http_response)){ |
| PRINT_INFO("No queue"); |
| //TODO |
| //abort(); |
| proxy->http_error = true; |
| } |
| |
| MHD_destroy_response (proxy->http_response); |
| proxy->http_response = NULL; |
| } |
| |
| void |
| http_cb_request_completed (void *cls, |
| struct MHD_Connection *connection, |
| void **con_cls, |
| enum MHD_RequestTerminationCode toe) |
| { |
| (void)cls; |
| (void)connection; |
| struct HTTP_URI *http_uri; |
| struct Proxy *proxy; |
| |
| http_uri = (struct HTTP_URI *)*con_cls; |
| if(NULL == http_uri) |
| return; |
| proxy = (struct Proxy *)http_uri->proxy; |
| assert(NULL != proxy); |
| |
| PRINT_INFO2("http_cb_request_completed %i for %s; id %i",toe, http_uri->uri, proxy->id); |
| |
| if(NULL != proxy->http_response) |
| { |
| MHD_destroy_response (proxy->http_response); |
| proxy->http_response = NULL; |
| } |
| |
| if(proxy->spdy_active) |
| { |
| proxy->http_active = false; |
| if(MHD_REQUEST_TERMINATED_COMPLETED_OK != toe) |
| { |
| proxy->http_error = true; |
| if(proxy->stream_id > 0 /*&& NULL != proxy->spdy_connection->session*/) |
| { |
| //send RST_STREAM_STATUS_CANCEL |
| PRINT_INFO2("send rst_stream %i %i",proxy->spdy_active, proxy->stream_id ); |
| spdylay_submit_rst_stream(proxy->spdy_connection->session, proxy->stream_id, 5); |
| } |
| /*else |
| { |
| DLL_remove(proxy->spdy_connection->proxies_head, proxy->spdy_connection->proxies_tail, proxy); |
| free_proxy(proxy); |
| }*/ |
| } |
| } |
| else |
| { |
| PRINT_INFO2("proxy free http id %i ", proxy->id); |
| free_proxy(proxy); |
| } |
| |
| --glob_opt.responses_pending; |
| } |