1 /**
2 	A HTTP 1.1/1.0 server implementation.
3 
4 	Copyright: © 2012-2017 Sönke Ludwig
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Sönke Ludwig, Jan Krüger, Ilya Shipunov
7 */
8 module vibe.http.server;
9 
10 public import vibe.core.net;
11 public import vibe.http.common;
12 public import vibe.http.session;
13 
14 import vibe.container.internal.appender : FixedAppender;
15 import vibe.container.internal.utilallocator;
16 import vibe.core.file;
17 import vibe.core.log;
18 import vibe.data.json;
19 import vibe.http.dist;
20 import vibe.http.log;
21 import vibe.inet.message;
22 import vibe.inet.url;
23 import vibe.inet.webform;
24 import vibe.internal.interfaceproxy : InterfaceProxy;
25 import vibe.stream.counting;
26 import vibe.stream.operations;
27 import vibe.stream.tls;
28 import vibe.stream.wrapper : ConnectionProxyStream, createConnectionProxyStream, createConnectionProxyStreamFL;
29 import vibe.stream.zlib;
30 import vibe.textfilter.urlencode;
31 import vibe.internal.freelistref;
32 import vibe.internal.string : formatAlloc, icmp2;
33 
34 import core.atomic;
35 import core.vararg;
36 import diet.traits : SafeFilterCallback, dietTraits;
37 import std.algorithm : canFind, splitter;
38 import std.array;
39 import std.conv;
40 import std.datetime;
41 import std.encoding : sanitize;
42 import std.exception;
43 import std.format;
44 import std.functional : toDelegate;
45 import std.string;
46 import std.traits : ReturnType;
47 import std.typecons;
48 import std.uri;
49 
50 
51 version (VibeNoSSL) version = HaveNoTLS;
52 else version (Have_botan) {}
53 else version (Have_openssl) {}
54 else version = HaveNoTLS;
55 
56 /**************************************************************************************************/
57 /* Public functions                                                                               */
58 /**************************************************************************************************/
59 
60 /**
61 	Starts a HTTP server listening on the specified port.
62 
63 	request_handler will be called for each HTTP request that is made. The
64 	res parameter of the callback then has to be filled with the response
65 	data.
66 
67 	request_handler can be either HTTPServerRequestDelegate/HTTPServerRequestFunction
68 	or a class/struct with a member function 'handleRequest' that has the same
69 	signature.
70 
71 	Note that if the application has been started with the --disthost command line
72 	switch, listenHTTP() will automatically listen on the specified VibeDist host
73 	instead of locally. This allows for a seamless switch from single-host to
74 	multi-host scenarios without changing the code. If you need to listen locally,
75 	use listenHTTPPlain() instead.
76 
77 	Params:
78 		settings = Customizes the HTTP servers functionality (host string or HTTPServerSettings object)
79 		request_handler = This callback is invoked for each incoming request and is responsible
80 			for generating the response.
81 
82 	Returns:
83 		A handle is returned that can be used to stop listening for further HTTP
84 		requests with the supplied settings. Another call to `listenHTTP` can be
85 		used afterwards to start listening again.
86 */
87 HTTPListener listenHTTP(Settings)(Settings _settings, HTTPServerRequestDelegate request_handler)
88 @safe
89 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
90 	// auto-construct HTTPServerSettings
91 	static if (is(Settings == string))
92 		auto settings = new HTTPServerSettings(_settings);
93 	else
94 		alias settings = _settings;
95 
96 	enforce(settings.bindAddresses.length, "Must provide at least one bind address for a HTTP server.");
97 
98 	// if a VibeDist host was specified on the command line, register there instead of listening
99 	// directly.
100 	if (s_distHost.length && !settings.disableDistHost) {
101 		return listenHTTPDist(settings, request_handler, s_distHost, s_distPort);
102 	} else {
103 		return listenHTTPPlain(settings, request_handler);
104 	}
105 }
106 /// ditto
107 HTTPListener listenHTTP(Settings)(Settings settings, HTTPServerRequestFunction request_handler)
108 @safe
109 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
110 	return listenHTTP(settings, () @trusted { return toDelegate(request_handler); } ());
111 }
112 /// ditto
113 HTTPListener listenHTTP(Settings)(Settings settings, HTTPServerRequestHandler request_handler)
114 @safe
115 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
116 	return listenHTTP(settings, &request_handler.handleRequest);
117 }
118 /// ditto
119 HTTPListener listenHTTP(Settings)(Settings settings, HTTPServerRequestDelegateS request_handler)
120 @safe
121 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
122 	return listenHTTP(settings, cast(HTTPServerRequestDelegate)request_handler);
123 }
124 /// ditto
125 HTTPListener listenHTTP(Settings)(Settings settings, HTTPServerRequestFunctionS request_handler)
126 @safe
127 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
128 	return listenHTTP(settings, () @trusted { return toDelegate(request_handler); } ());
129 }
130 /// ditto
131 HTTPListener listenHTTP(Settings)(Settings settings, HTTPServerRequestHandlerS request_handler)
132 @safe
133 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
134 	return listenHTTP(settings, &request_handler.handleRequest);
135 }
136 
137 /// Scheduled for deprecation - use a `@safe` callback instead.
138 HTTPListener listenHTTP(Settings)(Settings settings, void delegate(HTTPServerRequest, HTTPServerResponse) @system request_handler)
139 @system
140 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
141 	return listenHTTP(settings, (req, res) @trusted => request_handler(req, res));
142 }
143 /// ditto
144 HTTPListener listenHTTP(Settings)(Settings settings, void function(HTTPServerRequest, HTTPServerResponse) @system request_handler)
145 @system
146 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
147 	return listenHTTP(settings, (req, res) @trusted => request_handler(req, res));
148 }
149 /// ditto
150 HTTPListener listenHTTP(Settings)(Settings settings, void delegate(scope HTTPServerRequest, scope HTTPServerResponse) @system request_handler)
151 @system
152 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
153 	return listenHTTP(settings, (scope req, scope res) @trusted => request_handler(req, res));
154 }
155 /// ditto
156 HTTPListener listenHTTP(Settings)(Settings settings, void function(scope HTTPServerRequest, scope HTTPServerResponse) @system request_handler)
157 @system
158 if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
159 	return listenHTTP(settings, (scope req, scope res) @trusted => request_handler(req, res));
160 }
161 
162 unittest
163 {
164 	void test()
165 	{
166 		static void testSafeFunction(HTTPServerRequest req, HTTPServerResponse res) @safe {}
167 		listenHTTP("0.0.0.0:8080", &testSafeFunction);
168 		listenHTTP(":8080", new class HTTPServerRequestHandler {
169 			void handleRequest(HTTPServerRequest req, HTTPServerResponse res) @safe {}
170 		});
171 		listenHTTP(":8080", (req, res) {});
172 
173 		static void testSafeFunctionS(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe {}
174 		listenHTTP(":8080", &testSafeFunctionS);
175 		void testSafeDelegateS(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe {}
176 		listenHTTP(":8080", &testSafeDelegateS);
177 		listenHTTP(":8080", new class HTTPServerRequestHandler {
178 			void handleRequest(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe {}
179 		});
180 		listenHTTP(":8080", (scope req, scope res) {});
181 	}
182 }
183 
184 
185 /** Treats an existing connection as an HTTP connection and processes incoming
186 	requests.
187 
188 	After all requests have been processed, the connection will be closed and
189 	the function returns to the caller.
190 
191 	Params:
192 		connection = The stream to treat as an incoming HTTP client connection.
193 		context = Information about the incoming listener and available
194 			virtual hosts
195 */
196 void handleHTTPConnection(TCPConnection connection, HTTPServerContext context)
197 @safe {
198 	InterfaceProxy!Stream http_stream;
199 	http_stream = connection;
200 
201 	scope (exit) connection.close();
202 
203 	// check wether the client's address is banned
204 	foreach (ref virtual_host; context.m_virtualHosts)
205 		if ((virtual_host.settings.rejectConnectionPredicate !is null) &&
206 			virtual_host.settings.rejectConnectionPredicate(connection.remoteAddress()))
207 			return;
208 
209 	// Set NODELAY to true, to avoid delays caused by sending the response
210 	// header and body in separate chunks. Note that to avoid other performance
211 	// issues (caused by tiny packets), this requires using an output buffer in
212 	// the event driver, which is the case at least for the legacy libevent
213 	// based driver.
214 	connection.tcpNoDelay = true;
215 
216 	version(HaveNoTLS) {} else {
217 		TLSStreamType tls_stream;
218 	}
219 
220 	if (!connection.waitForData(10.seconds())) {
221 		logDebug("Client didn't send the initial request in a timely manner. Closing connection.");
222 		return;
223 	}
224 
225 	// If this is a HTTPS server, initiate TLS
226 	if (context.tlsContext) {
227 		version (HaveNoTLS) assert(false, "No TLS support compiled in.");
228 		else {
229 			logDebug("Accept TLS connection: %s", context.tlsContext.kind);
230 			// TODO: reverse DNS lookup for peer_name of the incoming connection for TLS client certificate verification purposes
231 			tls_stream = createTLSStreamFL(http_stream, context.tlsContext, TLSStreamState.accepting, null, connection.remoteAddress);
232 			http_stream = tls_stream;
233 		}
234 	}
235 
236 	while (!connection.empty) {
237 		HTTPServerSettings settings;
238 		bool keep_alive;
239 
240 		version(HaveNoTLS) {} else {
241 			// handle oderly TLS shutdowns
242 			if (tls_stream && tls_stream.empty) break;
243 		}
244 
245 		() @trusted {
246 			scope request_allocator = createRequestAllocator();
247 			scope (exit) freeRequestAllocator(request_allocator);
248 
249 			handleRequest(http_stream, connection, context, settings, keep_alive, request_allocator);
250 		} ();
251 		if (!keep_alive) { logTrace("No keep-alive - disconnecting client."); break; }
252 
253 		logTrace("Waiting for next request...");
254 		// wait for another possible request on a keep-alive connection
255 		if (!connection.waitForData(settings.keepAliveTimeout)) {
256 			if (!connection.connected) logTrace("Client disconnected.");
257 			else logDebug("Keep-alive connection timed out!");
258 			break;
259 		}
260 	}
261 
262 	logTrace("Done handling connection.");
263 }
264 
265 
266 /**
267 	Provides a HTTP request handler that responds with a static Diet template.
268 */
269 @property HTTPServerRequestDelegateS staticTemplate(string template_file)()
270 {
271 	return (scope HTTPServerRequest req, scope HTTPServerResponse res){
272 		res.render!(template_file, req);
273 	};
274 }
275 
276 /**
277 	Provides a HTTP request handler that responds with a static redirection to the specified URL.
278 
279 	Params:
280 		url = The URL to redirect to
281 		status = Redirection status to use $(LPAREN)by default this is $(D HTTPStatus.found)$(RPAREN).
282 
283 	Returns:
284 		Returns a $(D HTTPServerRequestDelegate) that performs the redirect
285 */
286 HTTPServerRequestDelegate staticRedirect(string url, HTTPStatus status = HTTPStatus.found)
287 @safe {
288 	return (HTTPServerRequest req, HTTPServerResponse res){
289 		res.redirect(url, status);
290 	};
291 }
292 /// ditto
293 HTTPServerRequestDelegate staticRedirect(URL url, HTTPStatus status = HTTPStatus.found)
294 @safe {
295 	return (HTTPServerRequest req, HTTPServerResponse res){
296 		res.redirect(url, status);
297 	};
298 }
299 
300 ///
301 unittest {
302 	import vibe.http.router;
303 
304 	void test()
305 	{
306 		auto router = new URLRouter;
307 		router.get("/old_url", staticRedirect("http://example.org/new_url", HTTPStatus.movedPermanently));
308 
309 		listenHTTP(new HTTPServerSettings, router);
310 	}
311 }
312 
313 
314 /**
315 	Sets a VibeDist host to register with.
316 */
317 void setVibeDistHost(string host, ushort port)
318 @safe {
319 	s_distHost = host;
320 	s_distPort = port;
321 }
322 
323 
324 /**
325 	Renders the given Diet template and makes all ALIASES available to the template.
326 
327 	You can call this function as a pseudo-member of `HTTPServerResponse` using
328 	D's uniform function call syntax.
329 
330 	See_also: `diet.html.compileHTMLDietFile`
331 
332 	Examples:
333 		---
334 		string title = "Hello, World!";
335 		int pageNumber = 1;
336 		res.render!("mytemplate.dt", title, pageNumber);
337 		---
338 */
339 @property void render(string template_file, ALIASES...)(HTTPServerResponse res)
340 {
341 	res.contentType = "text/html; charset=UTF-8";
342 	version (VibeUseOldDiet)
343 		pragma(msg, "VibeUseOldDiet is not supported anymore. Please undefine in the package recipe.");
344 	import vibe.stream.wrapper : streamOutputRange;
345 	import diet.html : compileHTMLDietFile;
346 	auto output = streamOutputRange!1024(res.bodyWriter);
347 	compileHTMLDietFile!(template_file, ALIASES, DefaultDietFilters)(output);
348 }
349 
350 
351 /**
352 	Provides the default `css`, `javascript`, `markdown` and `htmlescape` filters
353  */
354 @dietTraits
355 struct DefaultDietFilters {
356 	import diet.html : HTMLOutputStyle;
357 	import diet.traits : SafeFilterCallback;
358 	import std.string : splitLines;
359 
360 	version (VibeOutputCompactHTML) enum HTMLOutputStyle htmlOutputStyle = HTMLOutputStyle.compact;
361 	else enum HTMLOutputStyle htmlOutputStyle = HTMLOutputStyle.pretty;
362 
363 	static string filterCss(I)(I text, size_t indent = 0)
364 	{
365 		auto lines = splitLines(text);
366 
367 		string indent_string = "\n";
368 		while (indent-- > 0) indent_string ~= '\t';
369 
370 		string ret = indent_string~"<style><!--";
371 		indent_string = indent_string ~ '\t';
372 		foreach (ln; lines) ret ~= indent_string ~ ln;
373 		indent_string = indent_string[0 .. $-1];
374 		ret ~= indent_string ~ "--></style>";
375 
376 		return ret;
377 	}
378 
379 
380 	static string filterJavascript(I)(I text, size_t indent = 0)
381 	{
382 		auto lines = splitLines(text);
383 
384 		string indent_string = "\n";
385 		while (indent-- > 0) indent_string ~= '\t';
386 
387 		string ret = indent_string~"<script>";
388 		ret ~= indent_string~'\t' ~ "//<![CDATA[";
389 		foreach (ln; lines) ret ~= indent_string ~ '\t' ~ ln;
390 		ret ~= indent_string ~ '\t' ~ "//]]>" ~ indent_string ~ "</script>";
391 
392 		return ret;
393 	}
394 
395 	static string filterMarkdown(I)(I text)
396 	{
397 		import vibe.textfilter.markdown : markdown = filterMarkdown;
398 		// TODO: indent
399 		return markdown(text);
400 	}
401 
402 	static string filterHtmlescape(I)(I text)
403 	{
404 		import vibe.textfilter.html : htmlEscape;
405 		// TODO: indent
406 		return htmlEscape(text);
407 	}
408 
409 	static this()
410 	{
411 		filters["css"] = (in input, scope output) { output(filterCss(input)); };
412 		filters["javascript"] = (in input, scope output) { output(filterJavascript(input)); };
413 		filters["markdown"] = (in input, scope output) { output(filterMarkdown(() @trusted { return cast(string)input; } ())); };
414 		filters["htmlescape"] = (in input, scope output) { output(filterHtmlescape(input)); };
415 	}
416 
417 	static SafeFilterCallback[string] filters;
418 }
419 
420 
421 unittest {
422 	static string compile(string diet)() {
423 		import std.array : appender;
424 		import std.string : strip;
425 		import diet.html : compileHTMLDietString;
426 		auto dst = appender!string;
427 		dst.compileHTMLDietString!(diet, DefaultDietFilters);
428 		return strip(cast(string)(dst.data));
429 	}
430 
431 	assert(compile!":css .test" == "<style><!--\n\t.test\n--></style>");
432 	assert(compile!":javascript test();" == "<script>\n\t//<![CDATA[\n\ttest();\n\t//]]>\n</script>");
433 	assert(compile!":markdown **test**" == "<p><strong>test</strong>\n</p>");
434 	assert(compile!":htmlescape <test>" == "&lt;test&gt;");
435 	assert(compile!":css !{\".test\"}" == "<style><!--\n\t.test\n--></style>");
436 	assert(compile!":javascript !{\"test();\"}" == "<script>\n\t//<![CDATA[\n\ttest();\n\t//]]>\n</script>");
437 	assert(compile!":markdown !{\"**test**\"}" == "<p><strong>test</strong>\n</p>");
438 	assert(compile!":htmlescape !{\"<test>\"}" == "&lt;test&gt;");
439 	assert(compile!":javascript\n\ttest();" == "<script>\n\t//<![CDATA[\n\ttest();\n\t//]]>\n</script>");
440 }
441 
442 
443 /**
444 	Creates a HTTPServerRequest suitable for writing unit tests.
445 */
446 HTTPServerRequest createTestHTTPServerRequest(URL url, HTTPMethod method = HTTPMethod.GET, InputStream data = null)
447 @safe {
448 	InetHeaderMap headers;
449 	return createTestHTTPServerRequest(url, method, headers, data);
450 }
451 /// ditto
452 HTTPServerRequest createTestHTTPServerRequest(URL url, HTTPMethod method, InetHeaderMap headers, InputStream data = null)
453 @safe {
454 	auto tls = url.schema == "https";
455 	auto ret = new HTTPServerRequest(Clock.currTime(UTC()), url.port ? url.port : tls ? 443 : 80);
456 	ret.m_settings = new HTTPServerSettings;
457 	ret.requestPath = url.path;
458 	ret.queryString = url.queryString;
459 	ret.username = url.username;
460 	ret.password = url.password;
461 	ret.requestURI = url.localURI;
462 	ret.method = method;
463 	ret.tls = tls;
464 	ret.headers = headers;
465 	ret.bodyReader = data;
466 	return ret;
467 }
468 
469 /**
470 	Creates a HTTPServerResponse suitable for writing unit tests.
471 
472 	Params:
473 		data_sink = Optional output stream that captures the data that gets
474 			written to the response
475 		session_store = Optional session store to use when sessions are involved
476 		data_mode = If set to `TestHTTPResponseMode.bodyOnly`, only the body
477 			contents get written to `data_sink`. Otherwise the raw response
478 			including the HTTP header is written.
479 */
480 HTTPServerResponse createTestHTTPServerResponse(OutputStream data_sink = null,
481 	SessionStore session_store = null,
482 	TestHTTPResponseMode data_mode = TestHTTPResponseMode.plain)
483 @safe {
484 	import vibe.stream.wrapper;
485 
486 	HTTPServerSettings settings;
487 	if (session_store) {
488 		settings = new HTTPServerSettings;
489 		settings.sessionStore = session_store;
490 	}
491 
492 	InterfaceProxy!Stream outstr;
493 	if (data_sink && data_mode == TestHTTPResponseMode.plain)
494 		outstr = createProxyStream(Stream.init, data_sink);
495 	else outstr = createProxyStream(Stream.init, nullSink);
496 
497 	auto ret = new HTTPServerResponse(outstr, InterfaceProxy!ConnectionStream.init,
498 		settings, () @trusted { return vibeThreadAllocator(); } ());
499 	if (data_sink && data_mode == TestHTTPResponseMode.bodyOnly) ret.m_bodyWriter = data_sink;
500 	return ret;
501 }
502 
503 
504 /**************************************************************************************************/
505 /* Public types                                                                                   */
506 /**************************************************************************************************/
507 
508 /// Delegate based request handler
509 alias HTTPServerRequestDelegate = void delegate(HTTPServerRequest req, HTTPServerResponse res) @safe;
510 /// Static function based request handler
511 alias HTTPServerRequestFunction = void function(HTTPServerRequest req, HTTPServerResponse res) @safe;
512 /// Interface for class based request handlers
513 interface HTTPServerRequestHandler {
514 	/// Handles incoming HTTP requests
515 	void handleRequest(HTTPServerRequest req, HTTPServerResponse res) @safe ;
516 }
517 
518 /// Delegate based request handler with scoped parameters
519 alias HTTPServerRequestDelegateS = void delegate(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe;
520 /// Static function based request handler with scoped parameters
521 alias HTTPServerRequestFunctionS  = void function(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe;
522 /// Interface for class based request handlers with scoped parameters
523 interface HTTPServerRequestHandlerS {
524 	/// Handles incoming HTTP requests
525 	void handleRequest(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe;
526 }
527 
528 unittest {
529 	static assert(is(HTTPServerRequestDelegateS : HTTPServerRequestDelegate));
530 	static assert(is(HTTPServerRequestFunctionS : HTTPServerRequestFunction));
531 }
532 
533 /// Aggregates all information about an HTTP error status.
534 final class HTTPServerErrorInfo {
535 	/// The HTTP status code
536 	int code;
537 	/// The error message
538 	string message;
539 	/// Extended error message with debug information such as a stack trace
540 	string debugMessage;
541 	/// The error exception, if any
542 	Throwable exception;
543 }
544 
545 /// Delegate type used for user defined error page generator callbacks.
546 alias HTTPServerErrorPageHandler = void delegate(HTTPServerRequest req, HTTPServerResponse res, HTTPServerErrorInfo error) @safe;
547 
548 
549 enum TestHTTPResponseMode {
550 	plain,
551 	bodyOnly
552 }
553 
554 
555 /**
556 	Specifies optional features of the HTTP server.
557 
558 	Disabling unneeded features can speed up the server or reduce its memory usage.
559 
560 	Note that the options `parseFormBody`, `parseJsonBody` and `parseMultiPartBody`
561 	will also drain the `HTTPServerRequest.bodyReader` stream whenever a request
562 	body with form or JSON data is encountered.
563 */
564 enum HTTPServerOption {
565 	none                      = 0,
566 	/** Enables stack traces (`HTTPServerErrorInfo.debugMessage`).
567 
568 		Note that generating the stack traces are generally a costly
569 		operation that should usually be avoided in production
570 		environments. It can also reveal internal information about
571 		the application, such as function addresses, which can
572 		help an attacker to abuse possible security holes.
573 	*/
574 	errorStackTraces          = 1<<7,
575 	/// Enable port reuse in `listenTCP()`
576 	reusePort                 = 1<<8,
577 	/// Enable address reuse in `listenTCP()`
578 	reuseAddress              = 1<<10,
579 	/** The default set of options.
580 
581 		Includes all parsing options, as well as the `errorStackTraces`
582 		option if the code is compiled in debug mode.
583 	*/
584 	defaults                  = () { auto ret = reuseAddress; debug ret |= errorStackTraces; return ret; } (),
585 }
586 
587 
588 /**
589 	Contains all settings for configuring a basic HTTP server.
590 
591 	The defaults are sufficient for most normal uses.
592 */
593 final class HTTPServerSettings {
594 	/** The port on which the HTTP server is listening.
595 
596 		The default value is 80. If you are running a TLS enabled server you may want to set this
597 		to 443 instead.
598 
599 		Using a value of `0` instructs the server to use any available port on
600 		the given `bindAddresses` the actual addresses and ports can then be
601 		queried with `TCPListener.bindAddresses`.
602 	*/
603 	ushort port = 80;
604 
605 	/** The interfaces on which the HTTP server is listening.
606 
607 		By default, the server will listen on all IPv4 and IPv6 interfaces.
608 	*/
609 	string[] bindAddresses = ["::", "0.0.0.0"];
610 
611 	/** Determines the server host name.
612 
613 		If multiple servers are listening on the same port, the host name will determine which one
614 		gets a request.
615 	*/
616 	string hostName;
617 
618 	/** Provides a way to reject incoming connections as early as possible.
619 
620 		Allows to ban and unban network addresses and reduce the impact of DOS
621 		attacks.
622 
623 		If the callback returns `true` for a specific `NetworkAddress`,
624 		then all incoming requests from that address will be rejected.
625 	*/
626 	RejectConnectionPredicate rejectConnectionPredicate;
627 
628 	/** Configures optional features of the HTTP server
629 
630 		Disabling unneeded features can improve performance or reduce the server
631 		load in case of invalid or unwanted requests (DoS). By default,
632 		HTTPServerOption.defaults is used.
633 	*/
634 	HTTPServerOption options = HTTPServerOption.defaults;
635 
636 	/** Time of a request after which the connection is closed with an error; not supported yet
637 
638 		The default limit of 0 means that the request time is not limited.
639 	*/
640 	Duration maxRequestTime = 0.seconds;
641 
642 	/** Maximum time between two request on a keep-alive connection
643 
644 		The default value is 10 seconds.
645 	*/
646 	Duration keepAliveTimeout = 10.seconds;
647 
648 	/// Maximum number of transferred bytes per request after which the connection is closed with
649 	/// an error
650 	ulong maxRequestSize = 2097152;
651 
652 
653 	///	Maximum number of transferred bytes for the request header. This includes the request line
654 	/// the url and all headers.
655 	ulong maxRequestHeaderSize = 8192;
656 
657 	/// Maximum number of bytes in a single line in the request header.
658 	size_t maxRequestHeaderLineSize = 4096;
659 
660 	/// Sets a custom handler for displaying error pages for HTTP errors
661 	@property HTTPServerErrorPageHandler errorPageHandler() @safe { return errorPageHandler_; }
662 	/// ditto
663 	@property void errorPageHandler(HTTPServerErrorPageHandler del) @safe { errorPageHandler_ = del; }
664 	/// Scheduled for deprecation - use a `@safe` callback instead.
665 	@property void errorPageHandler(void delegate(HTTPServerRequest, HTTPServerResponse, HTTPServerErrorInfo) @system del)
666 	@system {
667 		this.errorPageHandler = (req, res, err) @trusted { del(req, res, err); };
668 	}
669 
670 	private HTTPServerErrorPageHandler errorPageHandler_ = null;
671 
672 	/// If set, a HTTPS server will be started instead of plain HTTP.
673 	TLSContext tlsContext;
674 
675 	/// Session management is enabled if a session store instance is provided
676 	SessionStore sessionStore;
677 	string sessionIdCookie = "vibe.session_id";
678 
679 	/// Session options to use when initializing a new session.
680 	SessionOption sessionOptions = SessionOption.httpOnly;
681 
682 	///
683 	import vibe.core.core : vibeVersionString;
684 	string serverString = "vibe.d/" ~ vibeVersionString;
685 
686 	/** Specifies the format used for the access log.
687 
688 		The log format is given using the Apache server syntax. By default NCSA combined is used.
689 
690 		---
691 		"%h - %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\""
692 		---
693 	*/
694 	string accessLogFormat = "%h - %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\"";
695 
696 	/// Spefifies the name of a file to which access log messages are appended.
697 	string accessLogFile = "";
698 
699 	/// If set, access log entries will be output to the console.
700 	bool accessLogToConsole = false;
701 
702 	/** Specifies a custom access logger instance.
703 	*/
704 	HTTPLogger accessLogger;
705 
706 	/// Returns a duplicate of the settings object.
707 	@property HTTPServerSettings dup()
708 	@safe {
709 		auto ret = new HTTPServerSettings;
710 		foreach (mem; __traits(allMembers, HTTPServerSettings)) {
711 			static if (mem == "sslContext") {}
712 			else static if (mem == "bindAddresses") ret.bindAddresses = bindAddresses.dup;
713 			else static if (__traits(compiles, __traits(getMember, ret, mem) = __traits(getMember, this, mem)))
714 				__traits(getMember, ret, mem) = __traits(getMember, this, mem);
715 		}
716 		return ret;
717 	}
718 
719 	/// Disable support for VibeDist and instead start listening immediately.
720 	bool disableDistHost = false;
721 
722 	/** Responds to "Accept-Encoding" by using compression if possible.
723 
724 		Compression can also be manually enabled by setting the
725 		"Content-Encoding" header of the HTTP response appropriately before
726 		sending the response body.
727 
728 		This setting is disabled by default. Also note that there are still some
729 		known issues with the GZIP compression code.
730 	*/
731 	bool useCompressionIfPossible = false;
732 
733 
734 	/** Interval between WebSocket ping frames.
735 
736 		The default value is 60 seconds; set to Duration.zero to disable pings.
737 	*/
738 	Duration webSocketPingInterval = 60.seconds;
739 
740 	/** Constructs a new settings object with default values.
741 	*/
742 	this() @safe {}
743 
744 	/** Constructs a new settings object with a custom bind interface and/or port.
745 
746 		The syntax of `bind_string` is `[<IP address>][:<port>]`, where either of
747 		the two parts can be left off. IPv6 addresses must be enclosed in square
748 		brackets, as they would within a URL.
749 
750 		Throws:
751 			An exception is thrown if `bind_string` is malformed.
752 	*/
753 	this(string bind_string)
754 	@safe {
755 		this();
756 
757 		if (bind_string.startsWith('[')) {
758 			auto idx = bind_string.indexOf(']');
759 			enforce(idx > 0, "Missing closing bracket for IPv6 address.");
760 			bindAddresses = [bind_string[1 .. idx]];
761 			bind_string = bind_string[idx+1 .. $];
762 
763 			enforce(bind_string.length == 0 || bind_string.startsWith(':'),
764 				"Only a colon may follow the IPv6 address.");
765 		}
766 
767 		auto idx = bind_string.indexOf(':');
768 		if (idx < 0) {
769 			if (bind_string.length > 0) bindAddresses = [bind_string];
770 		} else {
771 			if (idx > 0) bindAddresses = [bind_string[0 .. idx]];
772 			port = bind_string[idx+1 .. $].to!ushort;
773 		}
774 	}
775 
776 	///
777 	unittest {
778 		auto s = new HTTPServerSettings(":8080");
779 		assert(s.bindAddresses == ["::", "0.0.0.0"]); // default bind addresses
780 		assert(s.port == 8080);
781 
782 		s = new HTTPServerSettings("123.123.123.123");
783 		assert(s.bindAddresses == ["123.123.123.123"]);
784 		assert(s.port == 80);
785 
786 		s = new HTTPServerSettings("[::1]:443");
787 		assert(s.bindAddresses == ["::1"]);
788 		assert(s.port == 443);
789 	}
790 }
791 
792 
793 /// Callback type used to determine whether to reject incoming connections
794 alias RejectConnectionPredicate = bool delegate (in NetworkAddress) @safe nothrow;
795 
796 
797 /**
798 	Options altering how sessions are created.
799 
800 	Multiple values can be or'ed together.
801 
802 	See_Also: HTTPServerResponse.startSession
803 */
804 enum SessionOption {
805 	/// No options.
806 	none = 0,
807 
808 	/** Instructs the browser to disallow accessing the session ID from JavaScript.
809 
810 		See_Also: Cookie.httpOnly
811 	*/
812 	httpOnly = 1<<0,
813 
814 	/** Instructs the browser to disallow sending the session ID over
815 		unencrypted connections.
816 
817 		By default, the type of the connection on which the session is started
818 		will be used to determine if secure or noSecure is used.
819 
820 		See_Also: noSecure, Cookie.secure
821 	*/
822 	secure = 1<<1,
823 
824 	/** Instructs the browser to allow sending the session ID over unencrypted
825 		connections.
826 
827 		By default, the type of the connection on which the session is started
828 		will be used to determine if secure or noSecure is used.
829 
830 		See_Also: secure, Cookie.secure
831 	*/
832 	noSecure = 1<<2,
833 
834 	/**
835     Instructs the browser to allow sending this cookie along with cross-site requests.
836 
837     By default, the protection is `strict`. This flag allows to set it to `lax`.
838     The strict value will prevent the cookie from being sent by the browser
839     to the target site in all cross-site browsing context,
840     even when following a regular link.
841 	*/
842 	noSameSiteStrict = 1<<3,
843 }
844 
845 
846 /**
847 	Represents a HTTP request as received by the server side.
848 */
849 final class HTTPServerRequest : HTTPRequest {
850 	import std.variant : Variant;
851 	import vibe.container.dictionarylist : DictionaryList;
852 
853 	private {
854 		SysTime m_timeCreated;
855 		HTTPServerSettings m_settings;
856 		ushort m_port;
857 		string m_peer;
858 
859 		// lazily parsed request components
860 		Nullable!string m_path;
861 		Nullable!CookieValueMap m_cookies;
862 		Nullable!FormFields m_query;
863 		Nullable!Json m_json;
864 		Nullable!FormFields m_form;
865 		FilePartFormFields m_files;
866 	}
867 
868 	/// The IP address of the client
869 	NetworkAddress clientAddress;
870 
871 	/// Determines if the request should be logged to the access log file.
872 	bool noLog;
873 
874 	/// Determines if the request was issued over an TLS encrypted channel.
875 	bool tls;
876 
877 	/** Information about the TLS certificate provided by the client.
878 
879 		Remarks: This field is only set if `tls` is true, and the peer
880 		presented a client certificate.
881 	*/
882 	TLSCertificateInformation clientCertificate;
883 
884 	/** The path part of the requested URI.
885 	*/
886 	InetPath requestPath;
887 
888 	/** The user name part of the URL, if present.
889 	*/
890 	string username;
891 
892 	/** The _password part of the URL, if present.
893 	*/
894 	string password;
895 
896 	/** The _query string part of the URL.
897 	*/
898 	string queryString;
899 
900 	/** A map of general parameters for the request.
901 
902 		This map is supposed to be used by middleware functionality to store
903 		information for later stages. For example vibe.http.router.URLRouter uses this map
904 		to store the value of any named placeholders.
905 	*/
906 	DictionaryList!(string, true, 8) params;
907 
908 	/** A map of context items for the request.
909 
910 		This is especially useful for passing application specific data down
911 		the chain of processors along with the request itself.
912 
913 		For example, a generic route may be defined to check user login status,
914 		if the user is logged in, add a reference to user specific data to the
915 		context.
916 
917 		This is implemented with `std.variant.Variant` to allow any type of data.
918 	*/
919 	DictionaryList!(Variant, true, 2) context;
920 
921 	/** Supplies the request body as a stream.
922 
923 		Note that when certain server options are set (such as
924 		HTTPServerOption.parseJsonBody) and a matching request was sent,
925 		the returned stream will be empty. If needed, remove those
926 		options and do your own processing of the body when launching
927 		the server. HTTPServerOption has a list of all options that affect
928 		the request body.
929 	*/
930 	InputStream bodyReader;
931 
932 	/** The current Session object.
933 
934 		This field is set if HTTPServerResponse.startSession() has been called
935 		on a previous response and if the client has sent back the matching
936 		cookie.
937 
938 		Remarks: Requires the HTTPServerOption.parseCookies option.
939 	*/
940 	Session session;
941 
942 
943 	this(SysTime time, ushort port)
944 	@safe scope {
945 		m_timeCreated = time.toUTC();
946 		m_port = port;
947 	}
948 
949 	/// The IP address of the client in string form
950 	@property string peer()
951 	@safe nothrow scope {
952 		if (!m_peer) {
953 			version (Have_vibe_core) {} else scope (failure) assert(false);
954 			// store the IP address (IPv4 addresses forwarded over IPv6 are stored in IPv4 format)
955 			auto peer_address_string = this.clientAddress.toString();
956 			if (peer_address_string.startsWith("::ffff:") && peer_address_string[7 .. $].indexOf(':') < 0)
957 				m_peer = peer_address_string[7 .. $];
958 			else m_peer = peer_address_string;
959 		}
960 		return m_peer;
961 	}
962 
963 	/** The _path part of the URL.
964 
965 		Note that this function contains the decoded version of the
966 		requested path, which can yield incorrect results if the path
967 		contains URL encoded path separators. Use `requestPath` instead to
968 		get an encoding-aware representation.
969 	*/
970 	deprecated("Use .requestPath instead")
971 	string path()
972 	@safe scope {
973 		if (m_path.isNull)
974 			m_path = urlDecode(requestPath.toString);
975 		return m_path.get;
976 	}
977 
978 	/** Contains the list of cookies that are stored on the client.
979 
980 		Note that the a single cookie name may occur multiple times if multiple
981 		cookies have that name but different paths or domains that all match
982 		the request URI. By default, the first cookie will be returned, which is
983 		the or one of the cookies with the closest path match.
984 	*/
985 	@property ref CookieValueMap cookies()
986 	@safe return {
987 		if (m_cookies.isNull) {
988 			m_cookies = CookieValueMap.init;
989 			if (auto pv = "cookie" in headers)
990 				parseCookies(*pv, m_cookies.get);
991 		}
992 		return m_cookies.get;
993 	}
994 
995 	/** Contains all _form fields supplied using the _query string.
996 
997 		The fields are stored in the same order as they are received.
998 	*/
999 	@property ref FormFields query()
1000 	@safe return {
1001 		if (m_query.isNull) {
1002 			m_query = FormFields.init;
1003 			parseURLEncodedForm(queryString, m_query.get);
1004 		}
1005 
1006 		return m_query.get;
1007 	}
1008 
1009 	/** Contains the parsed Json for a JSON request.
1010 
1011 		A JSON request must have the Content-Type "application/json" or "application/vnd.api+json".
1012 	*/
1013 	@property ref Json json()
1014 	@safe return {
1015 		if (m_json.isNull) {
1016 			auto splitter = contentType.splitter(';');
1017 			auto ctype = splitter.empty ? "" : splitter.front;
1018 
1019 			if (icmp2(ctype, "application/json") == 0 || icmp2(ctype, "application/vnd.api+json") == 0) {
1020 				auto bodyStr = bodyReader.readAllUTF8();
1021 				if (!bodyStr.empty) m_json = parseJson(bodyStr);
1022 				else m_json = Json.undefined;
1023 			} else {
1024 				m_json = Json.undefined;
1025 			}
1026 		}
1027 		return m_json.get;
1028 	}
1029 
1030 	/// Get the json body when there is no content-type header
1031 	unittest {
1032 		assert(createTestHTTPServerRequest(URL("http://localhost/")).json.type == Json.Type.undefined);
1033 	}
1034 
1035 	/** Contains the parsed parameters of a HTML POST _form request.
1036 
1037 		The fields are stored in the same order as they are received.
1038 
1039 		Remarks:
1040 			A form request must either have the Content-Type
1041 			"application/x-www-form-urlencoded" or "multipart/form-data".
1042 	*/
1043 	@property ref FormFields form()
1044 	@safe return {
1045 		if (m_form.isNull)
1046 			parseFormAndFiles();
1047 
1048 		return m_form.get;
1049 	}
1050 
1051 	/** Contains information about any uploaded file for a HTML _form request.
1052 	*/
1053 	@property ref FilePartFormFields files()
1054 	@safe return {
1055 		// m_form and m_files are parsed in one step
1056 		if (m_form.isNull) {
1057 			parseFormAndFiles();
1058 			assert(!m_form.isNull);
1059 		}
1060 
1061         return m_files;
1062 	}
1063 
1064 	/** Time when this request started processing.
1065 	*/
1066 	@property SysTime timeCreated() const @safe scope { return m_timeCreated; }
1067 
1068 
1069 	/** The full URL that corresponds to this request.
1070 
1071 		The host URL includes the protocol, host and optionally the user
1072 		and password that was used for this request. This field is useful to
1073 		construct self referencing URLs.
1074 
1075 		Note that the port is currently not set, so that this only works if
1076 		the standard port is used.
1077 	*/
1078 	@property URL fullURL()
1079 	const @safe scope {
1080 		URL url;
1081 
1082 		auto xfh = this.headers.get("X-Forwarded-Host");
1083 		auto xfp = this.headers.get("X-Forwarded-Port");
1084 		auto xfpr = this.headers.get("X-Forwarded-Proto");
1085 
1086 		// Set URL host segment.
1087 		if (xfh.length) {
1088 			url.host = xfh;
1089 		} else if (!this.host.empty) {
1090 			url.host = this.host;
1091 		} else if (!m_settings.hostName.empty) {
1092 			url.host = m_settings.hostName;
1093 		} else {
1094 			url.host = m_settings.bindAddresses[0];
1095 		}
1096 
1097 		// Set URL schema segment.
1098 		if (xfpr.length) {
1099 			url.schema = xfpr;
1100 		} else if (this.tls) {
1101 			url.schema = "https";
1102 		} else {
1103 			url.schema = "http";
1104 		}
1105 
1106 		// Set URL port segment.
1107 		if (xfp.length) {
1108 			try {
1109 				url.port = xfp.to!ushort;
1110 			} catch (ConvException) {
1111 				// TODO : Consider responding with a 400/etc. error from here.
1112 				logWarn("X-Forwarded-Port header was not valid port (%s)", xfp);
1113 			}
1114 		} else if (!xfh) {
1115 			if (url.schema == "https") {
1116 				if (m_port != 443U) url.port = m_port;
1117 			} else {
1118 				if (m_port != 80U)  url.port = m_port;
1119 			}
1120 		}
1121 
1122 		if (url.host.startsWith('[')) { // handle IPv6 address
1123 			auto idx = url.host.indexOf(']');
1124 			if (idx >= 0 && idx+1 < url.host.length && url.host[idx+1] == ':')
1125 				url.host = url.host[1 .. idx];
1126 		} else { // handle normal host names or IPv4 address
1127 			auto idx = url.host.indexOf(':');
1128 			if (idx >= 0) url.host = url.host[0 .. idx];
1129 		}
1130 
1131 		url.username = this.username;
1132 		url.password = this.password;
1133 		url.localURI = this.requestURI;
1134 
1135 		return url;
1136 	}
1137 
1138 	/** The relative path to the root folder.
1139 
1140 		Using this function instead of absolute URLs for embedded links can be
1141 		useful to avoid dead link when the site is piped through a
1142 		reverse-proxy.
1143 
1144 		The returned string always ends with a slash.
1145 	*/
1146 	@property string rootDir()
1147 	const @safe scope {
1148 		import std.algorithm.searching : count;
1149 		import std.range : empty;
1150 		auto depth = requestPath.bySegment.count!(s => !s.name.empty);
1151 		if (depth > 0 && !requestPath.endsWithSlash) depth--;
1152 		return depth == 0 ? "./" : replicate("../", depth);
1153 	}
1154 
1155 	unittest {
1156 		assert(createTestHTTPServerRequest(URL("http://localhost/")).rootDir == "./");
1157 		assert(createTestHTTPServerRequest(URL("http://localhost/foo")).rootDir == "./");
1158 		assert(createTestHTTPServerRequest(URL("http://localhost/foo/")).rootDir == "../");
1159 		assert(createTestHTTPServerRequest(URL("http://localhost/foo/bar")).rootDir == "../");
1160 		assert(createTestHTTPServerRequest(URL("http://localhost")).rootDir == "./");
1161 	}
1162 
1163 	/** The settings of the server serving this request.
1164 	 */
1165 	package @property const(HTTPServerSettings) serverSettings()
1166 	const @safe scope {
1167 		return m_settings;
1168 	}
1169 
1170 	private void parseFormAndFiles()
1171 	@safe scope {
1172 		m_form = FormFields.init;
1173 		parseFormData(m_form.get, m_files, headers.get("Content-Type", ""), bodyReader, m_settings.maxRequestHeaderLineSize);
1174 	}
1175 }
1176 
1177 
1178 /**
1179 	Represents a HTTP response as sent from the server side.
1180 */
1181 final class HTTPServerResponse : HTTPResponse {
1182 	alias Allocator = typeof(vibeThreadAllocator());
1183 
1184 	private {
1185 		InterfaceProxy!Stream m_conn;
1186 		InterfaceProxy!ConnectionStream m_rawConnection;
1187 		InterfaceProxy!OutputStream m_bodyWriter;
1188 		Allocator m_requestAlloc;
1189 		FreeListRef!ChunkedOutputStream m_chunkedBodyWriter;
1190 		FreeListRef!CountingOutputStream m_countingWriter;
1191 		FreeListRef!ZlibOutputStream m_zlibOutputStream;
1192 		HTTPServerSettings m_settings;
1193 		Session m_session;
1194 		bool m_headerWritten = false;
1195 		bool m_isHeadResponse = false;
1196 		bool m_tls;
1197 		bool m_requiresConnectionClose;
1198 		SysTime m_timeFinalized;
1199 	}
1200 
1201 	static if (!is(Stream == InterfaceProxy!Stream)) {
1202 		this(Stream conn, ConnectionStream raw_connection, HTTPServerSettings settings, Allocator req_alloc)
1203 		@safe scope {
1204 			this(InterfaceProxy!Stream(conn), InterfaceProxy!ConnectionStream(raw_connection), settings, req_alloc);
1205 		}
1206 	}
1207 
1208 	this(InterfaceProxy!Stream conn, InterfaceProxy!ConnectionStream raw_connection, HTTPServerSettings settings, Allocator req_alloc)
1209 	@safe scope {
1210 		m_conn = conn;
1211 		m_rawConnection = raw_connection;
1212 		m_countingWriter = createCountingOutputStreamFL(conn);
1213 		m_settings = settings;
1214 		m_requestAlloc = req_alloc;
1215 	}
1216 
1217 	/** Sends a redirect request to the client.
1218 
1219 		Params:
1220 			url = The URL to redirect to
1221 			status = The HTTP redirect status (3xx) to send - by default this is $(D HTTPStatus.found)
1222 	*/
1223 	void redirect(string url, int status = HTTPStatus.found)
1224 	@safe scope {
1225 		// Disallow any characters that may influence the header parsing
1226 		enforce(!url.representation.canFind!(ch => ch < 0x20),
1227 			"Control character in redirection URL.");
1228 
1229 		statusCode = status;
1230 		headers["Location"] = url;
1231 		writeBody("redirecting...");
1232 	}
1233 	/// ditto
1234 	void redirect(URL url, int status = HTTPStatus.found)
1235 	@safe scope {
1236 		redirect(url.toString(), status);
1237 	}
1238 
1239 	///
1240 	@safe unittest {
1241 		import vibe.http.router;
1242 
1243 		void request_handler(HTTPServerRequest req, HTTPServerResponse res)
1244 		{
1245 			res.redirect("http://example.org/some_other_url");
1246 		}
1247 
1248 		void test()
1249 		{
1250 			auto router = new URLRouter;
1251 			router.get("/old_url", &request_handler);
1252 
1253 			listenHTTP(new HTTPServerSettings, router);
1254 		}
1255 	}
1256 
1257 scope:
1258 
1259 	/** Returns the time at which the request was finalized.
1260 
1261 		Note that this field will only be set after `finalize` has been called.
1262 	*/
1263 	@property SysTime timeFinalized() const @safe { return m_timeFinalized; }
1264 
1265 	/** Determines if the HTTP header has already been written.
1266 	*/
1267 	@property bool headerWritten() const @safe { return m_headerWritten; }
1268 
1269 	/** Determines if the response does not need a body.
1270 	*/
1271 	bool isHeadResponse() const @safe { return m_isHeadResponse; }
1272 
1273 	/** Determines if the response is sent over an encrypted connection.
1274 	*/
1275 	bool tls() const @safe { return m_tls; }
1276 
1277 	/** Writes the entire response body at once.
1278 
1279 		Params:
1280 			data = The data to write as the body contents
1281 			status = Optional response status code to set
1282 			content_type = Optional content type to apply to the response.
1283 				If no content type is given and no "Content-Type" header is
1284 				set in the response, this will default to
1285 				`"application/octet-stream"`.
1286 
1287 		See_Also: `HTTPStatusCode`
1288 	*/
1289 	void writeBody(in ubyte[] data, string content_type = null)
1290 	@safe {
1291 		if (content_type.length) headers["Content-Type"] = content_type;
1292 		else if ("Content-Type" !in headers) headers["Content-Type"] = "application/octet-stream";
1293 		headers["Content-Length"] = formatAlloc(m_requestAlloc, "%d", data.length);
1294 		bodyWriter.write(data);
1295 	}
1296 	/// ditto
1297 	void writeBody(in ubyte[] data, int status, string content_type = null)
1298 	@safe {
1299 		statusCode = status;
1300 		writeBody(data, content_type);
1301 	}
1302 	/// ditto
1303 	void writeBody(scope InputStream data, string content_type = null)
1304 	@safe {
1305 		if (content_type.length) headers["Content-Type"] = content_type;
1306 		else if ("Content-Type" !in headers) headers["Content-Type"] = "application/octet-stream";
1307 		data.pipe(bodyWriter);
1308 	}
1309 
1310 	/** Writes the entire response body as a single string.
1311 
1312 		Params:
1313 			data = The string to write as the body contents
1314 			status = Optional response status code to set
1315 			content_type = Optional content type to apply to the response.
1316 				If no content type is given and no "Content-Type" header is
1317 				set in the response, this will default to
1318 				`"text/plain; charset=UTF-8"`.
1319 
1320 		See_Also: `HTTPStatusCode`
1321 	*/
1322 	/// ditto
1323 	void writeBody(string data, string content_type = null)
1324 	@safe {
1325 		if (!content_type.length && "Content-Type" !in headers)
1326 			content_type = "text/plain; charset=UTF-8";
1327 		writeBody(cast(const(ubyte)[])data, content_type);
1328 	}
1329 	/// ditto
1330 	void writeBody(string data, int status, string content_type = null)
1331 	@safe {
1332 		statusCode = status;
1333 		writeBody(data, content_type);
1334 	}
1335 
1336 	/** Writes the whole response body at once, without doing any further encoding.
1337 
1338 		The caller has to make sure that the appropriate headers are set correctly
1339 		(i.e. Content-Type and Content-Encoding).
1340 
1341 		Note that the version taking a RandomAccessStream may perform additional
1342 		optimizations such as sending a file directly from the disk to the
1343 		network card using a DMA transfer.
1344 
1345 	*/
1346 	void writeRawBody(RandomAccessStream)(RandomAccessStream stream) @safe
1347 		if (isRandomAccessStream!RandomAccessStream)
1348 	{
1349 		assert(!m_headerWritten, "A body was already written!");
1350 		writeHeader();
1351 		if (m_isHeadResponse) return;
1352 
1353 		auto bytes = stream.size - stream.tell();
1354 		stream.pipe(m_conn);
1355 		m_countingWriter.increment(bytes);
1356 	}
1357 	/// ditto
1358 	void writeRawBody(InputStream)(InputStream stream, size_t num_bytes = 0) @safe
1359 		if (isInputStream!InputStream && !isRandomAccessStream!InputStream)
1360 	{
1361 		assert(!m_headerWritten, "A body was already written!");
1362 		writeHeader();
1363 		if (m_isHeadResponse) return;
1364 
1365 		if (num_bytes > 0) {
1366 			stream.pipe(m_conn, num_bytes);
1367 			m_countingWriter.increment(num_bytes);
1368 		} else stream.pipe(m_countingWriter, num_bytes);
1369 	}
1370 	/// ditto
1371 	void writeRawBody(RandomAccessStream)(RandomAccessStream stream, int status) @safe
1372 		if (isRandomAccessStream!RandomAccessStream)
1373 	{
1374 		statusCode = status;
1375 		writeRawBody(stream);
1376 	}
1377 	/// ditto
1378 	void writeRawBody(InputStream)(InputStream stream, int status, size_t num_bytes = 0) @safe
1379 		if (isInputStream!InputStream && !isRandomAccessStream!InputStream)
1380 	{
1381 		statusCode = status;
1382 		writeRawBody(stream, num_bytes);
1383 	}
1384 
1385 
1386 	/// Writes a JSON message with the specified status
1387 	void writeJsonBody(T)(T data, int status, bool allow_chunked = false)
1388 	{
1389 		statusCode = status;
1390 		writeJsonBody(data, allow_chunked);
1391 	}
1392 	/// ditto
1393 	void writeJsonBody(T)(T data, int status, string content_type, bool allow_chunked = false)
1394 	{
1395 		statusCode = status;
1396 		writeJsonBody(data, content_type, allow_chunked);
1397 	}
1398 
1399 	/// ditto
1400 	void writeJsonBody(T)(T data, string content_type, bool allow_chunked = false)
1401 	{
1402 		headers["Content-Type"] = content_type;
1403 		writeJsonBody(data, allow_chunked);
1404 	}
1405 	/// ditto
1406 	void writeJsonBody(T)(T data, bool allow_chunked = false)
1407 	{
1408 		doWriteJsonBody!(T, false)(data, allow_chunked);
1409 	}
1410 	/// ditto
1411 	void writePrettyJsonBody(T)(T data, bool allow_chunked = false)
1412 	{
1413 		doWriteJsonBody!(T, true)(data, allow_chunked);
1414 	}
1415 
1416 	private void doWriteJsonBody(T, bool PRETTY)(T data, bool allow_chunked = false)
1417 	{
1418 		import std.traits;
1419 		import vibe.stream.wrapper;
1420 
1421 		static if (!is(T == Json) && is(typeof(data.data())) && isArray!(typeof(data.data()))) {
1422 			static assert(!is(T == Appender!(typeof(data.data()))), "Passed an Appender!T to writeJsonBody - this is most probably not doing what's indended.");
1423 		}
1424 
1425 		if ("Content-Type" !in headers)
1426 			headers["Content-Type"] = "application/json; charset=UTF-8";
1427 
1428 
1429 		// set an explicit content-length field if chunked encoding is not allowed
1430 		if (!allow_chunked) {
1431 			import vibe.internal.rangeutil;
1432 			long length = 0;
1433 			auto counter = RangeCounter(() @trusted { return &length; } ());
1434 			static if (PRETTY) serializeToPrettyJson(counter, data);
1435 			else serializeToJson(counter, data);
1436 			headers["Content-Length"] = formatAlloc(m_requestAlloc, "%d", length);
1437 		}
1438 
1439 		auto rng = streamOutputRange!1024(bodyWriter);
1440 		static if (PRETTY) serializeToPrettyJson(() @trusted { return &rng; } (), data);
1441 		else serializeToJson(() @trusted { return &rng; } (), data);
1442 	}
1443 
1444 	/**
1445 	 * Writes the response with no body.
1446 	 *
1447 	 * This method should be used in situations where no body is
1448 	 * requested, such as a HEAD request. For an empty body, just use writeBody,
1449 	 * as this method causes problems with some keep-alive connections.
1450 	 */
1451 	void writeVoidBody()
1452 	@safe {
1453 		if (!m_isHeadResponse) {
1454 			assert("Content-Length" !in headers);
1455 			assert("Transfer-Encoding" !in headers);
1456 		}
1457 		assert(!headerWritten);
1458 		writeHeader();
1459 		m_conn.flush();
1460 	}
1461 
1462 	/** A stream for writing the body of the HTTP response.
1463 
1464 		Note that after 'bodyWriter' has been accessed for the first time, it
1465 		is not allowed to change any header or the status code of the response.
1466 	*/
1467 	@property InterfaceProxy!OutputStream bodyWriter()
1468 	@safe scope {
1469 		assert(!!m_conn);
1470 		if (m_bodyWriter) {
1471 			// for test responses, the body writer is pre-set, without headers
1472 			// being written, so we may need to do that here
1473 			if (!m_headerWritten) writeHeader();
1474 
1475 			return m_bodyWriter;
1476 		}
1477 
1478 		assert(!m_headerWritten, "A void body was already written!");
1479 		assert(this.statusCode >= 200, "1xx responses can't have body");
1480 
1481 		if (m_isHeadResponse) {
1482 			// for HEAD requests, we define a NullOutputWriter for convenience
1483 			// - no body will be written. However, the request handler should call writeVoidBody()
1484 			// and skip writing of the body in this case.
1485 			if ("Content-Length" !in headers)
1486 				headers["Transfer-Encoding"] = "chunked";
1487 			writeHeader();
1488 			m_bodyWriter = nullSink;
1489 			return m_bodyWriter;
1490 		}
1491 
1492 		if ("Content-Encoding" in headers && "Content-Length" in headers) {
1493 			// we do not known how large the compressed body will be in advance
1494 			// so remove the content-length and use chunked transfer
1495 			headers.remove("Content-Length");
1496 		}
1497 
1498 		if (auto pcl = "Content-Length" in headers) {
1499 			writeHeader();
1500 			m_countingWriter.writeLimit = (*pcl).to!ulong;
1501 			m_bodyWriter = m_countingWriter;
1502 		} else if (httpVersion <= HTTPVersion.HTTP_1_0) {
1503 			if ("Connection" in headers)
1504 				headers.remove("Connection"); // default to "close"
1505 			writeHeader();
1506 			m_bodyWriter = m_conn;
1507 		} else {
1508 			headers["Transfer-Encoding"] = "chunked";
1509 			writeHeader();
1510 			m_chunkedBodyWriter = createChunkedOutputStreamFL(m_countingWriter);
1511 			m_bodyWriter = m_chunkedBodyWriter;
1512 		}
1513 
1514 		if (auto pce = "Content-Encoding" in headers) {
1515 			if (icmp2(*pce, "gzip") == 0) {
1516 				m_zlibOutputStream = createGzipOutputStreamFL(m_bodyWriter);
1517 				m_bodyWriter = m_zlibOutputStream;
1518 			} else if (icmp2(*pce, "deflate") == 0) {
1519 				m_zlibOutputStream = createDeflateOutputStreamFL(m_bodyWriter);
1520 				m_bodyWriter = m_zlibOutputStream;
1521 			} else {
1522 				logWarn("Unsupported Content-Encoding set in response: '"~*pce~"'");
1523 			}
1524 		}
1525 
1526 		return m_bodyWriter;
1527 	}
1528 
1529 
1530 
1531 	/** Special method sending a SWITCHING_PROTOCOLS response to the client.
1532 
1533 		Notice: For the overload that returns a `ConnectionStream`, it must be
1534 			ensured that the returned instance doesn't outlive the request
1535 			handler callback.
1536 
1537 		Params:
1538 			protocol = The protocol set in the "Upgrade" header of the response.
1539 				Use an empty string to skip setting this field.
1540 	*/
1541 	ConnectionStream switchProtocol(string protocol)
1542 	@safe {
1543 		statusCode = HTTPStatus.switchingProtocols;
1544 		if (protocol.length) headers["Upgrade"] = protocol;
1545 		writeVoidBody();
1546 		m_requiresConnectionClose = true;
1547 		m_headerWritten = true;
1548 		return createConnectionProxyStream(m_conn, m_rawConnection);
1549 	}
1550 	/// ditto
1551 	void switchProtocol(string protocol, scope void delegate(scope ConnectionStream) @safe del)
1552 	@safe {
1553 		statusCode = HTTPStatus.switchingProtocols;
1554 		if (protocol.length) headers["Upgrade"] = protocol;
1555 		writeVoidBody();
1556 		m_requiresConnectionClose = true;
1557 		m_headerWritten = true;
1558 		() @trusted {
1559 			auto conn = createConnectionProxyStreamFL(m_conn, m_rawConnection);
1560 			del(conn);
1561 		} ();
1562 		finalize();
1563 	}
1564 
1565 	/** Special method for handling CONNECT proxy tunnel
1566 
1567 		Notice: For the overload that returns a `ConnectionStream`, it must be
1568 			ensured that the returned instance doesn't outlive the request
1569 			handler callback.
1570 	*/
1571 	ConnectionStream connectProxy()
1572 	@safe {
1573 		return createConnectionProxyStream(m_conn, m_rawConnection);
1574 	}
1575 	/// ditto
1576 	void connectProxy(scope void delegate(scope ConnectionStream) @safe del)
1577 	@safe {
1578 		() @trusted {
1579 			auto conn = createConnectionProxyStreamFL(m_conn, m_rawConnection);
1580 			del(conn);
1581 		} ();
1582 		finalize();
1583 	}
1584 
1585 	/** Sets the specified cookie value.
1586 
1587 		Params:
1588 			name = Name of the cookie
1589 			value = New cookie value - pass null to clear the cookie
1590 			path = Path (as seen by the client) of the directory tree in which the cookie is visible
1591 			encoding = Optional encoding (url, raw), default to URL encoding
1592 	*/
1593 	Cookie setCookie(string name, string value, string path = "/", Cookie.Encoding encoding = Cookie.Encoding.url)
1594 	@safe {
1595 		auto cookie = new Cookie();
1596 		cookie.path = path;
1597 		cookie.setValue(value, encoding);
1598 		if (value is null) {
1599 			cookie.maxAge = 0;
1600 			cookie.expires = "Thu, 01 Jan 1970 00:00:00 GMT";
1601 		}
1602 		cookies[name] = cookie;
1603 		return cookie;
1604 	}
1605 
1606 	/**
1607 		Initiates a new session.
1608 
1609 		The session is stored in the SessionStore that was specified when
1610 		creating the server. Depending on this, the session can be persistent
1611 		or temporary and specific to this server instance.
1612 	*/
1613 	Session startSession(string path = "/")
1614 	@safe {
1615 		return startSession(path, m_settings.sessionOptions);
1616 	}
1617 
1618 	/// ditto
1619 	Session startSession(string path, SessionOption options)
1620 	@safe {
1621 		assert(m_settings.sessionStore, "no session store set");
1622 		assert(!m_session, "Try to start a session, but already started one.");
1623 
1624 		bool secure;
1625 		if (options & SessionOption.secure) secure = true;
1626 		else if (options & SessionOption.noSecure) secure = false;
1627 		else secure = this.tls;
1628 
1629 		m_session = m_settings.sessionStore.create();
1630 		m_session.set("$sessionCookiePath", path);
1631 		m_session.set("$sessionCookieSecure", secure);
1632 		auto cookie = setCookie(m_settings.sessionIdCookie, m_session.id, path);
1633 		cookie.secure = secure;
1634 		cookie.httpOnly = (options & SessionOption.httpOnly) != 0;
1635 		cookie.sameSite = (options & SessionOption.noSameSiteStrict) ?
1636 						  Cookie.SameSite.lax : Cookie.SameSite.strict;
1637 		return m_session;
1638 	}
1639 
1640 	/**
1641 		Terminates the current session (if any).
1642 	*/
1643 	void terminateSession()
1644 	@safe {
1645 		if (!m_session) return;
1646 		auto cookie = setCookie(m_settings.sessionIdCookie, null, m_session.get!string("$sessionCookiePath"));
1647 		cookie.secure = m_session.get!bool("$sessionCookieSecure");
1648 		m_session.destroy();
1649 		m_session = Session.init;
1650 	}
1651 
1652 	@property ulong bytesWritten() @safe const { return m_countingWriter.bytesWritten; }
1653 
1654 	/**
1655 		Waits until either the connection closes, data arrives, or until the
1656 		given timeout is reached.
1657 
1658 		Returns:
1659 			$(D true) if the connection was closed and $(D false) if either the
1660 			timeout was reached, or if data has arrived for consumption.
1661 
1662 		See_Also: `connected`
1663 	*/
1664 	bool waitForConnectionClose(Duration timeout = Duration.max)
1665 	@safe {
1666 		if (!m_rawConnection || !m_rawConnection.connected) return true;
1667 		m_rawConnection.waitForData(timeout);
1668 		return !m_rawConnection.connected;
1669 	}
1670 
1671 	/**
1672 		Determines if the underlying connection is still alive.
1673 
1674 		Returns $(D true) if the remote peer is still connected and $(D false)
1675 		if the remote peer closed the connection.
1676 
1677 		See_Also: `waitForConnectionClose`
1678 	*/
1679 	@property bool connected()
1680 	@safe const {
1681 		if (!m_rawConnection) return false;
1682 		return m_rawConnection.connected;
1683 	}
1684 
1685 	/**
1686 		Finalizes the response. This is usually called automatically by the server.
1687 
1688 		This method can be called manually after writing the response to force
1689 		all network traffic associated with the current request to be finalized.
1690 		After the call returns, the `timeFinalized` property will be set.
1691 	*/
1692 	void finalize()
1693 	@safe {
1694 		if (m_zlibOutputStream) {
1695 			m_zlibOutputStream.finalize();
1696 			m_zlibOutputStream.destroy();
1697 		}
1698 		if (m_chunkedBodyWriter) {
1699 			m_chunkedBodyWriter.finalize();
1700 			m_chunkedBodyWriter.destroy();
1701 		}
1702 
1703 		// ignore exceptions caused by an already closed connection - the client
1704 		// may have closed the connection already and this doesn't usually indicate
1705 		// a problem.
1706 		if (m_rawConnection && m_rawConnection.connected) {
1707 			try if (m_conn) m_conn.flush();
1708 			catch (Exception e) logDebug("Failed to flush connection after finishing HTTP response: %s", e.msg);
1709 			if (!isHeadResponse && bytesWritten < headers.get("Content-Length", "0").to!ulong) {
1710 				logDebug("HTTP response only written partially before finalization. Terminating connection.");
1711 				m_requiresConnectionClose = true;
1712 			}
1713 
1714 			m_rawConnection = InterfaceProxy!ConnectionStream.init;
1715 		}
1716 
1717 		if (m_conn) {
1718 			m_conn = InterfaceProxy!Stream.init;
1719 			m_timeFinalized = Clock.currTime(UTC());
1720 		}
1721 	}
1722 
1723 	private void writeHeader()
1724 	@safe {
1725 		import vibe.stream.wrapper;
1726 
1727 		assert(!m_headerWritten, "Try to write header after body has already begun.");
1728 		assert(this.httpVersion != HTTPVersion.HTTP_1_0 || this.statusCode >= 200, "Informational status codes aren't supported by HTTP/1.0.");
1729 
1730 		// Don't set m_headerWritten for 1xx status codes
1731 		if (this.statusCode >= 200) m_headerWritten = true;
1732 		auto dst = streamOutputRange!1024(m_conn);
1733 
1734 		void writeLine(T...)(string fmt, T args)
1735 		@safe {
1736 			formattedWrite(() @trusted { return &dst; } (), fmt, args);
1737 			dst.put("\r\n");
1738 			logTrace(fmt, args);
1739 		}
1740 
1741 		logTrace("---------------------");
1742 		logTrace("HTTP server response:");
1743 		logTrace("---------------------");
1744 
1745 		// write the status line
1746 		writeLine("%s %d %s",
1747 			getHTTPVersionString(this.httpVersion),
1748 			this.statusCode,
1749 			this.statusPhrase.length ? this.statusPhrase : httpStatusText(this.statusCode));
1750 
1751 		// write all normal headers
1752 		foreach (k, v; this.headers.byKeyValue) {
1753 			dst.put(k);
1754 			dst.put(": ");
1755 			dst.put(v);
1756 			dst.put("\r\n");
1757 			logTrace("%s: %s", k, v);
1758 		}
1759 
1760 		logTrace("---------------------");
1761 
1762 		// write cookies
1763 		foreach (n, cookie; () @trusted { return this.cookies.byKeyValue; } ()) {
1764 			dst.put("Set-Cookie: ");
1765 			cookie.writeString(() @trusted { return &dst; } (), n);
1766 			dst.put("\r\n");
1767 		}
1768 
1769 		// finalize response header
1770 		dst.put("\r\n");
1771 	}
1772 }
1773 
1774 /**
1775 	Represents the request listener for a specific `listenHTTP` call.
1776 
1777 	This struct can be used to stop listening for HTTP requests at runtime.
1778 */
1779 struct HTTPListener {
1780 	private {
1781 		size_t[] m_virtualHostIDs;
1782 	}
1783 
1784 	private this(size_t[] ids) @safe { m_virtualHostIDs = ids; }
1785 
1786 	@property NetworkAddress[] bindAddresses()
1787 	@safe {
1788 		NetworkAddress[] ret;
1789 		foreach (l; s_listeners)
1790 			if (l.m_virtualHosts.canFind!(v => m_virtualHostIDs.canFind(v.id))) {
1791 				NetworkAddress a;
1792 				a = resolveHost(l.bindAddress);
1793 				a.port = l.bindPort;
1794 				ret ~= a;
1795 			}
1796 		return ret;
1797 	}
1798 
1799 	/** Stops handling HTTP requests and closes the TCP listening port if
1800 		possible.
1801 	*/
1802 	void stopListening()
1803 	@safe {
1804 		import std.algorithm : countUntil;
1805 
1806 		foreach (vhid; m_virtualHostIDs) {
1807 			foreach (lidx, l; s_listeners) {
1808 				if (l.removeVirtualHost(vhid)) {
1809 					if (!l.hasVirtualHosts) {
1810 						l.m_listener.stopListening();
1811 						logInfo("Stopped to listen for HTTP%s requests on %s:%s", l.tlsContext ? "S": "", l.bindAddress, l.bindPort);
1812 						s_listeners = s_listeners[0 .. lidx] ~ s_listeners[lidx+1 .. $];
1813 					}
1814 				}
1815 				break;
1816 			}
1817 		}
1818 	}
1819 }
1820 
1821 
1822 /** Represents a single HTTP server port.
1823 
1824 	This class defines the incoming interface, port, and TLS configuration of
1825 	the public server port. The public server port may differ from the local
1826 	one if a reverse proxy of some kind is facing the public internet and
1827 	forwards to this HTTP server.
1828 
1829 	Multiple virtual hosts can be configured to be served from the same port.
1830 	Their TLS settings must be compatible and each virtual host must have a
1831 	unique name.
1832 */
1833 final class HTTPServerContext {
1834 	private struct VirtualHost {
1835 		HTTPServerRequestDelegate requestHandler;
1836 		HTTPServerSettings settings;
1837 		HTTPLogger[] loggers;
1838 		size_t id;
1839 	}
1840 
1841 	private {
1842 		TCPListener m_listener;
1843 		VirtualHost[] m_virtualHosts;
1844 		string m_bindAddress;
1845 		ushort m_bindPort;
1846 		TLSContext m_tlsContext;
1847 		static size_t s_vhostIDCounter = 1;
1848 	}
1849 
1850 	@safe:
1851 
1852 	this(string bind_address, ushort bind_port)
1853 	{
1854 		m_bindAddress = bind_address;
1855 		m_bindPort = bind_port;
1856 	}
1857 
1858 	/** Returns the TLS context associated with the listener.
1859 
1860 		For non-HTTPS listeners, `null` will be returned. Otherwise, if only a
1861 		single virtual host has been added, the TLS context of that host's
1862 		settings is returned. For multiple virtual hosts, an SNI context is
1863 		returned, which forwards to the individual contexts based on the
1864 		requested host name.
1865 	*/
1866 	@property TLSContext tlsContext() { return m_tlsContext; }
1867 
1868 	/// The local network interface IP address associated with this listener
1869 	@property string bindAddress() const { return m_bindAddress; }
1870 
1871 	/// The local port associated with this listener
1872 	@property ushort bindPort() const { return m_bindPort; }
1873 
1874 	/// Determines if any virtual hosts have been addded
1875 	@property bool hasVirtualHosts() const { return m_virtualHosts.length > 0; }
1876 
1877 	/** Adds a single virtual host.
1878 
1879 		Note that the port and bind address defined in `settings` must match the
1880 		ones for this listener. The `settings.host` field must be unique for
1881 		all virtual hosts.
1882 
1883 		Returns: Returns a unique ID for the new virtual host
1884 	*/
1885 	size_t addVirtualHost(HTTPServerSettings settings, HTTPServerRequestDelegate request_handler)
1886 	{
1887 		assert(settings.port == 0 || settings.port == m_bindPort, "Virtual host settings do not match bind port.");
1888 		assert(settings.bindAddresses.canFind(m_bindAddress), "Virtual host settings do not match bind address.");
1889 
1890 		VirtualHost vhost;
1891 		vhost.id = s_vhostIDCounter++;
1892 		vhost.settings = settings;
1893 		vhost.requestHandler = request_handler;
1894 
1895 		if (settings.accessLogger) vhost.loggers ~= settings.accessLogger;
1896 		if (settings.accessLogToConsole)
1897 			vhost.loggers ~= new HTTPConsoleLogger(settings, settings.accessLogFormat);
1898 		if (settings.accessLogFile.length)
1899 			vhost.loggers ~= new HTTPFileLogger(settings, settings.accessLogFormat, settings.accessLogFile);
1900 
1901 		if (!m_virtualHosts.length) m_tlsContext = settings.tlsContext;
1902 
1903 		enforce((m_tlsContext !is null) == (settings.tlsContext !is null),
1904 			"Cannot mix HTTP and HTTPS virtual hosts within the same listener.");
1905 
1906 		if (m_tlsContext) addSNIHost(settings);
1907 
1908 		m_virtualHosts ~= vhost;
1909 
1910 		if (settings.hostName.length) {
1911 			auto proto = settings.tlsContext ? "https" : "http";
1912 			auto port = settings.tlsContext && settings.port == 443 || !settings.tlsContext && settings.port == 80 ? "" : ":" ~ settings.port.to!string;
1913 			logInfo("Added virtual host %s://%s:%s/ (%s)", proto, settings.hostName, m_bindPort, m_bindAddress);
1914 		}
1915 
1916 		return vhost.id;
1917 	}
1918 
1919 	/// Removes a previously added virtual host using its ID.
1920 	bool removeVirtualHost(size_t id)
1921 	{
1922 		import std.algorithm.searching : countUntil;
1923 
1924 		auto idx = m_virtualHosts.countUntil!(c => c.id == id);
1925 		if (idx < 0) return false;
1926 
1927 		auto ctx = m_virtualHosts[idx];
1928 		m_virtualHosts = m_virtualHosts[0 .. idx] ~ m_virtualHosts[idx+1 .. $];
1929 		return true;
1930 	}
1931 
1932 	private void addSNIHost(HTTPServerSettings settings)
1933 	{
1934 		if (settings.tlsContext !is m_tlsContext && m_tlsContext.kind != TLSContextKind.serverSNI) {
1935 			logDebug("Create SNI TLS context for %s, port %s", bindAddress, bindPort);
1936 			m_tlsContext = createTLSContext(TLSContextKind.serverSNI);
1937 			m_tlsContext.sniCallback = &onSNI;
1938 		}
1939 
1940 		foreach (ctx; m_virtualHosts) {
1941 			/*enforce(ctx.settings.hostName != settings.hostName,
1942 				"A server with the host name '"~settings.hostName~"' is already "
1943 				"listening on "~addr~":"~to!string(settings.port)~".");*/
1944 		}
1945 	}
1946 
1947 	private TLSContext onSNI(string servername)
1948 	{
1949 		foreach (vhost; m_virtualHosts)
1950 			if (vhost.settings.hostName.icmp(servername) == 0) {
1951 				logDebug("Found context for SNI host '%s'.", servername);
1952 				return vhost.settings.tlsContext;
1953 			}
1954 		logDebug("No context found for SNI host '%s'.", servername);
1955 		return null;
1956 	}
1957 }
1958 
1959 /**************************************************************************************************/
1960 /* Private types                                                                                  */
1961 /**************************************************************************************************/
1962 
1963 private final class LimitedHTTPInputStream : LimitedInputStream {
1964 @safe:
1965 
1966 	this(InterfaceProxy!InputStream stream, ulong byte_limit, bool silent_limit = false) {
1967 		super(stream, byte_limit, silent_limit, true);
1968 	}
1969 	override void onSizeLimitReached() {
1970 		throw new HTTPStatusException(HTTPStatus.requestEntityTooLarge);
1971 	}
1972 }
1973 
1974 private final class TimeoutHTTPInputStream : InputStream {
1975 @safe:
1976 
1977 	private {
1978 		long m_timeref;
1979 		long m_timeleft;
1980 		InterfaceProxy!InputStream m_in;
1981 	}
1982 
1983 	this(InterfaceProxy!InputStream stream, Duration timeleft, SysTime reftime)
1984 	{
1985 		enforce(timeleft > 0.seconds, "Timeout required");
1986 		m_in = stream;
1987 		m_timeleft = timeleft.total!"hnsecs"();
1988 		m_timeref = reftime.stdTime();
1989 	}
1990 
1991 	@property bool empty() { enforce(m_in, "InputStream missing"); return m_in.empty(); }
1992 	@property ulong leastSize() { enforce(m_in, "InputStream missing"); return m_in.leastSize();  }
1993 	@property bool dataAvailableForRead() {  enforce(m_in, "InputStream missing"); return m_in.dataAvailableForRead; }
1994 	const(ubyte)[] peek() { return m_in.peek(); }
1995 
1996 	size_t read(scope ubyte[] dst, IOMode mode)
1997 	{
1998 		enforce(m_in, "InputStream missing");
1999 		size_t nread = 0;
2000 		checkTimeout();
2001 		// FIXME: this should use ConnectionStream.waitForData to enforce the timeout during the
2002 		// read operation
2003 		return m_in.read(dst, mode);
2004 	}
2005 
2006 	alias read = InputStream.read;
2007 
2008 	private void checkTimeout()
2009 	@safe {
2010 		auto curr = Clock.currStdTime();
2011 		auto diff = curr - m_timeref;
2012 		if (diff > m_timeleft) throw new HTTPStatusException(HTTPStatus.requestTimeout);
2013 		m_timeleft -= diff;
2014 		m_timeref = curr;
2015 	}
2016 }
2017 
2018 /**************************************************************************************************/
2019 /* Private functions                                                                              */
2020 /**************************************************************************************************/
2021 
2022 private {
2023 	import core.sync.mutex;
2024 
2025 	shared string s_distHost;
2026 	shared ushort s_distPort = 11000;
2027 
2028 	HTTPServerContext[] s_listeners;
2029 }
2030 
2031 /**
2032 	[private] Starts a HTTP server listening on the specified port.
2033 
2034 	This is the same as listenHTTP() except that it does not use a VibeDist host for
2035 	remote listening, even if specified on the command line.
2036 */
2037 private HTTPListener listenHTTPPlain(HTTPServerSettings settings, HTTPServerRequestDelegate request_handler)
2038 @safe {
2039 	import vibe.core.core : runWorkerTaskDist;
2040 	import std.algorithm : canFind, find;
2041 
2042 	static TCPListener doListen(HTTPServerContext listen_info, bool reusePort, bool reuseAddress, bool is_tls)
2043 	@safe {
2044 		try {
2045 			TCPListenOptions options = TCPListenOptions.defaults;
2046 			if(reuseAddress) options |= TCPListenOptions.reuseAddress; else options &= ~TCPListenOptions.reuseAddress;
2047 			if(reusePort) options |= TCPListenOptions.reusePort; else options &= ~TCPListenOptions.reusePort;
2048 			auto ret = listenTCP(listen_info.bindPort, (TCPConnection conn) nothrow @safe {
2049 					try handleHTTPConnection(conn, listen_info);
2050 					catch (Exception e) {
2051 						logError("HTTP connection handler has thrown: %s", e.msg);
2052 						debug logDebug("Full error: %s", () @trusted { return e.toString().sanitize(); } ());
2053 						try conn.close();
2054 						catch (Exception e) logError("Failed to close connection: %s", e.msg);
2055 					}
2056 				}, listen_info.bindAddress, options);
2057 
2058 			// support port 0 meaning any available port
2059 			if (listen_info.bindPort == 0)
2060 				listen_info.m_bindPort = ret.bindAddress.port;
2061 
2062 			auto proto = is_tls ? "https" : "http";
2063 			auto urladdr = listen_info.bindAddress;
2064 			if (urladdr.canFind(':')) urladdr = "["~urladdr~"]";
2065 			logInfo("Listening for requests on %s://%s:%s/", proto, urladdr, listen_info.bindPort);
2066 			return ret;
2067 		} catch( Exception e ) {
2068 			logWarn("Failed to listen on %s:%s", listen_info.bindAddress, listen_info.bindPort);
2069 			return TCPListener.init;
2070 		}
2071 	}
2072 
2073 	size_t[] vid;
2074 
2075 	// Check for every bind address/port, if a new listening socket needs to be created and
2076 	// check for conflicting servers
2077 	foreach (addr; settings.bindAddresses) {
2078 		HTTPServerContext linfo;
2079 
2080 		auto l = s_listeners.find!(l => l.bindAddress == addr && l.bindPort == settings.port);
2081 		if (!l.empty) linfo = l.front;
2082 		else {
2083 			auto li = new HTTPServerContext(addr, settings.port);
2084 			if (auto tcp_lst = doListen(li,
2085 					(settings.options & HTTPServerOption.reusePort) != 0,
2086 					(settings.options & HTTPServerOption.reuseAddress) != 0,
2087 					settings.tlsContext !is null)) // DMD BUG 2043
2088 			{
2089 				li.m_listener = tcp_lst;
2090 				s_listeners ~= li;
2091 				linfo = li;
2092 			}
2093 		}
2094 
2095 		if (linfo) vid ~= linfo.addVirtualHost(settings, request_handler);
2096 	}
2097 
2098 	enforce(vid.length > 0, "Failed to listen for incoming HTTP connections on any of the supplied interfaces.");
2099 
2100 	return HTTPListener(vid);
2101 }
2102 
2103 private alias TLSStreamType = ReturnType!(createTLSStreamFL!(InterfaceProxy!Stream));
2104 
2105 
2106 private bool handleRequest(Allocator)(InterfaceProxy!Stream http_stream, TCPConnection tcp_connection, HTTPServerContext listen_info, ref HTTPServerSettings settings, ref bool keep_alive, scope Allocator request_allocator)
2107 @safe {
2108 	import std.algorithm.searching : canFind;
2109 
2110 	SysTime reqtime = Clock.currTime(UTC());
2111 
2112 	// some instances that live only while the request is running
2113 	FreeListRef!HTTPServerRequest req = FreeListRef!HTTPServerRequest(reqtime, listen_info.bindPort);
2114 	FreeListRef!TimeoutHTTPInputStream timeout_http_input_stream;
2115 	FreeListRef!LimitedHTTPInputStream limited_http_input_stream;
2116 	FreeListRef!ChunkedInputStream chunked_input_stream;
2117 
2118 	// store the IP address
2119 	req.clientAddress = tcp_connection.remoteAddress;
2120 
2121 	if (!listen_info.hasVirtualHosts) {
2122 		logWarn("Didn't find a HTTP listening context for incoming connection. Dropping.");
2123 		keep_alive = false;
2124 		return false;
2125 	}
2126 
2127 	// Default to the first virtual host for this listener
2128 	HTTPServerContext.VirtualHost context = listen_info.m_virtualHosts[0];
2129 	HTTPServerRequestDelegate request_task = context.requestHandler;
2130 	settings = context.settings;
2131 
2132 	// temporarily set to the default settings, the virtual host specific settings will be set further down
2133 	req.m_settings = settings;
2134 
2135 	// Create the response object
2136 	InterfaceProxy!ConnectionStream cproxy = tcp_connection;
2137 	auto res = FreeListRef!HTTPServerResponse(http_stream, cproxy, settings, request_allocator/*.Scoped_payload*/);
2138 	req.tls = res.m_tls = listen_info.tlsContext !is null;
2139 	if (req.tls) {
2140 		version (HaveNoTLS) assert(false);
2141 		else {
2142 			static if (is(InterfaceProxy!ConnectionStream == ConnectionStream))
2143 				req.clientCertificate = (cast(TLSStream)http_stream).peerCertificate;
2144 			else
2145 				req.clientCertificate = http_stream.extract!TLSStreamType.peerCertificate;
2146 		}
2147 	}
2148 
2149 	// Error page handler
2150 	void errorOut(int code, string msg, string debug_msg, Throwable ex)
2151 	@safe {
2152 		assert(!res.headerWritten);
2153 
2154 		res.statusCode = code;
2155 		if (settings && settings.errorPageHandler) {
2156 			/*scope*/ auto err = new HTTPServerErrorInfo;
2157 			err.code = code;
2158 			err.message = msg;
2159 			err.debugMessage = debug_msg;
2160 			err.exception = ex;
2161 			settings.errorPageHandler_(req, res, err);
2162 		} else {
2163 			if (debug_msg.length)
2164 				res.writeBody(format("%s - %s\n\n%s\n\nInternal error information:\n%s", code, httpStatusText(code), msg, debug_msg));
2165 			else res.writeBody(format("%s - %s\n\n%s", code, httpStatusText(code), msg));
2166 		}
2167 		assert(res.headerWritten);
2168 	}
2169 
2170 	bool parsed = false;
2171 	/*bool*/ keep_alive = false;
2172 
2173 	// parse the request
2174 	try {
2175 		logTrace("reading request..");
2176 
2177 		// limit the total request time
2178 		InterfaceProxy!InputStream reqReader = http_stream;
2179 		if (settings.maxRequestTime > dur!"seconds"(0) && settings.maxRequestTime != Duration.max) {
2180 			timeout_http_input_stream = FreeListRef!TimeoutHTTPInputStream(reqReader, settings.maxRequestTime, reqtime);
2181 			reqReader = timeout_http_input_stream;
2182 		}
2183 
2184 		// basic request parsing
2185 		parseRequestHeader(req, reqReader, request_allocator, settings.maxRequestHeaderSize, settings.maxRequestHeaderLineSize);
2186 		logTrace("Got request header.");
2187 
2188 		// find the matching virtual host
2189 		string reqhost;
2190 		ushort reqport = 0;
2191 		{
2192 			string s = req.host;
2193 			enforceHTTP(s.length > 0 || req.httpVersion <= HTTPVersion.HTTP_1_0, HTTPStatus.badRequest, "Missing Host header.");
2194 			if (s.startsWith('[')) { // IPv6 address
2195 				auto idx = s.indexOf(']');
2196 				enforce(idx > 0, "Missing closing ']' for IPv6 address.");
2197 				reqhost = s[1 .. idx];
2198 				s = s[idx+1 .. $];
2199 			} else if (s.length) { // host name or IPv4 address
2200 				auto idx = s.indexOf(':');
2201 				if (idx < 0) idx = s.length;
2202 				enforceHTTP(idx > 0, HTTPStatus.badRequest, "Missing Host header.");
2203 				reqhost = s[0 .. idx];
2204 				s = s[idx .. $];
2205 			}
2206 			if (s.startsWith(':')) reqport = s[1 .. $].to!ushort;
2207 		}
2208 
2209 		foreach (ctx; listen_info.m_virtualHosts)
2210 			if (icmp2(ctx.settings.hostName, reqhost) == 0 &&
2211 				(!reqport || reqport == ctx.settings.port))
2212 			{
2213 				context = ctx;
2214 				settings = ctx.settings;
2215 				request_task = ctx.requestHandler;
2216 				break;
2217 			}
2218 		req.m_settings = settings;
2219 		res.m_settings = settings;
2220 
2221 		// setup compressed output
2222 		if (settings.useCompressionIfPossible) {
2223 			if (auto pae = "Accept-Encoding" in req.headers) {
2224 				if (canFind(*pae, "gzip")) {
2225 					res.headers["Content-Encoding"] = "gzip";
2226 				} else if (canFind(*pae, "deflate")) {
2227 					res.headers["Content-Encoding"] = "deflate";
2228 				}
2229 			}
2230 		}
2231 
2232 		// limit request size
2233 		if (auto pcl = "Content-Length" in req.headers) {
2234 			string v = *pcl;
2235 			auto contentLength = parse!ulong(v); // DMDBUG: to! thinks there is a H in the string
2236 			enforceBadRequest(v.length == 0, "Invalid content-length");
2237 			enforceBadRequest(settings.maxRequestSize <= 0 || contentLength <= settings.maxRequestSize, "Request size too big");
2238 			limited_http_input_stream = FreeListRef!LimitedHTTPInputStream(reqReader, contentLength);
2239 		} else if (auto pt = "Transfer-Encoding" in req.headers) {
2240 			enforceBadRequest(icmp(*pt, "chunked") == 0);
2241 			chunked_input_stream = createChunkedInputStreamFL(reqReader);
2242 			InterfaceProxy!InputStream ciproxy = chunked_input_stream;
2243 			limited_http_input_stream = FreeListRef!LimitedHTTPInputStream(ciproxy, settings.maxRequestSize, true);
2244 		} else {
2245 			limited_http_input_stream = FreeListRef!LimitedHTTPInputStream(reqReader, 0);
2246 		}
2247 		req.bodyReader = limited_http_input_stream;
2248 
2249 		// handle Expect header
2250 		if (auto pv = "Expect" in req.headers) {
2251 			if (icmp2(*pv, "100-continue") == 0) {
2252 				logTrace("sending 100 continue");
2253 				http_stream.write("HTTP/1.1 100 Continue\r\n\r\n");
2254 			}
2255 		}
2256 
2257 		// eagerly parse the URL as its lightweight and defacto @nogc
2258 		auto url = URL.parse(req.requestURI);
2259 		req.queryString = url.queryString;
2260 		req.username = url.username;
2261 		req.password = url.password;
2262 		req.requestPath = url.path;
2263 
2264 		// lookup the session
2265 		if (settings.sessionStore) {
2266 			// use the first cookie that contains a valid session ID in case
2267 			// of multiple matching session cookies
2268 			foreach (val; req.cookies.getAll(settings.sessionIdCookie)) {
2269 				req.session = settings.sessionStore.open(val);
2270 				res.m_session = req.session;
2271 				if (req.session) break;
2272 			}
2273 		}
2274 
2275 		// write default headers
2276 		if (req.method == HTTPMethod.HEAD) res.m_isHeadResponse = true;
2277 		if (settings.serverString.length)
2278 			res.headers["Server"] = settings.serverString;
2279 		res.headers["Date"] = formatRFC822DateAlloc(reqtime);
2280 		if (req.persistent)
2281 			res.headers["Keep-Alive"] = formatAlloc(
2282 				request_allocator, "timeout=%d", settings.keepAliveTimeout.total!"seconds"());
2283 
2284 		// finished parsing the request
2285 		parsed = true;
2286 		logTrace("persist: %s", req.persistent);
2287 		keep_alive = req.persistent;
2288 
2289 		if (context.settings.rejectConnectionPredicate !is null)
2290 		{
2291 			import std.socket : Address, parseAddress;
2292 			
2293 			auto forward = req.headers.get("X-Forwarded-For", null);
2294 			if (forward !is null)
2295 			{
2296 				try {
2297 					auto ix = forward.indexOf(',');
2298 					if (ix != -1)
2299 						forward = forward[0 .. ix];
2300 					if (context.settings.rejectConnectionPredicate(NetworkAddress(parseAddress(forward))))
2301 						errorOut(HTTPStatus.forbidden, 
2302 							httpStatusText(HTTPStatus.forbidden), null, null);
2303 				} catch (Exception e)
2304 					logTrace("Malformed X-Forwarded-For header: %s", e.msg);
2305 			}
2306 		}
2307 
2308 		// handle the request
2309 		logTrace("handle request (body %d)", req.bodyReader.leastSize);
2310 		res.httpVersion = req.httpVersion;
2311 		request_task(req, res);
2312 
2313 		// if no one has written anything, return 404
2314 		if (!res.headerWritten) {
2315 			string dbg_msg;
2316 			logDiagnostic("No response written for %s", req.requestURI);
2317 			if (settings.options & HTTPServerOption.errorStackTraces)
2318 				dbg_msg = format("No routes match path '%s'", req.requestURI);
2319 			errorOut(HTTPStatus.notFound, httpStatusText(HTTPStatus.notFound), dbg_msg, null);
2320 		}
2321 	} catch (HTTPStatusException err) {
2322 		if (!res.headerWritten) errorOut(err.status, err.msg, err.debugMessage, err);
2323 		else logDiagnostic("HTTPStatusException while writing the response: %s", err.msg);
2324 		debug logDebug("Exception while handling request %s %s: %s", req.method,
2325 					   req.requestURI, () @trusted { return err.toString().sanitize; } ());
2326 		if (!parsed || res.headerWritten || justifiesConnectionClose(err.status))
2327 			keep_alive = false;
2328 	} catch (UncaughtException e) {
2329 		auto status = parsed ? HTTPStatus.internalServerError : HTTPStatus.badRequest;
2330 		string dbg_msg;
2331 		if (settings.options & HTTPServerOption.errorStackTraces)
2332 			dbg_msg = () @trusted { return e.toString().sanitize; } ();
2333 		if (!res.headerWritten && tcp_connection.connected)
2334 			errorOut(status, httpStatusText(status), dbg_msg, e);
2335 		else logDiagnostic("Error while writing the response: %s", e.msg);
2336 		debug logDebug("Exception while handling request %s %s: %s", req.method,
2337 					   req.requestURI, () @trusted { return e.toString().sanitize(); } ());
2338 		if (!parsed || res.headerWritten || !cast(Exception)e) keep_alive = false;
2339 	}
2340 
2341 	if (tcp_connection.connected && keep_alive) {
2342 		if (req.bodyReader && !req.bodyReader.empty) {
2343 			req.bodyReader.pipe(nullSink);
2344 			logTrace("dropped body");
2345 		}
2346 	}
2347 
2348 	// finalize (e.g. for chunked encoding)
2349 	res.finalize();
2350 
2351 	if (res.m_requiresConnectionClose)
2352 		keep_alive = false;
2353 
2354 	// NOTE: req.m_files may or may not be parsed/filled with actual data, as
2355 	//       it is lazily initialized when calling the .files or .form
2356 	//       properties
2357 	foreach (k, v ; req.m_files.byKeyValue) {
2358 		if (existsFile(v.tempPath)) {
2359 			removeFile(v.tempPath);
2360 			logDebug("Deleted upload tempfile %s", v.tempPath.toString());
2361 		}
2362 	}
2363 
2364 	if (!req.noLog) {
2365 		// log the request to access log
2366 		foreach (log; context.loggers)
2367 			log.log(req, res);
2368 	}
2369 
2370 	//logTrace("return %s (used pool memory: %s/%s)", keep_alive, request_allocator.allocatedSize, request_allocator.totalSize);
2371 	logTrace("return %s", keep_alive);
2372 	return keep_alive != false;
2373 }
2374 
2375 
2376 private void parseRequestHeader(InputStream, Allocator)(HTTPServerRequest req, InputStream http_stream, Allocator alloc, ulong max_header_size, size_t max_header_line_size)
2377 	if (isInputStream!InputStream)
2378 {
2379 	auto stream = FreeListRef!LimitedHTTPInputStream(http_stream, max_header_size);
2380 
2381 	logTrace("HTTP server reading status line");
2382 	auto reqln = () @trusted { return cast(string)stream.readLine(max_header_line_size, "\r\n", alloc); }();
2383 
2384 	logTrace("--------------------");
2385 	logTrace("HTTP server request:");
2386 	logTrace("--------------------");
2387 	logTrace("%s", reqln);
2388 
2389 	//Method
2390 	auto pos = reqln.indexOf(' ');
2391 	enforceBadRequest(pos >= 0, "invalid request method");
2392 
2393 	req.method = httpMethodFromString(reqln[0 .. pos]);
2394 	reqln = reqln[pos+1 .. $];
2395 	//Path
2396 	pos = reqln.indexOf(' ');
2397 	enforceBadRequest(pos >= 0, "invalid request path");
2398 
2399 	req.requestURI = reqln[0 .. pos];
2400 	reqln = reqln[pos+1 .. $];
2401 
2402 	req.httpVersion = parseHTTPVersion(reqln);
2403 
2404 	//headers
2405 	parseRFC5322Header(stream, req.headers, max_header_line_size, alloc, false);
2406 
2407 	foreach (k, v; req.headers.byKeyValue)
2408 		logTrace("%s: %s", k, v);
2409 	logTrace("--------------------");
2410 }
2411 
2412 private void parseCookies(string str, ref CookieValueMap cookies)
2413 @safe {
2414 	import std.encoding : sanitize;
2415 	import std.array : split;
2416 	import std.string : strip;
2417 	import std.algorithm.iteration : map, filter, each;
2418 	import vibe.http.common : Cookie;
2419 	() @trusted { return str.sanitize; } ()
2420 		.split(";")
2421 		.map!(kv => kv.strip.split("="))
2422 		.filter!(kv => kv.length == 2) //ignore illegal cookies
2423 		.each!(kv => cookies.add(kv[0], kv[1], Cookie.Encoding.raw) );
2424 }
2425 
2426 unittest
2427 {
2428   auto cvm = CookieValueMap();
2429   parseCookies("foo=bar;; baz=zinga; öö=üü   ;   møøse=was=sacked;    onlyval1; =onlyval2; onlykey=", cvm);
2430   assert(cvm["foo"] == "bar");
2431   assert(cvm["baz"] == "zinga");
2432   assert(cvm["öö"] == "üü");
2433   assert( "møøse" ! in cvm); //illegal cookie gets ignored
2434   assert( "onlyval1" ! in cvm); //illegal cookie gets ignored
2435   assert(cvm["onlykey"] == "");
2436   assert(cvm[""] == "onlyval2");
2437   assert(cvm.length() == 5);
2438   cvm = CookieValueMap();
2439   parseCookies("", cvm);
2440   assert(cvm.length() == 0);
2441   cvm = CookieValueMap();
2442   parseCookies(";;=", cvm);
2443   assert(cvm.length() == 1);
2444   assert(cvm[""] == "");
2445 }
2446 
2447 shared static this()
2448 {
2449 	version (VibeNoDefaultArgs) {}
2450 	else {
2451 		string disthost = s_distHost;
2452 		ushort distport = s_distPort;
2453 		import vibe.core.args : readOption;
2454 		readOption("disthost|d", () @trusted { return &disthost; } (), "Sets the name of a vibedist server to use for load balancing.");
2455 		readOption("distport", () @trusted { return &distport; } (), "Sets the port used for load balancing.");
2456 		setVibeDistHost(disthost, distport);
2457 	}
2458 }
2459 
2460 private struct CacheTime
2461 {
2462 	string cachedDate;
2463 	SysTime nextUpdate;
2464 
2465 	this(SysTime nextUpdate) @safe @nogc pure nothrow
2466 	{
2467 		this.nextUpdate = nextUpdate;
2468 	}
2469 
2470 	void update(SysTime time) @safe
2471 	{
2472 		this.nextUpdate = time + 1.seconds;
2473 		this.nextUpdate.fracSecs = nsecs(0);
2474 	}
2475 }
2476 
2477 private string formatRFC822DateAlloc(SysTime time)
2478 @safe {
2479 	static LAST = CacheTime(SysTime.min());
2480 
2481 	if (time > LAST.nextUpdate) {
2482 		auto app = new FixedAppender!(string, 32);
2483 		writeRFC822DateTimeString(app, time);
2484 		LAST.update(time);
2485 		LAST.cachedDate = () @trusted { return app.data; } ();
2486 		return () @trusted { return app.data; } ();
2487 	} else
2488 		return LAST.cachedDate;
2489 }
2490 
2491 version (VibeDebugCatchAll) private alias UncaughtException = Throwable;
2492 else private alias UncaughtException = Exception;