Added experimental spdy/3 support to spdyd, spdynative and spdycat

This commit is contained in:
Tatsuhiro Tsujikawa 2012-02-26 01:31:45 +09:00
parent 0a723aa10f
commit 70ebf673fc
6 changed files with 122 additions and 40 deletions

View File

@ -32,6 +32,7 @@
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <cassert>
#include <set>
#include <iostream>
@ -54,7 +55,8 @@ const std::string DEFAULT_HTML = "index.html";
const std::string SPDYD_SERVER = "spdyd spdylay/"SPDYLAY_VERSION;
} // namespace
Config::Config(): verbose(false), daemon(false), port(0), data_ptr(0)
Config::Config(): verbose(false), daemon(false), port(0), data_ptr(0),
spdy3_only(false)
{}
Request::Request(int32_t stream_id)
@ -168,12 +170,16 @@ void on_session_closed(EventHandler *hd, int64_t session_id)
SpdyEventHandler::SpdyEventHandler(const Config* config,
int fd, SSL *ssl,
uint16_t version,
const spdylay_session_callbacks *callbacks,
int64_t session_id)
: EventHandler(config),
fd_(fd), ssl_(ssl), session_id_(session_id), want_write_(false)
fd_(fd), ssl_(ssl), version_(version), session_id_(session_id),
want_write_(false)
{
spdylay_session_server_new(&session_, SPDYLAY_PROTO_SPDY2, callbacks, this);
int r;
r = spdylay_session_server_new(&session_, version, callbacks, this);
assert(r == 0);
}
SpdyEventHandler::~SpdyEventHandler()
@ -190,6 +196,11 @@ SpdyEventHandler::~SpdyEventHandler()
close(fd_);
}
uint16_t SpdyEventHandler::version() const
{
return version_;
}
int SpdyEventHandler::execute(Sessions *sessions)
{
int r;
@ -253,8 +264,8 @@ int SpdyEventHandler::submit_file_response(const std::string& status,
spdylay_data_provider *data_prd)
{
const char *nv[] = {
"status", status.c_str(),
"version", "HTTP/1.1",
get_header_field(version_, HD_STATUS).c_str(), status.c_str(),
get_header_field(version_, HD_VERSION).c_str(), "HTTP/1.1",
"server", SPDYD_SERVER.c_str(),
"content-length", util::to_str(file_length).c_str(),
"cache-control", "max-age=3600",
@ -276,9 +287,9 @@ int SpdyEventHandler::submit_response
spdylay_data_provider *data_prd)
{
const char **nv = new const char*[8+headers.size()*2+1];
nv[0] = "status";
nv[0] = get_header_field(version_, HD_STATUS).c_str();
nv[1] = status.c_str();
nv[2] = "version";
nv[2] = get_header_field(version_, HD_VERSION).c_str();
nv[3] = "HTTP/1.1";
nv[4] = "server";
nv[5] = SPDYD_SERVER.c_str();
@ -299,8 +310,8 @@ int SpdyEventHandler::submit_response(const std::string& status,
spdylay_data_provider *data_prd)
{
const char *nv[] = {
"status", status.c_str(),
"version", "HTTP/1.1",
get_header_field(version_, HD_STATUS).c_str(), status.c_str(),
get_header_field(version_, HD_VERSION).c_str(), "HTTP/1.1",
"server", SPDYD_SERVER.c_str(),
0
};
@ -432,26 +443,30 @@ void prepare_response(Request *req, SpdyEventHandler *hd)
bool method_found = false;
bool scheme_found = false;
bool version_found = false;
bool host_found = false;
time_t last_mod = 0;
bool last_mod_found = false;
for(int i = 0; i < (int)req->headers.size(); ++i) {
const std::string &field = req->headers[i].first;
const std::string &value = req->headers[i].second;
if(!url_found && field == "url") {
if(!url_found && field == get_header_field(hd->version(), HD_PATH)) {
url_found = true;
url = value;
} else if(field == "method") {
} else if(field == get_header_field(hd->version(), HD_METHOD)) {
method_found = true;
} else if(field == "scheme") {
} else if(field == get_header_field(hd->version(), HD_SCHEME)) {
scheme_found = true;
} else if(field == "version") {
} else if(field == get_header_field(hd->version(), HD_VERSION)) {
version_found = true;
} else if(field == get_header_field(hd->version(), HD_HOST)) {
host_found = true;
} else if(!last_mod_found && field == "if-modified-since") {
last_mod_found = true;
last_mod = util::parse_http_date(value);
}
}
if(!url_found || !method_found || !scheme_found || !version_found) {
if(!url_found || !method_found || !scheme_found || !version_found ||
!host_found) {
prepare_status_response(req, hd, STATUS_400);
return;
}
@ -608,7 +623,7 @@ public:
SSLAcceptEventHandler(const Config *config,
int fd, SSL *ssl, int64_t session_id)
: EventHandler(config),
fd_(fd), ssl_(ssl), fail_(false), finish_(false),
fd_(fd), ssl_(ssl), version_(0), fail_(false), finish_(false),
want_read_(true), want_write_(true),
session_id_(session_id)
{}
@ -636,7 +651,8 @@ public:
if(config()->verbose) {
std::cout << "The negotiated next protocol: " << proto << std::endl;
}
if(proto == "spdy/2") {
version_ = spdylay_npn_get_version(next_proto, next_proto_len);
if(version_) {
add_next_handler(sessions);
} else {
fail_ = true;
@ -697,7 +713,7 @@ private:
callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback;
callbacks.on_request_recv_callback = config()->on_request_recv_callback;
SpdyEventHandler *hd = new SpdyEventHandler(config(),
fd_, ssl_, &callbacks,
fd_, ssl_, version_, &callbacks,
session_id_);
if(sessions->mod_poll(hd) == -1) {
// fd_, ssl_ are freed by ~SpdyEventHandler()
@ -709,6 +725,7 @@ private:
int fd_;
SSL *ssl_;
uint16_t version_;
bool fail_, finish_;
bool want_read_, want_write_;
int64_t session_id_;
@ -862,13 +879,23 @@ int SpdyServer::run()
return -1;
}
// We only speak "spdy/2".
// We speaks "spdy/2" and "spdy/3".
std::pair<unsigned char*, size_t> next_proto;
unsigned char proto_list[7];
proto_list[0] = 6;
memcpy(&proto_list[1], "spdy/2", 6);
next_proto.first = proto_list;
next_proto.second = sizeof(proto_list);
unsigned char proto_list[14];
if(config_->spdy3_only) {
proto_list[0] = 6;
memcpy(&proto_list[1], "spdy/3", 6);
next_proto.first = proto_list;
next_proto.second = 7;
} else {
proto_list[0] = 6;
memcpy(&proto_list[1], "spdy/2", 6);
proto_list[7] = 6;
memcpy(&proto_list[8], "spdy/3", 6);
next_proto.first = proto_list;
next_proto.second = sizeof(proto_list);
}
SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, &next_proto);

View File

@ -50,6 +50,7 @@ struct Config {
std::string cert_file;
spdylay_on_request_recv_callback on_request_recv_callback;
void *data_ptr;
bool spdy3_only;
Config();
};
@ -93,7 +94,8 @@ struct Request {
class SpdyEventHandler : public EventHandler {
public:
SpdyEventHandler(const Config* config,
int fd, SSL *ssl, const spdylay_session_callbacks *callbacks,
int fd, SSL *ssl, uint16_t version,
const spdylay_session_callbacks *callbacks,
int64_t session_id);
virtual ~SpdyEventHandler();
virtual int execute(Sessions *sessions);
@ -102,6 +104,8 @@ public:
virtual int fd() const;
virtual bool finish();
uint16_t version() const;
ssize_t send_data(const uint8_t *data, size_t len, int flags);
ssize_t recv_data(uint8_t *data, size_t len, int flags);
@ -132,6 +136,7 @@ private:
spdylay_session *session_;
int fd_;
SSL* ssl_;
uint16_t version_;
int64_t session_id_;
bool want_write_;
std::map<int32_t, Request*> id2req_;

View File

@ -144,7 +144,8 @@ int communicate(const std::string& host, uint16_t port,
std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl;
return -1;
}
setup_ssl_ctx(ssl_ctx);
std::string next_proto;
setup_ssl_ctx(ssl_ctx, &next_proto);
SSL *ssl = SSL_new(ssl_ctx);
if(!ssl) {
std::cerr << ERR_error_string(ERR_get_error(), 0) << std::endl;
@ -155,7 +156,10 @@ int communicate(const std::string& host, uint16_t port,
}
make_non_block(fd);
set_tcp_nodelay(fd);
Spdylay sc(fd, ssl, callbacks);
Spdylay sc(fd, ssl,
spdylay_npn_get_version(reinterpret_cast<const unsigned char*>
(next_proto.c_str()), next_proto.size()),
callbacks);
nfds_t npollfds = 1;
pollfd pollfds[1];

View File

@ -28,6 +28,7 @@
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <string>
#include <iostream>
#include <string>
@ -68,6 +69,8 @@ void print_help(std::ostream& out)
<< " -v, --verbose Print debug information such as reception/\n"
<< " transmission of frames and name/value pairs.\n"
<< "\n"
<< " -3, --spdy3 Only use SPDY/3.\n"
<< "\n"
<< " -h, --help Print this help.\n"
<< std::endl;
}
@ -82,10 +85,11 @@ int main(int argc, char **argv)
{"htdocs", required_argument, 0, 'd' },
{"help", no_argument, 0, 'h' },
{"verbose", no_argument, 0, 'v' },
{"spdy3", no_argument, 0, '3' },
{0, 0, 0, 0 }
};
int option_index = 0;
int c = getopt_long(argc, argv, "Dd:hv", long_options, &option_index);
int c = getopt_long(argc, argv, "Dd:hv3", long_options, &option_index);
if(c == -1) {
break;
}
@ -102,6 +106,9 @@ int main(int argc, char **argv)
case 'v':
config.verbose = true;
break;
case '3':
config.spdy3_only = true;
break;
case '?':
exit(EXIT_FAILURE);
default:

View File

@ -48,10 +48,23 @@ namespace spdylay {
bool ssl_debug = false;
Spdylay::Spdylay(int fd, SSL *ssl, const spdylay_session_callbacks *callbacks)
: fd_(fd), ssl_(ssl), want_write_(false)
const std::string& get_header_field(uint16_t version, size_t field)
{
spdylay_session_client_new(&session_, SPDYLAY_PROTO_SPDY2, callbacks, this);
if(version == SPDYLAY_PROTO_SPDY2) {
return header_fields_spdy2[field];
} else if(version == SPDYLAY_PROTO_SPDY3) {
return header_fields_spdy3[field];
} else {
abort();
}
}
Spdylay::Spdylay(int fd, SSL *ssl, uint16_t version,
const spdylay_session_callbacks *callbacks)
: fd_(fd), ssl_(ssl), version_(version), want_write_(false)
{
int r = spdylay_session_client_new(&session_, version_, callbacks, this);
assert(r == 0);
}
Spdylay::~Spdylay()
@ -109,12 +122,12 @@ int Spdylay::submit_request(const std::string& hostport,
void *stream_user_data)
{
const char *nv[] = {
"host", hostport.c_str(),
"method", "GET",
"scheme", "https",
"url", path.c_str(),
get_header_field(version_, HD_METHOD).c_str(), "GET",
get_header_field(version_, HD_PATH).c_str(), path.c_str(),
get_header_field(version_, HD_VERSION).c_str(), "HTTP/1.1",
get_header_field(version_, HD_SCHEME).c_str(), "https",
get_header_field(version_, HD_HOST).c_str(), hostport.c_str(),
"user-agent", "spdylay/0.0.0",
"version", "HTTP/1.1",
NULL
};
return spdylay_submit_request(session_, pri, nv, NULL, stream_user_data);
@ -458,8 +471,12 @@ int select_next_proto_cb(SSL* ssl,
}
}
if(spdylay_select_next_protocol(out, outlen, in, inlen) != 1) {
std::cerr << "Server did not advertise spdy/2 protocol." << std::endl;
std::cerr << "Server did not advertise spdy/2 or spdy/3 protocol."
<< std::endl;
abort();
} else {
std::string& next_proto = *(std::string*)arg;
next_proto.assign(&(*out)[0], &(*out)[*outlen]);
}
if(ssl_debug) {
std::cout << " NPN selected the protocol: "
@ -468,13 +485,14 @@ int select_next_proto_cb(SSL* ssl,
return SSL_TLSEXT_ERR_OK;
}
void setup_ssl_ctx(SSL_CTX *ssl_ctx)
void setup_ssl_ctx(SSL_CTX *ssl_ctx, void *next_proto_select_cb_arg)
{
/* 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_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, 0);
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb,
next_proto_select_cb_arg);
}
int ssl_handshake(SSL *ssl, int fd)

View File

@ -38,9 +38,29 @@ namespace spdylay {
extern bool ssl_debug;
enum HeaderField {
HD_METHOD = 0,
HD_PATH = 1,
HD_VERSION = 2,
HD_HOST = 3,
HD_SCHEME = 4,
HD_STATUS = 5
};
const std::string header_fields_spdy2[] = {
"method", "url", "version", "host", "scheme", "status", "version"
};
const std::string header_fields_spdy3[] = {
":method", ":path", ":version", ":host", ":scheme", ":status", ":version"
};
const std::string& get_header_field(uint16_t version, size_t field);
class Spdylay {
public:
Spdylay(int fd, SSL *ssl, const spdylay_session_callbacks *callbacks);
Spdylay(int fd, SSL *ssl, uint16_t version,
const spdylay_session_callbacks *callbacks);
~Spdylay();
int recv();
int send();
@ -55,6 +75,7 @@ public:
private:
int fd_;
SSL *ssl_;
uint16_t version_;
spdylay_session *session_;
bool want_write_;
bool debug_;
@ -100,7 +121,7 @@ int select_next_proto_cb(SSL* ssl,
const unsigned char *in, unsigned int inlen,
void *arg);
void setup_ssl_ctx(SSL_CTX *ssl_ctx);
void setup_ssl_ctx(SSL_CTX *ssl_ctx, void *next_proto_select_cb_arg);
int ssl_handshake(SSL *ssl, int fd);