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