An API sends me a stream containing a zip archive of several files that I choose by providing their ids in the parameter SelectedIds of my request.
I receive a PSR7 response that I pass to HttpFoundationFactory to return a Response that corresponds to what the Symfony controller should return.
(the goal is to download the zip in the client side browser.)
Here is the content of my controller method
$client = $this->getApiClient();
$user = $this->getUser();
$idList = [51,52,53];
$psr7ApiResponse = $client->post('/v1/get-zip', [
'headers' => [
'Authorization' => sprintf('Bearer %s', $user->getToken()),
],
'http_errors' => false,
'json' => [
'SelectedIds' => $idList,
],
]);
$httpFoundationFactory = new HttpFoundationFactory();
return $httpFoundationFactory->createResponse($psr7ApiResponse);
It works perfectly locally but on the server I receive nothing, blank page. Would you know which way I should look because I have no error log, it looks like the stream is empty but I don't know how to check.
I tested the API with postman and it's ok ; my controller sends me back a 200 as well
Follow those steps in order to debug the problem:
1. Check if the content of $psr7ApiResponse is valid and correct
You are in a different environment. Maybe for some reason your server side script does not receive a valid answer. Maybe the authentication does not work. Print debug the answer you receive to some log file as detailed as necessary (use loggers:
https://symfony.com/doc/current/logging.html).
If the content or the resulting class of the call are not correct the problem is within the remote call communication and you have to debug that. This is most likely the case.
2. Check if your client really understands the answer and check if the answer is correct
Your client definitely should not receive a blank page (it indicates that the problem is 1).
Try to explicitly return a file by using Symfony\Component\HttpFoundation\File\File.
You also can set certain ZIP headers to a Response object manually - at least for debugging:
// set example variables
$filename = "zip-to-download.zip";
$filepath = "/path"; // maybe save the response to a temp file for debugging purposes
$response = $httpFoundationFactory->createResponse($psr7ApiResponse);
$response->headers->set('Pragma','public');
$response->headers->set('Expires',0);
$response->headers->set('Cache-Control','must-revalidate, post-check=0, pre-check=0');
$response->headers->set('Content-Description','File Transfer');
$response->headers->set('Content-type','application/octet-stream');
$response->headers->set('Content-Disposition','attachment; filename="'.$filename.'"');
$response->headers->set('Content-Transfer-Encoding','binary');
return $response;
Related
I have a problem when I am trying to POST data to third party API. I'm getting an error
yii\base\ErrorException: Header may not contain more than a single header, new line detected in /www/wwwroot/xxx/vendor/yiisoft/yii2/web/Response.php:382
As far as I know yii manages headers by itself and I added additional headers for auth purposes.
My additional headers:
protected function getHeaders($data = [])
{
ksort($data);
reset($data);
$ts = microtime().rand(0, 10000);
return [
'login: '.$this->login,
'ts: '.$ts,
'sig: '.md5($ts.$this->apiKey),
];
}
Code works in pure php tests, but not inside yii app.
I already tried to log response and request headers, didn't find any duplicated headers.
Only something like this one:
'CONTENT_TYPE' => 'application/json; charset=utf-8'
But I don't know if this counts for multiple headers.
The problem was on the API provider's side. My requests were not passing authentication, and I was getting back gibberish with incorrect headers.
I'm trying to create a "service" like application, which can be able to receive API calls from another services. (These services will be built, for different purposes). And also able to send API calls to an another one.
Each request that they send, and accept has to have the following format.
{
header : {
// some header information, like locale, currency code etc.
signature : "some-hashed-data-using-the-whole-request"
},
request : {
// the usable business data
}
}
To each request I want to append a hash, that is generated from the actual request or anyhow (salted with password or any kind of magic added). Its not that important at the moment. I gave the name signature to this field. So for each received request, I want to reproduce this signature from the request. If the signature I received is matching with the one I generated, I let the application run otherwise showing some error message.
I already read a few articles, but most of them is for user-pass combinations.
My question is not about that if it's a good solution or not. I just want to know how can implement a middleware like functionality - like in laravel - in Symfony 4?
Instead of putting headers into a JSON object the HTTP body, use HTTP headers directly. That’s what they are for. When you’re using non-standard headers, prefix them with X- and maybe a prefix for your application, for example X-YourApp-Signature. The request goes into the body, i.e. the value of the request property in your example.
The server side is pretty simple with Symfony:
public function someAction(Request $request)
{
$signature = $request->headers->get("X-YourApp-Signature");
$data = json_decode($request->getContent());
// ... go on processing the received values (validation etc.)
}
If you want to write a HTTP client application in PHP, I would recommend using the Guzzle library. Here’s an example:
$headers = ["X-YourApp-Signature" => "your_signature_string"];
$data = json_encode(["foo" => "bar"]);
$request = new \GuzzleHttp\Psr7\Request("POST", "https://example.com", $headers, $data);
$client = new \GuzzleHttp\Client();
$response = $client->send($request, ["timeout" => 10]);
var_dump($response);
Of course, you’ll also want to implement some error handling etc. (HTTP status >= 400), so the code will be a bit more complex in a real application.
As k0pernikus mentioned, the before after filters solves my issue.
I'm having some trouble using gae php as a simple proxy using "file_get_contents"
When i load a file for the first time I get the latest version available.
But if I change the content of the file, I dont get the latest version immediately.
$result = file_get_contents('http://example.com/'.$url);
The temporary solution I found was to add a random variable at the end of the query string, which allowed me to get a fresh version of the file every time :
$result = file_get_contents('http://example.com/'.$url.'?r=' . rand(0, 9999));
But this trick doesn't work for api calls with parameters for example.
I tried disabling APC cache in the php.ini of gae (using apc.enabled = "0") and i used clearstatcache(); in my script, but neither work.
Any ideas ?
Thanks.
As described in the appengine documentation the http stream wrapper uses urlfetch. As seen in another question urlfetch provides a public/shared cache and as such does not allow individual apps to clear it. For your own services you can set the HTTP cache headers to reduce or void the cache as necessary.
Additionally, you can also add HTTP request headers indicating the maximum age of data that is allowed to be returned. The python example given in mailing list thread is:
result = urlfetch.fetch(url, headers = {'Cache-Control' : 'max-age=300'})
Per php.net file_get_contents http header example and HTTP header documentation a modified example would be:
<?php
$opts = [
'http' => [
'method' => 'GET',
'header' => "Cache-Control: max-age=60\r\n",
],
];
$context = stream_context_create($opts);
$file = file_get_contents('http://www.example.com/', false, $context);
?>
I've set up a REST service and client in PHP and I'm having a bit of trouble with PUT.
Here's my situation:
I'm coding a REST resource that should accept an array of data and an image. The REST resource should update an existing record, so I'm using PUT. I'm sending the data with a PHP curl client I wrote. So - pretty much the same situation as if you were sending a HTML multipart form to a PHP script that does a file upload and accepts some additional POST fields - except with PUT and PHP curl..
Up 'till now I've been sending the PUT request something like this (pseudo code):
$a_some_data = array('name' => 'test', 'user_id' => 4);
$body = http_build_query($a_data);
$fh = fopen('php://memory', 'rw');
fwrite($body);
rewind($fh);
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => 'http://myapi/resource/someid',
CURLOPT_PUT => TRUE,
CURLOPT_INFILE => $fh,
CURLOPT_INFILESIZE => strlen($body)
));
curl_exec($ch);
and reading the data on the server like so:
parse_str(file_get_contents('php://input'), $put_data);
..which works just fine.
So now I would like to add a (binary) file into the mix.
- How would I implement this on the client side?
- How would I deal with the file on the server?
For a test I set up a HTML form with a file input, copied the raw multipart/form-data request it sends, and tried sending that data as a file with curl in a PUT request. That kind of works, but I would have to parse the raw data on the server manually, which I'm not sure is the best idea. Alternatively, I guess I could send the file as the body of the PUT request, and add the other parameters in the URL as a query string - but I guess that kind of defies the point of a PUT REST resource..
Please share your thoughts on this.
Thanks!
There are at least two other ways unless your original version isn't enough (since libcurl should deal just fine with binary files too with that script). Note that how you decide receive the PUT in the receiving end is not a curl issue so I'll leave it out of this response.
1 - Like you started out, but provide a CURLOPT_READFUNCTION with which you feed the data to libcurl that it will send.
2 - Use CURLOPT_POSTFIELDS (with a string) and make it look like a POST, and then you change the HTTP method with CURLOPT_CUSTOMREQUEST to "PUT"
I am using PHP with the Amazon Payments web service. I'm having problems with some of my requests. Amazon is returning an error as it should, however the way it goes about it is giving me problems.
Amazon returns XML data with a message about the error, but it also throws an HTTP 400 (or even 404 sometimes). This makes file_get_contents() throw an error right away and I have no way to get the content. I've tried using cURL also, but never got it to give me back a response.
I really need a way to get the XML returned regardless of HTTP status code. It has an important "message" element that gives me clues as to why my billing requests are failing.
Does anyone have a cURL example or otherwise that will allow me to do this? All my requests currently use file_get_contents() but I am not opposed to changing them. Everyone else seems to think cURL is the "right" way.
You have to define custom stream context (3rd argument of function file_get_contents) with ignore_errors option on.
As a follow-up to DoubleThink's post, here is a working example:
$url = 'http://whatever.com';
//Set stream options
$opts = array(
'http' => array('ignore_errors' => true)
);
//Create the stream context
$context = stream_context_create($opts);
//Open the file using the defined context
$file = file_get_contents($url, false, $context);