48 #include <curl/curl.h>
53 #define ERROR_RESPONSE "502 Bad Gateway"
90 #define PRINT_INFO(msg) do{\
91 fprintf(stdout, "%i:%s\n", __LINE__, msg);\
97 #define PRINT_INFO2(fmt, ...) do{\
98 fprintf(stdout, "%i\n", __LINE__);\
99 fprintf(stdout, fmt,##__VA_ARGS__);\
100 fprintf(stdout, "\n");\
106 #define PRINT_VERBOSE(msg) do{\
107 if(glob_opt.verbose){\
108 fprintf(stdout, "%i:%s\n", __LINE__, msg);\
115 #define PRINT_VERBOSE2(fmt, ...) do{\
116 if(glob_opt.verbose){\
117 fprintf(stdout, "%i\n", __LINE__);\
118 fprintf(stdout, fmt,##__VA_ARGS__);\
119 fprintf(stdout, "\n");\
126 #define CURL_SETOPT(handle, opt, val) do{\
128 if(CURLE_OK != (ret = curl_easy_setopt(handle, opt, val))) \
130 PRINT_INFO2("curl_easy_setopt failed (%i = %i)", opt, ret); \
137 #define DIE(msg) do{\
138 printf("FATAL ERROR (line %i): %s\n", __LINE__, msg);\
165 struct curl_slist *curl_headers;
172 size_t http_body_size;
173 size_t received_body_size;
178 bool is_curl_read_paused;
179 bool is_with_body_data;
195 free(uri->host_and_port);
199 free(uri->path_and_more);
221 return regcomp(preg,
"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?", REG_EXTENDED);
233 parse_uri(regex_t * preg,
const char * full_uri,
struct URI ** uri)
240 regmatch_t pmatch[10];
242 if (0 != (ret = regexec(preg, full_uri, nmatch, pmatch, 0)))
245 *uri = malloc(
sizeof(
struct URI));
249 (*uri)->full_uri = strdup(full_uri);
251 asprintf(&((*uri)->scheme),
253 (
int) (pmatch[2].rm_eo - pmatch[2].rm_so),
254 &full_uri[pmatch[2].rm_so]);
255 asprintf(&((*uri)->host_and_port),
"%.*s",
256 (
int) (pmatch[4].rm_eo - pmatch[4].rm_so),
257 &full_uri[pmatch[4].rm_so]);
258 asprintf(&((*uri)->path),
260 (
int) (pmatch[5].rm_eo - pmatch[5].rm_so),
261 &full_uri[pmatch[5].rm_so]);
262 asprintf(&((*uri)->path_and_more),
264 (
int) (pmatch[9].rm_eo - pmatch[5].rm_so),
265 &full_uri[pmatch[5].rm_so]);
266 asprintf(&((*uri)->query),
268 (
int) (pmatch[7].rm_eo - pmatch[7].rm_so),
269 &full_uri[pmatch[7].rm_so]);
270 asprintf(&((*uri)->fragment),
272 (
int) (pmatch[9].rm_eo - pmatch[9].rm_so),
273 &full_uri[pmatch[9].rm_so]);
275 colon = strrchr((*uri)->host_and_port,
':');
278 (*uri)->host = strdup((*uri)->host_and_port);
298 port = atoi(colon + 1);
299 if(port<1 || port >= 256 * 256)
305 asprintf(&((*uri)->host),
"%.*s", (
int)(colon - (*uri)->host_and_port), (*uri)->host_and_port);
318 *dst = malloc(src_size);
320 *dst = realloc(*dst, src_size + *dst_size);
324 memcpy(*dst + *dst_size, src, src_size);
325 *dst_size += src_size;
337 if(max_size >= *src_size)
345 if(
NULL == (newbody = malloc(*src_size - max_size)))
347 memcpy(newbody, *src + ret, *src_size - ret);
349 memcpy(dst, *src, ret);
376 if(
NULL == (session_alive = malloc(
sizeof(
bool))))
380 *session_alive =
true;
397 assert(
NULL != session_alive);
399 *session_alive =
false;
414 if(!
store_in_buffer(buf, size, &proxy->received_body, &proxy->received_body_size))
416 PRINT_INFO(
"not enough memory (malloc/realloc returned NULL)");
420 proxy->receiving_done = !more;
426 if(proxy->is_curl_read_paused)
428 if(CURLE_OK != (ret = curl_easy_pause(proxy->curl_handle, CURLPAUSE_CONT)))
447 struct Proxy *proxy = (
struct Proxy *)cls;
451 assert(!proxy->spdy_error);
453 if(proxy->curl_error)
459 if(!proxy->http_body_size)
462 if(proxy->curl_done || proxy->curl_error) *more =
false;
466 ret =
get_from_buffer(&(proxy->http_body), &(proxy->http_body_size), buffer, max);
474 if((proxy->curl_done || proxy->curl_error) && 0 == proxy->http_body_size) *more =
false;
489 if(CURLM_OK != (ret = curl_multi_remove_handle(
multi_handle, proxy->curl_handle)))
491 PRINT_INFO2(
"curl_multi_remove_handle failed (%i)", ret);
492 DIE(
"bug in cleanup");
497 curl_slist_free_all(proxy->curl_headers);
498 curl_easy_cleanup(proxy->curl_handle);
513 struct Proxy *proxy = (
struct Proxy *)cls;
517 free(proxy->http_body);
518 proxy->http_body =
NULL;
519 proxy->spdy_error =
true;
530 size_t realsize = size * nmemb;
531 struct Proxy *proxy = (
struct Proxy *)userp;
532 char *line = (
char *)ptr;
540 const char *
const * values;
544 if(!*(proxy->session_alive))
547 proxy->spdy_error =
true;
548 proxy->curl_error =
true;
553 if(
NULL != proxy->response)
return 0;
555 if(
'\r' == line[0] ||
'\n' == line[0])
569 proxy->headers =
NULL;
570 free(proxy->status_msg);
571 proxy->status_msg =
NULL;
572 free(proxy->version);
573 proxy->version =
NULL;
584 proxy->spdy_error =
true;
585 proxy->curl_error =
true;
588 proxy->response =
NULL;
598 if(
NULL == proxy->version)
602 for(i=pos; i<realsize &&
' '!=line[i]; ++i);
604 DIE(
"error on parsing headers");
605 if(
NULL == (proxy->version = strndup(line, i - pos)))
610 for(i=pos; i<realsize &&
' '!=line[i] &&
'\r'!=line[i] &&
'\n'!=line[i]; ++i);
611 if(
NULL == (status = strndup(&(line[pos]), i - pos)))
613 proxy->status = atoi(status);
615 if(i<realsize &&
'\r'!=line[i] &&
'\n'!=line[i])
619 for(i=pos; i<realsize &&
'\r'!=line[i] &&
'\n'!=line[i]; ++i);
620 if(
NULL == (proxy->status_msg = strndup(&(line[pos]), i - pos)))
623 PRINT_VERBOSE2(
"Header line received '%s' '%i' '%s' ", proxy->version, proxy->status, proxy->status_msg);
629 for(i=pos; i<realsize &&
':'!=line[i] &&
'\r'!=line[i] &&
'\n'!=line[i]; ++i)
630 line[i] = tolower(line[i]);
631 if(
NULL == (name = strndup(line, i - pos)))
642 if(i == realsize ||
'\r'==line[i] ||
'\n'==line[i])
646 DIE(
"SPDY_name_value_add failed");
652 while(pos<realsize && isspace(line[pos])) ++pos;
653 for(i=pos; i<realsize &&
'\r'!=line[i] &&
'\n'!=line[i]; ++i);
654 if(
NULL == (value = strndup(&(line[pos]), i - pos)))
661 for(i=0; i<(
unsigned int)num_values; ++i)
662 if(0 == strcasecmp(value, values[i]))
665 PRINT_VERBOSE2(
"header appears more than once with same value '%s: %s'", name, value);
671 PRINT_INFO2(
"SPDY_name_value_add failed (%i) for '%s'", ret, name);
685 size_t realsize = size * nmemb;
686 struct Proxy *proxy = (
struct Proxy *)userp;
689 if(!*(proxy->session_alive))
692 proxy->spdy_error =
true;
693 proxy->curl_error =
true;
697 if(!
store_in_buffer(contents, realsize, &proxy->http_body, &proxy->http_body_size))
699 PRINT_INFO(
"not enough memory (malloc/realloc returned NULL)");
700 proxy->curl_error =
true;
730 size_t max = size * nmemb;
731 struct Proxy *proxy = (
struct Proxy *)userp;
735 if((proxy->receiving_done && !proxy->received_body_size) || !proxy->is_with_body_data || max < 1)
741 if(!*(proxy->session_alive))
743 PRINT_VERBOSE(
"POST is still being sent, but session is dead");
744 return CURL_READFUNC_ABORT;
747 if(!proxy->received_body_size)
750 proxy->is_curl_read_paused =
true;
751 return CURL_READFUNC_PAUSE;
754 ret =
get_from_buffer(&(proxy->received_body), &(proxy->received_body_size), ptr, max);
758 return CURL_READFUNC_ABORT;
790 iterate_cb (
void *cls,
const char *name,
const char *
const * value,
int num_values)
792 struct Proxy *proxy = (
struct Proxy *)cls;
793 struct curl_slist **curl_headers = (&(proxy->curl_headers));
795 int line_len = strlen(name) + 3;
798 for(i=0; i<num_values; ++i)
801 line_len += strlen(value[i]);
804 if(
NULL == (line = malloc(line_len)))
812 line[0] = toupper(line[0]);
814 for(i=0; i<num_values; ++i)
816 if(i) strcat(line,
", ");
817 PRINT_VERBOSE2(
"Adding request header: '%s' len %ld", value[i], strlen(value[i]));
818 strcat(line, value[i]);
821 if(
NULL == (*curl_headers = curl_slist_append(*curl_headers, line)))
822 DIE(
"curl_slist_append failed");
858 PRINT_VERBOSE2(
"received request for '%s %s %s'\n", method, path, version);
861 if(
NULL == (proxy = malloc(
sizeof(
struct Proxy))))
863 memset(proxy, 0,
sizeof(
struct Proxy));
868 assert(
NULL != session);
870 assert(
NULL != proxy->session_alive);
874 proxy->request = request;
875 proxy->is_with_body_data = more;
882 ret = asprintf(&(proxy->url),
"%s://%s%s", scheme,
glob_opt.http_backend, path);
884 ret = asprintf(&(proxy->url),
"%s://%s%s", scheme, host, path);
890 DIE(
"parsing built uri failed");
895 PRINT_INFO2(
"path %s '%s' '%s'", path, uri->scheme, uri->host);
896 if(ret != 0 || !strlen(uri->scheme) || !strlen(uri->host))
897 DIE(
"parsing received uri failed");
901 ret = asprintf(&(proxy->url),
"%s://%s%s", uri->scheme,
glob_opt.http_backend, uri->path_and_more);
906 if(
NULL == (proxy->url = strdup(path)))
916 if(
NULL == (proxy->curl_handle = curl_easy_init()))
923 CURL_SETOPT(proxy->curl_handle, CURLOPT_VERBOSE, 1);
927 if(
NULL == (proxy->curl_headers = curl_slist_append(proxy->curl_headers,
"Expect:")))
928 DIE(
"curl_slist_append failed");
931 CURL_SETOPT(proxy->curl_handle, CURLOPT_READDATA, proxy);
936 CURL_SETOPT(proxy->curl_handle, CURLOPT_URL, proxy->url);
938 CURL_SETOPT(proxy->curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
940 CURL_SETOPT(proxy->curl_handle, CURLOPT_WRITEDATA, proxy);
942 CURL_SETOPT(proxy->curl_handle, CURLOPT_HEADERDATA, proxy);
943 CURL_SETOPT(proxy->curl_handle, CURLOPT_PRIVATE, proxy);
944 CURL_SETOPT(proxy->curl_handle, CURLOPT_HTTPHEADER, proxy->curl_headers);
945 CURL_SETOPT(proxy->curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);
946 CURL_SETOPT(proxy->curl_handle, CURLOPT_SSL_VERIFYHOST, 0L);
948 CURL_SETOPT(proxy->curl_handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
950 CURL_SETOPT(proxy->curl_handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
952 if(CURLM_OK != (ret = curl_multi_add_handle(
multi_handle, proxy->curl_handle)))
954 PRINT_INFO2(
"curl_multi_add_handle failed (%i)", ret);
961 && CURLM_CALL_MULTI_PERFORM != ret)
963 PRINT_INFO2(
"curl_multi_perform failed (%i)", ret);
974 unsigned long long timeoutlong = 0;
975 unsigned long long timeout_spdy = 0;
976 long timeout_curl = -1;
977 struct timeval timeout;
990 struct sockaddr_in *addr;
991 struct addrinfo hints;
992 char service[NI_MAXSERV];
993 struct addrinfo *gai;
999 signal(SIGPIPE, SIG_IGN);
1006 DIE(
"Regexp compilation failed");
1033 snprintf (service,
sizeof(service),
"%u",
glob_opt.listen_port);
1034 memset (&hints, 0,
sizeof(
struct addrinfo));
1035 hints.ai_family = AF_INET;
1036 hints.ai_socktype = SOCK_STREAM;
1038 ret = getaddrinfo(
glob_opt.listen_host, service, &hints, &gai);
1040 DIE(
"problem with specified host");
1042 addr = (
struct sockaddr_in *) gai->ai_addr;
1066 printf(
"no daemon\n");
1072 DIE(
"no multi_handle");
1074 timeout.tv_usec = 0;
1085 if(
SPDY_NO == ret_spdy || timeout_spdy > 5000)
1088 timeoutlong = timeout_spdy;
1091 if(CURLM_OK != (ret_curl = curl_multi_timeout(
multi_handle, &timeout_curl)))
1096 else if(timeout_curl >= 0 && timeoutlong > (
unsigned long)timeout_curl)
1097 timeoutlong = (
unsigned long)timeout_curl;
1101 timeout.tv_sec = timeoutlong / 1000;
1102 timeout.tv_usec = (timeoutlong % 1000) * 1000;
1108 assert(-1 != maxfd);
1110 if(CURLM_OK != (ret = curl_multi_fdset(
multi_handle, &rs,
1118 if(maxfd_curl > maxfd)
1121 PRINT_VERBOSE2(
"timeout before %lld %lld", (
unsigned long long)timeout.tv_sec, (
unsigned long long)timeout.tv_usec);
1122 ret = select(maxfd+1, &rs, &ws, &es, &timeout);
1123 PRINT_VERBOSE2(
"timeout after %lld %lld; ret is %i", (
unsigned long long)timeout.tv_sec, (
unsigned long long)timeout.tv_usec, ret);
1134 if(ret > 0 || (
SPDY_YES == ret_spdy && 0 == timeout_spdy))
1145 && CURLM_CALL_MULTI_PERFORM != ret)
1147 PRINT_INFO2(
"curl_multi_perform failed (%i)", ret);
1155 while ((msg = curl_multi_info_read(
multi_handle, &msgs_left))) {
1156 if (msg->msg == CURLMSG_DONE) {
1158 if(CURLE_OK != (ret = curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &curl_private)))
1163 assert(
NULL != curl_private);
1164 proxy = (
struct Proxy *)curl_private;
1165 if(CURLE_OK == msg->data.result)
1167 proxy->curl_done =
true;
1175 PRINT_VERBOSE2(
"bad curl result (%i) for '%s'", msg->data.result, proxy->url);
1176 if(proxy->spdy_done || proxy->spdy_error || (
NULL == proxy->response && !*(proxy->session_alive)))
1184 else if(
NULL == proxy->response && *(proxy->session_alive))
1189 proxy->headers =
NULL;
1213 proxy->curl_error =
true;
1232 #ifdef HAVE_CLOCK_GETTIME
1233 #ifdef CLOCK_MONOTONIC
1236 if (0 == clock_gettime(CLOCK_MONOTONIC, &ts))
1238 (
unsigned long long) ts.tv_sec,
1239 (
unsigned long long) ts.tv_nsec);
1262 "Usage: microspdy2http -p <PORT> [-c <CERTIFICATE>] [-k <CERT-KEY>]\n"
1263 " [-rvh0DtT] [-b <HTTP-SERVER>] [-l <HOST>]\n\n"
1265 " -p, --port Listening port.\n"
1266 " -l, --host Listening host. If not set, will listen on [::]\n"
1267 " -c, --certificate Path to a certificate file. Requiered if\n"
1268 " --no-tls is not set.\n"
1269 " -k, --certificate-key Path to a key file for the certificate.\n"
1270 " Requiered if --no-tls is not set.\n"
1271 " -b, --backend-server If set, the proxy will connect always to it.\n"
1272 " Otherwise the proxy will connect to the URL\n"
1273 " which is specified in the path or 'Host:'.\n"
1274 " -v, --verbose Print debug information.\n"
1275 " -r, --no-tls Do not use TLS. Client must use SPDY/3.\n"
1276 " -h, --curl-verbose Print debug information for curl.\n"
1277 " -0, --http10 Prefer HTTP/1.0 connections to the next hop.\n"
1278 " -D, --no-delay This makes sense only if --no-tls is used.\n"
1279 " TCP_NODELAY will be used for all sessions' sockets.\n"
1280 " -4, --curl-ipv4 Curl may use IPv4 to connect to the final destination.\n"
1281 " -6, --curl-ipv6 Curl may use IPv6 to connect to the final destination.\n"
1282 " If neither --curl-ipv4 nor --curl-ipv6 is set,\n"
1283 " both will be used by default.\n"
1284 " -T, --timeout Maximum time in seconds for each HTTP transfer.\n"
1285 " Use 0 for no timeout; this is the default value.\n"
1286 " -t, --transparent If set, the proxy will fetch an URL which\n"
1287 " is based on 'Host:' header and requested path.\n"
1288 " Otherwise, full URL in the requested path is required.\n\n"
1295 main (
int argc,
char *
const *argv)
1300 struct option long_options[] = {
1301 {
"port", required_argument, 0,
'p'},
1302 {
"certificate", required_argument, 0,
'c'},
1303 {
"certificate-key", required_argument, 0,
'k'},
1304 {
"backend-server", required_argument, 0,
'b'},
1305 {
"no-tls", no_argument, 0,
'r'},
1306 {
"verbose", no_argument, 0,
'v'},
1307 {
"curl-verbose", no_argument, 0,
'h'},
1308 {
"http10", no_argument, 0,
'0'},
1309 {
"no-delay", no_argument, 0,
'D'},
1310 {
"transparent", no_argument, 0,
't'},
1311 {
"curl-ipv4", no_argument, 0,
'4'},
1312 {
"curl-ipv6", no_argument, 0,
'6'},
1313 {
"timeout", required_argument, 0,
'T'},
1319 getopt_ret = getopt_long( argc, argv,
"p:l:c:k:b:rv0Dth46T:", long_options, &option_index);
1320 if (getopt_ret == -1)
1326 glob_opt.listen_port = atoi(optarg);
1330 glob_opt.listen_host= strdup(optarg);
1340 glob_opt.cert_key = strdup(optarg);
1344 glob_opt.http_backend = strdup(optarg);
1394 DIE(
"default from getopt");
#define CURL_SETOPT(handle, opt, val)
int SPDY_name_value_iterate(struct SPDY_NameValue *container, SPDY_NameValueIterator iterator, void *iterator_cls)
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)
int SPDY_get_fdset(struct SPDY_Daemon *daemon, fd_set *read_fd_set, fd_set *write_fd_set, fd_set *except_fd_set)
void SPDY_destroy_response(struct SPDY_Response *response)
#define SPDY_HTTP_METHOD_POST
#define PRINT_INFO2(fmt,...)
static void free_uri(struct URI *uri)
static void deinit_parse_uri(regex_t *preg)
public interface to libmicrospdy
#define SPDY_HTTP_VERSION_1_1
static bool call_spdy_run
#define SPDY_HTTP_HEADER_TRANSFER_ENCODING
static size_t curl_write_cb(void *contents, size_t size, size_t nmemb, void *userp)
struct SPDY_NameValue * SPDY_name_value_create()
struct SPDY_Daemon * SPDY_start_daemon(uint16_t port, const char *certfile, const char *keyfile, SPDY_NewSessionCallback nscb, SPDY_SessionClosedCallback sccb, SPDY_NewRequestCallback nrcb, SPDY_NewDataCallback npdcb, void *cls,...)
#define PRINT_VERBOSE(msg)
ssize_t response_callback(void *cls, void *buffer, size_t max, bool *more)
static int spdy_post_data_cb(void *cls, struct SPDY_Request *request, const void *buf, size_t size, bool more)
static void new_session_cb(void *cls, struct SPDY_Session *session)
void * SPDY_get_cls_from_request(struct SPDY_Request *request)
static CURLM * multi_handle
int SPDY_name_value_add(struct SPDY_NameValue *container, const char *name, const char *value)
static int init_parse_uri(regex_t *preg)
static size_t curl_header_cb(void *ptr, size_t size, size_t nmemb, void *userp)
#define SPDY_HTTP_BAD_GATEWAY
struct SPDY_Response * SPDY_build_response_with_callback(int status, const char *statustext, const char *version, struct SPDY_NameValue *headers, SPDY_ResponseCallback rcb, void *rcb_cls, uint32_t block_size)
static void cleanup(struct Proxy *proxy)
void * SPDY_get_cls_from_session(struct SPDY_Session *session)
static void session_closed_cb(void *cls, struct SPDY_Session *session, int by_client)
int SPDY_get_timeout(struct SPDY_Daemon *daemon, unsigned long long *timeout)
static bool store_in_buffer(const void *src, size_t src_size, void **dst, size_t *dst_size)
void SPDY_set_cls_to_request(struct SPDY_Request *request, void *cls)
int SPDY_queue_response(struct SPDY_Request *request, struct SPDY_Response *response, bool closestream, bool consider_priority, SPDY_ResponseResultCallback rrcb, void *rrcb_cls)
static bool call_curl_run
static ssize_t get_from_buffer(void **src, size_t *src_size, void *dst, size_t max_size)
void SPDY_name_value_destroy(struct SPDY_NameValue *container)
#define PRINT_VERBOSE2(fmt,...)
const char *const * SPDY_name_value_lookup(struct SPDY_NameValue *container, const char *name, int *num_values)
struct SPDY_Session * SPDY_get_session_for_request(const struct SPDY_Request *request)
void SPDY_destroy_request(struct SPDY_Request *request)
struct global_options glob_opt
void SPDY_set_cls_to_session(struct SPDY_Session *session, void *cls)
#define SPDY_HTTP_HEADER_CONNECTION
static int iterate_cb(void *cls, const char *name, const char *const *value, int num_values)
static void response_done_callback(void *cls, struct SPDY_Response *response, struct SPDY_Request *request, enum SPDY_RESPONSE_RESULT status, bool streamopened)
struct SPDY_Response * SPDY_build_response(int status, const char *statustext, const char *version, struct SPDY_NameValue *headers, const void *data, size_t size)
void SPDY_stop_daemon(struct SPDY_Daemon *daemon)
#define SPDY_HTTP_HEADER_KEEP_ALIVE
void SPDY_run(struct SPDY_Daemon *daemon)
static void catch_signal(int signal)
static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *userp)
static int parse_uri(regex_t *preg, const char *full_uri, struct URI **uri)