I am learning to write my first IOS app that will query some basic OIDs from a Proliphix IP Network Thermostat. Proliphix supports several methods such as Curl; PHP and API GET & SET. Out of these methods, what would be the easiest in Swift?
Can someone tell me how to convert one of the the following methods for Swift?
Here are examples of these from the Proliphix API that can be found on a google search.
Curl
Get
curl –u hostname:password –-data OID1.1= http://192.168.1.100:8100/get
Set
curl –u hostname:password --data OID1.10.5=120 --data submit=Submit
http://192.168.1.100:8100/pdp
API GET
The URL used is /get. An API GET request is a list of OIDs where their value is not specified. A properly formatted request should provide the Content-Length header. . The entry is the encoded basic authentication word (See RFC 2617 -HTTP Authentication: Basic and Digest Access Authentication).
Request
POST /get HTTP/1.1
Authorization: Basic <credentials>
Content-Type: application/x-www-form-urlencoded
User-Agent: Jakarta Commons-HttpClient/2.0.2
Host: 192.168.111.114:8214
Content-Length: 92
OID1.10.9=&OID1.2=&OID1.1=&OID1.4=&OID1.8=&OID2.7.1=&
Response
HTTP/1.1 200 OK
Cache-control: no-cache
Server: Ubicom/1.1
Content-Length: 166
OID1.10.9=example#proliphix.com&OID1.2=SW Dev 114&OID1.1=therm_rev_2 0.1.40&OID1.4=192.168.111.114&OID1.8=00:11:49:00:00:58&OID2.7.1=NT100
API SET
The URL used is /pdp . An API SET is similar to the API GET for the request message, except that the desired value is provided at the equals sign. The response is formatted differently. The entry is the encoded basic authentication word (See RFC 2617 -HTTP Authentication: Basic and Digest Access Authentication). The last item in the request must be “submit=Submit”. Do not include an ‘&’ after the "submit=Submit".
Request
POST /pdp HTTP/1.1
Authorization: Basic <credentials>
Content-Type: application/x-www-form-urlencoded
User-Agent: Jakarta Commons-HttpClient/2.0.2
Host: 192.168.111.114:8214
Content-Length: 193
Response
HTTP/1.1 200 OK
Cache-control: no-cache
Server: Ubicom/1.1
Content-Length: 308
PHP
PHP is a web-server specific scripting language, akin to mod_perl. It integrates well into Apache and offer many web-specific libraries as part of the base system.
Get
$oids = array('OID1.4'=>'', // commonIpAddr
'OID1.10.5'=>'',
‘submit’=>’Submit’); // commonCallhomeInterval
$url = “http://192.168.1.100:8100/get”;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPGET, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$myHeader = array("Content-Type: application/x-www-form-urlencoded" ); curl_setopt($ch, CURLOPT_HTTPHEADER, $myHeader);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($oids)); $response = curl_exec($ch);
curl_close($ch);
$oids = array();
parse_str($response, $oids); // converts '.' to underscore
$localip = $oids['OID1_4'];
$interval = $oids['OID1_10_5']; // in minutes
I would say that you should use the API that Proliphix is providing.
As you can see, they provide an example, and you've already managed to figure out how to provide the correct parameters through cURL so now you "just" need to convert this to Swift.
For this you need a HTTP networking API, you could use either the NSURLSession API provided by Apple, or perhaps Alamofire, just to mention a pair.
These API's take an URL which would be /get or /pdp in your case. Then you need to tell them wether this is a GET or a POST request. If the API needs any data (like the OID parameters in your case), you'll need to provide that as well and then you need to set up eventual headers.
Then you send your request and wait for an answer, which you then react to.
Here is an example on how to do this with NSURLSession:
if let url = NSURL(string: "http://httpbin.org/post"){
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST" //Or GET if that's what you need
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") //This is where you add your HTTP headers like Content-Type, Accept and so on
let params = ["OID1.2" : "SW+Dev+114", "OID1.4" : "192.168.111.114"] as Dictionary<String, String> //this is where you add your parameters
let httpData = NSKeyedArchiver.archivedDataWithRootObject(params) //you need to convert you parameters to NSData or to JSON data if the service accepts this, you might want to search for a solution on how to do this...hopefully this will get you in the right direction :-)
request.HTTPBody = httpData
let session = NSURLSession.sharedSession()
session.dataTaskWithRequest(request, completionHandler: { (returnData, response, error) -> Void in
var strData = NSString(data: returnData, encoding: NSUTF8StringEncoding)
println("\(strData)")
}).resume() //Remember this one or nothing will happen :-)
}
Hope this gets you in the right direction. You could also do a Google search for NSURLSession or Alamofire tutorial, now that you know what to search for.
Related
I've read many question on stackoverflow about passing parameters with GET and POST methods, but none of them satisfied my curiosity.
I use GETs to retrieve data passing parameters as path/query params and use POSTs to save data passing parameters as request body with Content-Type: application/json and sometimes request body + path/query params (depending on other APIs I've created for simmetry purpose only).
My question is when to use Content-Type: application/x-www-form-urlencoded. Say I want to call a POST without a request body of Content-Type: application/json but with params, do I have to use the application/x-www-form-urlencoded Content-Type or I can use path/query params as best practice?
What's the difference between sending data as path/query params and key-value params with Content-Type: application/x-www-form-urlencoded? Does the data size have to do with it?
What's the difference between sending data as path/query params and key-value params with Content-Type: application/x-www-form-urlencoded?
Consider this example
PUT /example?a=b&c=d
Content-Type: application/x-www-form-urlencoded
d=e&f=g
What's happening here? This is a request that the server replace its current representation of the resource identified by
/example?a=b&c=d
With the payload. In other words, after successful processing, we would expect
GET /example?a=b&c=d
to produce a response like
200 OK
Content-Type: application/x-www-form-urlencoded
d=e&f=g
POST /example?a=b&c=d
Content-Type: application/x-www-form-urlencoded
d=e&f=g
For POST, it's the same idea, except that instead of "replace the current representation", POST stands in for "process the payload according to the specific semantics of /example?a=b&c=d".
GETs to retrieve data passing parameters as path/query params
It might help to reframe your thinking here. We use GET to retrieve the current representation of a resource. We aren't passing parameters, we're passing a document (resource) identifier.
The fact that the origin server's implementation is going to parse that identifier is an implementation detail.
A URI Template is a compact sequence of characters for describing a
range of Uniform Resource Identifiers through variable expansion.
-- RFC 6570
I want to call a POST without a request body of Content-Type: application/json but with params, do I have to use the application/x-www-form-urlencoded Content-Type or I can use path/query params as best practice?
POST /example?a=b&c=d
Content-Type: application/json
{"d":"e","f":"g"}
Is a perfectly normal HTTP request.
Content-Type describes the payload only - it has nothing at all to do with the target URI, and how information might be encoded within it.
My web service uses JWT-based authorization bearer token authentication:
HTTP clients send a valid POST to /v1/auth/signIn with a valid JSON request entity (includes username + password info)
If they authenticate successfully, that endpoint sends back an auth bearer token as an HTTP response header that (from curl) looks like:
Response from curl:
HTTP/1.1 200 OK
Date: Tue, 04 Sep 2018 01:18:28 GMT
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Access-Control-Expose-Headers: Authorization
Authorization: Bearer <big_huge_string>
Content-Length: 0
Subsequent service calls to authenticated endpoints just need to include the token as an HTTP request header whose key/name is Authorization and whose value is "Bearer <xyz>" (where <xyz>) is the <big_huge_string> that came back on the sign in call above. Pretty basic standard JWT stuff.
I'm trying to write a Postman collection that starts with a "Sign In Request" that successfully signs in and gets a JWT token from the service, and then adds the appropriate HTTP request header in each subsequent call. Any ideas as to how I can:
Extract the <big_huge_string> off the HTTP response header that I'll get back from my Sign In Request?; and then
How to save that <big_huge_string> as a variable and inject that as an HTTP request header for all subsequent calls?
Thanks in advance!
Update
Tried the suggestion:
Getting closer, but console.log(...) isn't printing anything to Postman (or at least I don't know where to look for it). I should mention I'm not using the Chrome Application version of Postman, but the standalone app/executable (Version 6.1.4):
Any ideas how/where I can get console.log(...) working? I'm concerned about just changing the test to:
pm.test("Can Extract JWT", function() {
var authHeader = pm.response.headers.toObject().Authorization;
pm.expect(authHeader).to.not.be.equal(null);
pm.globals.set('token', authHeader)
});
Without first seeing what that authHeader even is. Any ideas?!
Once you have that Token value you can reference it in each of the request headers using the {{token}} syntax. It's getting the sign in Auth header that's the harder part.
You could use pm.response.headers to get a list of the Headers and then extract out the value that you need.
This is returned as a list so maybe using something like Lodash or converting this to an object can help get the value you need. It would be something like pm.response.headers.toObject().Authorization - I haven't tried it so my syntax might be slightly wrong.
You can log the Headers out to the Postman console and narrow it down that way to - just wrap it in a Console.log() statement.
When you get that value, it's just a basic pm.globals.set('token, pm.response.headers.toObject().Authorization) to save this globally.
It is either not being sent, or not being received correctly. Using curl direct from the command line (using the -d option) or from PHP (using CURLOPT_POSTFIELDS) does work.
I start with a PSR-7 request:
$request = GuzzleHttp\Psr7\Request('POST', $url);
I add authentication header, which authenticates against the API correctly:
$request = $request->withHeader('Authorization', 'Bearer ' . $accessToken);
Then I add the request body:
// The parameter for the API function
$body = \GuzzleHttp\Psr7\stream_for('args=dot');
$request = $request->withBody($body);
I can send the message to the API:
$client = new \GuzzleHttp\Client();
$response = $client->send($request, ['timeout' => 2]);
The response I get back indicates that the "args" parameter was simply not seen by the API. I have tried moving the authentication token to the args:
'args=dot&access_token=123456789'
This should work, and does work with curl from the command line (-d access_token=123456789) but the API fails to see that parameter also when sending cia curl (6.x) as above.
I can see the message does contain the body:
var_dump((string)$request->getBody());
// string(8) "args=dot"
// The "=" is NOT URL-encoded in any way.
So what could be going wrong here? Are the parameters not being sent, or are they being sent in the wrong format (maybe '=' is being encoded?), or is perhaps the wrong content-type being used? It is difficult to see what is being sent "on the wire" when using Guzzle, since the HTTP message is formatted and sent many layer deep.
Edit: Calling up a local test script instead of the remote API, I get this raw message detail:
POST
CONNECTION: close
CONTENT-LENGTH: 62
HOST: acadweb.co.uk
USER-AGENT: GuzzleHttp/6.1.1 curl/7.19.7 PHP/5.5.9
args=dot&access_token=5e09d638965288937dfa0ca36366c9f8a44d4f3e
So it looks like the body is being sent, so I guess something else is missing to tell the remote API how to interpret that body.
Edit: the command-line curl that does work, sent to the same test script, gives me two additional header fields in the request:
CONTENT-TYPE: application/x-www-form-urlencoded
ACCEPT: */*
I'm going to guess it is the content-type header which is missing from the Guzzle request which is the source of the problem. So is this a Guzzle bug? Should it not always sent a Content-Type, based on the assumptions it makes that are listed in the documentation?
The Content-Type header was the issue. Normally, Guzzle will hold your hand and insert headers it deems necessary, and makes a good guess at the Content-Type based on what you have given it, and how you have given it.
With Guzzle's PSR-7 messages, none of that hand-holding is done. It strictly leaves all the headers for you to handle. So when adding POST parameters to a PSR-7 Request, you must explicitly set the Content-Type:
$params = ['Foo' => 'Bar'];
$body = \GuzzleHttp\Psr7\stream_for(http_build_query($params));
$request = $request->withBody($body);
$request = $request->withHeader('Content-Type', 'application/x-www-form-urlencoded');
The ability to pass in the params as an array and to leave Guzzle to work out the rest, does not apply to Guzzle's PSR-7 implementation. It's a bit clumsy, as you need to serialise the POST parameters into a HTTP query string, and then stick that into a stream, but there you have it. There may be an easier way to handle this (e.g. a wrapper class I'm not aware of), and I'll wait and see if any come up before accepting this answer.
Be aware also that if constructing a multipart/form-data Request message, you need to add the boundary string to the Content-Type:
$request = $request->withHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary);
Where $boundary can be something like uniq() and is used in construction the multipart body.
The GuzzleHttp\Client provides all necessary wrapping.
$response = $client->post(
$uri,
[
'auth' => [null, 'Bearer ' . $token],
'form_params' => $parameters,
]);
Documentation available Guzzle Request Options
Edit: However, if your requests are being used within GuzzleHttp\Pool then, you can simply everything into the following:
$request = new GuzzleHttp\Psr7\Request(
'POST',
$uri,
[
'Authorization' => 'Bearer ' . $token,
'Content-Type' => 'application/x-www-form-urlencoded'
],
http_build_query($form_params, null, '&')
);
This question follows on from my previous one. I thought I would perform a basic token authentication by calling the get_space_usage API function. I tried
$headers = array("Authorization: Bearer token",
"Content-Type:application/json");
$ch = curl_init('https://api.dropboxapi.com/2/users/get_space_usage/');
curl_setopt($ch,CURLOPT_HTTPHEADER,$headers);
curl_setopt($ch,CURLOPT_POST,true);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
The documentation does not in fact indicate that it is necessary to provide a Content-Type header. However, without that header I get the message
Bad HTTP "Content-Type" header: "application/x-www-form-urlencoded". Expecting one of "application/json",...
Putting in that header but supplying no POST fields produces another error
request body: could not decode input as JSON
Just providing some dummy post data curl_setopt($ch,CURL_POSTFIELDS,json_encode(array('a'=>1))); does not do anything to remedy the situation. What am I doing wrong?
The documentation doesn't indicate that a Content-Type header is expected, because, since this endpoint doesn't take any parameters, no body is expected, and so there's no content to describe via a Content-Type header. Here's a working command line curl example, per the documentation:
curl -X POST https://api.dropboxapi.com/2/users/get_space_usage \
--header "Authorization: Bearer <ACCESS_TOKEN>"
Translating this to curl in PHP would involve making sure PHP also doesn't send up a Content-Type header. By default though, it apparently sends "application/x-www-form-urlencoded", but that isn't accepted by the API. If you do set a "application/json", the API will attempt to interpret the body as such, but won't be able to do so, since it isn't valid JSON, and so fails accordingly.
It's apparently not easy (or maybe not possible) to omit the Content-Type header with curl in PHP, so the alternative is to set "application/json", but supply valid JSON, such as "null". Here's a modified version of your code that does so:
<?php
$headers = array("Authorization: Bearer <ACCESS_TOKEN>",
"Content-Type: application/json");
$ch = curl_init('https://api.dropboxapi.com/2/users/get_space_usage');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, "null");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
I am trying to get an access token for Paypal's RESTful web services but unfortunately not making any headway. This is my first time dealing with REST, so please be patient with me :)
Here is what I have:
Client_id and secret as provided by Paypal for a sandbox account through the paypal developer website.
The ENDpoint: https://api.sandbox.paypal.com/v1/oauth2/token
The documentation that i am referring to is : https://developer.paypal.com/webapps/developer/docs/integration/direct/make-your-first-call/
Now the juicy part of making that API call. I am developing in PHP so I am using CURL to make the calls. something like this;
const CLIENT_ID = ****..*** ;
const SECRET = ***..***;
$base64EncodedClientID = base64_encode(self::CLIENT_ID . ":" . self::SECRET);
$headers = array("Authorization" => "Basic " . $base64EncodedClientId, "Accept" =>"*/*", "Content-type" => "multipart/form-data");
$params = array("grant_type"=>"client_credentials");
$url = "https://api.sandbox.paypal.com/v1/oauth2/token";
$ch = curl_init();
curl_setopt($ch,CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_POST, true);
curl_setopt($ch,CURLOPT_HEADER, $headers);
curl_setopt($ch,CURLOPT_POSTFIELDS,$params);
$response = curl_exec($ch);
Pretty vanilla right? Except that I do not get the JSON response that I expect from Paypal but false. This implies that my CURL request was not prepared well, perhaps I am setting the header incorrectly or the params are incorrect. Regardless, the URL is definitely accessible since I was able to access it through command line with the same credentials and got the desired JSON response.
The one glaring problem I have with the above code is that I am providing the client_id and secret as a header option. basic sense tells me that they need to be part of the POST field data However, if you look at line 89 of this Github code https://github.com/paypal/rest-api-sdk-php/blob/master/lib/PayPal/Auth/OAuthTokenCredential.php (Paypals' official PHP REST SDK), it clearly states that the credentials are being set in the header field.
Where am I messing up ?
With curl you don't need to manually generate the base64 encoded value for the Authorization header just use the CURLOPT_USERPWD option and pass the clientID and secret as the user:pwd.
curl_setopt($curl, CURLOPT_USERPWD, $clientId . ":" . $clientSecret);
here is a sample - look for the get_access_token() method:
https://github.com/paypal/rest-api-curlsamples/blob/master/execute_all_calls.php
Had the exact same problem you ran into. The issue is that PayPal accepts the content-type application/x-www-form-urlencoded. Your code is attempting to send multipart/form-data. CURL by default sends application/x-www-form-urlencoded, but you are passing your data as an array. Instead, you should be passing the data like a url encoded string since this is what application/x-www-form-urlencoded data looks like:
$params = "grant_type=client_credentials";
Your headers have the same problem. Pass it as an array of strings instead of a dictionary. For instance:
$headers = ["Authorization Basic " . $base64EncodedClientId];
Also, you don't need those other two headers you passed in. The 'Accept' header does nothing since you are accepting everything, and the Content-type is wrong for one, and two is defaulted to 'application/x-www-form-urlencoded' by CURL so unless you need to override that, there is no need.