Can someone find the error and verify my Swift NSURLSession code? - swift

I am writing my first, basic IOS app using Swift that will poll a Proliphix IP Thermostat and display the temperature. I have converted an HTTP Post to NSURLSession using Paw, but the converted code appears to have a 3 syntax errors when I load it in Xcode Playground.
My goal right now is to get this working in Playground, but ultimately use it in my App code. Could someone please load this code in your own Xcode Playground and fix the error and make it work? The URL is a public IP address of the thermostat and If working correctly, it will poll the thermostat and return a value for OID4.3.2.3 and = something like 772& (currently 77.2 degrees).
class MyRequestController {
func sendRequest() {
/* Configure session, choose between:
* defaultSessionConfiguration
* ephemeralSessionConfiguration
* backgroundSessionConfigurationWithIdentifier:
And set session-wide properties, such as: HTTPAdditionalHeaders,
HTTPCookieAcceptPolicy, requestCachePolicy or timeoutIntervalForRequest.
*/
let sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()
/* Create session, and optionally set a NSURLSessionDelegate. */
let session = NSURLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil)
/* Create the Request:
My API (POST http://my-thermostat.dyndns.org:8083/get)
*/
var URL = NSURL(string: "http://my-thermostat.dyndns.org:8083/get")
let request = NSMutableURLRequest(URL: URL!)
request.HTTPMethod = "POST"
// Headers
request.addValue("Basic dXNlcjpwYXNzd29yZA==", forHTTPHeaderField: "Authorization")
// Form URL-Encoded Body
let bodyParameters = [
"OID4.3.2.3": "",
]
let bodyString = self.stringFromQueryParameters(bodyParameters)
request.HTTPBody = bodyString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
/* Start a new Task */
let task = session.dataTaskWithRequest(request, completionHandler: { (data : NSData!, response : NSURLResponse!, error : NSError!) -> Void in
if (error == nil) {
// Success
let statusCode = (response as NSHTTPURLResponse).statusCode
println("URL Session Task Succeeded: HTTP \(statusCode)")
}
else {
// Failure
println("URL Session Task Failed: %#", error.localizedDescription);
}
})
task.resume()
}
/**
This creates a new query parameters string from the given NSDictionary. For
example, if the input is #{#"day":#"Tuesday", #"month":#"January"}, the output
string will be #"day=Tuesday&month=January".
#param queryParameters The input dictionary.
#return The created parameters string.
*/
func stringFromQueryParameters(queryParameters : Dictionary<String, String>) -> String {
var parts: [String] = []
for (name, value) in queryParameters {
var part = NSString(format: "%#=%#",
name.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!,
value.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!)
parts.append(part)
}
return "&".join(parts)
}
/**
Creates a new URL by adding the given query parameters.
#param URL The input URL.
#param queryParameters The query parameter dictionary to add.
#return A new NSURL.
*/
func NSURLByAppendingQueryParameters(URL : NSURL!, queryParameters : Dictionary<String, String>) -> NSURL {
let URLString : NSString = NSString(format: "%#?%#", URL.absoluteString!, self.stringFromQueryParameters(queryParameters))
return NSURL(string: URLString)!
}
}
I assume I need the following above this code in Playground to work properly?
import Foundation
import XCPlayground
Last, the HTTP Post I used to convert was from the following and Paw converted this to NSURLSession above.
POST /get HTTP/1.1
Authorization: Basic dXNlcjpwYXNzd29yZA==
Content-Type: application/x-www-form-urlencoded
Host: my-thermostat.dyndns.org:8083
Connection: close
User-Agent: Paw/2.2.2 (Macintosh; OS X/10.10.4) GCDHTTPRequest
Content-Length: 11
OID4.3.2.3=
Thank you for your help!

