Zf2 - How to create request to external API with file upload - rest

I have a Zf2 application that communicates with another Zf2 application through RestAPI calls.
I'm able to communicate between one to another using following code and exchange parameters:
//Prepare request
$request = new Request();
$request->getHeaders()->addHeaders(array(
'Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8'
));
$request->setUri($p_url);
$request->setMethod('POST');
$request->setPost(new Parameters($p_params));
$client = new Client();
//Send request
$client->resetParameters();
$response = $client->dispatch($request);
$data = json_decode($response->getBody(), true);
Now, I would like to do the same thing but with a multipart call: Json + files.
How can I do that?
I have tried several solutions from using setFileUpload method of client to writing headers parameters with content-type (multipart/form-data), content-disposition, ... without success.
Along my tests, I used Wireshark to check the request contents. Depending on the solution I tried, I fail in situation with "missing boundary" or HTTP error 405.
Thanks for your help.
Best

Finally, I found a solution
$this->_client->setUri($p_url);
$this->_client->setMethod('POST');
//Prepare for upload
$this->_client->setFileUpload($p_file, 'file');
//Set parameters along with file
$this->_client->setParameterPost($p_params);
//Send request
try {
$response = $this->_client->send();
} catch ( \Exception $ex ) {
}

Related

Postman Oauth 1.0 signature does not match what I get in Perl

