public static function http_client(array $kwargs) {
		$url = $method = null;
		$headers = $get = $post = $custom_opts = [];
		$expect_json = false;
		extract(self::extract_kwargs($kwargs, [
			'url' => null,
			'method' => 'GET',
			'headers'=> [],
			'get' => [],
			'post' => [],
			'custom_opts' => [],
			'expect_json' => false,
		]));

		if (!$url)
			throw new CommonError("URL not set.");

		$opts = [
			CURLOPT_RETURNTRANSFER => true,
			CURLOPT_SSL_VERIFYPEER => true,
			CURLOPT_CONNECTTIMEOUT => 16,
			CURLOPT_TIMEOUT        => 16,
			CURLOPT_FOLLOWLOCATION => true,
			CURLOPT_MAXREDIRS      => 8,
			CURLOPT_HEADER         => false,
		];
		foreach ($custom_opts as $key => $val)
			$opts[$key] = $val;

		$conn = curl_init();
		foreach ($opts as $key => $val)
			curl_setopt($conn, $key, $val);

		if ($headers)
			curl_setopt($conn, CURLOPT_HTTPHEADER, $headers);

		if ($get) {
			$url .= strpos($url, '?') !== false ? '&' : '?';
			$url .= http_build_query($get);
		}
		curl_setopt($conn, CURLOPT_URL, $url);

		if (is_array($post))
			$post = http_build_query($post);

		switch ($method) {
			case 'GET':
				break;
			case 'HEAD':
			case 'OPTIONS':
				curl_setopt($conn, CURLOPT_NOBODY, true);
				curl_setopt($conn, CURLOPT_HEADER, true);
				break;
			case 'POST':
			case 'PUT':
			case 'DELETE':
			case 'PATCH':
			case 'TRACE':
				curl_setopt($conn, CURLOPT_CUSTOMREQUEST, $method);
				curl_setopt($conn, CURLOPT_POSTFIELDS, $post);
				break;
			default:
				# CONNECT etc. are not supported ... yet?
				return [-1, null];
		}

		$body = curl_exec($conn);
		$info = curl_getinfo($conn);
		curl_close($conn);

		if (in_array($method, ['HEAD', 'OPTIONS']))
			return [$info['http_code'], $body];
		if ($expect_json)
			$body = @json_decode($body, true);
		return [$info['http_code'], $body];
	}