Hi, While working on a comet http server, I have run into a problem where the second request on a reused (persistent) TCP connection is not processed by evhttp. Has anyone experienced behavior like this?
I created a minimal test case [1] which demonstrates that all of the following must be true for the bug to be triggered: * HTTP persistent connection * HTTPS/SSL enabled * HTTP chunked encoding (on the first request over the TCP connection) When this situation occurs, the second request eventually times out after HTTP_CONNECT_TIMEOUT. I have observed this behavior against the most recent code in git at levent.git.sourceforge.net. Does anyone have experience with this issue or any recommendations of where in the code to begin looking for a fix? Thanks, -Andy [1] main.cc is an example evhttp server that provides who handlers [/no_chunks and /] over both SSL and clear text http.py is an example HTTP client that creates a persistent HTTP connection and sends two requests to each of the above handlers over a shared TCP connection
#!/usr/bin/env python import socket import ssl debug = False configs = [ { 'host': 'www.google.com', 'port': 80, 'url': '/', 'ssl': False, }, { 'host': 'www.google.com', 'port': 443, 'url': '/', 'ssl': True, }, { 'host': '127.0.0.1', 'port': 8888, 'url': '/no_chunks', 'ssl': False, }, { 'host': '127.0.0.1', 'port': 8889, 'url': '/no_chunks', 'ssl': True, }, { 'host': '127.0.0.1', 'port': 8888, 'url': '/', 'ssl': False, }, { 'host': '127.0.0.1', 'port': 8889, 'url': '/', 'ssl': True, }, ] def read_until(s, until): rtn = '' while 1: data = s.recv(1) if not data: raise Exception('malformed req') rtn += data if rtn.endswith(until): break return rtn def read_bytes(s, bytes): rtn = '' while 1: data = s.recv(1) if not data: raise Exception('malformed req') rtn += data if len(rtn) == bytes: break return rtn def process_req(s, config): req = """GET %(url)s HTTP/1.1\r Host: %(host)s:%(port)s\r \r """ % config if debug: print 'req: %s' % req s.sendall(req) chunked = None try: status = read_until(s, '\r\n') if debug: print 'status: %s' % status.strip() headers = read_until(s, '\r\n\r\n') if debug: print 'headers: %s' % headers.strip() header_list = headers.strip().split('\r\n') content_length = None for header in header_list: (key, value) = header.split(":", 1) if key.lower() == "content-length": content_length = int(value) elif key.lower() == "transfer-encoding": chunked = True if debug: print 'chunked: %s' % chunked if debug: print 'content_length: %s' % content_length if chunked: while True: chunk_size = read_until(s, '\r\n') if debug: print 'chunk_size: %s' % chunk_size.strip() chunk_size = int(chunk_size.strip(), 16) if chunk_size == 0: break chunk = read_bytes(s, chunk_size) if debug: print 'chunk: %s' % chunk burn = read_until(s, '\r\n') burn = read_until(s, '\r\n') else: message = read_bytes(s, content_length) except Exception as e: if debug: print 'REQ FAILED: %s' % e return (chunked, False) if debug: print 'REQ DONE' return (chunked, True) socket.setdefaulttimeout(10) for config in configs: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if config['ssl']: s = ssl.wrap_socket(s) s.connect((config['host'], config['port'])) for i in range(2): (chunked, success) = process_req(s, config) if not success: break s.close() config['chunked'] = chunked print 'host=%(host)s ssl=%(ssl)s chunked=%(chunked)s: ' % config, if success: print 'success' else: print 'failure'
#include <event2/buffer.h> #include <event2/bufferevent_ssl.h> #include <event2/event.h> #include <event2/http.h> #include <openssl/err.h> #include <openssl/ssl.h> #include <string> #include <vector> #include <iostream> const std::string crt_asn1_str = "XXX"; const std::string intermediate2_crt_asn1_str = "XXX"; const std::string intermediate1_crt_asn1_str = "XXX"; const std::string root_crt_asn1_str = "XXX"; const std::string key_asn1_str = "XXX"; void NonChunkHandler(struct evhttp_request *req, void */* arg */) { struct evbuffer *buf = evbuffer_new(); if (!buf) { evhttp_send_error(req, HTTP_INTERNAL, 0); return; } const std::string payload = "12345"; if (evbuffer_add(buf, payload.c_str(), payload.length()) != 0) { evbuffer_free(buf); return; } evhttp_send_reply(req, HTTP_OK, "OK", buf); evbuffer_free(buf); } void ChunkHandler(struct evhttp_request *req, void */* arg */) { evhttp_send_reply_start(req, HTTP_OK, "OK"); struct evbuffer *buf = evbuffer_new(); if (!buf) { evhttp_send_error(req, HTTP_INTERNAL, 0); return; } const std::string payload = "12345"; if (evbuffer_add(buf, payload.c_str(), payload.length()) != 0) { evbuffer_free(buf); return; } evhttp_send_reply_chunk(req, buf); evbuffer_free(buf); evhttp_send_reply_end(req); } struct bufferevent *BufferEventCallback(struct event_base *base, void *arg) { SSL_CTX *sctx = static_cast<SSL_CTX *>(arg); return bufferevent_openssl_socket_new(base, -1, SSL_new(sctx), BUFFEREVENT_SSL_ACCEPTING, BEV_OPT_CLOSE_ON_FREE); } int main(int /* argc */, char */* argv */[]) { struct event_base *base_; struct evhttp *http_, *https_; struct evhttp_bound_socket *handle_, *handles_; SSL_CTX *sctx_; base_ = event_base_new(); assert(base_ != NULL); http_ = evhttp_new(base_); assert(http_ != NULL); https_ = evhttp_new(base_); assert(https_ != NULL); assert(evhttp_set_cb(http_, "/no_chunks", NonChunkHandler, NULL) == 0); assert(evhttp_set_cb(https_, "/no_chunks", NonChunkHandler, NULL) == 0); evhttp_set_gencb(http_, ChunkHandler, NULL); evhttp_set_gencb(https_, ChunkHandler, NULL); SSL_load_error_strings(); SSL_library_init(); OpenSSL_add_all_algorithms(); sctx_ = SSL_CTX_new(SSLv23_server_method()); assert(sctx_ != NULL); if (SSL_CTX_use_certificate_ASN1(sctx_, crt_asn1_str.length(), reinterpret_cast<const unsigned char*> ( crt_asn1_str.c_str())) != 1) { std::cerr << "SSL_CTX_use_certificate_ASN1: " << ERR_reason_error_string(ERR_get_error()); exit(1); } std::vector<std::string> chain; chain.push_back(intermediate2_crt_asn1_str); chain.push_back(intermediate1_crt_asn1_str); chain.push_back(root_crt_asn1_str); for (std::vector<std::string>::const_iterator i = chain.begin(); i != chain.end(); ++i) { const unsigned char *crt = reinterpret_cast<const unsigned char*> ( i->c_str()); X509 *x509 = d2i_X509(NULL, &crt, i->length()); assert(x509 != NULL); if (SSL_CTX_add_extra_chain_cert(sctx_, x509) != 1) { std::cerr << "SSL_CTX_add_extra_chain_cert: " << ERR_reason_error_string(ERR_get_error()); exit(2); } break; } if (SSL_CTX_use_PrivateKey_ASN1( EVP_PKEY_RSA, sctx_, reinterpret_cast<const unsigned char*> (key_asn1_str.c_str()), key_asn1_str.length()) != 1) { std::cerr << "SSL_CTX_use_PrivateKey_ASN1: " << ERR_reason_error_string(ERR_get_error()); exit(3); } SSL_CTX_set_verify(sctx_, SSL_VERIFY_NONE, NULL); evhttp_set_bevcb(https_, BufferEventCallback, sctx_); handle_ = evhttp_bind_socket_with_handle(http_, "0.0.0.0", 8888); assert(handle_ != NULL); handles_ = evhttp_bind_socket_with_handle(https_, "0.0.0.0", 8889); assert(handles_ != NULL); event_base_dispatch(base_); return 0; }