PHP Curl 登录302跳转后411错误原因及解决方案
2024-12-14 17:18:06
PHP Curl 登录请求遭遇302跳转后返回411错误的原因与解决方案
当使用 PHP Curl 发送登录请求时,遇到服务器返回302状态码进行重定向,紧接着却收到411 "Length Required" 错误,这通常意味着重定向后的请求缺少必要的 Content-Length
头部。 这种情况比较特殊,因为 Curl 通常会自动处理重定向和 Content-Length
头部。 但某些服务器或应用可能对 Content-Length
的存在有严格要求,尤其是在处理 POST 请求时,即使重定向后的请求是 GET 请求也可能需要该头部。
问题分析
问题的核心在于:
-
首次 POST 请求成功 : PHP Curl 第一次发送 POST 请求到登录页面,服务器验证凭据后返回 302 Found 状态码,并提供
Location
头部指示重定向地址。同时,服务器设置了必要的认证 Cookie。 -
Curl 自动跟随重定向 :
CURLOPT_FOLLOWLOCATION
设置为 true, Curl 自动跟随重定向,发送 GET 请求到Location
指定的地址。 -
重定向请求缺少 Content-Length : 尽管是 GET 请求,且通常不需要
Content-Length
头部,但目标服务器可能因为之前处理过 POST 请求或其他原因,要求后续请求也必须包含此头部。 -
服务器返回 411 Length Required :由于重定向后的请求缺少
Content-Length
头部,服务器拒绝处理请求,并返回 411 Length Required 状态码。 -
Postman测试正常的原因 : Postman 可能在处理重定向时自动添加了
Content-Length
头部,或者服务器对 Postman 的请求有特殊处理。
解决方案
解决这个问题主要有以下几种思路:
1. 禁用自动重定向,手动处理重定向
这是最直接的解决方法。 通过禁用 Curl 的自动重定向功能 (CURLOPT_FOLLOWLOCATION = false
) ,获取重定向地址后,再手动构造新的 Curl 请求,显式设置 Content-Length
头部。 即使 GET 请求,也可以尝试设置 Content-Length: 0
。
- 原理 : 避免 Curl 自动处理重定向时可能遗漏
Content-Length
头部。 - 操作步骤 :
1. 设置CURLOPT_FOLLOWLOCATION
为false
。
2. 执行第一次 Curl 请求,获取响应头中的Location
值和 Cookie。
3. 解析Location
值,构建新的 Curl 请求,设置CURLOPT_URL
为重定向地址。
4. 手动添加Content-Length: 0
头部。
5. 重新发送请求,获取最终页面内容。 - 代码示例 :
<?php
include('php/simple_html_dom.php');
$username = 'username';
$password = 'password';
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://somesite.com/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_ENCODING => '',
CURLOPT_FOLLOWLOCATION => false, // Disable automatic redirection
CURLOPT_MAXREDIRS => 10,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HEADER => true, // Get the header
CURLOPT_NOBODY => false,
CURLOPT_COOKIEJAR => dirname(__FILE__) .'/cookie.txt',
CURLOPT_COOKIEFILE => dirname(__FILE__) .'/cookie.txt',
CURLOPT_USERAGENT=> 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36',
));
$response = curl_exec($curl);
// Check if redirection is needed
if(curl_getinfo($curl, CURLINFO_HTTP_CODE) == 302){
preg_match('/Location:(.*?)\n/', $response, $matches);
$redirectUrl = trim($matches[1]);
//Resolve relative url
if(strpos($redirectUrl,'http') !== 0){
$baseUrl = curl_getinfo($curl,CURLINFO_EFFECTIVE_URL);
$baseParts = parse_url($baseUrl);
$redirectUrl = $baseParts['scheme'].'://'.$baseParts['host'].$redirectUrl;
}
// echo "Redirect URL: " . $redirectUrl . "<br>";
$html = str_get_html($response);
$htmlFormData= [];
foreach($html->find('input') as $input) {
$htmlFormData[$input->name]=$input->value;
}
foreach($html->find('#header h3') as $h3) {
if($h3->plaintext === 'Login'){//when page title is Login
$htmlFormData['DES_Group'] = 'LOGIN';
$htmlFormData['DES_JSE'] = '1';
$htmlFormData['ctl00$ctl00$plcMain$contentMain$ucLogin$ctlAccountNumber$txtText'] = $username;
$htmlFormData['ctl00$ctl00$plcMain$contentMain$ucLogin$ctlAuthorisationCode$txtText'] = $password;
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://somesite.com/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HEADER => true, // Get header for next redirect check
CURLOPT_NOBODY => false,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_FOLLOWLOCATION => false, // Still disable auto-redirect
CURLOPT_COOKIEJAR => dirname(__FILE__) .'/cookie.txt',
CURLOPT_COOKIEFILE => dirname(__FILE__) .'/cookie.txt',
CURLOPT_POSTFIELDS => http_build_query($htmlFormData),
CURLOPT_HTTPHEADER => array('Content-Type: application/x-www-form-urlencoded')
));
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if($httpCode == 302){
preg_match('/Location:(.*?)\n/', $response, $matches);
$redirectUrl = trim($matches[1]);
//Resolve relative url
if(strpos($redirectUrl,'http') !== 0){
$baseUrl = curl_getinfo($curl,CURLINFO_EFFECTIVE_URL);
$baseParts = parse_url($baseUrl);
$redirectUrl = $baseParts['scheme'].'://'.$baseParts['host'].$redirectUrl;
}
}
// echo "Second Request Code: ". $httpCode . "<br>";
// echo "Effective URL: " . $redirectUrl ."<br>";
curl_setopt_array($curl, array(
CURLOPT_URL => $redirectUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HEADER => false,
CURLOPT_NOBODY => false,
CURLOPT_ENCODING => '',
CURLOPT_TIMEOUT => 0,
CURLOPT_HTTPHEADER => array('Content-Length: 0') // Add Content-Length header for redirection request
));
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$effectiveUrl = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL);
echo "HTTP Code: ".$httpCode."<br>";
echo "Effective URL: ".$effectiveUrl."<br>";
echo $response;
}else{
echo "Logged!. cookie.txt already contains .ASPXFORMSAUTH";
}
}
} else {
echo $response;
}
curl_close($curl);
?>
2. 使用 CURL 钩子函数修改请求头
Curl 提供了一些钩子函数,可以在请求发送前修改请求头。 通过 CURLOPT_HEADERFUNCTION
可以在接收到 Header 数据时进行处理。 然而,这种方法并不适合直接添加 Content-Length
,因为它主要用于读取 Header。 但是,可以尝试在第二次请求(手动构造的请求,或在 Curl FollowLocation 后的 Header 回调函数中) ,修改请求 Header 来添加 Content-Length : 0。
- 原理