| /* |
| This file is part of libmicrospdy |
| 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 proxy.c |
| * @brief Translates incoming SPDY requests to http server on localhost. |
| * Uses libcurl. |
| * No error handling for curl requests. |
| * TODO: |
| * - test all options! |
| * - don't abort on lack of memory |
| * - Correct recapitalizetion of header names before giving the headers |
| * to curl. |
| * - curl does not close sockets when connection is closed and no |
| * new sockets are opened (they stay in CLOSE_WAIT) |
| * - add '/' when a user requests http://example.com . Now this is a bad |
| * request |
| * - curl returns 0 or 1 ms for timeout even when nothing will be done; |
| * thus the loop uses CPU for nothing |
| * @author Andrey Uzunov |
| */ |
| |
| #include "platform.h" |
| #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 "microspdy.h" |
| #include <curl/curl.h> |
| #include <assert.h> |
| #include <getopt.h> |
| #include <regex.h> |
| |
| #define ERROR_RESPONSE "502 Bad Gateway" |
| |
| |
| struct global_options |
| { |
| char *http_backend; |
| char *cert; |
| char *cert_key; |
| char *listen_host; |
| unsigned int timeout; |
| uint16_t listen_port; |
| bool verbose; |
| bool curl_verbose; |
| bool transparent; |
| bool http10; |
| bool notls; |
| bool nodelay; |
| bool ipv4; |
| bool ipv6; |
| } glob_opt; |
| |
| |
| 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; |
| }; |
| |
| |
| #define PRINT_INFO(msg) do{\ |
| fprintf(stdout, "%i:%s\n", __LINE__, msg);\ |
| fflush(stdout);\ |
| }\ |
| while(0) |
| |
| |
| #define PRINT_INFO2(fmt, ...) do{\ |
| fprintf(stdout, "%i\n", __LINE__);\ |
| fprintf(stdout, fmt,##__VA_ARGS__);\ |
| fprintf(stdout, "\n");\ |
| fflush(stdout);\ |
| }\ |
| while(0) |
| |
| |
| #define PRINT_VERBOSE(msg) do{\ |
| if(glob_opt.verbose){\ |
| fprintf(stdout, "%i:%s\n", __LINE__, msg);\ |
| fflush(stdout);\ |
| }\ |
| }\ |
| while(0) |
| |
| |
| #define PRINT_VERBOSE2(fmt, ...) do{\ |
| if(glob_opt.verbose){\ |
| fprintf(stdout, "%i\n", __LINE__);\ |
| fprintf(stdout, fmt,##__VA_ARGS__);\ |
| fprintf(stdout, "\n");\ |
| fflush(stdout);\ |
| }\ |
| }\ |
| while(0) |
| |
| |
| #define CURL_SETOPT(handle, opt, val) do{\ |
| int ret; \ |
| if(CURLE_OK != (ret = curl_easy_setopt(handle, opt, val))) \ |
| { \ |
| PRINT_INFO2("curl_easy_setopt failed (%i = %i)", opt, ret); \ |
| abort(); \ |
| } \ |
| }\ |
| while(0) |
| |
| |
| #define DIE(msg) do{\ |
| printf("FATAL ERROR (line %i): %s\n", __LINE__, msg);\ |
| fflush(stdout);\ |
| exit(EXIT_FAILURE);\ |
| }\ |
| while(0) |
| |
| |
| static int loop = 1; |
| |
| static CURLM *multi_handle; |
| |
| static int still_running = 0; /* keep number of running handles */ |
| |
| static regex_t uri_preg; |
| |
| static bool call_spdy_run; |
| static bool call_curl_run; |
| |
| int debug_num_curls; |
| |
| |
| struct Proxy |
| { |
| 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; |
| void *received_body; |
| bool *session_alive; |
| size_t http_body_size; |
| size_t received_body_size; |
| //ssize_t length; |
| int status; |
| //bool done; |
| bool receiving_done; |
| bool is_curl_read_paused; |
| bool is_with_body_data; |
| //bool error; |
| bool curl_done; |
| bool curl_error; |
| bool spdy_done; |
| bool spdy_error; |
| }; |
| |
| |
| static 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); |
| } |
| } |
| |
| |
| static int |
| init_parse_uri(regex_t * preg) |
| { |
| // RFC 2396 |
| // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? |
| /* |
| scheme = $2 |
| authority = $4 |
| path = $5 |
| query = $7 |
| fragment = $9 |
| */ |
| |
| return regcomp(preg, "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?", REG_EXTENDED); |
| } |
| |
| |
| static void |
| deinit_parse_uri(regex_t * preg) |
| { |
| regfree(preg); |
| } |
| |
| |
| static int |
| parse_uri(regex_t * preg, const char * full_uri, struct URI ** uri) |
| { |
| //TODO memeory checks |
| 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 = malloc(sizeof(struct URI)); |
| if(NULL == *uri) |
| return -200; |
| |
| (*uri)->full_uri = strdup(full_uri); |
| |
| asprintf(&((*uri)->scheme), |
| "%.*s", |
| (int) (pmatch[2].rm_eo - pmatch[2].rm_so), |
| &full_uri[pmatch[2].rm_so]); |
| asprintf(&((*uri)->host_and_port), "%.*s", |
| (int) (pmatch[4].rm_eo - pmatch[4].rm_so), |
| &full_uri[pmatch[4].rm_so]); |
| asprintf(&((*uri)->path), |
| "%.*s", |
| (int) (pmatch[5].rm_eo - pmatch[5].rm_so), |
| &full_uri[pmatch[5].rm_so]); |
| asprintf(&((*uri)->path_and_more), |
| "%.*s", |
| (int) (pmatch[9].rm_eo - pmatch[5].rm_so), |
| &full_uri[pmatch[5].rm_so]); |
| asprintf(&((*uri)->query), |
| "%.*s", |
| (int) (pmatch[7].rm_eo - pmatch[7].rm_so), |
| &full_uri[pmatch[7].rm_so]); |
| asprintf(&((*uri)->fragment), |
| "%.*s", |
| (int) (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; |
| } |
| |
| |
| static bool |
| store_in_buffer(const void *src, size_t src_size, void **dst, size_t *dst_size) |
| { |
| if(0 == src_size) |
| return true; |
| |
| if(NULL == *dst) |
| *dst = malloc(src_size); |
| else |
| *dst = realloc(*dst, src_size + *dst_size); |
| if(NULL == *dst) |
| return false; |
| |
| memcpy(*dst + *dst_size, src, src_size); |
| *dst_size += src_size; |
| |
| return true; |
| } |
| |
| |
| static ssize_t |
| get_from_buffer(void **src, size_t *src_size, void *dst, size_t max_size) |
| { |
| size_t ret; |
| void *newbody; |
| |
| if(max_size >= *src_size) |
| { |
| ret = *src_size; |
| newbody = NULL; |
| } |
| else |
| { |
| ret = max_size; |
| if(NULL == (newbody = malloc(*src_size - max_size))) |
| return -1; |
| memcpy(newbody, *src + ret, *src_size - ret); |
| } |
| memcpy(dst, *src, ret); |
| free(*src); |
| *src = newbody; |
| *src_size -= ret; |
| |
| return ret; |
| } |
| |
| |
| static void |
| catch_signal(int signal) |
| { |
| (void)signal; |
| |
| loop = 0; |
| } |
| |
| static void |
| new_session_cb (void * cls, |
| struct SPDY_Session * session) |
| { |
| (void)cls; |
| |
| bool *session_alive; |
| |
| PRINT_VERBOSE("new session"); |
| //TODO clean this memory |
| if(NULL == (session_alive = malloc(sizeof(bool)))) |
| { |
| DIE("no memory"); |
| } |
| *session_alive = true; |
| SPDY_set_cls_to_session(session, |
| session_alive); |
| } |
| |
| static void |
| session_closed_cb (void * cls, |
| struct SPDY_Session * session, |
| int by_client) |
| { |
| (void)cls; |
| |
| bool *session_alive; |
| |
| PRINT_VERBOSE2("session closed; by client: %i", by_client); |
| |
| session_alive = SPDY_get_cls_from_session(session); |
| assert(NULL != session_alive); |
| |
| *session_alive = false; |
| } |
| |
| |
| static int |
| spdy_post_data_cb (void * cls, |
| struct SPDY_Request *request, |
| const void * buf, |
| size_t size, |
| bool more) |
| { |
| (void)cls; |
| int ret; |
| struct Proxy *proxy = (struct Proxy *)SPDY_get_cls_from_request(request); |
| |
| if(!store_in_buffer(buf, size, &proxy->received_body, &proxy->received_body_size)) |
| { |
| PRINT_INFO("not enough memory (malloc/realloc returned NULL)"); |
| return 0; |
| } |
| |
| proxy->receiving_done = !more; |
| |
| PRINT_VERBOSE2("POST bytes from SPDY: %zu", size); |
| |
| call_curl_run = true; |
| |
| if(proxy->is_curl_read_paused) |
| { |
| if(CURLE_OK != (ret = curl_easy_pause(proxy->curl_handle, CURLPAUSE_CONT))) |
| { |
| PRINT_INFO2("curl_easy_pause returned %i", ret); |
| abort(); |
| } |
| PRINT_VERBOSE("curl_read_cb pause resumed"); |
| } |
| |
| return SPDY_YES; |
| } |
| |
| |
| ssize_t |
| response_callback (void *cls, |
| void *buffer, |
| size_t max, |
| bool *more) |
| { |
| ssize_t ret; |
| struct Proxy *proxy = (struct Proxy *)cls; |
| |
| *more = true; |
| |
| assert(!proxy->spdy_error); |
| |
| if(proxy->curl_error) |
| { |
| PRINT_VERBOSE("tell spdy about the error"); |
| return -1; |
| } |
| |
| if(!proxy->http_body_size)//nothing to write now |
| { |
| PRINT_VERBOSE("nothing to write now"); |
| if(proxy->curl_done || proxy->curl_error) *more = false; |
| return 0; |
| } |
| |
| ret = get_from_buffer(&(proxy->http_body), &(proxy->http_body_size), buffer, max); |
| if(ret < 0) |
| { |
| PRINT_INFO("no memory"); |
| //TODO error? |
| return -1; |
| } |
| |
| if((proxy->curl_done || proxy->curl_error) && 0 == proxy->http_body_size) *more = false; |
| |
| PRINT_VERBOSE2("given bytes to microspdy: %zd", ret); |
| |
| return ret; |
| } |
| |
| |
| static void |
| cleanup(struct Proxy *proxy) |
| { |
| int ret; |
| |
| //fprintf(stderr, "free proxy for %s\n", proxy->url); |
| |
| if(CURLM_OK != (ret = curl_multi_remove_handle(multi_handle, proxy->curl_handle))) |
| { |
| PRINT_INFO2("curl_multi_remove_handle failed (%i)", ret); |
| DIE("bug in cleanup"); |
| } |
| debug_num_curls--; |
| //TODO bug on ku6.com or amazon.cn |
| // after curl_multi_remove_handle returned CURLM_BAD_EASY_HANDLE |
| curl_slist_free_all(proxy->curl_headers); |
| curl_easy_cleanup(proxy->curl_handle); |
| |
| free(proxy->url); |
| free(proxy); |
| } |
| |
| |
| static void |
| response_done_callback(void *cls, |
| struct SPDY_Response *response, |
| struct SPDY_Request *request, |
| enum SPDY_RESPONSE_RESULT status, |
| bool streamopened) |
| { |
| (void)streamopened; |
| struct Proxy *proxy = (struct Proxy *)cls; |
| |
| if(SPDY_RESPONSE_RESULT_SUCCESS != status) |
| { |
| free(proxy->http_body); |
| proxy->http_body = NULL; |
| proxy->spdy_error = true; |
| } |
| cleanup(proxy); |
| SPDY_destroy_request(request); |
| SPDY_destroy_response(response); |
| } |
| |
| |
| static size_t |
| curl_header_cb(void *ptr, size_t size, size_t nmemb, void *userp) |
| { |
| size_t realsize = size * nmemb; |
| struct Proxy *proxy = (struct Proxy *)userp; |
| char *line = (char *)ptr; |
| char *name; |
| char *value; |
| char *status; |
| unsigned int i; |
| unsigned int pos; |
| int ret; |
| int num_values; |
| const char * const * values; |
| bool abort_it; |
| |
| //printf("curl_header_cb %s\n", line); |
| if(!*(proxy->session_alive)) |
| { |
| PRINT_VERBOSE("headers received, but session is dead"); |
| proxy->spdy_error = true; |
| proxy->curl_error = true; |
| return 0; |
| } |
| |
| //trailer |
| if(NULL != proxy->response) return 0; |
| |
| if('\r' == line[0] || '\n' == line[0]) |
| { |
| //all headers were already handled; prepare spdy frames |
| if(NULL == (proxy->response = SPDY_build_response_with_callback(proxy->status, |
| proxy->status_msg, |
| proxy->version, |
| proxy->headers, |
| &response_callback, |
| proxy, |
| 0))) |
| //256))) |
| DIE("no response"); |
| |
| SPDY_name_value_destroy(proxy->headers); |
| proxy->headers = NULL; |
| free(proxy->status_msg); |
| proxy->status_msg = NULL; |
| free(proxy->version); |
| proxy->version = NULL; |
| |
| if(SPDY_YES != SPDY_queue_response(proxy->request, |
| proxy->response, |
| true, |
| false, |
| &response_done_callback, |
| proxy)) |
| { |
| //DIE("no queue"); |
| //TODO right? |
| proxy->spdy_error = true; |
| proxy->curl_error = true; |
| PRINT_VERBOSE2("no queue in curl_header_cb for %s", proxy->url); |
| SPDY_destroy_response(proxy->response); |
| proxy->response = NULL; |
| return 0; |
| } |
| |
| call_spdy_run = true; |
| |
| return realsize; |
| } |
| |
| pos = 0; |
| if(NULL == proxy->version) |
| { |
| //first line from headers |
| //version |
| for(i=pos; i<realsize && ' '!=line[i]; ++i); |
| if(i == realsize) |
| DIE("error on parsing headers"); |
| if(NULL == (proxy->version = strndup(line, i - pos))) |
| DIE("No memory"); |
| pos = i+1; |
| |
| //status (number) |
| for(i=pos; i<realsize && ' '!=line[i] && '\r'!=line[i] && '\n'!=line[i]; ++i); |
| if(NULL == (status = strndup(&(line[pos]), i - pos))) |
| DIE("No memory"); |
| proxy->status = atoi(status); |
| free(status); |
| if(i<realsize && '\r'!=line[i] && '\n'!=line[i]) |
| { |
| //status (message) |
| pos = i+1; |
| for(i=pos; i<realsize && '\r'!=line[i] && '\n'!=line[i]; ++i); |
| if(NULL == (proxy->status_msg = strndup(&(line[pos]), i - pos))) |
| DIE("No memory"); |
| } |
| PRINT_VERBOSE2("Header line received '%s' '%i' '%s' ", proxy->version, proxy->status, proxy->status_msg); |
| return realsize; |
| } |
| |
| //other lines |
| //header name |
| for(i=pos; i<realsize && ':'!=line[i] && '\r'!=line[i] && '\n'!=line[i]; ++i) |
| line[i] = tolower(line[i]); //spdy requires lower case |
| if(NULL == (name = strndup(line, i - pos))) |
| DIE("No memory"); |
| if(0 == strcmp(SPDY_HTTP_HEADER_CONNECTION, name) |
| || 0 == strcmp(SPDY_HTTP_HEADER_KEEP_ALIVE, name) |
| || 0 == strcmp(SPDY_HTTP_HEADER_TRANSFER_ENCODING, name) |
| ) |
| { |
| //forbidden in spdy, ignore |
| free(name); |
| return realsize; |
| } |
| if(i == realsize || '\r'==line[i] || '\n'==line[i]) |
| { |
| //no value. is it possible? |
| if(SPDY_YES != SPDY_name_value_add(proxy->headers, name, "")) |
| DIE("SPDY_name_value_add failed"); |
| return realsize; |
| } |
| |
| //header value |
| pos = i+1; |
| while(pos<realsize && isspace(line[pos])) ++pos; //remove leading space |
| for(i=pos; i<realsize && '\r'!=line[i] && '\n'!=line[i]; ++i); |
| if(NULL == (value = strndup(&(line[pos]), i - pos))) |
| DIE("No memory"); |
| PRINT_VERBOSE2("Adding header: '%s': '%s'", name, value); |
| if(SPDY_YES != (ret = SPDY_name_value_add(proxy->headers, name, value))) |
| { |
| abort_it=true; |
| if(NULL != (values = SPDY_name_value_lookup(proxy->headers, name, &num_values))) |
| for(i=0; i<(unsigned int)num_values; ++i) |
| if(0 == strcasecmp(value, values[i])) |
| { |
| abort_it=false; |
| PRINT_VERBOSE2("header appears more than once with same value '%s: %s'", name, value); |
| break; |
| } |
| |
| if(abort_it) |
| { |
| PRINT_INFO2("SPDY_name_value_add failed (%i) for '%s'", ret, name); |
| abort(); |
| } |
| } |
| free(name); |
| free(value); |
| |
| return realsize; |
| } |
| |
| |
| static size_t |
| curl_write_cb(void *contents, size_t size, size_t nmemb, void *userp) |
| { |
| size_t realsize = size * nmemb; |
| struct Proxy *proxy = (struct Proxy *)userp; |
| |
| //printf("curl_write_cb %i\n", realsize); |
| if(!*(proxy->session_alive)) |
| { |
| PRINT_VERBOSE("data received, but session is dead"); |
| proxy->spdy_error = true; |
| proxy->curl_error = true; |
| return 0; |
| } |
| |
| if(!store_in_buffer(contents, realsize, &proxy->http_body, &proxy->http_body_size)) |
| { |
| PRINT_INFO("not enough memory (malloc/realloc returned NULL)"); |
| proxy->curl_error = true; |
| return 0; |
| } |
| /* |
| if(NULL == proxy->http_body) |
| proxy->http_body = malloc(realsize); |
| else |
| proxy->http_body = realloc(proxy->http_body, proxy->http_body_size + realsize); |
| if(NULL == proxy->http_body) |
| { |
| PRINT_INFO("not enough memory (realloc returned NULL)"); |
| return 0; |
| } |
| |
| memcpy(proxy->http_body + proxy->http_body_size, contents, realsize); |
| proxy->http_body_size += realsize; |
| */ |
| |
| PRINT_VERBOSE2("received bytes from curl: %zu", realsize); |
| |
| call_spdy_run = true; |
| |
| return realsize; |
| } |
| |
| |
| static size_t |
| curl_read_cb(void *ptr, size_t size, size_t nmemb, void *userp) |
| { |
| ssize_t ret; |
| size_t max = size * nmemb; |
| struct Proxy *proxy = (struct Proxy *)userp; |
| //void *newbody; |
| |
| |
| if((proxy->receiving_done && !proxy->received_body_size) || !proxy->is_with_body_data || max < 1) |
| { |
| PRINT_VERBOSE("curl_read_cb last call"); |
| return 0; |
| } |
| |
| if(!*(proxy->session_alive)) |
| { |
| PRINT_VERBOSE("POST is still being sent, but session is dead"); |
| return CURL_READFUNC_ABORT; |
| } |
| |
| if(!proxy->received_body_size)//nothing to write now |
| { |
| PRINT_VERBOSE("curl_read_cb called paused"); |
| proxy->is_curl_read_paused = true; |
| return CURL_READFUNC_PAUSE;//TODO curl pause should be used |
| } |
| |
| ret = get_from_buffer(&(proxy->received_body), &(proxy->received_body_size), ptr, max); |
| if(ret < 0) |
| { |
| PRINT_INFO("no memory"); |
| return CURL_READFUNC_ABORT; |
| } |
| |
| /* |
| if(max >= proxy->received_body_size) |
| { |
| ret = proxy->received_body_size; |
| newbody = NULL; |
| } |
| else |
| { |
| ret = max; |
| if(NULL == (newbody = malloc(proxy->received_body_size - max))) |
| { |
| PRINT_INFO("no memory"); |
| return CURL_READFUNC_ABORT; |
| } |
| memcpy(newbody, proxy->received_body + max, proxy->received_body_size - max); |
| } |
| memcpy(ptr, proxy->received_body, ret); |
| free(proxy->received_body); |
| proxy->received_body = newbody; |
| proxy->received_body_size -= ret; |
| * */ |
| |
| PRINT_VERBOSE2("given POST bytes to curl: %zd", ret); |
| |
| return ret; |
| } |
| |
| |
| static int |
| iterate_cb (void *cls, const char *name, const char * const * value, int num_values) |
| { |
| struct Proxy *proxy = (struct Proxy *)cls; |
| struct curl_slist **curl_headers = (&(proxy->curl_headers)); |
| char *line; |
| int line_len = strlen(name) + 3; //+ ": \0" |
| int i; |
| |
| for(i=0; i<num_values; ++i) |
| { |
| if(i) line_len += 2; //", " |
| line_len += strlen(value[i]); |
| } |
| |
| if(NULL == (line = malloc(line_len)))//no recovery |
| DIE("No memory"); |
| line[0] = 0; |
| |
| strcat(line, name); |
| strcat(line, ": "); |
| //all spdy header names are lower case; |
| //for simplicity here we just capitalize the first letter |
| line[0] = toupper(line[0]); |
| |
| for(i=0; i<num_values; ++i) |
| { |
| if(i) strcat(line, ", "); |
| PRINT_VERBOSE2("Adding request header: '%s' len %ld", value[i], strlen(value[i])); |
| strcat(line, value[i]); |
| } |
| PRINT_VERBOSE2("Adding request header: '%s'", line); |
| if(NULL == (*curl_headers = curl_slist_append(*curl_headers, line))) |
| DIE("curl_slist_append failed"); |
| free(line); |
| |
| return SPDY_YES; |
| } |
| |
| |
| static void |
| standard_request_handler(void *cls, |
| struct SPDY_Request * request, |
| uint8_t priority, |
| const char *method, |
| const char *path, |
| const char *version, |
| const char *host, |
| const char *scheme, |
| struct SPDY_NameValue * headers, |
| bool more) |
| { |
| (void)cls; |
| (void)priority; |
| (void)host; |
| (void)scheme; |
| |
| struct Proxy *proxy; |
| int ret; |
| struct URI *uri; |
| struct SPDY_Session *session; |
| |
| proxy = SPDY_get_cls_from_request(request); |
| if(NULL != proxy) |
| { |
| //ignore trailers or more headers |
| return; |
| } |
| |
| PRINT_VERBOSE2("received request for '%s %s %s'\n", method, path, version); |
| |
| //TODO not freed once in a while |
| if(NULL == (proxy = malloc(sizeof(struct Proxy)))) |
| DIE("No memory"); |
| memset(proxy, 0, sizeof(struct Proxy)); |
| |
| //fprintf(stderr, "new proxy for %s\n", path); |
| |
| session = SPDY_get_session_for_request(request); |
| assert(NULL != session); |
| proxy->session_alive = SPDY_get_cls_from_session(session); |
| assert(NULL != proxy->session_alive); |
| |
| SPDY_set_cls_to_request(request, proxy); |
| |
| proxy->request = request; |
| proxy->is_with_body_data = more; |
| if(NULL == (proxy->headers = SPDY_name_value_create())) |
| DIE("No memory"); |
| |
| if(glob_opt.transparent) |
| { |
| if(NULL != glob_opt.http_backend) //use always same host |
| ret = asprintf(&(proxy->url),"%s://%s%s", scheme, glob_opt.http_backend, path); |
| else //use host header |
| ret = asprintf(&(proxy->url),"%s://%s%s", scheme, host, path); |
| if(-1 == ret) |
| DIE("No memory"); |
| |
| ret = parse_uri(&uri_preg, proxy->url, &uri); |
| if(ret != 0) |
| DIE("parsing built uri failed"); |
| } |
| else |
| { |
| ret = parse_uri(&uri_preg, path, &uri); |
| PRINT_INFO2("path %s '%s' '%s'", path, uri->scheme, uri->host); |
| if(ret != 0 || !strlen(uri->scheme) || !strlen(uri->host)) |
| DIE("parsing received uri failed"); |
| |
| if(NULL != glob_opt.http_backend) //use backend host |
| { |
| ret = asprintf(&(proxy->url),"%s://%s%s", uri->scheme, glob_opt.http_backend, uri->path_and_more); |
| if(-1 == ret) |
| DIE("No memory"); |
| } |
| else //use request path |
| if(NULL == (proxy->url = strdup(path))) |
| DIE("No memory"); |
| } |
| |
| free_uri(uri); |
| |
| PRINT_VERBOSE2("curl will request '%s'", proxy->url); |
| |
| SPDY_name_value_iterate(headers, &iterate_cb, proxy); |
| |
| if(NULL == (proxy->curl_handle = curl_easy_init())) |
| { |
| PRINT_INFO("curl_easy_init failed"); |
| abort(); |
| } |
| |
| if(glob_opt.curl_verbose) |
| CURL_SETOPT(proxy->curl_handle, CURLOPT_VERBOSE, 1); |
| |
| if(0 == strcmp(SPDY_HTTP_METHOD_POST,method)) |
| { |
| if(NULL == (proxy->curl_headers = curl_slist_append(proxy->curl_headers, "Expect:"))) |
| DIE("curl_slist_append failed"); |
| CURL_SETOPT(proxy->curl_handle, CURLOPT_POST, 1); |
| CURL_SETOPT(proxy->curl_handle, CURLOPT_READFUNCTION, curl_read_cb); |
| CURL_SETOPT(proxy->curl_handle, CURLOPT_READDATA, proxy); |
| } |
| |
| if(glob_opt.timeout) |
| CURL_SETOPT(proxy->curl_handle, CURLOPT_TIMEOUT, glob_opt.timeout); |
| CURL_SETOPT(proxy->curl_handle, CURLOPT_URL, proxy->url); |
| if(glob_opt.http10) |
| CURL_SETOPT(proxy->curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); |
| CURL_SETOPT(proxy->curl_handle, CURLOPT_WRITEFUNCTION, curl_write_cb); |
| CURL_SETOPT(proxy->curl_handle, CURLOPT_WRITEDATA, proxy); |
| CURL_SETOPT(proxy->curl_handle, CURLOPT_HEADERFUNCTION, curl_header_cb); |
| CURL_SETOPT(proxy->curl_handle, CURLOPT_HEADERDATA, proxy); |
| CURL_SETOPT(proxy->curl_handle, CURLOPT_PRIVATE, proxy); |
| CURL_SETOPT(proxy->curl_handle, CURLOPT_HTTPHEADER, proxy->curl_headers); |
| CURL_SETOPT(proxy->curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);//TODO |
| CURL_SETOPT(proxy->curl_handle, CURLOPT_SSL_VERIFYHOST, 0L); |
| if(glob_opt.ipv4 && !glob_opt.ipv6) |
| CURL_SETOPT(proxy->curl_handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); |
| else if(glob_opt.ipv6 && !glob_opt.ipv4) |
| CURL_SETOPT(proxy->curl_handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); |
| |
| if(CURLM_OK != (ret = curl_multi_add_handle(multi_handle, proxy->curl_handle))) |
| { |
| PRINT_INFO2("curl_multi_add_handle failed (%i)", ret); |
| abort(); |
| } |
| debug_num_curls++; |
| |
| //~5ms additional latency for calling this |
| if(CURLM_OK != (ret = curl_multi_perform(multi_handle, &still_running)) |
| && CURLM_CALL_MULTI_PERFORM != ret) |
| { |
| PRINT_INFO2("curl_multi_perform failed (%i)", ret); |
| abort(); |
| } |
| |
| call_curl_run = true; |
| } |
| |
| |
| static int |
| run () |
| { |
| unsigned long long timeoutlong = 0; |
| unsigned long long timeout_spdy = 0; |
| long timeout_curl = -1; |
| struct timeval timeout; |
| int ret; |
| int ret_curl; |
| int ret_spdy; |
| fd_set rs; |
| fd_set ws; |
| fd_set es; |
| int maxfd = -1; |
| int maxfd_curl = -1; |
| struct SPDY_Daemon *daemon; |
| CURLMsg *msg; |
| int msgs_left; |
| struct Proxy *proxy; |
| struct sockaddr_in *addr; |
| struct addrinfo hints; |
| char service[NI_MAXSERV]; |
| struct addrinfo *gai; |
| enum SPDY_IO_SUBSYSTEM io = glob_opt.notls ? SPDY_IO_SUBSYSTEM_RAW : SPDY_IO_SUBSYSTEM_OPENSSL; |
| enum SPDY_DAEMON_FLAG flags = SPDY_DAEMON_FLAG_NO; |
| //struct SPDY_Response *error_response; |
| char *curl_private; |
| |
| signal(SIGPIPE, SIG_IGN); |
| |
| if (signal(SIGINT, catch_signal) == SIG_ERR) |
| PRINT_VERBOSE("signal failed"); |
| |
| srand(time(NULL)); |
| if(init_parse_uri(&uri_preg)) |
| DIE("Regexp compilation failed"); |
| |
| SPDY_init(); |
| |
| if(glob_opt.nodelay) |
| flags |= SPDY_DAEMON_FLAG_NO_DELAY; |
| |
| if(NULL == glob_opt.listen_host) |
| { |
| daemon = SPDY_start_daemon(glob_opt.listen_port, |
| glob_opt.cert, |
| glob_opt.cert_key, |
| &new_session_cb, |
| &session_closed_cb, |
| &standard_request_handler, |
| &spdy_post_data_cb, |
| NULL, |
| SPDY_DAEMON_OPTION_SESSION_TIMEOUT, |
| 1800, |
| SPDY_DAEMON_OPTION_IO_SUBSYSTEM, |
| io, |
| SPDY_DAEMON_OPTION_FLAGS, |
| flags, |
| SPDY_DAEMON_OPTION_END); |
| } |
| else |
| { |
| snprintf (service, sizeof(service), "%u", glob_opt.listen_port); |
| memset (&hints, 0, sizeof(struct addrinfo)); |
| hints.ai_family = AF_INET; |
| hints.ai_socktype = SOCK_STREAM; |
| |
| ret = getaddrinfo(glob_opt.listen_host, service, &hints, &gai); |
| if(ret != 0) |
| DIE("problem with specified host"); |
| |
| addr = (struct sockaddr_in *) gai->ai_addr; |
| |
| daemon = SPDY_start_daemon(0, |
| glob_opt.cert, |
| glob_opt.cert_key, |
| &new_session_cb, |
| &session_closed_cb, |
| &standard_request_handler, |
| &spdy_post_data_cb, |
| NULL, |
| SPDY_DAEMON_OPTION_SESSION_TIMEOUT, |
| 1800, |
| SPDY_DAEMON_OPTION_IO_SUBSYSTEM, |
| io, |
| SPDY_DAEMON_OPTION_FLAGS, |
| flags, |
| SPDY_DAEMON_OPTION_SOCK_ADDR, |
| addr, |
| //SPDY_DAEMON_OPTION_MAX_NUM_FRAMES, |
| //1, |
| SPDY_DAEMON_OPTION_END); |
| } |
| |
| if(NULL==daemon){ |
| printf("no daemon\n"); |
| return 1; |
| } |
| |
| multi_handle = curl_multi_init(); |
| if(NULL==multi_handle) |
| DIE("no multi_handle"); |
| |
| timeout.tv_usec = 0; |
| |
| do |
| { |
| FD_ZERO(&rs); |
| FD_ZERO(&ws); |
| FD_ZERO(&es); |
| |
| PRINT_VERBOSE2("num curls %i", debug_num_curls); |
| |
| ret_spdy = SPDY_get_timeout(daemon, &timeout_spdy); |
| if(SPDY_NO == ret_spdy || timeout_spdy > 5000) |
| timeoutlong = 5000; |
| else |
| timeoutlong = timeout_spdy; |
| PRINT_VERBOSE2("SPDY timeout %lld; %i", timeout_spdy, ret_spdy); |
| |
| if(CURLM_OK != (ret_curl = curl_multi_timeout(multi_handle, &timeout_curl))) |
| { |
| PRINT_VERBOSE2("curl_multi_timeout failed (%i)", ret_curl); |
| //curl_timeo = timeoutlong; |
| } |
| else if(timeout_curl >= 0 && timeoutlong > (unsigned long)timeout_curl) |
| timeoutlong = (unsigned long)timeout_curl; |
| |
| PRINT_VERBOSE2("curl timeout %ld", timeout_curl); |
| |
| timeout.tv_sec = timeoutlong / 1000; |
| timeout.tv_usec = (timeoutlong % 1000) * 1000; |
| |
| maxfd = SPDY_get_fdset (daemon, |
| &rs, |
| &ws, |
| &es); |
| assert(-1 != maxfd); |
| |
| if(CURLM_OK != (ret = curl_multi_fdset(multi_handle, &rs, |
| &ws, |
| &es, &maxfd_curl))) |
| { |
| PRINT_INFO2("curl_multi_fdset failed (%i)", ret); |
| abort(); |
| } |
| |
| if(maxfd_curl > maxfd) |
| maxfd = maxfd_curl; |
| |
| PRINT_VERBOSE2("timeout before %lld %lld", (unsigned long long)timeout.tv_sec, (unsigned long long)timeout.tv_usec); |
| ret = select(maxfd+1, &rs, &ws, &es, &timeout); |
| PRINT_VERBOSE2("timeout after %lld %lld; ret is %i", (unsigned long long)timeout.tv_sec, (unsigned long long)timeout.tv_usec, ret); |
| |
| /*switch(ret) { |
| case -1: |
| PRINT_INFO2("select error: %i", errno); |
| break; |
| case 0: |
| break; |
| default:*/ |
| |
| //the second part should not happen with current implementation |
| if(ret > 0 || (SPDY_YES == ret_spdy && 0 == timeout_spdy)) |
| { |
| PRINT_VERBOSE("run spdy"); |
| SPDY_run(daemon); |
| call_spdy_run = false; |
| } |
| |
| //if(ret > 0 || (CURLM_OK == ret_curl && 0 == timeout_curl) || call_curl_run) |
| { |
| PRINT_VERBOSE("run curl"); |
| if(CURLM_OK != (ret = curl_multi_perform(multi_handle, &still_running)) |
| && CURLM_CALL_MULTI_PERFORM != ret) |
| { |
| PRINT_INFO2("curl_multi_perform failed (%i)", ret); |
| abort(); |
| } |
| call_curl_run = false; |
| } |
| /*break; |
| }*/ |
| |
| while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) { |
| if (msg->msg == CURLMSG_DONE) { |
| PRINT_VERBOSE("A curl handler is done"); |
| if(CURLE_OK != (ret = curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &curl_private))) |
| { |
| PRINT_INFO2("err %i",ret); |
| abort(); |
| } |
| assert(NULL != curl_private); |
| proxy = (struct Proxy *)curl_private; |
| if(CURLE_OK == msg->data.result) |
| { |
| proxy->curl_done = true; |
| call_spdy_run = true; |
| //TODO what happens with proxy when the client resets a stream |
| //and response_done is not yet set for the last frame? is it |
| //possible? |
| } |
| else |
| { |
| PRINT_VERBOSE2("bad curl result (%i) for '%s'", msg->data.result, proxy->url); |
| if(proxy->spdy_done || proxy->spdy_error || (NULL == proxy->response && !*(proxy->session_alive))) |
| { |
| PRINT_VERBOSE("cleaning"); |
| SPDY_name_value_destroy(proxy->headers); |
| SPDY_destroy_request(proxy->request); |
| SPDY_destroy_response(proxy->response); |
| cleanup(proxy); |
| } |
| else if(NULL == proxy->response && *(proxy->session_alive)) |
| { |
| //generate error for the client |
| PRINT_VERBOSE("will send Bad Gateway"); |
| SPDY_name_value_destroy(proxy->headers); |
| proxy->headers = NULL; |
| if(NULL == (proxy->response = SPDY_build_response(SPDY_HTTP_BAD_GATEWAY, |
| NULL, |
| SPDY_HTTP_VERSION_1_1, |
| NULL, |
| ERROR_RESPONSE, |
| strlen(ERROR_RESPONSE)))) |
| DIE("no response"); |
| if(SPDY_YES != SPDY_queue_response(proxy->request, |
| proxy->response, |
| true, |
| false, |
| &response_done_callback, |
| proxy)) |
| { |
| //clean and forget |
| PRINT_VERBOSE("cleaning"); |
| SPDY_destroy_request(proxy->request); |
| SPDY_destroy_response(proxy->response); |
| cleanup(proxy); |
| } |
| } |
| else |
| { |
| proxy->curl_error = true; |
| } |
| call_spdy_run = true; |
| //TODO spdy should be notified to send RST_STREAM |
| } |
| } |
| else PRINT_INFO("shouldn't happen"); |
| } |
| |
| if(call_spdy_run) |
| { |
| PRINT_VERBOSE("second call to SPDY_run"); |
| SPDY_run(daemon); |
| call_spdy_run = false; |
| } |
| |
| if(glob_opt.verbose) |
| { |
| |
| #ifdef HAVE_CLOCK_GETTIME |
| #ifdef CLOCK_MONOTONIC |
| struct timespec ts; |
| |
| if (0 == clock_gettime(CLOCK_MONOTONIC, &ts)) |
| PRINT_VERBOSE2 ("time now %lld %lld", |
| (unsigned long long) ts.tv_sec, |
| (unsigned long long) ts.tv_nsec); |
| #endif |
| #endif |
| } |
| |
| } |
| while(loop); |
| |
| SPDY_stop_daemon(daemon); |
| |
| curl_multi_cleanup(multi_handle); |
| |
| SPDY_deinit(); |
| |
| deinit_parse_uri(&uri_preg); |
| |
| return 0; |
| } |
| |
| |
| static void |
| display_usage() |
| { |
| printf( |
| "Usage: microspdy2http -p <PORT> [-c <CERTIFICATE>] [-k <CERT-KEY>]\n" |
| " [-rvh0DtT] [-b <HTTP-SERVER>] [-l <HOST>]\n\n" |
| "OPTIONS:\n" |
| " -p, --port Listening port.\n" |
| " -l, --host Listening host. If not set, will listen on [::]\n" |
| " -c, --certificate Path to a certificate file. Requiered if\n" |
| " --no-tls is not set.\n" |
| " -k, --certificate-key Path to a key file for the certificate.\n" |
| " Requiered if --no-tls is not set.\n" |
| " -b, --backend-server If set, the proxy will connect always to it.\n" |
| " Otherwise the proxy will connect to the URL\n" |
| " which is specified in the path or 'Host:'.\n" |
| " -v, --verbose Print debug information.\n" |
| " -r, --no-tls Do not use TLS. Client must use SPDY/3.\n" |
| " -h, --curl-verbose Print debug information for curl.\n" |
| " -0, --http10 Prefer HTTP/1.0 connections to the next hop.\n" |
| " -D, --no-delay This makes sense only if --no-tls is used.\n" |
| " TCP_NODELAY will be used for all sessions' sockets.\n" |
| " -4, --curl-ipv4 Curl may use IPv4 to connect to the final destination.\n" |
| " -6, --curl-ipv6 Curl may use IPv6 to connect to the final destination.\n" |
| " If neither --curl-ipv4 nor --curl-ipv6 is set,\n" |
| " both will be used by default.\n" |
| " -T, --timeout Maximum time in seconds for each HTTP transfer.\n" |
| " Use 0 for no timeout; this is the default value.\n" |
| " -t, --transparent If set, the proxy will fetch an URL which\n" |
| " is based on 'Host:' header and requested path.\n" |
| " Otherwise, full URL in the requested path is required.\n\n" |
| |
| ); |
| } |
| |
| |
| int |
| main (int argc, char *const *argv) |
| { |
| |
| int getopt_ret; |
| int option_index; |
| struct option long_options[] = { |
| {"port", required_argument, 0, 'p'}, |
| {"certificate", required_argument, 0, 'c'}, |
| {"certificate-key", required_argument, 0, 'k'}, |
| {"backend-server", required_argument, 0, 'b'}, |
| {"no-tls", no_argument, 0, 'r'}, |
| {"verbose", no_argument, 0, 'v'}, |
| {"curl-verbose", no_argument, 0, 'h'}, |
| {"http10", no_argument, 0, '0'}, |
| {"no-delay", no_argument, 0, 'D'}, |
| {"transparent", no_argument, 0, 't'}, |
| {"curl-ipv4", no_argument, 0, '4'}, |
| {"curl-ipv6", no_argument, 0, '6'}, |
| {"timeout", required_argument, 0, 'T'}, |
| {0, 0, 0, 0} |
| }; |
| |
| while (1) |
| { |
| getopt_ret = getopt_long( argc, argv, "p:l:c:k:b:rv0Dth46T:", long_options, &option_index); |
| if (getopt_ret == -1) |
| break; |
| |
| switch(getopt_ret) |
| { |
| case 'p': |
| glob_opt.listen_port = atoi(optarg); |
| break; |
| |
| case 'l': |
| glob_opt.listen_host= strdup(optarg); |
| if(NULL == glob_opt.listen_host) |
| return 1; |
| break; |
| |
| case 'c': |
| glob_opt.cert = strdup(optarg); |
| break; |
| |
| case 'k': |
| glob_opt.cert_key = strdup(optarg); |
| break; |
| |
| case 'b': |
| glob_opt.http_backend = strdup(optarg); |
| if(NULL == glob_opt.http_backend) |
| return 1; |
| break; |
| |
| case 'r': |
| glob_opt.notls = true; |
| break; |
| |
| case 'v': |
| glob_opt.verbose = true; |
| break; |
| |
| case 'h': |
| glob_opt.curl_verbose = true; |
| break; |
| |
| case '0': |
| glob_opt.http10 = true; |
| break; |
| |
| case 'D': |
| glob_opt.nodelay = true; |
| break; |
| |
| case 't': |
| glob_opt.transparent = true; |
| break; |
| |
| case '4': |
| glob_opt.ipv4 = true; |
| break; |
| |
| case '6': |
| glob_opt.ipv6 = true; |
| break; |
| |
| case 'T': |
| glob_opt.timeout = atoi(optarg); |
| 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.notls && (NULL == glob_opt.cert || NULL == glob_opt.cert_key)) |
| //|| !glob_opt.transparent && NULL != glob_opt.http_backend |
| ) |
| { |
| display_usage(); |
| return 1; |
| } |
| |
| return run(); |
| } |
| |