How to send raw data with curl GET in PHP? - php

I am developing REST API and while it is easy to set raw JSON data for request in cURL for POST
$payload = json_encode(array("user" => $data));
//attach encoded JSON string to the POST fields
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
I cannot figure out how to send such data with GET requests.
Is there something like CURLOPT_GETFIELDS or CURLOPT_RAWDATA? The purpose of sending JSON with GET request is to pass in some params.
I do not wish to add formdata to the request, I wish to post JSON so that it can be parsed on the receiver.
Thanks!
EDIT:
based on comments I want to avoid confusion, so the resulting request should look like:
GET / HTTP/1.1
Host: 127.0.0.1:3000
Content-Type: application/json
Accept: application/json
Host: 127.0.0.1:3000
content-length: 13
Connection: keep-alive
cache-control: no-cache
{
"a": "b"
}
as you can see, GET request here has data and it is parsed and works perfectly by web server. How do I achieve this with cURL?

GET requests do not have a body, that's the whole idea: you're just getting something from the server, as opposed to posting something to it. From RFC 7231:
A payload within a GET request message has no defined semantics;
sending a payload body on a GET request might cause some existing
implementations to reject the request.
In other words, a GET request can have data, but it should not. From earlier in the spec, where GET is defined as a safe method:
Request methods are considered "safe" if their defined semantics are
essentially read-only; i.e., the client does not request, and does
not expect, any state change on the origin server as a result of
applying a safe method to a target resource.
...
Of the request methods defined by this specification, the GET, HEAD,
OPTIONS, and TRACE methods are defined to be safe.
If you really want to have JSON in your GET request (and send it to a reasonably implemented server resource) the only place it can go is in the URI as part of the query string. For GET requests I find using file_get_contents to be much easier than dealing with cURL.
<?php
$payload = json_encode(["user" => $data]);
$url_data = http_build_query([
"json" => $payload
]);
$url = "https://some.example/endpoint.php?" . $url_data;
$result = file_get_contents($url);
If you want to send it to an unreasonably implemented server resource, and violate the spirit of the HTTP RFCs, you could do this:
<?php
$url = "https://some.example/endpoint.php";
$payload = json_encode(["user" => $data]);
$ctx = stream_context_create(["http" => [
"header"=>"Content-Type: application/json",
"content"=>$payload
]]);
$result = file_get_contents($url, false, $ctx);
If you're determined to do this specifically with cURL, you might have luck with the CURLOPT_CUSTOMREQUEST option set to "GET" and CURLOPT_POSTDATA with your data.

Related

How does Laravel know Request::wantsJson is a request for JSON?

I noticed that Laravel has a neat method Request::wantsJson - I assume when I make the request I can pass information to request a JSON response, but how do I do this, and what criteria does Laravel use to detect whether a request asks for JSON ?
It uses the Accept header sent by the client to determine if it wants a JSON response.
Let's look at the code :
public function wantsJson() {
$acceptable = $this->getAcceptableContentTypes();
return isset($acceptable[0]) && $acceptable[0] == 'application/json';
}
So if the client sends a request with the first acceptable content type to application/json then the method will return true.
As for how to request JSON, you should set the Accept header accordingly, it depends on what library you use to query your route, here are some examples with libraries I know :
Guzzle (PHP):
GuzzleHttp\get("http://laravel/route", ["headers" => ["Accept" => "application/json"]]);
cURL (PHP) :
$curl = curl_init();
curl_setopt_array($curl, [CURLOPT_URL => "http://laravel/route", CURLOPT_HTTPHEADER => ["Accept" => "application/json"], CURLOPT_RETURNTRANSFER => true]);
curl_exec($curl);
Requests (Python) :
requests.get("http://laravel/route", headers={"Accept":"application/json"})

How do I check if a specific return type is accepted by the client?

