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