Disabling the URL encoding in Guzzle - php

I have been trying to download a file in Guzzle and it acts wired, Then I noticed that the request URL has gone haywire. I don't understand how to use the setEncodingType(false); function.
This is what I have right now.
public class Foo{
private $client;
private $loginUrl = 'https://<site>/login';
private $parseUrl = 'https://<site>/download';
public function __construct()
{
require_once APPPATH . 'third_party/guzzle/autoloader.php';
$this->client = new GuzzleHttp\Client(['cookies' => true, 'allow_redirects' => [
'max' => 10, // allow at most 10 redirects.
'strict' => true, // use "strict" RFC compliant redirects.
'referer' => true, // add a Referer header
'protocols' => ['https'], // only allow https URLs
'track_redirects' => true
]]);
}
public function download(){
$q_params = array('param_a'=> 'a', 'param_b'=>'b');
$target_file = APPPATH.'files/tmp.log';
$response = $this->client->request('GET', $this->parseUrl,['query'=>$reportVars, 'sink' => $target_file]);
}
}
Can anyone tell me how can I use disable the url encoding in the above code?

Cursory glance through the code of GuzzleHttp\Client::applyOptions indicates that when you utilze the "query" request option the query will be built to PHP_QUERY_RFC3986 as shown below:
if (isset($options['query'])) {
$value = $options['query'];
if (is_array($value)) {
$value = http_build_query($value, null, '&', PHP_QUERY_RFC3986);
}
if (!is_string($value)) {
throw new \InvalidArgumentException('query must be a string or array');
}
$modify['query'] = $value;
unset($options['query']);
}
Guzzle utilizes GuzzleHttp\Psr7\Uri internally. Note how the withoutQueryValue() and withQueryValue() methods will also encode the query string.
I have had a lot of success "hard coding" my query parameters, like the following:
$uri = 'http://somewebsite.com/page.html?param_a=1&param2=245';
I would like to also note that there is no setEncodingType() within GuzzleHttp\Client.

Related

Guzzle async requests waiting for timeout even I use any to wrap around the promise - how can I make it return ASAP?

My sample code is below, which basically tries to get a certain URL using a list of proxies. I want to return a result as soon as a proxy returns:
$response = any(
array_map(
function (?string $proxy) use ($headers, $url) {
return $this->client->getAsync(
$url
, [
'timeout' => 5,
'http_errors' => FALSE,
'proxy' => $proxy,
]
);
}
, self::PROXIES
)
)
->wait();
However, whatever the value I set in the timeout, I found out that the whole HTTP request only returns when the full timeout has passed, i.e. 5 seconds in this case. If I change 5 to 10, the whole HTTP request only returns after 10 seconds.
How can I really make it return ASAP?
Are you sure that the proxy/end server work well? I mean, Guzzle do nothing special with proxied request, so there are no settings that you can tweak.
What do you get after the timeout? Normal 200 response or an exception?
To me it looks like the issue is in proxy or in the end server. Have you tried to request the URL directly, without a proxy? Is it fast or still takes 5-10-... seconds?
I finally wrote my own promise to solve the problem which really returns immediately.
/** #var Promise $master_promise */
$master_promise = new Promise(
function () use ($url, &$master_promise) {
$onFulfilled = static function (ResponseInterface $response) use ($master_promise) {
$master_promise->resolve($response);
};
$rejections = [];
foreach (static::PROXIES as $proxy) {
$this->client->getAsync(
$url
, [
'timeout' => static::TIMEOUT,
'http_errors' => FALSE,
'proxy' => $proxy,
]
)
->then(
$onFulfilled
, static function (GuzzleException $exception)
use ($master_promise, &$rejections)
{
$rejections[] = $exception;
if (count($rejections) === count(static::PROXIES)) {
$master_promise->reject(
new AggregateException(
'Calls by all proxies have failed.'
, $rejections
)
);
}
}
);
}
while ($master_promise->getState() === PromiseInterface::PENDING) {
$this->handler->tick();
}
}
);
$response = $master_promise->wait();

How to save cookie into a file and use that in other requests?

