[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[Libevent-users] evhttp: persistent connections over ssl with chunked transfer encoding bug?
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;
}