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