我正在使用 PHP curl 将一系列请求发送到需要登录的第三方服务器,然后保留该登录的会话cookie。
因此我将curl操作包装到此类中:
class SoapCli {
private $ch;
private $id;
private $rc;
function __construct() {
$this->rc=0;
$this->id=bin2hex(random_bytes(8));
$this->ch = curl_init();
$time=microtime(true);
error_log(PHP_EOL.PHP_EOL."Instance id $this->id created ($time): \$this->ch = ".print_r($this->ch,true).PHP_EOL,3,"log.txt");
curl_setopt($this->ch, CURLOPT_AUTOREFERER,1);
curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, 120);
curl_setopt($this->ch, CURLOPT_COOKIEFILE, "");
curl_setopt($this->ch, CURLOPT_ENCODING, "");
curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($this->ch, CURLOPT_MAXREDIRS, 10);
curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($this->ch, CURLOPT_VERBOSE, 1);
}
function Request(string $method, string $url, array $headers = array(), $postdata = "", $referer = null) {
$resp = new stdClass();
$resp->id = $this->id;
$this->rc++;
$time=microtime(true);
error_log("Instance id $this->id before request $this->rc ($time): \$this->ch = ".print_r($this->ch,true).PHP_EOL,3,"log.txt");
try {
curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($this->ch, CURLOPT_URL, $url);
curl_setopt($this->ch, CURLOPT_HTTPHEADER, $headers);
if (isset($referer)) curl_setopt($this->ch, CURLOPT_REFERER, $referer);
if (preg_match("/^POST$/i",$method)===1) curl_setopt($this->ch, CURLOPT_POSTFIELDS, $postdata);
$resp->body = curl_exec($this->ch);
$resp->err_message = curl_error($this->ch);
$resp->err_number = curl_errno($this->ch);
$resp->info = curl_getinfo($this->ch);
}
catch (Exception $exception) {
$resp->err_message = $exception->getMessage();
$resp->err_number = $exception->getCode();
$resp->info = $exception->getTrace();
}
$time=microtime(true);
error_log("Instance id $this->id before request $this->rc ($time): \$this->ch = ".print_r($this->ch,true).PHP_EOL,3,"log.txt");
return $resp;
}
}
但是,在第三个请求之后,存储了curl句柄资源的受保护变量的内容已替换为 0(整数)的值,我真的不知道为什么。我只能收集此日志:
Instance id 1cb893bc5b7369bd created (1547852391.7976): $this->ch = Resource id #3
Instance id 1cb893bc5b7369bd before request 1 (1547852391.8025): $this->ch = Resource id #3
Instance id 1cb893bc5b7369bd before request 1 (1547852392.0723): $this->ch = Resource id #3
Instance id 1cb893bc5b7369bd before request 2 (1547852392.0778): $this->ch = Resource id #3
Instance id 1cb893bc5b7369bd before request 2 (1547852392.357): $this->ch = Resource id #3
Instance id 1cb893bc5b7369bd before request 3 (1547852392.3616): $this->ch = Resource id #3
Instance id 1cb893bc5b7369bd before request 3 (1547852392.6225): $this->ch = Resource id #3
Instance id 1cb893bc5b7369bd before request 4 (1547852393.0264): $this->ch = 0
Instance id 1cb893bc5b7369bd before request 4 (1547852393.0758): $this->ch = 0
Instance id 1cb893bc5b7369bd before request 5 (1547852394.8992): $this->ch = 0
Instance id 1cb893bc5b7369bd before request 5 (1547852394.9461): $this->ch = 0
编辑:这是使用类SoapCli
的代码:
// index.php
$postdata = filter_input_array(INPUT_POST);
if ($_SESSION["logged_in"]===true) {
echo file_get_contents("main.html");
} else if (isset($postdata) && isset($postdata["action"])) {
$action = $postdata["action"];
if ($action==="Login" && isset($postdata["usrcpf"]) && isset($postdata["usrpwd"])) {
$username=$postdata["username"];
$password=$postdata["password"];
$sc=new SoapCli(); //instantiated here
$_SESSION["sc"]=$sc;
$login_response = $sc->Request(
"GET",
BASEURL."/login",
array(
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0",
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language: pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1",
"Cache-Control: max-age=0"
)
);
if ($login_response->err_number) {
echo file_get_contents("login_server_error.html");
} else {
$dom = new DOMDocument;
$dom->loadHTML($login_response->body);
$xdom = new DOMXPath($dom);
$csrf_token_nodes = $xdom->query("//input[@name='_csrf_token']/@value");
if ($csrf_token_nodes->length<1) {
echo file_get_contents("login_server_error.html");
} else {
$csrf_token = $csrf_token_nodes->item(0)->textContent;
$postdata = "_csrf_token=$csrf_token&_username=$username&_password=$password&_submit=Login";
$login_check_response = $sc->Request(
"POST",
BASEURL."/login_check",
array(
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0",
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language: pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3",
"Content-Type: application/x-www-form-urlencoded",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1"
),
$postdata,
BASEURL."/login"
);
if ($login_check_response->err_number) {
echo file_get_contents("login_server_error.html");
} elseif (strpos($login_check_response->body, "api.js")) {
echo file_get_contents("login_auth_error.html");
} else {
$route_userinfo = $sc->Request(
"POST",
BASEURL."/route",
array(
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0",
"Accept: */*",
"Accept-Language: pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3",
"Content-Type: application/json",
"X-Requested-With: XMLHttpRequest",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1",
),
USERINFO_JSON,
BASEURL."/"
);
if ($route_userinfo->err_number) {
echo file_get_contents("login_server_error.html");
} else {
$_SESSION["logged_in"]=true;
$_SESSION["user_info"]=json_decode($route_userinfo->body);
header("Location: ".$_SERVER["PHP_SELF"], true, 303);
}
}
}
}
} else {
http_response_code(400);
}
} else {
echo file_get_contents("login.html");
}
和
// ajax.php (called by JS in main.html, which is loaded after login)
if ($_SESSION["logged_in"]===true) {
$postdata = filter_input_array(INPUT_POST);
if (isset($postdata)) {
if (isset($postdata["content"])) {
if ($postdata["content"]==="tasks") {
$sc=$_SESSION["sc"];
$route_tasks = $sc->Request(
"POST",
BASEURL."/route",
array(
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0",
"Accept: */*",
"Accept-Language: pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3",
"Content-Type: application/json",
"X-Requested-With: XMLHttpRequest",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1",
),
TAKS_JSON,
BASEURL."/"
);
if ($route_tasks->err_number) {
echo file_get_contents("ajax_server_error.html");
} else {
$tarefas=json_decode($route_tasks->body);
if (isset($tarefas) && is_array($tarefas->records)) {
foreach($tarefas->records as $i=>$tarefa){
echo "<p>".$tarefa->especieTarefa->nome."</p>";
}
} else {
http_response_code(500);
}
}
}
} else {
http_response_code(400);
}
} else {
http_response_code(400);
}
} else {
http_response_code(403);
}
由于变量SoapCli::ch
在类之外是不可访问的,所以我真的看不到没有声明如何更改其内容。我也找不到任何有关会破坏该句柄的http请求/响应的信息。
无论是什么,它都与请求无关,因为我试图重复请求#3,该请求是有效的并且收到有效的响应,并且由于句柄消失了,因此重复失败。
此外,我正在PHP中尝试实现的功能已经由功能齐全的.NET桌面(winforms)应用程序完成,因此,并非出于外部原因无法完成。我只是想用PHP卷曲System.Net.HttpWebRequest
所做的事情,偶然发现了本文中描述的问题。
如何在需要时保存手柄?
我正在IIS Express / Windows 10上使用PHP 7.2。
答案 0 :(得分:1)
简单的答案是:当您尝试在ajax.php中使用它时,该句柄不存在
在ajax.php
内,查看以下行:
$sc=$_SESSION["sc"];
然后您致电:
$route_tasks = $sc->Request(
...
);
因此,您将类放在index.php
中,并且所有3个调用都在该位置成功完成,然后将一个对象写入$_SESSION["sc"]
变量中,显然该对象已被php的会话处理程序正确编码和解码因此,检索对象后仍可以在Request
内调用方法ajax.php
。
虽然您确实在ajax.php
中使用对象,但它与index.php
创建的对象实例不同,因为该实例属于index.php
线程以及{ {1}}处理;从curl
调用ajax.php
会创建一个不同的线程来处理它,并且也需要一个新的index.php
句柄。
将curl
更改为$sc=$_SESSION["sc"];
,以便可以在使用之前创建$sc=new SoapCli();
句柄。
答案 1 :(得分:0)
我发布这个答案只是为了展示我如何解决@Solrac在他的答案中描述和解释的问题(这是正确的,我会接受的):
class SoapCli {
private $ch;
private $cookiepot;
function __construct() {
$this->cookiepot=tempnam(sys_get_temp_dir(),"CookieJar");
$this->reconstruct();
}
function reconstruct() {
$this->ch = curl_init();
curl_setopt($this->ch, CURLOPT_AUTOREFERER, true);
curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, 300);
curl_setopt($this->ch, CURLOPT_COOKIEFILE, $this->cookiepot);
curl_setopt($this->ch, CURLOPT_COOKIEJAR, $this->cookiepot);
curl_setopt($this->ch, CURLOPT_ENCODING, "");
curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($this->ch, CURLOPT_HEADER, true);
curl_setopt($this->ch, CURLINFO_HEADER_OUT, true);
curl_setopt($this->ch, CURLOPT_MAXREDIRS, 32);
curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($this->ch, CURLOPT_VERBOSE, true);
}
function Request(string $method, string $url, array $headers = array(), $postdata = "", $referer = "") {
if (!is_resource($this->ch)) {
$this->reconstruct();
}
curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($this->ch, CURLOPT_URL, $url);
curl_setopt($this->ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($this->ch, CURLOPT_REFERER, $referer);
if (preg_match("/^POST$/i",$method)===1) curl_setopt($this->ch, CURLOPT_POSTFIELDS, $postdata);
$response=curl_exec($this->ch);
list($headers,$body)=preg_split("/\r\n\r\n(?!HTTP)/", $response, 2);
$resp_obj = (object) array(
"body"=>$body,
"headers"=>$headers,
"err_number"=>curl_errno($this->ch),
"err_message"=>curl_error($this->ch),
"info"=>curl_getinfo($this->ch)
);
return $resp_obj;
}
function log(string $text) {
file_put_contents($this->id."log.txt",$text.PHP_EOL,FILE_APPEND|FILE_TEXT|LOCK_EX);
}
}