If I create a playground with your code I get three errors.
One in the function sendRequest() where you must change this line:
let statusCode = (response as NSHTTPURLResponse).statusCode
to
let statusCode = (response as! NSHTTPURLResponse).statusCode
(as! instead of as)
One in the function stringFromQueryParameters where you must change the following line
parts.append(part)
to
parts.append(part as String)
(add as String)
And one in the function NSURLByAppendingQueryParameters where you must change this:
return NSURL(string: URLString)!
to this:
return NSURL(string: URLString as String)!
(add as String)
And then the compiler seems satisfied...at least on my machine :-)
And yes, you need to import Foundation and XCPlayground as you figured out already.
Furthermore, if you need to make asynchronous calls...as is the case here, you'll need this method in the top of your code:
XCPSetExecutionShouldContinueIndefinitely()
(you can read more about it here)
And finally, to actually see your code run, you can add this to the bottom of your code, after the class definition:
let requestController = MyRequestController()
requestController.sendRequest()
Here is the complete code
import Foundation
import XCPlayground
XCPSetExecutionShouldContinueIndefinitely()
class MyRequestController {
func sendRequest() {
/* Configure session, choose between:
* defaultSessionConfiguration
* ephemeralSessionConfiguration
* backgroundSessionConfigurationWithIdentifier:
And set session-wide properties, such as: HTTPAdditionalHeaders,
HTTPCookieAcceptPolicy, requestCachePolicy or timeoutIntervalForRequest.
*/
let sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()
/* Create session, and optionally set a NSURLSessionDelegate. */
let session = NSURLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil)
/* Create the Request:
My API (POST http://my-thermostat.dyndns.org:8083/get)
*/
var URL = NSURL(string: "http://my-thermostat.dyndns.org:8083/get")
let request = NSMutableURLRequest(URL: URL!)
request.HTTPMethod = "POST"
// Headers
request.addValue("Basic dXNlcjpwYXNzd29yZA==", forHTTPHeaderField: "Authorization")
// Form URL-Encoded Body
let bodyParameters = [
"OID4.3.2.3": "",
]
let bodyString = self.stringFromQueryParameters(bodyParameters)
request.HTTPBody = bodyString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
/* Start a new Task */
let task = session.dataTaskWithRequest(request, completionHandler: { (data : NSData!, response : NSURLResponse!, error : NSError!) -> Void in
if (error == nil) {
// Success
let statusCode = (response as! NSHTTPURLResponse).statusCode
println("URL Session Task Succeeded: HTTP \(statusCode)")
}
else {
// Failure
println("URL Session Task Failed: %#", error.localizedDescription);
}
})
task.resume()
}
/**
This creates a new query parameters string from the given NSDictionary. For
example, if the input is #{#"day":#"Tuesday", #"month":#"January"}, the output
string will be #"day=Tuesday&month=January".
#param queryParameters The input dictionary.
#return The created parameters string.
*/
func stringFromQueryParameters(queryParameters : Dictionary<String, String>) -> String {
var parts: [String] = []
for (name, value) in queryParameters {
var part = NSString(format: "%#=%#",
name.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!,
value.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!)
parts.append(part as String)
}
return "&".join(parts)
}
/**
Creates a new URL by adding the given query parameters.
#param URL The input URL.
#param queryParameters The query parameter dictionary to add.
#return A new NSURL.
*/
func NSURLByAppendingQueryParameters(URL : NSURL!, queryParameters : Dictionary<String, String>) -> NSURL {
let URLString : NSString = NSString(format: "%#?%#", URL.absoluteString!, self.stringFromQueryParameters(queryParameters))
return NSURL(string: URLString as String)!
}
}
let requestController = MyRequestController()
requestController.sendRequest()
Hope this helps

Related

how to pass output of HTTP request Task to variable in Swift

In Chrome browser, I input this address
https://jsonplaceholder.typicode.com/todos/1
Then I see output on Chrome window:
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
Now my question is that, can i do this in Swift?
Accessing an http address then get the output?
Now on this webpage, i find the below code.
import SwiftUI
// Create URL
let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1")
guard let requestUrl = url else { fatalError() }
// Create URL Request
var request = URLRequest(url: requestUrl)
// Specify HTTP Method to use
request.httpMethod = "GET"
// Send HTTP Request
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
// Check if Error took place
if let error = error {
print("Error took place \(error)")
return
}
// Read HTTP Response Status code
if let response = response as? HTTPURLResponse {
print("Response HTTP Status code: \(response.statusCode)")
}
// Convert HTTP Response Data to a simple String
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print("Response data string:\n \(dataString)")
}
}
task.resume()
I run this code in Playground in XCode. As it says on the webpage I got the right output:
Response data string:
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
But it prints on the console of XCode. I cannot find a way to pass this output to a variable. So I cannot go further.
I tried add lines like:
return dataString
all got error.
Or put it in a Func(), then call the function, also got error.
I am not a programmer and very new to Swift, hope people here can help.
You need to decode from JSON and store the result in an object.
Create an object that conforms to Codable and can store the result - it need to have all the properties of the JSON:
struct MyObject: Codable {
let userId: Int
let id: Int
let title: String
let completed: Bool
}
Create a function to decode:
func decode(data: Data) -> MyObject? {
do {
let result = try JSONDecoder().decode(MyObject.self, from: data)
return result
} catch {
print("\n-->> Error decoding JSON: \(error), \(error)")
return nil
}
}
Use the function:
if let data = data {
// downloaded will store the result from the JSON
let downloaded = decode(data: data)
print(downloaded) // Optional(Test_Swift.MyObject(userId: 1, id: 1, title: "delectus aut autem", completed: false))
print(downloaded?.title ?? "Could not decode") // delectus aut autem
}

