如何使用PHP发送POST请求?

802

实际上我想读取搜索查询后的内容。问题在于URL只接受POST方法,而不接受任何使用GET方法的操作...

我必须利用domdocumentfile_get_contents()来读取所有的内容。是否有一种方法可以让我通过PHP使用POST方法发送参数,然后读取内容?

18个回答

1453
无需CURL的方法:
$url = 'http://server.com/path';
$data = ['key1' => 'value1', 'key2' => 'value2'];

// use key 'http' even if you send the request to https://...
$options = [
    'http' => [
        'header' => "Content-type: application/x-www-form-urlencoded\r\n",
        'method' => 'POST',
        'content' => http_build_query($data),
    ],
];

$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if ($result === false) {
    /* Handle error */
}

var_dump($result);

请参阅PHP手册,了解有关该方法以及如何添加标头的更多信息,例如:

73
值得注意的是,如果你决定使用数组作为标头,请勿以 '\r\n' 结束键或值。stream_context_create() 只会获取到第一个 '\r\n' 之前的文本。 - raptor
14
只有在启用了fopen流包装器时,才能将URL用作file_get_contents()的文件名。请参见http://www.php.net/manual/en/filesystem.configuration.php#ini.allow-url-fopen。 - Pino
19
不使用CURL有具体的原因吗? - JacobF
51
在某些环境下,PHP的CURL扩展可能不存在,而file_get_contents()是PHP核心的一部分。此外,不必要地使用扩展可以增加应用程序受攻击的面。例如,Google搜索“php curl cve”。 - Pocketsand
6
布尔值为假(False),我不明白这代表什么意思。 - Miomir Dancevic
显示剩余13条评论

194

你可以使用cURL

<?php
//The url you wish to send the POST request to
$url = $file_name;

//The data you want to send via POST
$fields = [
    '__VIEWSTATE '      => $state,
    '__EVENTVALIDATION' => $valid,
    'btnSubmit'         => 'Submit'
];

//url-ify the data for the POST
$fields_string = http_build_query($fields);

//open connection
$ch = curl_init();

//set the url, number of POST vars, POST data
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_POST, true);
curl_setopt($ch,CURLOPT_POSTFIELDS, $fields_string);

//So that curl_exec returns the contents of the cURL; rather than echoing it
curl_setopt($ch,CURLOPT_RETURNTRANSFER, true); 

//execute post
$result = curl_exec($ch);
echo $result;
?>

4
这个方法适合我,因为我发送的页面没有内容,所以使用 file_get_contents 版本无法工作。 - CommentLuv
11
在PHP配置中,如果allow_url_fopen被关闭(例如在共享主机中),file_get_contents解决方案将无法使用。这个版本使用了curl库,我认为是最“通用”的,所以我投它一票。 - Dayron Gallardo
114
你没有注明你从哪里复制了这段代码示例:http://davidwalsh.name/curl-post - efreed
4
虽然并不十分重要,但是 CURLOPT_POSTFIELDS 参数数据实际上不需要被转换成字符串("urlified")。引用:"该参数可以作为 urlencoded 字符串传递,例如 'para1=val1&para2=val2&...',也可以作为一个数组,其中字段名作为键,字段数据作为值。如果值是一个数组,则 Content-Type 头将被设置为 multipart/form-data。" 链接:http://php.net/manual/en/function.curl-setopt.php。 - Edward
2
另外,不是针对您的不同写法而言,但我不知道为什么在这里指定CURLOPT_POST参数为数字,因为手册页面上说要将其设置为布尔值。引用:“CURLOPT_POST:TRUE表示执行常规HTTP POST。”链接:http://php.net/manual/en/function.curl-setopt.php。 - Edward
1
count($fields) 的目的是什么?为什么不将其设置为1? - Nubcake

87

我使用以下函数使用curl发送数据。 $data是要发布的字段数组(将使用http_build_query()正确编码)。

function httpPost($url, $data)
{
    $curl = curl_init($url);
    curl_setopt($curl, CURLOPT_POST, true);
    curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data));
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($curl);
    curl_close($curl);
    return $response;
}

@Edward提到可以省略http_build_query(),因为curl会正确编码传递给CURLOPT_POSTFIELDS参数的数组,这是正确的,但请注意,在这种情况下,数据将使用multipart/form-data进行编码,并且可能不希望一些终点使用application/x-www-form-urlencoded进行编码。在上面的函数中使用http_build_query()时,数据将使用application/x-www-form-urlencoded进行编码。


