server.d 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. /**
  2. A HTTP 1.1/1.0 server implementation.
  3. Copyright: © 2012-2024 Sönke Ludwig
  4. License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
  5. Authors: Sönke Ludwig, Jan Krüger, Ilya Shipunov
  6. */
  7. module vibe.http.internal.http1.server;
  8. import vibe.container.internal.appender : FixedAppender;
  9. import vibe.core.log;
  10. import vibe.core.net;
  11. import vibe.core.stream;
  12. import vibe.http.common;
  13. import vibe.http.server;
  14. import vibe.inet.message;
  15. import vibe.inet.url;
  16. import vibe.internal.freelistref;
  17. import vibe.internal.string : formatAlloc, icmp2;
  18. import core.time;
  19. import std.datetime : Clock, SysTime, UTC;
  20. import std.encoding : sanitize;
  21. import std.exception : enforce;
  22. import std.format : format, formattedWrite;
  23. /** Treats an existing connection as an HTTP connection and processes incoming
  24. requests.
  25. After all requests have been processed, the connection will be closed and
  26. the function returns to the caller.
  27. Params:
  28. connection = The stream to treat as an incoming HTTP client connection.
  29. context = Information about the incoming listener and available
  30. virtual hosts
  31. */
  32. void handleHTTP1Connection(TLSStreamType)(TCPConnection connection, TLSStreamType tls_stream, StreamProxy http_stream, HTTPServerContext context)
  33. @safe {
  34. while (!connection.empty) {
  35. HTTPServerSettings settings;
  36. bool keep_alive;
  37. static if (HaveNoTLS) {} else {
  38. // handle oderly TLS shutdowns
  39. if (tls_stream && tls_stream.empty) break;
  40. }
  41. () @trusted {
  42. scope request_allocator = createRequestAllocator();
  43. scope (exit) freeRequestAllocator(request_allocator);
  44. handleRequest!TLSStreamType(http_stream, connection, context, settings, keep_alive, request_allocator);
  45. } ();
  46. if (!keep_alive) { logTrace("No keep-alive - disconnecting client."); break; }
  47. logTrace("Waiting for next request...");
  48. // wait for another possible request on a keep-alive connection
  49. if (!connection.waitForData(settings.keepAliveTimeout)) {
  50. if (!connection.connected) logTrace("Client disconnected.");
  51. else logDebug("Keep-alive connection timed out!");
  52. break;
  53. }
  54. }
  55. }
  56. private bool handleRequest(TLSStreamType, Allocator)(StreamProxy http_stream, TCPConnection tcp_connection, HTTPServerContext listen_info, ref HTTPServerSettings settings, ref bool keep_alive, scope Allocator request_allocator)
  57. @safe {
  58. import vibe.container.internal.utilallocator : make, dispose;
  59. import vibe.http.internal.utils : formatRFC822DateAlloc;
  60. import std.algorithm.searching : canFind, startsWith;
  61. import std.conv : parse, to;
  62. import std.string : indexOf;
  63. import vibe.core.file : existsFile, removeFile;
  64. SysTime reqtime = Clock.currTime(UTC());
  65. // some instances that live only while the request is running
  66. FreeListRef!HTTPServerRequest req = FreeListRef!HTTPServerRequest(reqtime, listen_info.bindPort);
  67. FreeListRef!TimeoutHTTPInputStream timeout_http_input_stream;
  68. FreeListRef!LimitedHTTPInputStream limited_http_input_stream;
  69. FreeListRef!ChunkedInputStream chunked_input_stream;
  70. // store the IP address
  71. req.clientAddress = tcp_connection.remoteAddress;
  72. if (!listen_info.hasVirtualHosts) {
  73. logWarn("Didn't find a HTTP listening context for incoming connection. Dropping.");
  74. keep_alive = false;
  75. return false;
  76. }
  77. // Default to the first virtual host for this listener
  78. HTTPServerContext.VirtualHost context = listen_info.m_virtualHosts[0];
  79. HTTPServerRequestDelegate request_task = context.requestHandler;
  80. settings = context.settings;
  81. // temporarily set to the default settings, the virtual host specific settings will be set further down
  82. req.m_settings = settings;
  83. // Create the response object
  84. ConnectionStreamProxy cproxy = tcp_connection;
  85. auto exchange = () @trusted { return request_allocator.make!HTTP1ServerExchange(http_stream, cproxy); } ();
  86. scope (exit) () @trusted { request_allocator.dispose(exchange); } ();
  87. auto res = FreeListRef!HTTPServerResponse(exchange, settings, request_allocator/*.Scoped_payload*/);
  88. req.tls = res.m_tls = listen_info.tlsContext !is null;
  89. if (req.tls) {
  90. static if (HaveNoTLS) assert(false);
  91. else {
  92. static if (is(InterfaceProxy!ConnectionStream == ConnectionStream))
  93. req.clientCertificate = (cast(TLSStream)http_stream).peerCertificate;
  94. else
  95. req.clientCertificate = http_stream.extract!TLSStreamType.peerCertificate;
  96. }
  97. }
  98. // Error page handler
  99. void errorOut(int code, string msg, string debug_msg, Throwable ex)
  100. @safe {
  101. assert(!res.headerWritten);
  102. res.statusCode = code;
  103. if (settings && settings.errorPageHandler) {
  104. /*scope*/ auto err = new HTTPServerErrorInfo;
  105. err.code = code;
  106. err.message = msg;
  107. err.debugMessage = debug_msg;
  108. err.exception = ex;
  109. settings.errorPageHandler_(req, res, err);
  110. } else {
  111. if (debug_msg.length)
  112. res.writeBody(format("%s - %s\n\n%s\n\nInternal error information:\n%s", code, httpStatusText(code), msg, debug_msg));
  113. else res.writeBody(format("%s - %s\n\n%s", code, httpStatusText(code), msg));
  114. }
  115. assert(res.headerWritten);
  116. }
  117. bool parsed = false;
  118. /*bool*/ keep_alive = false;
  119. // parse the request
  120. try {
  121. logTrace("reading request..");
  122. // limit the total request time
  123. InputStreamProxy reqReader = http_stream;
  124. if (settings.maxRequestTime > dur!"seconds"(0) && settings.maxRequestTime != Duration.max) {
  125. timeout_http_input_stream = FreeListRef!TimeoutHTTPInputStream(reqReader, settings.maxRequestTime, reqtime);
  126. reqReader = timeout_http_input_stream;
  127. }
  128. // basic request parsing
  129. uint h2 = parseRequestHeader(req, reqReader, request_allocator, settings.maxRequestHeaderSize, settings.maxRequestHeaderLineSize, !!(settings.options & HTTPServerOption.enableHTTP2));
  130. if (h2) {
  131. import vibe.http.internal.http2.server : handleHTTP2Connection;
  132. import vibe.http.internal.http2.settings : HTTP2ServerContext, HTTP2Settings;
  133. // start http/2 with prior knowledge
  134. uint len = 22 - h2;
  135. ubyte[] dummy; dummy.length = len;
  136. http_stream.read(dummy); // finish reading connection preface
  137. auto h2settings = HTTP2Settings();
  138. auto h2context = new HTTP2ServerContext(listen_info, h2settings);
  139. handleHTTP2Connection(tcp_connection, tcp_connection, h2context, true);
  140. return true;
  141. }
  142. logTrace("Got request header.");
  143. // find the matching virtual host
  144. string reqhost;
  145. ushort reqport = 0;
  146. {
  147. string s = req.host;
  148. enforceHTTP(s.length > 0 || req.httpVersion <= HTTPVersion.HTTP_1_0, HTTPStatus.badRequest, "Missing Host header.");
  149. if (s.startsWith('[')) { // IPv6 address
  150. auto idx = s.indexOf(']');
  151. enforce(idx > 0, "Missing closing ']' for IPv6 address.");
  152. reqhost = s[1 .. idx];
  153. s = s[idx+1 .. $];
  154. } else if (s.length) { // host name or IPv4 address
  155. auto idx = s.indexOf(':');
  156. if (idx < 0) idx = s.length;
  157. enforceHTTP(idx > 0, HTTPStatus.badRequest, "Missing Host header.");
  158. reqhost = s[0 .. idx];
  159. s = s[idx .. $];
  160. }
  161. if (s.startsWith(':')) reqport = s[1 .. $].to!ushort;
  162. }
  163. foreach (ctx; listen_info.m_virtualHosts)
  164. if (icmp2(ctx.settings.hostName, reqhost) == 0 &&
  165. (!reqport || reqport == ctx.settings.port))
  166. {
  167. context = ctx;
  168. settings = ctx.settings;
  169. request_task = ctx.requestHandler;
  170. break;
  171. }
  172. req.m_settings = settings;
  173. res.m_settings = settings;
  174. // setup compressed output
  175. if (settings.useCompressionIfPossible) {
  176. if (auto pae = "Accept-Encoding" in req.headers) {
  177. if (canFind(*pae, "gzip")) {
  178. res.headers["Content-Encoding"] = "gzip";
  179. } else if (canFind(*pae, "deflate")) {
  180. res.headers["Content-Encoding"] = "deflate";
  181. }
  182. }
  183. }
  184. // limit request size
  185. if (auto pcl = "Content-Length" in req.headers) {
  186. string v = *pcl;
  187. auto contentLength = parse!ulong(v); // DMDBUG: to! thinks there is a H in the string
  188. enforceBadRequest(v.length == 0, "Invalid content-length");
  189. enforceBadRequest(settings.maxRequestSize <= 0 || contentLength <= settings.maxRequestSize, "Request size too big");
  190. limited_http_input_stream = FreeListRef!LimitedHTTPInputStream(reqReader, contentLength);
  191. } else if (auto pt = "Transfer-Encoding" in req.headers) {
  192. enforceBadRequest(icmp2(*pt, "chunked") == 0);
  193. chunked_input_stream = createChunkedInputStreamFL(reqReader);
  194. InputStreamProxy ciproxy = chunked_input_stream;
  195. limited_http_input_stream = FreeListRef!LimitedHTTPInputStream(ciproxy, settings.maxRequestSize, true);
  196. } else {
  197. limited_http_input_stream = FreeListRef!LimitedHTTPInputStream(reqReader, 0);
  198. }
  199. req.bodyReader = limited_http_input_stream;
  200. // handle Expect header
  201. if (auto pv = "Expect" in req.headers) {
  202. if (icmp2(*pv, "100-continue") == 0) {
  203. logTrace("sending 100 continue");
  204. http_stream.write("HTTP/1.1 100 Continue\r\n\r\n");
  205. }
  206. }
  207. // eagerly parse the URL as its lightweight and defacto @nogc
  208. auto url = URL.parse(req.requestURI);
  209. req.queryString = url.queryString;
  210. req.username = url.username;
  211. req.password = url.password;
  212. req.requestPath = url.path;
  213. // lookup the session
  214. if (settings.sessionStore) {
  215. // use the first cookie that contains a valid session ID in case
  216. // of multiple matching session cookies
  217. foreach (val; req.cookies.getAll(settings.sessionIdCookie)) {
  218. req.session = settings.sessionStore.open(val);
  219. res.m_session = req.session;
  220. if (req.session) break;
  221. }
  222. }
  223. // write default headers
  224. if (req.method == HTTPMethod.HEAD) exchange.m_isHeadResponse = true;
  225. if (settings.serverString.length)
  226. res.headers["Server"] = settings.serverString;
  227. res.headers["Date"] = formatRFC822DateAlloc(reqtime);
  228. if (req.persistent)
  229. res.headers["Keep-Alive"] = formatAlloc(
  230. request_allocator, "timeout=%d", settings.keepAliveTimeout.total!"seconds"());
  231. // finished parsing the request
  232. parsed = true;
  233. logTrace("persist: %s", req.persistent);
  234. keep_alive = req.persistent;
  235. if (context.settings.rejectConnectionPredicate !is null)
  236. {
  237. import std.socket : Address, parseAddress;
  238. auto forward = req.headers.get("X-Forwarded-For", null);
  239. if (forward !is null)
  240. {
  241. try {
  242. auto ix = forward.indexOf(',');
  243. if (ix != -1)
  244. forward = forward[0 .. ix];
  245. if (context.settings.rejectConnectionPredicate(NetworkAddress(parseAddress(forward))))
  246. errorOut(HTTPStatus.forbidden,
  247. httpStatusText(HTTPStatus.forbidden), null, null);
  248. } catch (Exception e)
  249. logTrace("Malformed X-Forwarded-For header: %s", e.msg);
  250. }
  251. }
  252. // handle the request
  253. logTrace("handle request (body %d)", req.bodyReader.leastSize);
  254. res.httpVersion = req.httpVersion;
  255. request_task(req, res);
  256. // if no one has written anything, return 404
  257. if (!res.headerWritten) {
  258. string dbg_msg;
  259. logDiagnostic("No response written for %s", req.requestURI);
  260. if (settings.options & HTTPServerOption.errorStackTraces)
  261. dbg_msg = format("No routes match path '%s'", req.requestURI);
  262. errorOut(HTTPStatus.notFound, httpStatusText(HTTPStatus.notFound), dbg_msg, null);
  263. }
  264. } catch (HTTPStatusException err) {
  265. if (!res.headerWritten) errorOut(err.status, err.msg, err.debugMessage, err);
  266. else logDiagnostic("HTTPStatusException while writing the response: %s", err.msg);
  267. debug logDebug("Exception while handling request %s %s: %s", req.method,
  268. req.requestURI, () @trusted { return err.toString().sanitize; } ());
  269. if (!parsed || res.headerWritten || justifiesConnectionClose(err.status))
  270. keep_alive = false;
  271. } catch (UncaughtException e) {
  272. auto status = parsed ? HTTPStatus.internalServerError : HTTPStatus.badRequest;
  273. string dbg_msg;
  274. if (settings.options & HTTPServerOption.errorStackTraces)
  275. dbg_msg = () @trusted { return e.toString().sanitize; } ();
  276. if (!res.headerWritten && tcp_connection.connected)
  277. errorOut(status, httpStatusText(status), dbg_msg, e);
  278. else logDiagnostic("Error while writing the response: %s", e.msg);
  279. debug logDebug("Exception while handling request %s %s: %s", req.method,
  280. req.requestURI, () @trusted { return e.toString().sanitize(); } ());
  281. if (!parsed || res.headerWritten || !cast(Exception)e) keep_alive = false;
  282. }
  283. if (tcp_connection.connected && keep_alive) {
  284. if (req.bodyReader && !req.bodyReader.empty) {
  285. req.bodyReader.pipe(nullSink);
  286. logTrace("dropped body");
  287. }
  288. }
  289. // finalize (e.g. for chunked encoding)
  290. res.finalize();
  291. if (exchange.m_requiresConnectionClose)
  292. keep_alive = false;
  293. // NOTE: req.m_files may or may not be parsed/filled with actual data, as
  294. // it is lazily initialized when calling the .files or .form
  295. // properties
  296. foreach (k, v ; req.m_files.byKeyValue) {
  297. if (existsFile(v.tempPath)) {
  298. removeFile(v.tempPath);
  299. logDebug("Deleted upload tempfile %s", v.tempPath.toString());
  300. }
  301. }
  302. if (!req.noLog) {
  303. // log the request to access log
  304. foreach (log; context.loggers)
  305. log.log(req, res);
  306. }
  307. //logTrace("return %s (used pool memory: %s/%s)", keep_alive, request_allocator.allocatedSize, request_allocator.totalSize);
  308. logTrace("return %s", keep_alive);
  309. return keep_alive != false;
  310. }
  311. private uint parseRequestHeader(InputStream, Allocator)(HTTPServerRequest req, InputStream http_stream, Allocator alloc, ulong max_header_size, size_t max_header_line_size, bool enable_http2)
  312. if (isInputStream!InputStream)
  313. {
  314. import std.string : indexOf;
  315. import vibe.stream.operations : readLine;
  316. auto stream = FreeListRef!LimitedHTTPInputStream(http_stream, max_header_size);
  317. logTrace("HTTP server reading status line");
  318. auto reqln = () @trusted { return cast(string)stream.readLine(max_header_line_size, "\r\n", alloc); }();
  319. if(reqln == "PRI * HTTP/2.0" && enable_http2) return cast(uint)reqln.length;
  320. logTrace("--------------------");
  321. logTrace("HTTP server request:");
  322. logTrace("--------------------");
  323. logTrace("%s", reqln);
  324. //Method
  325. auto pos = reqln.indexOf(' ');
  326. enforceBadRequest(pos >= 0, "invalid request method");
  327. req.method = httpMethodFromString(reqln[0 .. pos]);
  328. reqln = reqln[pos+1 .. $];
  329. //Path
  330. pos = reqln.indexOf(' ');
  331. enforceBadRequest(pos >= 0, "invalid request path");
  332. req.requestURI = reqln[0 .. pos];
  333. reqln = reqln[pos+1 .. $];
  334. req.httpVersion = parseHTTPVersion(reqln);
  335. //headers
  336. parseRFC5322Header(stream, req.headers, max_header_line_size, alloc, false);
  337. foreach (k, v; req.headers.byKeyValue)
  338. logTrace("%s: %s", k, v);
  339. logTrace("--------------------");
  340. return 0;
  341. }
  342. class HTTP1ServerExchange : HTTPServerExchange {
  343. import vibe.stream.counting : CountingOutputStream, createCountingOutputStreamFL;
  344. import vibe.stream.wrapper : createConnectionProxyStream, createConnectionProxyStreamFL;
  345. import vibe.stream.zlib : ZlibOutputStream, createDeflateOutputStreamFL, createGzipOutputStreamFL;
  346. protected {
  347. StreamProxy m_conn;
  348. ConnectionStreamProxy m_rawConnection;
  349. bool m_isHeadResponse = false;
  350. OutputStreamProxy m_bodyWriter;
  351. FreeListRef!ChunkedOutputStream m_chunkedBodyWriter;
  352. FreeListRef!CountingOutputStream m_countingWriter;
  353. FreeListRef!ZlibOutputStream m_zlibOutputStream;
  354. bool m_headerWritten = false;
  355. bool m_requiresConnectionClose;
  356. }
  357. this(StreamProxy conn, ConnectionStreamProxy raw_connection)
  358. @safe {
  359. m_conn = conn;
  360. m_rawConnection = raw_connection;
  361. m_countingWriter = createCountingOutputStreamFL(conn);
  362. }
  363. override @property bool isHeadResponse() const { return m_isHeadResponse; }
  364. override @property bool headerWritten() const { return m_headerWritten; }
  365. override @property ulong bytesWritten() @safe const { return m_countingWriter.bytesWritten; }
  366. override void writeBody(HTTPServerResponse res, RandomAccessStreamProxy stream)
  367. {
  368. assert(!m_headerWritten, "A body was already written!");
  369. writeHeader(res);
  370. if (m_isHeadResponse) return;
  371. auto bytes = stream.size - stream.tell();
  372. stream.pipe(m_conn);
  373. m_countingWriter.increment(bytes);
  374. }
  375. override void writeBody(HTTPServerResponse res, InputStreamProxy stream, ulong num_bytes = ulong.max)
  376. {
  377. assert(!m_headerWritten, "A body was already written!");
  378. writeHeader(res);
  379. if (m_isHeadResponse) return;
  380. if (num_bytes != ulong.max) {
  381. stream.pipe(m_conn, num_bytes);
  382. m_countingWriter.increment(num_bytes);
  383. } else stream.pipe(m_countingWriter);
  384. }
  385. override void writeVoidBody(HTTPServerResponse res)
  386. {
  387. if (!isHeadResponse) {
  388. assert("Content-Length" !in res.headers);
  389. assert("Transfer-Encoding" !in res.headers);
  390. }
  391. assert(!m_headerWritten);
  392. writeHeader(res);
  393. m_conn.flush();
  394. }
  395. override OutputStreamProxy bodyWriter(HTTPServerResponse res)
  396. {
  397. import std.conv : to;
  398. assert(!!m_conn);
  399. if (m_bodyWriter) {
  400. // for test responses, the body writer is pre-set, without headers
  401. // being written, so we may need to do that here
  402. if (!m_headerWritten) writeHeader(res);
  403. return m_bodyWriter;
  404. }
  405. assert(!m_headerWritten, "A void body was already written!");
  406. assert(res.statusCode >= 200, "1xx responses can't have body");
  407. if (m_isHeadResponse) {
  408. // for HEAD requests, we define a NullOutputWriter for convenience
  409. // - no body will be written. However, the request handler should call writeVoidBody()
  410. // and skip writing of the body in this case.
  411. if ("Content-Length" !in res.headers)
  412. res.headers["Transfer-Encoding"] = "chunked";
  413. writeHeader(res);
  414. m_bodyWriter = nullSink;
  415. return m_bodyWriter;
  416. }
  417. if ("Content-Encoding" in res.headers && "Content-Length" in res.headers) {
  418. // we do not known how large the compressed body will be in advance
  419. // so remove the content-length and use chunked transfer
  420. res.headers.remove("Content-Length");
  421. }
  422. if (auto pcl = "Content-Length" in res.headers) {
  423. writeHeader(res);
  424. m_countingWriter.writeLimit = (*pcl).to!ulong;
  425. m_bodyWriter = m_countingWriter;
  426. } else if (res.httpVersion <= HTTPVersion.HTTP_1_0) {
  427. if ("Connection" in res.headers)
  428. res.headers.remove("Connection"); // default to "close"
  429. writeHeader(res);
  430. m_bodyWriter = m_conn;
  431. } else {
  432. res.headers["Transfer-Encoding"] = "chunked";
  433. writeHeader(res);
  434. m_chunkedBodyWriter = createChunkedOutputStreamFL(m_countingWriter);
  435. m_bodyWriter = m_chunkedBodyWriter;
  436. }
  437. if (auto pce = "Content-Encoding" in res.headers) {
  438. if (icmp2(*pce, "gzip") == 0) {
  439. m_zlibOutputStream = createGzipOutputStreamFL(m_bodyWriter);
  440. m_bodyWriter = m_zlibOutputStream;
  441. } else if (icmp2(*pce, "deflate") == 0) {
  442. m_zlibOutputStream = createDeflateOutputStreamFL(m_bodyWriter);
  443. m_bodyWriter = m_zlibOutputStream;
  444. } else {
  445. logWarn("Unsupported Content-Encoding set in response: '"~*pce~"'");
  446. }
  447. }
  448. return m_bodyWriter;
  449. }
  450. override ConnectionStream switchProtocol(HTTPServerResponse res, string protocol)
  451. {
  452. res.statusCode = HTTPStatus.switchingProtocols;
  453. if (protocol.length) res.headers["Upgrade"] = protocol;
  454. writeVoidBody(res);
  455. m_requiresConnectionClose = true;
  456. m_headerWritten = true;
  457. return createConnectionProxyStream(m_conn, m_rawConnection);
  458. }
  459. override void switchProtocol(HTTPServerResponse res, string protocol, scope void delegate(scope ConnectionStream) @safe del)
  460. {
  461. res.statusCode = HTTPStatus.switchingProtocols;
  462. if (protocol.length) res.headers["Upgrade"] = protocol;
  463. writeVoidBody(res);
  464. m_requiresConnectionClose = true;
  465. m_headerWritten = true;
  466. () @trusted {
  467. auto conn = createConnectionProxyStreamFL(m_conn, m_rawConnection);
  468. del(conn);
  469. } ();
  470. finalize(res);
  471. }
  472. override ConnectionStream connectProxy(HTTPServerResponse res)
  473. {
  474. return createConnectionProxyStream(m_conn, m_rawConnection);
  475. }
  476. override void connectProxy(HTTPServerResponse res, scope void delegate(scope ConnectionStream) @safe del)
  477. {
  478. () @trusted {
  479. auto conn = createConnectionProxyStreamFL(m_conn, m_rawConnection);
  480. del(conn);
  481. } ();
  482. finalize(res);
  483. }
  484. void finalize(HTTPServerResponse res)
  485. {
  486. import std.conv : to;
  487. if (m_zlibOutputStream) {
  488. m_zlibOutputStream.finalize();
  489. m_zlibOutputStream.destroy();
  490. }
  491. if (m_chunkedBodyWriter) {
  492. m_chunkedBodyWriter.finalize();
  493. m_chunkedBodyWriter.destroy();
  494. }
  495. // ignore exceptions caused by an already closed connection - the client
  496. // may have closed the connection already and this doesn't usually indicate
  497. // a problem.
  498. if (m_rawConnection && m_rawConnection.connected) {
  499. try if (m_conn) m_conn.flush();
  500. catch (Exception e) logDebug("Failed to flush connection after finishing HTTP response: %s", e.msg);
  501. if (!isHeadResponse && m_countingWriter.bytesWritten < res.headers.get("Content-Length", "0").to!ulong) {
  502. logDebug("HTTP response only written partially before finalization. Terminating connection.");
  503. m_requiresConnectionClose = true;
  504. }
  505. m_rawConnection = ConnectionStreamProxy.init;
  506. }
  507. if (m_conn) {
  508. m_conn = StreamProxy.init;
  509. res.m_timeFinalized = Clock.currTime(UTC());
  510. }
  511. }
  512. private void writeHeader(HTTPServerResponse res)
  513. @safe {
  514. import vibe.stream.wrapper;
  515. assert(!m_headerWritten, "Try to write header after body has already begun.");
  516. assert(res.httpVersion != HTTPVersion.HTTP_1_0 || res.statusCode >= 200, "Informational status codes aren't supported by HTTP/1.0.");
  517. // Don't set m_headerWritten for 1xx status codes
  518. if (res.statusCode >= 200) m_headerWritten = true;
  519. auto dst = streamOutputRange!1024(m_conn);
  520. void writeLine(T...)(string fmt, T args)
  521. @safe {
  522. formattedWrite(() @trusted { return &dst; } (), fmt, args);
  523. dst.put("\r\n");
  524. logTrace(fmt, args);
  525. }
  526. logTrace("---------------------");
  527. logTrace("HTTP server response:");
  528. logTrace("---------------------");
  529. // write the status line
  530. writeLine("%s %d %s",
  531. getHTTPVersionString(res.httpVersion),
  532. res.statusCode,
  533. res.statusPhrase.length ? res.statusPhrase : httpStatusText(res.statusCode));
  534. // write all normal headers
  535. foreach (k, v; res.headers.byKeyValue) {
  536. dst.put(k);
  537. dst.put(": ");
  538. dst.put(v);
  539. dst.put("\r\n");
  540. logTrace("%s: %s", k, v);
  541. }
  542. logTrace("---------------------");
  543. // write cookies
  544. foreach (n, cookie; () @trusted { return res.cookies.byKeyValue; } ()) {
  545. dst.put("Set-Cookie: ");
  546. cookie.writeString(() @trusted { return &dst; } (), n);
  547. dst.put("\r\n");
  548. }
  549. // finalize response header
  550. dst.put("\r\n");
  551. }
  552. bool waitForConnectionClose(Duration timeout)
  553. {
  554. if (!m_rawConnection || !m_rawConnection.connected) return true;
  555. m_rawConnection.waitForData(timeout);
  556. return !m_rawConnection.connected;
  557. }
  558. @property bool connected()
  559. const {
  560. if (!m_rawConnection) return false;
  561. return m_rawConnection.connected;
  562. }
  563. }