博客原文地址http://blog.csdn.net/lgg201/article/details/8000756由于php的curl在curl_setopt($curl, CURLOPT_POSTFIELDS, xxx)时, 当xxx为数组时, 如果值的第一个字符是@, 则认为是文件上传, 当同时需要上传文件, 也需要提交可能首字符为@的其他普通数据时, 存在冲突. 因此, 在api_common.php中的post数据的设置进行了封装
<?php
/**
 * php-curl库封装
 * author: selfimpr
 * blog: http://blog.csdn.net/lgg201
 * mail: [email protected]
 */define('API_CURL_UPLOAD_FILE', '__file');#支持的请求方法
define('REQUEST_METHOD_GET', 'GET');
define('REQUEST_METHOD_POST', 'POST');
define('REQUEST_METHOD_HEAD', 'HEAD');#请求行为
define('REQUEST_BEHAVIOR_ALLOW_REDIRECT', 'allow_redirect');
define('REQUEST_BEHAVIOR_MAX_REDIRECT', 'max_redirect');
define('REQUEST_BEHAVIOR_USER_AGENT', 'user_agent');
define('REQUEST_BEHAVIOR_AUTOREFERER', 'autoreferer');
define('REQUEST_BEHAVIOR_UPLOAD', 'upload');
define('REQUEST_BEHAVIOR_CONNECTTIMEOUT', 'connecttimeout');
define('REQUEST_BEHAVIOR_DNS_CACHE_TIMEOUT', 'dns_cache_timeout');
define('REQUEST_BEHAVIOR_TIMEOUT', 'timeout');
define('REQUEST_BEHAVIOR_ENCODING', 'encoding');
define('REQUEST_BEHAVIOR_ERROR_HANDLER', 'error_handler');
define('REQUEST_BEHAVIORS', 'behaviors');
$GLOBALS[REQUEST_BEHAVIORS] = array(
REQUEST_BEHAVIOR_ALLOW_REDIRECT => TRUE, 
REQUEST_BEHAVIOR_MAX_REDIRECT => 5, 
REQUEST_BEHAVIOR_USER_AGENT => 'curl-lib', 
REQUEST_BEHAVIOR_AUTOREFERER => TRUE, 
REQUEST_BEHAVIOR_UPLOAD => FALSE, 
REQUEST_BEHAVIOR_CONNECTTIMEOUT => 3, 
REQUEST_BEHAVIOR_DNS_CACHE_TIMEOUT => 3600, 
REQUEST_BEHAVIOR_TIMEOUT => 3, 
REQUEST_BEHAVIOR_ENCODING => 'gzip', 
REQUEST_BEHAVIOR_ERROR_HANDLER => '__default_curl_error_handler', 
);define('MULTIPART_FORM_DATA_HEAD_FMT', 'Content-Type: multipart/form-data; boundary=----------------------------%s');
define('MULTIPART_FORM_DATA_BODY_STRING', "------------------------------%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n");
define('MULTIPART_FORM_DATA_BODY_FILE', "------------------------------%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: application/octet-stream\r\n\r\n%s\r\n");
define('MULTIPART_FORM_DATA_BODY_END', "------------------------------%s--\r\n\r\n");#响应键值
define('RESP_CODE', 'resp_code');
define('RESP_BODY', 'resp_body');
define('RESP_HEADER', 'resp_header');#HTTP 1xx状态验证
define('HTTP_1XX_RESP', '/^HTTP\/1.[01] 1\d{2} \w+/');#默认错误处理的错误消息
define('E_CURL_ERROR_FMT', 'curl "%s" error[%d]: %s');#默认的curl错误处理
function __default_curl_error_handler($curl, $url, $errno, $errstr) {
trigger_error(sprintf(E_CURL_ERROR_FMT, $url, $errno, $errstr), E_USER_ERROR);
}
#切换CURL请求方法
function __method_switch($curl, $method) {
switch ( $method) {
case REQUEST_METHOD_POST:
__curl_setopt($curl, CURLOPT_POST, TRUE);
break;
case REQUEST_METHOD_HEAD:
__curl_setopt($curl, CURLOPT_NOBODY, TRUE);
break;
case REQUEST_METHOD_GET:
__curl_setopt($curl, CURLOPT_HTTPGET, TRUE);
break;
default:
break;
}
}
#设置默认头信息
function __default_header_set($curl) {
__curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
__curl_setopt($curl, CURLOPT_HEADER, TRUE);
__curl_setopt($curl, CURLOPT_FOLLOWLOCATION, (bool)curl_behavior(REQUEST_BEHAVIOR_ALLOW_REDIRECT));
__curl_setopt($curl, CURLOPT_MAXREDIRS, (int)curl_behavior(REQUEST_BEHAVIOR_MAX_REDIRECT));
__curl_setopt($curl, CURLOPT_USERAGENT, (string)curl_behavior(REQUEST_BEHAVIOR_USER_AGENT));
__curl_setopt($curl, CURLOPT_AUTOREFERER, (bool)curl_behavior(REQUEST_BEHAVIOR_AUTOREFERER));
__curl_setopt($curl, CURLOPT_UPLOAD, (bool)curl_behavior(REQUEST_BEHAVIOR_UPLOAD));
__curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, (int)curl_behavior(REQUEST_BEHAVIOR_CONNECTTIMEOUT));
__curl_setopt($curl, CURLOPT_DNS_CACHE_TIMEOUT, (int)curl_behavior(REQUEST_BEHAVIOR_DNS_CACHE_TIMEOUT));
__curl_setopt($curl, CURLOPT_TIMEOUT, (int)curl_behavior(REQUEST_BEHAVIOR_TIMEOUT));
__curl_setopt($curl, CURLOPT_ENCODING, (string)curl_behavior(REQUEST_BEHAVIOR_ENCODING));
}
#设置用户自定义头信息
function __custom_header_set($curl, $headers = NULL) {
if ( empty($headers) ) return ;
if ( is_string($headers) ) 
$headers = explode("\r\n", $headers);
#类型修复
foreach ( $headers as &$header ) 
if ( is_array($header) ) 
$header = sprintf('%s: %s', $header[0], $header[1]);
__curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
}
#设置请求body
function __datas_set($curl, $datas = NULL) {
if ( empty($datas) ) return ;
if ( is_array($datas) ) {
$custom_body = FALSE;
$uniqid = uniqid();
$custom_body_str = '';
foreach ( $datas as $name => $data ) {
if ( is_array($data) && array_key_exists(API_CURL_UPLOAD_FILE, $data) ) {
$file = $data[API_CURL_UPLOAD_FILE];
if ( file_exists($file) ) {
$custom_body = TRUE;
$custom_body_str .= sprintf(MULTIPART_FORM_DATA_BODY_FILE, 
$uniqid, $name, 
$file, file_get_contents($file));
}
} else {
$custom_body_str .= sprintf(MULTIPART_FORM_DATA_BODY_STRING, 
$uniqid, $name, $data);
}
}
if ( $custom_body ) {
curl_setopt($curl, CURLOPT_HTTPHEADER, array(sprintf(MULTIPART_FORM_DATA_HEAD_FMT, $uniqid)));
$datas = $custom_body_str . sprintf(MULTIPART_FORM_DATA_BODY_END, $uniqid);
}
}
__curl_setopt($curl, CURLOPT_POSTFIELDS, $datas);
}
#对curl_setopt的封装
function __curl_setopt($curl, $optname, $optval) {
curl_setopt($curl, $optname, $optval);
__curl_error($curl);
}
#curl错误检查处理
function __curl_error($curl) {
if ( curl_errno($curl) ) {
$url = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL);
$errno = curl_errno($curl);
$errstr = curl_error($curl);
$errh = curl_behavior(REQUEST_BEHAVIOR_ERROR_HANDLER);
if ( function_exists($errh) )
$errh($curl, $url, $errno, $errstr);
}
}#api默认行为切换
function curl_behavior($names, $values = NULL) {
if ( !is_string($names) && !is_array($names) ) return ;
if ( !is_null($values) ) {
if ( is_string($names) ) 
$GLOBALS[REQUEST_BEHAVIORS][$names] = $values;
else if ( is_array($names) && !is_array($values) )
foreach ( $names as $name )
$GLOBALS[REQUEST_BEHAVIORS][$name] = $values;
else if ( is_array($names) && is_array($values) )
foreach ( $names as $k => $name ) 
$GLOBALS[REQUEST_BEHAVIORS][$name] = $values[$k];
}
if ( is_string($names) ) {
$return = $GLOBALS[REQUEST_BEHAVIORS][$names];
} else if ( is_array($names) ) {
$return = array();
foreach ( $names as $name ) 
$return[$name] = array_key_exists($name, $GLOBALS[REQUEST_BEHAVIORS]) 
? $GLOBALS[REQUEST_BEHAVIORS][$name]
: NULL;
}
return $return;
}
#请求入口
function curl_request($url, $method, $datas = NULL, $headers = NULL) {
$curl = curl_init($url);
__method_switch($curl, $method);
__default_header_set($curl);
__custom_header_set($curl, $headers);
__datas_set($curl, $datas);
$response = curl_exec($curl);
__curl_error($curl);
$status_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$components = explode("\r\n\r\n", $response);
$i = -1;
while ( ++ $i < count($components) ) 
if ( !preg_match(HTTP_1XX_RESP, $components[$i]) ) break;
$headers = $components[$i];
$body = implode("\r\n\r\n", array_slice($components, $i + 1));
return array(
RESP_CODE => $status_code, 
RESP_HEADER => $headers, 
RESP_BODY => $body, 
);
}
#GET请求
function curl_get($url, $headers = NULL) {
return curl_request($url, REQUEST_METHOD_GET, NULL, $headers);
}
#POST请求
function curl_post($url, $datas = NULL, $headers = NULL) {
return curl_request($url, REQUEST_METHOD_POST, $datas, $headers);
}
#HEAD请求
function curl_head($url, $headers = NULL) {
return curl_request($url, REQUEST_METHOD_HEAD, NULL, $headers);
}
#构造上传文件字段
function curl_post_file($file) {
return array(
API_CURL_UPLOAD_FILE => $file, 
);
}
#读取响应码
function curl_resp_code($resp) {
return $resp[RESP_CODE];
}
#读取响应头
function curl_resp_header($resp) {
return $resp[RESP_HEADER];
}
#读取响应体
function curl_resp_body($resp) {
return $resp[RESP_BODY];
}