将数组传递给CURLOPT_POSTFIELDS会导致使用multipart / form-data对数据进行编码,这可能不是理想的。 - Dima L.
用户确实要求使用file_get_contents,因此他需要一种更改默认流上下文的解决方案。 - Radon8472
@Radon8472 - ... CURLOPT_RETURNTRANSFER, true 的结果是 $response 包含了内容。 - ToolmakerSteve
我收到了以下错误:尝试从名称空间调用函数“curl_init”。 - Pathros
@Pathros,您可能没有安装Curl模块或者它没有被启用。 - Dima L.
显示剩余3条评论

53

我建议你使用开源包guzzle,该包经过全面单元测试并采用了最新的编码规范。

安装Guzzle

前往您项目文件夹中的命令行,并输入以下命令(假设您已经安装了包管理器composer)。如果您需要帮助安装Composer,请点击此处

php composer.phar require guzzlehttp/guzzle

使用 Guzzle 发送 POST 请求

Guzzle 的使用非常直接,因为它采用了轻量级的面向对象 API:

// Initialize Guzzle client
$client = new GuzzleHttp\Client();

// Create a POST request
$response = $client->request(
    'POST',
    'http://example.org/',
    [
        'form_params' => [
            'key1' => 'value1',
            'key2' => 'value2'
        ]
    ]
);

// Parse the response object, e.g. read the headers, body, etc.
$headers = $response->getHeaders();
$body = $response->getBody();

// Output headers and body for debugging purposes
var_dump($headers, $body);

10
了解这种解决方案相对于已发布的本机PHP解决方案和cURL解决方案的优势将是很有用的。 - artfulrobot
13
PHP开发者几乎都使用cURL,因为原生的PHP解决方案存在很多问题(例如与https连接、证书验证等)。为什么不在这种情况下使用cURL呢?答案很简单:Guzzle提供了一个直观、易于使用、轻量级的接口,可以抽象出所有那些“底层cURL处理问题”。现代PHP开发几乎都使用Composer,所以使用Guzzle非常简单。 - Andreas
2
谢谢,我知道guzzle很受欢迎,但是在某些情况下composer会引起麻烦(例如,在为已经使用guzzle或其他依赖项的大型软件项目开发插件时可能会出现问题)。因此,了解这些信息以便能够决定哪种解决方案最为强大是很好的。 - artfulrobot
2
@Andreas,虽然你是对的,但这是越来越多的抽象导致对低级技术理解越来越少的好例子,因此导致越来越多的开发人员不知道他们在那里做什么,甚至无法调试一个简单的请求。 - clockw0rk
2
@clockw0rk,不幸的是,你说得对。但是抽象(在某种程度上)仍然很有用,可以节省大量时间和错误/潜在的漏洞。显然,每个使用Guzzle的人都应该能够调试请求,并且还应该具有基本的网络知识和HTTP工作原理的了解。 - Andreas

33

我希望能对Fred Tanrikut基于curl的答案进行一些补充。虽然大部分内容已经在上面的回答中提到了,但我认为将它们汇总在一起展示是个好主意。

以下是我编写的一个类,它基于curl实现了HTTP-GET/POST/PUT/DELETE请求,关注的重点在于响应体:

class HTTPRequester {
    /**
     * @description Make HTTP-GET call
     * @param       $url
     * @param       array $params
     * @return      HTTP-Response body or an empty string if the request fails or is empty
     */
    public static function HTTPGet($url, array $params) {
        $query = http_build_query($params); 
        $ch    = curl_init($url.'?'.$query);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HEADER, false);
        $response = curl_exec($ch);
        curl_close($ch);
        return $response;
    }
    /**
     * @description Make HTTP-POST call
     * @param       $url
     * @param       array $params
     * @return      HTTP-Response body or an empty string if the request fails or is empty
     */
    public static function HTTPPost($url, array $params) {
        $query = http_build_query($params);
        $ch    = curl_init();
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
        $response = curl_exec($ch);
        curl_close($ch);
        return $response;
    }
    /**
     * @description Make HTTP-PUT call
     * @param       $url
     * @param       array $params
     * @return      HTTP-Response body or an empty string if the request fails or is empty
     */
    public static function HTTPPut($url, array $params) {
        $query = \http_build_query($params);
        $ch    = \curl_init();
        \curl_setopt($ch, \CURLOPT_RETURNTRANSFER, true);
        \curl_setopt($ch, \CURLOPT_HEADER, false);
        \curl_setopt($ch, \CURLOPT_URL, $url);
        \curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, 'PUT');
        \curl_setopt($ch, \CURLOPT_POSTFIELDS, $query);
        $response = \curl_exec($ch);
        \curl_close($ch);
        return $response;
    }
    /**
     * @category Make HTTP-DELETE call
     * @param    $url
     * @param    array $params
     * @return   HTTP-Response body or an empty string if the request fails or is empty
     */
    public static function HTTPDelete($url, array $params) {
        $query = \http_build_query($params);
        $ch    = \curl_init();
        \curl_setopt($ch, \CURLOPT_RETURNTRANSFER, true);
        \curl_setopt($ch, \CURLOPT_HEADER, false);
        \curl_setopt($ch, \CURLOPT_URL, $url);
        \curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, 'DELETE');
        \curl_setopt($ch, \CURLOPT_POSTFIELDS, $query);
        $response = \curl_exec($ch);
        \curl_close($ch);
        return $response;
    }
}

