Reputation: 2712
I'm using PHP curl to send a series of requests to a 3rd party server which requires login and then persisting the session cookie for that login.
So I wrapped the curl operation into this class:
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;
}
}
However, after the 3rd request, the protected variable that stored the curl handle resource has its content replaced by the value of 0 (integer) and I really can't figure out why. I could only collect this log:
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
EDIT: This is the code that consumes class 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");
}
and
// 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);
}
Since the variable SoapCli::ch
is not accessible out of the class, I really can't see how its content could be changed without a statement. I coundn't find any information about a kind of http request/response which would destroy the handle, either.
Whatever it is, it doesn't have to do with the request, because i tried to repeat request #3, which is valid and receives a valid response, and its repetition fails because of the handle is gone.
Plus, what I'm trying to implement in PHP is already done by a fully functional .NET desktop (winforms) application, so it's not like it can't be done for external reasons. I'm just trying to do with PHP curl what I did with System.Net.HttpWebRequest
, and stumbled upon the problem described at this post.
How can I preserve the handle as long as I need it?
I'm using PHP 7.2 on IIS Express/Windows 10.
Upvotes: 0
Views: 1125
Reputation: 2712
I'm posting this answer just to show how I went around the problem that was described and explained by @Solrac in his answer (which is correct and I'll accept):
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);
}
}
Upvotes: 1
Reputation: 931
Short answer is: the handle does not exist when you are trying to use it inside ajax.php
Inside ajax.php
, take a look at the following line:
$sc=$_SESSION["sc"];
And then you call:
$route_tasks = $sc->Request(
...
);
So you instaciated your class inside index.php
and all the 3 calls made there where succesfull, then you write an object into the $_SESSION["sc"]
variable and apparently the object gets encoded and decoded correctly by php's session handler which is why you are still able to call the method Request
inside ajax.php
after retrieving the object.
While you are indeed using an object in ajax.php
it is not the same instance of the object that was created by index.php
as that instance belongs to the index.php
thread along with the curl
handle; calling ajax.php
from index.php
will create a diffrent thread to handle it and will require a new curl
handle as well.
Change $sc=$_SESSION["sc"];
to $sc=new SoapCli();
so the curl
handle can be created before used.
Upvotes: 1