C++程序  |  1412行  |  34.52 KB

/*
    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();
}