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