1 /**
2 	A simple HTTP/1.1 client implementation.
3 
4 	Copyright: © 2012-2014 Sönke Ludwig
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Sönke Ludwig, Jan Krüger
7 */
8 module vibe.http.client;
9 
10 public import vibe.core.net;
11 public import vibe.http.common;
12 public import vibe.inet.url;
13 
14 import vibe.core.connectionpool;
15 import vibe.core.core;
16 import vibe.core.log;
17 import vibe.data.json;
18 import vibe.inet.message;
19 import vibe.inet.url;
20 import vibe.stream.counting;
21 import vibe.stream.tls;
22 import vibe.stream.operations;
23 import vibe.stream.wrapper : createConnectionProxyStream;
24 import vibe.stream.zlib;
25 import vibe.utils.array;
26 import vibe.utils.dictionarylist;
27 import vibe.internal.allocator;
28 import vibe.internal.freelistref;
29 import vibe.internal.interfaceproxy : InterfaceProxy, interfaceProxy;
30 
31 import core.exception : AssertError;
32 import std.algorithm : splitter;
33 import std.array;
34 import std.conv;
35 import std.encoding : sanitize;
36 import std.exception;
37 import std.format;
38 import std..string;
39 import std.typecons;
40 import std.datetime;
41 import std.socket : AddressFamily;
42 
43 version(Posix)
44 {
45 	version = UnixSocket;
46 }
47 
48 
49 /**************************************************************************************************/
50 /* Public functions                                                                               */
51 /**************************************************************************************************/
52 @safe:
53 
54 /**
55 	Performs a synchronous HTTP request on the specified URL.
56 
57 	The requester parameter allows to customize the request and to specify the request body for
58 	non-GET requests before it is sent. A response object is then returned or passed to the
59 	responder callback synchronously.
60 
61 	This function is a low-level HTTP client facility. It will not perform automatic redirect,
62 	caching or similar tasks. For a high-level download facility (similar to cURL), see the
63 	`vibe.inet.urltransfer` module.
64 
65 	Note that it is highly recommended to use one of the overloads that take a responder callback,
66 	as they can avoid some memory allocations and are safe against accidentally leaving stale
67 	response objects (objects whose response body wasn't fully read). For the returning overloads
68 	of the function it is recommended to put a `scope(exit)` right after the call in which
69 	`HTTPClientResponse.dropBody` is called to avoid this.
70 
71 	See_also: `vibe.inet.urltransfer.download`
72 */
73 HTTPClientResponse requestHTTP(string url, scope void delegate(scope HTTPClientRequest req) requester = null, const(HTTPClientSettings) settings = defaultSettings)
74 {
75 	return requestHTTP(URL.parse(url), requester, settings);
76 }
77 /// ditto
78 HTTPClientResponse requestHTTP(URL url, scope void delegate(scope HTTPClientRequest req) requester = null, const(HTTPClientSettings) settings = defaultSettings)
79 {
80 	import std.algorithm.searching : canFind;
81 
82 	bool use_tls = isTLSRequired(url, settings);
83 
84 	auto cli = connectHTTP(url.getFilteredHost, url.port, use_tls, settings);
85 	auto res = cli.request(
86 		(scope req){ httpRequesterDg(req, url, settings, requester); },
87 	);
88 
89 	// make sure the connection stays locked if the body still needs to be read
90 	if( res.m_client ) res.lockedConnection = cli;
91 
92 	logTrace("Returning HTTPClientResponse for conn %s", () @trusted { return cast(void*)res.lockedConnection.__conn; } ());
93 	return res;
94 }
95 /// ditto
96 void requestHTTP(string url, scope void delegate(scope HTTPClientRequest req) requester, scope void delegate(scope HTTPClientResponse req) responder, const(HTTPClientSettings) settings = defaultSettings)
97 {
98 	requestHTTP(URL(url), requester, responder, settings);
99 }
100 /// ditto
101 void requestHTTP(URL url, scope void delegate(scope HTTPClientRequest req) requester, scope void delegate(scope HTTPClientResponse req) responder, const(HTTPClientSettings) settings = defaultSettings)
102 {
103 	bool use_tls = isTLSRequired(url, settings);
104 
105 	auto cli = connectHTTP(url.getFilteredHost, url.port, use_tls, settings);
106 	cli.request(
107 		(scope req){ httpRequesterDg(req, url, settings, requester); },
108 		responder
109 	);
110 	assert(!cli.m_requesting, "HTTP client still requesting after return!?");
111 	assert(!cli.m_responding, "HTTP client still responding after return!?");
112 }
113 
114 private bool isTLSRequired(in URL url, in HTTPClientSettings settings)
115 {
116 	version(UnixSocket) {
117 		enforce(url.schema == "http" || url.schema == "https" || url.schema == "http+unix" || url.schema == "https+unix", "URL schema must be http(s) or http(s)+unix.");
118 	} else {
119 		enforce(url.schema == "http" || url.schema == "https", "URL schema must be http(s).");
120 	}
121 	enforce(url.host.length > 0, "URL must contain a host name.");
122 	bool use_tls;
123 
124 	if (settings.proxyURL.schema !is null)
125 		use_tls = settings.proxyURL.schema == "https";
126 	else
127 	{
128 		version(UnixSocket)
129 			use_tls = url.schema == "https" || url.schema == "https+unix";
130 		else
131 			use_tls = url.schema == "https";
132 	}
133 
134 	return use_tls;
135 }
136 
137 private void httpRequesterDg(scope HTTPClientRequest req, in URL url, in HTTPClientSettings settings, scope void delegate(scope HTTPClientRequest req) requester)
138 {
139 	import std.algorithm.searching : canFind;
140 	import vibe.http.internal.basic_auth_client: addBasicAuth;
141 
142 	if (url.localURI.length) {
143 		assert(url.path.absolute, "Request URL path must be absolute.");
144 		req.requestURL = url.localURI;
145 	}
146 
147 	if (settings.proxyURL.schema !is null)
148 		req.requestURL = url.toString(); // proxy exception to the URL representation
149 
150 	// IPv6 addresses need to be put into brackets
151 	auto hoststr = url.host.canFind(':') ? "["~url.host~"]" : url.host;
152 
153 	// Provide port number when it is not the default one (RFC2616 section 14.23)
154 	if (url.port && url.port != url.defaultPort)
155 		req.headers["Host"] = format("%s:%d", hoststr, url.port);
156 	else
157 		req.headers["Host"] = hoststr;
158 
159 	if ("authorization" !in req.headers && url.username != "")
160 		req.addBasicAuth(url.username, url.password);
161 
162 	if (requester) () @trusted { requester(req); } ();
163 }
164 
165 /** Posts a simple JSON request. Note that the server www.example.org does not
166 	exists, so there will be no meaningful result.
167 */
168 unittest {
169 	import vibe.core.log;
170 	import vibe.http.client;
171 	import vibe.stream.operations;
172 
173 	void test()
174 	{
175 		requestHTTP("http://www.example.org/",
176 			(scope req) {
177 				req.method = HTTPMethod.POST;
178 				//req.writeJsonBody(["name": "My Name"]);
179 			},
180 			(scope res) {
181 				logInfo("Response: %s", res.bodyReader.readAllUTF8());
182 			}
183 		);
184 	}
185 }
186 
187 
188 /**
189 	Returns a HTTPClient proxy object that is connected to the specified host.
190 
191 	Internally, a connection pool is used to reuse already existing connections. Note that
192 	usually requestHTTP should be used for making requests instead of manually using a
193 	HTTPClient to do so.
194 */
195 auto connectHTTP(string host, ushort port = 0, bool use_tls = false, const(HTTPClientSettings) settings = null)
196 {
197 	auto sttngs = settings ? settings : defaultSettings;
198 
199 	if (port == 0) port = use_tls ? 443 : 80;
200 	auto ckey = ConnInfo(host, port, use_tls, sttngs.proxyURL.host, sttngs.proxyURL.port, sttngs.networkInterface);
201 
202 	ConnectionPool!HTTPClient pool;
203 	s_connections.opApply((ref c) @safe {
204 		if (c[0] == ckey)
205 			pool = c[1];
206 		return 0;
207 	});
208 
209 	if (!pool) {
210 		logDebug("Create HTTP client pool %s:%s %s proxy %s:%d", host, port, use_tls, sttngs.proxyURL.host, sttngs.proxyURL.port);
211 		pool = new ConnectionPool!HTTPClient({
212 				auto ret = new HTTPClient;
213 				ret.connect(host, port, use_tls, sttngs);
214 				return ret;
215 			});
216 		if (s_connections.full) s_connections.popFront();
217 		s_connections.put(tuple(ckey, pool));
218 	}
219 
220 	return pool.lockConnection();
221 }
222 
223 static ~this()
224 {
225 	foreach (ci; s_connections) {
226 		ci[1].removeUnused((conn) {
227 			conn.disconnect();
228 		});
229 	}
230 }
231 
232 private struct ConnInfo { string host; ushort port; bool useTLS; string proxyIP; ushort proxyPort; NetworkAddress bind_addr; }
233 private static vibe.utils.array.FixedRingBuffer!(Tuple!(ConnInfo, ConnectionPool!HTTPClient), 16) s_connections;
234 
235 
236 /**************************************************************************************************/
237 /* Public types                                                                                   */
238 /**************************************************************************************************/
239 
240 /**
241 	Defines an HTTP/HTTPS proxy request or a connection timeout for an HTTPClient.
242 */
243 class HTTPClientSettings {
244 	URL proxyURL;
245 	Duration defaultKeepAliveTimeout = 10.seconds;
246 
247 	/** Timeout for establishing a connection to the server
248 
249 		Note that this setting is only supported when using the vibe-core
250 		module. If using one of the legacy drivers, any value other than
251 		`Duration.max` will emit a runtime warning and connects without a
252 		specific timeout.
253 	*/
254 	Duration connectTimeout = Duration.max;
255 
256 	/// Timeout during read operations on the underyling transport
257 	Duration readTimeout = Duration.max;
258 
259 	/// Forces a specific network interface to use for outgoing connections.
260 	NetworkAddress networkInterface = anyAddress;
261 
262 	/// Can be used to force looking up IPv4/IPv6 addresses for host names.
263 	AddressFamily dnsAddressFamily = AddressFamily.UNSPEC;
264 
265 	/** Allows to customize the TLS context before connecting to a server.
266 
267 		Note that this overrides a callback set with `HTTPClient.setTLSContextSetup`.
268 	*/
269 	void delegate(TLSContext ctx) @safe nothrow tlsContextSetup;
270 
271 	@property HTTPClientSettings dup()
272 	const @safe {
273 		auto ret = new HTTPClientSettings;
274 		ret.proxyURL = this.proxyURL;
275 		ret.connectTimeout = this.connectTimeout;
276 		ret.readTimeout = this.readTimeout;
277 		ret.networkInterface = this.networkInterface;
278 		ret.dnsAddressFamily = this.dnsAddressFamily;
279 		ret.tlsContextSetup = this.tlsContextSetup;
280 		return ret;
281 	}
282 }
283 
284 ///
285 unittest {
286 	void test() {
287 
288 		HTTPClientSettings settings = new HTTPClientSettings;
289 		settings.proxyURL = URL.parse("http://proxyuser:proxypass@192.168.2.50:3128");
290 		settings.defaultKeepAliveTimeout = 0.seconds; // closes connection immediately after receiving the data.
291 		requestHTTP("http://www.example.org",
292 					(scope req){
293 			req.method = HTTPMethod.GET;
294 		},
295 		(scope res){
296 			logInfo("Headers:");
297 			foreach(h; res.headers.byKeyValue) {
298 				logInfo("%s: %s", h.key, h.value);
299 			}
300 			logInfo("Response: %s", res.bodyReader.readAllUTF8());
301 		}, settings);
302 
303 	}
304 }
305 
306 version (Have_vibe_core)
307 unittest { // test connect timeout
308 	import std.conv : to;
309 	import vibe.core.stream : pipe, nullSink;
310 
311 	HTTPClientSettings settings = new HTTPClientSettings;
312 	settings.connectTimeout = 50.msecs;
313 
314 	// Use an IP address that is guaranteed to be unassigned globally to force
315 	// a timeout (see RFC 3330)
316 	auto cli = connectHTTP("192.0.2.0", 80, false, settings);
317 	auto timer = setTimer(500.msecs, { assert(false, "Connect timeout occurred too late"); });
318 	scope (exit) timer.stop();
319 
320 	try {
321 		cli.request(
322 			(scope req) { assert(false, "Expected no connection"); },
323 			(scope res) { assert(false, "Expected no response"); }
324 		);
325 		assert(false, "Response read expected to fail due to timeout");
326 	} catch(Exception e) {}
327 }
328 
329 unittest { // test read timeout
330 	import std.conv : to;
331 	import vibe.core.stream : pipe, nullSink;
332 
333 	version (VibeLibasyncDriver) {
334 		logInfo("Skipping HTTP client read timeout test due to buggy libasync driver.");
335 	} else {
336 		HTTPClientSettings settings = new HTTPClientSettings;
337 		settings.readTimeout = 50.msecs;
338 
339 		auto l = listenTCP(0, (conn) {
340 			try conn.pipe(nullSink);
341 			catch (Exception e) assert(false, e.msg);
342 			conn.close();
343 		}, "127.0.0.1");
344 
345 		auto cli = connectHTTP("127.0.0.1", l.bindAddress.port, false, settings);
346 		auto timer = setTimer(500.msecs, { assert(false, "Read timeout occurred too late"); });
347 		scope (exit) {
348 			timer.stop();
349 			l.stopListening();
350 			cli.disconnect();
351 			sleep(10.msecs); // allow the read connection end to fully close
352 		}
353 
354 		try {
355 			cli.request(
356 				(scope req) { req.method = HTTPMethod.GET; },
357 				(scope res) { assert(false, "Expected no response"); }
358 			);
359 			assert(false, "Response read expected to fail due to timeout");
360 		} catch(Exception e) {}
361 	}
362 }
363 
364 
365 /**
366 	Implementation of a HTTP 1.0/1.1 client with keep-alive support.
367 
368 	Note that it is usually recommended to use requestHTTP for making requests as that will use a
369 	pool of HTTPClient instances to keep the number of connection establishments low while not
370 	blocking requests from different tasks.
371 */
372 final class HTTPClient {
373 	@safe:
374 
375 	enum maxHeaderLineLength = 4096;
376 
377 	private {
378 		Rebindable!(const(HTTPClientSettings)) m_settings;
379 		string m_server;
380 		ushort m_port;
381 		bool m_useTLS;
382 		TCPConnection m_conn;
383 		InterfaceProxy!Stream m_stream;
384 		TLSStream m_tlsStream;
385 		TLSContext m_tls;
386 		static __gshared m_userAgent = "vibe.d/"~vibeVersionString~" (HTTPClient, +http://vibed.org/)";
387 		static __gshared void function(TLSContext) ms_tlsSetup;
388 		bool m_requesting = false, m_responding = false;
389 		SysTime m_keepAliveLimit;
390 		Duration m_keepAliveTimeout;
391 	}
392 
393 	/** Get the current settings for the HTTP client. **/
394 	@property const(HTTPClientSettings) settings() const {
395 		return m_settings;
396 	}
397 
398 	/**
399 		Sets the default user agent string for new HTTP requests.
400 	*/
401 	static void setUserAgentString(string str) @trusted { m_userAgent = str; }
402 
403 	/**
404 		Sets a callback that will be called for every TLS context that is created.
405 
406 		Setting such a callback is useful for adjusting the validation parameters
407 		of the TLS context.
408 	*/
409 	static void setTLSSetupCallback(void function(TLSContext) @safe func) @trusted { ms_tlsSetup = func; }
410 
411 	/**
412 		Connects to a specific server.
413 
414 		This method may only be called if any previous connection has been closed.
415 	*/
416 	void connect(string server, ushort port = 80, bool use_tls = false, const(HTTPClientSettings) settings = defaultSettings)
417 	{
418 		assert(!m_conn);
419 		assert(port != 0);
420 		disconnect();
421 		m_conn = TCPConnection.init;
422 		m_settings = settings;
423 		m_keepAliveTimeout = settings.defaultKeepAliveTimeout;
424 		m_keepAliveLimit = Clock.currTime(UTC()) + m_keepAliveTimeout;
425 		m_server = server;
426 		m_port = port;
427 		m_useTLS = use_tls;
428 		if (use_tls) {
429 			m_tls = createTLSContext(TLSContextKind.client);
430 			// this will be changed to trustedCert once a proper root CA store is available by default
431 			m_tls.peerValidationMode = TLSPeerValidationMode.none;
432 			if (settings.tlsContextSetup) settings.tlsContextSetup(m_tls);
433 			else () @trusted { if (ms_tlsSetup) ms_tlsSetup(m_tls); } ();
434 		}
435 	}
436 
437 	/**
438 		Forcefully closes the TCP connection.
439 
440 		Before calling this method, be sure that no request is currently being processed.
441 	*/
442 	void disconnect()
443 	nothrow {
444 		if (m_conn) {
445 			version (Have_vibe_core) {}
446 			else scope(failure) assert(false);
447 
448 			if (m_conn.connected) {
449 				try m_stream.finalize();
450 				catch (Exception e) logDebug("Failed to finalize connection stream when closing HTTP client connection: %s", e.msg);
451 				m_conn.close();
452 			}
453 
454 			if (m_useTLS) () @trusted { return destroy(m_stream); } ();
455 			m_stream = InterfaceProxy!Stream.init;
456 			() @trusted { return destroy(m_conn); } ();
457 			m_conn = TCPConnection.init;
458 		}
459 	}
460 
461 	private void doProxyRequest(T, U)(ref T res, U requester, ref bool close_conn, ref bool has_body)
462 	@trusted { // scope new
463 		import std.conv : to;
464 		import vibe.internal.utilallocator: RegionListAllocator;
465 		version (VibeManualMemoryManagement)
466 			scope request_allocator = new RegionListAllocator!(shared(Mallocator), false)(1024, Mallocator.instance);
467 		else
468 			scope request_allocator = new RegionListAllocator!(shared(GCAllocator), true)(1024, GCAllocator.instance);
469 
470 		res.dropBody();
471 		scope(failure)
472 			res.disconnect();
473 		if (res.statusCode != 407) {
474 			throw new HTTPStatusException(HTTPStatus.internalServerError, "Proxy returned Proxy-Authenticate without a 407 status code.");
475 		}
476 
477 		// send the request again with the proxy authentication information if available
478 		if (m_settings.proxyURL.username is null) {
479 			throw new HTTPStatusException(HTTPStatus.proxyAuthenticationRequired, "Proxy Authentication Required.");
480 		}
481 
482 		m_responding = false;
483 		close_conn = false;
484 		bool found_proxy_auth;
485 
486 		foreach (string proxyAuth; res.headers.getAll("Proxy-Authenticate"))
487 		{
488 			if (proxyAuth.length >= "Basic".length && proxyAuth[0.."Basic".length] == "Basic")
489 			{
490 				found_proxy_auth = true;
491 				break;
492 			}
493 		}
494 
495 		if (!found_proxy_auth)
496 		{
497 			throw new HTTPStatusException(HTTPStatus.notAcceptable, "The Proxy Server didn't allow Basic Authentication");
498 		}
499 
500 		SysTime connected_time;
501 		has_body = doRequestWithRetry(requester, true, close_conn, connected_time);
502 		m_responding = true;
503 
504 		static if (is(T == HTTPClientResponse))
505 			res = new HTTPClientResponse(this, has_body, close_conn, request_allocator, connected_time);
506 		else
507 			res = scoped!HTTPClientResponse(this, has_body, close_conn, request_allocator, connected_time);
508 
509 		if (res.headers.get("Proxy-Authenticate", null) !is null){
510 			res.dropBody();
511 			throw new HTTPStatusException(HTTPStatus.ProxyAuthenticationRequired, "Proxy Authentication Failed.");
512 		}
513 
514 	}
515 
516 	/**
517 		Performs a HTTP request.
518 
519 		`requester` is called first to populate the request with headers and the desired
520 		HTTP method and version. After a response has been received it is then passed
521 		to the caller which can in turn read the reponse body. Any part of the body
522 		that has not been processed will automatically be consumed and dropped.
523 
524 		Note that the `requester` callback might be invoked multiple times in the event
525 		that a request has to be resent due to a connection failure.
526 
527 		Also note that the second form of this method (returning a `HTTPClientResponse`) is
528 		not recommended to use as it may accidentially block a HTTP connection when
529 		only part of the response body was read and also requires a heap allocation
530 		for the response object. The callback based version on the other hand uses
531 		a stack allocation and guarantees that the request has been fully processed
532 		once it has returned.
533 	*/
534 	void request(scope void delegate(scope HTTPClientRequest req) requester, scope void delegate(scope HTTPClientResponse) responder)
535 	@trusted { // scope new
536 		import vibe.internal.utilallocator: RegionListAllocator;
537 		version (VibeManualMemoryManagement)
538 			scope request_allocator = new RegionListAllocator!(shared(Mallocator), false)(1024, Mallocator.instance);
539 		else
540 			scope request_allocator = new RegionListAllocator!(shared(GCAllocator), true)(1024, GCAllocator.instance);
541 
542 		scope (failure) {
543 			m_responding = false;
544 			disconnect();
545 		}
546 
547 		bool close_conn;
548 		SysTime connected_time;
549 		bool has_body = doRequestWithRetry(requester, false, close_conn, connected_time);
550 
551 		m_responding = true;
552 		auto res = scoped!HTTPClientResponse(this, has_body, close_conn, request_allocator, connected_time);
553 
554 		// proxy implementation
555 		if (res.headers.get("Proxy-Authenticate", null) !is null) {
556 			doProxyRequest(res, requester, close_conn, has_body);
557 		}
558 
559 		Exception user_exception;
560 		while (true)
561 		{
562 			try responder(res);
563 			catch (Exception e) {
564 				logDebug("Error while handling response: %s", e.toString().sanitize());
565 				user_exception = e;
566 			}
567 			if (res.statusCode < 200) {
568 				// just an informational status -> read and handle next response
569 				if (m_responding) res.dropBody();
570 				if (m_conn) {
571 					res = scoped!HTTPClientResponse(this, has_body, close_conn, request_allocator, connected_time);
572 					continue;
573 				}
574 			}
575 			if (m_responding) {
576 				logDebug("Failed to handle the complete response of the server - disconnecting.");
577 				res.disconnect();
578 			}
579 			assert(!m_responding, "Still in responding state after finalizing the response!?");
580 
581 			if (user_exception || res.headers.get("Connection") == "close")
582 				disconnect();
583 			break;
584 		}
585 		if (user_exception) throw user_exception;
586 	}
587 
588 	/// ditto
589 	HTTPClientResponse request(scope void delegate(HTTPClientRequest) requester)
590 	{
591 		bool close_conn;
592 		SysTime connected_time;
593 		scope (failure) {
594 			m_responding = false;
595 			disconnect();
596 		}
597 		bool has_body = doRequestWithRetry(requester, false, close_conn, connected_time);
598 		m_responding = true;
599 		auto res = new HTTPClientResponse(this, has_body, close_conn, () @trusted { return vibeThreadAllocator(); } (), connected_time);
600 
601 		// proxy implementation
602 		if (res.headers.get("Proxy-Authenticate", null) !is null) {
603 			doProxyRequest(res, requester, close_conn, has_body);
604 		}
605 
606 		return res;
607 	}
608 
609 	private bool doRequestWithRetry(scope void delegate(HTTPClientRequest req) requester, bool confirmed_proxy_auth /* basic only */, out bool close_conn, out SysTime connected_time)
610 	{
611 		if (m_conn && m_conn.connected && connected_time > m_keepAliveLimit){
612 			logDebug("Disconnected to avoid timeout");
613 			disconnect();
614 		}
615 
616 		// check if this isn't the first request on a connection
617 		bool is_persistent_request = m_conn && m_conn.connected;
618 
619 		// retry the request if the connection gets closed prematurely and this is a persistent request
620 		bool has_body;
621 		foreach (i; 0 .. is_persistent_request ? 2 : 1) {
622 		 	connected_time = Clock.currTime(UTC());
623 
624 			close_conn = false;
625 			has_body = doRequest(requester, close_conn, false, connected_time);
626 
627 			logTrace("HTTP client waiting for response");
628 			if (!m_stream.empty) break;
629 		}
630 		return has_body;
631 	}
632 
633 	private bool doRequest(scope void delegate(HTTPClientRequest req) requester, ref bool close_conn, bool confirmed_proxy_auth = false /* basic only */, SysTime connected_time = Clock.currTime(UTC()))
634 	{
635 		assert(!m_requesting, "Interleaved HTTP client requests detected!");
636 		assert(!m_responding, "Interleaved HTTP client request/response detected!");
637 
638 		m_requesting = true;
639 		scope(exit) m_requesting = false;
640 
641 		if (!m_conn || !m_conn.connected || m_conn.waitForDataEx(0.seconds) == WaitForDataStatus.noMoreData) {
642 			if (m_conn)
643 				disconnect(); // make sure all resources are freed
644 
645 			if (m_settings.proxyURL.host !is null){
646 
647 				enum AddressType {
648 					IPv4,
649 					IPv6,
650 					Host
651 				}
652 
653 				static AddressType getAddressType(string host){
654 					import std.regex : regex, Captures, Regex, matchFirst;
655 
656 					static IPv4Regex = regex(`^\s*((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\s*$`, ``);
657 					static IPv6Regex = regex(`^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$`, ``);
658 
659 					if (!matchFirst(host, IPv4Regex).empty)
660 					{
661 						return AddressType.IPv4;
662 					}
663 					else if (!matchFirst(host, IPv6Regex).empty)
664 					{
665 						return AddressType.IPv6;
666 					}
667 					else
668 					{
669 						return AddressType.Host;
670 					}
671 				}
672 
673 				import std.functional : memoize;
674 				alias findAddressType = memoize!getAddressType;
675 
676 				bool use_dns;
677 				if (() @trusted { return findAddressType(m_settings.proxyURL.host); } () == AddressType.Host)
678 				{
679 					use_dns = true;
680 				}
681 
682 				NetworkAddress proxyAddr = resolveHost(m_settings.proxyURL.host, m_settings.dnsAddressFamily, use_dns,
683 					m_settings.connectTimeout);
684 				proxyAddr.port = m_settings.proxyURL.port;
685 				m_conn = connectTCPWithTimeout(proxyAddr, m_settings.networkInterface, m_settings.connectTimeout);
686 			}
687 			else {
688 				version(UnixSocket)
689 				{
690 					import core.sys.posix.sys.un;
691 					import core.sys.posix.sys.socket;
692 					import std.regex : regex, Captures, Regex, matchFirst, ctRegex;
693 					import core.stdc..string : strcpy;
694 
695 					NetworkAddress addr;
696 					if (m_server[0] == '/')
697 					{
698 						addr.family = AF_UNIX;
699 						sockaddr_un* s = addr.sockAddrUnix();
700 						enforce(s.sun_path.length > m_server.length, "Unix sockets cannot have that long a name.");
701 						s.sun_family = AF_UNIX;
702 						() @trusted { strcpy(cast(char*)s.sun_path.ptr,m_server.toStringz()); } ();
703 					} else
704 					{
705 						addr = resolveHost(m_server, m_settings.dnsAddressFamily, true, m_settings.connectTimeout);
706 						addr.port = m_port;
707 					}
708 					m_conn = connectTCPWithTimeout(addr, m_settings.networkInterface, m_settings.connectTimeout);
709 				} else
710 				{
711 					auto addr = resolveHost(m_server, m_settings.dnsAddressFamily, true, m_settings.connectTimeout);
712 					addr.port = m_port;
713 					m_conn = connectTCPWithTimeout(addr, m_settings.networkInterface, m_settings.connectTimeout);
714 				}
715 			}
716 
717 			if (m_settings.readTimeout != Duration.max)
718 				m_conn.readTimeout = m_settings.readTimeout;
719 
720 			m_stream = m_conn;
721 			if (m_useTLS) {
722 				try m_tlsStream = createTLSStream(m_conn, m_tls, TLSStreamState.connecting, m_server, m_conn.remoteAddress);
723 				catch (Exception e) {
724 					m_conn.close();
725 					m_conn = TCPConnection.init;
726 					throw e;
727 				}
728 				m_stream = m_tlsStream;
729 			}
730 		}
731 
732 		return () @trusted { // scoped
733 			auto req = scoped!HTTPClientRequest(m_stream, m_conn);
734 			if (m_useTLS)
735 				req.m_peerCertificate = m_tlsStream.peerCertificate;
736 
737 			req.headers["User-Agent"] = m_userAgent;
738 			if (m_settings.proxyURL.host !is null){
739 				req.headers["Proxy-Connection"] = "keep-alive";
740 				if (confirmed_proxy_auth)
741 				{
742 					import std.base64;
743 					ubyte[] user_pass = cast(ubyte[])(m_settings.proxyURL.username ~ ":" ~ m_settings.proxyURL.password);
744 
745 					req.headers["Proxy-Authorization"] = "Basic " ~ cast(string) Base64.encode(user_pass);
746 				}
747 			}
748 			else {
749 				req.headers["Connection"] = "keep-alive";
750 			}
751 			req.headers["Accept-Encoding"] = "gzip, deflate";
752 			req.headers["Host"] = m_server;
753 			requester(req);
754 
755 			if (req.httpVersion == HTTPVersion.HTTP_1_0)
756 				close_conn = true;
757 			else  if (m_settings.proxyURL.host !is null)
758 				close_conn = req.headers.get("Proxy-Connection", "keep-alive") != "keep-alive";
759 			else
760 				close_conn = req.headers.get("Connection", "keep-alive") != "keep-alive";
761 
762 			req.finalize();
763 
764 			return req.method != HTTPMethod.HEAD;
765 		} ();
766 	}
767 }
768 
769 private auto connectTCPWithTimeout(NetworkAddress addr, NetworkAddress bind_address, Duration timeout)
770 {
771 	version (Have_vibe_core) {
772 		return connectTCP(addr, bind_address, timeout);
773 	} else {
774 		if (timeout != Duration.max)
775 			logWarn("HTTP client connect timeout is set, but not supported by the legacy vibe-d:core module.");
776 		return connectTCP(addr, bind_address);
777 	}
778 }
779 
780 /**
781 	Represents a HTTP client request (as sent to the server).
782 */
783 final class HTTPClientRequest : HTTPRequest {
784 	private {
785 		InterfaceProxy!OutputStream m_bodyWriter;
786 		FreeListRef!ChunkedOutputStream m_chunkedStream;
787 		bool m_headerWritten = false;
788 		FixedAppender!(string, 22) m_contentLengthBuffer;
789 		TCPConnection m_rawConn;
790 		TLSCertificateInformation m_peerCertificate;
791 	}
792 
793 
794 	/// private
795 	this(InterfaceProxy!Stream conn, TCPConnection raw_conn)
796 	{
797 		super(conn);
798 		m_rawConn = raw_conn;
799 	}
800 
801 	@property NetworkAddress localAddress() const { return m_rawConn.localAddress; }
802 	@property NetworkAddress remoteAddress() const { return m_rawConn.remoteAddress; }
803 
804 	@property ref inout(TLSCertificateInformation) peerCertificate() inout { return m_peerCertificate; }
805 
806 	/**
807 		Accesses the Content-Length header of the request.
808 
809 		Negative values correspond to an unset Content-Length header.
810 	*/
811 	@property long contentLength() const { return headers.get("Content-Length", "-1").to!long(); }
812 	/// ditto
813 	@property void contentLength(long value)
814 	{
815 		if (value >= 0) headers["Content-Length"] = clengthString(value);
816 		else if ("Content-Length" in headers) headers.remove("Content-Length");
817 	}
818 
819 	/**
820 		Writes the whole request body at once using raw bytes.
821 	*/
822 	void writeBody(RandomAccessStream data)
823 	{
824 		writeBody(data, data.size - data.tell());
825 	}
826 	/// ditto
827 	void writeBody(InputStream data)
828 	{
829 		data.pipe(bodyWriter);
830 		finalize();
831 	}
832 	/// ditto
833 	void writeBody(InputStream data, ulong length)
834 	{
835 		headers["Content-Length"] = clengthString(length);
836 		data.pipe(bodyWriter, length);
837 		finalize();
838 	}
839 	/// ditto
840 	void writeBody(in ubyte[] data, string content_type = null)
841 	{
842 		if( content_type != "" ) headers["Content-Type"] = content_type;
843 		headers["Content-Length"] = clengthString(data.length);
844 		bodyWriter.write(data);
845 		finalize();
846 	}
847 
848 	/**
849 		Writes the request body as JSON data.
850 	*/
851 	void writeJsonBody(T)(T data, bool allow_chunked = false)
852 	{
853 		import vibe.stream.wrapper : streamOutputRange;
854 
855 		headers["Content-Type"] = "application/json";
856 
857 		// set an explicit content-length field if chunked encoding is not allowed
858 		if (!allow_chunked) {
859 			import vibe.internal.rangeutil;
860 			long length = 0;
861 			auto counter = () @trusted { return RangeCounter(&length); } ();
862 			() @trusted { serializeToJson(counter, data); } ();
863 			headers["Content-Length"] = clengthString(length);
864 		}
865 
866 		auto rng = streamOutputRange!1024(bodyWriter);
867 		() @trusted { serializeToJson(&rng, data); } ();
868 		rng.flush();
869 		finalize();
870 	}
871 
872 	/** Writes the request body as form data.
873 	*/
874 	void writeFormBody(T)(T key_value_map)
875 	{
876 		import vibe.inet.webform : formEncode;
877 		import vibe.stream.wrapper : streamOutputRange;
878 
879 		import vibe.internal.rangeutil;
880 		long length = 0;
881 		auto counter = () @trusted { return RangeCounter(&length); } ();
882 		counter.formEncode(key_value_map);
883 		headers["Content-Length"] = clengthString(length);
884 		headers["Content-Type"] = "application/x-www-form-urlencoded";
885 		auto dst = streamOutputRange!1024(bodyWriter);
886 		() @trusted { return &dst; } ().formEncode(key_value_map);
887 	}
888 
889 	///
890 	unittest {
891 		void test(HTTPClientRequest req) {
892 			req.writeFormBody(["foo": "bar"]);
893 		}
894 	}
895 
896 	void writePart(MultiPart part)
897 	{
898 		assert(false, "TODO");
899 	}
900 
901 	/**
902 		An output stream suitable for writing the request body.
903 
904 		The first retrieval will cause the request header to be written, make sure
905 		that all headers are set up in advance.s
906 	*/
907 	@property InterfaceProxy!OutputStream bodyWriter()
908 	{
909 		if (m_bodyWriter) return m_bodyWriter;
910 
911 		assert(!m_headerWritten, "Trying to write request body after body was already written.");
912 
913 		if (httpVersion != HTTPVersion.HTTP_1_0
914 			&& "Content-Length" !in headers && "Transfer-Encoding" !in headers
915 			&& headers.get("Connection", "") != "close")
916 		{
917 			headers["Transfer-Encoding"] = "chunked";
918 		}
919 
920 		writeHeader();
921 		m_bodyWriter = m_conn;
922 
923 		if (headers.get("Transfer-Encoding", null) == "chunked") {
924 			m_chunkedStream = createChunkedOutputStreamFL(m_bodyWriter);
925 			m_bodyWriter = m_chunkedStream;
926 		}
927 
928 		return m_bodyWriter;
929 	}
930 
931 	private void writeHeader()
932 	{
933 		import vibe.stream.wrapper;
934 
935 		assert(!m_headerWritten, "HTTPClient tried to write headers twice.");
936 		m_headerWritten = true;
937 
938 		auto output = streamOutputRange!1024(m_conn);
939 
940 		formattedWrite(() @trusted { return &output; } (), "%s %s %s\r\n", httpMethodString(method), requestURL, getHTTPVersionString(httpVersion));
941 		logTrace("--------------------");
942 		logTrace("HTTP client request:");
943 		logTrace("--------------------");
944 		logTrace("%s", this);
945 		foreach (k, v; headers.byKeyValue) {
946 			() @trusted { formattedWrite(&output, "%s: %s\r\n", k, v); } ();
947 			logTrace("%s: %s", k, v);
948 		}
949 		output.put("\r\n");
950 		logTrace("--------------------");
951 	}
952 
953 	private void finalize()
954 	{
955 		// test if already finalized
956 		if (m_headerWritten && !m_bodyWriter)
957 			return;
958 
959 		// force the request to be sent
960 		if (!m_headerWritten) writeHeader();
961 		else {
962 			bodyWriter.flush();
963 			if (m_chunkedStream) {
964 				m_bodyWriter.finalize();
965 				m_conn.flush();
966 			}
967 			m_bodyWriter = typeof(m_bodyWriter).init;
968 			m_conn = typeof(m_conn).init;
969 		}
970 	}
971 
972 	private string clengthString(ulong len)
973 	{
974 		m_contentLengthBuffer.clear();
975 		() @trusted { formattedWrite(&m_contentLengthBuffer, "%s", len); } ();
976 		return () @trusted { return m_contentLengthBuffer.data; } ();
977 	}
978 }
979 
980 
981 /**
982 	Represents a HTTP client response (as received from the server).
983 */
984 final class HTTPClientResponse : HTTPResponse {
985 	@safe:
986 
987 	private {
988 		HTTPClient m_client;
989 		LockedConnection!HTTPClient lockedConnection;
990 		FreeListRef!LimitedInputStream m_limitedInputStream;
991 		FreeListRef!ChunkedInputStream m_chunkedInputStream;
992 		FreeListRef!ZlibInputStream m_zlibInputStream;
993 		FreeListRef!EndCallbackInputStream m_endCallback;
994 		InterfaceProxy!InputStream m_bodyReader;
995 		bool m_closeConn;
996 		int m_maxRequests;
997 	}
998 
999 	/// Contains the keep-alive 'max' parameter, indicates how many requests a client can
1000 	/// make before the server closes the connection.
1001 	@property int maxRequests() const {
1002 		return m_maxRequests;
1003 	}
1004 
1005 
1006 	/// All cookies that shall be set on the client for this request
1007 	override @property ref DictionaryList!Cookie cookies() {
1008 		if ("Set-Cookie" in this.headers && m_cookies.length == 0) {
1009 			foreach (cookieString; this.headers.getAll("Set-Cookie")) {
1010 				auto cookie = parseHTTPCookie(cookieString);
1011 				if (cookie[0].length)
1012 					m_cookies[cookie[0]] = cookie[1];
1013 			}
1014 		}
1015 		return m_cookies;
1016 	}
1017 
1018 	/// private
1019 	this(HTTPClient client, bool has_body, bool close_conn, IAllocator alloc, SysTime connected_time = Clock.currTime(UTC()))
1020 	{
1021 		m_client = client;
1022 		m_closeConn = close_conn;
1023 
1024 		scope(failure) finalize(true);
1025 
1026 		// read and parse status line ("HTTP/#.# #[ $]\r\n")
1027 		logTrace("HTTP client reading status line");
1028 		string stln = () @trusted { return cast(string)client.m_stream.readLine(HTTPClient.maxHeaderLineLength, "\r\n", alloc); } ();
1029 		logTrace("stln: %s", stln);
1030 		this.httpVersion = parseHTTPVersion(stln);
1031 
1032 		enforce(stln.startsWith(" "));
1033 		stln = stln[1 .. $];
1034 		this.statusCode = parse!int(stln);
1035 		if( stln.length > 0 ){
1036 			enforce(stln.startsWith(" "));
1037 			stln = stln[1 .. $];
1038 			this.statusPhrase = stln;
1039 		}
1040 
1041 		// read headers until an empty line is hit
1042 		parseRFC5322Header(client.m_stream, this.headers, HTTPClient.maxHeaderLineLength, alloc, false);
1043 
1044 		logTrace("---------------------");
1045 		logTrace("HTTP client response:");
1046 		logTrace("---------------------");
1047 		logTrace("%s", this);
1048 		foreach (k, v; this.headers.byKeyValue)
1049 			logTrace("%s: %s", k, v);
1050 		logTrace("---------------------");
1051 		Duration server_timeout;
1052 		bool has_server_timeout;
1053 		if (auto pka = "Keep-Alive" in this.headers) {
1054 			foreach(s; splitter(*pka, ',')){
1055 				auto pair = s.splitter('=');
1056 				auto name = pair.front.strip();
1057 				pair.popFront();
1058 				if (icmp(name, "timeout") == 0) {
1059 					has_server_timeout = true;
1060 					server_timeout = pair.front.to!int().seconds;
1061 				} else if (icmp(name, "max") == 0) {
1062 					m_maxRequests = pair.front.to!int();
1063 				}
1064 			}
1065 		}
1066 		Duration elapsed = Clock.currTime(UTC()) - connected_time;
1067 		if (this.headers.get("Connection") == "close") {
1068 			// this header will trigger m_client.disconnect() in m_client.doRequest() when it goes out of scope
1069 		} else if (has_server_timeout && m_client.m_keepAliveTimeout > server_timeout) {
1070 			m_client.m_keepAliveLimit = Clock.currTime(UTC()) + server_timeout - elapsed;
1071 		} else if (this.httpVersion == HTTPVersion.HTTP_1_1) {
1072 			m_client.m_keepAliveLimit = Clock.currTime(UTC()) + m_client.m_keepAliveTimeout;
1073 		}
1074 
1075 		if (!has_body) finalize();
1076 	}
1077 
1078 	~this()
1079 	{
1080 		debug if (m_client) {
1081 			import core.stdc.stdio;
1082 			printf("WARNING: HTTPClientResponse not fully processed before being finalized\n");
1083 		}
1084 	}
1085 
1086 	/**
1087 		An input stream suitable for reading the response body.
1088 	*/
1089 	@property InterfaceProxy!InputStream bodyReader()
1090 	{
1091 		if( m_bodyReader ) return m_bodyReader;
1092 
1093 		assert (m_client, "Response was already read or no response body, may not use bodyReader.");
1094 
1095 		// prepare body the reader
1096 		if (auto pte = "Transfer-Encoding" in this.headers) {
1097 			enforce(*pte == "chunked");
1098 			m_chunkedInputStream = createChunkedInputStreamFL(m_client.m_stream);
1099 			m_bodyReader = this.m_chunkedInputStream;
1100 		} else if (auto pcl = "Content-Length" in this.headers) {
1101 			m_limitedInputStream = createLimitedInputStreamFL(m_client.m_stream, to!ulong(*pcl));
1102 			m_bodyReader = m_limitedInputStream;
1103 		} else if (isKeepAliveResponse) {
1104 			m_limitedInputStream = createLimitedInputStreamFL(m_client.m_stream, 0);
1105 			m_bodyReader = m_limitedInputStream;
1106 		} else {
1107 			m_bodyReader = m_client.m_stream;
1108 		}
1109 
1110 		if( auto pce = "Content-Encoding" in this.headers ){
1111 			if( *pce == "deflate" ){
1112 				m_zlibInputStream = createDeflateInputStreamFL(m_bodyReader);
1113 				m_bodyReader = m_zlibInputStream;
1114 			} else if( *pce == "gzip" || *pce == "x-gzip"){
1115 				m_zlibInputStream = createGzipInputStreamFL(m_bodyReader);
1116 				m_bodyReader = m_zlibInputStream;
1117 			}
1118 			else enforce(*pce == "identity" || *pce == "", "Unsuported content encoding: "~*pce);
1119 		}
1120 
1121 		// be sure to free resouces as soon as the response has been read
1122 		m_endCallback = createEndCallbackInputStreamFL(m_bodyReader, &this.finalize);
1123 		m_bodyReader = m_endCallback;
1124 
1125 		return m_bodyReader;
1126 	}
1127 
1128 	/**
1129 		Provides unsafe means to read raw data from the connection.
1130 
1131 		No transfer decoding and no content decoding is done on the data.
1132 
1133 		Not that the provided delegate must read the whole stream,
1134 		as the state of the response is unknown after raw bytes have been
1135 		taken. Failure to read the right amount of data will lead to
1136 		protocol corruption in later requests.
1137 	*/
1138 	void readRawBody(scope void delegate(scope InterfaceProxy!InputStream stream) @safe del)
1139 	{
1140 		assert(!m_bodyReader, "May not mix use of readRawBody and bodyReader.");
1141 		del(interfaceProxy!InputStream(m_client.m_stream));
1142 		finalize();
1143 	}
1144 	/// ditto
1145 	static if (!is(InputStream == InterfaceProxy!InputStream))
1146 	void readRawBody(scope void delegate(scope InputStream stream) @safe del)
1147 	{
1148 		import vibe.internal.interfaceproxy : asInterface;
1149 
1150 		assert(!m_bodyReader, "May not mix use of readRawBody and bodyReader.");
1151 		del(m_client.m_stream.asInterface!(.InputStream));
1152 		finalize();
1153 	}
1154 
1155 	/**
1156 		Reads the whole response body and tries to parse it as JSON.
1157 	*/
1158 	Json readJson(){
1159 		auto bdy = bodyReader.readAllUTF8();
1160 		return () @trusted { return parseJson(bdy); } ();
1161 	}
1162 
1163 	/**
1164 		Reads and discards the response body.
1165 	*/
1166 	void dropBody()
1167 	{
1168 		if (m_client) {
1169 			if( bodyReader.empty ){
1170 				finalize();
1171 			} else {
1172 				bodyReader.pipe(nullSink);
1173 				assert(!lockedConnection.__conn);
1174 			}
1175 		}
1176 	}
1177 
1178 	/**
1179 		Forcefully terminates the connection regardless of the current state.
1180 
1181 		Note that this will only actually disconnect if the request has not yet
1182 		been fully processed. If the whole body was already read, the
1183 		connection is not owned by the current request operation anymore and
1184 		cannot be accessed. Use a "Connection: close" header instead in this
1185 		case to let the server close the connection.
1186 	*/
1187 	void disconnect()
1188 	{
1189 		finalize(true);
1190 	}
1191 
1192 	/**
1193 		Switches the connection to a new protocol and returns the resulting ConnectionStream.
1194 
1195 		The caller caller gets ownership of the ConnectionStream and is responsible
1196 		for closing it.
1197 
1198 		Notice:
1199 			When using the overload that returns a `ConnectionStream`, the caller
1200 			must make sure that the stream is not used after the
1201 			`HTTPClientRequest` has been destroyed.
1202 
1203 		Params:
1204 			new_protocol = The protocol to which the connection is expected to
1205 				upgrade. Should match the Upgrade header of the request. If an
1206 				empty string is passed, the "Upgrade" header will be ignored and
1207 				should be checked by other means.
1208 	*/
1209 	ConnectionStream switchProtocol(string new_protocol)
1210 	{
1211 		enforce(statusCode == HTTPStatus.switchingProtocols, "Server did not send a 101 - Switching Protocols response");
1212 		string *resNewProto = "Upgrade" in headers;
1213 		enforce(resNewProto, "Server did not send an Upgrade header");
1214 		enforce(!new_protocol.length || !icmp(*resNewProto, new_protocol),
1215 			"Expected Upgrade: " ~ new_protocol ~", received Upgrade: " ~ *resNewProto);
1216 		auto stream = createConnectionProxyStream!(typeof(m_client.m_stream), typeof(m_client.m_conn))(m_client.m_stream, m_client.m_conn);
1217 		m_closeConn = true; // cannot reuse connection for further requests!
1218 		return stream;
1219 	}
1220 	/// ditto
1221 	void switchProtocol(string new_protocol, scope void delegate(ConnectionStream str) @safe del)
1222 	{
1223 		enforce(statusCode == HTTPStatus.switchingProtocols, "Server did not send a 101 - Switching Protocols response");
1224 		string *resNewProto = "Upgrade" in headers;
1225 		enforce(resNewProto, "Server did not send an Upgrade header");
1226 		enforce(!new_protocol.length || !icmp(*resNewProto, new_protocol),
1227 			"Expected Upgrade: " ~ new_protocol ~", received Upgrade: " ~ *resNewProto);
1228 		auto stream = createConnectionProxyStream(m_client.m_stream, m_client.m_conn);
1229 		scope (exit) () @trusted { destroy(stream); } ();
1230 		m_closeConn = true;
1231 		del(stream);
1232 	}
1233 
1234 	private @property isKeepAliveResponse()
1235 	const {
1236 		string conn;
1237 		if (this.httpVersion == HTTPVersion.HTTP_1_0) {
1238 			// Workaround for non-standard-conformant servers - for example see #1780
1239 			auto pcl = "Content-Length" in this.headers;
1240 			if (pcl) conn = this.headers.get("Connection", "close");
1241 			else return false; // can't use keepalive when no content length is set
1242 		}
1243 		else conn = this.headers.get("Connection", "keep-alive");
1244 		return icmp(conn, "close") != 0;
1245 	}
1246 
1247 	private void finalize()
1248 	{
1249 		finalize(m_closeConn);
1250 	}
1251 
1252 	private void finalize(bool disconnect)
1253 	{
1254 		// ignore duplicate and too early calls to finalize
1255 		// (too early happesn for empty response bodies)
1256 		if (!m_client) return;
1257 
1258 		auto cli = m_client;
1259 		m_client = null;
1260 		cli.m_responding = false;
1261 		destroy(m_zlibInputStream);
1262 		destroy(m_chunkedInputStream);
1263 		destroy(m_limitedInputStream);
1264 		if (disconnect) cli.disconnect();
1265 		destroy(lockedConnection);
1266 	}
1267 }
1268 
1269 /** Returns clean host string. In case of unix socket it performs urlDecode on host. */
1270 package auto getFilteredHost(URL url)
1271 {
1272 	version(UnixSocket)
1273 	{
1274 		import vibe.textfilter.urlencode : urlDecode;
1275 		if (url.schema == "https+unix" || url.schema == "http+unix")
1276 			return urlDecode(url.host);
1277 		else
1278 			return url.host;
1279 	} else
1280 		return url.host;
1281 }
1282 
1283 // This object is a placeholder and should to never be modified.
1284 package @property const(HTTPClientSettings) defaultSettings()
1285 @trusted {
1286 	__gshared HTTPClientSettings ret = new HTTPClientSettings;
1287 	return ret;
1288 }