I'm building an API, and I'd like to let my clients specify the content formats they accept in my responses (xml, json, etc).
I'd also like to do it using the Accept section of the request.
Somewhere in my code, I call the following line:
$request->getAcceptableContentTypes();
which returns
array (size=5)
0 => string 'text/html' (length=9)
1 => string 'application/xhtml+xml' (length=21)
2 => string 'image/webp' (length=10)
3 => string 'application/xml' (length=15)
4 => string '*/*' (length=3)
This specific request accepts both xml and json returns... but I'm not sure how to verify that. A XML response is allowed (both by */*, application/xhtml+xml and application/xml), but its primary MIME type would be text/xml, which is not explicitly written above. Same thing applies to application/json, which is allowed by */*, but isn't explictly written.
Should I map all the possible mimetypes to its equivalents, should I enforce a specific and explict mimetype definition on my client or is there a more elegant way to do that?
EDIT: For clarification: I get Accepts: application/xhtml+xml. I'm only prepared to answer with Content-Type: text/xml. Should I throw an exception? Or should I explode the second part (xhtml+xml) and return text/xml anyway, due to the similarity of what's accepted and what I can answer? If so, how do I know they're "similar"? Will I need a lookup table? Can I disregard the first part (application)?
This process is called content negotiation.
The representation (html, json or xml) returned by a API web service is determined by the URL or by the Accept HTTP header.
Let's start to view at this from client-side:
GET http://api.company.com/products/10 HTTP/1.1
Host: http://api.company.com
Accept: application/json, text/javascript, */*; q=0.01
The server responds with
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: ...
Connection: Close
{"Id":1,"ProductName":"Helicopter","Category":"Aircraft","Price":5000}
Question: How do i check, if a specific return type is accepted by the client?"
Answer: by evaluating the accept header send by the client.
In the GET-request the client defines multiple accept headers (in order) (with preference q). The Accept-Header lists the mime-types that the client is willing to accept.
The tasks of the server would be to look at the available representations it has and serve the one with the highest preference to the client.
You have to evaluate the "Accept"-headers and the "q"-weight and send an appropriate representation to the client, indicating the type in the Content-Type - header.
The "q" argument in the Accept header is float between 0 and 1. It's a weight, indicating the preference for that media type. A higher number inidicates a higher preference.
The offical term is "relative quality factor".
The RFC describes several examples of how the preference should be understand.
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
It's not easy. Some of these things are "overlapping".
Keep it easy. Focus on "text/html", "application/json", "application/xml".
How can one implement that on server side?
It is a good practice to have a Negotiation class.
Let's say: the client requests a valid resource, but with unknown Accept header.
GET http://api.company.com/products/10 HTTP/1.1
Host: http://api.company.com
Accept: application/karamba
The request comes to the server.
The resource /product/10 is found - it's valid. The server has data.
Now "application/karamba" arrives, but there is not content-formatter for that.
The server can't deliver the format the client wants it's data in.
The server has to send an error 406 (Not Acceptable) as Response.
The workflow is roughly like this:
Request Object with a method preferedResponseType() to evaluate the Accept Header
or like you have it already $request->getAcceptableContentTypes()
good lib: http://williamdurand.fr/Negotiation/
instantiate an Response object, based on that type.
you might do that by mapping part of the string from the accept header to a class.
JsonResponse
XmlResponse
HtmlResponse
if, there isn't a response object for this type (it's unknown)
send an ErrorResponse. EXIT
fetch data from db
insert db results into response object, maybe JsonResponse()
JsonResponse()->flush() send it...
The essential part is the mapping from Accept Header to a Class which can answer that request.
Finally, you have to make a decision, if you really want to support many Response types.
Ok, so much theory. Let's take a look at a live API: GitHub.
Request to https://api.github.com/users/caspyin
browser based GET request to the API endpoint
with Accept Header text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Response is JSON send with Content-Type:application/json; charset=utf-8
Wait. What? Why?
Github responses are JSON, because they decided to keep it simple.
They expose their accepted mime-types here: https://developer.github.com/v3/media/#request-specific-version
Update (2014-09-29)
Check out this new library: https://github.com/auraphp/Aura.Accept
Especially: https://github.com/auraphp/Aura.Accept/blob/master/tests/unit/src/MediaTest.php
You need to do pattern matching not equality checking:
application/xml+json != application/json
application/xml+json != application/xml
But application/json matches application/xml+json
One thing you could do is flatten accept headers with + and do consecutive matching if only there is no */* till you find an acceptable format else return 406 Not Acceptable.
<?php
$accept = [
'text/html',
'application/json+xml',
'image/webp',
'application/xml',
'*/*'
];
function flattenAccept($accept) {
$newAccpet = [];
foreach ($accept as $mimeType) {
if (strpos($mimeType, '+') !== false) { // Contains +
$arr = explode('/', $mimeType);
$type = $arr[0];
$medias = explode('+', $arr[1]);
foreach ($medias as $media) {
array_push($newAccpet, $type."/".$media); // Flatten
}
} else {
array_push($newAccpet, $mimeType);
}
}
return array_unique($newAccpet);
}
function matching($mimeType, $accept) {
if (in_array("*/*", $accept)) return true;
return in_array($mimeType, $accept);
}
$newAccept = flattenAccept($accept);
var_dump($newAccept);
var_dump(matching("application/json", $newAccept));
Result
array(5) {
[0] =>
string(9) "text/html"
[1] =>
string(16) "application/json"
[2] =>
string(15) "application/xml"
[3] =>
string(10) "image/webp"
[5] =>
string(3) "*/*"
}
bool(true)
Most APIs which allow multiple MIME type responses force the client to specify the response format. These are usually done in one of 2 ways: either as a query parameter ('http://exampleAPI.com/getAllUsers?json') or as a request header ('Accept: application/json').
Requiring this from the client will give you explicit values for the accepted MIME types and you can decide if you'd like to respond with an error or apply a default setting when it's missing or incorrect.
This solution won't give your client an overhead either, as in a generic client-side implementation, you would create a wrapper class to automate authentication and maybe to organise your requests and post-process your responses in certain cases.
For future reference, I'd like to point out an alternative and less expensive way to accomplish what I asked on this question.
Hoa/Mime is a library that does just what I asked. It had not been releasead at the time of the question.
print_r(Hoa\Mime\Mime::getExtensionsFromMime('text/html')); will print
Array
(
[0] => html
[1] => htm
)
Similarly, var_dump(Hoa\Mime\Mime::getMimeFromExtension('webm')); will print string(10) "video/webm".
A Mime object also has the methods getExtension() and getOtherExtensions(), so I can check for alternative acceptable content-types, without coming up with my own lookup table.
This is a little more complete than #Issam Zoli's answer, as it provides room for returning, for example, text/xml when an application/xml is requested. His answer is great for a lower-level understanding of what must be done, if one wishes to implement on his own, instead of using a third-party bundle.

