nghttpx: Add --frontend-frame-debug option to debug HTTP/2 frame in upstream

The output format is the same one with nghttp/nghttpd. The output
is made into stderr to make it sync with logging.
This commit is contained in:
Tatsuhiro Tsujikawa 2014-02-09 18:47:26 +09:00
parent 909b79e69b
commit c7c283f3a9
7 changed files with 117 additions and 66 deletions

View File

@ -71,6 +71,7 @@ nghttpd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttpd.cc \
NGHTTPX_SRCS = \ NGHTTPX_SRCS = \
util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \ util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \
app_helper.cc app_helper.h \
shrpx_config.cc shrpx_config.h \ shrpx_config.cc shrpx_config.h \
shrpx_error.h \ shrpx_error.h \
shrpx_listen_handler.cc shrpx_listen_handler.h \ shrpx_listen_handler.cc shrpx_listen_handler.h \

View File

@ -131,13 +131,6 @@ const char* strframetype(uint8_t type)
}; };
} // namespace } // namespace
namespace {
void print_frame_attr_indent()
{
printf(" ");
}
} // namespace
namespace { namespace {
bool color_output = false; bool color_output = false;
} // namespace } // namespace
@ -147,6 +140,22 @@ void set_color_output(bool f)
color_output = f; color_output = f;
} }
namespace {
FILE *outfile = stdout;
} // namespace
void set_output(FILE *file)
{
outfile = file;
}
namespace {
void print_frame_attr_indent()
{
fprintf(outfile, " ");
}
} // namespace
namespace { namespace {
const char* ansi_esc(const char *code) const char* ansi_esc(const char *code)
{ {
@ -168,11 +177,11 @@ void print_nv(nghttp2_nv *nva, size_t nvlen, bool indent = true)
if(indent) { if(indent) {
print_frame_attr_indent(); print_frame_attr_indent();
} }
printf("%s", ansi_esc("\033[1;34m")); fprintf(outfile, "%s", ansi_esc("\033[1;34m"));
fwrite(nv.name, nv.namelen, 1, stdout); fwrite(nv.name, nv.namelen, 1, outfile);
printf("%s: ", ansi_escend()); fprintf(outfile, "%s: ", ansi_escend());
fwrite(nv.value, nv.valuelen, 1, stdout); fwrite(nv.value, nv.valuelen, 1, outfile);
printf("\n"); fprintf(outfile, "\n");
} }
} }
} // namelen } // namelen
@ -180,7 +189,7 @@ void print_nv(nghttp2_nv *nva, size_t nvlen, bool indent = true)
void print_timer() void print_timer()
{ {
auto millis = get_timer(); auto millis = get_timer();
printf("%s[%3ld.%03ld]%s", fprintf(outfile, "%s[%3ld.%03ld]%s",
ansi_esc("\033[33m"), ansi_esc("\033[33m"),
(long int)(millis.count()/1000), (long int)(millis.count()%1000), (long int)(millis.count()/1000), (long int)(millis.count()%1000),
ansi_escend()); ansi_escend());
@ -189,7 +198,7 @@ void print_timer()
namespace { namespace {
void print_frame_hd(const nghttp2_frame_hd& hd) void print_frame_hd(const nghttp2_frame_hd& hd)
{ {
printf("<length=%zu, flags=0x%02x, stream_id=%d>\n", fprintf(outfile, "<length=%zu, flags=0x%02x, stream_id=%d>\n",
hd.length, hd.flags, hd.stream_id); hd.length, hd.flags, hd.stream_id);
} }
} // namespace } // namespace
@ -285,7 +294,7 @@ void print_flags(const nghttp2_frame_hd& hd)
} }
break; break;
} }
printf("; %s\n", s.c_str()); fprintf(outfile, "; %s\n", s.c_str());
} }
} // namespace } // namespace
@ -304,7 +313,7 @@ const char* frame_name_ansi_esc(print_type ptype)
namespace { namespace {
void print_frame(print_type ptype, const nghttp2_frame *frame) void print_frame(print_type ptype, const nghttp2_frame *frame)
{ {
printf("%s%s%s frame ", fprintf(outfile, "%s%s%s frame ",
frame_name_ansi_esc(ptype), frame_name_ansi_esc(ptype),
strframetype(frame->hd.type), strframetype(frame->hd.type),
ansi_escend()); ansi_escend());
@ -317,25 +326,25 @@ void print_frame(print_type ptype, const nghttp2_frame *frame)
case NGHTTP2_DATA: case NGHTTP2_DATA:
if(frame->hd.flags & (NGHTTP2_FLAG_PAD_HIGH | NGHTTP2_FLAG_PAD_LOW)) { if(frame->hd.flags & (NGHTTP2_FLAG_PAD_HIGH | NGHTTP2_FLAG_PAD_LOW)) {
print_frame_attr_indent(); print_frame_attr_indent();
printf("(padlen=%zu)\n", frame->data.padlen); fprintf(outfile, "(padlen=%zu)\n", frame->data.padlen);
} }
break; break;
case NGHTTP2_HEADERS: case NGHTTP2_HEADERS:
print_frame_attr_indent(); print_frame_attr_indent();
printf("(pri=%d, padlen=%zu)\n", fprintf(outfile, "(pri=%d, padlen=%zu)\n",
frame->headers.pri, frame->headers.padlen); frame->headers.pri, frame->headers.padlen);
switch(frame->headers.cat) { switch(frame->headers.cat) {
case NGHTTP2_HCAT_REQUEST: case NGHTTP2_HCAT_REQUEST:
print_frame_attr_indent(); print_frame_attr_indent();
printf("; Open new stream\n"); fprintf(outfile, "; Open new stream\n");
break; break;
case NGHTTP2_HCAT_RESPONSE: case NGHTTP2_HCAT_RESPONSE:
print_frame_attr_indent(); print_frame_attr_indent();
printf("; First response header\n"); fprintf(outfile, "; First response header\n");
break; break;
case NGHTTP2_HCAT_PUSH_RESPONSE: case NGHTTP2_HCAT_PUSH_RESPONSE:
print_frame_attr_indent(); print_frame_attr_indent();
printf("; First push response header\n"); fprintf(outfile, "; First push response header\n");
break; break;
default: default:
break; break;
@ -344,20 +353,21 @@ void print_frame(print_type ptype, const nghttp2_frame *frame)
break; break;
case NGHTTP2_PRIORITY: case NGHTTP2_PRIORITY:
print_frame_attr_indent(); print_frame_attr_indent();
printf("(pri=%d)\n", frame->priority.pri); fprintf(outfile, "(pri=%d)\n", frame->priority.pri);
break; break;
case NGHTTP2_RST_STREAM: case NGHTTP2_RST_STREAM:
print_frame_attr_indent(); print_frame_attr_indent();
printf("(error_code=%s(%u))\n", fprintf(outfile, "(error_code=%s(%u))\n",
strstatus(frame->rst_stream.error_code), strstatus(frame->rst_stream.error_code),
frame->rst_stream.error_code); frame->rst_stream.error_code);
break; break;
case NGHTTP2_SETTINGS: case NGHTTP2_SETTINGS:
print_frame_attr_indent(); print_frame_attr_indent();
printf("(niv=%lu)\n", static_cast<unsigned long>(frame->settings.niv)); fprintf(outfile, "(niv=%lu)\n",
static_cast<unsigned long>(frame->settings.niv));
for(size_t i = 0; i < frame->settings.niv; ++i) { for(size_t i = 0; i < frame->settings.niv; ++i) {
print_frame_attr_indent(); print_frame_attr_indent();
printf("[%s(%d):%u]\n", fprintf(outfile, "[%s(%d):%u]\n",
strsettingsid(frame->settings.iv[i].settings_id), strsettingsid(frame->settings.iv[i].settings_id),
frame->settings.iv[i].settings_id, frame->settings.iv[i].settings_id,
frame->settings.iv[i].value); frame->settings.iv[i].value);
@ -365,19 +375,20 @@ void print_frame(print_type ptype, const nghttp2_frame *frame)
break; break;
case NGHTTP2_PUSH_PROMISE: case NGHTTP2_PUSH_PROMISE:
print_frame_attr_indent(); print_frame_attr_indent();
printf("(promised_stream_id=%d, padlen=%zu)\n", fprintf(outfile, "(promised_stream_id=%d, padlen=%zu)\n",
frame->push_promise.promised_stream_id, frame->push_promise.promised_stream_id,
frame->push_promise.padlen); frame->push_promise.padlen);
print_nv(frame->push_promise.nva, frame->push_promise.nvlen); print_nv(frame->push_promise.nva, frame->push_promise.nvlen);
break; break;
case NGHTTP2_PING: case NGHTTP2_PING:
print_frame_attr_indent(); print_frame_attr_indent();
printf("(opaque_data=%s)\n", fprintf(outfile, "(opaque_data=%s)\n",
util::format_hex(frame->ping.opaque_data, 8).c_str()); util::format_hex(frame->ping.opaque_data, 8).c_str());
break; break;
case NGHTTP2_GOAWAY: case NGHTTP2_GOAWAY:
print_frame_attr_indent(); print_frame_attr_indent();
printf("(last_stream_id=%d, error_code=%s(%u), opaque_data(%u)=[%s])\n", fprintf(outfile,
"(last_stream_id=%d, error_code=%s(%u), opaque_data(%u)=[%s])\n",
frame->goaway.last_stream_id, frame->goaway.last_stream_id,
strstatus(frame->goaway.error_code), strstatus(frame->goaway.error_code),
frame->goaway.error_code, frame->goaway.error_code,
@ -387,11 +398,11 @@ void print_frame(print_type ptype, const nghttp2_frame *frame)
break; break;
case NGHTTP2_WINDOW_UPDATE: case NGHTTP2_WINDOW_UPDATE:
print_frame_attr_indent(); print_frame_attr_indent();
printf("(window_size_increment=%d)\n", fprintf(outfile, "(window_size_increment=%d)\n",
frame->window_update.window_size_increment); frame->window_update.window_size_increment);
break; break;
default: default:
printf("\n"); fprintf(outfile, "\n");
break; break;
} }
} }
@ -408,7 +419,7 @@ int verbose_on_header_callback(nghttp2_session *session,
static_cast<uint16_t>(namelen), static_cast<uint16_t>(valuelen) static_cast<uint16_t>(namelen), static_cast<uint16_t>(valuelen)
}; };
print_timer(); print_timer();
printf(" (stream_id=%d) ", frame->hd.stream_id); fprintf(outfile, " (stream_id=%d) ", frame->hd.stream_id);
print_nv(&nv, 1, false /* no indent */); print_nv(&nv, 1, false /* no indent */);
return 0; return 0;
} }
@ -417,9 +428,9 @@ int verbose_on_frame_recv_callback
(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) (nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
{ {
print_timer(); print_timer();
printf(" recv "); fprintf(outfile, " recv ");
print_frame(PRINT_RECV, frame); print_frame(PRINT_RECV, frame);
fflush(stdout); fflush(outfile);
return 0; return 0;
} }
@ -428,9 +439,9 @@ int verbose_on_invalid_frame_recv_callback
nghttp2_error_code error_code, void *user_data) nghttp2_error_code error_code, void *user_data)
{ {
print_timer(); print_timer();
printf(" [INVALID; status=%s] recv ", strstatus(error_code)); fprintf(outfile, " [INVALID; status=%s] recv ", strstatus(error_code));
print_frame(PRINT_RECV, frame); print_frame(PRINT_RECV, frame);
fflush(stdout); fflush(outfile);
return 0; return 0;
} }
@ -439,11 +450,11 @@ void dump_header(const uint8_t *head, size_t headlen)
{ {
size_t i; size_t i;
print_frame_attr_indent(); print_frame_attr_indent();
printf("Header dump: "); fprintf(outfile, "Header dump: ");
for(i = 0; i < headlen; ++i) { for(i = 0; i < headlen; ++i) {
printf("%02X ", head[i]); fprintf(outfile, "%02X ", head[i]);
} }
printf("\n"); fprintf(outfile, "\n");
} }
} // namespace } // namespace
@ -455,9 +466,9 @@ int verbose_on_unknown_frame_recv_callback(nghttp2_session *session,
void *user_data) void *user_data)
{ {
print_timer(); print_timer();
printf(" recv unknown frame\n"); fprintf(outfile, " recv unknown frame\n");
dump_header(head, headlen); dump_header(head, headlen);
fflush(stdout); fflush(outfile);
return 0; return 0;
} }
@ -465,9 +476,9 @@ int verbose_on_frame_send_callback
(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) (nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
{ {
print_timer(); print_timer();
printf(" send "); fprintf(outfile, " send ");
print_frame(PRINT_SEND, frame); print_frame(PRINT_SEND, frame);
fflush(stdout); fflush(outfile);
return 0; return 0;
} }

View File

@ -86,6 +86,10 @@ void print_timer();
// variable. // variable.
void set_color_output(bool f); void set_color_output(bool f);
// Set output file when printing HTTP2 frames. By default, stdout is
// used.
void set_output(FILE *file);
} // namespace nghttp2 } // namespace nghttp2
#endif // APP_HELPER_H #endif // APP_HELPER_H