Marvel API 409 response, "You must provide a user key"

Wondering if anyone can help me here:
private static func getURLRequestData(completion: #escaping (Data?) -> ()) {
// Gets the raw JSON Data
let session = URLSession(configuration: .default)
guard let url = URL(string: urlString) else { return }
var urlRequest = URLRequest(url: url)
let headers = [
"ts" : ts,
"apikey" : apiKey,
"hash" : hash,
"limit" : limit,
"orderBy" : orderedBy
]
urlRequest.allHTTPHeaderFields = headers
let task = session.dataTask(with: urlRequest) { (data, response, error) in
if error != nil {
print(#line, error!.localizedDescription)
}
print(response)
completion(data)
}
task.resume()
}
As you can see, I am setting the headers correctly with my API Key but for some reason I'm getting a 409 return which is a missing "user key".
Has anyone experienced anything like this. For what its worth, the exact same request is working in Paw
You must provide informations according documentation link documentation here
Example:
var publickey = 'you-public-key';
var privatekey = 'you-private-key';
var ts = new Date().getTime();
var stringToHash = ts + privatekey + publickey;
var hash = md5(stringToHash);
var baseUrl = 'https://gateway.marvel.com:443/v1/public/characters';
var limit = 20;
var url = baseUrl + '?limit=' + limit + '&ts=' + ts + '&apikey=' + publickey + '&hash=' + hash;

Swift upload file via WCF

I've been fighting this for over 10 hours and done my best before coming here. Many tutorials I've looked out ended up being outdated by a version or two and/or missing some key knowledge in the explanation.
I'm trying to upload a PDF to a server using a WCF Rest Service. When I debug on the WCF service, the variable named document is a null stream. I've searched the web many hours trying many different things and I've exhausted MANY attempts to get such a little thing to work! What is really annoying is that this isn't the first time someone has needed to do this and yet I haven't found an answer anywhere.
If there is a better way to post the PDF other than what I've posted, I'm open to suggestions but I don't want to use third-party frameworks.
Requirements:
- Using the WCF service is required
- I'm not saving to a file system or UNC
I need to get a valid memory stream passed to the service and I can take care of the function code from there. I've tried using a Base64DataString before this and I could get that to work either. If you wanted to provide that as an option, I'm open to it.
Please help!
Swift 4 code:
import PDFKit
class FileUpload {
static func generateBoundaryString() -> String {
return "Boundary-\(UUID().uuidString)"
}
static func dataUploadBodyWithParameters(_ parameters: [String: Any]?, filename: String, mimetype: String, dataKey: String, data: Data, boundary: String) -> Data {
var body = Data()
// encode parameters first
if parameters != nil {
for (key, value) in parameters! {
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.appendString("\(value)\r\n")
}
}
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(dataKey)\"; filename=\"\(filename)\"\r\n")
body.appendString("Content-Type: \(mimetype)\r\n\r\n")
body.append(data)
body.appendString("\r\n")
body.appendString("--\(boundary)--\r\n")
print(body)
return body
}
static func uploadData(_ data: Data, toURL urlString: String, withFileKey fileKey: String, completion: ((_ success: Bool, _ result: Any?) -> Void)?) {
if let url = URL(string: urlString) {
// build request
let boundary = generateBoundaryString()
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
// build body
let body = dataUploadBodyWithParameters(nil, filename: "iOSUpload.pdf", mimetype: "application/pdf", dataKey: fileKey, data: data, boundary: boundary)
request.httpBody = body
//UIApplication.shared.isNetworkActivityIndicatorVisible = true
URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) -> Void in
if data != nil && error == nil {
do {
let result = try JSONSerialization.jsonObject(with: data!, options: [])
print(result)
DispatchQueue.main.async(execute: { completion?(true, result) })
} catch {
DispatchQueue.main.async(execute: { completion?(false, nil) })
}
} else { DispatchQueue.main.async(execute: { completion?(false, nil) }) }
//UIApplication.shared.isNetworkActivityIndicatorVisible = false
}).resume()
} else { DispatchQueue.main.async(execute: { completion?(false, nil) }) }
}
}
extension Data {
mutating func appendString(_ string: String) {
let data = string.data(using: String.Encoding.utf8, allowLossyConversion: true)
append(data!)
}
}
}
WCF Relevant Code:
<ServiceContract>
Public Interface IWebService
<OperationContract>
<WebInvoke(Method:="POST", UriTemplate:="UploadFile/{fileName}")>
Function UploadFile(fileName As String, document As Stream) As String
End Interface
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single)>
Public Class MyWebService : Implements IWebService
Public Function UploadFile(fileName As String, document As Stream) As String Implements IWebService.UploadFile
'document is null
End Function

