2 * nghttp2 - HTTP/2 C Library
4 * Copyright (c) 2014 Tatsuhiro Tsujikawa
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 * This program is intended to measure library performance, avoiding
27 * overhead of underlying I/O library (e.g., libevent, Boost ASIO).
33 #endif /* HAVE_CONFIG_H */
35 #include <sys/types.h>
36 #ifdef HAVE_SYS_SOCKET_H
37 #include <sys/socket.h>
38 #endif /* HAVE_SYS_SOCKET_H */
42 #endif /* HAVE_FCNTL_H */
45 #endif /* HAVE_NETDB_H */
46 #ifdef HAVE_NETINET_IN_H
47 #include <netinet/in.h>
48 #endif /* HAVE_NETINET_IN_H */
49 #include <netinet/tcp.h>
52 #endif /* HAVE_UNISTD_H */
56 #endif /* HAVE_TIME_H */
62 #include <sys/epoll.h>
63 #include <sys/timerfd.h>
65 #include <nghttp2/nghttp2.h>
67 #define SERVER_NAME "tiny-nghttpd nghttp2/" NGHTTP2_VERSION
69 #define MAKE_NV(name, value) \
71 (uint8_t *)(name), (uint8_t *)(value), sizeof((name)) - 1, \
72 sizeof((value)) - 1, NGHTTP2_NV_FLAG_NONE \
75 #define MAKE_NV2(name, value, valuelen) \
77 (uint8_t *)(name), (uint8_t *)(value), sizeof((name)) - 1, (valuelen), \
78 NGHTTP2_NV_FLAG_NONE \
81 #define array_size(a) (sizeof((a)) / sizeof((a)[0]))
83 /* Returns the length of remaning data in buffer */
84 #define io_buf_len(iobuf) ((iobuf)->last - (iobuf)->pos)
85 /* Returns the space buffer can still accept */
86 #define io_buf_left(iobuf) ((iobuf)->end - (iobuf)->last)
89 /* beginning of buffer */
91 /* one byte beyond the end of buffer */
93 /* next read/write position of buffer */
95 /* one byte beyond last data of buffer */
104 typedef struct stream {
105 struct stream *prev, *next;
106 /* mandatory header fields */
112 /* region of response body in rawscrbuf */
113 uint8_t *res_begin, *res_end;
114 /* io_buf wrapping rawscrbuf */
117 /* length of mandatory header fields */
123 /* stream ID of this stream */
125 /* fd for reading file */
127 /* scratch buffer for this stream */
128 uint8_t rawscrbuf[4096];
131 typedef struct { int (*handler)(io_loop *, uint32_t, void *); } evhandle;
135 nghttp2_session *session;
138 /* pending library output */
139 const uint8_t *cache;
140 /* io_buf wrapping rawoutbuf */
142 /* length of cache */
147 uint8_t rawoutbuf[65536];
164 /* length of docroot */
167 nghttp2_session_callbacks *shared_callbacks;
169 static int handle_accept(io_loop *loop, uint32_t events, void *ptr);
170 static int handle_connection(io_loop *loop, uint32_t events, void *ptr);
171 static int handle_timer(io_loop *loop, uint32_t events, void *ptr);
173 static void io_buf_init(io_buf *buf, uint8_t *underlying, size_t len) {
174 buf->begin = buf->pos = buf->last = underlying;
175 buf->end = underlying + len;
178 static void io_buf_add(io_buf *buf, const void *src, size_t len) {
179 memcpy(buf->last, src, len);
183 static char *io_buf_add_str(io_buf *buf, const void *src, size_t len) {
184 uint8_t *start = buf->last;
186 memcpy(buf->last, src, len);
190 return (char *)start;
193 static int memeq(const void *a, const void *b, size_t n) {
194 return memcmp(a, b, n) == 0;
197 #define streq(A, B, N) ((sizeof((A)) - 1) == (N) && memeq((A), (B), (N)))
200 NGHTTP2_TOKEN__AUTHORITY,
201 NGHTTP2_TOKEN__METHOD,
203 NGHTTP2_TOKEN__SCHEME,
207 /* Inspired by h2o header lookup. https://github.com/h2o/h2o */
208 static int lookup_token(const uint8_t *name, size_t namelen) {
211 switch (name[namelen - 1]) {
213 if (streq(":pat", name, 4)) {
214 return NGHTTP2_TOKEN__PATH;
220 switch (name[namelen - 1]) {
222 if (streq(":metho", name, 6)) {
223 return NGHTTP2_TOKEN__METHOD;
227 if (streq(":schem", name, 6)) {
228 return NGHTTP2_TOKEN__SCHEME;
234 switch (name[namelen - 1]) {
236 if (streq(":authorit", name, 9)) {
237 return NGHTTP2_TOKEN__AUTHORITY;
246 static char *cpydig(char *buf, int n, size_t len) {
251 *p-- = (n % 10) + '0';
258 static const char *MONTH[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
259 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
260 static const char *DAY_OF_WEEK[] = {"Sun", "Mon", "Tue", "Wed",
261 "Thu", "Fri", "Sat"};
263 static size_t http_date(char *buf, time_t t) {
267 if (gmtime_r(&t, &tms) == NULL) {
271 /* Sat, 27 Sep 2014 06:31:15 GMT */
273 memcpy(p, DAY_OF_WEEK[tms.tm_wday], 3);
277 p = cpydig(p, tms.tm_mday, 2);
279 memcpy(p, MONTH[tms.tm_mon], 3);
282 p = cpydig(p, tms.tm_year + 1900, 4);
284 p = cpydig(p, tms.tm_hour, 2);
286 p = cpydig(p, tms.tm_min, 2);
288 p = cpydig(p, tms.tm_sec, 2);
289 memcpy(p, " GMT", 4);
295 static char date[29];
298 static void update_date() { datelen = http_date(date, time(NULL)); }
300 static size_t utos(char *buf, size_t len, uint64_t n) {
313 for (; t; t /= 10, ++nwrite)
322 *buf-- = (n % 10) + '0';
329 static void print_errno(const char *prefix, int errnum) {
333 errmsg = strerror_r(errnum, buf, sizeof(buf));
335 fprintf(stderr, "%s: %s\n", prefix, errmsg);
338 #define list_insert(head, elem) \
340 (elem)->prev = (head); \
341 (elem)->next = (head)->next; \
343 if ((head)->next) { \
344 (head)->next->prev = (elem); \
346 (head)->next = (elem); \
349 #define list_remove(elem) \
351 (elem)->prev->next = (elem)->next; \
352 if ((elem)->next) { \
353 (elem)->next->prev = (elem)->prev; \
357 static stream *stream_new(int32_t stream_id, connection *conn) {
360 strm = malloc(sizeof(stream));
362 strm->prev = strm->next = NULL;
365 strm->authority = NULL;
368 strm->res_begin = NULL;
369 strm->res_end = NULL;
372 strm->authoritylen = 0;
375 strm->stream_id = stream_id;
379 list_insert(&conn->strm_head, strm);
381 io_buf_init(&strm->scrbuf, strm->rawscrbuf, sizeof(strm->rawscrbuf));
386 static void stream_del(stream *strm) {
389 if (strm->filefd != -1) {
396 static connection *connection_new(int fd) {
400 conn = malloc(sizeof(connection));
402 rv = nghttp2_session_server_new(&conn->session, shared_callbacks, conn);
411 io_buf_init(&conn->buf, conn->rawoutbuf, sizeof(conn->rawoutbuf));
412 conn->evhn.handler = handle_connection;
413 conn->strm_head.next = NULL;
422 static void connection_del(connection *conn) {
425 nghttp2_session_del(conn->session);
426 shutdown(conn->fd, SHUT_WR);
429 strm = conn->strm_head.next;
431 stream *next_strm = strm->next;
440 static int connection_start(connection *conn) {
441 nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100};
444 rv = nghttp2_submit_settings(conn->session, NGHTTP2_FLAG_NONE, &iv, 1);
453 static int server_init(server *serv, const char *node, const char *service) {
455 struct addrinfo hints;
456 struct addrinfo *res, *rp;
459 socklen_t optlen = sizeof(on);
461 memset(&hints, 0, sizeof(hints));
462 hints.ai_family = AF_UNSPEC;
463 hints.ai_socktype = SOCK_STREAM;
464 hints.ai_protocol = 0;
465 hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
467 rv = getaddrinfo(node, service, &hints, &res);
470 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
474 for (rp = res; rp; rp = rp->ai_next) {
476 socket(rp->ai_family, rp->ai_socktype | SOCK_NONBLOCK, rp->ai_protocol);
482 rv = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, optlen);
485 print_errno("setsockopt", errno);
488 if (bind(fd, rp->ai_addr, rp->ai_addrlen) != 0) {
493 if (listen(fd, 65536) != 0) {
504 fprintf(stderr, "No address to bind\n");
509 serv->evhn.handler = handle_accept;
514 static int timer_init(timer *tmr) {
516 struct itimerspec timerval = {{1, 0}, {1, 0}};
518 fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
520 print_errno("timerfd_create", errno);
524 if (timerfd_settime(fd, 0, &timerval, NULL) != 0) {
525 print_errno("timerfd_settime", errno);
530 tmr->evhn.handler = handle_timer;
535 static int io_loop_init(io_loop *loop) {
538 epfd = epoll_create1(0);
541 print_errno("epoll_create", errno);
550 static int io_loop_ctl(io_loop *loop, int op, int fd, uint32_t events,
553 struct epoll_event ev;
558 rv = epoll_ctl(loop->epfd, op, fd, &ev);
561 print_errno("epoll_ctl", errno);
568 static int io_loop_add(io_loop *loop, int fd, uint32_t events, void *ptr) {
569 return io_loop_ctl(loop, EPOLL_CTL_ADD, fd, events, ptr);
572 static int io_loop_mod(io_loop *loop, int fd, uint32_t events, void *ptr) {
573 return io_loop_ctl(loop, EPOLL_CTL_MOD, fd, events, ptr);
576 static int io_loop_run(io_loop *loop, server *serv _U_) {
577 #define NUM_EVENTS 1024
578 struct epoll_event events[NUM_EVENTS];
583 struct epoll_event *ev, *end;
585 while ((nev = epoll_wait(loop->epfd, events, NUM_EVENTS, -1)) == -1 &&
590 print_errno("epoll_wait", errno);
594 for (ev = events, end = events + nev; ev != end; ++ev) {
596 evhn->handler(loop, ev->events, ev->data.ptr);
601 static int handle_timer(io_loop *loop _U_, uint32_t events _U_, void *ptr) {
606 while ((nread = read(tmr->fd, &buf, sizeof(buf))) == -1 && errno == EINTR)
609 assert(nread == sizeof(buf));
616 static int handle_accept(io_loop *loop, uint32_t events _U_, void *ptr) {
620 socklen_t optlen = sizeof(on);
626 while ((acfd = accept4(serv->fd, NULL, NULL, SOCK_NONBLOCK)) == -1 &&
645 rv = setsockopt(acfd, IPPROTO_TCP, TCP_NODELAY, &on, optlen);
648 print_errno("setsockopt", errno);
651 conn = connection_new(acfd);
658 if (connection_start(conn) != 0 ||
659 io_loop_add(loop, acfd, EPOLLIN | EPOLLOUT, conn) != 0) {
660 connection_del(conn);
665 static void stream_error(connection *conn, int32_t stream_id,
666 uint32_t error_code) {
667 nghttp2_submit_rst_stream(conn->session, NGHTTP2_FLAG_NONE, stream_id,
671 static int send_data_callback(nghttp2_session *session _U_,
672 nghttp2_frame *frame, const uint8_t *framehd,
673 size_t length, nghttp2_data_source *source,
675 connection *conn = user_data;
676 uint8_t *p = conn->buf.last;
677 stream *strm = source->ptr;
679 /* We never use padding in this program */
680 assert(frame->data.padlen == 0);
682 if ((size_t)io_buf_left(&conn->buf) < 9 + frame->hd.length) {
683 return NGHTTP2_ERR_WOULDBLOCK;
686 memcpy(p, framehd, 9);
691 while ((nread = read(strm->filefd, p, length)) == -1 && errno == EINTR)
694 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
706 static ssize_t fd_read_callback(nghttp2_session *session _U_,
707 int32_t stream_id _U_, uint8_t *buf _U_,
708 size_t length, uint32_t *data_flags,
709 nghttp2_data_source *source,
710 void *user_data _U_) {
711 stream *strm = source->ptr;
713 (int64_t)length < strm->fileleft ? (int64_t)length : strm->fileleft;
715 *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
717 strm->fileleft -= nread;
718 if (nread == 0 || strm->fileleft == 0) {
719 if (strm->fileleft != 0) {
720 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
722 *data_flags |= NGHTTP2_DATA_FLAG_EOF;
727 static ssize_t resbuf_read_callback(nghttp2_session *session _U_,
728 int32_t stream_id _U_, uint8_t *buf,
729 size_t length, uint32_t *data_flags,
730 nghttp2_data_source *source,
731 void *user_data _U_) {
732 stream *strm = source->ptr;
733 size_t left = strm->res_end - strm->res_begin;
734 size_t nwrite = length < left ? length : left;
736 memcpy(buf, strm->res_begin, nwrite);
737 strm->res_begin += nwrite;
739 if (strm->res_begin == strm->res_end) {
740 *data_flags |= NGHTTP2_DATA_FLAG_EOF;
746 static int hex_digit(char c) {
747 return ('0' <= c && c <= '9') || ('A' <= c && c <= 'F') ||
748 ('a' <= c && c <= 'f');
751 static unsigned int hex_to_uint(char c) {
763 static void percent_decode(io_buf *buf, const char *s) {
765 if (*s == '?' || *s == '#') {
769 if (*s == '%' && hex_digit(*(s + 1)) && hex_digit(*(s + 2))) {
770 *buf->last++ = (hex_to_uint(*(s + 1)) << 4) + hex_to_uint(*(s + 2));
779 static int check_path(const char *path, size_t len) {
780 return path[0] == '/' && strchr(path, '\\') == NULL &&
781 strstr(path, "/../") == NULL && strstr(path, "/./") == NULL &&
782 (len < 3 || memcmp(path + len - 3, "/..", 3) != 0) &&
783 (len < 2 || memcmp(path + len - 2, "/.", 2) != 0);
786 static int make_path(io_buf *pathbuf, const char *req, size_t reqlen _U_) {
793 if (docrootlen + strlen(req) + sizeof("index.html") >
794 (size_t)io_buf_left(pathbuf)) {
798 io_buf_add(pathbuf, docroot, docrootlen);
802 percent_decode(pathbuf, req);
804 if (*(pathbuf->last - 1) == '/') {
805 io_buf_add(pathbuf, "index.html", sizeof("index.html") - 1);
808 *pathbuf->last++ = '\0';
810 if (!check_path((const char *)p, pathbuf->last - 1 - p)) {
818 static int status_response(stream *strm, connection *conn,
819 const char *status_code) {
821 size_t status_codelen = strlen(status_code);
822 char contentlength[19];
823 size_t contentlengthlen;
825 nghttp2_data_provider prd, *prdptr;
826 nghttp2_nv nva[5] = {
827 MAKE_NV(":status", ""), MAKE_NV("server", SERVER_NAME),
828 MAKE_NV2("date", date, datelen), MAKE_NV("content-length", ""),
832 nva[0].value = (uint8_t *)status_code;
833 nva[0].valuelen = strlen(status_code);
835 #define BODY1 "<html><head><title>"
836 #define BODY2 "</title></head><body><h1>"
837 #define BODY3 "</h1></body></html>"
839 reslen = sizeof(BODY1) - 1 + sizeof(BODY2) - 1 + sizeof(BODY3) - 1 +
842 if ((size_t)io_buf_left(&strm->scrbuf) < reslen) {
843 contentlength[0] = '0';
844 contentlengthlen = 1;
847 contentlengthlen = utos(contentlength, sizeof(contentlength), reslen);
849 strm->res_begin = strm->scrbuf.last;
851 io_buf_add(&strm->scrbuf, BODY1, sizeof(BODY1) - 1);
852 io_buf_add(&strm->scrbuf, status_code, strlen(status_code));
853 io_buf_add(&strm->scrbuf, BODY2, sizeof(BODY2) - 1);
854 io_buf_add(&strm->scrbuf, status_code, strlen(status_code));
855 io_buf_add(&strm->scrbuf, BODY3, sizeof(BODY3) - 1);
857 strm->res_end = strm->scrbuf.last;
861 nva[nvlen].value = (uint8_t *)contentlength;
862 nva[nvlen].valuelen = contentlengthlen;
866 prd.source.ptr = strm;
867 prd.read_callback = resbuf_read_callback;
869 rv = nghttp2_submit_response(conn->session, strm->stream_id, nva, nvlen,
878 static int redirect_response(stream *strm, connection *conn) {
881 nghttp2_nv nva[5] = {
882 MAKE_NV(":status", "301"), MAKE_NV("server", SERVER_NAME),
883 MAKE_NV2("date", date, datelen), MAKE_NV("content-length", "0"),
884 MAKE_NV("location", ""),
887 /* + 1 for trailing '/' */
888 locationlen = strm->schemelen + 3 + strm->hostlen + strm->pathlen + 1;
889 if ((size_t)io_buf_left(&strm->scrbuf) < locationlen) {
893 nva[4].value = strm->scrbuf.last;
894 nva[4].valuelen = locationlen;
896 io_buf_add(&strm->scrbuf, strm->scheme, strm->schemelen);
897 io_buf_add(&strm->scrbuf, "://", 3);
898 io_buf_add(&strm->scrbuf, strm->host, strm->hostlen);
899 io_buf_add(&strm->scrbuf, strm->path, strm->pathlen);
900 *strm->scrbuf.last++ = '/';
902 rv = nghttp2_submit_response(conn->session, strm->stream_id, nva,
903 array_size(nva), NULL);
912 static int process_request(stream *strm, connection *conn) {
916 nghttp2_data_provider prd;
919 char contentlength[19];
920 size_t contentlengthlen;
923 nghttp2_nv nva[5] = {
924 MAKE_NV(":status", "200"), MAKE_NV("server", SERVER_NAME),
925 MAKE_NV2("date", date, datelen), MAKE_NV("content-length", ""),
929 io_buf_init(&pathbuf, (uint8_t *)path, sizeof(path));
931 rv = make_path(&pathbuf, strm->path, strm->pathlen);
934 return status_response(strm, conn, "400");
937 fd = open(path, O_RDONLY);
940 return status_response(strm, conn, "404");
945 rv = fstat(fd, &stbuf);
948 return status_response(strm, conn, "404");
951 if (stbuf.st_mode & S_IFDIR) {
952 return redirect_response(strm, conn);
955 prd.source.ptr = strm;
956 prd.read_callback = fd_read_callback;
958 strm->fileleft = stbuf.st_size;
960 lastmodlen = http_date(lastmod, stbuf.st_mtim.tv_sec);
961 contentlengthlen = utos(contentlength, sizeof(contentlength), stbuf.st_size);
963 nva[nvlen].value = (uint8_t *)contentlength;
964 nva[nvlen].valuelen = contentlengthlen;
969 nva[nvlen].name = (uint8_t *)"last-modified";
970 nva[nvlen].namelen = sizeof("last-modified") - 1;
971 nva[nvlen].value = (uint8_t *)lastmod;
972 nva[nvlen].valuelen = lastmodlen;
973 nva[nvlen].flags = NGHTTP2_NV_FLAG_NONE;
979 nghttp2_submit_response(conn->session, strm->stream_id, nva, nvlen, &prd);
987 static int on_begin_headers_callback(nghttp2_session *session,
988 const nghttp2_frame *frame,
990 connection *conn = user_data;
993 if (frame->hd.type != NGHTTP2_HEADERS ||
994 frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
998 strm = stream_new(frame->hd.stream_id, conn);
1001 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
1002 NGHTTP2_INTERNAL_ERROR);
1006 nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, strm);
1011 static int on_header_callback(nghttp2_session *session,
1012 const nghttp2_frame *frame, const uint8_t *name,
1013 size_t namelen, const uint8_t *value,
1014 size_t valuelen, uint8_t flags _U_,
1015 void *user_data _U_) {
1018 if (frame->hd.type != NGHTTP2_HEADERS ||
1019 frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
1023 strm = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
1029 switch (lookup_token(name, namelen)) {
1030 case NGHTTP2_TOKEN__METHOD:
1031 strm->method = io_buf_add_str(&strm->scrbuf, value, valuelen);
1032 if (!strm->method) {
1033 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
1035 strm->methodlen = valuelen;
1037 case NGHTTP2_TOKEN__SCHEME:
1038 strm->scheme = io_buf_add_str(&strm->scrbuf, value, valuelen);
1039 if (!strm->scheme) {
1040 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
1042 strm->schemelen = valuelen;
1044 case NGHTTP2_TOKEN__AUTHORITY:
1045 strm->authority = io_buf_add_str(&strm->scrbuf, value, valuelen);
1046 if (!strm->authority) {
1047 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
1049 strm->authoritylen = valuelen;
1051 case NGHTTP2_TOKEN__PATH:
1052 strm->path = io_buf_add_str(&strm->scrbuf, value, valuelen);
1054 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
1056 strm->pathlen = valuelen;
1058 case NGHTTP2_TOKEN_HOST:
1059 strm->host = io_buf_add_str(&strm->scrbuf, value, valuelen);
1061 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
1063 strm->hostlen = valuelen;
1070 static int on_frame_recv_callback(nghttp2_session *session,
1071 const nghttp2_frame *frame, void *user_data) {
1072 connection *conn = user_data;
1075 if (frame->hd.type != NGHTTP2_HEADERS ||
1076 frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
1080 strm = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
1087 strm->host = strm->authority;
1088 strm->hostlen = strm->authoritylen;
1091 if (process_request(strm, conn) != 0) {
1092 stream_error(conn, strm->stream_id, NGHTTP2_INTERNAL_ERROR);
1099 static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
1100 uint32_t error_code _U_,
1101 void *user_data _U_) {
1104 strm = nghttp2_session_get_stream_user_data(session, stream_id);
1115 static int on_frame_not_send_callback(nghttp2_session *session _U_,
1116 const nghttp2_frame *frame,
1117 int lib_error_code _U_, void *user_data) {
1118 connection *conn = user_data;
1120 if (frame->hd.type != NGHTTP2_HEADERS) {
1124 /* Issue RST_STREAM so that stream does not hang around. */
1125 nghttp2_submit_rst_stream(conn->session, NGHTTP2_FLAG_NONE,
1126 frame->hd.stream_id, NGHTTP2_INTERNAL_ERROR);
1131 static int do_read(connection *conn) {
1138 while ((nread = read(conn->fd, buf, sizeof(buf))) == -1 && errno == EINTR)
1141 if (errno == EAGAIN || errno == EWOULDBLOCK) {
1152 nproc = nghttp2_session_mem_recv(conn->session, buf, nread);
1160 static int do_write(connection *conn) {
1162 if (io_buf_len(&conn->buf)) {
1164 while ((nwrite = write(conn->fd, conn->buf.pos,
1165 io_buf_len(&conn->buf))) == -1 &&
1169 if (errno == EAGAIN || errno == EWOULDBLOCK) {
1175 conn->buf.pos += nwrite;
1177 if (io_buf_len(&conn->buf)) {
1181 io_buf_init(&conn->buf, conn->rawoutbuf, sizeof(conn->rawoutbuf));
1185 io_buf_add(&conn->buf, conn->cache, conn->cachelen);
1194 n = nghttp2_session_mem_send(conn->session, &b);
1201 if (io_buf_len(&conn->buf) == 0) {
1207 if (io_buf_left(&conn->buf) < n) {
1213 io_buf_add(&conn->buf, b, n);
1218 static int handle_connection(io_loop *loop, uint32_t events, void *ptr) {
1219 connection *conn = ptr;
1221 uint32_t nextev = 0;
1223 if (events & (EPOLLHUP | EPOLLERR)) {
1227 if (events & EPOLLIN) {
1235 rv = do_write(conn);
1241 if (nghttp2_session_want_read(conn->session)) {
1245 if (io_buf_len(&conn->buf) || nghttp2_session_want_write(conn->session)) {
1253 io_loop_mod(loop, conn->fd, nextev, conn);
1258 connection_del(conn);
1263 int main(int argc, char **argv) {
1268 struct sigaction act;
1269 const char *address;
1270 const char *service;
1273 fprintf(stderr, "Usage: tiny-nghttpd <address> <port> <doc-root>\n");
1280 docrootlen = strlen(docroot);
1282 memset(&act, 0, sizeof(act));
1283 act.sa_handler = SIG_IGN;
1284 sigaction(SIGPIPE, &act, NULL);
1286 rv = server_init(&serv, address, service);
1292 rv = timer_init(&tmr);
1298 rv = io_loop_init(&loop);
1304 rv = nghttp2_session_callbacks_new(&shared_callbacks);
1306 fprintf(stderr, "nghttp2_session_callbacks_new: %s", nghttp2_strerror(rv));
1310 nghttp2_session_callbacks_set_on_begin_headers_callback(
1311 shared_callbacks, on_begin_headers_callback);
1312 nghttp2_session_callbacks_set_on_header_callback(shared_callbacks,
1313 on_header_callback);
1314 nghttp2_session_callbacks_set_on_frame_recv_callback(shared_callbacks,
1315 on_frame_recv_callback);
1316 nghttp2_session_callbacks_set_on_stream_close_callback(
1317 shared_callbacks, on_stream_close_callback);
1318 nghttp2_session_callbacks_set_on_frame_not_send_callback(
1319 shared_callbacks, on_frame_not_send_callback);
1320 nghttp2_session_callbacks_set_send_data_callback(shared_callbacks,
1321 send_data_callback);
1323 rv = io_loop_add(&loop, serv.fd, EPOLLIN, &serv);
1329 rv = io_loop_add(&loop, tmr.fd, EPOLLIN, &tmr);
1337 io_loop_run(&loop, &serv);
1339 nghttp2_session_callbacks_del(shared_callbacks);