I'm logging in to a page through Guzzle. It's saving the cookie. When I make subsequent requests it works perfectly fine. But, when I run the php again, I don't want php to do the same process which is logging in, getting the cookie again. So, I want to use existing cookie but I could not manage to do that. I don't think that it is well explained at Guzzle Documentation
Basically, steps must be like this:
When the php runs for the first time, it will login to the url.
get the cookies. Save the cookies to the disk. Use it for subsequent
requests.
When php runs again, it must check if cookie exists or
not. if not go to first step. If exists, use the cookie file for the requests.
My class is as following. The problem here is, when php runs 2nd time, I need to log in again.
<?php
namespace OfferBundle\Tools;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\FileCookieJar;
class Example
{
private $site;
private static $client;
private static $cookieCreated = false;
private static $cookieExists;
private static $loginUrl = "http://website.com/account/login";
private static $header = [
'Content-Type' => 'application/x-www-form-urlencoded',
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; WOW64)'
];
private static $cookieFile = 'cookie.txt';
private static $cookieJar;
private static $credential = array(
'EmailAddress' => 'username',
'Password' => 'password',
'RememberMe' => true
);
public function __construct($site) {
self::$cookieExists = file_exists(self::$cookieFile) ? true : false;
self::$cookieJar = new FileCookieJar(self::$cookieFile, true);
self::$client = new Client(['cookies' => self::$cookieJar]);
if(!self::$cookieCreated && !self::$cookieExists) {
self::createLoginCookie();
}
$this->site = $site;
}
public function doSth()
{
$url = 'http://website.com/'.$this->site;
$result = (String)self::$client->request('GET',$url, ['headers' => self::$header])->getBody();
return $result;
}
private static function createLoginCookie()
{
self::$client->request('POST', self::$loginUrl, [
'form_params' => self::$credential,
'connect_timeout' => 20,
'headers' => self::$header
]);
self::$cookieCreated = true;
}
Executed php:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use OfferBundle\Tools\Example;
class DefaultController extends Controller
{
/**
* #Route("/")
*/
public function indexAction()
{
$sm = new Example('anothersite.com');
$result = $sm->doSth();
dump($summary);
die;
}
}
Here is my soluion:
Go to vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php
And comment out $this->save() part in the destructor.
public function __destruct()
{
//$this->save($this->filename);
}
Use the following process to login and save the cookie to 'cookie_path'
$response = self::$client->request('POST', self::$loginUrl, [
'form_params' => $formData,
'connect_timeout' => 20,
'headers' => self::$header,
'cookies' => new FileCookieJar('cookie_path')
]);
If you want your saved cookie to be used in all your requests by default, create another client object and pass the cookie to the constructor.
$new_client = new Client(['cookies' => new FileCookieJar('cookie_path')])
Now, your new client is ready to use the cookie in all of your requests.

SOAP action error when using soapClient

i am getting this error when trying to make a soap call.
The SOAP action specified on the message, '', does not match the HTTP SOAP Action.
When i call $service->SearchTouristItems($sti); (this function is further below) i get the above error and i have no idea why.
The below is the code i am using.
// i used http://www.urdalen.no/wsdl2php/ to create TCS2Service which extends SoapClient
$service = new TCS2Service() ;
$sd = new ServiceDescriptor;
$sd->UniqueIdentifier = 'xxxxxxxxx-xxxxx-xxxx-xxxxx-xxxxxx';
$stic = new SearchTouristItemCriteria;
$stic->SearchString = array ('dublin') ;
$sti = new SearchTouristItems;
$sti->searchTouristItemCriteria = $sd;
$sti->serviceDescriptor = $stic;
$result = $service->SearchTouristItems($sti);
echo "<pre>";
print_r($result);
echo "</pre>";
SearchTouristItems looks like this
/**
*
*
* #param SearchTouristItems $parameters
* #return SearchTouristItemsResponse
*/
public function SearchTouristItems(SearchTouristItems $parameters) {
return $this->__soapCall('SearchTouristItems', array($parameters), array(
'uri' => 'http://tempuri.org/',
'soapaction' => ''
)
);
}
this is the initilization of the client
public function TCS2Service($wsdl = "http://www.example.com/services/TCS2Service.svc", $options = array( 'soap_version' => SOAP_1_2,
'exceptions' => true,
'trace' => 1,
'cache_wsdl' => WSDL_CACHE_NONE,)) {
foreach(self::$classmap as $key => $value) {
if(!isset($options['classmap'][$key])) {
$options['classmap'][$key] = $value;
}
}
parent::__construct($wsdl, $options);
}
Not sure though but what is the value of 'soapaction' => '' in your code is replaced with the provided parameter. I do not have that experience calling web services with PHP so just gave it a thought.
I think your ws-addressing is not turned on. Please turn on the ws-
addressing and check again.
What I would do :
check if the SOAP action is well defined in the WSDL: look for address location="
try another WSDL to php converter
send the WSDL url so I can try it by my side

