Make spdycat and spdyd barely work
This commit is contained in:
parent
48cb017245
commit
24cab312cf
|
@ -492,7 +492,7 @@ typedef struct {
|
|||
/**
|
||||
* The error code. See :type:`nghttp2_error_code`.
|
||||
*/
|
||||
uint32_t error_code;
|
||||
nghttp2_error_code error_code;
|
||||
} nghttp2_rst_stream;
|
||||
|
||||
/**
|
||||
|
@ -575,7 +575,7 @@ typedef struct {
|
|||
/**
|
||||
* The error code. See :type:`nghttp2_error_code`.
|
||||
*/
|
||||
uint32_t error_code;
|
||||
nghttp2_error_code error_code;
|
||||
/**
|
||||
* The additional debug data
|
||||
*/
|
||||
|
@ -743,7 +743,7 @@ typedef void (*nghttp2_on_data_chunk_recv_callback)
|
|||
* :type:`nghttp2_on_data_chunk_recv_callback`.
|
||||
*/
|
||||
typedef void (*nghttp2_on_data_recv_callback)
|
||||
(nghttp2_session *session, uint8_t flags, int32_t stream_id, int32_t length,
|
||||
(nghttp2_session *session, uint16_t length, uint8_t flags, int32_t stream_id,
|
||||
void *user_data);
|
||||
|
||||
/**
|
||||
|
@ -784,7 +784,7 @@ typedef void (*nghttp2_on_frame_not_send_callback)
|
|||
* Callback function invoked after DATA frame is sent.
|
||||
*/
|
||||
typedef void (*nghttp2_on_data_send_callback)
|
||||
(nghttp2_session *session, uint8_t flags, int32_t stream_id, int32_t length,
|
||||
(nghttp2_session *session, uint16_t length, uint8_t flags, int32_t stream_id,
|
||||
void *user_data);
|
||||
|
||||
/**
|
||||
|
|
|
@ -1126,10 +1126,10 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session)
|
|||
if(session->callbacks.on_data_send_callback) {
|
||||
session->callbacks.on_data_send_callback
|
||||
(session,
|
||||
session->aob.framebuflen - NGHTTP2_FRAME_HEAD_LENGTH,
|
||||
data_frame->eof ? data_frame->hd.flags :
|
||||
(data_frame->hd.flags & (~NGHTTP2_FLAG_END_STREAM)),
|
||||
data_frame->hd.stream_id,
|
||||
session->aob.framebuflen - NGHTTP2_FRAME_HEAD_LENGTH,
|
||||
session->user_data);
|
||||
}
|
||||
if(data_frame->eof && (data_frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
|
||||
|
@ -1650,6 +1650,8 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
|
|||
if(rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
} else {
|
||||
return nghttp2_session_fail_session(session, NGHTTP2_PROTOCOL_ERROR);
|
||||
}
|
||||
}
|
||||
session->remote_settings[entry->settings_id] = entry->value;
|
||||
|
@ -1915,7 +1917,7 @@ int nghttp2_session_on_data_received(nghttp2_session *session,
|
|||
valid = 1;
|
||||
if(session->callbacks.on_data_recv_callback) {
|
||||
session->callbacks.on_data_recv_callback
|
||||
(session, flags, stream_id, length, session->user_data);
|
||||
(session, length, flags, stream_id, session->user_data);
|
||||
}
|
||||
} else if(stream->state != NGHTTP2_STREAM_CLOSING) {
|
||||
error_code = NGHTTP2_PROTOCOL_ERROR;
|
||||
|
@ -1927,7 +1929,7 @@ int nghttp2_session_on_data_received(nghttp2_session *session,
|
|||
valid = 1;
|
||||
if(session->callbacks.on_data_recv_callback) {
|
||||
session->callbacks.on_data_recv_callback
|
||||
(session, flags, stream_id, length, session->user_data);
|
||||
(session, length, flags, stream_id, session->user_data);
|
||||
}
|
||||
if(flags & NGHTTP2_FLAG_END_STREAM) {
|
||||
nghttp2_session_call_on_request_recv(session, stream_id);
|
||||
|
|
|
@ -31,6 +31,7 @@ AM_CFLAGS = -Wall
|
|||
AM_CPPFLAGS = -Wall -I$(srcdir)/../lib/includes -I$(builddir)/../lib/includes \
|
||||
@OPENSSL_CFLAGS@ @XML_CPPFLAGS@ @LIBEVENT_OPENSSL_CFLAGS@ @DEFS@
|
||||
AM_LDFLAGS = @OPENSSL_LIBS@ @XML_LIBS@ @LIBEVENT_OPENSSL_LIBS@ @SRC_LIBS@
|
||||
AM_CXXFLAGS = -std=c++11
|
||||
|
||||
LDADD = $(top_builddir)/lib/libnghttp2.la
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ const std::string SPDYD_SERVER = "spdyd nghttp2/" NGHTTP2_VERSION;
|
|||
|
||||
Config::Config(): verbose(false), daemon(false), port(0),
|
||||
on_request_recv_callback(0), data_ptr(0),
|
||||
version(0), verify_client(false), no_tls(false)
|
||||
verify_client(false), no_tls(false)
|
||||
{}
|
||||
|
||||
Request::Request(int32_t stream_id)
|
||||
|
@ -93,10 +93,9 @@ public:
|
|||
{}
|
||||
~Sessions()
|
||||
{
|
||||
for(std::set<EventHandler*>::iterator i = handlers_.begin(),
|
||||
eoi = handlers_.end(); i != eoi; ++i) {
|
||||
on_close(*this, *i);
|
||||
delete *i;
|
||||
for(auto handler : handlers_) {
|
||||
on_close(*this, handler);
|
||||
delete handler;
|
||||
}
|
||||
SSL_CTX_free(ssl_ctx_);
|
||||
}
|
||||
|
@ -175,22 +174,19 @@ void on_session_closed(EventHandler *hd, int64_t session_id)
|
|||
|
||||
SpdyEventHandler::SpdyEventHandler(const Config* config,
|
||||
int fd, SSL *ssl,
|
||||
uint16_t version,
|
||||
const nghttp2_session_callbacks *callbacks,
|
||||
int64_t session_id)
|
||||
: EventHandler(config),
|
||||
fd_(fd), ssl_(ssl), version_(version), session_id_(session_id),
|
||||
fd_(fd), ssl_(ssl), session_id_(session_id),
|
||||
io_flags_(0)
|
||||
{
|
||||
int r;
|
||||
r = nghttp2_session_server_new(&session_, version, callbacks, this);
|
||||
r = nghttp2_session_server_new(&session_, callbacks, this);
|
||||
assert(r == 0);
|
||||
nghttp2_settings_entry entry;
|
||||
entry.settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
|
||||
entry.value = NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS;
|
||||
entry.flags = NGHTTP2_ID_FLAG_SETTINGS_NONE;
|
||||
r = nghttp2_submit_settings(session_, NGHTTP2_FLAG_SETTINGS_NONE,
|
||||
&entry, 1);
|
||||
r = nghttp2_submit_settings(session_, &entry, 1);
|
||||
assert(r == 0);
|
||||
}
|
||||
|
||||
|
@ -198,9 +194,8 @@ SpdyEventHandler::~SpdyEventHandler()
|
|||
{
|
||||
on_session_closed(this, session_id_);
|
||||
nghttp2_session_del(session_);
|
||||
for(std::map<int32_t, Request*>::iterator i = id2req_.begin(),
|
||||
eoi = id2req_.end(); i != eoi; ++i) {
|
||||
delete (*i).second;
|
||||
for(auto& item : id2req_) {
|
||||
delete item.second;
|
||||
}
|
||||
if(ssl_) {
|
||||
SSL_shutdown(ssl_);
|
||||
|
@ -210,11 +205,6 @@ SpdyEventHandler::~SpdyEventHandler()
|
|||
close(fd_);
|
||||
}
|
||||
|
||||
uint16_t SpdyEventHandler::version() const
|
||||
{
|
||||
return version_;
|
||||
}
|
||||
|
||||
int SpdyEventHandler::execute(Sessions *sessions)
|
||||
{
|
||||
int r;
|
||||
|
@ -300,18 +290,17 @@ int SpdyEventHandler::submit_file_response(const std::string& status,
|
|||
std::string last_modified_str;
|
||||
const char *nv[] = {
|
||||
":status", status.c_str(),
|
||||
":version", "HTTP/1.1",
|
||||
"server", SPDYD_SERVER.c_str(),
|
||||
"content-length", content_length.c_str(),
|
||||
"cache-control", "max-age=3600",
|
||||
"date", date_str.c_str(),
|
||||
0, 0,
|
||||
0
|
||||
nullptr, nullptr,
|
||||
nullptr
|
||||
};
|
||||
if(last_modified != 0) {
|
||||
last_modified_str = util::http_date(last_modified);
|
||||
nv[12] = "last-modified";
|
||||
nv[13] = last_modified_str.c_str();
|
||||
nv[10] = "last-modified";
|
||||
nv[11] = last_modified_str.c_str();
|
||||
}
|
||||
return nghttp2_submit_response(session_, stream_id, nv, data_prd);
|
||||
}
|
||||
|
@ -323,20 +312,18 @@ int SpdyEventHandler::submit_response
|
|||
nghttp2_data_provider *data_prd)
|
||||
{
|
||||
std::string date_str = util::http_date(time(0));
|
||||
const char **nv = new const char*[8+headers.size()*2+1];
|
||||
const char **nv = new const char*[6+headers.size()*2+1];
|
||||
nv[0] = ":status";
|
||||
nv[1] = status.c_str();
|
||||
nv[2] = ":version";
|
||||
nv[3] = "HTTP/1.1";
|
||||
nv[4] = "server";
|
||||
nv[5] = SPDYD_SERVER.c_str();
|
||||
nv[6] = "date";
|
||||
nv[7] = date_str.c_str();
|
||||
nv[2] = "server";
|
||||
nv[3] = SPDYD_SERVER.c_str();
|
||||
nv[4] = "date";
|
||||
nv[5] = date_str.c_str();
|
||||
for(int i = 0; i < (int)headers.size(); ++i) {
|
||||
nv[8+i*2] = headers[i].first.c_str();
|
||||
nv[8+i*2+1] = headers[i].second.c_str();
|
||||
nv[6+i*2] = headers[i].first.c_str();
|
||||
nv[6+i*2+1] = headers[i].second.c_str();
|
||||
}
|
||||
nv[8+headers.size()*2] = 0;
|
||||
nv[6+headers.size()*2] = nullptr;
|
||||
int r = nghttp2_submit_response(session_, stream_id, nv, data_prd);
|
||||
delete [] nv;
|
||||
return r;
|
||||
|
@ -348,9 +335,8 @@ int SpdyEventHandler::submit_response(const std::string& status,
|
|||
{
|
||||
const char *nv[] = {
|
||||
":status", status.c_str(),
|
||||
":version", "HTTP/1.1",
|
||||
"server", SPDYD_SERVER.c_str(),
|
||||
0
|
||||
nullptr
|
||||
};
|
||||
return nghttp2_submit_response(session_, stream_id, nv, data_prd);
|
||||
}
|
||||
|
@ -568,32 +554,36 @@ void append_nv(Request *req, char **nv)
|
|||
} // namespace
|
||||
|
||||
namespace {
|
||||
void hd_on_ctrl_recv_callback
|
||||
(nghttp2_session *session, nghttp2_frame_type type, nghttp2_frame *frame,
|
||||
void *user_data)
|
||||
void hd_on_frame_recv_callback
|
||||
(nghttp2_session *session, nghttp2_frame *frame, void *user_data)
|
||||
{
|
||||
SpdyEventHandler *hd = (SpdyEventHandler*)user_data;
|
||||
if(hd->config()->verbose) {
|
||||
print_session_id(hd->session_id());
|
||||
on_ctrl_recv_callback(session, type, frame, user_data);
|
||||
on_frame_recv_callback(session, frame, user_data);
|
||||
}
|
||||
switch(type) {
|
||||
case NGHTTP2_SYN_STREAM: {
|
||||
int32_t stream_id = frame->syn_stream.stream_id;
|
||||
Request *req = new Request(stream_id);
|
||||
append_nv(req, frame->syn_stream.nv);
|
||||
switch(frame->hd.type) {
|
||||
case NGHTTP2_HEADERS:
|
||||
switch(frame->headers.cat) {
|
||||
case NGHTTP2_HCAT_START_STREAM: {
|
||||
int32_t stream_id = frame->hd.stream_id;
|
||||
auto req = new Request(stream_id);
|
||||
append_nv(req, frame->headers.nv);
|
||||
hd->add_stream(stream_id, req);
|
||||
break;
|
||||
}
|
||||
case NGHTTP2_HEADERS: {
|
||||
int32_t stream_id = frame->headers.stream_id;
|
||||
Request *req = hd->get_stream(stream_id);
|
||||
case NGHTTP2_HCAT_HEADERS: {
|
||||
Request *req = hd->get_stream(frame->hd.stream_id);
|
||||
append_nv(req, frame->headers.nv);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -605,14 +595,14 @@ void htdocs_on_request_recv_callback
|
|||
}
|
||||
|
||||
namespace {
|
||||
void hd_on_ctrl_send_callback
|
||||
(nghttp2_session *session, nghttp2_frame_type type, nghttp2_frame *frame,
|
||||
void hd_on_frame_send_callback
|
||||
(nghttp2_session *session, nghttp2_frame *frame,
|
||||
void *user_data)
|
||||
{
|
||||
SpdyEventHandler *hd = (SpdyEventHandler*)user_data;
|
||||
if(hd->config()->verbose) {
|
||||
print_session_id(hd->session_id());
|
||||
on_ctrl_send_callback(session, type, frame, user_data);
|
||||
on_frame_send_callback(session, frame, user_data);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
@ -628,34 +618,34 @@ void on_data_chunk_recv_callback
|
|||
|
||||
namespace {
|
||||
void hd_on_data_recv_callback
|
||||
(nghttp2_session *session, uint8_t flags, int32_t stream_id, int32_t length,
|
||||
(nghttp2_session *session, uint16_t length, uint8_t flags, int32_t stream_id,
|
||||
void *user_data)
|
||||
{
|
||||
// TODO Handle POST
|
||||
SpdyEventHandler *hd = (SpdyEventHandler*)user_data;
|
||||
if(hd->config()->verbose) {
|
||||
print_session_id(hd->session_id());
|
||||
on_data_recv_callback(session, flags, stream_id, length, user_data);
|
||||
on_data_recv_callback(session, length, flags, stream_id, user_data);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void hd_on_data_send_callback
|
||||
(nghttp2_session *session, uint8_t flags, int32_t stream_id, int32_t length,
|
||||
(nghttp2_session *session, uint16_t length, uint8_t flags, int32_t stream_id,
|
||||
void *user_data)
|
||||
{
|
||||
SpdyEventHandler *hd = (SpdyEventHandler*)user_data;
|
||||
if(hd->config()->verbose) {
|
||||
print_session_id(hd->session_id());
|
||||
on_data_send_callback(session, flags, stream_id, length, user_data);
|
||||
on_data_send_callback(session, length, flags, stream_id, user_data);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void on_stream_close_callback
|
||||
(nghttp2_session *session, int32_t stream_id, nghttp2_status_code status_code,
|
||||
(nghttp2_session *session, int32_t stream_id, nghttp2_error_code error_code,
|
||||
void *user_data)
|
||||
{
|
||||
SpdyEventHandler *hd = (SpdyEventHandler*)user_data;
|
||||
|
@ -676,15 +666,15 @@ void fill_callback(nghttp2_session_callbacks& callbacks, const Config *config)
|
|||
callbacks.send_callback = hd_send_callback;
|
||||
callbacks.recv_callback = hd_recv_callback;
|
||||
callbacks.on_stream_close_callback = on_stream_close_callback;
|
||||
callbacks.on_ctrl_recv_callback = hd_on_ctrl_recv_callback;
|
||||
callbacks.on_ctrl_send_callback = hd_on_ctrl_send_callback;
|
||||
callbacks.on_frame_recv_callback = hd_on_frame_recv_callback;
|
||||
callbacks.on_frame_send_callback = hd_on_frame_send_callback;
|
||||
callbacks.on_data_recv_callback = hd_on_data_recv_callback;
|
||||
callbacks.on_data_send_callback = hd_on_data_send_callback;
|
||||
if(config->verbose) {
|
||||
callbacks.on_invalid_ctrl_recv_callback = on_invalid_ctrl_recv_callback;
|
||||
callbacks.on_ctrl_recv_parse_error_callback =
|
||||
on_ctrl_recv_parse_error_callback;
|
||||
callbacks.on_unknown_ctrl_recv_callback = on_unknown_ctrl_recv_callback;
|
||||
callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
|
||||
callbacks.on_frame_recv_parse_error_callback =
|
||||
on_frame_recv_parse_error_callback;
|
||||
callbacks.on_unknown_frame_recv_callback = on_unknown_frame_recv_callback;
|
||||
}
|
||||
callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback;
|
||||
callbacks.on_request_recv_callback = config->on_request_recv_callback;
|
||||
|
@ -696,7 +686,7 @@ public:
|
|||
SSLAcceptEventHandler(const Config *config,
|
||||
int fd, SSL *ssl, int64_t session_id)
|
||||
: EventHandler(config),
|
||||
fd_(fd), ssl_(ssl), version_(0), fail_(false), finish_(false),
|
||||
fd_(fd), ssl_(ssl), fail_(false), finish_(false),
|
||||
io_flags_(WANT_READ),
|
||||
session_id_(session_id)
|
||||
{}
|
||||
|
@ -725,17 +715,11 @@ public:
|
|||
if(config()->verbose) {
|
||||
std::cout << "The negotiated next protocol: " << proto << std::endl;
|
||||
}
|
||||
version_ = nghttp2_npn_get_version(next_proto, next_proto_len);
|
||||
if(config()->version != 0) {
|
||||
if(config()->version != version_) {
|
||||
version_ = 0;
|
||||
std::cerr << "The negotiated next protocol is not supported."
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
if(version_) {
|
||||
if(proto == "HTTP-draft-04/2.0") {
|
||||
add_next_handler(sessions);
|
||||
} else {
|
||||
std::cerr << "The negotiated next protocol is not supported."
|
||||
<< std::endl;
|
||||
fail_ = true;
|
||||
}
|
||||
} else {
|
||||
|
@ -779,7 +763,7 @@ private:
|
|||
nghttp2_session_callbacks callbacks;
|
||||
fill_callback(callbacks, config());
|
||||
SpdyEventHandler *hd = new SpdyEventHandler(config(),
|
||||
fd_, ssl_, version_, &callbacks,
|
||||
fd_, ssl_, &callbacks,
|
||||
session_id_);
|
||||
if(sessions->mod_poll(hd) == -1) {
|
||||
// fd_, ssl_ are freed by ~SpdyEventHandler()
|
||||
|
@ -791,7 +775,6 @@ private:
|
|||
|
||||
int fd_;
|
||||
SSL *ssl_;
|
||||
uint16_t version_;
|
||||
bool fail_, finish_;
|
||||
uint8_t io_flags_;
|
||||
int64_t session_id_;
|
||||
|
@ -842,9 +825,7 @@ private:
|
|||
if(config()->no_tls) {
|
||||
nghttp2_session_callbacks callbacks;
|
||||
fill_callback(callbacks, config());
|
||||
SpdyEventHandler *hd = new SpdyEventHandler(config(),
|
||||
cfd, 0,
|
||||
config()->version, &callbacks,
|
||||
auto hd = new SpdyEventHandler(config(), cfd, nullptr, &callbacks,
|
||||
session_id);
|
||||
if(sessions->add_poll(hd) == -1) {
|
||||
delete hd;
|
||||
|
@ -857,8 +838,7 @@ private:
|
|||
close(cfd);
|
||||
return;
|
||||
}
|
||||
SSLAcceptEventHandler *hd = new SSLAcceptEventHandler(config(), cfd, ssl,
|
||||
session_id);
|
||||
auto hd = new SSLAcceptEventHandler(config(), cfd, ssl, session_id);
|
||||
if(sessions->add_poll(hd) == -1) {
|
||||
delete hd;
|
||||
SSL_free(ssl);
|
||||
|
@ -946,7 +926,7 @@ int SpdyServer::run()
|
|||
{
|
||||
SSL_CTX *ssl_ctx = 0;
|
||||
std::pair<unsigned char*, size_t> next_proto;
|
||||
unsigned char proto_list[14];
|
||||
unsigned char proto_list[255];
|
||||
if(!config_->no_tls) {
|
||||
ssl_ctx = SSL_CTX_new(SSLv23_server_method());
|
||||
if(!ssl_ctx) {
|
||||
|
@ -978,25 +958,11 @@ int SpdyServer::run()
|
|||
verify_callback);
|
||||
}
|
||||
|
||||
// We speaks "spdy/2" and "spdy/3".
|
||||
proto_list[0] = 6;
|
||||
memcpy(&proto_list[1], "spdy/3", 6);
|
||||
proto_list[7] = 6;
|
||||
memcpy(&proto_list[8], "spdy/2", 6);
|
||||
proto_list[0] = 17;
|
||||
memcpy(&proto_list[1], "HTTP-draft-04/2.0", 17);
|
||||
next_proto.first = proto_list;
|
||||
next_proto.second = 18;
|
||||
|
||||
switch(config_->version) {
|
||||
case NGHTTP2_PROTO_SPDY3:
|
||||
next_proto.first = proto_list;
|
||||
next_proto.second = 7;
|
||||
break;
|
||||
case NGHTTP2_PROTO_SPDY2:
|
||||
next_proto.first = proto_list+7;
|
||||
next_proto.second = 7;
|
||||
break;
|
||||
default:
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -1011,8 +977,7 @@ int SpdyServer::run()
|
|||
if(sfd_[i] == -1) {
|
||||
continue;
|
||||
}
|
||||
ListenEventHandler *listen_hd = new ListenEventHandler(config_,
|
||||
sfd_[i],
|
||||
auto listen_hd = new ListenEventHandler(config_, sfd_[i],
|
||||
&session_id_seed);
|
||||
if(sessions.add_poll(listen_hd) == -1) {
|
||||
std::cerr << ipv << ": Adding listening socket to poll failed."
|
||||
|
@ -1034,8 +999,7 @@ int SpdyServer::run()
|
|||
perror("EventPoll");
|
||||
} else {
|
||||
for(int i = 0; i < n; ++i) {
|
||||
EventHandler *hd = reinterpret_cast<EventHandler*>
|
||||
(sessions.get_user_data(i));
|
||||
auto hd = reinterpret_cast<EventHandler*>(sessions.get_user_data(i));
|
||||
int events = sessions.get_events(i);
|
||||
int r = 0;
|
||||
if(hd->mark_del()) {
|
||||
|
@ -1059,10 +1023,9 @@ int SpdyServer::run()
|
|||
del_list.push_back(hd);
|
||||
}
|
||||
}
|
||||
for(std::vector<EventHandler*>::iterator i = del_list.begin(),
|
||||
eoi = del_list.end(); i != eoi; ++i) {
|
||||
on_close(sessions, *i);
|
||||
sessions.remove_handler(*i);
|
||||
for(auto handler : del_list) {
|
||||
on_close(sessions, handler);
|
||||
sessions.remove_handler(handler);
|
||||
}
|
||||
del_list.clear();
|
||||
}
|
||||
|
|
|
@ -52,7 +52,6 @@ struct Config {
|
|||
std::string cert_file;
|
||||
nghttp2_on_request_recv_callback on_request_recv_callback;
|
||||
void *data_ptr;
|
||||
uint16_t version;
|
||||
bool verify_client;
|
||||
bool no_tls;
|
||||
Config();
|
||||
|
@ -88,7 +87,7 @@ private:
|
|||
|
||||
struct Request {
|
||||
int32_t stream_id;
|
||||
std::vector<std::pair<std::string, std::string> > headers;
|
||||
std::vector<std::pair<std::string, std::string>> headers;
|
||||
int file;
|
||||
std::pair<std::string, size_t> response_body;
|
||||
Request(int32_t stream_id);
|
||||
|
@ -98,7 +97,7 @@ struct Request {
|
|||
class SpdyEventHandler : public EventHandler {
|
||||
public:
|
||||
SpdyEventHandler(const Config* config,
|
||||
int fd, SSL *ssl, uint16_t version,
|
||||
int fd, SSL *ssl,
|
||||
const nghttp2_session_callbacks *callbacks,
|
||||
int64_t session_id);
|
||||
virtual ~SpdyEventHandler();
|
||||
|
@ -108,8 +107,6 @@ 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);
|
||||
|
@ -129,7 +126,7 @@ public:
|
|||
int submit_response
|
||||
(const std::string& status,
|
||||
int32_t stream_id,
|
||||
const std::vector<std::pair<std::string, std::string> >& headers,
|
||||
const std::vector<std::pair<std::string, std::string>>& headers,
|
||||
nghttp2_data_provider *data_prd);
|
||||
|
||||
void add_stream(int32_t stream_id, Request *req);
|
||||
|
@ -140,7 +137,6 @@ private:
|
|||
nghttp2_session *session_;
|
||||
int fd_;
|
||||
SSL* ssl_;
|
||||
uint16_t version_;
|
||||
int64_t session_id_;
|
||||
uint8_t io_flags_;
|
||||
std::map<int32_t, Request*> id2req_;
|
||||
|
|
|
@ -50,13 +50,12 @@ namespace nghttp2 {
|
|||
|
||||
bool ssl_debug = false;
|
||||
|
||||
Spdylay::Spdylay(int fd, SSL *ssl, uint16_t version,
|
||||
Spdylay::Spdylay(int fd, SSL *ssl,
|
||||
const nghttp2_session_callbacks *callbacks,
|
||||
void *user_data)
|
||||
: fd_(fd), ssl_(ssl), version_(version), user_data_(user_data),
|
||||
io_flags_(0)
|
||||
: fd_(fd), ssl_(ssl), user_data_(user_data), io_flags_(0)
|
||||
{
|
||||
int r = nghttp2_session_client_new(&session_, version_, callbacks, this);
|
||||
int r = nghttp2_session_client_new(&session_, callbacks, this);
|
||||
assert(r == 0);
|
||||
}
|
||||
|
||||
|
@ -152,17 +151,16 @@ int Spdylay::submit_request(const std::string& scheme,
|
|||
{
|
||||
POS_METHOD = 0,
|
||||
POS_PATH,
|
||||
POS_VERSION,
|
||||
POS_SCHEME,
|
||||
POS_HOST,
|
||||
POS_ACCEPT,
|
||||
POS_ACCEPT_ENCODING,
|
||||
POS_USERAGENT
|
||||
};
|
||||
|
||||
const char *static_nv[] = {
|
||||
":method", data_prd ? "POST" : "GET",
|
||||
":path", path.c_str(),
|
||||
":version", "HTTP/1.1",
|
||||
":scheme", scheme.c_str(),
|
||||
":host", hostport.c_str(),
|
||||
"accept", "*/*",
|
||||
|
@ -181,8 +179,8 @@ int Spdylay::submit_request(const std::string& scheme,
|
|||
|
||||
memcpy(nv, static_nv, hardcoded_entry_count * sizeof(*static_nv));
|
||||
|
||||
std::map<std::string,std::string>::const_iterator i = headers.begin();
|
||||
std::map<std::string,std::string>::const_iterator end = headers.end();
|
||||
auto i = std::begin(headers);
|
||||
auto end = std::end(headers);
|
||||
|
||||
int pos = hardcoded_entry_count;
|
||||
|
||||
|
@ -213,7 +211,7 @@ int Spdylay::submit_request(const std::string& scheme,
|
|||
}
|
||||
++i;
|
||||
}
|
||||
nv[pos] = NULL;
|
||||
nv[pos] = nullptr;
|
||||
|
||||
int r = nghttp2_submit_request(session_, pri, nv, data_prd,
|
||||
stream_user_data);
|
||||
|
@ -223,9 +221,9 @@ int Spdylay::submit_request(const std::string& scheme,
|
|||
return r;
|
||||
}
|
||||
|
||||
int Spdylay::submit_settings(int flags, nghttp2_settings_entry *iv, size_t niv)
|
||||
int Spdylay::submit_settings(nghttp2_settings_entry *iv, size_t niv)
|
||||
{
|
||||
return nghttp2_submit_settings(session_, flags, iv, niv);
|
||||
return nghttp2_submit_settings(session_, iv, niv);
|
||||
}
|
||||
|
||||
bool Spdylay::would_block()
|
||||
|
@ -456,15 +454,41 @@ ssize_t recv_callback(nghttp2_session *session,
|
|||
}
|
||||
|
||||
namespace {
|
||||
const char *ctrl_names[] = {
|
||||
"SYN_STREAM",
|
||||
"SYN_REPLY",
|
||||
const char* strstatus(nghttp2_error_code error_code)
|
||||
{
|
||||
switch(error_code) {
|
||||
case NGHTTP2_NO_ERROR:
|
||||
return "NO_ERROR";
|
||||
case NGHTTP2_PROTOCOL_ERROR:
|
||||
return "PROTOCOL_ERROR";
|
||||
case NGHTTP2_INTERNAL_ERROR:
|
||||
return "INTERNAL_ERROR";
|
||||
case NGHTTP2_FLOW_CONTROL_ERROR:
|
||||
return "FLOW_CONTROL_ERROR";
|
||||
case NGHTTP2_STREAM_CLOSED:
|
||||
return "STREAM_CLOSED";
|
||||
case NGHTTP2_FRAME_TOO_LARGE:
|
||||
return "FRAME_TOO_LARGE";
|
||||
case NGHTTP2_REFUSED_STREAM:
|
||||
return "REFUSED_STREAM";
|
||||
case NGHTTP2_CANCEL:
|
||||
return "CANCEL";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
const char *frame_names[] = {
|
||||
"DATA",
|
||||
"HEADERS",
|
||||
"PRIORITY",
|
||||
"RST_STREAM",
|
||||
"SETTINGS",
|
||||
"NOOP",
|
||||
"PUSH_PROMISE",
|
||||
"PING",
|
||||
"GOAWAY",
|
||||
"HEADERS",
|
||||
"WINDOW_UPDATE"
|
||||
};
|
||||
} // namespace
|
||||
|
@ -521,10 +545,57 @@ void print_timer()
|
|||
}
|
||||
|
||||
namespace {
|
||||
void print_ctrl_hd(const nghttp2_ctrl_hd& hd)
|
||||
void print_frame_hd(const nghttp2_frame_hd& hd)
|
||||
{
|
||||
printf("<version=%u, flags=%u, length=%d>\n",
|
||||
hd.version, hd.flags, hd.length);
|
||||
printf("<length=%d, flags=%u, stream_id=%d>\n",
|
||||
hd.length, hd.flags, hd.stream_id);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void print_flags(const nghttp2_frame_hd& hd)
|
||||
{
|
||||
std::string s;
|
||||
switch(hd.type) {
|
||||
case NGHTTP2_DATA:
|
||||
if(hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||
s += "END_STREAM";
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_HEADERS:
|
||||
if(hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||
s += "END_STREAM";
|
||||
}
|
||||
if(hd.flags & NGHTTP2_FLAG_END_HEADERS) {
|
||||
if(!s.empty()) {
|
||||
s += " | ";
|
||||
}
|
||||
s += "END_HEADERS";
|
||||
}
|
||||
if(hd.flags & NGHTTP2_FLAG_PRIORITY) {
|
||||
if(!s.empty()) {
|
||||
s += " | ";
|
||||
}
|
||||
s += "PRIORITY";
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_PUSH_PROMISE:
|
||||
if(hd.flags & NGHTTP2_FLAG_END_PUSH_PROMISE) {
|
||||
s += "END_PUSH_PROMISE";
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_PING:
|
||||
if(hd.flags & NGHTTP2_FLAG_PONG) {
|
||||
s += "PONG";
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_WINDOW_UPDATE:
|
||||
if(hd.flags & NGHTTP2_FLAG_END_FLOW_CONTROL) {
|
||||
s += "END_FLOW_CONTROL";
|
||||
}
|
||||
break;
|
||||
}
|
||||
printf("; %s\n", s.c_str());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -541,60 +612,69 @@ const char* frame_name_ansi_esc(print_type ptype)
|
|||
} // namespace
|
||||
|
||||
namespace {
|
||||
void print_frame(print_type ptype, nghttp2_frame_type type,
|
||||
nghttp2_frame *frame)
|
||||
void print_frame(print_type ptype, nghttp2_frame *frame)
|
||||
{
|
||||
printf("%s%s%s frame ",
|
||||
frame_name_ansi_esc(ptype),
|
||||
ctrl_names[type-1],
|
||||
frame_names[frame->hd.type],
|
||||
ansi_escend());
|
||||
print_ctrl_hd(frame->syn_stream.hd);
|
||||
switch(type) {
|
||||
case NGHTTP2_SYN_STREAM:
|
||||
print_frame_hd(frame->hd);
|
||||
if(frame->hd.flags) {
|
||||
print_frame_attr_indent();
|
||||
printf("(stream_id=%d, assoc_stream_id=%d, pri=%u)\n",
|
||||
frame->syn_stream.stream_id, frame->syn_stream.assoc_stream_id,
|
||||
frame->syn_stream.pri);
|
||||
print_nv(frame->syn_stream.nv);
|
||||
print_flags(frame->hd);
|
||||
}
|
||||
switch(frame->hd.type) {
|
||||
case NGHTTP2_HEADERS:
|
||||
print_frame_attr_indent();
|
||||
printf("(pri=%d)\n", frame->headers.pri);
|
||||
switch(frame->headers.cat) {
|
||||
case NGHTTP2_HCAT_START_STREAM:
|
||||
print_frame_attr_indent();
|
||||
printf("; Open new stream\n");
|
||||
break;
|
||||
case NGHTTP2_SYN_REPLY:
|
||||
case NGHTTP2_HCAT_REPLY:
|
||||
print_frame_attr_indent();
|
||||
printf("(stream_id=%d)\n", frame->syn_reply.stream_id);
|
||||
print_nv(frame->syn_reply.nv);
|
||||
printf("; First response header\n");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
print_nv(frame->headers.nv);
|
||||
break;
|
||||
case NGHTTP2_RST_STREAM:
|
||||
print_frame_attr_indent();
|
||||
printf("(stream_id=%d, status_code=%u)\n",
|
||||
frame->rst_stream.stream_id, frame->rst_stream.status_code);
|
||||
printf("(error_code=%s(%u))\n",
|
||||
strstatus(frame->rst_stream.error_code),
|
||||
frame->rst_stream.error_code);
|
||||
break;
|
||||
case NGHTTP2_SETTINGS:
|
||||
print_frame_attr_indent();
|
||||
printf("(niv=%lu)\n", static_cast<unsigned long>(frame->settings.niv));
|
||||
for(size_t i = 0; i < frame->settings.niv; ++i) {
|
||||
print_frame_attr_indent();
|
||||
printf("[%d(%u):%u]\n",
|
||||
printf("[%d:%u]\n",
|
||||
frame->settings.iv[i].settings_id,
|
||||
frame->settings.iv[i].flags, frame->settings.iv[i].value);
|
||||
frame->settings.iv[i].value);
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_PING:
|
||||
print_frame_attr_indent();
|
||||
printf("(unique_id=%d)\n", frame->ping.unique_id);
|
||||
printf("(opaque_data=%s)\n",
|
||||
util::format_hex(frame->ping.opaque_data, 8).c_str());
|
||||
break;
|
||||
case NGHTTP2_GOAWAY:
|
||||
print_frame_attr_indent();
|
||||
printf("(last_good_stream_id=%d)\n", frame->goaway.last_good_stream_id);
|
||||
break;
|
||||
case NGHTTP2_HEADERS:
|
||||
print_frame_attr_indent();
|
||||
printf("(stream_id=%d)\n", frame->headers.stream_id);
|
||||
print_nv(frame->headers.nv);
|
||||
printf("(last_stream_id=%d, error_code=%s(%u), opaque_data=%s)\n",
|
||||
frame->goaway.last_stream_id,
|
||||
strstatus(frame->goaway.error_code),
|
||||
frame->goaway.error_code,
|
||||
util::format_hex(frame->goaway.opaque_data,
|
||||
frame->goaway.opaque_data_len).c_str());
|
||||
break;
|
||||
case NGHTTP2_WINDOW_UPDATE:
|
||||
print_frame_attr_indent();
|
||||
printf("(stream_id=%d, delta_window_size=%d)\n",
|
||||
frame->window_update.stream_id,
|
||||
frame->window_update.delta_window_size);
|
||||
printf("(window_size_increment=%d)\n",
|
||||
frame->window_update.window_size_increment);
|
||||
break;
|
||||
default:
|
||||
printf("\n");
|
||||
|
@ -603,57 +683,22 @@ void print_frame(print_type ptype, nghttp2_frame_type type,
|
|||
}
|
||||
} // namespace
|
||||
|
||||
void on_ctrl_recv_callback
|
||||
(nghttp2_session *session, nghttp2_frame_type type, nghttp2_frame *frame,
|
||||
void *user_data)
|
||||
void on_frame_recv_callback
|
||||
(nghttp2_session *session, nghttp2_frame *frame, void *user_data)
|
||||
{
|
||||
print_timer();
|
||||
printf(" recv ");
|
||||
print_frame(PRINT_RECV, type, frame);
|
||||
print_frame(PRINT_RECV, frame);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
namespace {
|
||||
const char* strstatus(uint32_t status_code)
|
||||
{
|
||||
switch(status_code) {
|
||||
case NGHTTP2_OK:
|
||||
return "OK";
|
||||
case NGHTTP2_PROTOCOL_ERROR:
|
||||
return "PROTOCOL_ERROR";
|
||||
case NGHTTP2_INVALID_STREAM:
|
||||
return "INVALID_STREAM";
|
||||
case NGHTTP2_REFUSED_STREAM:
|
||||
return "REFUSED_STREAM";
|
||||
case NGHTTP2_UNSUPPORTED_VERSION:
|
||||
return "UNSUPPORTED_VERSION";
|
||||
case NGHTTP2_CANCEL:
|
||||
return "CANCEL";
|
||||
case NGHTTP2_INTERNAL_ERROR:
|
||||
return "INTERNAL_ERROR";
|
||||
case NGHTTP2_FLOW_CONTROL_ERROR:
|
||||
return "FLOW_CONTROL_ERROR";
|
||||
case NGHTTP2_STREAM_IN_USE:
|
||||
return "STREAM_IN_USE";
|
||||
case NGHTTP2_STREAM_ALREADY_CLOSED:
|
||||
return "STREAM_ALREADY_CLOSED";
|
||||
case NGHTTP2_INVALID_CREDENTIALS:
|
||||
return "INVALID_CREDENTIALS";
|
||||
case NGHTTP2_FRAME_TOO_LARGE:
|
||||
return "FRAME_TOO_LARGE";
|
||||
default:
|
||||
return "Unknown status code";
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void on_invalid_ctrl_recv_callback
|
||||
(nghttp2_session *session, nghttp2_frame_type type, nghttp2_frame *frame,
|
||||
uint32_t status_code, void *user_data)
|
||||
void on_invalid_frame_recv_callback
|
||||
(nghttp2_session *session, nghttp2_frame *frame,
|
||||
nghttp2_error_code error_code, void *user_data)
|
||||
{
|
||||
print_timer();
|
||||
printf(" [INVALID; status=%s] recv ", strstatus(status_code));
|
||||
print_frame(PRINT_RECV, type, frame);
|
||||
printf(" [INVALID; status=%s] recv ", strstatus(error_code));
|
||||
print_frame(PRINT_RECV, frame);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
|
@ -670,7 +715,7 @@ void dump_header(const uint8_t *head, size_t headlen)
|
|||
}
|
||||
} // namespace
|
||||
|
||||
void on_ctrl_recv_parse_error_callback(nghttp2_session *session,
|
||||
void on_frame_recv_parse_error_callback(nghttp2_session *session,
|
||||
nghttp2_frame_type type,
|
||||
const uint8_t *head,
|
||||
size_t headlen,
|
||||
|
@ -681,7 +726,7 @@ void on_ctrl_recv_parse_error_callback(nghttp2_session *session,
|
|||
print_timer();
|
||||
printf(" [PARSE_ERROR] recv %s%s%s frame\n",
|
||||
frame_name_ansi_esc(PRINT_RECV),
|
||||
ctrl_names[type-1],
|
||||
frame_names[type],
|
||||
ansi_escend());
|
||||
print_frame_attr_indent();
|
||||
printf("Error: %s\n", nghttp2_strerror(error_code));
|
||||
|
@ -689,7 +734,7 @@ void on_ctrl_recv_parse_error_callback(nghttp2_session *session,
|
|||
fflush(stdout);
|
||||
}
|
||||
|
||||
void on_unknown_ctrl_recv_callback(nghttp2_session *session,
|
||||
void on_unknown_frame_recv_callback(nghttp2_session *session,
|
||||
const uint8_t *head,
|
||||
size_t headlen,
|
||||
const uint8_t *payload,
|
||||
|
@ -702,43 +747,43 @@ void on_unknown_ctrl_recv_callback(nghttp2_session *session,
|
|||
fflush(stdout);
|
||||
}
|
||||
|
||||
void on_ctrl_send_callback
|
||||
(nghttp2_session *session, nghttp2_frame_type type, nghttp2_frame *frame,
|
||||
void *user_data)
|
||||
void on_frame_send_callback
|
||||
(nghttp2_session *session, nghttp2_frame *frame, void *user_data)
|
||||
{
|
||||
print_timer();
|
||||
printf(" send ");
|
||||
print_frame(PRINT_SEND, type, frame);
|
||||
print_frame(PRINT_SEND, frame);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
namespace {
|
||||
void print_data_frame(print_type ptype, uint8_t flags, int32_t stream_id,
|
||||
int32_t length)
|
||||
void print_data_frame(print_type ptype, uint16_t length, uint8_t flags,
|
||||
int32_t stream_id)
|
||||
{
|
||||
printf("%sDATA%s frame (stream_id=%d, flags=%d, length=%d)\n",
|
||||
printf("%sDATA%s frame (length=%d, flags=%d, stream_id=%d)\n",
|
||||
frame_name_ansi_esc(ptype), ansi_escend(),
|
||||
stream_id, flags, length);
|
||||
length, flags, stream_id);
|
||||
print_frame_attr_indent();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void on_data_recv_callback
|
||||
(nghttp2_session *session, uint8_t flags, int32_t stream_id, int32_t length,
|
||||
(nghttp2_session *session, uint16_t length, uint8_t flags, int32_t stream_id,
|
||||
void *user_data)
|
||||
{
|
||||
print_timer();
|
||||
printf(" recv ");
|
||||
print_data_frame(PRINT_RECV, flags, stream_id, length);
|
||||
print_data_frame(PRINT_RECV, length, flags, stream_id);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void on_data_send_callback
|
||||
(nghttp2_session *session, uint8_t flags, int32_t stream_id, int32_t length,
|
||||
(nghttp2_session *session, uint16_t length, uint8_t flags, int32_t stream_id,
|
||||
void *user_data)
|
||||
{
|
||||
print_timer();
|
||||
printf(" send ");
|
||||
print_data_frame(PRINT_SEND, flags, stream_id, length);
|
||||
print_data_frame(PRINT_SEND, length, flags, stream_id);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
|
@ -770,18 +815,10 @@ int select_next_proto_cb(SSL* ssl,
|
|||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
std::string& next_proto = *(std::string*)arg;
|
||||
if(next_proto.empty()) {
|
||||
if(nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) {
|
||||
std::cerr << "Server did not advertise spdy/2 or spdy/3 protocol."
|
||||
std::cerr << "Server did not advertise HTTP/2.0 protocol."
|
||||
<< std::endl;
|
||||
abort();
|
||||
} else {
|
||||
next_proto.assign(&(*out)[0], &(*out)[*outlen]);
|
||||
}
|
||||
} else {
|
||||
*out = (unsigned char*)(next_proto.c_str());
|
||||
*outlen = next_proto.size();
|
||||
}
|
||||
if(ssl_debug) {
|
||||
std::cout << " NPN selected the protocol: "
|
||||
|
|
|
@ -43,7 +43,7 @@ extern bool ssl_debug;
|
|||
|
||||
class Spdylay {
|
||||
public:
|
||||
Spdylay(int fd, SSL *ssl, uint16_t version,
|
||||
Spdylay(int fd, SSL *ssl,
|
||||
const nghttp2_session_callbacks *callbacks,
|
||||
void *user_data);
|
||||
~Spdylay();
|
||||
|
@ -62,13 +62,12 @@ public:
|
|||
const nghttp2_data_provider *data_prd,
|
||||
int64_t data_length,
|
||||
void *stream_user_data);
|
||||
int submit_settings(int flags, nghttp2_settings_entry *iv, size_t niv);
|
||||
int submit_settings(nghttp2_settings_entry *iv, size_t niv);
|
||||
bool would_block();
|
||||
void* user_data();
|
||||
private:
|
||||
int fd_;
|
||||
SSL *ssl_;
|
||||
uint16_t version_;
|
||||
nghttp2_session *session_;
|
||||
void *user_data_;
|
||||
uint8_t io_flags_;
|
||||
|
@ -94,15 +93,14 @@ ssize_t recv_callback(nghttp2_session *session,
|
|||
|
||||
void print_nv(char **nv);
|
||||
|
||||
void on_ctrl_recv_callback
|
||||
(nghttp2_session *session, nghttp2_frame_type type, nghttp2_frame *frame,
|
||||
void *user_data);
|
||||
void on_frame_recv_callback
|
||||
(nghttp2_session *session, nghttp2_frame *frame, void *user_data);
|
||||
|
||||
void on_invalid_ctrl_recv_callback
|
||||
(nghttp2_session *session, nghttp2_frame_type type, nghttp2_frame *frame,
|
||||
uint32_t status_code, void *user_data);
|
||||
void on_invalid_frame_recv_callback
|
||||
(nghttp2_session *session, nghttp2_frame *frame,
|
||||
nghttp2_error_code error_code, void *user_data);
|
||||
|
||||
void on_ctrl_recv_parse_error_callback(nghttp2_session *session,
|
||||
void on_frame_recv_parse_error_callback(nghttp2_session *session,
|
||||
nghttp2_frame_type type,
|
||||
const uint8_t *head,
|
||||
size_t headlen,
|
||||
|
@ -110,23 +108,22 @@ void on_ctrl_recv_parse_error_callback(nghttp2_session *session,
|
|||
size_t payloadlen,
|
||||
int error_code, void *user_data);
|
||||
|
||||
void on_unknown_ctrl_recv_callback(nghttp2_session *session,
|
||||
void on_unknown_frame_recv_callback(nghttp2_session *session,
|
||||
const uint8_t *head,
|
||||
size_t headlen,
|
||||
const uint8_t *payload,
|
||||
size_t payloadlen,
|
||||
void *user_data);
|
||||
|
||||
void on_ctrl_send_callback
|
||||
(nghttp2_session *session, nghttp2_frame_type type, nghttp2_frame *frame,
|
||||
void *user_data);
|
||||
void on_frame_send_callback
|
||||
(nghttp2_session *session, nghttp2_frame *frame, void *user_data);
|
||||
|
||||
void on_data_recv_callback
|
||||
(nghttp2_session *session, uint8_t flags, int32_t stream_id, int32_t length,
|
||||
(nghttp2_session *session, uint16_t length, uint8_t flags, int32_t stream_id,
|
||||
void *user_data);
|
||||
|
||||
void on_data_send_callback
|
||||
(nghttp2_session *session, uint8_t flags, int32_t stream_id, int32_t length,
|
||||
(nghttp2_session *session, uint16_t length, uint8_t flags, int32_t stream_id,
|
||||
void *user_data);
|
||||
|
||||
void ctl_poll(pollfd *pollfd, Spdylay *sc);
|
||||
|
|
137
src/spdycat.cc
137
src/spdycat.cc
|
@ -75,7 +75,6 @@ struct Config {
|
|||
bool stat;
|
||||
bool no_tls;
|
||||
int multiply;
|
||||
int spdy_version;
|
||||
// milliseconds
|
||||
int timeout;
|
||||
std::string certfile;
|
||||
|
@ -85,7 +84,7 @@ struct Config {
|
|||
std::string datafile;
|
||||
Config():null_out(false), remote_name(false), verbose(false),
|
||||
get_assets(false), stat(false), no_tls(false), multiply(1),
|
||||
spdy_version(-1), timeout(-1), window_bits(-1)
|
||||
timeout(-1), window_bits(-1)
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -447,46 +446,41 @@ void on_data_chunk_recv_callback
|
|||
}
|
||||
}
|
||||
|
||||
void check_stream_id(nghttp2_session *session,
|
||||
nghttp2_frame_type type, nghttp2_frame *frame,
|
||||
void check_stream_id(nghttp2_session *session, nghttp2_frame *frame,
|
||||
void *user_data)
|
||||
{
|
||||
SpdySession *spdySession = get_session(user_data);
|
||||
int32_t stream_id = frame->syn_stream.stream_id;
|
||||
int32_t stream_id = frame->hd.stream_id;
|
||||
Request *req = (Request*)nghttp2_session_get_stream_user_data(session,
|
||||
stream_id);
|
||||
spdySession->streams[stream_id] = req;
|
||||
req->record_syn_stream_time();
|
||||
}
|
||||
|
||||
void on_ctrl_send_callback2
|
||||
(nghttp2_session *session, nghttp2_frame_type type, nghttp2_frame *frame,
|
||||
void *user_data)
|
||||
void on_frame_send_callback2
|
||||
(nghttp2_session *session, nghttp2_frame *frame, void *user_data)
|
||||
{
|
||||
if(type == NGHTTP2_SYN_STREAM) {
|
||||
check_stream_id(session, type, frame, user_data);
|
||||
if(frame->hd.type == NGHTTP2_HEADERS &&
|
||||
frame->headers.cat == NGHTTP2_HCAT_START_STREAM) {
|
||||
check_stream_id(session, frame, user_data);
|
||||
}
|
||||
if(config.verbose) {
|
||||
on_ctrl_send_callback(session, type, frame, user_data);
|
||||
on_frame_send_callback(session, frame, user_data);
|
||||
}
|
||||
}
|
||||
|
||||
void check_response_header
|
||||
(nghttp2_session *session, nghttp2_frame_type type, nghttp2_frame *frame,
|
||||
void *user_data)
|
||||
(nghttp2_session *session, nghttp2_frame *frame, void *user_data)
|
||||
{
|
||||
char **nv;
|
||||
int32_t stream_id;
|
||||
if(type == NGHTTP2_SYN_REPLY) {
|
||||
nv = frame->syn_reply.nv;
|
||||
stream_id = frame->syn_reply.stream_id;
|
||||
} else if(type == NGHTTP2_HEADERS) {
|
||||
if(frame->hd.type == NGHTTP2_HEADERS) {
|
||||
nv = frame->headers.nv;
|
||||
stream_id = frame->headers.stream_id;
|
||||
stream_id = frame->hd.stream_id;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
Request *req = (Request*)nghttp2_session_get_stream_user_data(session,
|
||||
auto req = (Request*)nghttp2_session_get_stream_user_data(session,
|
||||
stream_id);
|
||||
if(!req) {
|
||||
// Server-pushed stream does not have stream user data
|
||||
|
@ -512,35 +506,34 @@ void check_response_header
|
|||
}
|
||||
}
|
||||
|
||||
void on_ctrl_recv_callback2
|
||||
(nghttp2_session *session, nghttp2_frame_type type, nghttp2_frame *frame,
|
||||
void *user_data)
|
||||
void on_frame_recv_callback2
|
||||
(nghttp2_session *session, nghttp2_frame *frame, void *user_data)
|
||||
{
|
||||
if(type == NGHTTP2_SYN_REPLY) {
|
||||
Request *req = (Request*)nghttp2_session_get_stream_user_data
|
||||
(session, frame->syn_reply.stream_id);
|
||||
if(frame->hd.type == NGHTTP2_HEADERS &&
|
||||
frame->headers.cat == NGHTTP2_HCAT_REPLY) {
|
||||
auto req = (Request*)nghttp2_session_get_stream_user_data
|
||||
(session, frame->hd.stream_id);
|
||||
assert(req);
|
||||
req->record_syn_reply_time();
|
||||
}
|
||||
check_response_header(session, type, frame, user_data);
|
||||
check_response_header(session, frame, user_data);
|
||||
if(config.verbose) {
|
||||
on_ctrl_recv_callback(session, type, frame, user_data);
|
||||
on_frame_recv_callback(session, frame, user_data);
|
||||
}
|
||||
}
|
||||
|
||||
void on_stream_close_callback
|
||||
(nghttp2_session *session, int32_t stream_id, nghttp2_status_code status_code,
|
||||
(nghttp2_session *session, int32_t stream_id, nghttp2_error_code error_code,
|
||||
void *user_data)
|
||||
{
|
||||
SpdySession *spdySession = get_session(user_data);
|
||||
std::map<int32_t, Request*>::iterator itr =
|
||||
spdySession->streams.find(stream_id);
|
||||
auto itr = spdySession->streams.find(stream_id);
|
||||
if(itr != spdySession->streams.end()) {
|
||||
update_html_parser(spdySession, (*itr).second, 0, 0, 1);
|
||||
(*itr).second->record_complete_time();
|
||||
++spdySession->complete;
|
||||
if(spdySession->all_requests_processed()) {
|
||||
nghttp2_submit_goaway(session, NGHTTP2_GOAWAY_OK);
|
||||
nghttp2_submit_goaway(session, NGHTTP2_NO_ERROR, NULL, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -549,7 +542,7 @@ void print_stats(const SpdySession& spdySession)
|
|||
{
|
||||
std::cout << "***** Statistics *****" << std::endl;
|
||||
for(size_t i = 0; i < spdySession.reqvec.size(); ++i) {
|
||||
const Request *req = spdySession.reqvec[i];
|
||||
auto req = spdySession.reqvec[i];
|
||||
std::cout << "#" << i+1 << ": " << req->uri << std::endl;
|
||||
std::cout << " Status: " << req->status << std::endl;
|
||||
std::cout << " Delta (ms) from SSL/TLS handshake(SYN_STREAM):"
|
||||
|
@ -578,22 +571,21 @@ void print_stats(const SpdySession& spdySession)
|
|||
}
|
||||
}
|
||||
|
||||
int spdy_evloop(int fd, SSL *ssl, int spdy_version, SpdySession& spdySession,
|
||||
int spdy_evloop(int fd, SSL *ssl, SpdySession& spdySession,
|
||||
const nghttp2_session_callbacks *callbacks, int timeout)
|
||||
{
|
||||
int result = 0;
|
||||
Spdylay sc(fd, ssl, spdy_version, callbacks, &spdySession);
|
||||
int rv;
|
||||
Spdylay sc(fd, ssl, callbacks, &spdySession);
|
||||
spdySession.sc = ≻
|
||||
|
||||
nfds_t npollfds = 1;
|
||||
pollfd pollfds[1];
|
||||
|
||||
if(spdy_version >= NGHTTP2_PROTO_SPDY3 && config.window_bits != -1) {
|
||||
if(config.window_bits != -1) {
|
||||
nghttp2_settings_entry iv[1];
|
||||
iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
|
||||
iv[0].flags = NGHTTP2_ID_FLAG_SETTINGS_NONE;
|
||||
iv[0].value = 1 << config.window_bits;
|
||||
int rv = sc.submit_settings(NGHTTP2_FLAG_SETTINGS_NONE, iv, 1);
|
||||
rv = sc.submit_settings(iv, 1);
|
||||
assert(rv == 0);
|
||||
}
|
||||
for(int i = 0, n = spdySession.reqvec.size(); i < n; ++i) {
|
||||
|
@ -660,8 +652,6 @@ int communicate(const std::string& host, uint16_t port,
|
|||
{
|
||||
int result = 0;
|
||||
int rv;
|
||||
int spdy_version;
|
||||
std::string next_proto;
|
||||
int timeout = config.timeout;
|
||||
SSL_CTX *ssl_ctx = 0;
|
||||
SSL *ssl = 0;
|
||||
|
@ -683,15 +673,6 @@ int communicate(const std::string& host, uint16_t port,
|
|||
<< std::endl;
|
||||
}
|
||||
|
||||
switch(config.spdy_version) {
|
||||
case NGHTTP2_PROTO_SPDY2:
|
||||
next_proto = "spdy/2";
|
||||
break;
|
||||
case NGHTTP2_PROTO_SPDY3:
|
||||
next_proto = "spdy/3";
|
||||
break;
|
||||
}
|
||||
|
||||
if(!config.no_tls) {
|
||||
ssl_ctx = SSL_CTX_new(TLSv1_client_method());
|
||||
if(!ssl_ctx) {
|
||||
|
@ -699,7 +680,7 @@ int communicate(const std::string& host, uint16_t port,
|
|||
result = -1;
|
||||
goto fin;
|
||||
}
|
||||
setup_ssl_ctx(ssl_ctx, &next_proto);
|
||||
setup_ssl_ctx(ssl_ctx, nullptr);
|
||||
if(!config.keyfile.empty()) {
|
||||
if(SSL_CTX_use_PrivateKey_file(ssl_ctx, config.keyfile.c_str(),
|
||||
SSL_FILETYPE_PEM) != 1) {
|
||||
|
@ -722,12 +703,11 @@ int communicate(const std::string& host, uint16_t port,
|
|||
result = -1;
|
||||
goto fin;
|
||||
}
|
||||
|
||||
{
|
||||
// If the user overrode the host header, use that value for the
|
||||
// SNI extension
|
||||
const char *host_string = 0;
|
||||
std::map<std::string,std::string>::const_iterator i =
|
||||
config.headers.find( "Host" );
|
||||
auto i = config.headers.find( "Host" );
|
||||
if ( i != config.headers.end() ) {
|
||||
host_string = (*i).second.c_str();
|
||||
}
|
||||
|
@ -740,6 +720,7 @@ int communicate(const std::string& host, uint16_t port,
|
|||
result = -1;
|
||||
goto fin;
|
||||
}
|
||||
}
|
||||
rv = ssl_nonblock_handshake(ssl, fd, timeout);
|
||||
if(rv == -1) {
|
||||
result = -1;
|
||||
|
@ -759,16 +740,7 @@ int communicate(const std::string& host, uint16_t port,
|
|||
}
|
||||
|
||||
spdySession.record_handshake_time();
|
||||
spdy_version = nghttp2_npn_get_version(
|
||||
reinterpret_cast<const unsigned char*>(next_proto.c_str()),
|
||||
next_proto.size());
|
||||
if (spdy_version <= 0) {
|
||||
std::cerr << "No supported SPDY version was negotiated." << std::endl;
|
||||
result = -1;
|
||||
goto fin;
|
||||
}
|
||||
|
||||
result = spdy_evloop(fd, ssl, spdy_version, spdySession, callbacks, timeout);
|
||||
result = spdy_evloop(fd, ssl, spdySession, callbacks, timeout);
|
||||
fin:
|
||||
if(ssl) {
|
||||
SSL_shutdown(ssl);
|
||||
|
@ -787,7 +759,7 @@ ssize_t file_read_callback
|
|||
uint8_t *buf, size_t length, int *eof,
|
||||
nghttp2_data_source *source, void *user_data)
|
||||
{
|
||||
Request *req = (Request*)nghttp2_session_get_stream_user_data
|
||||
auto req = (Request*)nghttp2_session_get_stream_user_data
|
||||
(session, stream_id);
|
||||
int fd = source->fd;
|
||||
ssize_t r;
|
||||
|
@ -812,14 +784,14 @@ int run(char **uris, int n)
|
|||
callbacks.send_callback = send_callback;
|
||||
callbacks.recv_callback = recv_callback;
|
||||
callbacks.on_stream_close_callback = on_stream_close_callback;
|
||||
callbacks.on_ctrl_recv_callback = on_ctrl_recv_callback2;
|
||||
callbacks.on_ctrl_send_callback = on_ctrl_send_callback2;
|
||||
callbacks.on_frame_recv_callback = on_frame_recv_callback2;
|
||||
callbacks.on_frame_send_callback = on_frame_send_callback2;
|
||||
if(config.verbose) {
|
||||
callbacks.on_data_recv_callback = on_data_recv_callback;
|
||||
callbacks.on_invalid_ctrl_recv_callback = on_invalid_ctrl_recv_callback;
|
||||
callbacks.on_ctrl_recv_parse_error_callback =
|
||||
on_ctrl_recv_parse_error_callback;
|
||||
callbacks.on_unknown_ctrl_recv_callback = on_unknown_ctrl_recv_callback;
|
||||
callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
|
||||
callbacks.on_frame_recv_parse_error_callback =
|
||||
on_frame_recv_parse_error_callback;
|
||||
callbacks.on_unknown_frame_recv_callback = on_unknown_frame_recv_callback;
|
||||
}
|
||||
callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback;
|
||||
ssl_debug = config.verbose;
|
||||
|
@ -882,7 +854,7 @@ int run(char **uris, int n)
|
|||
|
||||
void print_usage(std::ostream& out)
|
||||
{
|
||||
out << "Usage: spdycat [-Oansv23] [-t <SECONDS>] [-w <WINDOW_BITS>] [--cert=<CERT>]\n"
|
||||
out << "Usage: spdycat [-Oansv] [-t <SECONDS>] [-w <WINDOW_BITS>] [--cert=<CERT>]\n"
|
||||
<< " [--key=<KEY>] [--no-tls] [-d <FILE>] [-m <N>] <URI>..."
|
||||
<< std::endl;
|
||||
}
|
||||
|
@ -899,8 +871,6 @@ void print_help(std::ostream& out)
|
|||
<< " The filename is dereived from URI. If URI\n"
|
||||
<< " ends with '/', 'index.html' is used as a\n"
|
||||
<< " filename. Not implemented yet.\n"
|
||||
<< " -2, --spdy2 Only use SPDY/2.\n"
|
||||
<< " -3, --spdy3 Only use SPDY/3.\n"
|
||||
<< " -t, --timeout=<N> Timeout each request after <N> seconds.\n"
|
||||
<< " -w, --window-bits=<N>\n"
|
||||
<< " Sets the initial window size to 2**<N>.\n"
|
||||
|
@ -915,8 +885,7 @@ void print_help(std::ostream& out)
|
|||
<< " The file must be in PEM format.\n"
|
||||
<< " --key=<KEY> Use the client private key file. The file\n"
|
||||
<< " must be in PEM format.\n"
|
||||
<< " --no-tls Disable SSL/TLS. Use -2 or -3 to specify\n"
|
||||
<< " SPDY protocol version to use.\n"
|
||||
<< " --no-tls Disable SSL/TLS.\n"
|
||||
<< " -d, --data=<FILE> Post FILE to server. If - is given, data\n"
|
||||
<< " will be read from stdin.\n"
|
||||
<< " -m, --multiply=<N> Request each URI <N> times. By default, same\n"
|
||||
|
@ -933,8 +902,6 @@ int main(int argc, char **argv)
|
|||
{"verbose", no_argument, 0, 'v' },
|
||||
{"null-out", no_argument, 0, 'n' },
|
||||
{"remote-name", no_argument, 0, 'O' },
|
||||
{"spdy2", no_argument, 0, '2' },
|
||||
{"spdy3", no_argument, 0, '3' },
|
||||
{"timeout", required_argument, 0, 't' },
|
||||
{"window-bits", required_argument, 0, 'w' },
|
||||
{"get-assets", no_argument, 0, 'a' },
|
||||
|
@ -949,7 +916,7 @@ int main(int argc, char **argv)
|
|||
{0, 0, 0, 0 }
|
||||
};
|
||||
int option_index = 0;
|
||||
int c = getopt_long(argc, argv, "Oad:m:nhH:v23st:w:", long_options,
|
||||
int c = getopt_long(argc, argv, "Oad:m:nhH:vst:w:", long_options,
|
||||
&option_index);
|
||||
if(c == -1) {
|
||||
break;
|
||||
|
@ -967,12 +934,6 @@ int main(int argc, char **argv)
|
|||
case 'v':
|
||||
config.verbose = true;
|
||||
break;
|
||||
case '2':
|
||||
config.spdy_version = NGHTTP2_PROTO_SPDY2;
|
||||
break;
|
||||
case '3':
|
||||
config.spdy_version = NGHTTP2_PROTO_SPDY3;
|
||||
break;
|
||||
case 't':
|
||||
config.timeout = atoi(optarg) * 1000;
|
||||
break;
|
||||
|
@ -1052,14 +1013,6 @@ int main(int argc, char **argv)
|
|||
}
|
||||
}
|
||||
|
||||
if(config.no_tls) {
|
||||
if(config.spdy_version == -1) {
|
||||
std::cerr << "Specify SPDY protocol version using either -2 or -3."
|
||||
<< std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
set_color_output(isatty(fileno(stdout)));
|
||||
|
||||
struct sigaction act;
|
||||
|
|
27
src/spdyd.cc
27
src/spdyd.cc
|
@ -47,7 +47,7 @@ extern bool ssl_debug;
|
|||
namespace {
|
||||
void print_usage(std::ostream& out)
|
||||
{
|
||||
out << "Usage: spdyd [-23DVhv] [-d <PATH>] [--no-tls] <PORT> [<PRIVATE_KEY> <CERT>]"
|
||||
out << "Usage: spdyd [-DVhv] [-d <PATH>] [--no-tls] <PORT> [<PRIVATE_KEY> <CERT>]"
|
||||
<< std::endl;
|
||||
}
|
||||
} // namespace
|
||||
|
@ -74,10 +74,7 @@ void print_help(std::ostream& out)
|
|||
<< " current working directory.\n"
|
||||
<< " -v, --verbose Print debug information such as reception/\n"
|
||||
<< " transmission of frames and name/value pairs.\n"
|
||||
<< " -2, --spdy2 Only use SPDY/2.\n"
|
||||
<< " -3, --spdy3 Only use SPDY/3.\n"
|
||||
<< " --no-tls Disable SSL/TLS. Use -2 or -3 to specify\n"
|
||||
<< " SPDY protocol version to use.\n"
|
||||
<< " --no-tls Disable SSL/TLS.\n"
|
||||
<< " -h, --help Print this help.\n"
|
||||
<< std::endl;
|
||||
}
|
||||
|
@ -93,14 +90,12 @@ int main(int argc, char **argv)
|
|||
{"htdocs", required_argument, 0, 'd' },
|
||||
{"help", no_argument, 0, 'h' },
|
||||
{"verbose", no_argument, 0, 'v' },
|
||||
{"spdy2", no_argument, 0, '2' },
|
||||
{"spdy3", no_argument, 0, '3' },
|
||||
{"verify-client", no_argument, 0, 'V' },
|
||||
{"no-tls", no_argument, &flag, 1 },
|
||||
{0, 0, 0, 0 }
|
||||
};
|
||||
int option_index = 0;
|
||||
int c = getopt_long(argc, argv, "DVd:hv23", long_options, &option_index);
|
||||
int c = getopt_long(argc, argv, "DVd:hv", long_options, &option_index);
|
||||
if(c == -1) {
|
||||
break;
|
||||
}
|
||||
|
@ -120,12 +115,6 @@ int main(int argc, char **argv)
|
|||
case 'v':
|
||||
config.verbose = true;
|
||||
break;
|
||||
case '2':
|
||||
config.version = NGHTTP2_PROTO_SPDY2;
|
||||
break;
|
||||
case '3':
|
||||
config.version = NGHTTP2_PROTO_SPDY3;
|
||||
break;
|
||||
case '?':
|
||||
exit(EXIT_FAILURE);
|
||||
case 0:
|
||||
|
@ -140,7 +129,7 @@ int main(int argc, char **argv)
|
|||
break;
|
||||
}
|
||||
}
|
||||
if(argc-optind < (config.no_tls ? 1 : 3)) {
|
||||
if(argc - optind < (config.no_tls ? 1 : 3)) {
|
||||
print_usage(std::cerr);
|
||||
std::cerr << "Too few arguments" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
|
@ -148,13 +137,7 @@ int main(int argc, char **argv)
|
|||
|
||||
config.port = strtol(argv[optind++], 0, 10);
|
||||
|
||||
if(config.no_tls) {
|
||||
if(config.version == 0) {
|
||||
std::cerr << "Specify SPDY protocol version using either -2 or -3."
|
||||
<< std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
if(!config.no_tls) {
|
||||
config.private_key_file = argv[optind++];
|
||||
config.cert_file = argv[optind++];
|
||||
}
|
||||
|
|
20
src/util.cc
20
src/util.cc
|
@ -184,6 +184,26 @@ char upcase(char c)
|
|||
}
|
||||
}
|
||||
|
||||
std::string format_hex(const unsigned char *s, size_t len)
|
||||
{
|
||||
std::string res;
|
||||
for(size_t i = 0; i < len; ++i) {
|
||||
unsigned char c = s[i] >> 4;
|
||||
if(c > 9) {
|
||||
res += c - 10 + 'a';
|
||||
} else {
|
||||
res += c + '0';
|
||||
}
|
||||
c = s[i] & 0xf;
|
||||
if(c > 9) {
|
||||
res += c - 10 + 'a';
|
||||
} else {
|
||||
res += c + '0';
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
|
||||
} // namespace nghttp2
|
||||
|
|
|
@ -204,6 +204,8 @@ std::string percentEncode(const std::string& target);
|
|||
std::string percentDecode
|
||||
(std::string::const_iterator first, std::string::const_iterator last);
|
||||
|
||||
std::string format_hex(const unsigned char *s, size_t len);
|
||||
|
||||
std::string http_date(time_t t);
|
||||
|
||||
time_t parse_http_date(const std::string& s);
|
||||
|
|
|
@ -174,8 +174,8 @@ static void on_data_chunk_recv_callback(nghttp2_session *session,
|
|||
}
|
||||
|
||||
static void on_data_recv_callback(nghttp2_session *session,
|
||||
uint8_t flags, int32_t stream_id,
|
||||
int32_t length, void *user_data)
|
||||
uint16_t length, uint8_t flags,
|
||||
int32_t stream_id, void *user_data)
|
||||
{
|
||||
my_user_data *ud = (my_user_data*)user_data;
|
||||
++ud->data_recv_cb_called;
|
||||
|
|
Loading…
Reference in New Issue