<?php
/**
 * *************************************************************************
 *
 * Copyright (c) 2014 Baidu.com, Inc. All Rights Reserved
 *
 * ************************************************************************
 */
/**
 *
 * Packing the cUrl and make it easy
 *
 * @file HttpRequest.php
 * @encoding UTF-8
 * 
 * 
 *         @date 2014年12月25日
 *        
 */

require_once(PUSH_SDK_HOME.'/lib/PushSimpleLog.php');
require_once(PUSH_SDK_HOME.'/lib/PushException.php');

class HttpRequest {
    const HTTP_GET = 'GET';
    const HTTP_POST = 'POST';
    /**
     * @var PushSimpleLog
     */
    private $log = null;
    private $urlBase;
    private static $defaultOpts = array (
        
        // default use the http version 1.0 , To prevent on the MAC platform using post speed too slow
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,
        CURLOPT_FAILONERROR => false,
        CURLOPT_HEADER => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 10,
        CURLOPT_HTTPHEADER => array (
             // server端 ,目前唯一支持编码格式,即为utf-8;
            'Content-type: application/x-www-form-urlencoded;charset=utf-8', 
        ),
    );
    
    private $currentOpts = array();
    /**
     * Constructor
     *
     * @param string $urlBase
     *        url基础路径
     * @param array $opts
     *        cUrl options
     * @param PushSimpleLog $log
     * @throws Exception
     */
    function __construct($urlBase = null, $opts = array(), $log = null) {
        
        // 如果未指定, 则创建一个被disable掉的log对象;
        $this -> log = $log === null ? new PushSimpleLog() : $log;
        
        if (! is_callable('curl_version')) {
            throw new Exception("php extension [cUrl] is not exists!!");
        }
        
        if ($urlBase === null) {
            throw new HttpException('URL must be a string');
        }
        
        if (preg_match('/^http.*/i', $urlBase) === 0) {
            throw new HttpException('URL is not correct');
        }
        
        if (strrpos($urlBase, '/') != strlen($urlBase) - 1) {
            $urlBase .= '/';
        }
        
        $this -> urlBase = $urlBase;
        
        if(defined(CURLOPT_IPRESOLVE)){
            self::$defaultOpts[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
        }
        
        $this -> currentOpts = self::$defaultOpts;
        
        if (is_array($opts)) {
            foreach ( $opts as $k => $v ) {
                $this -> currentOpts [$k] = $v;
            }
        }
    }
    
    /**
     * 判断一个字符串是否为以http://开头
     *
     * @param string $url
     * @return boolean
     */
    public function isUrlFormat($url){
        return $this->isMatchReg('/^http:\/\/.*/i',$url);
    }
    
    /**
     * 判断一个字符串,是否符合给写的格式
     *
     * @param string $match 正则表达式格式字符串  PREG库风格
     * @param string $str  将验证的字符串
     * @return boolean
     */
    function isMatchReg($match,$str){
        return preg_match($match,$str) > 0;
    }
    /**
     * 以标准uri风格编码一个array<K,V>
     * 
     * @param array<K,V> $body
     * @return string
     */
    private function encodePostBody($body) {
        if ($body === null) {
            return '';
        }
        
        if (! is_array($body)) {
            return urlencode(strval($body));
        }
        
        $result = array ();
        foreach ( $body as $k => $v ) {
            $result [ ] = urlencode($k) . '=' . urlencode($v);
        }
        
        return join('&', $result);
    }
    /**
     * 解析curl的response,并折分其中的header与body部份.
     * @param string $content
     * @return array
     */
    private function parseResponse($content) {
        
        list ( $headerStr, $body ) = explode("\r\n\r\n", $content, 2);
        
        $header = array ();
        
        $headerPart = explode("\r\n", $headerStr);
        
        list ( $httpProtocol, $statusCode, $statStr ) = explode(' ', array_shift($headerPart), 3);
        
        foreach ( $headerPart as $item ) {
            list ( $key, $value ) = explode(':', $item);
            $header [$key] = $value;
        }
        
        $result = array (
            'protocol' => $httpProtocol,
            'status' => intval($statusCode),
            'statusMsg' => $statStr,
            'header' => $header,
            'content' => $body, 
        );
        
        return $result;
    }
    /**
     * 发起http请求, 并返回响应内容
     *
     * @param [string] $path
     *        url的拼接部份,将拼接到urlBase部份
     * @param [string] $method
     *        使用的http请求方法, 目前只支持GET和POST
     * @param [mixed] $payload
     *        请求时的附加的数据.
     * @param [array] $reqHeaders
     *        请求时附加的header信息
     * @param [array] $curlOpts
     *        每次请求时, 对curl的配置信息.
     * @throws HttpException 请求异常时, 抛出异常
     * @return array http response信息 , 具有如下结构 array(
     *         httpProtocol:'',
     *         status:'',
     *         statusMsg:'',
     *         header:array(),
     *         content:""
     *         )
     */
    function request($path = '', $method = "GET", $payload = "", $reqHeaders = null, $curlOpts = null) {
        if ($path === null) {
            throw new HttpException('path must be string');
        }
            
        // 决定访问位置 
        $url = $this->resolve($path);
        
        $curlOptsFinal = $this -> currentOpts;
        
        if (is_array($curlOpts)) {
            foreach ( $curlOpts as $k => $v ) {
                $curlOptsFinal [$k] = $v;
            }
        }
        
        if (is_array($reqHeaders)) {
            if(!is_array($curlOptsFinal [CURLOPT_HTTPHEADER])){
                $curlOptsFinal [CURLOPT_HTTPHEADER] = array(); // 这玩意必须是个数组.不是就直接覆盖.
            }
            $curlOptsFinal [CURLOPT_HTTPHEADER] = array_merge($curlOptsFinal [CURLOPT_HTTPHEADER], $reqHeaders);
            
            //print_r($curlOptsFinal);
        }
        
        $port = parse_url($url, PHP_URL_PORT);
        
        $curlOptsFinal [CURLOPT_URL] = $url;
        
        $curlOptsFinal [CURLOPT_PORT] = empty($port) ? 80 : $port;
        
        $req = curl_init();
        
        curl_setopt_array($req, $curlOptsFinal);
        
        switch ($method) {
            case self::HTTP_GET :
                curl_setopt($req, CURLOPT_CUSTOMREQUEST, 'GET');
                break;
            case self::HTTP_POST :
                curl_setopt($req, CURLOPT_POST, true);
                curl_setopt($req, CURLOPT_POSTFIELDS, $payload);
                break;
            default :
                throw new HttpException("method not be support");
        }
        
        
        $response = curl_exec($req);
        $errCode = curl_errno($req);
        
        
        if ($errCode === 0) {
            curl_close($req);
            // log http access
            $response = $this -> parseResponse($response);
            $status = $response['status'];
            $this -> log -> log("HttpRequest: $status $method $url");
            
            return $response;
        } else {
            // get message and close curl resource;
            
            $errMsg = curl_error($req);
            curl_close($req);
             
            throw new HttpException( "curl_error($errMsg); with url($url)", $errCode);
        }
    }
    /**
     * 快速发起get请求
     * 
     * @param string $path
     *        请求资源路径
     * @param array $query
     *        请求参数
     * @param array $headers
     *        附带header
     * @param array $curlOpts
     *        附加curlOpts
     *        
     * @return array http response信息 , 具有如下结构 array(
     *         httpProtocol:'',
     *         status:'',
     *         statusMsg:'',
     *         header:array(),
     *         content:""
     *         )
     */
    function get($path, $query = null, $headers = null, $curlOpts = null) {
        $payload = $this -> encodePostBody($query);
        
        if ($payload !== "") {
            if (strpos($path, '?')) {
                $url = $path . "&" . $payload;
            } else {
                $url = $path . "?" . $payload;
            }
        }else{
            $url = $path;
        }
        
        return $this -> request($url, self::HTTP_GET, '', $headers, $curlOpts);
    }
    /**
     * 快速发起post请求
     *
     * @param string $path
     *        请求资源路径
     * @param array $postBody
     *        请求参数
     * @param array $headers
     *        附带header
     * @param array $curlOpts
     *        附加curlOpts
     *        
     * @return array http response信息 , 具有如下结构 array(
     *         httpProtocol:'',
     *         status:'',
     *         statusMsg:'',
     *         header:array(),
     *         content:""
     *         )
     */
    function post($path = null, $postBody = null, $headers = null, $curlOpts = null) {
        $payload = $this -> encodePostBody($postBody);
        $this -> log -> debug("\n\n ====== Dump the payload before Http POST send! ====== \n\n$payload\n\n====== End Dump! ======\n");
        return $this -> request($path, self::HTTP_POST, $payload, $headers, $curlOpts);
    }
    
    /**
     * 根据传入内容,决定具体请求地址;
     * @param string $path
     * @return Ambigous <string, unknown>
     */
    public function resolve($path){
        // 如果不指定完整的url, 则认为是访问$urlBase内资源
        if ($this -> isUrlFormat($path)) {
            $url = $path;
        }
        // 忽略重复的路径分隔符
        else if (strpos($path, '/') === 0) {
            $url = $this -> urlBase . substr($path, 1);
        } else {
            $url = $this -> urlBase . $path;
        }
        
        return $url;
    }
    
    /**
     * 取得url中表示资源位置的部份,即去掉query_string及其后的部份
     * 
     * @param string $url
     * @return string
     */
    public function getResourceAddress($url){
        $url = $this->resolve($url);
        
        $p = strpos($url,'?');
        $p = $p === false ? strpos($url,'#') : $p;
        
        return $p === false ? $url : substr($url, 0, $p);
    }
}