改进

  • 使用 http_build_query 函数从请求数组中获取查询字符串。(你也可以使用数组本身,参见:http://php.net/manual/zh/function.curl-setopt.php)
  • 返回响应而不是将其输出到屏幕上。顺便说一下,你可以通过删除 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 这行代码来避免返回值,这时返回值是一个布尔值(true = 请求成功,否则就是发生了错误),而响应将被输出到屏幕上。参见:http://php.net/en/manual/function.curl-exec.php
  • 使用 curl_close 来清理会话并删除 curl 处理程序。参见:http://php.net/manual/zh/function.curl-close.php
  • curl_setopt 函数中使用布尔值代替使用数字。(虽然任何非零数字也被视为 true,但使用 true 会生成更易读的代码,当然这只是我的观点)
  • 能够使用 HTTP-PUT / DELETE 请求(对于测试 RESTful 服务很有用)

使用示例

GET

$response = HTTPRequester::HTTPGet("http://localhost/service/foobar.php", array("getParam" => "foobar"));

POST

$response = HTTPRequester::HTTPPost("http://localhost/service/foobar.php", array("postParam" => "foobar"));

PUT

-->

PUT

$response = HTTPRequester::HTTPPut("http://localhost/service/foobar.php", array("putParam" => "foobar"));

删除

$response = HTTPRequester::HTTPDelete("http://localhost/service/foobar.php", array("deleteParam" => "foobar"));

测试

您可以使用这个简单的类制作一些很酷的服务测试。

class HTTPRequesterCase extends TestCase {
    /**
     * @description test static method HTTPGet
     */
    public function testHTTPGet() {
        $requestArr = array("getLicenses" => 1);
        $url        = "http://localhost/project/req/licenseService.php";
        $this->assertEquals(HTTPRequester::HTTPGet($url, $requestArr), '[{"error":false,"val":["NONE","AGPL","GPLv3"]}]');
    }
    /**
     * @description test static method HTTPPost
     */
    public function testHTTPPost() {
        $requestArr = array("addPerson" => array("foo", "bar"));
        $url        = "http://localhost/project/req/personService.php";
        $this->assertEquals(HTTPRequester::HTTPPost($url, $requestArr), '[{"error":false}]');
    }
    /**
     * @description test static method HTTPPut
     */
    public function testHTTPPut() {
        $requestArr = array("updatePerson" => array("foo", "bar"));
        $url        = "http://localhost/project/req/personService.php";
        $this->assertEquals(HTTPRequester::HTTPPut($url, $requestArr), '[{"error":false}]');
    }
    /**
     * @description test static method HTTPDelete
     */
    public function testHTTPDelete() {
        $requestArr = array("deletePerson" => array("foo", "bar"));
        $url        = "http://localhost/project/req/personService.php";
        $this->assertEquals(HTTPRequester::HTTPDelete($url, $requestArr), '[{"error":false}]');
    }
}

1
对我来说,它显示“未捕获的错误:调用未定义的方法 HTTPRequester::HTTPost()”。我只是将您的类粘贴到我的.php文件中。还需要做其他事情吗? - LinusGeffarth
1
你能否请发一下你的代码?没有任何代码片段,很难猜出问题出在哪里。 - mwatzer
1
就像我说的,我已经把你的代码文字直接复制到我的 PHP 文件中,但出现了这个错误。 - LinusGeffarth
2
好的,现在我明白问题所在了...示例中写错了!你需要调用HTTPRequester::HTTPPost()而不是HTTPRequester::HTTPost()。 - mwatzer
1
啊。那个很容易错过。在我发现额外的 P 之前,我不得不读你的评论大约5次。谢谢! - LinusGeffarth
显示剩余4条评论

27
如果你碰巧正在使用WordPress来开发应用程序(事实上,这是一种方便的方式,可以获取授权、信息页面等,即使是非常简单的东西),你可以使用以下片段:
$response = wp_remote_post( $url, array('body' => $parameters));

if ( is_wp_error( $response ) ) {
    // $response->get_error_message()
} else {
    // $response['body']
}

根据Web服务器上可用的方式,它使用不同的方法来进行实际的HTTP请求。有关更多详细信息,请参阅HTTP API文档

如果您不想开发自定义主题或插件来启动WordPress引擎,您可以在WordPress根目录中的一个独立的PHP文件中执行以下操作:

require_once( dirname(__FILE__) . '/wp-load.php' );

// ... your code

它不会显示任何主题或输出任何HTML,只需使用WordPress API进行编程!


26

如果您想使用 CURL 方法,还有另一种选择。

一旦理解了 PHP curl 扩展的工作方式以及将各种标志与 setopt() 调用相结合,这就非常简单了。在此示例中,我有一个变量 $xml,它保存了我准备发送的 XML 内容 - 我将把该内容发布到示例测试方法。

$url = 'http://api.example.com/services/xmlrpc/';
$ch = curl_init($url);

curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
curl_close($ch);
//process $response

首先,我们初始化连接,然后使用setopt()设置一些选项。这些选项告诉PHP我们正在进行post请求,并且我们正在提供数据。CURLOPT_RETURNTRANSFER标志告诉curl将输出作为curl_exec的返回值而不是输出它。接下来,我们发出调用并关闭连接——结果在$response中。


1
在第三个curl_setopt()调用中,第一个参数应该是$ch而不是$curl,对吗? - jcomeau_ictx
你能使用同样的代码来POST JSON数据吗?但是将$xml替换为$json(其中$json可能是一个JSON字符串)? - Neal Davis

25

另一种不需要curl的替代方法是使用本地函数:

  • stream_context_create()

    创建并返回一个带有在选项中提供的任何选项的流上下文。

  • stream_get_contents()

    file_get_contents()相同,只是stream_get_contents()操作于已经打开的流资源,并返回从指定的偏移量开始,最多maxlength字节的剩余内容的字符串。

这些函数可以简单地实现POST功能:

<?php

function post_request($url, array $params) {
  $query_content = http_build_query($params);
  $fp = fopen($url, 'r', FALSE, // do not use_include_path
    stream_context_create([
    'http' => [
      'header'  => [ // header array does not need '\r\n'
        'Content-type: application/x-www-form-urlencoded',
        'Content-Length: ' . strlen($query_content)
      ],
      'method'  => 'POST',
      'content' => $query_content
    ]
  ]));
  if ($fp === FALSE) {
    return json_encode(['error' => 'Failed to get contents...']);
  }
  $result = stream_get_contents($fp); // no maxlength/offset
  fclose($fp);
  return $result;
}

1
这种不需要CURL的方法对我来说很好用,可以验证来自谷歌的reCAPTCHA。这个答案与谷歌代码库中的这个代码相符:https://github.com/google/recaptcha/blob/master/src/ReCaptcha/RequestMethod/Post.php - Xavi Montero
1
如果 $fpfalse,则无需使用 fclose()。因为 fclose() 需要一个资源作为参数。 - Floris
1
@Floris 刚刚编辑了它,确实 fclose 文档 中提到“文件指针必须有效”。感谢您注意到这一点! - CPHPython
1
我尝试过这个,但是我无法在我的API中解析“post”数据。 我使用的是json_decode(file_get_contents("php://input"))。 有什么想法吗? 编辑:通过将内容类型标头更改为application/json,它起作用了。谢谢! - obey

23

这里只使用一个命令,而不需要使用cURL。非常简单。

echo file_get_contents('https://www.server.com', false, stream_context_create([
    'http' => [
        'method' => 'POST',
        'header'  => "Content-type: application/x-www-form-urlencoded",
        'content' => http_build_query([
            'key1' => 'Hello world!', 'key2' => 'second value'
        ])
    ]
]));

Key2 会如何工作?它们之间的分隔符是什么? - Sayed Muhammad Idrees
1
@Sayedidrees 要添加 key2,您可以将其作为第二个数组项输入。'key1' => '你好,世界!','key2' => '第二个值' - Liga

10

使用以下方式是在PHP中发送GETPOST请求的更好方法:

<?php
    $r = new HttpRequest('http://example.com/form.php', HttpRequest::METH_POST);
    $r->setOptions(array('cookies' => array('lang' => 'de')));
    $r->addPostFields(array('user' => 'mike', 'pass' => 's3c|r3t'));

    try {
        echo $r->send()->getBody();
    } catch (HttpException $ex) {
        echo $ex;
    }
?>

这段代码来自官方文档,位于http://docs.php.net/manual/da/httprequest.send.php


1
@akinuri感谢您的提醒,我将分享新的内容。 - Imran Zahoor
如何在PHP 5x上实现? - user285594
@YumYumYum,请查看dbau上面的答案,使用了这种技术 https://www.php.net/manual/en/function.stream-context-create.php或者您可以始终返回标准的curl解决方案。 - Imran Zahoor
2
这不是原生的PHP。这需要PECL HTTP。 - Mike Shiyan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接