From de27a9bf03af1b256969e3846d431458845f91f0 Mon Sep 17 00:00:00 2001
From: Tatsuhiro Tsujikawa
For HEADERS, PUSH_PROMISE and DATA frames, this callback may be +called after stream is closed (see +nghttp2_on_stream_close_callback). The application should +check that stream is still alive using its own stream management or +nghttp2_session_get_stream_user_data().
+Only HEADERS and DATA frame can signal the end of incoming data. If +frame->hd.flags & NGHTTP2_FLAG_END_STREAM is nonzero, the +frame is the last frame from the remote peer in this stream.
The implementation of this function must return 0 if it succeeds. If nonzero value is returned, it is treated as fatal error and nghttp2_session_recv() and nghttp2_session_mem_recv() @@ -1350,9 +1358,6 @@ third argument passed in to the call to
If frame is HEADERS or PUSH_PROMISE, the nva and nvlen member of their data structure are always NULL and 0 respectively.
-If this callback is called, nghttp2_on_header_callback and -nghttp2_on_end_headers_callback will not be called for this -frame.
The implementation of this function must return 0 if it succeeds. If nonzero is returned, it is treated as fatal error and nghttp2_session_recv() and nghttp2_session_send() functions @@ -1444,21 +1449,6 @@ succeeds. If nonzero is returned, it is treated as fatal error and immediately return NGHTTP2_ERR_CALLBACK_FAILURE.
-Callback function invoked when the request from the remote peer is -received. In other words, the frame with END_STREAM flag set is -received. In HTTP, this means HTTP request, including request -body, is fully received. The user_data pointer is the third -argument passed in to the call to nghttp2_session_client_new() or -nghttp2_session_server_new().
-The implementation of this function must return 0 if it -succeeds. If nonzero is returned, it is treated as fatal error and -nghttp2_session_recv() and nghttp2_session_send() functions -immediately return NGHTTP2_ERR_CALLBACK_FAILURE.
-Callback function invoked when the reception of header block in +HEADERS or PUSH_PROMISE is started. Each header name/value pair +will be emitted by nghttp2_on_header_callback.
+The frame->hd.flags may not have +NGHTTP2_FLAG_END_HEADERS flag set, which indicates that one +or more CONTINUATION frames are involved. But the application does +not need to care about that because the header name/value pairs are +emitted transparently regardless of CONTINUATION frames.
+The implementation of this function must return 0 if it succeeds or +NGHTTP2_ERR_CALLBACK_FAILURE. If nonzero value other than +NGHTTP2_ERR_CALLBACK_FAILURE is returned, it is treated as +if NGHTTP2_ERR_CALLBACK_FAILURE is returned. If +NGHTTP2_ERR_CALLBACK_FAILURE is returned, +nghttp2_session_mem_recv() function will immediately return +NGHTTP2_ERR_CALLBACK_FAILURE.
+The name may be NULL if the namelen is 0. The same thing can be said about the value.
If the application uses nghttp2_session_mem_recv(), it can return @@ -1501,7 +1513,7 @@ region included in the input bytes.
Returning NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE will close the stream by issuing RST_STREAM with NGHTTP2_INTERNAL_ERROR. In this case, -nghttp2_on_end_headers_callback will not be invoked.
+nghttp2_on_frame_recv_callback will not be invoked.The implementation of this function must return 0 if it succeeds. It may return NGHTTP2_ERR_PAUSE or NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE. For other critical @@ -1513,25 +1525,6 @@ the other nonzero value is returned, it is treated as immediately return NGHTTP2_ERR_CALLBACK_FAILURE.
Callback function invoked when all header name/value pairs are -processed or after the header decompression error is detected. If -the error_code is NGHTTP2_NO_ERROR, it indicates the -header decompression succeeded. Otherwise, error prevented the -completion of the header decompression. In this case, the library -will handle the error by either transmitting RST_STREAM or GOAWAY -and terminate session.
-If the error_code is not NGHTTP2_NO_ERROR, then -nghttp2_on_request_recv_callback will not called for this -frame if the frame is HEADERS.
-The implementation of this function must return 0 if it -succeeds. If nonzero value is returned, it is treated as fatal -error and nghttp2_session_recv() and nghttp2_session_mem_recv() -functions immediately return NGHTTP2_ERR_CALLBACK_FAILURE.
-Callback function invoked when the stream is closed.
Callback function invoked when request from the remote peer is -received.
-Callback function invoked when the reception of header block in +HEADERS or PUSH_PROMISE is started.
+Callback function invoked when all header name/value pairs are -processed.
-np`a`& z^Y@R(cN^4Am)NFiaN8^!VbRvssbRem$HiIDxI1UTS;~qB2M4j})8JSoKkhy!jD&g@ z$}z;8^3u?W>B0${CRw_=Wp;;W5EEhZA@wi*yV%COfPV-qbLO8EmggxPavGwdI)n2S zfBrVRo2BrXeR~ukpi1y%UnMm{AfaGos~dv$nc#dzXGv@Gh2cHMC7Y-ls%wZ1Jq*Ao z0py9o4?=n#8nwp~#A(4KzZL^n(}evjIf#~Nkj^-uHnN`$Yx(yaw}|5q@Hzo^Vd2%a zEV@z6H-FgS%Fy8-J%l2#iGN^gTdsu|yHUT*0_CFGoucwCk0$)y&1|r|fKumUz2TXT zx7}K{OEmhG*bab^Owl1`ZQ0gyUJ#bunj}aqO?6eoSDR$1Gh04aXHkjvXV~RdghKt5 zp{}VqnFYOno6Ri^%OM)p1xjBRgj?y)4`LKoWPf+;5OrjsK-Ae`61?iBX1!v=|GGxW z4rz|4a}yvZm^mtoLdG!Wa)RN_kz{$4=27`B!5|v*=fVAs?bDpK88M4|LO$4T7k{SZ zQq|yGOhaPh3I=mthGBI`0gWqx8fG(7x@GN`Eg7y?O-T&w_iFx&4=mmf1~ z|9=g#_j!ugj&AC%4&-^j6>;e_w8ykIWP`14s^<_i ;1pY)shN}3&Y z)lqO*oij`etyrwBOSc5-JV!e?sikMzoZvTU __yEY=406@Qeavdxmlh*{&O&P4MJYMtO-VWKAbK z?giQbDFSP)3f?|P-~$DglEeBAS7t|Sy?&8adB9WA4<$H3&pupzLoCmgC=^Z8LFs>u z(1HAFd4`5nAtR!yw7(V6F9r}6 5V_oST?htx$NXZFC1)H9MuC+G}qE$$_OnydzP& zU06RxbrIma_Gwa`Fpg-HB_>mW03BvF#6D#G!sA7@;~ohTIXvq9u~c1ZlYhMFzTsV9 z;Qh$?q}USM>2{5b7h GB8oI3Ea)V j5E5x#j_hq !~%? zb-5^$?L5|73oB4U63pts7ubH!FD1{6#wkQT9nd|TE9zPT1j@2?_ib+LU$SR&ZGNNr zW78K4g83~oK_f|({7BNv|9^QgcYjHVY-Y)|Q9*@^A+Ki+QdM3;ZC)3;_R`Q)l3(3n zyJ>wZ;dhhi;wNP-0%x=$B-{e!qHwIAap5>@*Gj15z6H|MW0}COvP15NOfJ?(xHOG)sDGedTk5O&MpXl1 zTYCd4jux7_>3FLZefCz!o0YD2E{djx4GfY%hCn`g_JVv-q9oB7-A3-@GPaT27=_2S z_Wq`M2_Z#Hu6&{nh&oDpL4%33mMgL%OOpc0K|Xp?f|bIiWfF 7)X ztC(vSRU+-g+U}+iAb%wDJ-A_ONgJfoG5Uxe-{0M!De~a{Q2Yv_ma)iIcvp V{W^0-df$`pV4bYR(yMg3p9744%X z3ybJDeX#vo_n6l{unQ0DQ+7C3Renc#UeS_OTE=NuSDZ#X%YRr@B|M|A@Cp7(>pTuC zwu-)-*y)88tG`xZo)m1~zVy%4=TG*-qwnwTC~;kC+Fy0cG^pq6J`sz;sNgr&^xx0x z@<{W7pOTVk**Rs|+?V=Q*;zY>Y0WUtmaHs=IZ>FVFJbhf)ZmwW5uO;$xG+A8hyP)% zd?dO-T)K9)yMGxQ`xPGXebavM`enX;ZxQO5-!Tq?%^q18vtqo->MdupCRbIG9ZJfw zs(AbO`F?|@?GxA5En%DEA}rduCbgU=;`ulWT6Y&LIxAUm <3< zLOaHoQ(jvpv3)pU+a_z@bnO1{EMg;UKJf50e3RQ)lz$L`Rl&lO#)^W&A@dj&)j6E6 z{Ppj%yZsbCv)>;@45$))+1FWv5J)K4xcZi$eI_}d$yw6Id}(-3amgk6mYN1)LpK93 z$pCqh_=AvMhDP17L@6(s;@eUHYnrj2l>pI-M|{Bnb&-8 jG`43(~C&=La#0jDO@O(>9zZYz%ji2x(lg11}K8Y6ht! zD<`#05&^E~cg{s&(UiSO_R$_fUL?Fosy7AB(E4f_z`w9PFIblYi|8g~fZIET+Kx|k zORz5kk5m_)fiDjm_=X@rzUx@1qi9JdvVe=-h^WB9=HXY8L+Rb85Ugq}WCI9=*?u1s zB7Zti`)?gPBs5|>rfvEru H+Z{cspuYdD8k^qWFV znMT3Ng8f@F*s`Lk=nJsqn0q(c4q6mBJD=_dPziy>aK`Jvb%lm#^VY*CrIxParBTU> zGbqE1z24#SXi_6_N=O8fK{oIM1vuj`EPv5>Xn?$CRFdYwgkh2`Fe;IY0Hya(@c}1H zD{u*(|Eh>PdDUweYftY$o*;E*BZtA;=LCG9!Kc)UOOHs4BewZ|$?GB#NgQHo@fE@G z%`I^PU!labeGP5+H9-gRlM`45R_IK)u-5%nCchYfUsSLY>_P?i3Rc#Db;4!1WPcqw zSIg%y_R6XK$*L6?(bs`o*8|I6q>mT2WRyFNf|Zzdp!+y2)X;ij8;uAOskSveY`yKp zi0(cFTSyM%vZm?LxLsO5CKVOnyzyyPpD>PS#VaOLfjS`?M#y4%OQfg)$Y8))_o++% z3Fdo=S0{-#Tu?{3CBuuQ(qnwe#DCdfZIo}gf4naOcCb~@`B<8V6iFb~HI#I|S_M`3 zP#sl(5gu#;4~L5Hihy(uaEr$@eG5vynW&&!>-L&hhzDJ=0m5yl$k)~b2UbyDS1-H< z@&mNCW=);JIb>kbNHIJ(#C{Zp(Pt-c1rOS)s3aVB#J8)&D8(gS6Zlxd-GAg&1L?4G z=h!H%GcKz87^yXcV=w&@wQfUK@1>vV+TUq7utsF&bxQQoqd-Vep;bWI=mC(8KZp=(0|emck~1eOm+4n Z)Bu z!zQsxpC^me=1aKx-0Xj?5QS4oj0?wc?^Z$h4qc75SgQnnoga!YW`An2K0%pz+(HHQ zgQGv`&s4V{wvA`t>1gd}+Fr2P(EDJ69Od+lbXhdDL}8Q!D(v#sWf|l>6fH~6=$>+~ zfU#ZW)+ju-bu*y${)7@|xloNJAety0)D9-nx>*b>bDot*dh*dl6s%ab9h)djXf@F% zP-1fxD^5u!<}#PY=zr3*`>3a{&|{Oy$ajqgscsmc5?h(Nke`qETRi-FEQIFbqlu>c zCPt%}k-hT1wCN8DH+Xm+!ZA{;@R`5WC8@xj)6kl6L^n*Mouz&S;wG+0pf%6C5eHV; z;VyU$@rG+vzQ5^}Ga!1W@Ta)Q8P!E0=uyi7QAs_iyf^~fY=6`+J>fzF+vW@pOpVF* zb1rqA_}c y+KSt`|HApCbqd{_>Qv$r%PY0y$VB>FsjH+PscDd`@PF~ylVx<`6cX&QAj2-7A} zA87PiBnm4ZaR~v=#6l0cY6_eH+^7v4V6J&Z@DU*6#KQgj@UAq kI#9vxb}g!C$P;YTc*W2ZC}5{NgpBMij?QfSKg^@4O@ callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback; callbacks.on_stream_close_callback = on_stream_close_callback; callbacks.on_header_callback = on_header_callback; - callbacks.on_end_headers_callback = on_end_headers_callback; + callbacks.on_begin_headers_callback = on_begin_headers_callback; nghttp2_session_client_new(&session_data->session, &callbacks, session_data); } @@ -508,29 +508,6 @@ unnecessary to compare them, but real applications surely deal with multiple streams, and stream_user_data is very handy to identify which HEADERS we are seeing in the callback. Therefore we just show how to use it here. - The on_frame_recv_callback() function is invoked when a frame is -received from the remote peer:
---static int on_frame_recv_callback(nghttp2_session *session, - const nghttp2_frame *frame, void *user_data) -{ - http2_session_data *session_data = (http2_session_data*)user_data; - switch(frame->hd.type) { - case NGHTTP2_HEADERS: - if(frame->headers.cat == NGHTTP2_HCAT_RESPONSE && - session_data->stream_data->stream_id == frame->hd.stream_id) { - fprintf(stderr, "Response headers for stream ID=%d:\n", - frame->hd.stream_id); - } - break; - } - return 0; -} -In this tutorial, we are just interested in the HTTP response -HEADERS. We check te frame type and its category (it should be -NGHTTP2_HCAT_RESPONSE for HTTP response HEADERS). Also check -its stream ID.
Each request header name/value pair is emitted via on_header_callback function:
static int on_header_callback(nghttp2_session *session, @@ -555,18 +532,16 @@ its stream ID.In this turotial, we just print the name/value pair.
After all name/value pairs are emitted for a frame, -on_end_headers_callback function is called:
-static int on_end_headers_callback(nghttp2_session *session, - const nghttp2_frame *frame, - nghttp2_error_code error_code, - void *user_data) +on_frame_recv_callback function is called: +-static int on_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { http2_session_data *session_data = (http2_session_data*)user_data; switch(frame->hd.type) { case NGHTTP2_HEADERS: if(frame->headers.cat == NGHTTP2_HCAT_RESPONSE && session_data->stream_data->stream_id == frame->hd.stream_id) { - fprintf(stderr, "All headers received with error_code=%d\n", error_code); + fprintf(stderr, "All headers received\n"); } break; } @@ -574,8 +549,10 @@ its stream ID. }This callback may be called prematurely because of errors (e.g., -header decompression failure) which is indicated by error_code.
+In this tutorial, we are just interested in the HTTP response +HEADERS. We check te frame type and its category (it should be +NGHTTP2_HCAT_RESPONSE for HTTP response HEADERS). Also check +its stream ID.
The on_data_chunk_recv_callback() function is invoked when a chunk of data is received from the remote peer:
@@ -534,25 +534,22 @@ pending data when output buffer becomes empty.static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, @@ -859,30 +836,11 @@ here. return 0; } -/* nghttp2_on_end_headers_callback: Called when nghttp2 library emits - all header name/value pairs, or may be called prematurely because - of errors which is indicated by |error_code|. */ -static int on_end_headers_callback(nghttp2_session *session, - const nghttp2_frame *frame, - nghttp2_error_code error_code, - void *user_data) -{ - http2_session_data *session_data = (http2_session_data*)user_data; - switch(frame->hd.type) { - case NGHTTP2_HEADERS: - if(frame->headers.cat == NGHTTP2_HCAT_RESPONSE && - session_data->stream_data->stream_id == frame->hd.stream_id) { - fprintf(stderr, "All headers received with error_code=%d\n", error_code); - } - break; - } - return 0; -} - -/* nghttp2_on_frame_recv_callback: Called when nghttp2 library - received a frame from the remote peer. */ -static int on_frame_recv_callback(nghttp2_session *session, - const nghttp2_frame *frame, void *user_data) +/* nghttp2_on_begin_headers_callback: Called when nghttp2 library gets + started to receive header block. */ +static int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) { http2_session_data *session_data = (http2_session_data*)user_data; switch(frame->hd.type) { @@ -897,6 +855,23 @@ here. return 0; } +/* nghttp2_on_frame_recv_callback: Called when nghttp2 library + received a complete frame from the remote peer. */ +static int on_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) +{ + http2_session_data *session_data = (http2_session_data*)user_data; + switch(frame->hd.type) { + case NGHTTP2_HEADERS: + if(frame->headers.cat == NGHTTP2_HCAT_RESPONSE && + session_data->stream_data->stream_id == frame->hd.stream_id) { + fprintf(stderr, "All headers received\n"); + } + break; + } + return 0; +} + /* nghttp2_on_data_chunk_recv_callback: Called when DATA frame is received from the remote peer. In this implementation, if the frame is meant to the stream we initiated, print the received data in @@ -989,7 +964,7 @@ here. callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback; callbacks.on_stream_close_callback = on_stream_close_callback; callbacks.on_header_callback = on_header_callback; - callbacks.on_end_headers_callback = on_end_headers_callback; + callbacks.on_begin_headers_callback = on_begin_headers_callback; nghttp2_session_client_new(&session_data->session, &callbacks, session_data); } diff --git a/tutorial-server.html b/tutorial-server.html index 90ff26ac..d9cf7bcf 100644 --- a/tutorial-server.html +++ b/tutorial-server.html @@ -367,9 +367,9 @@ these 2 functions later. callbacks.send_callback = send_callback; callbacks.on_frame_recv_callback = on_frame_recv_callback; - callbacks.on_request_recv_callback = on_request_recv_callback; callbacks.on_stream_close_callback = on_stream_close_callback; callbacks.on_header_callback = on_header_callback; + callbacks.on_begin_headers_callback = on_begin_headers_callback; nghttp2_session_server_new(&session_data->session, &callbacks, session_data); }We have already described about nghttp2 callback send_callback(). Let’s describe remaining nghttp2 callbacks we setup in initialize_nghttp2_setup() function.
-The on_frame_recv_callback() function is invoked when a frame is -received from the remote peer:
-static int on_frame_recv_callback(nghttp2_session *session, - const nghttp2_frame *frame, void *user_data) +The on_begin_headers_callback() function is invoked when reception +of header block in HEADERS or PUSH_PROMISE frame is started:
+@@ -567,7 +564,7 @@ order to get the object without searching through doubly linked list.static int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) { http2_session_data *session_data = (http2_session_data*)user_data; http2_stream_data *stream_data; - switch(frame->hd.type) { - case NGHTTP2_HEADERS: - if(frame->headers.cat != NGHTTP2_HCAT_REQUEST) { - break; - } - stream_data = create_http2_stream_data(session_data, frame->hd.stream_id); - nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, - stream_data); - break; - default: - break; + + if(frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; } + stream_data = create_http2_stream_data(session_data, frame->hd.stream_id); + nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, + stream_data); return 0; }In this example server, we want to serve files relative to the current working directory the program was invoked. Each header name/value pair is emitted via on_header_callback function, which is called after -on_frame_recv_callback():
+on_begin_headers_callback():static int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, @@ -601,59 +598,37 @@ is emitted via on_header_callback requested path in http2_stream_data object. In this example program, we ignore :method header field and always treat the request as GET request. -It is ok for the server to start sending response in this callback (or -in nghttp2_on_end_headers_callback(), which is not used in this -tutorial). In this example, we defer it to -on_request_recv_callback() function.
-The on_request_recv_callback() function is invoked when all HTTP -request, including entity body, was received:
-static int on_request_recv_callback(nghttp2_session *session, - int32_t stream_id, void *user_data) +The on_frame_recv_callback() function is invoked when a frame is +fully received:
+static int on_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { - int fd; http2_session_data *session_data = (http2_session_data*)user_data; http2_stream_data *stream_data; - nghttp2_nv hdrs[] = { - MAKE_NV(":status", "200") - }; - char *rel_path; - - stream_data = (http2_stream_data*)nghttp2_session_get_stream_user_data - (session, stream_id); - if(!stream_data->request_path) { - if(error_reply(session, stream_data) != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; + switch(frame->hd.type) { + case NGHTTP2_DATA: + case NGHTTP2_HEADERS: + /* Check that the client request has finished */ + if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + stream_data = nghttp2_session_get_stream_user_data(session, + frame->hd.stream_id); + /* For DATA and HEADERS frame, this callback may be called after + on_stream_close_callback. Check that stream still alive. */ + if(!stream_data) { + return 0; + } + return on_request_recv(session, session_data, stream_data); } - return 0; - } - fprintf(stderr, "%s GET %s\n", session_data->client_addr, - stream_data->request_path); - if(!check_path(stream_data->request_path)) { - if(error_reply(session, stream_data) != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - return 0; - } - for(rel_path = stream_data->request_path; *rel_path == '/'; ++rel_path); - fd = open(rel_path, O_RDONLY); - if(fd == -1) { - if(error_reply(session, stream_data) != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - return 0; - } - stream_data->fd = fd; - - if(send_response(session, stream_id, hdrs, ARRLEN(hdrs), fd) != 0) { - close(fd); - return NGHTTP2_ERR_CALLBACK_FAILURE; + break; + default: + break; } return 0; }First we retrieve http2_stream_data object associated to the -stream in on_frame_recv_callback(). It is done using +stream in on_begin_headers_callback(). It is done using nghttp2_session_get_stream_user_data(). If the requested path cannot be served for some reasons (e.g., file is not found), we send 404 response, which is done in error_reply(). Otherwise, we open @@ -1056,56 +1031,6 @@ stream is about to close and we no longer use that object.
return res; } -/* nghttp2_on_header_callback: Called when nghttp2 library emits - single header name/value pair. */ -static int on_header_callback(nghttp2_session *session, - const nghttp2_frame *frame, - const uint8_t *name, size_t namelen, - const uint8_t *value, size_t valuelen, - void *user_data) -{ - http2_stream_data *stream_data; - const char PATH[] = ":path"; - switch(frame->hd.type) { - case NGHTTP2_HEADERS: - if(frame->headers.cat != NGHTTP2_HCAT_REQUEST) { - break; - } - stream_data = nghttp2_session_get_stream_user_data(session, - frame->hd.stream_id); - if(stream_data->request_path) { - break; - } - if(namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) { - size_t j; - for(j = 0; j < valuelen && value[j] != '?'; ++j); - stream_data->request_path = percent_decode(value, j); - } - break; - } - return 0; -} - -static int on_frame_recv_callback(nghttp2_session *session, - const nghttp2_frame *frame, void *user_data) -{ - http2_session_data *session_data = (http2_session_data*)user_data; - http2_stream_data *stream_data; - switch(frame->hd.type) { - case NGHTTP2_HEADERS: - if(frame->headers.cat != NGHTTP2_HCAT_REQUEST) { - break; - } - stream_data = create_http2_stream_data(session_data, frame->hd.stream_id); - nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, - stream_data); - break; - default: - break; - } - return 0; -} - static ssize_t file_read_callback (nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, int *eof, @@ -1174,6 +1099,53 @@ stream is about to close and we no longer use that object. return 0; } +/* nghttp2_on_header_callback: Called when nghttp2 library emits + single header name/value pair. */ +static int on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + void *user_data) +{ + http2_stream_data *stream_data; + const char PATH[] = ":path"; + switch(frame->hd.type) { + case NGHTTP2_HEADERS: + if(frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + break; + } + stream_data = nghttp2_session_get_stream_user_data(session, + frame->hd.stream_id); + if(stream_data->request_path) { + break; + } + if(namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) { + size_t j; + for(j = 0; j < valuelen && value[j] != '?'; ++j); + stream_data->request_path = percent_decode(value, j); + } + break; + } + return 0; +} + +static int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) +{ + http2_session_data *session_data = (http2_session_data*)user_data; + http2_stream_data *stream_data; + + if(frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + stream_data = create_http2_stream_data(session_data, frame->hd.stream_id); + nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, + stream_data); + return 0; +} + /* Minimum check for directory traversal. Returns nonzero if it is safe. */ static int check_path(const char *path) @@ -1186,19 +1158,16 @@ stream is about to close and we no longer use that object. !ends_with(path, "/..") && !ends_with(path, "/."); } -static int on_request_recv_callback(nghttp2_session *session, - int32_t stream_id, void *user_data) +static int on_request_recv(nghttp2_session *session, + http2_session_data *session_data, + http2_stream_data *stream_data) { int fd; - http2_session_data *session_data = (http2_session_data*)user_data; - http2_stream_data *stream_data; nghttp2_nv hdrs[] = { MAKE_NV(":status", "200") }; char *rel_path; - stream_data = (http2_stream_data*)nghttp2_session_get_stream_user_data - (session, stream_id); if(!stream_data->request_path) { if(error_reply(session, stream_data) != 0) { return NGHTTP2_ERR_CALLBACK_FAILURE; @@ -1223,13 +1192,40 @@ stream is about to close and we no longer use that object. } stream_data->fd = fd; - if(send_response(session, stream_id, hdrs, ARRLEN(hdrs), fd) != 0) { + if(send_response(session, stream_data->stream_id, hdrs, + ARRLEN(hdrs), fd) != 0) { close(fd); return NGHTTP2_ERR_CALLBACK_FAILURE; } return 0; } +static int on_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) +{ + http2_session_data *session_data = (http2_session_data*)user_data; + http2_stream_data *stream_data; + switch(frame->hd.type) { + case NGHTTP2_DATA: + case NGHTTP2_HEADERS: + /* Check that the client request has finished */ + if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + stream_data = nghttp2_session_get_stream_user_data(session, + frame->hd.stream_id); + /* For DATA and HEADERS frame, this callback may be called after + on_stream_close_callback. Check that stream still alive. */ + if(!stream_data) { + return 0; + } + return on_request_recv(session, session_data, stream_data); + } + break; + default: + break; + } + return 0; +} + static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, nghttp2_error_code error_code, @@ -1250,9 +1246,9 @@ stream is about to close and we no longer use that object. callbacks.send_callback = send_callback; callbacks.on_frame_recv_callback = on_frame_recv_callback; - callbacks.on_request_recv_callback = on_request_recv_callback; callbacks.on_stream_close_callback = on_stream_close_callback; callbacks.on_header_callback = on_header_callback; + callbacks.on_begin_headers_callback = on_begin_headers_callback; nghttp2_session_server_new(&session_data->session, &callbacks, session_data); }