View File

@ -53,6 +53,7 @@
#include "shrpx_listen_handler.h" #include "shrpx_listen_handler.h"
#include "shrpx_ssl.h" #include "shrpx_ssl.h"
#include "util.h" #include "util.h"
#include "app_helper.h"
using namespace nghttp2; using namespace nghttp2;
@ -435,6 +436,7 @@ void fill_default_config()
mod_config()->http2_upstream_dump_request_header = nullptr; mod_config()->http2_upstream_dump_request_header = nullptr;
mod_config()->http2_upstream_dump_response_header = nullptr; mod_config()->http2_upstream_dump_response_header = nullptr;
mod_config()->http2_no_cookie_crumbling = false; mod_config()->http2_no_cookie_crumbling = false;
mod_config()->upstream_frame_debug = false;
} }
} // namespace } // namespace
@ -736,6 +738,10 @@ void print_help(std::ostream& out)
<< " an empty line.\n" << " an empty line.\n"
<< " This option is not thread safe and MUST NOT\n" << " This option is not thread safe and MUST NOT\n"
<< " be used with option -n=N, where N >= 2.\n" << " be used with option -n=N, where N >= 2.\n"
<< " -o, --frontend-frame-debug\n"
<< " Print HTTP/2 frames in frontend to stderr.\n"
<< " This option is not thread safe and MUST NOT\n"
<< " be used with option -n=N, where N >= 2.\n"
<< " -D, --daemon Run in a background. If -D is used, the\n" << " -D, --daemon Run in a background. If -D is used, the\n"
<< " current working directory is changed to '/'.\n" << " current working directory is changed to '/'.\n"
<< " --pid-file=<PATH> Set path to save PID of this program.\n" << " --pid-file=<PATH> Set path to save PID of this program.\n"
@ -771,6 +777,7 @@ int main(int argc, char **argv)
{"client-proxy", no_argument, nullptr, 'p'}, {"client-proxy", no_argument, nullptr, 'p'},
{"http2-proxy", no_argument, nullptr, 's'}, {"http2-proxy", no_argument, nullptr, 's'},
{"version", no_argument, nullptr, 'v'}, {"version", no_argument, nullptr, 'v'},
{"frontend-frame-debug", no_argument, nullptr, 'o'},
{"add-x-forwarded-for", no_argument, &flag, 1}, {"add-x-forwarded-for", no_argument, &flag, 1},
{"frontend-http2-read-timeout", required_argument, &flag, 2}, {"frontend-http2-read-timeout", required_argument, &flag, 2},
{"frontend-read-timeout", required_argument, &flag, 3}, {"frontend-read-timeout", required_argument, &flag, 3},
@ -821,7 +828,7 @@ int main(int argc, char **argv)
}; };
int option_index = 0; int option_index = 0;
int c = getopt_long(argc, argv, "DL:b:c:f:hkn:psv", long_options, int c = getopt_long(argc, argv, "DL:b:c:f:hkn:opsv", long_options,
&option_index); &option_index);
if(c == -1) { if(c == -1) {
break; break;
@ -851,6 +858,9 @@ int main(int argc, char **argv)
case 'n': case 'n':
cmdcfgs.emplace_back(SHRPX_OPT_WORKERS, optarg); cmdcfgs.emplace_back(SHRPX_OPT_WORKERS, optarg);
break; break;
case 'o':
cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_FRAME_DEBUG, "yes");
break;
case 'p': case 'p':
cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PROXY, "yes"); cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PROXY, "yes");
break; break;
@ -1204,6 +1214,15 @@ int main(int argc, char **argv)
get_rate_limit(get_config()->write_burst), get_rate_limit(get_config()->write_burst),
nullptr); nullptr);
if(get_config()->upstream_frame_debug) {
// To make it sync to logging
set_output(stderr);
if(isatty(fileno(stdout))) {
set_color_output(true);
}
reset_timer();
}
struct sigaction act; struct sigaction act;
memset(&act, 0, sizeof(struct sigaction)); memset(&act, 0, sizeof(struct sigaction));
act.sa_handler = SIG_IGN; act.sa_handler = SIG_IGN;