解决方案 »

  1.   

    To 上面二位:这个东西是应业务需求而生, 如果没有必要, 请教如何简单实现下面需求:
    1. 微博类产品
    2. 有两个系统均为php实现: A系统提供api接口, B系统基于http协议调用A系统接口完成逻辑
    3. B系统对A系统的接口调用使用php的curl扩展处理
    4. 其中有一个接口的业务是图文微博发布, B系统需要发送一个请求, 提交"微博内容"和"图片文件"
    5. 要提交的"微博内容"中会有首字符是"@"的情况下面是php的curl扩展对curl_setopt($curl, CURLOPT_POSTFIELDS, xxx)处理的源代码:
    从代码中可以看到, 当提交的数据中本身可能首字符是@时, 和文件上传存在二义性语法冲突.case CURLOPT_POSTFIELDS:
    /* zvalue是curl_setopt的第三个参数 */
    if (Z_TYPE_PP(zvalue) == IS_ARRAY || Z_TYPE_PP(zvalue) == IS_OBJECT) {
    /* 数组或对象类型的数据处理逻辑 */
    zval            **current;
    HashTable        *postfields;
    struct HttpPost  *first = NULL;
    struct HttpPost  *last  = NULL;
    char             *postval;
    char             *string_key = NULL;
    ulong             num_key;
    uint              string_key_len; postfields = HASH_OF(*zvalue);
    if (!postfields) {
    php_error_docref(NULL TSRMLS_CC, E_WARNING, "Couldn't get HashTable in CURLOPT_POSTFIELDS");
    RETVAL_FALSE;
    return 1;
    } /* 遍历第三个参数(数组或对象) */
    for (zend_hash_internal_pointer_reset(postfields);
     zend_hash_get_current_data(postfields, (void **) &current) == SUCCESS;
     zend_hash_move_forward(postfields)
    ) { SEPARATE_ZVAL(current);
    convert_to_string_ex(current); zend_hash_get_current_key_ex(postfields, &string_key, &string_key_len, &num_key, 0, NULL); postval = Z_STRVAL_PP(current); /* The arguments after _NAMELENGTH and _CONTENTSLENGTH
     * must be explicitly cast to long in curl_formadd
     * use since curl needs a long not an int. */
    if (*postval == '@') {
    /* 如果首字符是@, 则进行文件处理 */
    char *type, *filename;
    ++postval; if ((type = php_memnstr(postval, ";type=", sizeof(";type=") - 1, postval + Z_STRLEN_PP(current)))) {
    *type = '\0';
    }
    if ((filename = php_memnstr(postval, ";filename=", sizeof(";filename=") - 1, postval + Z_STRLEN_PP(current)))) {
    *filename = '\0';
    }
    /* safe_mode / open_basedir check */
    if (php_check_open_basedir(postval TSRMLS_CC) || (PG(safe_mode) && !php_checkuid(postval, "rb+", CHECKUID_CHECK_MODE_PARAM))) {
    RETVAL_FALSE;
    return 1;
    }
    error = curl_formadd(&first, &last,
    CURLFORM_COPYNAME, string_key,
    CURLFORM_NAMELENGTH, (long)string_key_len - 1,
    CURLFORM_FILENAME, filename ? filename + sizeof(";filename=") - 1 : postval,
    CURLFORM_CONTENTTYPE, type ? type + sizeof(";type=") - 1 : "application/octet-stream",
    CURLFORM_FILE, postval,
    CURLFORM_END);
    if (type) {
    *type = ';';
    }
    if (filename) {
    *filename = ';';
    }
    } else {
    /* 首字符不是@, 则直接增加到要发送的body中 */
    error = curl_formadd(&first, &last,
     CURLFORM_COPYNAME, string_key,
     CURLFORM_NAMELENGTH, (long)string_key_len - 1,
     CURLFORM_COPYCONTENTS, postval,
     CURLFORM_CONTENTSLENGTH, (long)Z_STRLEN_PP(current),
     CURLFORM_END);
    }
    } SAVE_CURL_ERROR(ch, error);
    if (error != CURLE_OK) {
    RETVAL_FALSE;
    return 1;
    } zend_llist_add_element(&ch->to_free.post, &first);
    /* 设置body */
    error = curl_easy_setopt(ch->cp, CURLOPT_HTTPPOST, first); } else {
    /* 第三个参数是字符串时, 直接设置到body中 */
    #if LIBCURL_VERSION_NUM >= 0x071101
    convert_to_string_ex(zvalue);
    /* with curl 7.17.0 and later, we can use COPYPOSTFIELDS, but we have to provide size before */
    error = curl_easy_setopt(ch->cp, CURLOPT_POSTFIELDSIZE, Z_STRLEN_PP(zvalue));
    error = curl_easy_setopt(ch->cp, CURLOPT_COPYPOSTFIELDS, Z_STRVAL_PP(zvalue));
    #else
    char *post = NULL; convert_to_string_ex(zvalue);
    post = estrndup(Z_STRVAL_PP(zvalue), Z_STRLEN_PP(zvalue));
    zend_llist_add_element(&ch->to_free.str, &post); error = curl_easy_setopt(ch->cp, CURLOPT_POSTFIELDS, post);
    error = curl_easy_setopt(ch->cp, CURLOPT_POSTFIELDSIZE, Z_STRLEN_PP(zvalue));
    #endif
    }
    break;
      

  2.   

    补充下, 上面的代码来自php-5.3.5源代码目录下的ext/curl/interface.c:1902行
      

  3.   

    所以这种场景下还是socket连接最靠谱么
      

  4.   


    不使用套接字直接处理, 原因在于curl本身封装的其实挺好, 整个系统多处需要http-client功能时, 用这样的封装还是要省事不少的, 因此, 选择了对php-curl的这个缺陷进行修补.如果只是因为这么一个小缺陷, 重新基于套接字开发一个http-client, 那费力不讨好...
      

  5.   

    恩  不错  需要的时候 过来 copy一下啊 呵呵