Im trying to upload an image through Dropbox HTTP API after authenticating, using NATIVE iOS functions and methods (cannot use ALAMOFIRE). When I send the request it gives me a timeout after 30 seconds.
...Code used:
let data = UIImagePNGRepresentation(self.image!)
var request = URLRequest.init(url: url)
request.allHTTPHeaderFields = [
"Authorization":"Bearer <TOKEN_HERE>",
"Dropbox-API-Arg":"{\"path\": \"/TCC/uploaded.png\",\"mode\": \"add\",\"autorename\": false,\"mute\": false}",
"Content-Type": "application/octet-stream",
]
URLSession.shared.uploadTask(with: request,
from: data) { returnData, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200
else {
if let callback = failure {
callback()
}
return
}
//UI updates in main thread
DispatchQueue.main.async() {
if success != nil {
success!()
}
}
}.resume()
Hello #Danilo Rodrigues,
There is a statement missing, you need to explicitly declare the method used:
let data = UIImagePNGRepresentation(self.image!)
var request = URLRequest.init(url: url)
request.allHTTPHeaderFields = [
"Authorization":"Bearer <TOKEN_HERE>",
"Dropbox-API-Arg":"{\"path\": \"/TCC/uploaded.png\",\"mode\": \"add\",\"autorename\": false,\"mute\": false}",
"Content-Type": "application/octet-stream",
]
request.httpMethod = "POST" //ADDED THIS LINE
URLSession.shared.uploadTask(with: request,
from: data) { returnData, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200
else {
if let callback = failure {
callback()
}
return
}
//UI updates in main thread
DispatchQueue.main.async() {
if success != nil {
success!()
}
}
}.resume()
Related
I am logging in a user with intentionally invalid credentials and trying to send back the error response from the server ("Unable to login")
How to access this response in my request so I can display the message to the user?
Right now I can print the response object (see below) in Xcode but I don't see the error message I am trying to access from the server ( "unable to login" ) in this object??
What am I doing wrong?
Javascript Express router code
router.post(`/api/user/login`, async (req, res,) => {
console.log(req.body)
try {
const user = await User.findByCredentials(req.body.email, req.body.password)
const token = await user.generateAuthToken()
res.send({user, token})
} catch (err) {
const message = err.message // err.message -> "unable to login"
res.status(400).send({message}) // HOW DO I ACCESS THIS MESSAGE IN MY REQUEST?
}
})
Swift API Request
static func loginUser (user: UserLogIn, completion: #escaping(Result <LoginResponse,
NetworkError>) -> Void) {
let user = user
guard let url = URL(string: Ref.API_ROOT + Ref.LOGIN) else { return }
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
do {
let encoder = JSONEncoder()
urlRequest.httpBody = try encoder.encode(user)
let dataTask = URLSession.shared.dataTask(with: urlRequest) { data, response, error in
guard let jsonData = data, error == nil else {
if let error = error as NSError?, error.domain == NSURLErrorDomain {
completion(.failure(.networkError("\(error.localizedDescription)")))
}
return
}
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
print(response!)
completion(.failure(.loginError("Domain Error")))
return
}
do {
let response = try JSONDecoder().decode(LoginResponse.self, from: jsonData)
completion(.success(response))
} catch {
completion(.failure(.urlBuildError("Url build error")))
}
}
dataTask.resume()
} catch {
completion(.failure(.unableToComplete("Unable to complete")))
}
}
Response Object :
Why is my error response "unable to login" not in here?
<NSHTTPURLResponse: 0x623453d12134> { URL: http://localhost:3000/api/user/login } { Status Code:
400, Headers {
Connection = (
"keep-alive"
);
"Content-Length" = (
30
);
"Content-Type" = (
"application/json; charset=utf-8"
);
Date = (
"Wed, 14 Apr 2021 15:38:42 GMT"
);
Etag = (
"W/\"1e-J0jWW/rGxsghjubfgt2iegw3A\""
);
"Keep-Alive" = (
"timeout=5"
);
"X-Powered-By" = (
Express
);
} }
For URLSession, HTTPURLResponse class contains only the metadata associated with the HTTP response (documentation) therefore, the logged response seems correct.
In your case, I believe res.status(400).send({message}) sends the message object in HTTP response's body. Therefore, to parse that data,
session.dataTask(with: urlRequest, completion: #escaping(Result <LoginResponse,
NetworkError>) -> Void) { (responseBody, response, error) in
if let error = error {
// handle transport error
}
let response = response as! HTTPURLResponse
let responseBody = responseBody!
if !(200...299).contains(response.statusCode) {
// handle HTTP server-side error
if let responseString = String(bytes: responseBody, encoding: .utf8) {
// The response body seems to be a valid UTF-8 string, so print that.
// This will probably be `{"message": "unable to login"}`
print(responseString)
} else {
// Otherwise print a hex dump of the body.
print(responseBody as NSData)
}
completion(.failure(.loginError("Domain Error")))
return
}
// Handle decoding response...
}.resume()
You probably would have to do some decoding since you're sending an object for the error message on node.js. Refer to the Apple docs on Debugging HTTP Server-Side Errors for more information.
Response Object : Why is my error response "unable to login" not in here?
Because you have added the response in a guard let and your function is returning from there itself, and as it is not able to go ahead it never reaches to the decoding part.
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
print(response!)
completion(.failure(.loginError("Domain Error")))
return //Returning from here
}
Here, when the statusCode is not 200 then it goes in the else part, inside which you are printing the response and then, in the next line, you have you completionHandler and then return, so it returns from there only.
If you want to see your message, you have decode the data in the else part like this:
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
do {
let response = try JSONDecoder().decode(LoginResponse.self, from: data)
completion(.success(response)) //Here you will get your errorMessage, I have not seen your LoginResponse model, I hope you have added your keys there.
} catch let error {
completion(.failure(.urlBuildError(error.localizedDescription)))
return
}
}
Here response is URLResponse and not the response you are expecting, please go through the dataTaskMethod and know what it is:
https://developer.apple.com/documentation/foundation/urlsession/1410330-datatask
I'm not using Alamofire, so i want to use JSON post approach in SharedClass and i want to send my api name and all parameters to that function. Finally i want to get the response back. I tried but it's not working. If it's not correct please correct me or if any other options are available please suggest me.
My code in SharedClass
func postRequestFunction(apiName:String , parameters:String ) -> [String:Any] {
var localURL = "hostname/public/index.php/v/***?"
localURL = localURL.replacingOccurrences(of: "***", with: apiName)
var request = URLRequest(url: URL(string: localURL)!)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
print("shared URL : \(request)")
request.httpBody = parameters.data(using: .utf8)
var returnRes:[String:Any] = [:]
let task = URLSession.shared.dataTask(with: request) { data, response, error in guard let data = data, error == nil else { // check for fundamental networking error
print(error!)
// print("error=\(String(describing: error))")
print("localizedDescription : \(String(describing: error?.localizedDescription))")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
do {
returnRes = try JSONSerialization.jsonObject(with: data, options: []) as! [String : Any]
print(returnRes)
} catch let error as NSError {
print(error)
}
}
task.resume()
return returnRes
}
In my view controller class my code is. Here i'm calling function
func getProjectDetails() {
let response = SharedClass.sharedInstance.postRequestFunction(apiName: "API Name", parameters: parameters)
print(response)
let res = response["Response"] as! [String:Any]
let status = res["status"] as! String
if status == "SUCCESS" {
//I will handle response here
} else {
let message = res["message"] as! String
//Call alert function
SharedClass.sharedInstance.alert(view: self, title: "", message: message)
}
}
Here is my solution:
class APIManager {
private init () {}
static let shared = APIManager()
func postRequestFunction(apiName: String , parameters: String, onCompletion: #escaping (_ success: Bool, _ error: Error?, _ result: [String: Any]?)->()) {
var localURL = "hostname/public/index.php/v/***?"
localURL = localURL.replacingOccurrences(of: "***", with: apiName)
var request = URLRequest(url: URL(string: localURL)!)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
print("shared URL : \(request)")
request.httpBody = parameters.data(using: .utf8)
var returnRes:[String:Any] = [:]
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
onCompletion(false, error, nil)
} else {
guard let data = data else {
onCompletion(false, error, nil)
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode == 200 {
do {
returnRes = try JSONSerialization.jsonObject(with: data, options: []) as! [String : Any]
onCompletion(true, nil, returnRes)
} catch let error as NSError {
onCompletion(false, error, nil)
}
} else {
onCompletion(false, error, nil)
}
}
}
task.resume()
}
}
func getProjectDetails() {
/* Notes:
** onCompletion Block Parameters:
success - This indicates whether the API called successfully or not.
error - This indicates errors from either API calling failed, JSON parsing, or httpStatus is not 200.
result - This indicates the JSON parsed result.
** APIManager:
I have renamed your SharedClass to APIManager for better readibility.
** sharedInstance:
I have renamed sharedInstance to shared for better readibility.
*/
APIManager.shared.postRequestFunction(apiName: "API Name", parameters: "parameters") { (success, error, result) in
if success {
if let res = result?["Response"] as? [String: Any] {
if let status = res["status"] as? String {
if status == "SUCCESS" {
//You can handle response here.
} else {
let message = res["message"] as! String
//Call alert function.
}
}
}
} else {
print(error?.localizedDescription)
}
}
}
You forgot the asynchronous paradigm of Service, You can return your API response in Closure, as like below
func postRequestFunction(apiName:String , parameters:String, returnRes: #escaping ([String: Any]) -> () ) {
var localURL = "hostname/public/index.php/v/***?"
localURL = localURL.replacingOccurrences(of: "***", with: apiName)
var request = URLRequest(url: URL(string: localURL)!)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
print("shared URL : \(request)")
request.httpBody = parameters.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in guard let data = data, error == nil else {
// check for fundamental networking error
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
do {
if let response = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] {
returnRes(response)
}
} catch let error as NSError {
print(error)
}
}
task.resume()
}
And use like below
postRequestFunction(apiName: "yourUrl", parameters: "Param") { (response) in
print(response)
}
I am getting error while making POST request with swift 4(iOS)
I am getting following debug description:
2017-11-09 23:12:57.283421+0300 ios1[35428:5830006]
Task <23DDE1DF-B58F-4A9E-9BB1-21571EE25661>.<1> HTTP load failed (error code: -1004 [1:61])
My code:
let dict = ["link": web.text, "addr": edit.text]
guard let uploadData = try? JSONEncoder().encode(dict) else { return }
let actInd = showActivityIndicatory(uiView: appView)
let url = URL(string: host)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.uploadTask(with: request, from: uploadData) { data, response, error in
if error != nil {
// handle the transport error
result = 2
print(error.debugDescription)
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 201 else {
result = 1
return
}
if response.mimeType == "text/plain" || response.mimeType == "text/plain", let data = data {
result = 0
print(data)
}
}
task.resume()
P.S.: I could see that request has been done correctly on server.
Thank you a lot
So when I send request without any cookies I get an error.
when trying to get request manually (in a browser) I first need to go to homepage, which loads 4 cookies, then the request can proceed without error. Some experimenting showed that I need only one of the cookies.
This is completely new to me and any snippets/guides on how this can be implemented are welcome. I understand that this question Is pretty vague but I am lost in where to start looking and would appreciate any help
let url = URL(string: "homepage")
var request = URLRequest(url: url!)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil, let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
print("response1")
print("error = \(error)")
print("response = \(response)")
return
}
if let json = try? JSONSerialization.jsonObject(with: data) {
print("responseObject = \(json)")
} else {
print("response2")
print("responseString = \(String(data: data, encoding: .utf8))")
}
let url2 = URL(string: "requestpage")
var request = URLRequest(url: url2!)
request.setValue("application/json", forHTTPHeaderField: "Accept")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil, let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
print("response2")
print("error = \(error)")
print("response = \(response)")
return
}
if let json = try? JSONSerialization.jsonObject(with: data) {
print("responseObject = \(json)")
} else {
print("response2")
print("responseString = \(String(data: data, encoding: .utf8))")
}
}
task.resume()
}
task.resume()
In your code, you should:
initiate URLSessionTask for the landing page that sets the cookies;
that will, with no intervention on your part, will set the cookies in your URLSession;
after that response is successfully received, you can then send the next request and the cookies should be there automatically with no extra code.
Unless you did something to explicitly interfere with this process, URLSession seamlessly receives the the cookies and includes them in subsequent requests for you.
If you're scraping a web site (and if so, please check the ToS to make sure it's permitted), you are well advised to request the pages in the same order that a web browser would. It might feel like you could bypass that landing page and set the cookies yourself, but often the cookie values are generated dynamically and you can have problems if you try to bypass that landing page.
You asked for an example, and there's really not much to show. Just initiate second request from the completion of the first request. There's not a single reference to cookies in the Swift code. URLSession takes care of everything:
let url = baseURL.appendingPathComponent("setcookie.php")
var request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil, let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
print("error = \(error)")
print("response = \(response)")
return
}
let url = baseURL.appendingPathComponent("results.php")
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Accept")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil, let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
print("error = \(error)")
print("response = \(response)")
return
}
if let json = try? JSONSerialization.jsonObject(with: data) {
print("responseObject = \(json)")
} else {
print("responseString = \(String(data: data, encoding: .utf8))")
}
}
task.resume()
}
task.resume()
My first page, setcookie, just set a cookie:
<?php
setcookie("foo", "bar", time()+3600);
$result = array("success" => true);
echo json_encode($result);
?>
And my second page retrieved the cookies:
<?php
$result = array("success" => true);
if (isset($_COOKIE))
$result["_COOKIE"] = $_COOKIE;
header("Content-Type: application/json");
echo json_encode($result);
?>
And the above Swift code output:
responseObject = {
"_COOKIE" = {
foo = bar;
};
success = 1;
}
I'm login through an API using a POST request with URLRequest and URLSession. Everything seems to work well since I receive the correct response from the API.
Then I want to send a GET request to the same API but it seems that I'm not connected anymore.
For what I saw, the API is sending a Cookie in its Header in that form :
Cookie:PHPSESSID=abc123
I guess the problem is coming from that cookie wich is not send when I do a GET Request.
Here is my code.
ViewController :
#IBAction func Connection(_ sender: AnyObject) {
let loginFunc = Login()
loginFunc.login(username: username.text!, password: password.text!) { jsonString in
let response = jsonString
print(response)
}
let get = GetRequest()
get.get(req: "patient/info") { jsonString in
let response = jsonString
print(response)
}
}
Login.swift :
class Login {
func login(username: String, password: String, completion: #escaping (String) -> ()) {
var request = URLRequest(url: URL(string: "http://myurl/web/app_dev.php/login_check")!)
request.httpMethod = "POST"
let config = URLSessionConfiguration.default
let postString = "_username=" + username + "&_password=" + password
request.httpBody = postString.data(using: .utf8)
var responseString = ""
let mysession = URLSession.init(configuration: config)
let task = mysession.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
responseString = String(data: data, encoding: .utf8)!
let httpResponse = response as! HTTPURLResponse
let field = httpResponse.allHeaderFields["Cookie"]
print(field)
print(type(of: response))
completion(responseString)
}
task.resume()
}
}
GetRequest.swift :
class GetRequest {
func get(req: String, completion: #escaping (String) -> ()) {
var request = URLRequest(url: URL(string: "http://myurl/web/app_dev.php/" + req)!)
request.httpMethod = "GET"
var responseString = ""
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
responseString = String(data: data, encoding: .utf8)!
print(responseString)
completion(responseString)
}
task.resume()
}
}
Hey for sending cookies along with your ajax request please use. You need to use withCredentials to true.
$.ajaxSetup({
dataType: 'json',
contentType: 'application/json',
async: true,
cache: false,
processData: false,
crossDomain: true,
xhrFields: {
withCredentials: true
}
});