Wkhtmltopdf redirect to login page in symfony2

I'm using wkhtmltopdf to generate a pdf report in my application,but when the pdf is generated,i got the login page in the pdf.
this is my Action:
public function exportPdfAction($id = 0)
{
$em = $this->container->get('doctrine')->getEntityManager();
$id = $this->get('request')->get($this->admin->getIdParameter());
$object = $this->admin->getObject($id);
if (!$object) {
throw new NotFoundHttpException(sprintf('unable to find the object with id : %s', $id));
}
if (false === $this->admin->isGranted('VIEW', $object)) {
throw new AccessDeniedException();
}
$pageUrl = $this->generateUrl('admin_rh_leave_conge_show', array('id'=>$id), true); // use absolute path!
return new Response(
$this->get('knp_snappy.pdf')->getOutput($pageUrl),
200,
array(
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="Fiche_conge.pdf"'
)
);
}
how can i resolve the problem ?
this is a bit late, but I had the exact same problem, and found a solution for it:
You can pass in options as second parameter in the getOutput()-method. One of these options is cookie:
use Symfony\Component\HttpFoundation\Response;
...
$session = $this->get('session');
$session->save();
session_write_close();
return new Response(
$this->get('knp_snappy.pdf')->getOutput(
$pageUrl,
array('cookie' => array($session->getName() => $session->getId()))
),
200,
array(
'Content-Type' => 'application/pdf',
)
);
See http://wkhtmltopdf.org/ and https://github.com/KnpLabs/KnpSnappyBundle/issues/42 for details.
I had a similar problem with that bundle. In my case was the issue that the script was running from the command line. And the problem was that there the executed user was not authenticated in the sonata admin.
So be sure your calling the pdf is logged in user and don't switch between production and development environment that will lost the session and you have to relogin.
So check if the script with is calling the snappy pdf generation is correctly authenticated and has the sonata_admin_role (access to the sonata admin backend).
Hope that helps.
2021: I still had the exact same problem but found the accepted solution of Iris Schaffer a little bit dirty. So here is another way. You can just generate the html in the controller you're in.
Instead of using ->getOutput() we use ->getOutputFromHtml()
/**
* #Route("/dossiers/{dossier}/work-order/download", name="download_work_order")
* #Security("is_granted('DOWNLOAD_WORK_ORDER', dossier)")
*
* #param Dossier $dossier
* #return Response
*/
public function generateWorkOrderPdfAction(Dossier $dossier): Response
{
/**
* Since we are a valid logged-in user in this controller we generate everything in advance
* So wkhtmltopdf does not have login issues
*/
$html = $this->forward('PlanningBundle\Controller\WorkOrderController::generateWorkOrderHTMLAction', [
'dossier' => $dossier,
])->getContent();
$options = [
'footer-html' => $this->renderView('#Dossier/PDF/footer.html.twig', [
'dossier' => $dossier,
]),
];
return new Response(
$this->get('knp_snappy.pdf')->getOutputFromHtml($html, $options),
200,
[
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="work-order-' . $dossier->getName() . '.pdf"',
]
);
}

SoapClient set custom HTTP Header

