<?
	#
	# $Id$
	#

	########################################################################

	$GLOBALS['timings']['http_count']	= 0;
	$GLOBALS['timings']['http_time']	= 0;
	$GLOBALS['timing_keys']['http'] = 'HTTP Requests';

	########################################################################

	function http_head($url, $headers=array(), $more=array()){
		$more['head'] = 1;
		return http_get($url, $headers, $more);
	}

	########################################################################

	function http_get($url, $headers=array(), $more=array()){

		$ch = _http_curl_handle($url, $headers, $more);

		if ($more['head']){
			curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'HEAD');
			curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
		}

		if ($more['return_curl_handle']){
			return $ch;
		}

		return _http_request($ch, $url, $more);
	}

	########################################################################

	function http_post($url, $post_fields, $headers=array(), $more=array()){

		$ch = _http_curl_handle($url, $headers, $more);

		curl_setopt($ch, CURLOPT_POST, true);
		curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);

		if ($more['return_curl_handle']){
			return $ch;
		}

		return _http_request($ch, $url, $more);
	}

	########################################################################

	function http_multi(&$requests){

		$handles = array();
		$responses = array();

		foreach ($requests as $req){

			$url = $req['url'];

			$method = (isset($req['method'])) ? strtoupper($req['method']) : 'GET';
			$headers = (is_array($req['headers'])) ? $req['headers'] : array();
			$more = (is_array($req['more'])) ? $req['more'] : array();

			$more['return_curl_handle'] = 1;

			if ($method == 'HEAD'){
				$ch = http_head($url, $headers, $more);
			}

			else if ($method == 'POST'){
				$ch = http_post($url, $headers, $more);
			}

			else if ($method == 'GET'){
				$ch = http_get($url, $headers, $more);
			}

			else {
				log_warning("http", "unsupported HTTP method : {$method}");
				continue;
			}

			$handles[] = $ch;
		}

		# http://us.php.net/manual/en/function.curl-multi-init.php

		$mh = curl_multi_init();

		foreach ($handles as $ch){
			curl_multi_add_handle($mh, $ch);
		}

		$active = null;
		$start = microtime_ms();

		# this syntax makes my eyes bleed but whatever...

		do {
			$mrc = curl_multi_exec($mh, $active);
		} while ($mrc == CURLM_CALL_MULTI_PERFORM);

		while ($active && $mrc == CURLM_OK){
			if (curl_multi_select($mh) != -1){
				do {
					$mrc = curl_multi_exec($mh, $active);
				} while ($mrc == CURLM_CALL_MULTI_PERFORM);
			}
		}

		$end = microtime_ms();

		$GLOBALS['timings']['http_count'] += count($handlers);
		$GLOBALS['timings']['http_time'] += $end-$start;

		foreach ($handles as $ch){

			$raw = curl_multi_getcontent($ch);
			$info = curl_getinfo($ch);

			curl_multi_remove_handle($mh, $ch);

			$rsp = _http_parse_response($raw, $info);
			$responses[] = $rsp;
		}

		curl_multi_close($mh);

		return $responses;
	}

	########################################################################

	# returns a plain vanilla curl handler with basic/common options set

	function _http_curl_handle($url, $headers=array(), $more=array()){

		$defaults = array(
			'http_timeout' => $GLOBALS['cfg']['http_timeout'],
		);

		$more = array_merge($defaults, $more);

		$headers_prepped = _http_prepare_outgoing_headers($headers);

		$ch = curl_init();

		curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
		curl_setopt($ch, CURLOPT_HTTPHEADER, $headers_prepped);
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_TIMEOUT, $more['http_timeout']);
		curl_setopt($ch, CURLINFO_HEADER_OUT, true);
		curl_setopt($ch, CURLOPT_HEADER, true);

		if ($more['http_port']){
			curl_setopt($ch, CURLOPT_PORT, $more['http_port']);
		}

		if ($more['follow_redirects']){
			curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
			curl_setopt($ch, CURLOPT_MAXREDIRS, intval($more['follow_redirects']));
		}

		return $ch;
	}

	########################################################################

	function _http_request($ch, $url, $more=array()){

		$start = microtime_ms();

		$raw = curl_exec($ch);
		$info = curl_getinfo($ch);

		$end = microtime_ms();

		curl_close($ch);

		$GLOBALS['timings']['http_count']++;
		$GLOBALS['timings']['http_time'] += $end-$start;

		return _http_parse_response($raw, $info);
	}

	########################################################################

	function _http_parse_response($raw, $info){

		list($head, $body) = explode("\r\n\r\n", $raw, 2);
		list($head_out, $body_out) = explode("\r\n\r\n", $info['request_header'], 2);
		unset($info['request_header']);

		$headers_in = http_parse_headers($head, '_status');
		$headers_out = http_parse_headers($head_out, '_request');

		preg_match("/^([A-Z]+)\s/", $headers_out['_request'], $m);
		$method = $m[1];

		# log_notice("http", "{$method} {$url}", $end-$start);

		# http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2
		# http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_Success (note HTTP 207 WTF)

		$status = $info['http_code'];

		if (($status < 200) || ($status > 299)){

			return array(
				'ok'		=> 0,
				'error'	=> 'http_failed',
				'code'		=> $info['http_code'],
				'method'	=> $method,
				'url'		=> $info['url'],
				'info'		=> $info,
				'req_headers'	=> $headers_out,
				'headers'	=> $headers_in,
				'body'		=> $body,
			);
		}

		return array(
			'ok'		=> 1,
			'url'		=> $info['url'],
			'info'		=> $info,
			'method'	=> $method,
			'req_headers'	=> $headers_out,
			'headers'	=> $headers_in,
			'body'		=> $body,
		);
	}

	########################################################################

	function http_parse_headers($raw, $first){

		#
		# first, deal with folded lines
		#

		$raw_lines = explode("\r\n", $raw);

		$lines = array();
		$lines[] = array_shift($raw_lines);

		foreach ($raw_lines as $line){
			if (preg_match("!^[ \t]!", $line)){
				$lines[count($lines)-1] .= ' '.trim($line);
			}else{
				$lines[] = trim($line);
			}
		}


		#
		# now split them out
		#

		$out = array(
			$first => array_shift($lines),
		);

		foreach ($lines as $line){
			list($k, $v) = explode(':', $line, 2);
			$out[StrToLower($k)] = trim($v);
		}

		return $out;
	}

	########################################################################

	function _http_prepare_outgoing_headers($headers=array()){

		$prepped = array();

		if (! isset($headers['Expect'])){
			$headers['Expect'] = '';	# Get around error 417
		}

		foreach ($headers as $key => $value){
			$prepped[] = "{$key}: {$value}";
		}

		return $prepped;
	}

	########################################################################
?>