View File

@ -115,6 +115,7 @@ const char SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER[] =
const char SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER[] = const char SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER[] =
"frontend-http2-dump-response-header"; "frontend-http2-dump-response-header";
const char SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING[] = "http2-no-cookie-crumbling"; const char SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING[] = "http2-no-cookie-crumbling";
const char SHRPX_OPT_FRONTEND_FRAME_DEBUG[] = "frontend-frame-debug";
namespace { namespace {
Config *config = nullptr; Config *config = nullptr;
@ -480,6 +481,8 @@ int parse_config(const char *opt, const char *optarg)
mod_config()->http2_upstream_dump_response_header = f; mod_config()->http2_upstream_dump_response_header = f;
} else if(util::strieq(opt, SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING)) { } else if(util::strieq(opt, SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING)) {
mod_config()->http2_no_cookie_crumbling = util::strieq(optarg, "yes"); mod_config()->http2_no_cookie_crumbling = util::strieq(optarg, "yes");
} else if(util::strieq(opt, SHRPX_OPT_FRONTEND_FRAME_DEBUG)) {
mod_config()->upstream_frame_debug = util::strieq(optarg, "yes");
} else if(util::strieq(opt, "conf")) { } else if(util::strieq(opt, "conf")) {
LOG(WARNING) << "conf is ignored"; LOG(WARNING) << "conf is ignored";
} else { } else {

View File

@ -102,6 +102,7 @@ extern const char SHRPX_OPT_CLIENT_CERT_FILE[];
extern const char SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER[]; extern const char SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER[];
extern const char SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER[]; extern const char SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER[];
extern const char SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING[]; extern const char SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING[];
extern const char SHRPX_OPT_FRONTEND_FRAME_DEBUG[];
union sockaddr_union { union sockaddr_union {
sockaddr sa; sockaddr sa;
@ -213,6 +214,7 @@ struct Config {
// true if stderr refers to a terminal. // true if stderr refers to a terminal.
bool tty; bool tty;
bool http2_no_cookie_crumbling; bool http2_no_cookie_crumbling;
bool upstream_frame_debug;
}; };
const Config* get_config(); const Config* get_config();

View File

@ -39,6 +39,7 @@
#include "http2.h" #include "http2.h"
#include "util.h" #include "util.h"
#include "base64.h" #include "base64.h"
#include "app_helper.h"
using namespace nghttp2; using namespace nghttp2;
@ -205,6 +206,10 @@ int on_header_callback(nghttp2_session *session,
const uint8_t *value, size_t valuelen, const uint8_t *value, size_t valuelen,
void *user_data) void *user_data)
{ {
if(get_config()->upstream_frame_debug) {
verbose_on_header_callback(session, frame, name, namelen, value, valuelen,
user_data);
}
if(frame->hd.type != NGHTTP2_HEADERS || if(frame->hd.type != NGHTTP2_HEADERS ||
frame->headers.cat != NGHTTP2_HCAT_REQUEST) { frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
return 0; return 0;
@ -359,6 +364,9 @@ int on_frame_recv_callback
(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) (nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
{ {
int rv; int rv;
if(get_config()->upstream_frame_debug) {
verbose_on_frame_recv_callback(session, frame, user_data);
}
auto upstream = static_cast<Http2Upstream*>(user_data); auto upstream = static_cast<Http2Upstream*>(user_data);
switch(frame->hd.type) { switch(frame->hd.type) {
case NGHTTP2_DATA: { case NGHTTP2_DATA: {
@ -428,6 +436,9 @@ namespace {
int on_frame_send_callback(nghttp2_session* session, int on_frame_send_callback(nghttp2_session* session,
const nghttp2_frame *frame, void *user_data) const nghttp2_frame *frame, void *user_data)
{ {
if(get_config()->upstream_frame_debug) {
verbose_on_frame_send_callback(session, frame, user_data);
}
auto upstream = static_cast<Http2Upstream*>(user_data); auto upstream = static_cast<Http2Upstream*>(user_data);
if(frame->hd.type == NGHTTP2_SETTINGS && if(frame->hd.type == NGHTTP2_SETTINGS &&
(frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) { (frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {