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 	private {
851 		SysTime m_timeCreated;
852 		HTTPServerSettings m_settings;
853 		ushort m_port;
854 		string m_peer;
855 	}
856 
857 	public {
858 		/// The IP address of the client
859 		@property string peer()
860 		@safe nothrow {
861 			if (!m_peer) {
862 				version (Have_vibe_core) {} else scope (failure) assert(false);
863 				// store the IP address (IPv4 addresses forwarded over IPv6 are stored in IPv4 format)
864 				auto peer_address_string = this.clientAddress.toString();
865 				if (peer_address_string.startsWith("::ffff:") && peer_address_string[7 .. $].indexOf(':') < 0)
866 					m_peer = peer_address_string[7 .. $];
867 				else m_peer = peer_address_string;
868 			}
869 			return m_peer;
870 		}
871 		/// ditto
872 		NetworkAddress clientAddress;
873 
874 		/// Determines if the request should be logged to the access log file.
875 		bool noLog;
876 
877 		/// Determines if the request was issued over an TLS encrypted channel.
878 		bool tls;
879 
880 		/** Information about the TLS certificate provided by the client.
881 
882 			Remarks: This field is only set if `tls` is true, and the peer
883 			presented a client certificate.
884 		*/
885 		TLSCertificateInformation clientCertificate;
886 
887 		/** Deprecated: The _path part of the URL.
888 
889 			Note that this function contains the decoded version of the
890 			requested path, which can yield incorrect results if the path
891 			contains URL encoded path separators. Use `requestPath` instead to
892 			get an encoding-aware representation.
893 		*/
894 		string path() @safe {
895 			if (_path.isNull) {
896 				_path = urlDecode(requestPath.toString);
897 			}
898 			return _path.get;
899 		}
900 
901 		private Nullable!string _path;
902 
903 		/** The path part of the requested URI.
904 		*/
905 		InetPath requestPath;
906 
907 		/** The user name part of the URL, if present.
908 		*/
909 		string username;
910 
911 		/** The _password part of the URL, if present.
912 		*/
913 		string password;
914 
915 		/** The _query string part of the URL.
916 		*/
917 		string queryString;
918 
919 		/** Contains the list of _cookies that are stored on the client.
920 
921 			Note that the a single cookie name may occur multiple times if multiple
922 			cookies have that name but different paths or domains that all match
923 			the request URI. By default, the first cookie will be returned, which is
924 			the or one of the cookies with the closest path match.
925 		*/
926 		@property ref CookieValueMap cookies() @safe {
927 			if (_cookies.isNull) {
928 				_cookies = CookieValueMap.init;
929 				if (auto pv = "cookie" in headers)
930 					parseCookies(*pv, _cookies.get);
931 			}
932 			return _cookies.get;
933 		}
934 		private Nullable!CookieValueMap _cookies;
935 
936 		/** Contains all _form fields supplied using the _query string.
937 
938 			The fields are stored in the same order as they are received.
939 		*/
940 		@property ref FormFields query() @safe {
941 			if (_query.isNull) {
942 				_query = FormFields.init;
943 				parseURLEncodedForm(queryString, _query.get);
944 			}
945 
946 			return _query.get;
947 		}
948 		Nullable!FormFields _query;
949 
950 		import vibe.utils.dictionarylist;
951 		/** A map of general parameters for the request.
952 
953 			This map is supposed to be used by middleware functionality to store
954 			information for later stages. For example vibe.http.router.URLRouter uses this map
955 			to store the value of any named placeholders.
956 		*/
957 		DictionaryList!(string, true, 8) params;
958 
959 		import std.variant : Variant;
960 		/** A map of context items for the request.
961 
962 			This is especially useful for passing application specific data down
963 			the chain of processors along with the request itself.
964 
965 			For example, a generic route may be defined to check user login status,
966 			if the user is logged in, add a reference to user specific data to the
967 			context.
968 
969 			This is implemented with `std.variant.Variant` to allow any type of data.
970 		*/
971 		DictionaryList!(Variant, true, 2) context;
972 
973 		/** Supplies the request body as a stream.
974 
975 			Note that when certain server options are set (such as
976 			HTTPServerOption.parseJsonBody) and a matching request was sent,
977 			the returned stream will be empty. If needed, remove those
978 			options and do your own processing of the body when launching
979 			the server. HTTPServerOption has a list of all options that affect
980 			the request body.
981 		*/
982 		InputStream bodyReader;
983 
984 		/** Contains the parsed Json for a JSON request.
985 
986 			A JSON request must have the Content-Type "application/json" or "application/vnd.api+json".
987 		*/
988 		@property ref Json json() @safe {
989 			if (_json.isNull) {
990 				auto splitter = contentType.splitter(';');
991 				auto ctype = splitter.empty ? "" : splitter.front;
992 
993 				if (icmp2(ctype, "application/json") == 0 || icmp2(ctype, "application/vnd.api+json") == 0) {
994 					auto bodyStr = bodyReader.readAllUTF8();
995 					if (!bodyStr.empty) _json = parseJson(bodyStr);
996 					else _json = Json.undefined;
997 				} else {
998 					_json = Json.undefined;
999 				}
1000 			}
1001 			return _json.get;
1002 		}
1003 
1004 		/// Get the json body when there is no content-type header
1005 		unittest {
1006 			assert(createTestHTTPServerRequest(URL("http://localhost/")).json.type == Json.Type.undefined);
1007 		}
1008 
1009 		private Nullable!Json _json;
1010 
1011 		/** Contains the parsed parameters of a HTML POST _form request.
1012 
1013 			The fields are stored in the same order as they are received.
1014 
1015 			Remarks:
1016 				A form request must either have the Content-Type
1017 				"application/x-www-form-urlencoded" or "multipart/form-data".
1018 		*/
1019 		@property ref FormFields form() @safe {
1020 			if (_form.isNull)
1021 				parseFormAndFiles();
1022 
1023 			return _form.get;
1024 		}
1025 
1026 		private Nullable!FormFields _form;
1027 
1028 		private void parseFormAndFiles() @safe {
1029 			_form = FormFields.init;
1030 			parseFormData(_form.get, _files, headers.get("Content-Type", ""), bodyReader, MaxHTTPHeaderLineLength);
1031 		}
1032 
1033 		/** Contains information about any uploaded file for a HTML _form request.
1034 		*/
1035 		@property ref FilePartFormFields files() @safe {
1036 			// _form and _files are parsed in one step
1037 			if (_form.isNull) {
1038 				parseFormAndFiles();
1039 				assert(!_form.isNull);
1040 			}
1041 
1042             return _files;
1043 		}
1044 
1045 		private FilePartFormFields _files;
1046 
1047 		/** The current Session object.
1048 
1049 			This field is set if HTTPServerResponse.startSession() has been called
1050 			on a previous response and if the client has sent back the matching
1051 			cookie.
1052 
1053 			Remarks: Requires the HTTPServerOption.parseCookies option.
1054 		*/
1055 		Session session;
1056 	}
1057 
1058 	package {
1059 		/** The settings of the server serving this request.
1060 		 */
1061 		@property const(HTTPServerSettings) serverSettings() const @safe
1062 		{
1063 			return m_settings;
1064 		}
1065 	}
1066 
1067 	this(SysTime time, ushort port)
1068 	@safe {
1069 		m_timeCreated = time.toUTC();
1070 		m_port = port;
1071 	}
1072 
1073 	/** Time when this request started processing.
1074 	*/
1075 	@property SysTime timeCreated() const @safe { return m_timeCreated; }
1076 
1077 
1078 	/** The full URL that corresponds to this request.
1079 
1080 		The host URL includes the protocol, host and optionally the user
1081 		and password that was used for this request. This field is useful to
1082 		construct self referencing URLs.
1083 
1084 		Note that the port is currently not set, so that this only works if
1085 		the standard port is used.
1086 	*/
1087 	@property URL fullURL()
1088 	const @safe {
1089 		URL url;
1090 
1091 		auto xfh = this.headers.get("X-Forwarded-Host");
1092 		auto xfp = this.headers.get("X-Forwarded-Port");
1093 		auto xfpr = this.headers.get("X-Forwarded-Proto");
1094 
1095 		// Set URL host segment.
1096 		if (xfh.length) {
1097 			url.host = xfh;
1098 		} else if (!this.host.empty) {
1099 			url.host = this.host;
1100 		} else if (!m_settings.hostName.empty) {
1101 			url.host = m_settings.hostName;
1102 		} else {
1103 			url.host = m_settings.bindAddresses[0];
1104 		}
1105 
1106 		// Set URL schema segment.
1107 		if (xfpr.length) {
1108 			url.schema = xfpr;
1109 		} else if (this.tls) {
1110 			url.schema = "https";
1111 		} else {
1112 			url.schema = "http";
1113 		}
1114 
1115 		// Set URL port segment.
1116 		if (xfp.length) {
1117 			try {
1118 				url.port = xfp.to!ushort;
1119 			} catch (ConvException) {
1120 				// TODO : Consider responding with a 400/etc. error from here.
1121 				logWarn("X-Forwarded-Port header was not valid port (%s)", xfp);
1122 			}
1123 		} else if (!xfh) {
1124 			if (url.schema == "https") {
1125 				if (m_port != 443U) url.port = m_port;
1126 			} else {
1127 				if (m_port != 80U)  url.port = m_port;
1128 			}
1129 		}
1130 
1131 		if (url.host.startsWith('[')) { // handle IPv6 address
1132 			auto idx = url.host.indexOf(']');
1133 			if (idx >= 0 && idx+1 < url.host.length && url.host[idx+1] == ':')
1134 				url.host = url.host[1 .. idx];
1135 		} else { // handle normal host names or IPv4 address
1136 			auto idx = url.host.indexOf(':');
1137 			if (idx >= 0) url.host = url.host[0 .. idx];
1138 		}
1139 
1140 		url.username = this.username;
1141 		url.password = this.password;
1142 		url.localURI = this.requestURI;
1143 
1144 		return url;
1145 	}
1146 
1147 	/** The relative path to the root folder.
1148 
1149 		Using this function instead of absolute URLs for embedded links can be
1150 		useful to avoid dead link when the site is piped through a
1151 		reverse-proxy.
1152 
1153 		The returned string always ends with a slash.
1154 	*/
1155 	@property string rootDir()
1156 	const @safe {
1157 		import std.algorithm.searching : count;
1158 		auto depth = requestPath.bySegment.count!(s => s.name.length > 0);
1159 		if (depth > 0 && !requestPath.endsWithSlash) depth--;
1160 		return depth == 0 ? "./" : replicate("../", depth);
1161 	}
1162 
1163 	unittest {
1164 		assert(createTestHTTPServerRequest(URL("http://localhost/")).rootDir == "./");
1165 		assert(createTestHTTPServerRequest(URL("http://localhost/foo")).rootDir == "./");
1166 		assert(createTestHTTPServerRequest(URL("http://localhost/foo/")).rootDir == "../");
1167 		assert(createTestHTTPServerRequest(URL("http://localhost/foo/bar")).rootDir == "../");
1168 		assert(createTestHTTPServerRequest(URL("http://localhost")).rootDir == "./");
1169 	}
1170 }
1171 
1172 
1173 /**
1174 	Represents a HTTP response as sent from the server side.
1175 */
1176 final class HTTPServerResponse : HTTPResponse {
1177 	private {
1178 		InterfaceProxy!Stream m_conn;
1179 		InterfaceProxy!ConnectionStream m_rawConnection;
1180 		InterfaceProxy!OutputStream m_bodyWriter;
1181 		IAllocator m_requestAlloc;
1182 		FreeListRef!ChunkedOutputStream m_chunkedBodyWriter;
1183 		FreeListRef!CountingOutputStream m_countingWriter;
1184 		FreeListRef!ZlibOutputStream m_zlibOutputStream;
1185 		HTTPServerSettings m_settings;
1186 		Session m_session;
1187 		bool m_headerWritten = false;
1188 		bool m_isHeadResponse = false;
1189 		bool m_tls;
1190 		bool m_requiresConnectionClose;
1191 		SysTime m_timeFinalized;
1192 	}
1193 
1194 	static if (!is(Stream == InterfaceProxy!Stream)) {
1195 		this(Stream conn, ConnectionStream raw_connection, HTTPServerSettings settings, IAllocator req_alloc)
1196 		@safe {
1197 			this(InterfaceProxy!Stream(conn), InterfaceProxy!ConnectionStream(raw_connection), settings, req_alloc);
1198 		}
1199 	}
1200 
1201 	this(InterfaceProxy!Stream conn, InterfaceProxy!ConnectionStream raw_connection, HTTPServerSettings settings, IAllocator req_alloc)
1202 	@safe {
1203 		m_conn = conn;
1204 		m_rawConnection = raw_connection;
1205 		m_countingWriter = createCountingOutputStreamFL(conn);
1206 		m_settings = settings;
1207 		m_requestAlloc = req_alloc;
1208 	}
1209 
1210 	/** Returns the time at which the request was finalized.
1211 
1212 		Note that this field will only be set after `finalize` has been called.
1213 	*/
1214 	@property SysTime timeFinalized() const @safe { return m_timeFinalized; }
1215 
1216 	/** Determines if the HTTP header has already been written.
1217 	*/
1218 	@property bool headerWritten() const @safe { return m_headerWritten; }
1219 
1220 	/** Determines if the response does not need a body.
1221 	*/
1222 	bool isHeadResponse() const @safe { return m_isHeadResponse; }
1223 
1224 	/** Determines if the response is sent over an encrypted connection.
1225 	*/
1226 	bool tls() const @safe { return m_tls; }
1227 
1228 	/** Writes the entire response body at once.
1229 
1230 		Params:
1231 			data = The data to write as the body contents
1232 			status = Optional response status code to set
1233 			content_type = Optional content type to apply to the response.
1234 				If no content type is given and no "Content-Type" header is
1235 				set in the response, this will default to
1236 				`"application/octet-stream"`.
1237 
1238 		See_Also: `HTTPStatusCode`
1239 	*/
1240 	void writeBody(in ubyte[] data, string content_type = null)
1241 	@safe {
1242 		if (content_type.length) headers["Content-Type"] = content_type;
1243 		else if ("Content-Type" !in headers) headers["Content-Type"] = "application/octet-stream";
1244 		headers["Content-Length"] = formatAlloc(m_requestAlloc, "%d", data.length);
1245 		bodyWriter.write(data);
1246 	}
1247 	/// ditto
1248 	void writeBody(in ubyte[] data, int status, string content_type = null)
1249 	@safe {
1250 		statusCode = status;
1251 		writeBody(data, content_type);
1252 	}
1253 	/// ditto
1254 	void writeBody(scope InputStream data, string content_type = null)
1255 	@safe {
1256 		if (content_type.length) headers["Content-Type"] = content_type;
1257 		else if ("Content-Type" !in headers) headers["Content-Type"] = "application/octet-stream";
1258 		data.pipe(bodyWriter);
1259 	}
1260 
1261 	/** Writes the entire response body as a single string.
1262 
1263 		Params:
1264 			data = The string to write as the body contents
1265 			status = Optional response status code to set
1266 			content_type = Optional content type to apply to the response.
1267 				If no content type is given and no "Content-Type" header is
1268 				set in the response, this will default to
1269 				`"text/plain; charset=UTF-8"`.
1270 
1271 		See_Also: `HTTPStatusCode`
1272 	*/
1273 	/// ditto
1274 	void writeBody(string data, string content_type = null)
1275 	@safe {
1276 		if (!content_type.length && "Content-Type" !in headers)
1277 			content_type = "text/plain; charset=UTF-8";
1278 		writeBody(cast(const(ubyte)[])data, content_type);
1279 	}
1280 	/// ditto
1281 	void writeBody(string data, int status, string content_type = null)
1282 	@safe {
1283 		statusCode = status;
1284 		writeBody(data, content_type);
1285 	}
1286 
1287 	/** Writes the whole response body at once, without doing any further encoding.
1288 
1289 		The caller has to make sure that the appropriate headers are set correctly
1290 		(i.e. Content-Type and Content-Encoding).
1291 
1292 		Note that the version taking a RandomAccessStream may perform additional
1293 		optimizations such as sending a file directly from the disk to the
1294 		network card using a DMA transfer.
1295 
1296 	*/
1297 	void writeRawBody(RandomAccessStream)(RandomAccessStream stream) @safe
1298 		if (isRandomAccessStream!RandomAccessStream)
1299 	{
1300 		assert(!m_headerWritten, "A body was already written!");
1301 		writeHeader();
1302 		if (m_isHeadResponse) return;
1303 
1304 		auto bytes = stream.size - stream.tell();
1305 		stream.pipe(m_conn);
1306 		m_countingWriter.increment(bytes);
1307 	}
1308 	/// ditto
1309 	void writeRawBody(InputStream)(InputStream stream, size_t num_bytes = 0) @safe
1310 		if (isInputStream!InputStream && !isRandomAccessStream!InputStream)
1311 	{
1312 		assert(!m_headerWritten, "A body was already written!");
1313 		writeHeader();
1314 		if (m_isHeadResponse) return;
1315 
1316 		if (num_bytes > 0) {
1317 			stream.pipe(m_conn, num_bytes);
1318 			m_countingWriter.increment(num_bytes);
1319 		} else stream.pipe(m_countingWriter, num_bytes);
1320 	}
1321 	/// ditto
1322 	void writeRawBody(RandomAccessStream)(RandomAccessStream stream, int status) @safe
1323 		if (isRandomAccessStream!RandomAccessStream)
1324 	{
1325 		statusCode = status;
1326 		writeRawBody(stream);
1327 	}
1328 	/// ditto
1329 	void writeRawBody(InputStream)(InputStream stream, int status, size_t num_bytes = 0) @safe
1330 		if (isInputStream!InputStream && !isRandomAccessStream!InputStream)
1331 	{
1332 		statusCode = status;
1333 		writeRawBody(stream, num_bytes);
1334 	}
1335 
1336 
1337 	/// Writes a JSON message with the specified status
1338 	void writeJsonBody(T)(T data, int status, bool allow_chunked = false)
1339 	{
1340 		statusCode = status;
1341 		writeJsonBody(data, allow_chunked);
1342 	}
1343 	/// ditto
1344 	void writeJsonBody(T)(T data, int status, string content_type, bool allow_chunked = false)
1345 	{
1346 		statusCode = status;
1347 		writeJsonBody(data, content_type, allow_chunked);
1348 	}
1349 
1350 	/// ditto
1351 	void writeJsonBody(T)(T data, string content_type, bool allow_chunked = false)
1352 	{
1353 		headers["Content-Type"] = content_type;
1354 		writeJsonBody(data, allow_chunked);
1355 	}
1356 	/// ditto
1357 	void writeJsonBody(T)(T data, bool allow_chunked = false)
1358 	{
1359 		doWriteJsonBody!(T, false)(data, allow_chunked);
1360 	}
1361 	/// ditto
1362 	void writePrettyJsonBody(T)(T data, bool allow_chunked = false)
1363 	{
1364 		doWriteJsonBody!(T, true)(data, allow_chunked);
1365 	}
1366 
1367 	private void doWriteJsonBody(T, bool PRETTY)(T data, bool allow_chunked = false)
1368 	{
1369 		import std.traits;
1370 		import vibe.stream.wrapper;
1371 
1372 		static if (!is(T == Json) && is(typeof(data.data())) && isArray!(typeof(data.data()))) {
1373 			static assert(!is(T == Appender!(typeof(data.data()))), "Passed an Appender!T to writeJsonBody - this is most probably not doing what's indended.");
1374 		}
1375 
1376 		if ("Content-Type" !in headers)
1377 			headers["Content-Type"] = "application/json; charset=UTF-8";
1378 
1379 
1380 		// set an explicit content-length field if chunked encoding is not allowed
1381 		if (!allow_chunked) {
1382 			import vibe.internal.rangeutil;
1383 			long length = 0;
1384 			auto counter = RangeCounter(() @trusted { return &length; } ());
1385 			static if (PRETTY) serializeToPrettyJson(counter, data);
1386 			else serializeToJson(counter, data);
1387 			headers["Content-Length"] = formatAlloc(m_requestAlloc, "%d", length);
1388 		}
1389 
1390 		auto rng = streamOutputRange!1024(bodyWriter);
1391 		static if (PRETTY) serializeToPrettyJson(() @trusted { return &rng; } (), data);
1392 		else serializeToJson(() @trusted { return &rng; } (), data);
1393 	}
1394 
1395 	/**
1396 	 * Writes the response with no body.
1397 	 *
1398 	 * This method should be used in situations where no body is
1399 	 * requested, such as a HEAD request. For an empty body, just use writeBody,
1400 	 * as this method causes problems with some keep-alive connections.
1401 	 */
1402 	void writeVoidBody()
1403 	@safe {
1404 		if (!m_isHeadResponse) {
1405 			assert("Content-Length" !in headers);
1406 			assert("Transfer-Encoding" !in headers);
1407 		}
1408 		assert(!headerWritten);
1409 		writeHeader();
1410 		m_conn.flush();
1411 	}
1412 
1413 	/** A stream for writing the body of the HTTP response.
1414 
1415 		Note that after 'bodyWriter' has been accessed for the first time, it
1416 		is not allowed to change any header or the status code of the response.
1417 	*/
1418 	@property InterfaceProxy!OutputStream bodyWriter()
1419 	@safe {
1420 		assert(!!m_conn);
1421 		if (m_bodyWriter) {
1422 			// for test responses, the body writer is pre-set, without headers
1423 			// being written, so we may need to do that here
1424 			if (!m_headerWritten) writeHeader();
1425 
1426 			return m_bodyWriter;
1427 		}
1428 
1429 		assert(!m_headerWritten, "A void body was already written!");
1430 		assert(this.statusCode >= 200, "1xx responses can't have body");
1431 
1432 		if (m_isHeadResponse) {
1433 			// for HEAD requests, we define a NullOutputWriter for convenience
1434 			// - no body will be written. However, the request handler should call writeVoidBody()
1435 			// and skip writing of the body in this case.
1436 			if ("Content-Length" !in headers)
1437 				headers["Transfer-Encoding"] = "chunked";
1438 			writeHeader();
1439 			m_bodyWriter = nullSink;
1440 			return m_bodyWriter;
1441 		}
1442 
1443 		if ("Content-Encoding" in headers && "Content-Length" in headers) {
1444 			// we do not known how large the compressed body will be in advance
1445 			// so remove the content-length and use chunked transfer
1446 			headers.remove("Content-Length");
1447 		}
1448 
1449 		if (auto pcl = "Content-Length" in headers) {
1450 			writeHeader();
1451 			m_countingWriter.writeLimit = (*pcl).to!ulong;
1452 			m_bodyWriter = m_countingWriter;
1453 		} else if (httpVersion <= HTTPVersion.HTTP_1_0) {
1454 			if ("Connection" in headers)
1455 				headers.remove("Connection"); // default to "close"
1456 			writeHeader();
1457 			m_bodyWriter = m_conn;
1458 		} else {
1459 			headers["Transfer-Encoding"] = "chunked";
1460 			writeHeader();
1461 			m_chunkedBodyWriter = createChunkedOutputStreamFL(m_countingWriter);
1462 			m_bodyWriter = m_chunkedBodyWriter;
1463 		}
1464 
1465 		if (auto pce = "Content-Encoding" in headers) {
1466 			if (icmp2(*pce, "gzip") == 0) {
1467 				m_zlibOutputStream = createGzipOutputStreamFL(m_bodyWriter);
1468 				m_bodyWriter = m_zlibOutputStream;
1469 			} else if (icmp2(*pce, "deflate") == 0) {
1470 				m_zlibOutputStream = createDeflateOutputStreamFL(m_bodyWriter);
1471 				m_bodyWriter = m_zlibOutputStream;
1472 			} else {
1473 				logWarn("Unsupported Content-Encoding set in response: '"~*pce~"'");
1474 			}
1475 		}
1476 
1477 		return m_bodyWriter;
1478 	}
1479 
1480 	/** Sends a redirect request to the client.
1481 
1482 		Params:
1483 			url = The URL to redirect to
1484 			status = The HTTP redirect status (3xx) to send - by default this is $(D HTTPStatus.found)
1485 	*/
1486 	void redirect(string url, int status = HTTPStatus.found)
1487 	@safe {
1488 		// Disallow any characters that may influence the header parsing
1489 		enforce(!url.representation.canFind!(ch => ch < 0x20),
1490 			"Control character in redirection URL.");
1491 
1492 		statusCode = status;
1493 		headers["Location"] = url;
1494 		writeBody("redirecting...");
1495 	}
1496 	/// ditto
1497 	void redirect(URL url, int status = HTTPStatus.found)
1498 	@safe {
1499 		redirect(url.toString(), status);
1500 	}
1501 
1502 	///
1503 	@safe unittest {
1504 		import vibe.http.router;
1505 
1506 		void request_handler(HTTPServerRequest req, HTTPServerResponse res)
1507 		{
1508 			res.redirect("http://example.org/some_other_url");
1509 		}
1510 
1511 		void test()
1512 		{
1513 			auto router = new URLRouter;
1514 			router.get("/old_url", &request_handler);
1515 
1516 			listenHTTP(new HTTPServerSettings, router);
1517 		}
1518 	}
1519 
1520 
1521 	/** Special method sending a SWITCHING_PROTOCOLS response to the client.
1522 
1523 		Notice: For the overload that returns a `ConnectionStream`, it must be
1524 			ensured that the returned instance doesn't outlive the request
1525 			handler callback.
1526 
1527 		Params:
1528 			protocol = The protocol set in the "Upgrade" header of the response.
1529 				Use an empty string to skip setting this field.
1530 	*/
1531 	ConnectionStream switchProtocol(string protocol)
1532 	@safe {
1533 		statusCode = HTTPStatus.switchingProtocols;
1534 		if (protocol.length) headers["Upgrade"] = protocol;
1535 		writeVoidBody();
1536 		m_requiresConnectionClose = true;
1537 		m_headerWritten = true;
1538 		return createConnectionProxyStream(m_conn, m_rawConnection);
1539 	}
1540 	/// ditto
1541 	void switchProtocol(string protocol, scope void delegate(scope ConnectionStream) @safe del)
1542 	@safe {
1543 		statusCode = HTTPStatus.switchingProtocols;
1544 		if (protocol.length) headers["Upgrade"] = protocol;
1545 		writeVoidBody();
1546 		m_requiresConnectionClose = true;
1547 		m_headerWritten = true;
1548 		() @trusted {
1549 			auto conn = createConnectionProxyStreamFL(m_conn, m_rawConnection);
1550 			del(conn);
1551 		} ();
1552 		finalize();
1553 	}
1554 
1555 	/** Special method for handling CONNECT proxy tunnel
1556 
1557 		Notice: For the overload that returns a `ConnectionStream`, it must be
1558 			ensured that the returned instance doesn't outlive the request
1559 			handler callback.
1560 	*/
1561 	ConnectionStream connectProxy()
1562 	@safe {
1563 		return createConnectionProxyStream(m_conn, m_rawConnection);
1564 	}
1565 	/// ditto
1566 	void connectProxy(scope void delegate(scope ConnectionStream) @safe del)
1567 	@safe {
1568 		() @trusted {
1569 			auto conn = createConnectionProxyStreamFL(m_conn, m_rawConnection);
1570 			del(conn);
1571 		} ();
1572 		finalize();
1573 	}
1574 
1575 	/** Sets the specified cookie value.
1576 
1577 		Params:
1578 			name = Name of the cookie
1579 			value = New cookie value - pass null to clear the cookie
1580 			path = Path (as seen by the client) of the directory tree in which the cookie is visible
1581 			encoding = Optional encoding (url, raw), default to URL encoding
1582 	*/
1583 	Cookie setCookie(string name, string value, string path = "/", Cookie.Encoding encoding = Cookie.Encoding.url)
1584 	@safe {
1585 		auto cookie = new Cookie();
1586 		cookie.path = path;
1587 		cookie.setValue(value, encoding);
1588 		if (value is null) {
1589 			cookie.maxAge = 0;
1590 			cookie.expires = "Thu, 01 Jan 1970 00:00:00 GMT";
1591 		}
1592 		cookies[name] = cookie;
1593 		return cookie;
1594 	}
1595 
1596 	/**
1597 		Initiates a new session.
1598 
1599 		The session is stored in the SessionStore that was specified when
1600 		creating the server. Depending on this, the session can be persistent
1601 		or temporary and specific to this server instance.
1602 	*/
1603 	Session startSession(string path = "/")
1604 	@safe {
1605 		return startSession(path, m_settings.sessionOptions);
1606 	}
1607 
1608 	/// ditto
1609 	Session startSession(string path, SessionOption options)
1610 	@safe {
1611 		assert(m_settings.sessionStore, "no session store set");
1612 		assert(!m_session, "Try to start a session, but already started one.");
1613 
1614 		bool secure;
1615 		if (options & SessionOption.secure) secure = true;
1616 		else if (options & SessionOption.noSecure) secure = false;
1617 		else secure = this.tls;
1618 
1619 		m_session = m_settings.sessionStore.create();
1620 		m_session.set("$sessionCookiePath", path);
1621 		m_session.set("$sessionCookieSecure", secure);
1622 		auto cookie = setCookie(m_settings.sessionIdCookie, m_session.id, path);
1623 		cookie.secure = secure;
1624 		cookie.httpOnly = (options & SessionOption.httpOnly) != 0;
1625 		cookie.sameSite = (options & SessionOption.noSameSiteStrict) ?
1626 						  Cookie.SameSite.lax : Cookie.SameSite.strict;
1627 		return m_session;
1628 	}
1629 
1630 	/**
1631 		Terminates the current session (if any).
1632 	*/
1633 	void terminateSession()
1634 	@safe {
1635 		if (!m_session) return;
1636 		auto cookie = setCookie(m_settings.sessionIdCookie, null, m_session.get!string("$sessionCookiePath"));
1637 		cookie.secure = m_session.get!bool("$sessionCookieSecure");
1638 		m_session.destroy();
1639 		m_session = Session.init;
1640 	}
1641 
1642 	@property ulong bytesWritten() @safe const { return m_countingWriter.bytesWritten; }
1643 
1644 	/**
1645 		Waits until either the connection closes, data arrives, or until the
1646 		given timeout is reached.
1647 
1648 		Returns:
1649 			$(D true) if the connection was closed and $(D false) if either the
1650 			timeout was reached, or if data has arrived for consumption.
1651 
1652 		See_Also: `connected`
1653 	*/
1654 	bool waitForConnectionClose(Duration timeout = Duration.max)
1655 	@safe {
1656 		if (!m_rawConnection || !m_rawConnection.connected) return true;
1657 		m_rawConnection.waitForData(timeout);
1658 		return !m_rawConnection.connected;
1659 	}
1660 
1661 	/**
1662 		Determines if the underlying connection is still alive.
1663 
1664 		Returns $(D true) if the remote peer is still connected and $(D false)
1665 		if the remote peer closed the connection.
1666 
1667 		See_Also: `waitForConnectionClose`
1668 	*/
1669 	@property bool connected()
1670 	@safe const {
1671 		if (!m_rawConnection) return false;
1672 		return m_rawConnection.connected;
1673 	}
1674 
1675 	/**
1676 		Finalizes the response. This is usually called automatically by the server.
1677 
1678 		This method can be called manually after writing the response to force
1679 		all network traffic associated with the current request to be finalized.
1680 		After the call returns, the `timeFinalized` property will be set.
1681 	*/
1682 	void finalize()
1683 	@safe {
1684 		if (m_zlibOutputStream) {
1685 			m_zlibOutputStream.finalize();
1686 			m_zlibOutputStream.destroy();
1687 		}
1688 		if (m_chunkedBodyWriter) {
1689 			m_chunkedBodyWriter.finalize();
1690 			m_chunkedBodyWriter.destroy();
1691 		}
1692 
1693 		// ignore exceptions caused by an already closed connection - the client
1694 		// may have closed the connection already and this doesn't usually indicate
1695 		// a problem.
1696 		if (m_rawConnection && m_rawConnection.connected) {
1697 			try if (m_conn) m_conn.flush();
1698 			catch (Exception e) logDebug("Failed to flush connection after finishing HTTP response: %s", e.msg);
1699 			if (!isHeadResponse && bytesWritten < headers.get("Content-Length", "0").to!ulong) {
1700 				logDebug("HTTP response only written partially before finalization. Terminating connection.");
1701 				m_requiresConnectionClose = true;
1702 			}
1703 
1704 			m_rawConnection = InterfaceProxy!ConnectionStream.init;
1705 		}
1706 
1707 		if (m_conn) {
1708 			m_conn = InterfaceProxy!Stream.init;
1709 			m_timeFinalized = Clock.currTime(UTC());
1710 		}
1711 	}
1712 
1713 	private void writeHeader()
1714 	@safe {
1715 		import vibe.stream.wrapper;
1716 
1717 		assert(!m_headerWritten, "Try to write header after body has already begun.");
1718 		assert(this.httpVersion != HTTPVersion.HTTP_1_0 || this.statusCode >= 200, "Informational status codes aren't supported by HTTP/1.0.");
1719 
1720 		// Don't set m_headerWritten for 1xx status codes
1721 		if (this.statusCode >= 200) m_headerWritten = true;
1722 		auto dst = streamOutputRange!1024(m_conn);
1723 
1724 		void writeLine(T...)(string fmt, T args)
1725 		@safe {
1726 			formattedWrite(() @trusted { return &dst; } (), fmt, args);
1727 			dst.put("\r\n");
1728 			logTrace(fmt, args);
1729 		}
1730 
1731 		logTrace("---------------------");
1732 		logTrace("HTTP server response:");
1733 		logTrace("---------------------");
1734 
1735 		// write the status line
1736 		writeLine("%s %d %s",
1737 			getHTTPVersionString(this.httpVersion),
1738 			this.statusCode,
1739 			this.statusPhrase.length ? this.statusPhrase : httpStatusText(this.statusCode));
1740 
1741 		// write all normal headers
1742 		foreach (k, v; this.headers.byKeyValue) {
1743 			dst.put(k);
1744 			dst.put(": ");
1745 			dst.put(v);
1746 			dst.put("\r\n");
1747 			logTrace("%s: %s", k, v);
1748 		}
1749 
1750 		logTrace("---------------------");
1751 
1752 		// write cookies
1753 		foreach (n, cookie; this.cookies.byKeyValue) {
1754 			dst.put("Set-Cookie: ");
1755 			cookie.writeString(() @trusted { return &dst; } (), n);
1756 			dst.put("\r\n");
1757 		}
1758 
1759 		// finalize response header
1760 		dst.put("\r\n");
1761 	}
1762 }
1763 
1764 /**
1765 	Represents the request listener for a specific `listenHTTP` call.
1766 
1767 	This struct can be used to stop listening for HTTP requests at runtime.
1768 */
1769 struct HTTPListener {
1770 	private {
1771 		size_t[] m_virtualHostIDs;
1772 	}
1773 
1774 	private this(size_t[] ids) @safe { m_virtualHostIDs = ids; }
1775 
1776 	@property NetworkAddress[] bindAddresses()
1777 	@safe {
1778 		NetworkAddress[] ret;
1779 		foreach (l; s_listeners)
1780 			if (l.m_virtualHosts.canFind!(v => m_virtualHostIDs.canFind(v.id))) {
1781 				NetworkAddress a;
1782 				a = resolveHost(l.bindAddress);
1783 				a.port = l.bindPort;
1784 				ret ~= a;
1785 			}
1786 		return ret;
1787 	}
1788 
1789 	/** Stops handling HTTP requests and closes the TCP listening port if
1790 		possible.
1791 	*/
1792 	void stopListening()
1793 	@safe {
1794 		import std.algorithm : countUntil;
1795 
1796 		foreach (vhid; m_virtualHostIDs) {
1797 			foreach (lidx, l; s_listeners) {
1798 				if (l.removeVirtualHost(vhid)) {
1799 					if (!l.hasVirtualHosts) {
1800 						l.m_listener.stopListening();
1801 						logInfo("Stopped to listen for HTTP%s requests on %s:%s", l.tlsContext ? "S": "", l.bindAddress, l.bindPort);
1802 						s_listeners = s_listeners[0 .. lidx] ~ s_listeners[lidx+1 .. $];
1803 					}
1804 				}
1805 				break;
1806 			}
1807 		}
1808 	}
1809 }
1810 
1811 
1812 /** Represents a single HTTP server port.
1813 
1814 	This class defines the incoming interface, port, and TLS configuration of
1815 	the public server port. The public server port may differ from the local
1816 	one if a reverse proxy of some kind is facing the public internet and
1817 	forwards to this HTTP server.
1818 
1819 	Multiple virtual hosts can be configured to be served from the same port.
1820 	Their TLS settings must be compatible and each virtual host must have a
1821 	unique name.
1822 */
1823 final class HTTPServerContext {
1824 	private struct VirtualHost {
1825 		HTTPServerRequestDelegate requestHandler;
1826 		HTTPServerSettings settings;
1827 		HTTPLogger[] loggers;
1828 		size_t id;
1829 	}
1830 
1831 	private {
1832 		TCPListener m_listener;
1833 		VirtualHost[] m_virtualHosts;
1834 		string m_bindAddress;
1835 		ushort m_bindPort;
1836 		TLSContext m_tlsContext;
1837 		static size_t s_vhostIDCounter = 1;
1838 	}
1839 
1840 	@safe:
1841 
1842 	this(string bind_address, ushort bind_port)
1843 	{
1844 		m_bindAddress = bind_address;
1845 		m_bindPort = bind_port;
1846 	}
1847 
1848 	/** Returns the TLS context associated with the listener.
1849 
1850 		For non-HTTPS listeners, `null` will be returned. Otherwise, if only a
1851 		single virtual host has been added, the TLS context of that host's
1852 		settings is returned. For multiple virtual hosts, an SNI context is
1853 		returned, which forwards to the individual contexts based on the
1854 		requested host name.
1855 	*/
1856 	@property TLSContext tlsContext() { return m_tlsContext; }
1857 
1858 	/// The local network interface IP address associated with this listener
1859 	@property string bindAddress() const { return m_bindAddress; }
1860 
1861 	/// The local port associated with this listener
1862 	@property ushort bindPort() const { return m_bindPort; }
1863 
1864 	/// Determines if any virtual hosts have been addded
1865 	@property bool hasVirtualHosts() const { return m_virtualHosts.length > 0; }
1866 
1867 	/** Adds a single virtual host.
1868 
1869 		Note that the port and bind address defined in `settings` must match the
1870 		ones for this listener. The `settings.host` field must be unique for
1871 		all virtual hosts.
1872 
1873 		Returns: Returns a unique ID for the new virtual host
1874 	*/
1875 	size_t addVirtualHost(HTTPServerSettings settings, HTTPServerRequestDelegate request_handler)
1876 	{
1877 		assert(settings.port == 0 || settings.port == m_bindPort, "Virtual host settings do not match bind port.");
1878 		assert(settings.bindAddresses.canFind(m_bindAddress), "Virtual host settings do not match bind address.");
1879 
1880 		VirtualHost vhost;
1881 		vhost.id = s_vhostIDCounter++;
1882 		vhost.settings = settings;
1883 		vhost.requestHandler = request_handler;
1884 
1885 		if (settings.accessLogger) vhost.loggers ~= settings.accessLogger;
1886 		if (settings.accessLogToConsole)
1887 			vhost.loggers ~= new HTTPConsoleLogger(settings, settings.accessLogFormat);
1888 		if (settings.accessLogFile.length)
1889 			vhost.loggers ~= new HTTPFileLogger(settings, settings.accessLogFormat, settings.accessLogFile);
1890 
1891 		if (!m_virtualHosts.length) m_tlsContext = settings.tlsContext;
1892 
1893 		enforce((m_tlsContext !is null) == (settings.tlsContext !is null),
1894 			"Cannot mix HTTP and HTTPS virtual hosts within the same listener.");
1895 
1896 		if (m_tlsContext) addSNIHost(settings);
1897 
1898 		m_virtualHosts ~= vhost;
1899 
1900 		if (settings.hostName.length) {
1901 			auto proto = settings.tlsContext ? "https" : "http";
1902 			auto port = settings.tlsContext && settings.port == 443 || !settings.tlsContext && settings.port == 80 ? "" : ":" ~ settings.port.to!string;
1903 			logInfo("Added virtual host %s://%s:%s/ (%s)", proto, settings.hostName, m_bindPort, m_bindAddress);
1904 		}
1905 
1906 		return vhost.id;
1907 	}
1908 
1909 	/// Removes a previously added virtual host using its ID.
1910 	bool removeVirtualHost(size_t id)
1911 	{
1912 		import std.algorithm.searching : countUntil;
1913 
1914 		auto idx = m_virtualHosts.countUntil!(c => c.id == id);
1915 		if (idx < 0) return false;
1916 
1917 		auto ctx = m_virtualHosts[idx];
1918 		m_virtualHosts = m_virtualHosts[0 .. idx] ~ m_virtualHosts[idx+1 .. $];
1919 		return true;
1920 	}
1921 
1922 	private void addSNIHost(HTTPServerSettings settings)
1923 	{
1924 		if (settings.tlsContext !is m_tlsContext && m_tlsContext.kind != TLSContextKind.serverSNI) {
1925 			logDebug("Create SNI TLS context for %s, port %s", bindAddress, bindPort);
1926 			m_tlsContext = createTLSContext(TLSContextKind.serverSNI);
1927 			m_tlsContext.sniCallback = &onSNI;
1928 		}
1929 
1930 		foreach (ctx; m_virtualHosts) {
1931 			/*enforce(ctx.settings.hostName != settings.hostName,
1932 				"A server with the host name '"~settings.hostName~"' is already "
1933 				"listening on "~addr~":"~to!string(settings.port)~".");*/
1934 		}
1935 	}
1936 
1937 	private TLSContext onSNI(string servername)
1938 	{
1939 		foreach (vhost; m_virtualHosts)
1940 			if (vhost.settings.hostName.icmp(servername) == 0) {
1941 				logDebug("Found context for SNI host '%s'.", servername);
1942 				return vhost.settings.tlsContext;
1943 			}
1944 		logDebug("No context found for SNI host '%s'.", servername);
1945 		return null;
1946 	}
1947 }
1948 
1949 /**************************************************************************************************/
1950 /* Private types                                                                                  */
1951 /**************************************************************************************************/
1952 
1953 private enum MaxHTTPHeaderLineLength = 4096;
1954 
1955 private final class LimitedHTTPInputStream : LimitedInputStream {
1956 @safe:
1957 
1958 	this(InterfaceProxy!InputStream stream, ulong byte_limit, bool silent_limit = false) {
1959 		super(stream, byte_limit, silent_limit, true);
1960 	}
1961 	override void onSizeLimitReached() {
1962 		throw new HTTPStatusException(HTTPStatus.requestEntityTooLarge);
1963 	}
1964 }
1965 
1966 private final class TimeoutHTTPInputStream : InputStream {
1967 @safe:
1968 
1969 	private {
1970 		long m_timeref;
1971 		long m_timeleft;
1972 		InterfaceProxy!InputStream m_in;
1973 	}
1974 
1975 	this(InterfaceProxy!InputStream stream, Duration timeleft, SysTime reftime)
1976 	{
1977 		enforce(timeleft > 0.seconds, "Timeout required");
1978 		m_in = stream;
1979 		m_timeleft = timeleft.total!"hnsecs"();
1980 		m_timeref = reftime.stdTime();
1981 	}
1982 
1983 	@property bool empty() { enforce(m_in, "InputStream missing"); return m_in.empty(); }
1984 	@property ulong leastSize() { enforce(m_in, "InputStream missing"); return m_in.leastSize();  }
1985 	@property bool dataAvailableForRead() {  enforce(m_in, "InputStream missing"); return m_in.dataAvailableForRead; }
1986 	const(ubyte)[] peek() { return m_in.peek(); }
1987 
1988 	size_t read(scope ubyte[] dst, IOMode mode)
1989 	{
1990 		enforce(m_in, "InputStream missing");
1991 		size_t nread = 0;
1992 		checkTimeout();
1993 		// FIXME: this should use ConnectionStream.waitForData to enforce the timeout during the
1994 		// read operation
1995 		return m_in.read(dst, mode);
1996 	}
1997 
1998 	alias read = InputStream.read;
1999 
2000 	private void checkTimeout()
2001 	@safe {
2002 		auto curr = Clock.currStdTime();
2003 		auto diff = curr - m_timeref;
2004 		if (diff > m_timeleft) throw new HTTPStatusException(HTTPStatus.requestTimeout);
2005 		m_timeleft -= diff;
2006 		m_timeref = curr;
2007 	}
2008 }
2009 
2010 /**************************************************************************************************/
2011 /* Private functions                                                                              */
2012 /**************************************************************************************************/
2013 
2014 private {
2015 	import core.sync.mutex;
2016 
2017 	shared string s_distHost;
2018 	shared ushort s_distPort = 11000;
2019 
2020 	HTTPServerContext[] s_listeners;
2021 }
2022 
2023 /**
2024 	[private] Starts a HTTP server listening on the specified port.
2025 
2026 	This is the same as listenHTTP() except that it does not use a VibeDist host for
2027 	remote listening, even if specified on the command line.
2028 */
2029 private HTTPListener listenHTTPPlain(HTTPServerSettings settings, HTTPServerRequestDelegate request_handler)
2030 @safe {
2031 	import vibe.core.core : runWorkerTaskDist;
2032 	import std.algorithm : canFind, find;
2033 
2034 	static TCPListener doListen(HTTPServerContext listen_info, bool reusePort, bool reuseAddress, bool is_tls)
2035 	@safe {
2036 		try {
2037 			TCPListenOptions options = TCPListenOptions.defaults;
2038 			if(reuseAddress) options |= TCPListenOptions.reuseAddress; else options &= ~TCPListenOptions.reuseAddress;
2039 			if(reusePort) options |= TCPListenOptions.reusePort; else options &= ~TCPListenOptions.reusePort;
2040 			auto ret = listenTCP(listen_info.bindPort, (TCPConnection conn) nothrow @safe {
2041 					try handleHTTPConnection(conn, listen_info);
2042 					catch (Exception e) {
2043 						logError("HTTP connection handler has thrown: %s", e.msg);
2044 						debug logDebug("Full error: %s", () @trusted { return e.toString().sanitize(); } ());
2045 						try conn.close();
2046 						catch (Exception e) logError("Failed to close connection: %s", e.msg);
2047 					}
2048 				}, listen_info.bindAddress, options);
2049 
2050 			// support port 0 meaning any available port
2051 			if (listen_info.bindPort == 0)
2052 				listen_info.m_bindPort = ret.bindAddress.port;
2053 
2054 			auto proto = is_tls ? "https" : "http";
2055 			auto urladdr = listen_info.bindAddress;
2056 			if (urladdr.canFind(':')) urladdr = "["~urladdr~"]";
2057 			logInfo("Listening for requests on %s://%s:%s/", proto, urladdr, listen_info.bindPort);
2058 			return ret;
2059 		} catch( Exception e ) {
2060 			logWarn("Failed to listen on %s:%s", listen_info.bindAddress, listen_info.bindPort);
2061 			return TCPListener.init;
2062 		}
2063 	}
2064 
2065 	size_t[] vid;
2066 
2067 	// Check for every bind address/port, if a new listening socket needs to be created and
2068 	// check for conflicting servers
2069 	foreach (addr; settings.bindAddresses) {
2070 		HTTPServerContext linfo;
2071 
2072 		auto l = s_listeners.find!(l => l.bindAddress == addr && l.bindPort == settings.port);
2073 		if (!l.empty) linfo = l.front;
2074 		else {
2075 			auto li = new HTTPServerContext(addr, settings.port);
2076 			if (auto tcp_lst = doListen(li,
2077 					(settings.options & HTTPServerOption.reusePort) != 0,
2078 					(settings.options & HTTPServerOption.reuseAddress) != 0,
2079 					settings.tlsContext !is null)) // DMD BUG 2043
2080 			{
2081 				li.m_listener = tcp_lst;
2082 				s_listeners ~= li;
2083 				linfo = li;
2084 			}
2085 		}
2086 
2087 		if (linfo) vid ~= linfo.addVirtualHost(settings, request_handler);
2088 	}
2089 
2090 	enforce(vid.length > 0, "Failed to listen for incoming HTTP connections on any of the supplied interfaces.");
2091 
2092 	return HTTPListener(vid);
2093 }
2094 
2095 private alias TLSStreamType = ReturnType!(createTLSStreamFL!(InterfaceProxy!Stream));
2096 
2097 
2098 private bool handleRequest(InterfaceProxy!Stream http_stream, TCPConnection tcp_connection, HTTPServerContext listen_info, ref HTTPServerSettings settings, ref bool keep_alive, scope IAllocator request_allocator)
2099 @safe {
2100 	import std.algorithm.searching : canFind;
2101 
2102 	SysTime reqtime = Clock.currTime(UTC());
2103 
2104 	// some instances that live only while the request is running
2105 	FreeListRef!HTTPServerRequest req = FreeListRef!HTTPServerRequest(reqtime, listen_info.bindPort);
2106 	FreeListRef!TimeoutHTTPInputStream timeout_http_input_stream;
2107 	FreeListRef!LimitedHTTPInputStream limited_http_input_stream;
2108 	FreeListRef!ChunkedInputStream chunked_input_stream;
2109 
2110 	// store the IP address
2111 	req.clientAddress = tcp_connection.remoteAddress;
2112 
2113 	if (!listen_info.hasVirtualHosts) {
2114 		logWarn("Didn't find a HTTP listening context for incoming connection. Dropping.");
2115 		keep_alive = false;
2116 		return false;
2117 	}
2118 
2119 	// Default to the first virtual host for this listener
2120 	HTTPServerContext.VirtualHost context = listen_info.m_virtualHosts[0];
2121 	HTTPServerRequestDelegate request_task = context.requestHandler;
2122 	settings = context.settings;
2123 
2124 	// temporarily set to the default settings, the virtual host specific settings will be set further down
2125 	req.m_settings = settings;
2126 
2127 	// Create the response object
2128 	InterfaceProxy!ConnectionStream cproxy = tcp_connection;
2129 	auto res = FreeListRef!HTTPServerResponse(http_stream, cproxy, settings, request_allocator/*.Scoped_payload*/);
2130 	req.tls = res.m_tls = listen_info.tlsContext !is null;
2131 	if (req.tls) {
2132 		version (HaveNoTLS) assert(false);
2133 		else {
2134 			static if (is(InterfaceProxy!ConnectionStream == ConnectionStream))
2135 				req.clientCertificate = (cast(TLSStream)http_stream).peerCertificate;
2136 			else
2137 				req.clientCertificate = http_stream.extract!TLSStreamType.peerCertificate;
2138 		}
2139 	}
2140 
2141 	// Error page handler
2142 	void errorOut(int code, string msg, string debug_msg, Throwable ex)
2143 	@safe {
2144 		assert(!res.headerWritten);
2145 
2146 		res.statusCode = code;
2147 		if (settings && settings.errorPageHandler) {
2148 			/*scope*/ auto err = new HTTPServerErrorInfo;
2149 			err.code = code;
2150 			err.message = msg;
2151 			err.debugMessage = debug_msg;
2152 			err.exception = ex;
2153 			settings.errorPageHandler_(req, res, err);
2154 		} else {
2155 			if (debug_msg.length)
2156 				res.writeBody(format("%s - %s\n\n%s\n\nInternal error information:\n%s", code, httpStatusText(code), msg, debug_msg));
2157 			else res.writeBody(format("%s - %s\n\n%s", code, httpStatusText(code), msg));
2158 		}
2159 		assert(res.headerWritten);
2160 	}
2161 
2162 	bool parsed = false;
2163 	/*bool*/ keep_alive = false;
2164 
2165 	// parse the request
2166 	try {
2167 		logTrace("reading request..");
2168 
2169 		// limit the total request time
2170 		InterfaceProxy!InputStream reqReader = http_stream;
2171 		if (settings.maxRequestTime > dur!"seconds"(0) && settings.maxRequestTime != Duration.max) {
2172 			timeout_http_input_stream = FreeListRef!TimeoutHTTPInputStream(reqReader, settings.maxRequestTime, reqtime);
2173 			reqReader = timeout_http_input_stream;
2174 		}
2175 
2176 		// basic request parsing
2177 		parseRequestHeader(req, reqReader, request_allocator, settings.maxRequestHeaderSize);
2178 		logTrace("Got request header.");
2179 
2180 		// find the matching virtual host
2181 		string reqhost;
2182 		ushort reqport = 0;
2183 		{
2184 			string s = req.host;
2185 			enforceHTTP(s.length > 0 || req.httpVersion <= HTTPVersion.HTTP_1_0, HTTPStatus.badRequest, "Missing Host header.");
2186 			if (s.startsWith('[')) { // IPv6 address
2187 				auto idx = s.indexOf(']');
2188 				enforce(idx > 0, "Missing closing ']' for IPv6 address.");
2189 				reqhost = s[1 .. idx];
2190 				s = s[idx+1 .. $];
2191 			} else if (s.length) { // host name or IPv4 address
2192 				auto idx = s.indexOf(':');
2193 				if (idx < 0) idx = s.length;
2194 				enforceHTTP(idx > 0, HTTPStatus.badRequest, "Missing Host header.");
2195 				reqhost = s[0 .. idx];
2196 				s = s[idx .. $];
2197 			}
2198 			if (s.startsWith(':')) reqport = s[1 .. $].to!ushort;
2199 		}
2200 
2201 		foreach (ctx; listen_info.m_virtualHosts)
2202 			if (icmp2(ctx.settings.hostName, reqhost) == 0 &&
2203 				(!reqport || reqport == ctx.settings.port))
2204 			{
2205 				context = ctx;
2206 				settings = ctx.settings;
2207 				request_task = ctx.requestHandler;
2208 				break;
2209 			}
2210 		req.m_settings = settings;
2211 		res.m_settings = settings;
2212 
2213 		// setup compressed output
2214 		if (settings.useCompressionIfPossible) {
2215 			if (auto pae = "Accept-Encoding" in req.headers) {
2216 				if (canFind(*pae, "gzip")) {
2217 					res.headers["Content-Encoding"] = "gzip";
2218 				} else if (canFind(*pae, "deflate")) {
2219 					res.headers["Content-Encoding"] = "deflate";
2220 				}
2221 			}
2222 		}
2223 
2224 		// limit request size
2225 		if (auto pcl = "Content-Length" in req.headers) {
2226 			string v = *pcl;
2227 			auto contentLength = parse!ulong(v); // DMDBUG: to! thinks there is a H in the string
2228 			enforceBadRequest(v.length == 0, "Invalid content-length");
2229 			enforceBadRequest(settings.maxRequestSize <= 0 || contentLength <= settings.maxRequestSize, "Request size too big");
2230 			limited_http_input_stream = FreeListRef!LimitedHTTPInputStream(reqReader, contentLength);
2231 		} else if (auto pt = "Transfer-Encoding" in req.headers) {
2232 			enforceBadRequest(icmp(*pt, "chunked") == 0);
2233 			chunked_input_stream = createChunkedInputStreamFL(reqReader);
2234 			InterfaceProxy!InputStream ciproxy = chunked_input_stream;
2235 			limited_http_input_stream = FreeListRef!LimitedHTTPInputStream(ciproxy, settings.maxRequestSize, true);
2236 		} else {
2237 			limited_http_input_stream = FreeListRef!LimitedHTTPInputStream(reqReader, 0);
2238 		}
2239 		req.bodyReader = limited_http_input_stream;
2240 
2241 		// handle Expect header
2242 		if (auto pv = "Expect" in req.headers) {
2243 			if (icmp2(*pv, "100-continue") == 0) {
2244 				logTrace("sending 100 continue");
2245 				http_stream.write("HTTP/1.1 100 Continue\r\n\r\n");
2246 			}
2247 		}
2248 
2249 		// eagerly parse the URL as its lightweight and defacto @nogc
2250 		auto url = URL.parse(req.requestURI);
2251 		req.queryString = url.queryString;
2252 		req.username = url.username;
2253 		req.password = url.password;
2254 		req.requestPath = url.path;
2255 
2256 		// lookup the session
2257 		if (settings.sessionStore) {
2258 			// use the first cookie that contains a valid session ID in case
2259 			// of multiple matching session cookies
2260 			foreach (val; req.cookies.getAll(settings.sessionIdCookie)) {
2261 				req.session = settings.sessionStore.open(val);
2262 				res.m_session = req.session;
2263 				if (req.session) break;
2264 			}
2265 		}
2266 
2267 		// write default headers
2268 		if (req.method == HTTPMethod.HEAD) res.m_isHeadResponse = true;
2269 		if (settings.serverString.length)
2270 			res.headers["Server"] = settings.serverString;
2271 		res.headers["Date"] = formatRFC822DateAlloc(reqtime);
2272 		if (req.persistent)
2273 			res.headers["Keep-Alive"] = formatAlloc(
2274 				request_allocator, "timeout=%d", settings.keepAliveTimeout.total!"seconds"());
2275 
2276 		// finished parsing the request
2277 		parsed = true;
2278 		logTrace("persist: %s", req.persistent);
2279 		keep_alive = req.persistent;
2280 
2281 		if (context.settings.rejectConnectionPredicate !is null)
2282 		{
2283 			import std.socket : Address, parseAddress;
2284 			
2285 			auto forward = req.headers.get("X-Forwarded-For", null);
2286 			if (forward !is null)
2287 			{
2288 				try {
2289 					auto ix = forward.indexOf(',');
2290 					if (ix != -1)
2291 						forward = forward[0 .. ix];
2292 					if (context.settings.rejectConnectionPredicate(NetworkAddress(parseAddress(forward))))
2293 						errorOut(HTTPStatus.forbidden, 
2294 							httpStatusText(HTTPStatus.forbidden), null, null);
2295 				} catch (Exception e)
2296 					logTrace("Malformed X-Forwarded-For header: %s", e.msg);
2297 			}
2298 		}
2299 
2300 		// handle the request
2301 		logTrace("handle request (body %d)", req.bodyReader.leastSize);
2302 		res.httpVersion = req.httpVersion;
2303 		request_task(req, res);
2304 
2305 		// if no one has written anything, return 404
2306 		if (!res.headerWritten) {
2307 			string dbg_msg;
2308 			logDiagnostic("No response written for %s", req.requestURI);
2309 			if (settings.options & HTTPServerOption.errorStackTraces)
2310 				dbg_msg = format("No routes match path '%s'", req.requestURI);
2311 			errorOut(HTTPStatus.notFound, httpStatusText(HTTPStatus.notFound), dbg_msg, null);
2312 		}
2313 	} catch (HTTPStatusException err) {
2314 		if (!res.headerWritten) errorOut(err.status, err.msg, err.debugMessage, err);
2315 		else logDiagnostic("HTTPStatusException while writing the response: %s", err.msg);
2316 		debug logDebug("Exception while handling request %s %s: %s", req.method,
2317 					   req.requestURI, () @trusted { return err.toString().sanitize; } ());
2318 		if (!parsed || res.headerWritten || justifiesConnectionClose(err.status))
2319 			keep_alive = false;
2320 	} catch (UncaughtException e) {
2321 		auto status = parsed ? HTTPStatus.internalServerError : HTTPStatus.badRequest;
2322 		string dbg_msg;
2323 		if (settings.options & HTTPServerOption.errorStackTraces)
2324 			dbg_msg = () @trusted { return e.toString().sanitize; } ();
2325 		if (!res.headerWritten && tcp_connection.connected)
2326 			errorOut(status, httpStatusText(status), dbg_msg, e);
2327 		else logDiagnostic("Error while writing the response: %s", e.msg);
2328 		debug logDebug("Exception while handling request %s %s: %s", req.method,
2329 					   req.requestURI, () @trusted { return e.toString().sanitize(); } ());
2330 		if (!parsed || res.headerWritten || !cast(Exception)e) keep_alive = false;
2331 	}
2332 
2333 	if (tcp_connection.connected && keep_alive) {
2334 		if (req.bodyReader && !req.bodyReader.empty) {
2335 			req.bodyReader.pipe(nullSink);
2336 			logTrace("dropped body");
2337 		}
2338 	}
2339 
2340 	// finalize (e.g. for chunked encoding)
2341 	res.finalize();
2342 
2343 	if (res.m_requiresConnectionClose)
2344 		keep_alive = false;
2345 
2346 	foreach (k, v ; req._files.byKeyValue) {
2347 		if (existsFile(v.tempPath)) {
2348 			removeFile(v.tempPath);
2349 			logDebug("Deleted upload tempfile %s", v.tempPath.toString());
2350 		}
2351 	}
2352 
2353 	if (!req.noLog) {
2354 		// log the request to access log
2355 		foreach (log; context.loggers)
2356 			log.log(req, res);
2357 	}
2358 
2359 	//logTrace("return %s (used pool memory: %s/%s)", keep_alive, request_allocator.allocatedSize, request_allocator.totalSize);
2360 	logTrace("return %s", keep_alive);
2361 	return keep_alive != false;
2362 }
2363 
2364 
2365 private void parseRequestHeader(InputStream)(HTTPServerRequest req, InputStream http_stream, IAllocator alloc, ulong max_header_size)
2366 	if (isInputStream!InputStream)
2367 {
2368 	auto stream = FreeListRef!LimitedHTTPInputStream(http_stream, max_header_size);
2369 
2370 	logTrace("HTTP server reading status line");
2371 	auto reqln = () @trusted { return cast(string)stream.readLine(MaxHTTPHeaderLineLength, "\r\n", alloc); }();
2372 
2373 	logTrace("--------------------");
2374 	logTrace("HTTP server request:");
2375 	logTrace("--------------------");
2376 	logTrace("%s", reqln);
2377 
2378 	//Method
2379 	auto pos = reqln.indexOf(' ');
2380 	enforceBadRequest(pos >= 0, "invalid request method");
2381 
2382 	req.method = httpMethodFromString(reqln[0 .. pos]);
2383 	reqln = reqln[pos+1 .. $];
2384 	//Path
2385 	pos = reqln.indexOf(' ');
2386 	enforceBadRequest(pos >= 0, "invalid request path");
2387 
2388 	req.requestURI = reqln[0 .. pos];
2389 	reqln = reqln[pos+1 .. $];
2390 
2391 	req.httpVersion = parseHTTPVersion(reqln);
2392 
2393 	//headers
2394 	parseRFC5322Header(stream, req.headers, MaxHTTPHeaderLineLength, alloc, false);
2395 
2396 	foreach (k, v; req.headers.byKeyValue)
2397 		logTrace("%s: %s", k, v);
2398 	logTrace("--------------------");
2399 }
2400 
2401 private void parseCookies(string str, ref CookieValueMap cookies)
2402 @safe {
2403 	import std.encoding : sanitize;
2404 	import std.array : split;
2405 	import std.string : strip;
2406 	import std.algorithm.iteration : map, filter, each;
2407 	import vibe.http.common : Cookie;
2408 	() @trusted { return str.sanitize; } ()
2409 		.split(";")
2410 		.map!(kv => kv.strip.split("="))
2411 		.filter!(kv => kv.length == 2) //ignore illegal cookies
2412 		.each!(kv => cookies.add(kv[0], kv[1], Cookie.Encoding.raw) );
2413 }
2414 
2415 unittest
2416 {
2417   auto cvm = CookieValueMap();
2418   parseCookies("foo=bar;; baz=zinga; öö=üü   ;   møøse=was=sacked;    onlyval1; =onlyval2; onlykey=", cvm);
2419   assert(cvm["foo"] == "bar");
2420   assert(cvm["baz"] == "zinga");
2421   assert(cvm["öö"] == "üü");
2422   assert( "møøse" ! in cvm); //illegal cookie gets ignored
2423   assert( "onlyval1" ! in cvm); //illegal cookie gets ignored
2424   assert(cvm["onlykey"] == "");
2425   assert(cvm[""] == "onlyval2");
2426   assert(cvm.length() == 5);
2427   cvm = CookieValueMap();
2428   parseCookies("", cvm);
2429   assert(cvm.length() == 0);
2430   cvm = CookieValueMap();
2431   parseCookies(";;=", cvm);
2432   assert(cvm.length() == 1);
2433   assert(cvm[""] == "");
2434 }
2435 
2436 shared static this()
2437 {
2438 	version (VibeNoDefaultArgs) {}
2439 	else {
2440 		string disthost = s_distHost;
2441 		ushort distport = s_distPort;
2442 		import vibe.core.args : readOption;
2443 		readOption("disthost|d", () @trusted { return &disthost; } (), "Sets the name of a vibedist server to use for load balancing.");
2444 		readOption("distport", () @trusted { return &distport; } (), "Sets the port used for load balancing.");
2445 		setVibeDistHost(disthost, distport);
2446 	}
2447 }
2448 
2449 private struct CacheTime
2450 {
2451 	string cachedDate;
2452 	SysTime nextUpdate;
2453 
2454 	this(SysTime nextUpdate) @safe @nogc pure nothrow
2455 	{
2456 		this.nextUpdate = nextUpdate;
2457 	}
2458 
2459 	void update(SysTime time) @safe
2460 	{
2461 		this.nextUpdate = time + 1.seconds;
2462 		this.nextUpdate.fracSecs = nsecs(0);
2463 	}
2464 }
2465 
2466 private string formatRFC822DateAlloc(SysTime time)
2467 @safe {
2468 	static LAST = CacheTime(SysTime.min());
2469 
2470 	if (time > LAST.nextUpdate) {
2471 		auto app = new FixedAppender!(string, 29);
2472 		writeRFC822DateTimeString(app, time);
2473 		LAST.update(time);
2474 		LAST.cachedDate = () @trusted { return app.data; } ();
2475 		return () @trusted { return app.data; } ();
2476 	} else
2477 		return LAST.cachedDate;
2478 }
2479 
2480 version (VibeDebugCatchAll) private alias UncaughtException = Throwable;
2481 else private alias UncaughtException = Exception;