I'm trying to authenticate to the Here.com API via Oauth 1.0. I have a working Postman request that successfully gets an authentication token. I tried implementing the signature-generating procedure in Perl, but the signature I get is different from the one Postman gets. I also checked this sandbox using the same parameters, and it seem to generate the same signature I get, however this signature fails and the one that Postman generates succeeds, and I honestly cannot figure out why. Let me share some examples:
This is the code I'm playing with in Perl, with test parameters:
use URI::Encode;
use Digest::SHA qw(hmac_sha256 hmac_sha1 hmac_sha256_base64);
use MIME::Base64;
my $oauth_values = {
consumer_key => "my-test-key",
consumer_secret => "my-test-secret",
request_url => "https://account.api.here.com/oauth2/token",
request_method => "POST",
signature_method => "HMAC-SHA256",
timestamp => 1234567890,
nonce => "test-nonce",
protocol_version => "1.0",
};
my $encoder = URI::Encode->new( { encode_reserved => 1 } );
my $signature_base_string = "oauth_consumer_key=$oauth_values->{consumer_key}" .
"&oauth_nonce=$oauth_values->{nonce}" .
"&oauth_signature_method=$oauth_values->{signature_method}" .
"&oauth_timestamp=$oauth_values->{timestamp}" .
"&oauth_version=$oauth_values->{protocol_version}";
my $base_string = "POST&" . $encoder->encode($oauth_values->{request_url}) . "&" . $encoder->encode($signature_base_string);
print "Signature base string: $signature_base_string\n";
print "Base string: $base_string\n";
my $signature = encode_base64(hmac_sha256($base_string, $oauth_values->{consumer_secret} . "&"));
print "Signature: $signature\n";
Note that the params on the base string are in alphabetical order, as they should.
The output I get from that code is the following:
Signature base string: oauth_consumer_key=my-test-key&oauth_nonce=test-nonce&oauth_signature_method=HMAC-SHA256&oauth_timestamp=1234567890&oauth_version=1.0
Base string: POST&https%3A%2F%2Faccount.api.here.com%2Foauth2%2Ftoken&oauth_consumer_key%3Dmy-test-key%26oauth_nonce%3Dtest-nonce%26oauth_signature_method%3DHMAC-SHA256%26oauth_timestamp%3D1234567890%26oauth_version%3D1.0
Signature: KOXTa8e/Vw083CAwZctnZiJVIvAjH1aw4/5RWXeIhX4=
If I put these same parameters on the sandbox, I get the same signature:
sandbox signature screenshot
However, when I put the exact same parameters in Postman:
Postman parameter settings
then the signature that Postman generates is different:
OxhDiuqUEBAd45vNn4zIy/0etSVOn2fvquw+kQMxwsg=
This is what I get when I generate the code snippet in Python:
import requests
url = "https://account.api.here.com/oauth2/token"
payload = ""
headers = {
'Authorization': 'OAuth oauth_consumer_key="my-test-key",oauth_signature_method="HMAC-SHA256",oauth_timestamp="1234567890",oauth_nonce="test-nonce",oauth_version="1.0",oauth_signature="OxhDiuqUEBAd45vNn4zIy/0etSVOn2fvquw+kQMxwsg="'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
I've even read the RFC with the steps, and I cannot spot an error in my method.
So how come that Postman signature is correct and the one I generate is not? What am I missing or doing wrong?
I was able to figure it out. It turns out that encode_base64 is adding a new line at the end, which was breaking the signature I sent in the request. Adding chomp right after it fixed it.
Also, the difference I was getting between Postman and Perl signatures was due to the way Postman calculates the base string when the URL is parametrised, apparently, as switching to a "hardcoded" URL made the signatures coincide. Surprisingly, in both cases Postman is able to get an auth token, which leads me to think that the signature I get when I peek into the "Code" option in Postman is not necessarily the same one that is being sent in the request.
In a nutshell, doing this works:
my $digest = encode_base64(hmac_sha256($base_string, $oauth_values->{consumer_secret} . "&"));
chomp $digest;
my $signature_encoded = $encoder->encode($digest);

How to send form data in slim framework v3 in PUT routing

I am very new in slim framework and i am using slim V3 i have done post route and it works fine but when i try to update record with put method it will works with Content-type = application/x-www-form-urlencoded and update my record with success
when i try to send file into slim api with POSTMAN Chrome Extension it will not sending file with form data request.
Here is my code
$app->put('/message/{message_id}', function ($request, $response, $args)
{
$imagePath = '';
$data = $request->getParsedBody();
$files = $request->getUploadedFiles();
$file = $files['file'];
if ($file->getError() == UPLOAD_ERR_OK ) {
$filename = $file->getClientFilename();
$file->moveTo('assets/images/'.$filename);
$imagePath = 'assets/images/'.$filename;
}
$message = Message::find($args['message_id']);
$message->body = $data['message'];
$message->user_id = $data['user_id'];
$message->image_url = $imagePath;
$message->save();
if ($message->id) {
return $response->withStatus(200)->withJson([
'message_id' => $message->id,
'message_uri' => '/message/'.$message->id,
]);
}else{
return $response->withStatus(400)->withJson(['message'=>'something went wrong!']);
}
});
When you want to upload a file with postman you need to remove or disable the Content-Type inside the header.

Guzzle returning a 404 on a valid URL

I'm using Guzzle with CurlAuthPlugin to authenticate. When I run the code, I get
Client error response\ [status code] 404\ [reason phrase] Not Found\ [url] https:\/\/api.buto.tv\/v2\/video\/tag\/v2\/video\/tag\/
The code I'm using is:
$client = new Client(<URL>);
// Add the auth plugin to the client object
$authPlugin = new CurlAuthPlugin(<APIKEY>, 'x');
$client->addSubscriber($authPlugin);
$response = $client->get('v2/video/tag/')->send();
But the URL is perfectly valid a I can paste that in to a browser and it works fine
I've also tried:
$client = new Client('https://api.buto.tv');
$request = $client->get('v2/video/tag/');
$request->setAuth('user', 'pass');
$response = $request->send();
But I get the same error. I have output the URL it's requesting with echo $request->getUrl(); and if I copy and paste the URL in to a browser, the URL is fine
I think you may be missing a slash '/' after api.buto.tv, so the url is resolving to 'https://api.buto.tvv2/video/tag/' instead of 'https://api.buto.tv/v2/video/tag/'.
$client = new Client('https://api.buto.tv/');
$request = $client->get('v2/video/tag/');

How to match a result to a request when sending multiple requests?

A. Summary
As its title, Guzzle allows to send multiple requests at once to save time, as in documentation.
$responses = $client->send(array(
$requestObj1,
$requestObj2,
...
));
(given that each request object is an instance of
Guzzle\Http\Message\EntityEnclosingRequestInterface)
When responses come back, to identify which response is for which request, we can loop through each request and get the response (only available after executing the above command):
$response1 = $requestObj1->getResponse();
$response2 = $requestObj2->getResponse();
...
B. Problem
If the request object contains the same data. It's impossible to identify the original request.
Assume we have the following scenario where we need to create 2 articles: A and B on a distance server: something.com/articles/create.json
Each request has same POST data:
subject: This is a test article
After created, the Guzzle responses with 2 location come back:
something.com/articles/223.json
something.com/articles/245.json
Using the above method to link response-to-its-request, we still don't know which response is for which article, because the request object is exactly the same.
Hence in my database I cannot write down the result:
article A -> Location: 245.json
article B -> Location: 223.json
because it can be the other way arround:
article A -> Location: 223.json
article B -> Location: 245.json
A solution is to put some extra parameter in the POST request, e.g.
subject: This is a test article
record: A
However, the distance server will return error and does not create article because it does not understand the key "record". The distance server is a third party server and I cannot change the way it works.
Another proper solution for this is to set some specific id/tag on the request object, so we can identify it afterwards. However, I've looked through the documentation but there is no method to uniquely identity the request like
$request->setID("id1")
or
$request->setTag("id1")
This has been bugging me for months and still cannot resolve this issue.
If you have solution, please let me know.
Many many thanks and you've saved me!!!!
Thanks for reading this long post.
I've found a proper way to do it, Guzzle allow to add callback once a request is completed. So we can achieve this by setting it on each request in the batch
Each request by default can be created like this
$request = $client->createRequest('GET', 'http://httpbin.org', [
'headers' => ['X-Foo' => 'Bar']
]);
So, to achieve what we want:
$allRequests = [];
$allResults = [];
for($k=0; $k<=10; $k++){
$allRequests['key_'.$k] = $client->createRequest('GET', 'http://httpbin.org?id='.$k, [
'headers' => ['X-Foo' => 'Bar'],
'events' => [
'complete' => function ($e) use (&$allResults, $k){
$response = $e->getResponse();
$allResults['key_'.$k] = $response->getBody().'';
}
]
]);
}
$client->sendAll(array_values($allRequests));
print_r($allResults);
So now the $allResults has result for each corresponding request.
e.g. $allResults['key_1'] is the result of $allRequests['key_1']
I was having the same problem with this.
I solved it by adding a custom query parameter with a unique id generated for each request and add it to the request url (you will need to remember this ids for each one of them to address it after).
After $responses = $client->send($requests) you could iterate through the responses and retrieve the effective url $response->getEffectiveUrl() and parse it (see parse_url and parse_str) to get the custom parameter (with the unique id) and search in your array of requests which one has it.
I found a much better answer.
I was sending batches of 20 requests at a time, 4 concurrently, and used the pooling technique where I got fulfilled, and rejected back, as in the documentation.
I found that I could add this code to the end of my requestAsync() function calls, when yielding / building the array (I do both in different places).
$request = $request->then(function (\GuzzleHttp\Psr7\Response $response) use ($source_db_object) {
$response->_source_object = $source_db_object;
return $response;
});
And then in the clousures on the pool, I can just access the _source_object on the response normally, and it works great.
I find it a little hacky, but if you are just sure to use a name that NEVER clashes with anything in Guzzle, this should be fine.
Here is a full example:
use GuzzleHttp\Client;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Response as GuzzleResponse;
$client = new Client();
$requests = [];
// Simple set-up here, generate some random async requests
for ($i = 0; $i < 10; $i++) {
$request = $client->requestAsync('GET', 'https://jsonplaceholder.typicode.com/todos/1');
// Here we can attach any identifiable data
$request->_source_object = $i;
array_push($requests, $request);
}
$generator = function () use($requests) {
while ($request = array_pop($requests)) {
yield function() use ($request) {
return $request->then(function (GuzzleResponse $response) use ($request) {
// Attach _source_object from request to the response
$response->_source_object = $request->_source_object ?? [];
return $response;
});
};
}
};
$requestPool = new Pool($client, $generator(), [
'concurrency' => 5,
'fulfilled' => function ($response) {
// Then we can properly access the _source_object data once response has arrived here!
echo $response->_source_object . "\n";
}
]);
$requestPool->promise()->wait();
I do it this way :
// create your requests
$requests[] = $client->createRequest('GET', '/endpoint', ['config' => ['order_id' => 123]]);
...
// in your success callback get
$id = $event->getRequest()->getConfig()['order_id']
An update related to the new GuzzleHttp guzzlehttp/guzzle
Concurrent/parallel calls are now run through a few different methods including Promises.. Concurrent Requests
The old way of passing a array of RequestInterfaces will not work anymore.
See example here
$newClient = new \GuzzleHttp\Client(['base_uri' => $base]);
foreach($documents->documents as $doc){
$params = [
'language' =>'eng',
'text' => $doc->summary,
'apikey' => $key
];
$requestArr[$doc->reference] = $newClient->getAsync( '/1/api/sync/analyze/v1?' . http_build_query( $params) );
}
$time_start = microtime(true);
$responses = \GuzzleHttp\Promise\unwrap($requestArr); //$newClient->send( $requestArr );
$time_end = microtime(true);
$this->get('logger')->error(' NewsPerf Dev: took ' . ($time_end - $time_start) );
In this example you will be able to refer to each of the Responses using $requestArr[$doc->reference] . In short give an index to your array and run the Promise::unwrap call.
I also had come across this issue. This was the first thread coming up. I know this is a resolved thread, but I have eventually come up with a better solution. This is for all those who might encounter the issue.
One of the options is to use Guzzle Pool::batch.
What batch does is, it pushed the results of pooled requests into an array and returns the array. This ensures that the response and requests are in the same order.
$client = new Client();
// Create the requests
$requests = function ($total) use($client) {
for ($i = 1; $i <= $total; $i++) {
yield new Request('GET', 'http://www.example.com/foo' . $i);
}
};
// Use the Pool::batch()
$pool_batch = Pool::batch($client, $requests(5));
foreach ($pool_batch as $pool => $res) {
if ($res instanceof RequestException) {
// Do sth
continue;
}
// Do sth
}

Adding authHeader to Perl SOAP::Lite request

I am having some trouble creating a request to this WSDL that works; it requires authHeaders and I am not having much luck adding them. This is what I am trying:
# make proxy for the service
my $soap = SOAP::Lite->service($wsdl);
# add fault hanlder
$soap->on_fault(
sub { # SOAP fault handler
my $soap = shift;
my $res = shift;
# Map faults to exceptions
if(ref($res) eq '') {
die($res);
}
else {
die($res->faultstring);
}
return new SOAP::SOM;
}
);
# authentication request headers
my #headers = (
SOAP::Header->name('user')->value('myemail#whatever.com')->uri($apins),
SOAP::Header->name('password')->value('mypassword')->uri($apins),
SOAP::Header->name('appName')->value('TestApp')->uri($apins),
SOAP::Header->name('appVersion')->value('0.02')->uri($apins)
);
# request method
print $soap->getCompanyInfo('NB', #headers);
The response I get when doing this is:
String value expected instead of SOAP::Header reference
The method I am requesting has two string parameters, both optional. And suggestions?
I was able to get help form the SOAP::Lite mailing list. If I want to pass my own headers, I have to use the call method instead of the actually method name.
# create header for requests
my $authHeader = SOAP::Header->name("xsd:authHeader" =>
\SOAP::Header->value(
SOAP::Header->name('xsd:user')->value($s7user)->type(''),
SOAP::Header->name('xsd:password')->value($s7pass)->type(''),
SOAP::Header->name('xsd:appName')->value('TestApp')->type(''),
SOAP::Header->name('xsd:appVersion')->value('0.03')->type('')
));
# create data to pass as method paramaters
my $params = SOAP::Data->name('ns:email')->value($s7user)->type('');
# request method
$soap->call('checkLogin', $params, $authHeader);
In order to use the call method, you will need to define a proxy (endpoint) on your soap object. Hope this is helpful for someone else down the road.