I am doing some work writing a PHP-based SOAP client application that uses the SOAP libraries native to PHP5. I need to send a an HTTP cookie and an additional HTTP header as part of the request. The cookie part is no problem:
Code:
$client = new SoapClient($webServiceURI, array("exceptions" => 0, "trace" => 1, "encoding" => $phpInternalEncoding));
$client->__setCookie($kkey, $vvalue);
My problem is the HTTP header. I was hoping there would have been a function named
__setHeader
or
__setHttpHeader
in the SOAP libraries. But no such luck.
Anyone else dealt with this? Is there a workaround? Would a different SOAP library be easier to work with? Thanks.
(I found this unanswerd question here http://www.phpfreaks.com/forums/index.php?topic=125387.0, I copied it b/c i've the same issue)
Try setting a stream context for the soap client:
$client = new SoapClient($webServiceURI, array(
"exceptions" => 0,
"trace" => 1,
"encoding" => $phpInternalEncoding,
'stream_context' => stream_context_create(array(
'http' => array(
'header' => 'SomeCustomHeader: value'
),
)),
));
This answer is the proper way to do it in PHP 5.3+ SoapClient set custom HTTP Header
However, PHP 5.2 does not take all of the values from the stream context into consideration. To get around this, you can make a subclass that handles it for you (in a hacky way, but it works).
class SoapClientBackport extends SoapClient {
public function __construct($wsdl, $options = array()){
if($options['stream_context'] && is_resource($options['stream_context'])){
$stream_context_options = stream_context_get_options($options['stream_context']);
$user_agent = (isset($stream_context_options['http']['user_agent']) ? $stream_context_options['http']['user_agent'] : "PHP-SOAP/" . PHP_VERSION) . "\r\n";
if(isset($stream_context_options['http']['header'])){
if(is_string($stream_context_options['http']['header'])){
$user_agent .= $stream_context_options['http']['header'] . "\r\n";
}
else if(is_array($stream_context_options['http']['header'])){
$user_agent .= implode("\r\n", $stream_context_options['http']['header']);
}
}
$options['user_agent'] = $user_agent;
}
parent::__construct($wsdl, $options);
}
}
I ran into a situation where I had to provide a hash of all the text of the soap request in the HTTP header of the request for authentication purposes. I accomplished this by subclassing SoapClient and using the stream_context option to set the header:
class AuthenticatingSoapClient extends SoapClient {
private $secretKey = "secretKeyString";
private $context;
function __construct($wsdl, $options) {
// Create the stream_context and add it to the options
$this->context = stream_context_create();
$options = array_merge($options, array('stream_context' => $this->context));
parent::SoapClient($wsdl, $options);
}
// Override doRequest to calculate the authentication hash from the $request.
function __doRequest($request, $location, $action, $version, $one_way = 0) {
// Grab all the text from the request.
$xml = simplexml_load_string($request);
$innerText = dom_import_simplexml($xml)->textContent;
// Calculate the authentication hash.
$encodedText = utf8_encode($innerText);
$authHash = base64_encode(hash_hmac("sha256", $encodedText, $this->secretKey, true));
// Set the HTTP headers.
stream_context_set_option($this->context, array('http' => array('header' => 'AuthHash: '. $authHash)));
return (parent::__doRequest($request, $location, $action, $version, $one_way));
}
}
Maybe someone searching will find this useful.
its easy to implement in nuSoap:
NUSOAP.PHP
add to class nusoap_base:
var additionalHeaders = array();
then goto function send of the same class
and add
foreach ($this->additionalHeaders as $key => $value) {
$http->setHeader($key, $value);
}
somewhere around (just before)
$http->setSOAPAction($soapaction); (line 7596)
now you can easy set headers:
$soapClient = new nusoap_client('wsdl adress','wsdl');
$soapClient->additionalHeaders = array('key'=>'val','key2'=>'val');
The SoapClient::__soapCall method has an $input_headers argument, which takes an array of SoapHeaders.
You could also use Zend Framework's SOAP client, which provides an addSoapInputHeader convenience method.

Categories