examples: Rewrite client example

This commit is contained in:
Tatsuhiro Tsujikawa 2013-09-02 22:49:34 +09:00
parent d192a602f4
commit 666ab068ff
3 changed files with 126 additions and 130 deletions

View File

@ -20,7 +20,7 @@
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
SUBDIRS = lib src tests doc SUBDIRS = lib src examples tests doc
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4

View File

@ -29,8 +29,8 @@ AM_CPPFLAGS = -Wall -I$(srcdir)/../lib/includes -I$(builddir)/../lib/includes \
AM_LDFLAGS = @OPENSSL_LIBS@ AM_LDFLAGS = @OPENSSL_LIBS@
LDADD = $(top_builddir)/lib/libnghttp2.la LDADD = $(top_builddir)/lib/libnghttp2.la
noinst_PROGRAMS = spdycli noinst_PROGRAMS = client
spdycli_SOURCES = spdycli.c client_SOURCES = client.c
endif # ENABLE_EXAMPLES endif # ENABLE_EXAMPLES

View File

@ -1,7 +1,7 @@
/* /*
* nghttp2 - SPDY Library * nghttp2 - HTTP/2.0 C Library
* *
* Copyright (c) 2012 Tatsuhiro Tsujikawa * Copyright (c) 2013 Tatsuhiro Tsujikawa
* *
* Permission is hereby granted, free of charge, to any person obtaining * Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the * a copy of this software and associated documentation files (the
@ -54,12 +54,12 @@ enum {
struct Connection { struct Connection {
SSL *ssl; SSL *ssl;
nghttp2_session *session; nghttp2_session *session;
/* WANT_READ if SSL connection needs more input; or WANT_WRITE if it /* WANT_READ if SSL/TLS connection needs more input; or WANT_WRITE
needs more output; or IO_NONE. This is necessary because SSL/TLS if it needs more output; or IO_NONE. This is necessary because
re-negotiation is possible at any time. nghttp2 API offers SSL/TLS re-negotiation is possible at any time. nghttp2 API
similar functions like nghttp2_session_want_read() and offers similar functions like nghttp2_session_want_read() and
nghttp2_session_want_write() but they do not take into account nghttp2_session_want_write() but they do not take into account
SSL connection. */ SSL/TSL connection. */
int want_io; int want_io;
}; };
@ -131,28 +131,32 @@ static void diec(const char *func, int error_code)
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
static char CONTENT_LENGTH[] = "content-encoding";
static size_t CONTENT_LENGTH_LEN = sizeof(CONTENT_LENGTH) - 1;
static char GZIP[] = "gzip";
static size_t GZIP_LEN = sizeof(GZIP) - 1;
/* /*
* Check response is content-encoding: gzip. We need this because SPDY * Check response is content-encoding: gzip. We need this because
* client is required to support gzip. * HTTP/2.0 client is required to support gzip.
*/ */
static void check_gzip(struct Request *req, char **nv) static void check_gzip(struct Request *req, nghttp2_nv *nva, size_t nvlen)
{ {
int gzip = 0;
size_t i; size_t i;
for(i = 0; nv[i]; i += 2) { if(req->inflater) {
if(strcmp("content-encoding", nv[i]) == 0) { return;
gzip = strcmp("gzip", nv[i+1]) == 0;
break;
}
} }
if(gzip) { for(i = 0; i < nvlen; ++i) {
int rv; if(CONTENT_LENGTH_LEN == nva[i].namelen &&
if(req->inflater) { memcmp(CONTENT_LENGTH, nva[i].name, nva[i].namelen) == 0 &&
return; GZIP_LEN == nva[i].valuelen &&
} memcmp(GZIP, nva[i].value, nva[i].valuelen) == 0) {
rv = nghttp2_gzip_inflate_new(&req->inflater); int rv;
if(rv != 0) { rv = nghttp2_gzip_inflate_new(&req->inflater);
die("Can't allocate inflate stream."); if(rv != 0) {
die("Can't allocate inflate stream.");
}
break;
} }
} }
} }
@ -218,86 +222,85 @@ static ssize_t recv_callback(nghttp2_session *session,
} }
/* /*
* The implementation of nghttp2_before_ctrl_send_callback type. We * The implementation of nghttp2_before_frame_send_callback type. We
* use this function to get stream ID of the request. This is because * use this function to get stream ID of the request. This is because
* stream ID is not known when we submit the request * stream ID is not known when we submit the request
* (nghttp2_submit_request). * (nghttp2_submit_request).
*/ */
static void before_ctrl_send_callback(nghttp2_session *session, static int before_frame_send_callback(nghttp2_session *session,
nghttp2_frame_type type,
nghttp2_frame *frame, nghttp2_frame *frame,
void *user_data) void *user_data)
{ {
if(type == NGHTTP2_SYN_STREAM) { if(frame->hd.type == NGHTTP2_HEADERS &&
frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
struct Request *req; struct Request *req;
int stream_id = frame->syn_stream.stream_id; int32_t stream_id = frame->hd.stream_id;
req = nghttp2_session_get_stream_user_data(session, stream_id); req = nghttp2_session_get_stream_user_data(session, stream_id);
if(req && req->stream_id == -1) { if(req && req->stream_id == -1) {
req->stream_id = stream_id; req->stream_id = stream_id;
printf("[INFO] Stream ID = %d\n", stream_id); printf("[INFO] Stream ID = %d\n", stream_id);
} }
} }
return 0;
} }
static void on_ctrl_send_callback(nghttp2_session *session, static int on_frame_send_callback(nghttp2_session *session,
nghttp2_frame_type type,
nghttp2_frame *frame, void *user_data) nghttp2_frame *frame, void *user_data)
{ {
char **nv;
const char *name = NULL;
int32_t stream_id;
size_t i; size_t i;
switch(type) { switch(frame->hd.type) {
case NGHTTP2_SYN_STREAM:
nv = frame->syn_stream.nv;
name = "SYN_STREAM";
stream_id = frame->syn_stream.stream_id;
break;
default:
break;
}
if(name && nghttp2_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(nghttp2_session *session,
nghttp2_frame_type type,
nghttp2_frame *frame, void *user_data)
{
struct Request *req;
char **nv;
const char *name = NULL;
int32_t stream_id;
size_t i;
switch(type) {
case NGHTTP2_SYN_REPLY:
nv = frame->syn_reply.nv;
name = "SYN_REPLY";
stream_id = frame->syn_reply.stream_id;
break;
case NGHTTP2_HEADERS: case NGHTTP2_HEADERS:
nv = frame->headers.nv; if(nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) {
name = "HEADERS"; const nghttp2_nv *nva = frame->headers.nva;
stream_id = frame->headers.stream_id; printf("[INFO] C ----------------------------> S (HEADERS)\n");
break; for(i = 0; i < frame->headers.nvlen; ++i) {
default: fwrite(nva[i].name, nva[i].namelen, 1, stdout);
break; printf(": ");
} fwrite(nva[i].value, nva[i].valuelen, 1, stdout);
if(!name) { printf("\n");
return; }
}
req = nghttp2_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]);
} }
break;
case NGHTTP2_RST_STREAM:
printf("[INFO] C ----------------------------> S (RST_STREAM)\n");
break;
case NGHTTP2_GOAWAY:
printf("[INFO] C ----------------------------> S (GOAWAY)\n");
break;
} }
return 0;
}
static int on_frame_recv_callback(nghttp2_session *session,
nghttp2_frame *frame, void *user_data)
{
size_t i;
switch(frame->hd.type) {
case NGHTTP2_HEADERS:
if(frame->headers.cat == NGHTTP2_HCAT_RESPONSE) {
const nghttp2_nv *nva = frame->headers.nva;
struct Request *req;
req = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
if(req) {
check_gzip(req, frame->headers.nva, frame->headers.nvlen);
printf("[INFO] C <---------------------------- S (HEADERS)\n");
for(i = 0; i < frame->headers.nvlen; ++i) {
fwrite(nva[i].name, nva[i].namelen, 1, stdout);
printf(": ");
fwrite(nva[i].value, nva[i].valuelen, 1, stdout);
printf("\n");
}
}
}
break;
case NGHTTP2_RST_STREAM:
printf("[INFO] C <---------------------------- S (RST_STREAM)\n");
break;
case NGHTTP2_GOAWAY:
printf("[INFO] C <---------------------------- S (GOAWAY)\n");
break;
}
return 0;
} }
/* /*
@ -306,20 +309,21 @@ static void on_ctrl_recv_callback(nghttp2_session *session,
* fetch 1 resource in this program, after reception of the response, * fetch 1 resource in this program, after reception of the response,
* we submit GOAWAY and close the session. * we submit GOAWAY and close the session.
*/ */
static void on_stream_close_callback(nghttp2_session *session, static int on_stream_close_callback(nghttp2_session *session,
int32_t stream_id, int32_t stream_id,
nghttp2_status_code status_code, nghttp2_error_code error_code,
void *user_data) void *user_data)
{ {
struct Request *req; struct Request *req;
req = nghttp2_session_get_stream_user_data(session, stream_id); req = nghttp2_session_get_stream_user_data(session, stream_id);
if(req) { if(req) {
int rv; int rv;
rv = nghttp2_submit_goaway(session, NGHTTP2_GOAWAY_OK); rv = nghttp2_submit_goaway(session, NGHTTP2_NO_ERROR, NULL, 0);
if(rv != 0) { if(rv != 0) {
diec("nghttp2_submit_goaway", rv); diec("nghttp2_submit_goaway", rv);
} }
} }
return 0;
} }
#define MAX_OUTLEN 4096 #define MAX_OUTLEN 4096
@ -328,16 +332,16 @@ static void on_stream_close_callback(nghttp2_session *session,
* The implementation of nghttp2_on_data_chunk_recv_callback type. We * The implementation of nghttp2_on_data_chunk_recv_callback type. We
* use this function to print the received response body. * use this function to print the received response body.
*/ */
static void on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
int32_t stream_id, int32_t stream_id,
const uint8_t *data, size_t len, const uint8_t *data, size_t len,
void *user_data) void *user_data)
{ {
struct Request *req; struct Request *req;
req = nghttp2_session_get_stream_user_data(session, stream_id); req = nghttp2_session_get_stream_user_data(session, stream_id);
if(req) { if(req) {
printf("[INFO] C <---------------------------- S (DATA)\n"); printf("[INFO] C <---------------------------- S (DATA chunk)\n"
printf(" %lu bytes\n", (unsigned long int)len); "%lu bytes\n", (unsigned long int)len);
if(req->inflater) { if(req->inflater) {
while(len > 0) { while(len > 0) {
uint8_t out[MAX_OUTLEN]; uint8_t out[MAX_OUTLEN];
@ -354,11 +358,11 @@ static void on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
len -= tlen; len -= tlen;
} }
} else { } else {
/* TODO add support gzip */
fwrite(data, 1, len, stdout); fwrite(data, 1, len, stdout);
} }
printf("\n"); printf("\n");
} }
return 0;
} }
/* /*
@ -372,16 +376,16 @@ static void setup_nghttp2_callbacks(nghttp2_session_callbacks *callbacks)
memset(callbacks, 0, sizeof(nghttp2_session_callbacks)); memset(callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks->send_callback = send_callback; callbacks->send_callback = send_callback;
callbacks->recv_callback = recv_callback; callbacks->recv_callback = recv_callback;
callbacks->before_ctrl_send_callback = before_ctrl_send_callback; callbacks->before_frame_send_callback = before_frame_send_callback;
callbacks->on_ctrl_send_callback = on_ctrl_send_callback; callbacks->on_frame_send_callback = on_frame_send_callback;
callbacks->on_ctrl_recv_callback = on_ctrl_recv_callback; callbacks->on_frame_recv_callback = on_frame_recv_callback;
callbacks->on_stream_close_callback = on_stream_close_callback; callbacks->on_stream_close_callback = on_stream_close_callback;
callbacks->on_data_chunk_recv_callback = on_data_chunk_recv_callback; callbacks->on_data_chunk_recv_callback = on_data_chunk_recv_callback;
} }
/* /*
* Callback function for SSL/TLS NPN. Since this program only supports * Callback function for TLS NPN. Since this program only supports
* SPDY protocol, if server does not offer SPDY protocol the nghttp2 * HTTP/2.0 protocol, if server does not offer HTTP/2.0 the nghttp2
* library supports, we terminate program. * library supports, we terminate program.
*/ */
static int select_next_proto_cb(SSL* ssl, static int select_next_proto_cb(SSL* ssl,
@ -390,31 +394,26 @@ static int select_next_proto_cb(SSL* ssl,
void *arg) void *arg)
{ {
int rv; int rv;
uint16_t *spdy_proto_version; /* nghttp2_select_next_protocol() selects HTTP/2.0 protocol the
/* nghttp2_select_next_protocol() selects SPDY protocol version the
nghttp2 library supports. */ nghttp2 library supports. */
rv = nghttp2_select_next_protocol(out, outlen, in, inlen); rv = nghttp2_select_next_protocol(out, outlen, in, inlen);
if(rv <= 0) { if(rv <= 0) {
die("Server did not advertise spdy/2 or spdy/3 protocol."); die("Server did not advertise HTTP/2.0 protocol");
} }
spdy_proto_version = (uint16_t*)arg;
*spdy_proto_version = rv;
return SSL_TLSEXT_ERR_OK; return SSL_TLSEXT_ERR_OK;
} }
/* /*
* Setup SSL context. We pass |spdy_proto_version| to get negotiated * Setup SSL/TLS context.
* SPDY protocol version in NPN callback.
*/ */
static void init_ssl_ctx(SSL_CTX *ssl_ctx, uint16_t *spdy_proto_version) static void init_ssl_ctx(SSL_CTX *ssl_ctx)
{ {
/* Disable SSLv2 and enable all workarounds for buggy servers */ /* Disable SSLv2 and enable all workarounds for buggy servers */
SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2); SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2);
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
/* Set NPN callback */ /* Set NPN callback */
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL);
spdy_proto_version);
} }
static void ssl_handshake(SSL *ssl, int fd) static void ssl_handshake(SSL *ssl, int fd)
@ -479,9 +478,6 @@ static void make_non_block(int fd)
} }
} }
/*
* Setting TCP_NODELAY is not mandatory for the SPDY protocol.
*/
static void set_tcp_nodelay(int fd) static void set_tcp_nodelay(int fd)
{ {
int val = 1; int val = 1;
@ -517,18 +513,15 @@ static void submit_request(struct Connection *connection, struct Request *req)
{ {
int pri = 0; int pri = 0;
int rv; int rv;
const char *nv[15]; const char *nv[13];
/* We always use SPDY/3 style header even if the negotiated protocol /* Make sure that the last item is NULL */
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[0] = ":method"; nv[1] = "GET";
nv[2] = ":path"; nv[3] = req->path; nv[2] = ":path"; nv[3] = req->path;
nv[4] = ":version"; nv[5] = "HTTP/1.1"; nv[4] = ":scheme"; nv[5] = "https";
nv[6] = ":scheme"; nv[7] = "https"; nv[6] = ":host"; nv[7] = req->hostport;
nv[8] = ":host"; nv[9] = req->hostport; nv[8] = "accept"; nv[9] = "*/*";
nv[10] = "accept"; nv[11] = "*/*"; nv[10] = "user-agent"; nv[11] = "nghttp2/"NGHTTP2_VERSION;
nv[12] = "user-agent"; nv[13] = "nghttp2/"NGHTTP2_VERSION; nv[12] = NULL;
nv[14] = NULL;
rv = nghttp2_submit_request(connection->session, pri, nv, NULL, req); rv = nghttp2_submit_request(connection->session, pri, nv, NULL, req);
if(rv != 0) { if(rv != 0) {
diec("nghttp2_submit_request", rv); diec("nghttp2_submit_request", rv);
@ -583,7 +576,6 @@ static void fetch_uri(const struct URI *uri)
int rv; int rv;
nfds_t npollfds = 1; nfds_t npollfds = 1;
struct pollfd pollfds[1]; struct pollfd pollfds[1];
uint16_t spdy_proto_version;
request_init(&req, uri); request_init(&req, uri);
@ -598,7 +590,7 @@ static void fetch_uri(const struct URI *uri)
if(ssl_ctx == NULL) { if(ssl_ctx == NULL) {
dief("SSL_CTX_new", ERR_error_string(ERR_get_error(), NULL)); dief("SSL_CTX_new", ERR_error_string(ERR_get_error(), NULL));
} }
init_ssl_ctx(ssl_ctx, &spdy_proto_version); init_ssl_ctx(ssl_ctx);
ssl = SSL_new(ssl_ctx); ssl = SSL_new(ssl_ctx);
if(ssl == NULL) { if(ssl == NULL) {
dief("SSL_new", ERR_error_string(ERR_get_error(), NULL)); dief("SSL_new", ERR_error_string(ERR_get_error(), NULL));
@ -610,13 +602,17 @@ static void fetch_uri(const struct URI *uri)
connection.ssl = ssl; connection.ssl = ssl;
connection.want_io = IO_NONE; connection.want_io = IO_NONE;
/* Send connection header in blocking I/O mode */
SSL_write(ssl, NGHTTP2_CLIENT_CONNECTION_HEADER,
NGHTTP2_CLIENT_CONNECTION_HEADER_LEN);
/* Here make file descriptor non-block */ /* Here make file descriptor non-block */
make_non_block(fd); make_non_block(fd);
set_tcp_nodelay(fd); set_tcp_nodelay(fd);
printf("[INFO] SPDY protocol version = %d\n", spdy_proto_version); printf("[INFO] SSL/TLS handshake completed\n");
rv = nghttp2_session_client_new(&connection.session, spdy_proto_version, rv = nghttp2_session_client_new(&connection.session, &callbacks,
&callbacks, &connection); &connection);
if(rv != 0) { if(rv != 0) {
diec("nghttp2_session_client_new", rv); diec("nghttp2_session_client_new", rv);
} }
@ -743,7 +739,7 @@ int main(int argc, char **argv)
int rv; int rv;
if(argc < 2) { if(argc < 2) {
die("Specify URI"); die("Specify a https URI");
} }
memset(&act, 0, sizeof(struct sigaction)); memset(&act, 0, sizeof(struct sigaction));