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