Twitter 1.1 OAuth authenticity_token_error(99)

I use the following code to get the bearer token:
$token = base64_encode($client_id.':'.$client_sec);
$data = array ('grant_type' => 'client_credentials');
$data = http_build_query($data);
$header = array(
'Authorization: Basic '.$token,
'Content-type: application/x-www-form-urlencoded;charset=UTF-8',
'Content-Length: ' . strlen($data)
);
$options = array(
CURLOPT_HTTPHEADER => $header,
CURLOPT_HEADER => false,
CURLOPT_URL => 'https://api.twitter.com/oauth2/token',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => $data
);
$ch = curl_init();
curl_setopt_array($ch, $options);
$result = curl_exec($ch);
curl_close($ch);
print_r($result);
exit;
But output all the time:
{"errors":[{"label":"authenticity_token_error","code":99,"message":"Unable to verify your credentials"}]}
What I doing wrong?
After fighting with this problem for a while I found the problem was I was making the call to /oauth2/token using Advanced Rest Client from a browser I was already logged into Twitter with. After logging out of Twitter and making the API call again it worked fine.
Short answer: make sure you do not already have an active session logged into Twitter when attempting to request a Bearer token.
I struggled with this for awhile and none of the answers I've found seemed to help. The documentation for the error is also a vague "something went wrong".
My problem is that I was using a mashup of code I found, and the headers weren't used correctly:
$headers = array(
'Authorization' => 'Basic ' . base64_encode($appid . ':' . $secret), // WRONG!!!
'Authorization: Basic ' . base64_encode($appid . ':' . $secret), // Correct!
'Content-Type: application/x-www-form-urlencoded;charset=UTF-8', // Correct!
);
For me, the problem was that the Authorization header was using key-value format, while the content-type header was not. This broke the authorization header.
Here are some other things to check that also relate to error 99:
Verify that your credentials are correct and base64 encoded (see above)
Make sure the request is using POST
Ensure the content-type is set (see above)
Make sure you included grant_type=client_credentials as a post field.
SSL is required, make sure that is being used (https://)
Try verbose logging to help debugging. It should include SSL certificate information, your authorization header, and content type header. This won't show the grant_type field though, only headers.
If everything looks OK but it still won't work, you might be getting rate limited. Rate limits reset every 15 minutes.
When you finally get your access token, make sure you cache it to avoid rate limiting. You get 450 requests every 15 minutes, I believe. Half of that will be spent on getting your access token if you don't cache it!
There's an accepted answer here already but just in case someone stroll to this post and had the same issue I did...
Twitter docs for reference -> OAuth 2.0 docs
Misconception #1: The Authorization String is generated using the consumer key (aka API-Key) and consumer secret (aka API Secret Key). The display of those credentials in the UI on developer.twitter.com is less apparent than that of apps.twitter.com. Nonetheless common RIF problem.
Misconception #2: This one is not really an misconception but an implementation error when base64 encoding the url concatenated Consumer Key+":"+Consumer Secret. If not doing this programmatically be sure to check for whitespaces anywhere (especially around the :) in the concatenated string you are base64 encoding.
Just a tad bit advice as well postman has a wonderful utility that makes the rest call to retrieve an oauth2.0 token (as well as other auth tokens) this was useful for me when trying to consume api's with the that required an oauth1.0 token
After fighting with this problem, i finally come up with the solution. Twitter is not actually sending the right message if error exist anywhere.
When i send request from curl, it works fine but when through code. i was having same error {"errors":[{"label":"authenticity_token_error","code":99,"message":"Unable to verify your credentials"}]}
So what i got, problem was lying with Access control header. setting these header does not work for me
xhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded")
xhttp.setRequestHeader('Access-Control-Allow-Headers', '*');
xhttp.setRequestHeader('Access-Control-Allow-Origin', '*')
xhttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
as a workaround i just used this url to bypass request to handler cors
https://cors-anywhere.herokuapp.com/https://api.twitter.com/oauth2/token
added "https://cors-anywhere.herokuapp.com/" before the actual url and it began to work. hope someone may face this issue in problem
Twitter OAuth 2.0 Bearer Token:
Step 1: Encode consumer key and secret
A - Concatenate the encoded consumer key, a colon character ":", and the encoded consumer secret into a single string.
B - Base64 encode the string from the previous step.
Example function: convertStringBase64("Api key" +":"+"Api key secret")
C- This steps generate you "Authorization"
Step 2: Obtain a Bearer Token
URL: https://api.twitter.com/oauth2/token
The request must be an HTTP POST request.
The request must include an Authorization header with the value of Basic <base64 encoded value from step 1>.
The request must include a Content-Type header with the value of application/x-www-form-urlencoded;charset=UTF-8.
The body of the request must be grant_type=client_credentials.
Example request (Authorization header has been wrapped):
POST /oauth2/token HTTP/1.1
Host: api.twitter.com
User-Agent: My Twitter App v1.0.23
Authorization: Basic eHZ6MWV2RlM0d0VFUFRHRUZQSEJvZzpMOHFxOVBaeVJn
NmllS0dFS2hab2xHQzB2SldMdzhpRUo4OERSZHlPZw==
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Content-Length: 29
Accept-Encoding: gzip
grant_type=client_credentials
If the request was formatted correctly, the server would respond with a JSON-encoded payload:
Example response:
HTTP/1.1 200 OK
Status: 200 OK
Content-Type: application/json; charset=utf-8
...
Content-Encoding: gzip
Content-Length: 140
{"token_type":"bearer","access_token":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2FAAAAAAAAAAAAAAAAAAAA%3DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}
For more informtaion look this in Twitter Developer API: https://developer.twitter.com/en/docs/authentication/oauth-2-0/application-only

Requesting JSON from PHP

I am trying to retrieve data from a server which usually returns it in XML, however I trying to request it in a JSON format (if requested correctly it will return the data in JSON).
$header = array(
'http' => array(
'header'=>"Content-type: application/json"
),
);
$response = file_get_contents($query, false, $header);
print_r($response);
This approach was taken from here. Currently the program does not return anything.
Does anyone spot any potential problems with this?
You need to set the HTTP Accept header to tell the server that you want it to give you JSON:
Accept: application/json
(assuming that the remote server is correctly implemented to read the header)
The Content-Type request header indicates the type of the payload that you are POSTing.
In your case, it does not apply, since you're sending a GET request.

Http response header and body not correct as expected

I am working on proxy with PHP. In my php code I am sending proper required headers and expected to get response body and headers. However I am getting response body correctly as I want but not getting headers properly (supposed to get Status 200 but getting 401). When i traced with firefox I found that SAP URL itsself making 2 request internally by using data which I send. so with my first request it is not authenticated so SAP url itslef managining to send same request again and 2nd time it gives both proper response body with headers. Howevber I php code when I get it i get response body from 2nd response and headers from 1st response.
here is code.
$opts = array(
'http'=>array(
'method'=>"POST",
'content' => $xml_request,
'header'=>array("Host:" . $sap_url,
"Content-Type: text/xml; charset=UTF-8",
$authstring,$xml_request)
)
);
$context = stream_context_create($opts);
$result = file_get_contents($sap_url, false, $context);
$http_res_array = get_headers($sap_url);
You should probably use curl functions instead and do BOTH requests yourself. file_get_contents, does the second request for you, but takes away the possibility to fetch the second headers.
Maybe a little old but anyways:
You're using the get_headers()-function to get the headers. It's documentation states that:
Fetches all the headers sent by the server in response to a [new] HTTP request
It doesn't empathize that this function will actually send a new request to the server and return the response-header for that request. Therefor, the headers can be slightly different.
Since you're using file_get_contents() to load the content, you can use the global $http_response_header-variable right after your request, which will contain the response-header from the last executed request.

Categories