From fa0ab174e18cfa3ce6ed65c6d940835c4142f7c9 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 24 Apr 2012 23:48:05 +0900 Subject: [PATCH] Inflate response body if content-encoding: gzip is used. Erase Request from stream2req when stream is closed. --- examples/spdycat.cc | 129 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 126 insertions(+), 3 deletions(-) diff --git a/examples/spdycat.cc b/examples/spdycat.cc index 7e1e446..692a65a 100644 --- a/examples/spdycat.cc +++ b/examples/spdycat.cc @@ -51,6 +51,7 @@ #include #include #include +#include #include "spdylay_ssl.h" #include "uri.h" @@ -71,7 +72,61 @@ struct Config { struct Request { uri::UriStruct us; - Request(const uri::UriStruct& us):us(us) {} + z_stream *inflater; + Request(const uri::UriStruct& us):us(us), inflater(0) {} + ~Request() + { + if(inflater) { + inflateEnd(inflater); + delete inflater; + } + } + + void init_inflater() + { + inflater = new z_stream(); + inflater->next_in = Z_NULL; + inflater->zalloc = Z_NULL; + inflater->zfree = Z_NULL; + inflater->opaque = Z_NULL; + int rv = inflateInit2(inflater, 47); + assert(rv == Z_OK); + } + + // Inflates data in |in| with the length |*inlen_ptr| and stores the + // inflated data to |out| which has allocated size at least + // |*outlen_ptr|. On return, |*outlen_ptr| is updated to represent + // the number of data written in |out|. Similarly, |*inlen_ptr| is + // updated to represent the number of input bytes processed. + // + // This function returns 0 if it succeeds, or -1. + int inflate_data(uint8_t *out, size_t *outlen_ptr, + const uint8_t *in, size_t *inlen_ptr) + { + assert(inflater); + inflater->avail_in = *inlen_ptr; + inflater->next_in = const_cast(in); + inflater->avail_out = *outlen_ptr; + inflater->next_out = out; + + int rv = inflate(inflater, Z_NO_FLUSH); + + *inlen_ptr -= inflater->avail_in; + *outlen_ptr -= inflater->avail_out; + switch(rv) { + case Z_OK: + case Z_STREAM_END: + case Z_BUF_ERROR: + return 0; + case Z_DATA_ERROR: + case Z_STREAM_ERROR: + case Z_NEED_DICT: + case Z_MEM_ERROR: + return -1; + default: + assert(0); + } + } }; std::map stream2req; @@ -86,7 +141,25 @@ void on_data_chunk_recv_callback { std::map::iterator itr = stream2req.find(stream_id); if(itr != stream2req.end()) { - std::cout.write(reinterpret_cast(data), len); + Request *req = (*itr).second; + if(req->inflater) { + while(len > 0) { + const size_t MAX_OUTLEN = 4096; + uint8_t out[MAX_OUTLEN]; + size_t outlen = MAX_OUTLEN; + size_t tlen = len; + int rv = req->inflate_data(out, &outlen, data, &tlen); + if(rv == -1) { + spdylay_submit_rst_stream(session, stream_id, SPDYLAY_INTERNAL_ERROR); + break; + } + std::cout.write(reinterpret_cast(out), outlen); + data += tlen; + len -= tlen; + } + } else { + std::cout.write(reinterpret_cast(data), len); + } } } @@ -118,6 +191,54 @@ void on_ctrl_send_callback3 on_ctrl_send_callback(session, type, frame, user_data); } +void check_gzip +(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, + void *user_data) +{ + char **nv; + int32_t stream_id; + if(type == SPDYLAY_SYN_REPLY) { + nv = frame->syn_reply.nv; + stream_id = frame->syn_reply.stream_id; + } else if(type == SPDYLAY_HEADERS) { + nv = frame->headers.nv; + stream_id = frame->headers.stream_id; + } else { + return; + } + bool gzip = false; + for(size_t i = 0; nv[i]; i += 2) { + if(strcmp("content-encoding", nv[i]) == 0) { + gzip = strcmp("gzip", nv[i+1]) == 0; + break; + } + } + if(gzip) { + Request *req = (Request*)spdylay_session_get_stream_user_data(session, + stream_id); + assert(req); + if(req->inflater) { + return; + } + req->init_inflater(); + } +} + +void on_ctrl_recv_callback2 +(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, + void *user_data) +{ + check_gzip(session, type, frame, user_data); +} + +void on_ctrl_recv_callback3 +(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, + void *user_data) +{ + check_gzip(session, type, frame, user_data); + on_ctrl_recv_callback(session, type, frame, user_data); +} + void on_stream_close_callback (spdylay_session *session, int32_t stream_id, spdylay_status_code status_code, void *user_data) @@ -128,6 +249,7 @@ void on_stream_close_callback if(complete == numreq) { spdylay_submit_goaway(session, SPDYLAY_GOAWAY_OK); } + stream2req.erase(itr); } } @@ -258,10 +380,11 @@ int run(char **uris, int n) callbacks.recv_callback = recv_callback; callbacks.on_stream_close_callback = on_stream_close_callback; if(config.verbose) { - callbacks.on_ctrl_recv_callback = on_ctrl_recv_callback; + callbacks.on_ctrl_recv_callback = on_ctrl_recv_callback3; callbacks.on_data_recv_callback = on_data_recv_callback; callbacks.on_ctrl_send_callback = on_ctrl_send_callback3; } else { + callbacks.on_ctrl_recv_callback = on_ctrl_recv_callback2; callbacks.on_ctrl_send_callback = on_ctrl_send_callback2; } if(!config.null_out) {