How to write GraphQL Query

I have a working web graphql query as :
{
me{
... on Student{
profile {
fullName
emailId
mobileNumber
civilId
address
city
state
country
zipCode
userProfilePic
userCategory
createdAt
updatedAt
}
}
}
}
It returns the profile details of a particular student. I log using mutation and gets the token for a user.
I want to create a graphql file (ex. StudentProfile.graphql) in order to make fetch request (similar to http. get) using Apollo client.
I make this request to fetch the graphql query.
func fetchStudentProfileDetails(){
let tokenString = "Bearer " + "....my token ..."
print(tokenString)
let newApollo: ApolloClient = {
let configuration = URLSessionConfiguration.default
// Add additional headers as needed
configuration.httpAdditionalHeaders = ["Authorization": tokenString]
let url = URL(string: "http://52.88.217.19/graphql")!
return ApolloClient(networkTransport: HTTPNetworkTransport(url: url, configuration: configuration))
}()
newApollo.fetch(query: StudentProfileQuery()) { (result, error) in
self.profileDetailsTextView.text = "Success"
if let error = error {
NSLog("Error while fetching query: \(error.localizedDescription)");
self.profileDetailsTextView.text = error.localizedDescription
}
guard let result = result else {
NSLog("No query result");
self.profileDetailsTextView.text = "No query result"
return
}
if let errors = result.errors {
NSLog("Errors in query result: \(errors)")
self.profileDetailsTextView.text = String(describing: errors)
}
guard let data = result.data else {
NSLog("No query result data");
return
}
}
}
How do I convert the following web query into a query in the .graphql file?
so, you can call to create new document into Graphql server using a simple NSUrlSession
let headers = ["content-type": "application/json"]
let parameters = ["query": "mutation { createProfile(fullName: \"test name\" emailId: \"test#email.com\") { id } }"] as [String : Any]
let postData = JSONSerialization.data(withJSONObject: parameters, options: [])
let request = NSMutableURLRequest(url: NSURL(string: "https://<url graphql>")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error)
} else {
let httpResponse = response as? HTTPURLResponse
print(httpResponse)
}
})
dataTask.resume()
I'm not sure I completely understand your question, but you should be able to use HTTP to make queries. For most people, a *.gql file just contains the query as a String which they URLEncode. Below is an example of reading from a variable but you could do just the same reading the query from a file as a string/buffer.
const myQuery = `{
user {
name
}
}`;
const queryURL = "http://52.88.217.19/graphql/?query=" + URLEncode(myQuery);
fetch(queryURL)
.then((result) => {
console.log(result);
})
If this does not answer your question, please help me better understand what you are asking, and I will try to revise my answer.

Put request failing with Alamofire 2.0

