exchange.d 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708
  1. module vibe.http.internal.http2.exchange;
  2. import vibe.http.internal.http2.multiplexing;
  3. import vibe.http.internal.http2.settings;
  4. import vibe.http.internal.http2.server : HTTP2ConnectionStream, HTTP2StreamState;
  5. import vibe.http.internal.http2.hpack.hpack;
  6. import vibe.http.internal.http2.hpack.tables;
  7. import vibe.http.internal.http2.frame;
  8. import vibe.http.common;
  9. import vibe.http.status;
  10. import vibe.http.server;
  11. import vibe.container.internal.utilallocator;
  12. import vibe.core.log;
  13. import vibe.core.stream;
  14. import vibe.core.core;
  15. import vibe.internal.interfaceproxy;
  16. import vibe.stream.tls;
  17. import vibe.internal.array;
  18. import vibe.internal.string : formatAlloc, icmp2;
  19. import vibe.stream.wrapper : ConnectionProxyStream, createConnectionProxyStream, createConnectionProxyStreamFL;
  20. import vibe.stream.memory;
  21. import vibe.inet.url;
  22. import vibe.inet.message;
  23. import std.range;
  24. import std.string;
  25. import std.conv;
  26. import std.traits;
  27. import std.typecons;
  28. import std.datetime;
  29. import std.exception;
  30. import std.format;
  31. import std.algorithm.iteration;
  32. import std.algorithm.mutation;
  33. import std.algorithm.searching;
  34. import std.algorithm.comparison;
  35. /**
  36. * HTTP/2 message exchange module as documented in:
  37. * RFC 7540 (HTTP/2) section 8
  38. */
  39. enum StartLine { REQUEST, RESPONSE };
  40. private alias H2F = HTTP2HeaderTableField;
  41. alias DataOutputStream = MemoryOutputStream;
  42. /// accepts a HTTP/1.1 header list, converts it to an HTTP/2 header frame and encodes it
  43. ubyte[] buildHeaderFrame(alias type)(string statusLine, InetHeaderMap headers,
  44. HTTP2ServerContext context, ref IndexingTable table, scope IAllocator alloc, bool
  45. isTLS = true) @safe
  46. {
  47. // frame header + frame payload
  48. FixedAppender!(ubyte[], 9) hbuf;
  49. auto pbuf = AllocAppender!(ubyte[])(alloc);
  50. auto res = AllocAppender!(ubyte[])(alloc);
  51. // split the start line of each req / res into pseudo-headers
  52. convertStartMessage(statusLine, pbuf, table, type, isTLS);
  53. // "Host" header does not exist in HTTP/2, use ":authority" pseudo-header
  54. if("Host" in headers) {
  55. headers[":authority"] = headers["Host"];
  56. headers.remove("Host");
  57. }
  58. foreach(k,v; headers.byKeyValue) {
  59. H2F(k.toLower,v).encodeHPACK(pbuf, table);
  60. }
  61. // TODO padding
  62. if(context.next_sid == 0) context.next_sid = 1;
  63. hbuf.createHTTP2FrameHeader(cast(uint)pbuf.data.length, HTTP2FrameType.HEADERS, 0x0, context.next_sid);
  64. res.put(hbuf.data);
  65. res.put(pbuf.data);
  66. return res.data;
  67. }
  68. /// DITTO for first request in case of h2c
  69. ubyte[] buildHeaderFrame(alias type)(string statusLine, InetHeaderMap headers,
  70. HTTP2ServerContext context, scope IAllocator alloc) @trusted
  71. {
  72. return buildHeaderFrame!type(statusLine, headers, context, context.table, alloc);
  73. }
  74. /// generates an HTTP/2 pseudo-header representation to encode a HTTP/1.1 start message line
  75. private void convertStartMessage(T)(string src, ref T dst, ref IndexingTable table, StartLine type, bool isTLS = true) @safe
  76. {
  77. void toPseudo(string buf) @safe
  78. {
  79. // exclude protocol version (not needed in HTTP/2)
  80. if(buf != "HTTP/1.1" && buf != "HTTP/2")
  81. {
  82. if(type == StartLine.REQUEST) { // request
  83. // request-line = method SP request-target SP HTTP-version CRLF
  84. try {
  85. auto method = httpMethodFromString(buf); // might throw
  86. H2F(":method", method).encodeHPACK(dst, table);
  87. } catch(Exception e) {
  88. H2F(":scheme", (isTLS ? "https" : "http")).encodeHPACK(dst, table);
  89. H2F(":path", buf).encodeHPACK(dst, table);
  90. }
  91. } else if(type == StartLine.RESPONSE) { // response (status-line)
  92. // status-line = HTTP-version SP status-code SP reason-phrase CRLF
  93. static foreach(st; __traits(allMembers, HTTPStatus)) {
  94. if(buf.isNumeric && __traits(getMember, HTTPStatus, st) == buf.to!int) {
  95. mixin("H2F(\":status\",HTTPStatus."~st~").encodeHPACK(dst, table); return;");
  96. }
  97. }
  98. }
  99. }
  100. }
  101. // consider each chunk of the start message line
  102. src.strip("\r\n").splitter(' ').each!(s => toPseudo(s));
  103. }
  104. unittest {
  105. import std.experimental.allocator;
  106. import std.experimental.allocator.mallocator;
  107. HTTP2Settings settings;
  108. HTTPServerContext ctx;
  109. auto context = new HTTP2ServerContext(ctx, settings);
  110. auto table = IndexingTable(settings.headerTableSize);
  111. scope alloc = new RegionListAllocator!(shared(Mallocator), false)(1024, Mallocator.instance);
  112. string statusline = "GET / HTTP/2\r\n\r\n";
  113. InetHeaderMap hmap;
  114. hmap["Host"] = "www.example.com";
  115. ubyte[] expected = [0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1 , 0xe3, 0xc2 , 0xe5, 0xf2 , 0x3a, 0x6b , 0xa0, 0xab , 0x90, 0xf4 , 0xff];
  116. // [9..$] excludes the HTTP/2 Frame header
  117. auto res = buildHeaderFrame!(StartLine.REQUEST)(statusline, hmap, context, table, alloc,
  118. false)[9..$];
  119. assert(res == expected);
  120. statusline = "HTTP/2 200 OK";
  121. InetHeaderMap hmap1;
  122. expected = [0x88];
  123. res = buildHeaderFrame!(StartLine.RESPONSE)(statusline, hmap1, context, table, alloc,
  124. false)[9..$];
  125. assert(res == expected);
  126. }
  127. /* ======================================================= */
  128. /* HTTP/2 REQUEST HANDLING */
  129. /* ======================================================= */
  130. /** Similar to originalHandleRequest, adapted to HTTP/2
  131. * The request is converted to HTTPServerRequest through parseHTTP2RequestHeader
  132. * once the HTTPServerResponse is built, HEADERS frame and (optionally) DATA Frames are sent
  133. */
  134. bool handleHTTP2Request(UStream)(ref HTTP2ConnectionStream!UStream stream,
  135. TCPConnection tcp_connection, HTTP2ServerContext h2context,
  136. HTTP2HeaderTableField[] headers, ref IndexingTable table, scope IAllocator alloc) @safe
  137. {
  138. import vibe.http.internal.utils : formatRFC822DateAlloc;
  139. SysTime reqtime = Clock.currTime(UTC());
  140. HTTPServerContext listen_info = h2context.h1context;
  141. // initialize request
  142. auto req = () @trusted { return alloc.make!HTTPServerRequest(reqtime, listen_info.bindPort); } ();
  143. scope (exit) () @trusted { alloc.dispose(req); } ();
  144. // store the IP address
  145. req.clientAddress = tcp_connection.remoteAddress;
  146. if (!listen_info.hasVirtualHosts) {
  147. logWarn("Didn't find a HTTP listening context for incoming connection. Dropping.");
  148. return false;
  149. }
  150. // Default to the first virtual host for this listener
  151. HTTPServerContext.VirtualHost context = listen_info.m_virtualHosts[0];
  152. HTTPServerRequestDelegate request_task = context.requestHandler;
  153. HTTPServerSettings settings = context.settings;
  154. // temporarily set to the default settings
  155. req.m_settings = settings;
  156. // Create the response object
  157. InterfaceProxy!ConnectionStream cproxy = tcp_connection;
  158. InterfaceProxy!Stream cstream = stream.connection; // TCPConnection / TLSStream
  159. // check for TLS encryption
  160. bool istls;
  161. static if(is(UStream : TLSStream)) {
  162. istls = true;
  163. } else {
  164. istls = false;
  165. }
  166. req.tls = istls;
  167. if (req.tls) {
  168. static if (HaveNoTLS) assert(false);
  169. else {
  170. static if (is(InterfaceProxy!Stream == Stream))
  171. req.clientCertificate = (cast(TLSStream)stream.connection).peerCertificate;
  172. else static if (is(typeof(stream.connection) : TLSStream))
  173. req.clientCertificate = stream.connection.peerCertificate;
  174. else
  175. assert(false);
  176. }
  177. }
  178. bool parsed = false;
  179. // parse request:
  180. // both status line + headers (already unpacked in `headers`)
  181. // defined in vibe.http.server because of protected struct HTTPServerRequest
  182. parseHTTP2RequestHeader(headers, req);
  183. if(req.host.empty) {
  184. req.host = tcp_connection.localAddress.toString;
  185. }
  186. string reqhost;
  187. ushort reqport = 0;
  188. {
  189. string s = req.host;
  190. enforceHTTP(s.length > 0 || req.httpVersion <= HTTPVersion.HTTP_1_0, HTTPStatus.badRequest, "Missing Host header.");
  191. if (s.startsWith('[')) { // IPv6 address
  192. auto idx = s.indexOf(']');
  193. enforce(idx > 0, "Missing closing ']' for IPv6 address.");
  194. reqhost = s[1 .. idx];
  195. s = s[idx+1 .. $];
  196. } else if (s.length) { // host name or IPv4 address
  197. auto idx = s.indexOf(':');
  198. if (idx < 0) idx = s.length;
  199. enforceHTTP(idx > 0, HTTPStatus.badRequest, "Missing Host header.");
  200. reqhost = s[0 .. idx];
  201. s = s[idx .. $];
  202. }
  203. if (s.startsWith(':')) reqport = s[1 .. $].to!ushort;
  204. }
  205. foreach (ctx; listen_info.m_virtualHosts) {
  206. if (icmp2(ctx.settings.hostName, reqhost) == 0 &&
  207. (!reqport || reqport == ctx.settings.port))
  208. {
  209. context = ctx;
  210. settings = ctx.settings;
  211. request_task = ctx.requestHandler;
  212. break;
  213. }
  214. }
  215. req.m_settings = settings;
  216. auto tcpc = tcp_connection;
  217. auto exchange = () @trusted { return alloc.make!(HTTP2ServerExchange!UStream)(stream,
  218. tcpc, h2context, headers, table, alloc); } ();
  219. scope (exit) () @trusted { alloc.dispose(exchange); } ();
  220. auto res = () @trusted { return alloc.make!HTTPServerResponse(exchange, settings, alloc); } ();
  221. scope (exit) () @trusted { alloc.dispose(res); } ();
  222. res.httpVersion = HTTPVersion.HTTP_2;
  223. // setup compressed output
  224. if (settings.useCompressionIfPossible) {
  225. if (auto pae = "Accept-Encoding" in req.headers) {
  226. if (canFind(*pae, "gzip")) {
  227. res.headers["Content-Encoding"] = "gzip";
  228. } else if (canFind(*pae, "deflate")) {
  229. res.headers["Content-Encoding"] = "deflate";
  230. }
  231. }
  232. }
  233. // handle Expect header
  234. if (auto pv = "Expect" in req.headers) {
  235. if (icmp2(*pv, "100-continue") == 0) {
  236. logTrace("sending 100 continue");
  237. InetHeaderMap hmap;
  238. auto cres = buildHeaderFrame!(StartLine.RESPONSE)(
  239. "HTTP/1.1 100 Continue\r\n\r\n", hmap, h2context, table, alloc, istls);
  240. }
  241. assert(false); // TODO determine if actually used with HTTP/2 (PUSH_PROMISE?)
  242. }
  243. // eagerly parse the URL as its lightweight and defacto @nogc
  244. auto url = URL.parse(req.requestURI);
  245. req.queryString = url.queryString;
  246. req.username = url.username;
  247. req.password = url.password;
  248. req.requestPath = url.path;
  249. // lookup the session
  250. if (settings.sessionStore) {
  251. // use the first cookie that contains a valid session ID in case
  252. // of multiple matching session cookies
  253. foreach (val; req.cookies.getAll(settings.sessionIdCookie)) {
  254. req.session = settings.sessionStore.open(val);
  255. res.m_session = req.session;
  256. if (req.session) break;
  257. }
  258. }
  259. // write default headers
  260. if (req.method == HTTPMethod.HEAD) exchange.m_isHeadResponse = true;
  261. if (settings.serverString.length)
  262. res.headers["Server"] = settings.serverString;
  263. res.headers["Date"] = formatRFC822DateAlloc(reqtime);
  264. if (req.persistent) res.headers["Keep-Alive"] = formatAlloc(alloc, "timeout=%d", settings.keepAliveTimeout.total!"seconds"());
  265. // finished parsing the request
  266. parsed = true;
  267. logTrace("persist: %s", req.persistent);
  268. //keep_alive = req.persistent;
  269. logDebug("Received request on stream ID %d: %s %s", stream.streamId, req.method, req.requestPath);
  270. foreach (k, v; req.headers.byKeyValue)
  271. logDebugV("%s: %s", k, v);
  272. // utility to format the status line
  273. auto statusLine = AllocAppender!string(alloc);
  274. void writeLine(T...)(string fmt, T args)
  275. @safe {
  276. formattedWrite(() @trusted { return &statusLine; } (), fmt, args);
  277. statusLine.put("\r\n");
  278. logTrace(fmt, args);
  279. }
  280. // header frame to be sent
  281. ubyte[] headerFrame;
  282. // handle payload (DATA frame)
  283. auto dataWriter = createDataOutputStream(alloc);
  284. exchange.m_bodyWriter = dataWriter;
  285. h2context.next_sid = stream.streamId;
  286. // run task (writes body)
  287. request_task(req, res);
  288. if(req.method != HTTPMethod.HEAD && dataWriter.data.length > 0) { // HEADERS + DATA
  289. // write the status line
  290. writeLine("%s %d %s",
  291. getHTTPVersionString(res.httpVersion),
  292. res.statusCode,
  293. res.statusPhrase.length ? res.statusPhrase : httpStatusText(res.statusCode));
  294. // build the HEADERS frame
  295. () @trusted {
  296. headerFrame = buildHeaderFrame!(StartLine.RESPONSE)(statusLine.data, res.headers,
  297. h2context, table, alloc, istls);
  298. } ();
  299. // send HEADERS frame
  300. if(headerFrame.length < h2context.settings.maxFrameSize) {
  301. headerFrame[4] += 0x4; // set END_HEADERS flag (sending complete header)
  302. cstream.write(headerFrame);
  303. } else {
  304. // TODO CONTINUATION frames
  305. assert(false);
  306. }
  307. logDebug("Sent HEADERS frame on streamID " ~ stream.streamId.to!string);
  308. auto tlen = dataWriter.data.length;
  309. // multiple DATA Frames might be required
  310. void sendDataTask()
  311. @safe {
  312. logDebug("[DATA] Starting dispatch task");
  313. scope(exit) {
  314. if(stream.state == HTTP2StreamState.HALF_CLOSED_REMOTE) {
  315. stream.state = HTTP2StreamState.CLOSED;
  316. } else {
  317. stream.state = HTTP2StreamState.HALF_CLOSED_LOCAL;
  318. }
  319. }
  320. try {
  321. auto abort = false;
  322. uint done = 0;
  323. // window length
  324. uint wlen = sendWindowLength(h2context.multiplexer,
  325. stream.streamId, h2context.settings.maxFrameSize, tlen);
  326. // until the whole payload is sent
  327. while(done <= tlen) {
  328. auto dataFrame = AllocAppender!(ubyte[])(alloc);
  329. dataFrame.createHTTP2FrameHeader(
  330. wlen,
  331. HTTP2FrameType.DATA,
  332. (done+wlen >= tlen) ? 0x1 : 0x0, // END_STREAM 0x1
  333. stream.streamId
  334. );
  335. // send is over
  336. if(done == tlen) {
  337. logDebug("[DATA] Completed DATA frame dispatch");
  338. // remove task from waiting state
  339. doneCondition(h2context.multiplexer, stream.streamId);
  340. closeStream(h2context.multiplexer, stream.streamId);
  341. break;
  342. }
  343. // wait to resume and retry
  344. if(wlen == 0) {
  345. logDebug("[DATA] Dispatch task waiting for WINDOW_UPDATE");
  346. // after 60 seconds waiting, terminate dispatch
  347. () @trusted {
  348. auto timer = setTimer(600.seconds, {
  349. logDebug("[DATA] timer expired, aborting dispatch");
  350. notifyCondition(h2context.multiplexer);
  351. abort = true;
  352. });
  353. // wait until a new WINDOW_UPDATE is received (or timer expires)
  354. waitCondition(h2context.multiplexer, stream.streamId);
  355. // task resumed: cancel timer
  356. if(!abort) timer.stop;
  357. else return;
  358. } ();
  359. logDebug("[DATA] Dispatch task resumed");
  360. } else {
  361. // write
  362. dataFrame.put(dataWriter.data[done..done+wlen]);
  363. cstream.write(dataFrame.data);
  364. done += wlen;
  365. logDebug("[DATA] Sent frame chunk (%d/%d bytes) on streamID %d",
  366. done, tlen, stream.streamId);
  367. updateWindow(h2context.multiplexer, stream.streamId, wlen);
  368. // return control to the event loop
  369. yield();
  370. }
  371. // compute new window length
  372. wlen = sendWindowLength(h2context.multiplexer,
  373. stream.streamId, h2context.settings.maxFrameSize, tlen - done);
  374. }
  375. } catch (Exception e) {
  376. logException(e, "Failed to send DATA frame");
  377. return;
  378. }
  379. }
  380. // spawn the asynchronous data sender
  381. sendDataTask();
  382. } else if(dataWriter.data.length > 0) { // HEAD response, HEADERS frame, no DATA
  383. // write the status line
  384. writeLine("%s %d %s",
  385. getHTTPVersionString(res.httpVersion),
  386. res.statusCode,
  387. res.statusPhrase.length ? res.statusPhrase : httpStatusText(res.statusCode));
  388. // build the HEADERS frame
  389. () @trusted {
  390. headerFrame = buildHeaderFrame!(StartLine.RESPONSE)(statusLine.data, res.headers,
  391. h2context, table, alloc, istls);
  392. } ();
  393. // send HEADERS frame
  394. if(headerFrame.length < h2context.settings.maxFrameSize) {
  395. headerFrame[4] += 0x5; // set END_HEADERS, END_STREAM flag
  396. cstream.write(headerFrame);
  397. } else {
  398. // TODO CONTINUATION frames
  399. assert(false);
  400. }
  401. logDebug("Sent HEADERS frame on streamID " ~ stream.streamId.to!string);
  402. logDebug("[Data] No DATA frame to send");
  403. if(stream.state == HTTP2StreamState.HALF_CLOSED_REMOTE) {
  404. stream.state = HTTP2StreamState.CLOSED;
  405. } else {
  406. stream.state = HTTP2StreamState.HALF_CLOSED_LOCAL;
  407. }
  408. closeStream(h2context.multiplexer, stream.streamId);
  409. } else { // 404: no DATA for the given path
  410. writeLine("%s %d %s",
  411. "HTTP/2",
  412. 404,
  413. "Not Found");
  414. // build the HEADERS frame
  415. () @trusted {
  416. headerFrame = buildHeaderFrame!(StartLine.RESPONSE)(statusLine.data, res.headers,
  417. h2context, table, alloc, istls);
  418. } ();
  419. if(headerFrame.length < h2context.settings.maxFrameSize) {
  420. headerFrame[4] += 0x5; // set END_HEADERS, END_STREAM flag
  421. cstream.write(headerFrame);
  422. }
  423. logDebug("No response: sent 404 HEADERS frame");
  424. }
  425. return true;
  426. }
  427. uint sendWindowLength(Mux)(ref Mux multiplexer, const uint sid, const uint maxfsize, const ulong len) @safe
  428. {
  429. return min(connectionWindow(multiplexer), streamConnectionWindow(multiplexer, sid), maxfsize, len);
  430. }
  431. void updateWindow(Mux)(ref Mux multiplexer, const uint sid, const ulong sent) @safe
  432. {
  433. auto cw = connectionWindow(multiplexer) - sent;
  434. auto scw = streamConnectionWindow(multiplexer, sid) - sent;
  435. updateConnectionWindow(multiplexer, cw);
  436. updateStreamConnectionWindow(multiplexer, sid, cw);
  437. }
  438. private DataOutputStream createDataOutputStream(IAllocator alloc = vibeThreadAllocator())
  439. @safe nothrow {
  440. return createMemoryOutputStream(alloc);
  441. }
  442. private HeaderOutputStream createHeaderOutputStream(IAllocator alloc = vibeThreadAllocator())
  443. @safe nothrow {
  444. return new HeaderOutputStream(alloc);
  445. }
  446. final class HTTP2ServerExchange(UStream) : HTTPServerExchange {
  447. private {
  448. HTTP2ConnectionStream!UStream* m_stream;
  449. TCPConnection m_connection;
  450. HTTP2ServerContext m_h2context;
  451. HTTP2HeaderTableField[] m_headers;
  452. IndexingTable* m_table;
  453. IAllocator m_allocator;
  454. bool m_isHeadResponse;
  455. OutputStreamProxy m_bodyWriter;
  456. bool m_headerWritten = false;
  457. }
  458. this(ref HTTP2ConnectionStream!UStream stream,
  459. TCPConnection tcp_connection, HTTP2ServerContext h2context,
  460. HTTP2HeaderTableField[] headers, ref IndexingTable table, scope IAllocator alloc)
  461. {
  462. m_stream = &stream;
  463. m_connection = tcp_connection;
  464. m_h2context = h2context;
  465. m_headers = headers;
  466. m_table = &table;
  467. m_allocator = alloc;
  468. }
  469. @property bool isHeadResponse() const { return m_isHeadResponse; }
  470. @property bool headerWritten() const { assert(false); }
  471. @property ulong bytesWritten() const { assert(false); }
  472. @property bool connected() const { assert(false); }
  473. bool waitForConnectionClose(Duration timeout)
  474. {
  475. assert(false);
  476. }
  477. void writeBody(HTTPServerResponse res, RandomAccessStreamProxy stream)
  478. {
  479. stream.pipe(m_bodyWriter);
  480. m_bodyWriter.finalize();
  481. }
  482. void writeBody(HTTPServerResponse res, InputStreamProxy stream, ulong num_bytes = ulong.max)
  483. {
  484. stream.pipe(m_bodyWriter, num_bytes);
  485. m_bodyWriter.finalize();
  486. }
  487. void writeVoidBody(HTTPServerResponse res)
  488. {
  489. m_bodyWriter.finalize();
  490. }
  491. OutputStreamProxy bodyWriter(HTTPServerResponse res)
  492. {
  493. if (!m_headerWritten)
  494. writeHeader();
  495. return m_bodyWriter;
  496. }
  497. ConnectionStream switchProtocol(HTTPServerResponse res, string protocol)
  498. {
  499. assert(false);
  500. }
  501. void switchProtocol(HTTPServerResponse res, string protocol, scope void delegate(scope ConnectionStream) @safe del)
  502. {
  503. assert(false);
  504. }
  505. ConnectionStream connectProxy(HTTPServerResponse res)
  506. {
  507. assert(false);
  508. }
  509. void connectProxy(HTTPServerResponse res, scope void delegate(scope ConnectionStream) @safe del)
  510. {
  511. assert(false);
  512. }
  513. void finalize(HTTPServerResponse res)
  514. {
  515. // ...
  516. }
  517. private void writeHeader()
  518. {
  519. // FIXME: Currently, the header and the body are written after the
  520. // request callback has already returned. This should be changed
  521. // to happen during the callback's execution, just like for
  522. // HTTP/1.x.
  523. assert(!m_headerWritten);
  524. m_headerWritten = true;
  525. }
  526. }
  527. private final class HeaderOutputStream : OutputStream {
  528. @safe:
  529. private {
  530. AllocAppender!(string) m_destination;
  531. }
  532. this(IAllocator alloc)
  533. nothrow {
  534. m_destination = AllocAppender!(string)(alloc);
  535. }
  536. /// An array with all data written to the stream so far.
  537. @property string data() @trusted nothrow { return m_destination.data(); }
  538. /// Resets the stream to its initial state containing no data.
  539. void reset(AppenderResetMode mode = AppenderResetMode.keepData)
  540. @system {
  541. m_destination.reset(mode);
  542. }
  543. /// Reserves space for data - useful for optimization.
  544. void reserve(size_t nbytes)
  545. {
  546. m_destination.reserve(nbytes);
  547. }
  548. size_t write(in string bytes, IOMode)
  549. {
  550. () @trusted { m_destination.put(bytes); } ();
  551. return bytes.length;
  552. }
  553. /// DITTO
  554. size_t write(scope const(ubyte[]) bytes, IOMode)
  555. {
  556. () @trusted { m_destination.put(cast(string)bytes); } ();
  557. return bytes.length;
  558. }
  559. alias write = OutputStream.write;
  560. void flush()
  561. nothrow {
  562. }
  563. void finalize()
  564. nothrow {
  565. }
  566. }
  567. void parseHTTP2RequestHeader(R)(ref R headers, HTTPServerRequest req) @safe
  568. {
  569. import std.algorithm.searching : find, startsWith;
  570. import std.algorithm.iteration : filter;
  571. //Method
  572. req.method = cast(HTTPMethod)headers.find!((h,m) => h.name == m)(":method")[0].value;
  573. //Host
  574. auto host = headers.find!((h,m) => h.name == m)(":authority");
  575. if(!host.empty) req.host = cast(string)host[0].value;
  576. //Path
  577. auto pathstr = cast(string)headers.find!((h,m) => h.name == m)(":path")[0].value;
  578. if(req.tls) req.requestURI = "https://" ~ req.host ~ pathstr;
  579. else req.requestURI = "http://" ~ req.host ~ pathstr;
  580. auto url = URL.parse(req.requestURI);
  581. req.queryString = url.queryString;
  582. req.username = url.username;
  583. req.password = url.password;
  584. req.requestPath = url.path;
  585. //HTTP version
  586. req.httpVersion = HTTPVersion.HTTP_2;
  587. //headers
  588. foreach(h; headers.filter!(f => !f.name.startsWith(":"))) {
  589. req.headers[h.name] = cast(string)h.value;
  590. }
  591. }