Added proper command-line option support using getopt_long.

Now -nvh option works.
This commit is contained in:
Tatsuhiro Tsujikawa 2012-01-31 00:46:46 +09:00
parent 856d230595
commit d3a3dc5943
3 changed files with 204 additions and 82 deletions

View File

@ -31,6 +31,7 @@
#include <netinet/in.h> #include <netinet/in.h>
#include <netinet/tcp.h> #include <netinet/tcp.h>
#include <signal.h> #include <signal.h>
#include <getopt.h>
#include <cassert> #include <cassert>
#include <cstdio> #include <cstdio>
@ -43,6 +44,8 @@
#include <set> #include <set>
#include <iomanip> #include <iomanip>
#include <fstream> #include <fstream>
#include <map>
#include <vector>
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include <openssl/err.h> #include <openssl/err.h>
@ -53,16 +56,31 @@
namespace spdylay { namespace spdylay {
std::string target_path; struct Config {
int32_t target_stream_id; bool null_out;
bool debug = false; bool remote_name;
bool verbose;
Config():null_out(false), remote_name(false), verbose(false) {}
};
struct Request {
uri::UriStruct us;
Request(const uri::UriStruct& us):us(us) {}
};
std::map<int32_t, Request*> stream2req;
std::map<std::string, Request*> path2req;
size_t numreq, complete;
Config config;
extern bool ssl_debug; extern bool ssl_debug;
void on_data_chunk_recv_callback void on_data_chunk_recv_callback
(spdylay_session *session, uint8_t flags, int32_t stream_id, (spdylay_session *session, uint8_t flags, int32_t stream_id,
const uint8_t *data, size_t len, void *user_data) const uint8_t *data, size_t len, void *user_data)
{ {
if(!debug && stream_id == target_stream_id) { std::map<int32_t, Request*>::iterator itr = stream2req.find(stream_id);
if(itr != stream2req.end()) {
std::cout.write(reinterpret_cast<const char*>(data), len); std::cout.write(reinterpret_cast<const char*>(data), len);
} }
} }
@ -72,8 +90,10 @@ void check_stream_id(spdylay_frame_type type, spdylay_frame *frame)
if(type == SPDYLAY_SYN_STREAM) { if(type == SPDYLAY_SYN_STREAM) {
for(int i = 0; frame->syn_stream.nv[i]; i += 2) { for(int i = 0; frame->syn_stream.nv[i]; i += 2) {
if(strcmp("url", frame->syn_stream.nv[i]) == 0) { if(strcmp("url", frame->syn_stream.nv[i]) == 0) {
if(target_path == frame->syn_stream.nv[i+1]) { std::map<std::string, Request*>::iterator itr = path2req.find
target_stream_id = frame->syn_stream.stream_id; (frame->syn_stream.nv[i+1]);
if(itr != path2req.end()) {
stream2req[frame->syn_stream.stream_id] = (*itr).second;
} }
break; break;
} }
@ -100,17 +120,23 @@ void on_stream_close_callback
(spdylay_session *session, int32_t stream_id, spdylay_status_code status_code, (spdylay_session *session, int32_t stream_id, spdylay_status_code status_code,
void *user_data) void *user_data)
{ {
if(target_stream_id == stream_id) { std::map<int32_t, Request*>::iterator itr = stream2req.find(stream_id);
if(itr != stream2req.end()) {
++complete;
if(complete == numreq) {
spdylay_submit_goaway(session); spdylay_submit_goaway(session);
} }
}
} }
int communicate(const std::string& host, uint16_t port, int communicate(const std::string& host, uint16_t port,
const std::string& path, std::vector<Request>& reqvec,
const spdylay_session_callbacks *callbacks) const spdylay_session_callbacks *callbacks)
{ {
target_path = path; numreq = reqvec.size();
ssl_debug = debug; complete = 0;
path2req.clear();
stream2req.clear();
int fd = connect_to(host, port); int fd = connect_to(host, port);
if(fd == -1) { if(fd == -1) {
std::cerr << "Could not connect to the host" << std::endl; std::cerr << "Could not connect to the host" << std::endl;
@ -138,8 +164,13 @@ int communicate(const std::string& host, uint16_t port,
perror("epoll_create"); perror("epoll_create");
return -1; return -1;
} }
sc.submit_request(path, 3); for(int i = 0, n = reqvec.size(); i < n; ++i) {
uri::UriStruct& us = reqvec[i].us;
std::string path = us.dir+us.file+us.query;
int r = sc.submit_request(path, 3);
assert(r == 0);
path2req[path] = &reqvec[i];
}
ctl_epollev(epollfd, EPOLL_CTL_ADD, &sc); ctl_epollev(epollfd, EPOLL_CTL_ADD, &sc);
static const size_t MAX_EVENTS = 1; static const size_t MAX_EVENTS = 1;
epoll_event events[MAX_EVENTS]; epoll_event events[MAX_EVENTS];
@ -177,48 +208,100 @@ int communicate(const std::string& host, uint16_t port,
return ok ? 0 : -1; return ok ? 0 : -1;
} }
int run(const std::string& host, uint16_t port, const std::string& path) int run(char **uris, int n)
{ {
spdylay_session_callbacks callbacks; spdylay_session_callbacks callbacks;
memset(&callbacks, 0, sizeof(spdylay_session_callbacks)); memset(&callbacks, 0, sizeof(spdylay_session_callbacks));
callbacks.send_callback = send_callback; callbacks.send_callback = send_callback;
callbacks.recv_callback = recv_callback; callbacks.recv_callback = recv_callback;
callbacks.on_stream_close_callback = on_stream_close_callback; callbacks.on_stream_close_callback = on_stream_close_callback;
if(debug) { if(config.verbose) {
callbacks.on_ctrl_recv_callback = on_ctrl_recv_callback; callbacks.on_ctrl_recv_callback = on_ctrl_recv_callback;
callbacks.on_data_recv_callback = on_data_recv_callback; callbacks.on_data_recv_callback = on_data_recv_callback;
callbacks.on_ctrl_send_callback = on_ctrl_send_callback3; callbacks.on_ctrl_send_callback = on_ctrl_send_callback3;
} else { } else {
callbacks.on_ctrl_send_callback = on_ctrl_send_callback2; callbacks.on_ctrl_send_callback = on_ctrl_send_callback2;
}
if(!config.null_out) {
callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback; callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback;
} }
return communicate(host, port, path, &callbacks); ssl_debug = config.verbose;
std::vector<Request> reqvec;
std::string prev_host;
uint16_t prev_port = 0;
for(int i = 0; i < n; ++i) {
uri::UriStruct us;
if(uri::parse(us, uris[i])) {
if(prev_host != us.host || prev_port != us.port) {
if(!reqvec.empty()) {
communicate(prev_host, prev_port, reqvec, &callbacks);
reqvec.clear();
}
prev_host = us.host;
prev_port = us.port;
}
reqvec.push_back(Request(us));
}
}
if(!reqvec.empty()) {
communicate(prev_host, prev_port, reqvec, &callbacks);
}
return 0;
} }
} // namespace spdylay void print_usage(std::ostream& out)
void print_usage(const char *prog)
{ {
std::cerr << "Usage: " << prog << " [-d] URI" << std::endl; out << "Usage: spdycat [-Onv] [URI...]" << std::endl;
std::cerr << " Get the resource identified by URI via spdy/2 protocol and\n" }
<< " output the resource.\n"
<< "\n" void print_help(std::ostream& out)
<< " -d: Output debug information instead of output the resource." {
print_usage(out);
out << "\n"
<< "OPTIONS:\n"
<< " -v, --verbose Print debug information such as reception/\n"
<< " transmission of frames and name/value pairs.\n"
<< " -n, --null-out Discard downloaded data.\n"
<< " -O, --remote-name Save download data in the current directory.\n"
<< " The filename is dereived from URI. If URI\n"
<< " ends with '/', 'index.html' is used as a\n"
<< " filename. Not implemented yet.\n"
<< std::endl; << std::endl;
} }
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
std::string uri; while(1) {
if(argc == 2) { static option long_options[] = {
spdylay::debug = false; {"verbose", no_argument, 0, 'v' },
uri = argv[1]; {"null-out", no_argument, 0, 'n' },
} else if(argc == 3 && strcmp(argv[1], "-d") == 0) { {"remote-name", no_argument, 0, 'O' },
spdylay::debug = true; {"help", no_argument, 0, 'h' },
uri = argv[2]; {0, 0, 0, 0 }
} else { };
print_usage(basename(argv[0])); int option_index = 0;
int c = getopt_long(argc, argv, "Onhv", long_options, &option_index);
if(c == -1) {
break;
}
switch(c) {
case 'O':
config.remote_name = true;
break;
case 'h':
print_help(std::cout);
exit(EXIT_SUCCESS);
case 'n':
config.null_out = true;
break;
case 'v':
config.verbose = true;
break;
case '?':
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
default:
break;
}
} }
struct sigaction act; struct sigaction act;
memset(&act, 0, sizeof(struct sigaction)); memset(&act, 0, sizeof(struct sigaction));
@ -226,11 +309,14 @@ int main(int argc, char **argv)
sigaction(SIGPIPE, &act, 0); sigaction(SIGPIPE, &act, 0);
SSL_load_error_strings(); SSL_load_error_strings();
SSL_library_init(); SSL_library_init();
spdylay::uri::UriStruct us; reset_timer();
if(!spdylay::uri::parse(us, uri)) { run(argv+optind, argc-optind);
std::cerr << "Could not parse URI" << std::endl;
exit(EXIT_FAILURE);
}
spdylay::run(us.host, us.port, us.dir+us.file+us.query);
return 0; return 0;
} }
} // namespace spdylay
int main(int argc, char **argv)
{
return spdylay::main(argc, argv);
}

View File

@ -204,7 +204,8 @@ ssize_t recv_callback(spdylay_session *session,
return r; return r;
} }
static const char *ctrl_names[] = { namespace {
const char *ctrl_names[] = {
"SYN_STREAM", "SYN_STREAM",
"SYN_REPLY", "SYN_REPLY",
"RST_STREAM", "RST_STREAM",
@ -214,6 +215,7 @@ static const char *ctrl_names[] = {
"GOAWAY", "GOAWAY",
"HEADERS" "HEADERS"
}; };
} // namespace
void print_nv(char **nv) void print_nv(char **nv)
{ {
@ -223,14 +225,26 @@ void print_nv(char **nv)
} }
} }
void on_ctrl_recv_callback void print_timer()
(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame,
void *user_data)
{ {
printf("recv %s frame ", ctrl_names[type-1]); timespec ts;
get_timer(&ts);
printf("[%3ld.%03ld]", ts.tv_sec, ts.tv_nsec/1000000);
}
void print_frame(spdylay_frame_type type, spdylay_frame *frame)
{
printf("%s frame ", ctrl_names[type-1]);
switch(type) { switch(type) {
case SPDYLAY_SYN_STREAM:
printf("(stream_id=%d, assoc_stream_id=%d, flags=%u, length=%d, pri=%u)\n",
frame->syn_stream.stream_id, frame->syn_stream.assoc_stream_id,
frame->syn_stream.hd.flags,
frame->syn_stream.hd.length, frame->syn_stream.pri);
print_nv(frame->syn_stream.nv);
break;
case SPDYLAY_SYN_REPLY: case SPDYLAY_SYN_REPLY:
printf("(stream_id=%d, flags=%d, length=%d)\n", printf("(stream_id=%d, flags=%u, length=%d)\n",
frame->syn_reply.stream_id, frame->syn_reply.hd.flags, frame->syn_reply.stream_id, frame->syn_reply.hd.flags,
frame->syn_reply.hd.length); frame->syn_reply.hd.length);
print_nv(frame->syn_reply.nv); print_nv(frame->syn_reply.nv);
@ -238,40 +252,6 @@ void on_ctrl_recv_callback
case SPDYLAY_PING: case SPDYLAY_PING:
printf("(unique_id=%d)\n", frame->ping.unique_id); printf("(unique_id=%d)\n", frame->ping.unique_id);
break; break;
default:
printf("\n");
break;
}
fflush(stdout);
}
void on_data_recv_callback
(spdylay_session *session, uint8_t flags, int32_t stream_id, int32_t length,
void *user_data)
{
printf("recv DATA frame (stream_id=%d, flags=%d, length=%d)\n",
stream_id, flags, length);
// if(flags & SPDYLAY_FLAG_FIN) {
// spdylay_submit_ping(session);
// }
fflush(stdout);
}
void on_ctrl_send_callback
(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame,
void *user_data)
{
printf("send %s frame ", ctrl_names[type-1]);
switch(type) {
case SPDYLAY_SYN_STREAM:
printf("(stream_id=%d, flags=%d, length=%d)\n",
frame->syn_stream.stream_id, frame->syn_stream.hd.flags,
frame->syn_stream.hd.length);
print_nv(frame->syn_stream.nv);
break;
case SPDYLAY_PING:
printf("(unique_id=%d)\n", frame->ping.unique_id);
break;
case SPDYLAY_GOAWAY: case SPDYLAY_GOAWAY:
printf("(last_good_stream_id=%d)\n", frame->goaway.last_good_stream_id); printf("(last_good_stream_id=%d)\n", frame->goaway.last_good_stream_id);
break; break;
@ -279,6 +259,35 @@ void on_ctrl_send_callback
printf("\n"); printf("\n");
break; break;
} }
}
void on_ctrl_recv_callback
(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame,
void *user_data)
{
print_timer();
printf(" recv ");
print_frame(type, frame);
fflush(stdout);
}
void on_data_recv_callback
(spdylay_session *session, uint8_t flags, int32_t stream_id, int32_t length,
void *user_data)
{
print_timer();
printf(" recv DATA frame (stream_id=%d, flags=%d, length=%d)\n",
stream_id, flags, length);
fflush(stdout);
}
void on_ctrl_send_callback
(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame,
void *user_data)
{
print_timer();
printf(" send ");
print_frame(type, frame);
fflush(stdout); fflush(stdout);
} }
@ -306,7 +315,9 @@ int select_next_proto_cb(SSL* ssl,
*out = (unsigned char*)in+1; *out = (unsigned char*)in+1;
*outlen = in[0]; *outlen = in[0];
if(ssl_debug) { if(ssl_debug) {
std::cout << "NPN select next proto: server offers:" << std::endl; print_timer();
std::cout << " NPN select next protocol: the remote server offers:"
<< std::endl;
} }
for(unsigned int i = 0; i < inlen; i += in[i]+1) { for(unsigned int i = 0; i < inlen; i += in[i]+1) {
if(ssl_debug) { if(ssl_debug) {
@ -345,4 +356,24 @@ int ssl_handshake(SSL *ssl, int fd)
return 0; return 0;
} }
namespace {
timespec basets;
} // namespace
void reset_timer()
{
clock_gettime(CLOCK_MONOTONIC_RAW, &basets);
}
void get_timer(timespec* ts)
{
clock_gettime(CLOCK_MONOTONIC_RAW, ts);
ts->tv_nsec -= basets.tv_nsec;
ts->tv_sec -= basets.tv_sec;
if(ts->tv_nsec < 0) {
ts->tv_nsec += 1000000000;
--ts->tv_sec;
}
}
} // namespace spdylay } // namespace spdylay

View File

@ -27,6 +27,7 @@
#include <stdint.h> #include <stdint.h>
#include <cstdlib> #include <cstdlib>
#include <time.h>
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include <openssl/err.h> #include <openssl/err.h>
@ -93,6 +94,10 @@ void setup_ssl_ctx(SSL_CTX *ssl_ctx);
int ssl_handshake(SSL *ssl, int fd); int ssl_handshake(SSL *ssl, int fd);
void reset_timer();
void get_timer(timespec *ts);
} // namespace spdylay } // namespace spdylay
#endif // SPDYLAY_SSL_H #endif // SPDYLAY_SSL_H