I have recently upgraded to Alamofire 2.0, and now my Put request is failing with a 400 error, when it was previously working properly. I perform the call with the code:
Alamofire.request(Router.Put(query: url, params: params, encoding: .JSON))
.validate()
.responseJSON() {
(request, response, result) in
print("request: \(request)")
print("response: \(response)")
print("result: \(result)")
switch result {
case .Success(_):
// success
case .Failure(let data, _):
// error occured
}
}
and my custom Router class:
enum Router: URLRequestConvertible {
case Get(query: String, params: [String: AnyObject]?)
case Post(query: String, params: [String: AnyObject]?)
case Put(query: String, params: [String: AnyObject]?, encoding: ParameterEncoding)
case Delete(query: String, params: [String: AnyObject]?)
var URLRequest: NSMutableURLRequest {
var encodeMethod: Alamofire.ParameterEncoding = Alamofire.ParameterEncoding.URL
// Default to GET
var httpMethod: String = Alamofire.Method.GET.rawValue
let (path, parameters): (String, [String: AnyObject]?) = {
switch self {
case .Get(let query, let params):
// Set the request call
httpMethod = Alamofire.Method.GET.rawValue
// Return the query
return (query, params)
case .Post(let query, let params):
// Set the request call
httpMethod = Alamofire.Method.POST.rawValue
// Return the query
return (query, params)
case .Put(let query, let params, let encoding):
// Set the request call
httpMethod = Alamofire.Method.PUT.rawValue
// Set the encoding
encodeMethod = encoding
// Return the query
return (query, params)
case .Delete(let query, let params):
// Set the request call
httpMethod = Alamofire.Method.DELETE.rawValue
// Return the query
return (query, params)
}
}()
// Create the URL Request
let URLRequest = NSMutableURLRequest(URL: NSURL(string: Globals.BASE_URL + path)!)
// set header fields
if let key = NSUserDefaults.standardUserDefaults().stringForKey(Globals.NS_KEY_SESSION) {
URLRequest.setValue(key, forHTTPHeaderField: "X-XX-API")
}
// Add user agent
if let userAgent = NSUserDefaults.standardUserDefaults().stringForKey(Globals.NS_KEY_USER_AGENT) {
URLRequest.setValue(userAgent, forHTTPHeaderField: "User-Agent")
}
// Set the HTTP method
URLRequest.HTTPMethod = httpMethod
URLRequest.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData
return encodeMethod.encode(URLRequest, parameters: parameters).0
}
}
Instead of the call being a success, the response is:
response: Optional(<NSHTTPURLResponse: 0x7fcee15b34d0> { URL: https://apiurl } { status code: 400, headers {
"Cache-Control" = "no-cache";
"Content-Length" = 26;
"Content-Type" = "application/json; charset=utf-8";
Date = "Tue, 15 Sep 2015 15:33:50 GMT";
Expires = "-1";
Pragma = "no-cache";
Server = "Microsoft-IIS/8.5";
} })
I looked into the problem and on the server side, Content-Type is coming in as blank for the request, when it should be coming in as application/json. The Content-Type should automatically get added when there is body data in the request. I have the params set:
// Save the profile
var params: [String: AnyObject] = ["indexPhoto": userProfile.indexPhoto,
"dob": df.stringFromDate(userProfile.dob) as NSString,
"identAs": userProfile.identAs]
// Add manually since creating the dictionary all at once is too much for swift to handle
params.updateValue(String(format:"%.2f", userProfile.heightIn), forKey: "heightIn")
params.updateValue(String(format:"%.2f", userProfile.weightLbs), forKey: "weightLbs")
params.updateValue(userProfile.eyes, forKey: "eyes")
params.updateValue(userProfile.hair, forKey: "hair")
...
Is there something I could be missing with this? Before I upgraded to Alamofire 2.0 this call was working just fine.
Maybe there is a different way but I fixed the same issue by sending empty params instead of nil. I don't use any parameters when using .PUT and because of that server doesn't know how to encode request and response (even if I explicitly set content type inside header, I receive blank content type on the server) so my solution was sending empty params like in the code below. Hope it helps someone.
var url = https://api.mysite.com/v1/issue/369613/delete
let params = ["":""]
let headers = NetworkConnection.addAuthorizationHeader(token, tokenType: tokenType)
manager.request(.PUT, url, parameters: params, encoding: .JSON, headers: headers)
.responseJSON { response in
if let JSONdata = response.result.value {
print("JSONdata: \(JSONdata)")
}
}