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.core.file;
15 import vibe.core.log;
16 import vibe.data.json;
17 import vibe.http.dist;
18 import vibe.http.log;
19 import vibe.inet.message;
20 import vibe.inet.url;
21 import vibe.inet.webform;
22 import vibe.internal.interfaceproxy : InterfaceProxy;
23 import vibe.stream.counting;
24 import vibe.stream.operations;
25 import vibe.stream.tls;
26 import vibe.stream.wrapper : ConnectionProxyStream, createConnectionProxyStream, createConnectionProxyStreamFL;
27 import vibe.stream.zlib;
28 import vibe.textfilter.urlencode;
29 import vibe.utils.array;
30 import vibe.internal.allocator;
31 import vibe.internal.freelistref;
32 import vibe.utils.string;
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 			import vibe.internal.utilallocator: RegionListAllocator;
247 
248 			version (VibeManualMemoryManagement)
249 				scope request_allocator = new RegionListAllocator!(shared(Mallocator), false)(1024, Mallocator.instance);
250 			else
251 				scope request_allocator = new RegionListAllocator!(shared(GCAllocator), true)(1024, GCAllocator.instance);
252 
253 			handleRequest(http_stream, connection, context, settings, keep_alive, request_allocator);
254 		} ();
255 		if (!keep_alive) { logTrace("No keep-alive - disconnecting client."); break; }
256 
257 		logTrace("Waiting for next request...");
258 		// wait for another possible request on a keep-alive connection
259 		if (!connection.waitForData(settings.keepAliveTimeout)) {
260 			if (!connection.connected) logTrace("Client disconnected.");
261 			else logDebug("Keep-alive connection timed out!");
262 			break;
263 		}
264 	}
265 
266 	logTrace("Done handling connection.");
267 }
268 
269 
270 /**
271 	Provides a HTTP request handler that responds with a static Diet template.
272 */
273 @property HTTPServerRequestDelegateS staticTemplate(string template_file)()
274 {
275 	return (scope HTTPServerRequest req, scope HTTPServerResponse res){
276 		res.render!(template_file, req);
277 	};
278 }
279 
280 /**
281 	Provides a HTTP request handler that responds with a static redirection to the specified URL.
282 
283 	Params:
284 		url = The URL to redirect to
285 		status = Redirection status to use $(LPAREN)by default this is $(D HTTPStatus.found)$(RPAREN).
286 
287 	Returns:
288 		Returns a $(D HTTPServerRequestDelegate) that performs the redirect
289 */
290 HTTPServerRequestDelegate staticRedirect(string url, HTTPStatus status = HTTPStatus.found)
291 @safe {
292 	return (HTTPServerRequest req, HTTPServerResponse res){
293 		res.redirect(url, status);
294 	};
295 }
296 /// ditto
297 HTTPServerRequestDelegate staticRedirect(URL url, HTTPStatus status = HTTPStatus.found)
298 @safe {
299 	return (HTTPServerRequest req, HTTPServerResponse res){
300 		res.redirect(url, status);
301 	};
302 }
303 
304 ///
305 unittest {
306 	import vibe.http.router;
307 
308 	void test()
309 	{
310 		auto router = new URLRouter;
311 		router.get("/old_url", staticRedirect("http://example.org/new_url", HTTPStatus.movedPermanently));
312 
313 		listenHTTP(new HTTPServerSettings, router);
314 	}
315 }
316 
317 
318 /**
319 	Sets a VibeDist host to register with.
320 */
321 void setVibeDistHost(string host, ushort port)
322 @safe {
323 	s_distHost = host;
324 	s_distPort = port;
325 }
326 
327 
328 /**
329 	Renders the given Diet template and makes all ALIASES available to the template.
330 
331 	You can call this function as a pseudo-member of `HTTPServerResponse` using
332 	D's uniform function call syntax.
333 
334 	See_also: `diet.html.compileHTMLDietFile`
335 
336 	Examples:
337 		---
338 		string title = "Hello, World!";
339 		int pageNumber = 1;
340 		res.render!("mytemplate.dt", title, pageNumber);
341 		---
342 */
343 @property void render(string template_file, ALIASES...)(HTTPServerResponse res)
344 {
345 	res.contentType = "text/html; charset=UTF-8";
346 	version (VibeUseOldDiet)
347 		pragma(msg, "VibeUseOldDiet is not supported anymore. Please undefine in the package recipe.");
348 	import vibe.stream.wrapper : streamOutputRange;
349 	import diet.html : compileHTMLDietFile;
350 	auto output = streamOutputRange!1024(res.bodyWriter);
351 	compileHTMLDietFile!(template_file, ALIASES, DefaultDietFilters)(output);
352 }
353 
354 
355 /**
356 	Provides the default `css`, `javascript`, `markdown` and `htmlescape` filters
357  */
358 @dietTraits
359 struct DefaultDietFilters {
360 	import diet.html : HTMLOutputStyle;
361 	import diet.traits : SafeFilterCallback;
362 	import std.string : splitLines;
363 
364 	version (VibeOutputCompactHTML) enum HTMLOutputStyle htmlOutputStyle = HTMLOutputStyle.compact;
365 	else enum HTMLOutputStyle htmlOutputStyle = HTMLOutputStyle.pretty;
366 
367 	static string filterCss(I)(I text, size_t indent = 0)
368 	{
369 		auto lines = splitLines(text);
370 
371 		string indent_string = "\n";
372 		while (indent-- > 0) indent_string ~= '\t';
373 
374 		string ret = indent_string~"<style><!--";
375 		indent_string = indent_string ~ '\t';
376 		foreach (ln; lines) ret ~= indent_string ~ ln;
377 		indent_string = indent_string[0 .. $-1];
378 		ret ~= indent_string ~ "--></style>";
379 
380 		return ret;
381 	}
382 
383 
384 	static string filterJavascript(I)(I text, size_t indent = 0)
385 	{
386 		auto lines = splitLines(text);
387 
388 		string indent_string = "\n";
389 		while (indent-- > 0) indent_string ~= '\t';
390 
391 		string ret = indent_string~"<script>";
392 		ret ~= indent_string~'\t' ~ "//<![CDATA[";
393 		foreach (ln; lines) ret ~= indent_string ~ '\t' ~ ln;
394 		ret ~= indent_string ~ '\t' ~ "//]]>" ~ indent_string ~ "</script>";
395 
396 		return ret;
397 	}
398 
399 	static string filterMarkdown(I)(I text)
400 	{
401 		import vibe.textfilter.markdown : markdown = filterMarkdown;
402 		// TODO: indent
403 		return markdown(text);
404 	}
405 
406 	static string filterHtmlescape(I)(I text)
407 	{
408 		import vibe.textfilter.html : htmlEscape;
409 		// TODO: indent
410 		return htmlEscape(text);
411 	}
412 
413 	static this()
414 	{
415 		filters["css"] = (in input, scope output) { output(filterCss(input)); };
416 		filters["javascript"] = (in input, scope output) { output(filterJavascript(input)); };
417 		filters["markdown"] = (in input, scope output) { output(filterMarkdown(() @trusted { return cast(string)input; } ())); };
418 		filters["htmlescape"] = (in input, scope output) { output(filterHtmlescape(input)); };
419 	}
420 
421 	static SafeFilterCallback[string] filters;
422 }
423 
424 
425 unittest {
426 	static string compile(string diet)() {
427 		import std.array : appender;
428 		import std.string : strip;
429 		import diet.html : compileHTMLDietString;
430 		auto dst = appender!string;
431 		dst.compileHTMLDietString!(diet, DefaultDietFilters);
432 		return strip(cast(string)(dst.data));
433 	}
434 
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!":css !{\".test\"}" == "<style><!--\n\t.test\n--></style>");
440 	assert(compile!":javascript !{\"test();\"}" == "<script>\n\t//<![CDATA[\n\ttest();\n\t//]]>\n</script>");
441 	assert(compile!":markdown !{\"**test**\"}" == "<p><strong>test</strong>\n</p>");
442 	assert(compile!":htmlescape !{\"<test>\"}" == "&lt;test&gt;");
443 	assert(compile!":javascript\n\ttest();" == "<script>\n\t//<![CDATA[\n\ttest();\n\t//]]>\n</script>");
444 }
445 
446 
447 /**
448 	Creates a HTTPServerRequest suitable for writing unit tests.
449 */
450 HTTPServerRequest createTestHTTPServerRequest(URL url, HTTPMethod method = HTTPMethod.GET, InputStream data = null)
451 @safe {
452 	InetHeaderMap headers;
453 	return createTestHTTPServerRequest(url, method, headers, data);
454 }
455 /// ditto
456 HTTPServerRequest createTestHTTPServerRequest(URL url, HTTPMethod method, InetHeaderMap headers, InputStream data = null)
457 @safe {
458 	auto tls = url.schema == "https";
459 	auto ret = new HTTPServerRequest(Clock.currTime(UTC()), url.port ? url.port : tls ? 443 : 80);
460 	ret.requestPath = url.path;
461 	ret.queryString = url.queryString;
462 	ret.username = url.username;
463 	ret.password = url.password;
464 	ret.requestURI = url.localURI;
465 	ret.method = method;
466 	ret.tls = tls;
467 	ret.headers = headers;
468 	ret.bodyReader = data;
469 	return ret;
470 }
471 
472 /**
473 	Creates a HTTPServerResponse suitable for writing unit tests.
474 
475 	Params:
476 		data_sink = Optional output stream that captures the data that gets
477 			written to the response
478 		session_store = Optional session store to use when sessions are involved
479 		data_mode = If set to `TestHTTPResponseMode.bodyOnly`, only the body
480 			contents get written to `data_sink`. Otherwise the raw response
481 			including the HTTP header is written.
482 */
483 HTTPServerResponse createTestHTTPServerResponse(OutputStream data_sink = null,
484 	SessionStore session_store = null,
485 	TestHTTPResponseMode data_mode = TestHTTPResponseMode.plain)
486 @safe {
487 	import vibe.stream.wrapper;
488 
489 	HTTPServerSettings settings;
490 	if (session_store) {
491 		settings = new HTTPServerSettings;
492 		settings.sessionStore = session_store;
493 	}
494 
495 	InterfaceProxy!Stream outstr;
496 	if (data_sink && data_mode == TestHTTPResponseMode.plain)
497 		outstr = createProxyStream(Stream.init, data_sink);
498 	else outstr = createProxyStream(Stream.init, nullSink);
499 
500 	auto ret = new HTTPServerResponse(outstr, InterfaceProxy!ConnectionStream.init,
501 		settings, () @trusted { return vibeThreadAllocator(); } ());
502 	if (data_sink && data_mode == TestHTTPResponseMode.bodyOnly) ret.m_bodyWriter = data_sink;
503 	return ret;
504 }
505 
506 
507 /**************************************************************************************************/
508 /* Public types                                                                                   */
509 /**************************************************************************************************/
510 
511 /// Delegate based request handler
512 alias HTTPServerRequestDelegate = void delegate(HTTPServerRequest req, HTTPServerResponse res) @safe;
513 /// Static function based request handler
514 alias HTTPServerRequestFunction = void function(HTTPServerRequest req, HTTPServerResponse res) @safe;
515 /// Interface for class based request handlers
516 interface HTTPServerRequestHandler {
517 	/// Handles incoming HTTP requests
518 	void handleRequest(HTTPServerRequest req, HTTPServerResponse res) @safe ;
519 }
520 
521 /// Delegate based request handler with scoped parameters
522 alias HTTPServerRequestDelegateS = void delegate(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe;
523 /// Static function based request handler with scoped parameters
524 alias HTTPServerRequestFunctionS  = void function(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe;
525 /// Interface for class based request handlers with scoped parameters
526 interface HTTPServerRequestHandlerS {
527 	/// Handles incoming HTTP requests
528 	void handleRequest(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe;
529 }
530 
531 unittest {
532 	static assert(is(HTTPServerRequestDelegateS : HTTPServerRequestDelegate));
533 	static assert(is(HTTPServerRequestFunctionS : HTTPServerRequestFunction));
534 }
535 
536 /// Aggregates all information about an HTTP error status.
537 final class HTTPServerErrorInfo {
538 	/// The HTTP status code
539 	int code;
540 	/// The error message
541 	string message;
542 	/// Extended error message with debug information such as a stack trace
543 	string debugMessage;
544 	/// The error exception, if any
545 	Throwable exception;
546 }
547 
548 /// Delegate type used for user defined error page generator callbacks.
549 alias HTTPServerErrorPageHandler = void delegate(HTTPServerRequest req, HTTPServerResponse res, HTTPServerErrorInfo error) @safe;
550 
551 
552 enum TestHTTPResponseMode {
553 	plain,
554 	bodyOnly
555 }
556 
557 
558 /**
559 	Specifies optional features of the HTTP server.
560 
561 	Disabling unneeded features can speed up the server or reduce its memory usage.
562 
563 	Note that the options `parseFormBody`, `parseJsonBody` and `parseMultiPartBody`
564 	will also drain the `HTTPServerRequest.bodyReader` stream whenever a request
565 	body with form or JSON data is encountered.
566 */
567 enum HTTPServerOption {
568 	none                      = 0,
569 	/** Enables stack traces (`HTTPServerErrorInfo.debugMessage`).
570 
571 		Note that generating the stack traces are generally a costly
572 		operation that should usually be avoided in production
573 		environments. It can also reveal internal information about
574 		the application, such as function addresses, which can
575 		help an attacker to abuse possible security holes.
576 	*/
577 	errorStackTraces          = 1<<7,
578 	/// Enable port reuse in `listenTCP()`
579 	reusePort                 = 1<<8,
580 	/// Enable address reuse in `listenTCP()`
581 	reuseAddress              = 1<<10,
582 	/** The default set of options.
583 
584 		Includes all parsing options, as well as the `errorStackTraces`
585 		option if the code is compiled in debug mode.
586 	*/
587 	defaults                  = () { auto ret = reuseAddress; debug ret |= errorStackTraces; return ret; } (),
588 }
589 
590 
591 /**
592 	Contains all settings for configuring a basic HTTP server.
593 
594 	The defaults are sufficient for most normal uses.
595 */
596 final class HTTPServerSettings {
597 	/** The port on which the HTTP server is listening.
598 
599 		The default value is 80. If you are running a TLS enabled server you may want to set this
600 		to 443 instead.
601 
602 		Using a value of `0` instructs the server to use any available port on
603 		the given `bindAddresses` the actual addresses and ports can then be
604 		queried with `TCPListener.bindAddresses`.
605 	*/
606 	ushort port = 80;
607 
608 	/** The interfaces on which the HTTP server is listening.
609 
610 		By default, the server will listen on all IPv4 and IPv6 interfaces.
611 	*/
612 	string[] bindAddresses = ["::", "0.0.0.0"];
613 
614 	/** Determines the server host name.
615 
616 		If multiple servers are listening on the same port, the host name will determine which one
617 		gets a request.
618 	*/
619 	string hostName;
620 
621 	/** Provides a way to reject incoming connections as early as possible.
622 
623 		Allows to ban and unban network addresses and reduce the impact of DOS
624 		attacks.
625 
626 		If the callback returns `true` for a specific `NetworkAddress`,
627 		then all incoming requests from that address will be rejected.
628 	*/
629 	RejectConnectionPredicate rejectConnectionPredicate;
630 
631 	/** Configures optional features of the HTTP server
632 
633 		Disabling unneeded features can improve performance or reduce the server
634 		load in case of invalid or unwanted requests (DoS). By default,
635 		HTTPServerOption.defaults is used.
636 	*/
637 	HTTPServerOption options = HTTPServerOption.defaults;
638 
639 	/** Time of a request after which the connection is closed with an error; not supported yet
640 
641 		The default limit of 0 means that the request time is not limited.
642 	*/
643 	Duration maxRequestTime = 0.seconds;
644 
645 	/** Maximum time between two request on a keep-alive connection
646 
647 		The default value is 10 seconds.
648 	*/
649 	Duration keepAliveTimeout = 10.seconds;
650 
651 	/// Maximum number of transferred bytes per request after which the connection is closed with
652 	/// an error
653 	ulong maxRequestSize = 2097152;
654 
655 
656 	///	Maximum number of transferred bytes for the request header. This includes the request line
657 	/// the url and all headers.
658 	ulong maxRequestHeaderSize = 8192;
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.utils.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, MaxHTTPHeaderLineLength);
1174 	}
1175 }
1176 
1177 
1178 /**
1179 	Represents a HTTP response as sent from the server side.
1180 */
1181 final class HTTPServerResponse : HTTPResponse {
1182 	private {
1183 		InterfaceProxy!Stream m_conn;
1184 		InterfaceProxy!ConnectionStream m_rawConnection;
1185 		InterfaceProxy!OutputStream m_bodyWriter;
1186 		IAllocator m_requestAlloc;
1187 		FreeListRef!ChunkedOutputStream m_chunkedBodyWriter;
1188 		FreeListRef!CountingOutputStream m_countingWriter;
1189 		FreeListRef!ZlibOutputStream m_zlibOutputStream;
1190 		HTTPServerSettings m_settings;
1191 		Session m_session;
1192 		bool m_headerWritten = false;
1193 		bool m_isHeadResponse = false;
1194 		bool m_tls;
1195 		bool m_requiresConnectionClose;
1196 		SysTime m_timeFinalized;
1197 	}
1198 
1199 	static if (!is(Stream == InterfaceProxy!Stream)) {
1200 		this(Stream conn, ConnectionStream raw_connection, HTTPServerSettings settings, IAllocator req_alloc)
1201 		@safe scope {
1202 			this(InterfaceProxy!Stream(conn), InterfaceProxy!ConnectionStream(raw_connection), settings, req_alloc);
1203 		}
1204 	}
1205 
1206 	this(InterfaceProxy!Stream conn, InterfaceProxy!ConnectionStream raw_connection, HTTPServerSettings settings, IAllocator req_alloc)
1207 	@safe scope {
1208 		m_conn = conn;
1209 		m_rawConnection = raw_connection;
1210 		m_countingWriter = createCountingOutputStreamFL(conn);
1211 		m_settings = settings;
1212 		m_requestAlloc = req_alloc;
1213 	}
1214 
1215 	/** Sends a redirect request to the client.
1216 
1217 		Params:
1218 			url = The URL to redirect to
1219 			status = The HTTP redirect status (3xx) to send - by default this is $(D HTTPStatus.found)
1220 	*/
1221 	void redirect(string url, int status = HTTPStatus.found)
1222 	@safe scope {
1223 		// Disallow any characters that may influence the header parsing
1224 		enforce(!url.representation.canFind!(ch => ch < 0x20),
1225 			"Control character in redirection URL.");
1226 
1227 		statusCode = status;
1228 		headers["Location"] = url;
1229 		writeBody("redirecting...");
1230 	}
1231 	/// ditto
1232 	void redirect(URL url, int status = HTTPStatus.found)
1233 	@safe scope {
1234 		redirect(url.toString(), status);
1235 	}
1236 
1237 	///
1238 	@safe unittest {
1239 		import vibe.http.router;
1240 
1241 		void request_handler(HTTPServerRequest req, HTTPServerResponse res)
1242 		{
1243 			res.redirect("http://example.org/some_other_url");
1244 		}
1245 
1246 		void test()
1247 		{
1248 			auto router = new URLRouter;
1249 			router.get("/old_url", &request_handler);
1250 
1251 			listenHTTP(new HTTPServerSettings, router);
1252 		}
1253 	}
1254 
1255 scope:
1256 
1257 	/** Returns the time at which the request was finalized.
1258 
1259 		Note that this field will only be set after `finalize` has been called.
1260 	*/
1261 	@property SysTime timeFinalized() const @safe { return m_timeFinalized; }
1262 
1263 	/** Determines if the HTTP header has already been written.
1264 	*/
1265 	@property bool headerWritten() const @safe { return m_headerWritten; }
1266 
1267 	/** Determines if the response does not need a body.
1268 	*/
1269 	bool isHeadResponse() const @safe { return m_isHeadResponse; }
1270 
1271 	/** Determines if the response is sent over an encrypted connection.
1272 	*/
1273 	bool tls() const @safe { return m_tls; }
1274 
1275 	/** Writes the entire response body at once.
1276 
1277 		Params:
1278 			data = The data to write as the body contents
1279 			status = Optional response status code to set
1280 			content_type = Optional content type to apply to the response.
1281 				If no content type is given and no "Content-Type" header is
1282 				set in the response, this will default to
1283 				`"application/octet-stream"`.
1284 
1285 		See_Also: `HTTPStatusCode`
1286 	*/
1287 	void writeBody(in ubyte[] data, string content_type = null)
1288 	@safe {
1289 		if (content_type.length) headers["Content-Type"] = content_type;
1290 		else if ("Content-Type" !in headers) headers["Content-Type"] = "application/octet-stream";
1291 		headers["Content-Length"] = formatAlloc(m_requestAlloc, "%d", data.length);
1292 		bodyWriter.write(data);
1293 	}
1294 	/// ditto
1295 	void writeBody(in ubyte[] data, int status, string content_type = null)
1296 	@safe {
1297 		statusCode = status;
1298 		writeBody(data, content_type);
1299 	}
1300 	/// ditto
1301 	void writeBody(scope InputStream data, string content_type = null)
1302 	@safe {
1303 		if (content_type.length) headers["Content-Type"] = content_type;
1304 		else if ("Content-Type" !in headers) headers["Content-Type"] = "application/octet-stream";
1305 		data.pipe(bodyWriter);
1306 	}
1307 
1308 	/** Writes the entire response body as a single string.
1309 
1310 		Params:
1311 			data = The string to write as the body contents
1312 			status = Optional response status code to set
1313 			content_type = Optional content type to apply to the response.
1314 				If no content type is given and no "Content-Type" header is
1315 				set in the response, this will default to
1316 				`"text/plain; charset=UTF-8"`.
1317 
1318 		See_Also: `HTTPStatusCode`
1319 	*/
1320 	/// ditto
1321 	void writeBody(string data, string content_type = null)
1322 	@safe {
1323 		if (!content_type.length && "Content-Type" !in headers)
1324 			content_type = "text/plain; charset=UTF-8";
1325 		writeBody(cast(const(ubyte)[])data, content_type);
1326 	}
1327 	/// ditto
1328 	void writeBody(string data, int status, string content_type = null)
1329 	@safe {
1330 		statusCode = status;
1331 		writeBody(data, content_type);
1332 	}
1333 
1334 	/** Writes the whole response body at once, without doing any further encoding.
1335 
1336 		The caller has to make sure that the appropriate headers are set correctly
1337 		(i.e. Content-Type and Content-Encoding).
1338 
1339 		Note that the version taking a RandomAccessStream may perform additional
1340 		optimizations such as sending a file directly from the disk to the
1341 		network card using a DMA transfer.
1342 
1343 	*/
1344 	void writeRawBody(RandomAccessStream)(RandomAccessStream stream) @safe
1345 		if (isRandomAccessStream!RandomAccessStream)
1346 	{
1347 		assert(!m_headerWritten, "A body was already written!");
1348 		writeHeader();
1349 		if (m_isHeadResponse) return;
1350 
1351 		auto bytes = stream.size - stream.tell();
1352 		stream.pipe(m_conn);
1353 		m_countingWriter.increment(bytes);
1354 	}
1355 	/// ditto
1356 	void writeRawBody(InputStream)(InputStream stream, size_t num_bytes = 0) @safe
1357 		if (isInputStream!InputStream && !isRandomAccessStream!InputStream)
1358 	{
1359 		assert(!m_headerWritten, "A body was already written!");
1360 		writeHeader();
1361 		if (m_isHeadResponse) return;
1362 
1363 		if (num_bytes > 0) {
1364 			stream.pipe(m_conn, num_bytes);
1365 			m_countingWriter.increment(num_bytes);
1366 		} else stream.pipe(m_countingWriter, num_bytes);
1367 	}
1368 	/// ditto
1369 	void writeRawBody(RandomAccessStream)(RandomAccessStream stream, int status) @safe
1370 		if (isRandomAccessStream!RandomAccessStream)
1371 	{
1372 		statusCode = status;
1373 		writeRawBody(stream);
1374 	}
1375 	/// ditto
1376 	void writeRawBody(InputStream)(InputStream stream, int status, size_t num_bytes = 0) @safe
1377 		if (isInputStream!InputStream && !isRandomAccessStream!InputStream)
1378 	{
1379 		statusCode = status;
1380 		writeRawBody(stream, num_bytes);
1381 	}
1382 
1383 
1384 	/// Writes a JSON message with the specified status
1385 	void writeJsonBody(T)(T data, int status, bool allow_chunked = false)
1386 	{
1387 		statusCode = status;
1388 		writeJsonBody(data, allow_chunked);
1389 	}
1390 	/// ditto
1391 	void writeJsonBody(T)(T data, int status, string content_type, bool allow_chunked = false)
1392 	{
1393 		statusCode = status;
1394 		writeJsonBody(data, content_type, allow_chunked);
1395 	}
1396 
1397 	/// ditto
1398 	void writeJsonBody(T)(T data, string content_type, bool allow_chunked = false)
1399 	{
1400 		headers["Content-Type"] = content_type;
1401 		writeJsonBody(data, allow_chunked);
1402 	}
1403 	/// ditto
1404 	void writeJsonBody(T)(T data, bool allow_chunked = false)
1405 	{
1406 		doWriteJsonBody!(T, false)(data, allow_chunked);
1407 	}
1408 	/// ditto
1409 	void writePrettyJsonBody(T)(T data, bool allow_chunked = false)
1410 	{
1411 		doWriteJsonBody!(T, true)(data, allow_chunked);
1412 	}
1413 
1414 	private void doWriteJsonBody(T, bool PRETTY)(T data, bool allow_chunked = false)
1415 	{
1416 		import std.traits;
1417 		import vibe.stream.wrapper;
1418 
1419 		static if (!is(T == Json) && is(typeof(data.data())) && isArray!(typeof(data.data()))) {
1420 			static assert(!is(T == Appender!(typeof(data.data()))), "Passed an Appender!T to writeJsonBody - this is most probably not doing what's indended.");
1421 		}
1422 
1423 		if ("Content-Type" !in headers)
1424 			headers["Content-Type"] = "application/json; charset=UTF-8";
1425 
1426 
1427 		// set an explicit content-length field if chunked encoding is not allowed
1428 		if (!allow_chunked) {
1429 			import vibe.internal.rangeutil;
1430 			long length = 0;
1431 			auto counter = RangeCounter(() @trusted { return &length; } ());
1432 			static if (PRETTY) serializeToPrettyJson(counter, data);
1433 			else serializeToJson(counter, data);
1434 			headers["Content-Length"] = formatAlloc(m_requestAlloc, "%d", length);
1435 		}
1436 
1437 		auto rng = streamOutputRange!1024(bodyWriter);
1438 		static if (PRETTY) serializeToPrettyJson(() @trusted { return &rng; } (), data);
1439 		else serializeToJson(() @trusted { return &rng; } (), data);
1440 	}
1441 
1442 	/**
1443 	 * Writes the response with no body.
1444 	 *
1445 	 * This method should be used in situations where no body is
1446 	 * requested, such as a HEAD request. For an empty body, just use writeBody,
1447 	 * as this method causes problems with some keep-alive connections.
1448 	 */
1449 	void writeVoidBody()
1450 	@safe {
1451 		if (!m_isHeadResponse) {
1452 			assert("Content-Length" !in headers);
1453 			assert("Transfer-Encoding" !in headers);
1454 		}
1455 		assert(!headerWritten);
1456 		writeHeader();
1457 		m_conn.flush();
1458 	}
1459 
1460 	/** A stream for writing the body of the HTTP response.
1461 
1462 		Note that after 'bodyWriter' has been accessed for the first time, it
1463 		is not allowed to change any header or the status code of the response.
1464 	*/
1465 	@property InterfaceProxy!OutputStream bodyWriter()
1466 	@safe scope {
1467 		assert(!!m_conn);
1468 		if (m_bodyWriter) {
1469 			// for test responses, the body writer is pre-set, without headers
1470 			// being written, so we may need to do that here
1471 			if (!m_headerWritten) writeHeader();
1472 
1473 			return m_bodyWriter;
1474 		}
1475 
1476 		assert(!m_headerWritten, "A void body was already written!");
1477 		assert(this.statusCode >= 200, "1xx responses can't have body");
1478 
1479 		if (m_isHeadResponse) {
1480 			// for HEAD requests, we define a NullOutputWriter for convenience
1481 			// - no body will be written. However, the request handler should call writeVoidBody()
1482 			// and skip writing of the body in this case.
1483 			if ("Content-Length" !in headers)
1484 				headers["Transfer-Encoding"] = "chunked";
1485 			writeHeader();
1486 			m_bodyWriter = nullSink;
1487 			return m_bodyWriter;
1488 		}
1489 
1490 		if ("Content-Encoding" in headers && "Content-Length" in headers) {
1491 			// we do not known how large the compressed body will be in advance
1492 			// so remove the content-length and use chunked transfer
1493 			headers.remove("Content-Length");
1494 		}
1495 
1496 		if (auto pcl = "Content-Length" in headers) {
1497 			writeHeader();
1498 			m_countingWriter.writeLimit = (*pcl).to!ulong;
1499 			m_bodyWriter = m_countingWriter;
1500 		} else if (httpVersion <= HTTPVersion.HTTP_1_0) {
1501 			if ("Connection" in headers)
1502 				headers.remove("Connection"); // default to "close"
1503 			writeHeader();
1504 			m_bodyWriter = m_conn;
1505 		} else {
1506 			headers["Transfer-Encoding"] = "chunked";
1507 			writeHeader();
1508 			m_chunkedBodyWriter = createChunkedOutputStreamFL(m_countingWriter);
1509 			m_bodyWriter = m_chunkedBodyWriter;
1510 		}
1511 
1512 		if (auto pce = "Content-Encoding" in headers) {
1513 			if (icmp2(*pce, "gzip") == 0) {
1514 				m_zlibOutputStream = createGzipOutputStreamFL(m_bodyWriter);
1515 				m_bodyWriter = m_zlibOutputStream;
1516 			} else if (icmp2(*pce, "deflate") == 0) {
1517 				m_zlibOutputStream = createDeflateOutputStreamFL(m_bodyWriter);
1518 				m_bodyWriter = m_zlibOutputStream;
1519 			} else {
1520 				logWarn("Unsupported Content-Encoding set in response: '"~*pce~"'");
1521 			}
1522 		}
1523 
1524 		return m_bodyWriter;
1525 	}
1526 
1527 
1528 
1529 	/** Special method sending a SWITCHING_PROTOCOLS response to the client.
1530 
1531 		Notice: For the overload that returns a `ConnectionStream`, it must be
1532 			ensured that the returned instance doesn't outlive the request
1533 			handler callback.
1534 
1535 		Params:
1536 			protocol = The protocol set in the "Upgrade" header of the response.
1537 				Use an empty string to skip setting this field.
1538 	*/
1539 	ConnectionStream switchProtocol(string protocol)
1540 	@safe {
1541 		statusCode = HTTPStatus.switchingProtocols;
1542 		if (protocol.length) headers["Upgrade"] = protocol;
1543 		writeVoidBody();
1544 		m_requiresConnectionClose = true;
1545 		m_headerWritten = true;
1546 		return createConnectionProxyStream(m_conn, m_rawConnection);
1547 	}
1548 	/// ditto
1549 	void switchProtocol(string protocol, scope void delegate(scope ConnectionStream) @safe del)
1550 	@safe {
1551 		statusCode = HTTPStatus.switchingProtocols;
1552 		if (protocol.length) headers["Upgrade"] = protocol;
1553 		writeVoidBody();
1554 		m_requiresConnectionClose = true;
1555 		m_headerWritten = true;
1556 		() @trusted {
1557 			auto conn = createConnectionProxyStreamFL(m_conn, m_rawConnection);
1558 			del(conn);
1559 		} ();
1560 		finalize();
1561 	}
1562 
1563 	/** Special method for handling CONNECT proxy tunnel
1564 
1565 		Notice: For the overload that returns a `ConnectionStream`, it must be
1566 			ensured that the returned instance doesn't outlive the request
1567 			handler callback.
1568 	*/
1569 	ConnectionStream connectProxy()
1570 	@safe {
1571 		return createConnectionProxyStream(m_conn, m_rawConnection);
1572 	}
1573 	/// ditto
1574 	void connectProxy(scope void delegate(scope ConnectionStream) @safe del)
1575 	@safe {
1576 		() @trusted {
1577 			auto conn = createConnectionProxyStreamFL(m_conn, m_rawConnection);
1578 			del(conn);
1579 		} ();
1580 		finalize();
1581 	}
1582 
1583 	/** Sets the specified cookie value.
1584 
1585 		Params:
1586 			name = Name of the cookie
1587 			value = New cookie value - pass null to clear the cookie
1588 			path = Path (as seen by the client) of the directory tree in which the cookie is visible
1589 			encoding = Optional encoding (url, raw), default to URL encoding
1590 	*/
1591 	Cookie setCookie(string name, string value, string path = "/", Cookie.Encoding encoding = Cookie.Encoding.url)
1592 	@safe {
1593 		auto cookie = new Cookie();
1594 		cookie.path = path;
1595 		cookie.setValue(value, encoding);
1596 		if (value is null) {
1597 			cookie.maxAge = 0;
1598 			cookie.expires = "Thu, 01 Jan 1970 00:00:00 GMT";
1599 		}
1600 		cookies[name] = cookie;
1601 		return cookie;
1602 	}
1603 
1604 	/**
1605 		Initiates a new session.
1606 
1607 		The session is stored in the SessionStore that was specified when
1608 		creating the server. Depending on this, the session can be persistent
1609 		or temporary and specific to this server instance.
1610 	*/
1611 	Session startSession(string path = "/")
1612 	@safe {
1613 		return startSession(path, m_settings.sessionOptions);
1614 	}
1615 
1616 	/// ditto
1617 	Session startSession(string path, SessionOption options)
1618 	@safe {
1619 		assert(m_settings.sessionStore, "no session store set");
1620 		assert(!m_session, "Try to start a session, but already started one.");
1621 
1622 		bool secure;
1623 		if (options & SessionOption.secure) secure = true;
1624 		else if (options & SessionOption.noSecure) secure = false;
1625 		else secure = this.tls;
1626 
1627 		m_session = m_settings.sessionStore.create();
1628 		m_session.set("$sessionCookiePath", path);
1629 		m_session.set("$sessionCookieSecure", secure);
1630 		auto cookie = setCookie(m_settings.sessionIdCookie, m_session.id, path);
1631 		cookie.secure = secure;
1632 		cookie.httpOnly = (options & SessionOption.httpOnly) != 0;
1633 		cookie.sameSite = (options & SessionOption.noSameSiteStrict) ?
1634 						  Cookie.SameSite.lax : Cookie.SameSite.strict;
1635 		return m_session;
1636 	}
1637 
1638 	/**
1639 		Terminates the current session (if any).
1640 	*/
1641 	void terminateSession()
1642 	@safe {
1643 		if (!m_session) return;
1644 		auto cookie = setCookie(m_settings.sessionIdCookie, null, m_session.get!string("$sessionCookiePath"));
1645 		cookie.secure = m_session.get!bool("$sessionCookieSecure");
1646 		m_session.destroy();
1647 		m_session = Session.init;
1648 	}
1649 
1650 	@property ulong bytesWritten() @safe const { return m_countingWriter.bytesWritten; }
1651 
1652 	/**
1653 		Waits until either the connection closes, data arrives, or until the
1654 		given timeout is reached.
1655 
1656 		Returns:
1657 			$(D true) if the connection was closed and $(D false) if either the
1658 			timeout was reached, or if data has arrived for consumption.
1659 
1660 		See_Also: `connected`
1661 	*/
1662 	bool waitForConnectionClose(Duration timeout = Duration.max)
1663 	@safe {
1664 		if (!m_rawConnection || !m_rawConnection.connected) return true;
1665 		m_rawConnection.waitForData(timeout);
1666 		return !m_rawConnection.connected;
1667 	}
1668 
1669 	/**
1670 		Determines if the underlying connection is still alive.
1671 
1672 		Returns $(D true) if the remote peer is still connected and $(D false)
1673 		if the remote peer closed the connection.
1674 
1675 		See_Also: `waitForConnectionClose`
1676 	*/
1677 	@property bool connected()
1678 	@safe const {
1679 		if (!m_rawConnection) return false;
1680 		return m_rawConnection.connected;
1681 	}
1682 
1683 	/**
1684 		Finalizes the response. This is usually called automatically by the server.
1685 
1686 		This method can be called manually after writing the response to force
1687 		all network traffic associated with the current request to be finalized.
1688 		After the call returns, the `timeFinalized` property will be set.
1689 	*/
1690 	void finalize()
1691 	@safe {
1692 		if (m_zlibOutputStream) {
1693 			m_zlibOutputStream.finalize();
1694 			m_zlibOutputStream.destroy();
1695 		}
1696 		if (m_chunkedBodyWriter) {
1697 			m_chunkedBodyWriter.finalize();
1698 			m_chunkedBodyWriter.destroy();
1699 		}
1700 
1701 		// ignore exceptions caused by an already closed connection - the client
1702 		// may have closed the connection already and this doesn't usually indicate
1703 		// a problem.
1704 		if (m_rawConnection && m_rawConnection.connected) {
1705 			try if (m_conn) m_conn.flush();
1706 			catch (Exception e) logDebug("Failed to flush connection after finishing HTTP response: %s", e.msg);
1707 			if (!isHeadResponse && bytesWritten < headers.get("Content-Length", "0").to!ulong) {
1708 				logDebug("HTTP response only written partially before finalization. Terminating connection.");
1709 				m_requiresConnectionClose = true;
1710 			}
1711 
1712 			m_rawConnection = InterfaceProxy!ConnectionStream.init;
1713 		}
1714 
1715 		if (m_conn) {
1716 			m_conn = InterfaceProxy!Stream.init;
1717 			m_timeFinalized = Clock.currTime(UTC());
1718 		}
1719 	}
1720 
1721 	private void writeHeader()
1722 	@safe {
1723 		import vibe.stream.wrapper;
1724 
1725 		assert(!m_headerWritten, "Try to write header after body has already begun.");
1726 		assert(this.httpVersion != HTTPVersion.HTTP_1_0 || this.statusCode >= 200, "Informational status codes aren't supported by HTTP/1.0.");
1727 
1728 		// Don't set m_headerWritten for 1xx status codes
1729 		if (this.statusCode >= 200) m_headerWritten = true;
1730 		auto dst = streamOutputRange!1024(m_conn);
1731 
1732 		void writeLine(T...)(string fmt, T args)
1733 		@safe {
1734 			formattedWrite(() @trusted { return &dst; } (), fmt, args);
1735 			dst.put("\r\n");
1736 			logTrace(fmt, args);
1737 		}
1738 
1739 		logTrace("---------------------");
1740 		logTrace("HTTP server response:");
1741 		logTrace("---------------------");
1742 
1743 		// write the status line
1744 		writeLine("%s %d %s",
1745 			getHTTPVersionString(this.httpVersion),
1746 			this.statusCode,
1747 			this.statusPhrase.length ? this.statusPhrase : httpStatusText(this.statusCode));
1748 
1749 		// write all normal headers
1750 		foreach (k, v; this.headers.byKeyValue) {
1751 			dst.put(k);
1752 			dst.put(": ");
1753 			dst.put(v);
1754 			dst.put("\r\n");
1755 			logTrace("%s: %s", k, v);
1756 		}
1757 
1758 		logTrace("---------------------");
1759 
1760 		// write cookies
1761 		foreach (n, cookie; this.cookies.byKeyValue) {
1762 			dst.put("Set-Cookie: ");
1763 			cookie.writeString(() @trusted { return &dst; } (), n);
1764 			dst.put("\r\n");
1765 		}
1766 
1767 		// finalize response header
1768 		dst.put("\r\n");
1769 	}
1770 }
1771 
1772 /**
1773 	Represents the request listener for a specific `listenHTTP` call.
1774 
1775 	This struct can be used to stop listening for HTTP requests at runtime.
1776 */
1777 struct HTTPListener {
1778 	private {
1779 		size_t[] m_virtualHostIDs;
1780 	}
1781 
1782 	private this(size_t[] ids) @safe { m_virtualHostIDs = ids; }
1783 
1784 	@property NetworkAddress[] bindAddresses()
1785 	@safe {
1786 		NetworkAddress[] ret;
1787 		foreach (l; s_listeners)
1788 			if (l.m_virtualHosts.canFind!(v => m_virtualHostIDs.canFind(v.id))) {
1789 				NetworkAddress a;
1790 				a = resolveHost(l.bindAddress);
1791 				a.port = l.bindPort;
1792 				ret ~= a;
1793 			}
1794 		return ret;
1795 	}
1796 
1797 	/** Stops handling HTTP requests and closes the TCP listening port if
1798 		possible.
1799 	*/
1800 	void stopListening()
1801 	@safe {
1802 		import std.algorithm : countUntil;
1803 
1804 		foreach (vhid; m_virtualHostIDs) {
1805 			foreach (lidx, l; s_listeners) {
1806 				if (l.removeVirtualHost(vhid)) {
1807 					if (!l.hasVirtualHosts) {
1808 						l.m_listener.stopListening();
1809 						logInfo("Stopped to listen for HTTP%s requests on %s:%s", l.tlsContext ? "S": "", l.bindAddress, l.bindPort);
1810 						s_listeners = s_listeners[0 .. lidx] ~ s_listeners[lidx+1 .. $];
1811 					}
1812 				}
1813 				break;
1814 			}
1815 		}
1816 	}
1817 }
1818 
1819 
1820 /** Represents a single HTTP server port.
1821 
1822 	This class defines the incoming interface, port, and TLS configuration of
1823 	the public server port. The public server port may differ from the local
1824 	one if a reverse proxy of some kind is facing the public internet and
1825 	forwards to this HTTP server.
1826 
1827 	Multiple virtual hosts can be configured to be served from the same port.
1828 	Their TLS settings must be compatible and each virtual host must have a
1829 	unique name.
1830 */
1831 final class HTTPServerContext {
1832 	private struct VirtualHost {
1833 		HTTPServerRequestDelegate requestHandler;
1834 		HTTPServerSettings settings;
1835 		HTTPLogger[] loggers;
1836 		size_t id;
1837 	}
1838 
1839 	private {
1840 		TCPListener m_listener;
1841 		VirtualHost[] m_virtualHosts;
1842 		string m_bindAddress;
1843 		ushort m_bindPort;
1844 		TLSContext m_tlsContext;
1845 		static size_t s_vhostIDCounter = 1;
1846 	}
1847 
1848 	@safe:
1849 
1850 	this(string bind_address, ushort bind_port)
1851 	{
1852 		m_bindAddress = bind_address;
1853 		m_bindPort = bind_port;
1854 	}
1855 
1856 	/** Returns the TLS context associated with the listener.
1857 
1858 		For non-HTTPS listeners, `null` will be returned. Otherwise, if only a
1859 		single virtual host has been added, the TLS context of that host's
1860 		settings is returned. For multiple virtual hosts, an SNI context is
1861 		returned, which forwards to the individual contexts based on the
1862 		requested host name.
1863 	*/
1864 	@property TLSContext tlsContext() { return m_tlsContext; }
1865 
1866 	/// The local network interface IP address associated with this listener
1867 	@property string bindAddress() const { return m_bindAddress; }
1868 
1869 	/// The local port associated with this listener
1870 	@property ushort bindPort() const { return m_bindPort; }
1871 
1872 	/// Determines if any virtual hosts have been addded
1873 	@property bool hasVirtualHosts() const { return m_virtualHosts.length > 0; }
1874 
1875 	/** Adds a single virtual host.
1876 
1877 		Note that the port and bind address defined in `settings` must match the
1878 		ones for this listener. The `settings.host` field must be unique for
1879 		all virtual hosts.
1880 
1881 		Returns: Returns a unique ID for the new virtual host
1882 	*/
1883 	size_t addVirtualHost(HTTPServerSettings settings, HTTPServerRequestDelegate request_handler)
1884 	{
1885 		assert(settings.port == 0 || settings.port == m_bindPort, "Virtual host settings do not match bind port.");
1886 		assert(settings.bindAddresses.canFind(m_bindAddress), "Virtual host settings do not match bind address.");
1887 
1888 		VirtualHost vhost;
1889 		vhost.id = s_vhostIDCounter++;
1890 		vhost.settings = settings;
1891 		vhost.requestHandler = request_handler;
1892 
1893 		if (settings.accessLogger) vhost.loggers ~= settings.accessLogger;
1894 		if (settings.accessLogToConsole)
1895 			vhost.loggers ~= new HTTPConsoleLogger(settings, settings.accessLogFormat);
1896 		if (settings.accessLogFile.length)
1897 			vhost.loggers ~= new HTTPFileLogger(settings, settings.accessLogFormat, settings.accessLogFile);
1898 
1899 		if (!m_virtualHosts.length) m_tlsContext = settings.tlsContext;
1900 
1901 		enforce((m_tlsContext !is null) == (settings.tlsContext !is null),
1902 			"Cannot mix HTTP and HTTPS virtual hosts within the same listener.");
1903 
1904 		if (m_tlsContext) addSNIHost(settings);
1905 
1906 		m_virtualHosts ~= vhost;
1907 
1908 		if (settings.hostName.length) {
1909 			auto proto = settings.tlsContext ? "https" : "http";
1910 			auto port = settings.tlsContext && settings.port == 443 || !settings.tlsContext && settings.port == 80 ? "" : ":" ~ settings.port.to!string;
1911 			logInfo("Added virtual host %s://%s:%s/ (%s)", proto, settings.hostName, m_bindPort, m_bindAddress);
1912 		}
1913 
1914 		return vhost.id;
1915 	}
1916 
1917 	/// Removes a previously added virtual host using its ID.
1918 	bool removeVirtualHost(size_t id)
1919 	{
1920 		import std.algorithm.searching : countUntil;
1921 
1922 		auto idx = m_virtualHosts.countUntil!(c => c.id == id);
1923 		if (idx < 0) return false;
1924 
1925 		auto ctx = m_virtualHosts[idx];
1926 		m_virtualHosts = m_virtualHosts[0 .. idx] ~ m_virtualHosts[idx+1 .. $];
1927 		return true;
1928 	}
1929 
1930 	private void addSNIHost(HTTPServerSettings settings)
1931 	{
1932 		if (settings.tlsContext !is m_tlsContext && m_tlsContext.kind != TLSContextKind.serverSNI) {
1933 			logDebug("Create SNI TLS context for %s, port %s", bindAddress, bindPort);
1934 			m_tlsContext = createTLSContext(TLSContextKind.serverSNI);
1935 			m_tlsContext.sniCallback = &onSNI;
1936 		}
1937 
1938 		foreach (ctx; m_virtualHosts) {
1939 			/*enforce(ctx.settings.hostName != settings.hostName,
1940 				"A server with the host name '"~settings.hostName~"' is already "
1941 				"listening on "~addr~":"~to!string(settings.port)~".");*/
1942 		}
1943 	}
1944 
1945 	private TLSContext onSNI(string servername)
1946 	{
1947 		foreach (vhost; m_virtualHosts)
1948 			if (vhost.settings.hostName.icmp(servername) == 0) {
1949 				logDebug("Found context for SNI host '%s'.", servername);
1950 				return vhost.settings.tlsContext;
1951 			}
1952 		logDebug("No context found for SNI host '%s'.", servername);
1953 		return null;
1954 	}
1955 }
1956 
1957 /**************************************************************************************************/
1958 /* Private types                                                                                  */
1959 /**************************************************************************************************/
1960 
1961 private enum MaxHTTPHeaderLineLength = 4096;
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(InterfaceProxy!Stream http_stream, TCPConnection tcp_connection, HTTPServerContext listen_info, ref HTTPServerSettings settings, ref bool keep_alive, scope IAllocator 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);
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)(HTTPServerRequest req, InputStream http_stream, IAllocator alloc, ulong max_header_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(MaxHTTPHeaderLineLength, "\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, MaxHTTPHeaderLineLength, 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, 29);
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;