libmicrohttpd

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

commit c1a7d6e2b5aa34bb8bf860471083e8757d0efcbd
parent 489d8e1bad4b5ac84c6744d47a4e0f71890f2aa1
Author: Andrey Uzunov <andrey.uzunov@gmail.com>
Date:   Fri,  5 Jul 2013 16:48:33 +0000

spdy: daemon can now be started without TLS support ("raw SPDY") + test case

Diffstat:
Msrc/include/microspdy.h | 7+++++++
Msrc/microspdy/applicationlayer.c | 3+++
Msrc/microspdy/daemon.c | 49+++++++++++++++++++++++++++++++------------------
Msrc/microspdy/io_raw.c | 13+++++++++++++
Msrc/microspdy/session.c | 28+++++++++-------------------
Msrc/microspdy/structures.h | 5+++++
Msrc/testspdy/Makefile.am | 7+++++++
Asrc/testspdy/test_notls.c | 940+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 1015 insertions(+), 37 deletions(-)

diff --git a/src/include/microspdy.h b/src/include/microspdy.h @@ -360,6 +360,13 @@ enum SPDY_DAEMON_OPTION * which is the result of bitwise OR of desired flags. */ SPDY_DAEMON_OPTION_FLAGS = 4, + + /** + * IO subsystem type used by daemon and all its sessions. If not set, + * TLS provided by openssl is used. Must be followed by a + * SPDY_IO_SUBSYSTEM value. + */ + SPDY_DAEMON_OPTION_IO_SUBSYSTEM = 8, }; diff --git a/src/microspdy/applicationlayer.c b/src/microspdy/applicationlayer.c @@ -344,6 +344,8 @@ SPDY_start_daemon (uint16_t port, SPDYF_DEBUG("library not initialized"); return NULL; } + /* + * for now make this checks in framing layer if(NULL == certfile) { SPDYF_DEBUG("certfile is NULL"); @@ -354,6 +356,7 @@ SPDY_start_daemon (uint16_t port, SPDYF_DEBUG("keyfile is NULL"); return NULL; } + */ va_start(valist, cls); daemon = SPDYF_start_daemon_va ( port, diff --git a/src/microspdy/daemon.c b/src/microspdy/daemon.c @@ -142,6 +142,9 @@ spdyf_parse_options_va (struct SPDY_Daemon *daemon, case SPDY_DAEMON_OPTION_FLAGS: daemon->flags = va_arg (valist, enum SPDY_DAEMON_FLAG); break; + case SPDY_DAEMON_OPTION_IO_SUBSYSTEM: + daemon->io_subsystem = va_arg (valist, enum SPDY_IO_SUBSYSTEM); + break; default: SPDYF_DEBUG("Wrong option for the daemon %i",opt); return SPDY_NO; @@ -191,18 +194,40 @@ SPDYF_start_daemon_va (uint16_t port, memset (daemon, 0, sizeof (struct SPDY_Daemon)); daemon->socket_fd = -1; daemon->port = port; - SPDYF_io_set_daemon(daemon, SPDY_IO_SUBSYSTEM_OPENSSL); - - if (NULL == (daemon->certfile = strdup (certfile))) + + if(SPDY_YES != spdyf_parse_options_va (daemon, valist)) { - SPDYF_DEBUG("str"); + SPDYF_DEBUG("parse"); goto free_and_fail; } - if (NULL == (daemon->keyfile = strdup (keyfile))) + + if(!port && NULL == daemon->address) { - SPDYF_DEBUG("str"); + SPDYF_DEBUG("Port is 0"); goto free_and_fail; } + if(0 == daemon->io_subsystem) + daemon->io_subsystem = SPDY_IO_SUBSYSTEM_OPENSSL; + + if(SPDY_YES != SPDYF_io_set_daemon(daemon, daemon->io_subsystem)) + goto free_and_fail; + + if(SPDY_IO_SUBSYSTEM_RAW != daemon->io_subsystem) + { + if (NULL == certfile + || NULL == (daemon->certfile = strdup (certfile))) + { + SPDYF_DEBUG("strdup (certfile)"); + goto free_and_fail; + } + if (NULL == keyfile + || NULL == (daemon->keyfile = strdup (keyfile))) + { + SPDYF_DEBUG("strdup (keyfile)"); + goto free_and_fail; + } + } + daemon->new_session_cb = nscb; daemon->session_closed_cb = sccb; daemon->new_request_cb = nrcb; @@ -211,18 +236,6 @@ SPDYF_start_daemon_va (uint16_t port, daemon->fcls = fcls; daemon->fnew_stream_cb = fnscb; - if(SPDY_YES != spdyf_parse_options_va (daemon, valist)) - { - SPDYF_DEBUG("parse"); - goto free_and_fail; - } - - if(!port && NULL == daemon->address) - { - SPDYF_DEBUG("Port is 0"); - goto free_and_fail; - } - #if HAVE_INET6 //handling IPv6 if((daemon->flags & SPDY_DAEMON_FLAG_ONLY_IPV6) diff --git a/src/microspdy/io_raw.c b/src/microspdy/io_raw.c @@ -61,6 +61,19 @@ SPDYF_raw_new_session(struct SPDY_Session *session) { (void)session; + //TODO + //setting the socket to be non-blocking + /* + * different handling is needed by libssl if non-blocking is used + * + fd_flags = fcntl (new_socket_fd, F_GETFL); + if ( -1 == fd_flags + || 0 != fcntl (new_socket_fd, F_SETFL, fd_flags | O_NONBLOCK)) + { + SPDYF_DEBUG("WARNING: Couldn't set the new connection to be non-blocking"); + } + */ + return SPDY_YES; } diff --git a/src/microspdy/session.c b/src/microspdy/session.c @@ -1264,10 +1264,11 @@ int SPDYF_session_accept(struct SPDY_Daemon *daemon) { int new_socket_fd; - //int fd_flags; + int ret; struct SPDY_Session *session = NULL; socklen_t addr_len; struct sockaddr *addr; + #if HAVE_INET6 struct sockaddr_in6 addr6; @@ -1280,25 +1281,13 @@ SPDYF_session_accept(struct SPDY_Daemon *daemon) addr_len = sizeof(addr6); #endif - new_socket_fd = accept (daemon->socket_fd, addr, &addr_len); - - if(new_socket_fd < 1) - return SPDY_NO; - - //setting the socket to be non-blocking - /* - * different handling is needed by libssl if non-blocking is used - * - fd_flags = fcntl (new_socket_fd, F_GETFL); - if ( -1 == fd_flags - || 0 != fcntl (new_socket_fd, F_SETFL, fd_flags | O_NONBLOCK)) - { - SPDYF_DEBUG("WARNING: Couldn't set the new connection to be non-blocking"); - } - */ + new_socket_fd = accept (daemon->socket_fd, addr, &addr_len); + + if(new_socket_fd < 1) + return SPDY_NO; if (NULL == (session = malloc (sizeof (struct SPDY_Session)))) - { + { goto free_and_fail; } memset (session, 0, sizeof (struct SPDY_Session)); @@ -1306,7 +1295,8 @@ SPDYF_session_accept(struct SPDY_Daemon *daemon) session->daemon = daemon; session->socket_fd = new_socket_fd; - SPDYF_io_set_session(session, SPDY_IO_SUBSYSTEM_OPENSSL); + ret = SPDYF_io_set_session(session, daemon->io_subsystem); + SPDYF_ASSERT(SPDY_YES == ret, "Somehow daemon->io_subsystem iswrong here"); //init TLS context, handshake will be done if(SPDY_YES != session->fio_new_session(session)) diff --git a/src/microspdy/structures.h b/src/microspdy/structures.h @@ -920,6 +920,11 @@ struct SPDY_Daemon enum SPDY_DAEMON_FLAG flags; /** + * IO subsystem type used by daemon and all its sessions. + */ + enum SPDY_IO_SUBSYSTEM io_subsystem; + + /** * Listen port. */ uint16_t port; diff --git a/src/testspdy/Makefile.am b/src/testspdy/Makefile.am @@ -27,6 +27,7 @@ check_PROGRAMS = \ test_struct_namevalue \ test_new_connection \ test_request_response \ + test_notls \ test_request_response_with_callback \ test_requests_with_assets \ test_misc \ @@ -71,6 +72,12 @@ test_request_response_SOURCES = \ test_request_response_LDADD = $(SPDY_LDADD) \ -lspdylay +test_notls_SOURCES = \ + test_notls.c \ + $(SPDY_SOURCES) +test_notls_LDADD = $(SPDY_LDADD) \ + -lspdylay + test_request_response_with_callback_SOURCES = \ test_request_response_with_callback.c \ $(SPDY_SOURCES) diff --git a/src/testspdy/test_notls.c b/src/testspdy/test_notls.c @@ -0,0 +1,940 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 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 request_response.c + * @brief tests receiving request and sending response. spdycli.c (spdylay) + * code is reused here + * @author Andrey Uzunov + * @author Tatsuhiro Tsujikawa + */ + +#include "platform.h" +#include "microspdy.h" +#include <sys/wait.h> +#include "common.h" + +#define RESPONSE_BODY "<html><body><b>Hi, this is libmicrospdy!</b></body></html>" + +#define CLS "anything" + +pid_t parent; +pid_t child; +char *rcvbuf; +int rcvbuf_c = 0; + +int session_closed_called = 0; + +void +killchild(int pid, char *message) +{ + printf("%s\n",message); + kill(pid, SIGKILL); + exit(1); +} + +void +killparent(int pid, char *message) +{ + printf("%s\n",message); + kill(pid, SIGKILL); + _exit(1); +} + + +/***** + * start of code needed to utilize spdylay + */ + +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <poll.h> +#include <signal.h> +#include <stdio.h> +#include <assert.h> + +#include <spdylay/spdylay.h> + +enum { + IO_NONE, + WANT_READ, + WANT_WRITE +}; + +struct Connection { + spdylay_session *session; + /* WANT_READ if SSL connection needs more input; or WANT_WRITE if it + needs more output; or IO_NONE. This is necessary because SSL/TLS + re-negotiation is possible at any time. Spdylay API offers + similar functions like spdylay_session_want_read() and + spdylay_session_want_write() but they do not take into account + SSL connection. */ + int want_io; + int fd; +}; + +struct Request { + char *host; + uint16_t port; + /* In this program, path contains query component as well. */ + char *path; + /* This is the concatenation of host and port with ":" in + between. */ + char *hostport; + /* Stream ID for this request. */ + int32_t stream_id; + /* The gzip stream inflater for the compressed response. */ + spdylay_gzip *inflater; +}; + +struct URI { + const char *host; + size_t hostlen; + uint16_t port; + /* In this program, path contains query component as well. */ + const char *path; + size_t pathlen; + const char *hostport; + size_t hostportlen; +}; + +/* + * Returns copy of string |s| with the length |len|. The returned + * string is NULL-terminated. + */ +static char* strcopy(const char *s, size_t len) +{ + char *dst; + dst = malloc(len+1); + memcpy(dst, s, len); + dst[len] = '\0'; + return dst; +} + +/* + * Prints error message |msg| and exit. + */ +static void die(const char *msg) +{ + fprintf(stderr, "FATAL: %s\n", msg); + exit(EXIT_FAILURE); +} + +/* + * Prints error containing the function name |func| and message |msg| + * and exit. + */ +static void dief(const char *func, const char *msg) +{ + fprintf(stderr, "FATAL: %s: %s\n", func, msg); + exit(EXIT_FAILURE); +} + +/* + * Prints error containing the function name |func| and error code + * |error_code| and exit. + */ +static void diec(const char *func, int error_code) +{ + fprintf(stderr, "FATAL: %s: error_code=%d, msg=%s\n", func, error_code, + spdylay_strerror(error_code)); + exit(EXIT_FAILURE); +} + +/* + * Check response is content-encoding: gzip. We need this because SPDY + * client is required to support gzip. + */ +static void check_gzip(struct Request *req, char **nv) +{ + int gzip = 0; + size_t i; + for(i = 0; nv[i]; i += 2) { + if(strcmp("content-encoding", nv[i]) == 0) { + gzip = strcmp("gzip", nv[i+1]) == 0; + break; + } + } + if(gzip) { + int rv; + if(req->inflater) { + return; + } + rv = spdylay_gzip_inflate_new(&req->inflater); + if(rv != 0) { + die("Can't allocate inflate stream."); + } + } +} + +/* + * The implementation of spdylay_send_callback type. Here we write + * |data| with size |length| to the network and return the number of + * bytes actually written. See the documentation of + * spdylay_send_callback for the details. + */ +static ssize_t send_callback(spdylay_session *session, + const uint8_t *data, size_t length, int flags, + void *user_data) +{ + struct Connection *connection; + ssize_t rv; + connection = (struct Connection*)user_data; + connection->want_io = IO_NONE; + + rv = write(connection->fd, + data, + length); + + if (rv < 0) + { + switch(errno) + { + case EAGAIN: + #if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: + #endif + connection->want_io = WANT_WRITE; + rv = SPDYLAY_ERR_WOULDBLOCK; + break; + + default: + rv = SPDYLAY_ERR_CALLBACK_FAILURE; + } + } + return rv; +} + +/* + * The implementation of spdylay_recv_callback type. Here we read data + * from the network and write them in |buf|. The capacity of |buf| is + * |length| bytes. Returns the number of bytes stored in |buf|. See + * the documentation of spdylay_recv_callback for the details. + */ +static ssize_t recv_callback(spdylay_session *session, + uint8_t *buf, size_t length, int flags, + void *user_data) +{ + struct Connection *connection; + ssize_t rv; + connection = (struct Connection*)user_data; + connection->want_io = IO_NONE; + + rv = read(connection->fd, + buf, + length); + + if (rv < 0) + { + switch(errno) + { + case EAGAIN: + #if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: + #endif + connection->want_io = WANT_READ; + rv = SPDYLAY_ERR_WOULDBLOCK; + break; + + default: + rv = SPDYLAY_ERR_CALLBACK_FAILURE; + } + } + else if(rv == 0) + rv = SPDYLAY_ERR_EOF; + return rv; +} + +/* + * The implementation of spdylay_before_ctrl_send_callback type. We + * use this function to get stream ID of the request. This is because + * stream ID is not known when we submit the request + * (spdylay_submit_request). + */ +static void before_ctrl_send_callback(spdylay_session *session, + spdylay_frame_type type, + spdylay_frame *frame, + void *user_data) +{ + if(type == SPDYLAY_SYN_STREAM) { + struct Request *req; + int stream_id = frame->syn_stream.stream_id; + req = spdylay_session_get_stream_user_data(session, stream_id); + if(req && req->stream_id == -1) { + req->stream_id = stream_id; + printf("[INFO] Stream ID = %d\n", stream_id); + } + } +} + +static void on_ctrl_send_callback(spdylay_session *session, + spdylay_frame_type type, + spdylay_frame *frame, void *user_data) +{ + char **nv; + const char *name = NULL; + int32_t stream_id; + size_t i; + switch(type) { + case SPDYLAY_SYN_STREAM: + nv = frame->syn_stream.nv; + name = "SYN_STREAM"; + stream_id = frame->syn_stream.stream_id; + break; + default: + break; + } + if(name && spdylay_session_get_stream_user_data(session, stream_id)) { + printf("[INFO] C ----------------------------> S (%s)\n", name); + for(i = 0; nv[i]; i += 2) { + printf(" %s: %s\n", nv[i], nv[i+1]); + } + } +} + +static void on_ctrl_recv_callback(spdylay_session *session, + spdylay_frame_type type, + spdylay_frame *frame, void *user_data) +{ + struct Request *req; + char **nv; + const char *name = NULL; + int32_t stream_id; + size_t i; + switch(type) { + case SPDYLAY_SYN_REPLY: + nv = frame->syn_reply.nv; + name = "SYN_REPLY"; + stream_id = frame->syn_reply.stream_id; + break; + case SPDYLAY_HEADERS: + nv = frame->headers.nv; + name = "HEADERS"; + stream_id = frame->headers.stream_id; + break; + default: + break; + } + if(!name) { + return; + } + req = spdylay_session_get_stream_user_data(session, stream_id); + if(req) { + check_gzip(req, nv); + printf("[INFO] C <---------------------------- S (%s)\n", name); + for(i = 0; nv[i]; i += 2) { + printf(" %s: %s\n", nv[i], nv[i+1]); + } + } +} + +/* + * The implementation of spdylay_on_stream_close_callback type. We use + * this function to know the response is fully received. Since we just + * fetch 1 resource in this program, after reception of the response, + * we submit GOAWAY and close the session. + */ +static void on_stream_close_callback(spdylay_session *session, + int32_t stream_id, + spdylay_status_code status_code, + void *user_data) +{ + struct Request *req; + req = spdylay_session_get_stream_user_data(session, stream_id); + if(req) { + int rv; + rv = spdylay_submit_goaway(session, SPDYLAY_GOAWAY_OK); + if(rv != 0) { + diec("spdylay_submit_goaway", rv); + } + } +} + +#define MAX_OUTLEN 4096 + +/* + * The implementation of spdylay_on_data_chunk_recv_callback type. We + * use this function to print the received response body. + */ +static void on_data_chunk_recv_callback(spdylay_session *session, uint8_t flags, + int32_t stream_id, + const uint8_t *data, size_t len, + void *user_data) +{ + struct Request *req; + req = spdylay_session_get_stream_user_data(session, stream_id); + if(req) { + printf("[INFO] C <---------------------------- S (DATA)\n"); + printf(" %lu bytes\n", (unsigned long int)len); + if(req->inflater) { + while(len > 0) { + uint8_t out[MAX_OUTLEN]; + size_t outlen = MAX_OUTLEN; + size_t tlen = len; + int rv; + rv = spdylay_gzip_inflate(req->inflater, out, &outlen, data, &tlen); + if(rv == -1) { + spdylay_submit_rst_stream(session, stream_id, SPDYLAY_INTERNAL_ERROR); + break; + } + fwrite(out, 1, outlen, stdout); + data += tlen; + len -= tlen; + } + } else { + /* TODO add support gzip */ + fwrite(data, 1, len, stdout); + + //check if the data is correct + //if(strcmp(RESPONSE_BODY, data) != 0) + //killparent(parent, "\nreceived data is not the same"); + if(len + rcvbuf_c > strlen(RESPONSE_BODY)) + killparent(parent, "\nreceived data is not the same"); + + strcpy(rcvbuf + rcvbuf_c,(char*)data); + rcvbuf_c+=len; + } + printf("\n"); + } +} + +/* + * Setup callback functions. Spdylay API offers many callback + * functions, but most of them are optional. The send_callback is + * always required. Since we use spdylay_session_recv(), the + * recv_callback is also required. + */ +static void setup_spdylay_callbacks(spdylay_session_callbacks *callbacks) +{ + memset(callbacks, 0, sizeof(spdylay_session_callbacks)); + callbacks->send_callback = send_callback; + callbacks->recv_callback = recv_callback; + callbacks->before_ctrl_send_callback = before_ctrl_send_callback; + callbacks->on_ctrl_send_callback = on_ctrl_send_callback; + callbacks->on_ctrl_recv_callback = on_ctrl_recv_callback; + callbacks->on_stream_close_callback = on_stream_close_callback; + callbacks->on_data_chunk_recv_callback = on_data_chunk_recv_callback; +} + + +/* + * Connects to the host |host| and port |port|. This function returns + * the file descriptor of the client socket. + */ +static int connect_to(const char *host, uint16_t port) +{ + struct addrinfo hints; + int fd = -1; + int rv; + char service[NI_MAXSERV]; + struct addrinfo *res, *rp; + snprintf(service, sizeof(service), "%u", port); + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + rv = getaddrinfo(host, service, &hints, &res); + if(rv != 0) { + dief("getaddrinfo", gai_strerror(rv)); + } + for(rp = res; rp; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if(fd == -1) { + continue; + } + while((rv = connect(fd, rp->ai_addr, rp->ai_addrlen)) == -1 && + errno == EINTR); + if(rv == 0) { + break; + } + close(fd); + fd = -1; + } + freeaddrinfo(res); + return fd; +} + +static void make_non_block(int fd) +{ + int flags, rv; + while((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR); + if(flags == -1) { + dief("fcntl", strerror(errno)); + } + while((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR); + if(rv == -1) { + dief("fcntl", strerror(errno)); + } +} + +/* + * Setting TCP_NODELAY is not mandatory for the SPDY protocol. + */ +static void set_tcp_nodelay(int fd) +{ + int val = 1; + int rv; + rv = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val)); + if(rv == -1) { + dief("setsockopt", strerror(errno)); + } +} + +/* + * Update |pollfd| based on the state of |connection|. + */ +static void ctl_poll(struct pollfd *pollfd, struct Connection *connection) +{ + pollfd->events = 0; + if(spdylay_session_want_read(connection->session) || + connection->want_io == WANT_READ) { + pollfd->events |= POLLIN; + } + if(spdylay_session_want_write(connection->session) || + connection->want_io == WANT_WRITE) { + pollfd->events |= POLLOUT; + } +} + +/* + * Submits the request |req| to the connection |connection|. This + * function does not send packets; just append the request to the + * internal queue in |connection->session|. + */ +static void submit_request(struct Connection *connection, struct Request *req) +{ + int pri = 0; + int rv; + const char *nv[15]; + /* We always use SPDY/3 style header even if the negotiated protocol + version is SPDY/2. The library translates the header name as + necessary. Make sure that the last item is NULL! */ + nv[0] = ":method"; nv[1] = "GET"; + nv[2] = ":path"; nv[3] = req->path; + nv[4] = ":version"; nv[5] = "HTTP/1.1"; + nv[6] = ":scheme"; nv[7] = "https"; + nv[8] = ":host"; nv[9] = req->hostport; + nv[10] = "accept"; nv[11] = "*/*"; + nv[12] = "user-agent"; nv[13] = "spdylay/"SPDYLAY_VERSION; + nv[14] = NULL; + rv = spdylay_submit_request(connection->session, pri, nv, NULL, req); + if(rv != 0) { + diec("spdylay_submit_request", rv); + } +} + +/* + * Performs the network I/O. + */ +static void exec_io(struct Connection *connection) +{ + int rv; + rv = spdylay_session_recv(connection->session); + if(rv != 0) { + diec("spdylay_session_recv", rv); + } + rv = spdylay_session_send(connection->session); + if(rv != 0) { + diec("spdylay_session_send", rv); + } +} + +static void request_init(struct Request *req, const struct URI *uri) +{ + req->host = strcopy(uri->host, uri->hostlen); + req->port = uri->port; + req->path = strcopy(uri->path, uri->pathlen); + req->hostport = strcopy(uri->hostport, uri->hostportlen); + req->stream_id = -1; + req->inflater = NULL; +} + +static void request_free(struct Request *req) +{ + free(req->host); + free(req->path); + free(req->hostport); + spdylay_gzip_inflate_del(req->inflater); +} + +/* + * Fetches the resource denoted by |uri|. + */ +static void fetch_uri(const struct URI *uri) +{ + spdylay_session_callbacks callbacks; + int fd; + struct Request req; + struct Connection connection; + int rv; + nfds_t npollfds = 1; + struct pollfd pollfds[1]; + uint16_t spdy_proto_version = 3; + + request_init(&req, uri); + + setup_spdylay_callbacks(&callbacks); + + /* Establish connection and setup SSL */ + fd = connect_to(req.host, req.port); + + connection.fd = fd; + connection.want_io = IO_NONE; + + /* Here make file descriptor non-block */ + make_non_block(fd); + set_tcp_nodelay(fd); + + printf("[INFO] SPDY protocol version = %d\n", spdy_proto_version); + rv = spdylay_session_client_new(&connection.session, spdy_proto_version, + &callbacks, &connection); + if(rv != 0) { + diec("spdylay_session_client_new", rv); + } + + /* Submit the HTTP request to the outbound queue. */ + submit_request(&connection, &req); + + pollfds[0].fd = fd; + ctl_poll(pollfds, &connection); + + /* Event loop */ + while(spdylay_session_want_read(connection.session) || + spdylay_session_want_write(connection.session)) { + int nfds = poll(pollfds, npollfds, -1); + if(nfds == -1) { + dief("poll", strerror(errno)); + } + if(pollfds[0].revents & (POLLIN | POLLOUT)) { + exec_io(&connection); + } + if((pollfds[0].revents & POLLHUP) || (pollfds[0].revents & POLLERR)) { + die("Connection error"); + } + ctl_poll(pollfds, &connection); + } + + /* Resource cleanup */ + spdylay_session_del(connection.session); + shutdown(fd, SHUT_WR); + close(fd); + request_free(&req); +} + +static int parse_uri(struct URI *res, const char *uri) +{ + /* We only interested in https */ + size_t len, i, offset; + memset(res, 0, sizeof(struct URI)); + len = strlen(uri); + if(len < 9 || memcmp("https://", uri, 8) != 0) { + return -1; + } + offset = 8; + res->host = res->hostport = &uri[offset]; + res->hostlen = 0; + if(uri[offset] == '[') { + /* IPv6 literal address */ + ++offset; + ++res->host; + for(i = offset; i < len; ++i) { + if(uri[i] == ']') { + res->hostlen = i-offset; + offset = i+1; + break; + } + } + } else { + const char delims[] = ":/?#"; + for(i = offset; i < len; ++i) { + if(strchr(delims, uri[i]) != NULL) { + break; + } + } + res->hostlen = i-offset; + offset = i; + } + if(res->hostlen == 0) { + return -1; + } + /* Assuming https */ + res->port = 443; + if(offset < len) { + if(uri[offset] == ':') { + /* port */ + const char delims[] = "/?#"; + int port = 0; + ++offset; + for(i = offset; i < len; ++i) { + if(strchr(delims, uri[i]) != NULL) { + break; + } + if('0' <= uri[i] && uri[i] <= '9') { + port *= 10; + port += uri[i]-'0'; + if(port > 65535) { + return -1; + } + } else { + return -1; + } + } + if(port == 0) { + return -1; + } + offset = i; + res->port = port; + } + } + res->hostportlen = uri+offset-res->host; + for(i = offset; i < len; ++i) { + if(uri[i] == '#') { + break; + } + } + if(i-offset == 0) { + res->path = "/"; + res->pathlen = 1; + } else { + res->path = &uri[offset]; + res->pathlen = i-offset; + } + return 0; +} + + +/***** + * end of code needed to utilize spdylay + */ + + +/***** + * start of code needed to utilize microspdy + */ + + +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) +{ + struct SPDY_Response *response=NULL; + + if(strcmp(CLS,cls)!=0) + { + killchild(child,"wrong cls"); + } + + response = SPDY_build_response(200,NULL,SPDY_HTTP_VERSION_1_1,NULL,RESPONSE_BODY,strlen(RESPONSE_BODY)); + + if(NULL==response){ + fprintf(stdout,"no response obj\n"); + exit(3); + } + + if(SPDY_queue_response(request,response,true,false,NULL,(void*)strdup(path))!=SPDY_YES) + { + fprintf(stdout,"queue\n"); + exit(4); + } +} + +void +session_closed_handler (void *cls, + struct SPDY_Session * session, + int by_client) +{ + printf("session_closed_handler called\n"); + + if(strcmp(CLS,cls)!=0) + { + killchild(child,"wrong cls"); + } + + if(SPDY_YES != by_client) + { + //killchild(child,"wrong by_client"); + printf("session closed by server\n"); + } + else + { + printf("session closed by client\n"); + } + + if(NULL == session) + { + killchild(child,"session is NULL"); + } + + session_closed_called = 1; +} + + +/***** + * end of code needed to utilize microspdy + */ + +//child process +void +childproc(int port) +{ + struct URI uri; + struct sigaction act; + int rv; + char *uristr; + + memset(&act, 0, sizeof(struct sigaction)); + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, 0); + + asprintf(&uristr, "https://127.0.0.1:%i/",port); + if(NULL == (rcvbuf = malloc(strlen(RESPONSE_BODY)+1))) + killparent(parent,"no memory"); + + SSL_load_error_strings(); + SSL_library_init(); + + rv = parse_uri(&uri, uristr); + if(rv != 0) { + killparent(parent,"parse_uri failed"); + } + fetch_uri(&uri); + + if(strcmp(rcvbuf, RESPONSE_BODY)) + killparent(parent,"received data is different"); +} + +//parent proc +int +parentproc( int port) +{ + int childstatus; + unsigned long long timeoutlong=0; + struct timeval timeout; + int ret; + fd_set read_fd_set; + fd_set write_fd_set; + fd_set except_fd_set; + int maxfd = -1; + struct SPDY_Daemon *daemon; + + SPDY_init(); + + daemon = SPDY_start_daemon(port, + NULL, + NULL, + NULL,&session_closed_handler,&standard_request_handler,NULL,CLS, + SPDY_DAEMON_OPTION_IO_SUBSYSTEM, SPDY_IO_SUBSYSTEM_RAW, + SPDY_DAEMON_OPTION_END); + + if(NULL==daemon){ + printf("no daemon\n"); + return 1; + } + + timeout.tv_usec = 0; + + do + { + FD_ZERO(&read_fd_set); + FD_ZERO(&write_fd_set); + FD_ZERO(&except_fd_set); + + ret = SPDY_get_timeout(daemon, &timeoutlong); + if(SPDY_NO == ret || timeoutlong > 1) + { + timeout.tv_sec = 1; + } + else + { + timeout.tv_sec = timeoutlong; + } + + maxfd = SPDY_get_fdset (daemon, + &read_fd_set, + &write_fd_set, + &except_fd_set); + + ret = select(maxfd+1, &read_fd_set, &write_fd_set, &except_fd_set, &timeout); + + switch(ret) { + case -1: + printf("select error: %i\n", errno); + killchild(child, "select error"); + break; + case 0: + + break; + default: + SPDY_run(daemon); + + break; + } + } + while(waitpid(child,&childstatus,WNOHANG) != child); + + //give chance to the client to close socket and handle this in run + usleep(100000); + SPDY_run(daemon); + + SPDY_stop_daemon(daemon); + + SPDY_deinit(); + + return WEXITSTATUS(childstatus); +} + +int main(int argc, char **argv) +{ + int port = get_port(12123); + parent = getpid(); + + child = fork(); + if (child == -1) + { + fprintf(stderr, "can't fork, error %d\n", errno); + exit(EXIT_FAILURE); + } + + if (child == 0) + { + childproc(port); + _exit(0); + } + else + { + int ret = parentproc(port); + if(1 == session_closed_called && 0 == ret) + exit(0); + else + exit(ret ? ret : 21); + } + return 1; +}