/*
This file is part of libmicrospdy
Copyright 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);
if (NULL == dst)
abort ();
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)
{
(void)session;
(void)flags;
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)
{
(void)session;
(void)flags;
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)
{
(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)
{
(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)
{
(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)
{
(void)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)
{
(void)flags;
(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;
dief("connect", strerror(errno));
}
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("fcntl1", strerror(errno));
}
while((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR);
if(rv == -1) {
dief("fcntl2", 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);
if (-1 == fd)
abort ();
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,
bool more)
{
(void)cls;
(void)request;
(void)priority;
(void)host;
(void)scheme;
(void)headers;
(void)method;
(void)version;
(void)more;
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);
usleep(10000);
asprintf(&uristr, "https://127.0.0.1:%i/",port);
if(NULL == (rcvbuf = malloc(strlen(RESPONSE_BODY)+1)))
killparent(parent,"no memory");
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_FLAGS, SPDY_DAEMON_FLAG_NO_DELAY,
SPDY_DAEMON_OPTION_END);
if(NULL==daemon){
printf("no daemon\n");
return 1;
}
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 > 1000)
{
timeout.tv_sec = 1;
timeout.tv_usec = 0;
}
else
{
timeout.tv_sec = timeoutlong / 1000;
timeout.tv_usec = (timeoutlong % 1000) * 1000;
}
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 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;
}