URLSession Response doesn't contain headers from last redirect - swift

I have an URL that I, when called in a webbrowser, will redirect me 2 times and in the response header of the second redirect it will send the Information that I want to extract.
So to automatically extract that information in swift, I wrote this short piece of code that makes the HTTP Request and then prints the response headers:
printv(text: "Loading JSID Location")
req = URLRequest.init(url: JSIDLocation!)
var task : URLSessionDataTask
task = URLSession.shared.dataTask(with: req) {(data, response, error) in
if let res = response as? HTTPURLResponse {
res.allHeaderFields.forEach { (arg0) in
let (key, value) = arg0
self.printv(text: "\(key): \(value)")
}
}
self.printv(text: String.init(data: data!, encoding: String.Encoding.utf8)!)
}
task.resume()
(printv is a function that will format the string and print it to a label)
So when I run this, I expect it to print the response headers and the body of the last redirect, but what actually happens is that i just prints response headers and body of the original URL. As those don't contain the information im looking for, that won't help me. I already googled my problem, and I found out that HTTP Redirects by default are activated in URLSessions and that you'd had to mess with URLSessionDelegates in order to deactivate them but that's definetly not something I did.
Thank you for your help!

If you want redirect information, you need to become the URLSessionDataTaskDelegate.
let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
Then you need to implement, the redirection delegate function and be sure to call the completion handler with the given new redirect request:
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: #escaping (URLRequest?) -> Void) {
// operate on response to learn about the headers here
completionHandler(request)
}

Related

Correct Alamofire retry for JWT if status 401?

I am trying to make a retry for my Alamofire Interceptor because I work with JSON Web Token. Adapt works great. But the server updates the Access token every 10 minutes after user registration or authorization. After 10 mins Access token doesn't work anymore, and the server response is 401. So I need to Refresh the token when the status is 401. As I mentioned above, adapt works great. But I need help understanding how to deal with retry. Below is my Interceptor:
class RequestInterceptor: Alamofire.RequestInterceptor {
func adapt( _ urlRequest: URLRequest, for session: Session, completion: #escaping (Result<URLRequest, Error>) -> Void) {
var urlRequest = urlRequest
urlRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
completion(.success(urlRequest))
}
func retry( _ request: Request, for session: Session, dueTo error: Error, completion: #escaping (RetryResult) -> Void) {
guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 else {
completion(.doNotRetryWithError(error))
return
}
}
}
My View Model:
func refreshTokenFunc() {
AF.request(TabBarModel.Request.refreshTokenUrl, method: .post, parameters: parameters, encoder: JSONParameterEncoder.default, interceptor: RequestInterceptor()).response { response in
...
And usage (I work with SwiftUI):
.task {
tabBarViewModel.refreshTokenFunc()
}
I was trying with some examples from the Internet. But it doesn't work for me.
In you retry you need to call the completion handler on both sides of the guard, not just in the else side. completion(.retry) is common but you could also track a delay to make sure you don't overload the backend.
Additionally, you should be validating response and checking the error, not reaching directly into request.task.
AF.request(...).validate()... // Ensure the response code is within range.
// In retry
guard let error = error.asAFError, error.responseCode == 401 else { ... }

JWT Request Made but It tells that request does not contain access token

I tried to make a Request with JWT Authorization, The server is Using Python/Flask-Restful. The API Works on Postman, so I guess there must be something wrong with my IOS Code. The server returns an error shows that
"Authorization Required. Request does not contain an access token",
I`m making the request from IOS Using following code.
func GetUserData(username: String, accesstoken: String,completion: #escaping (_ result: UserDataModel) -> Void){
let url = URL(string: "http://********/****/\(****)")
var request = URLRequest(url: url!)
request.httpMethod = "GET"
request.addValue("Authorization", forHTTPHeaderField: accesstoken)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let response = response as? HTTPURLResponse{
if response.statusCode != 200 {
print("Server Error When Update User Data")
} else {
if let data = data {
do {
******
completion(Data)
}
catch {
print(error)
}
}
}
}
}.resume()
}
I have no idea What is going on, Any help?
It looks like you're adding the header:
Bearer base64junk: Authorization
When instead you want:
Authorization: Bearer base64junk
You just have the parameters to addValue(_:forHTTPHeaderField:) backwards. You want this instead:
request.addValue(accesstoken, forHTTPHeaderField: "Authorization")
This should be obvious if you read that line of code like an English sentence ("value authorization for header field access token"?). In the future, you could also use something like Charles Web proxy to intercept your requests and verify that they are indeed formed the way you expect.

sending get / put / post in swift

I can easily issue a GET request and it returns (as expected) JSON data that is decoded to myDataModel object:
class func getData(completionHandler: #escaping (myDataModel?, Error?) -> Void)
{
let url = "https://example.com/api/someResource?ws_key=ABC...XYZ"
if let myUrl = URL(string: url)
{
URLSession.shared.dataTask(with: myUrl)
{
(data, response, err) in
if let data = data
{
do
{
let result = try JSONDecoder().decode(myDataModel.self, from: data)
completionHandler(result, nil)
}
catch let JSONerr
{
completionHandler(nil, JSONerr)
}
}
}.resume()
}
}
This work fine, so GET is no problem. (PS. the above has been simplified and modified.)
Likewise, I can issue a POST request and it returns (as expected) JSON data, when I use parameters like key1=value1&key2=value2. (I read that the default POST Content-Type is application/x-www-form-urlencoded.)
However, in another application I need to POST a piece of XML. After many tries and getting many errors, the approach I'm using is to: Set the header Content-Type to text/xml; charset=utf-8; Have no parameters and send the XML as the request body. I use a refined method:
PostHTTP(url: "https://example.com/api/someResource?ws_key=ABC...XYZ",
parameters: nil,
headers: ["Content-Type": "text/xml; charset=utf-8", "Content-Length": "\(xml.count)"],
body: "\(xml)") { (result) in ... }
(I image that you can determine what happens behind the scenes.)
For the POST request, to send a piece of XML:
Do I need to set the Content-Length or is this automatic?
Can I send parameters with the XML?
What headers (like Content-Type) do I require?
What structure (eg. xml=<?xml...) and encoding (eg. addingPercentEncoding) do I require?
Also I need to PUT data and I have similar method. The response from my attempt has the error
String could not be parsed as XML, XML length: 0
For a PUT request:
What headers (like Content-Type) do I require?
What structure (eg. xml=<?xml...) and encoding (eg. addingPercentEncoding) do I require?
Since I have tried many ways, an example of both PUT and POST would be ideal.
If you want to send data of XML you can do this in both PUT and POST
It does not have to be determined Content-Length
But you must add Content-Type
let req = NSMutableURLRequest(url: URL(string:"myUrl")!)
req.httpMethod = "POST"
req.setValue("application/xml;charset=utf-8;", forHTTPHeaderField: "Content-Type")
req.setValue("application/xml;", forHTTPHeaderField: "Accept")
var postBody = Data()
if let anEncoding = ("<?xml version='1.0' encoding='UTF-8'?>").data(using: .utf8) {
postBody.append(anEncoding)
}
if let anEncoding = "<Request>".data(using: .utf8) {
postBody.append(anEncoding)
}
if let anEncoding = "<test>\(123)</test>".data(using: .utf8) {
postBody.append(anEncoding)
}
if let anEncoding = "</Request>".data(using: .utf8) {
postBody.append(anEncoding)
}
req.httpBody = postBody
req.setValue("\(postBody.count)", forHTTPHeaderField: "Content-Length")
URLSession.shared.dataTask(with: req as URLRequest) { (data, urlreq, error) in
}

Partially downloading data in Swift

I'm trying to develop a download accelerator in Swift. It should get the file's size and divide it to n parts. Then it should download them at once by running multiple threads, and then merge the parts.
I read C# - Creating a Download Accelerator, unfortunately it doesn't help me.
I can do the multiple thread part easily by
DispatchQueue.main.async {
// The new thread
}
but the other part is harder. I usually download a file like this:
try Data(contentsOf: URL(string: assetsUrl!)!)
or I can do the thing that is explained in this answer
class Downloader {
class func load(url: URL, to localUrl: URL, completion: #escaping () -> ()) {
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = try! URLRequest(url: url, method: .get)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Success: \(statusCode)")
}
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: localUrl)
completion()
} catch (let writeError) {
print("error writing file \(localUrl) : \(writeError)")
}
} else {
print("Failure: %#", error?.localizedDescription);
}
}
task.resume()
}
}
But this is not C - it's very simplistic and doesn't accept many arguments. How can I make it get "first 200_000 bytes" from the server?
First of all, the server needs to implement HTTP range requests. If it doesn't, and you don't control the server, then you will not be able to do this.
If the server supports HTTP range requests, then you need to specify the range with request headers, as explained here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests
The essentials are that you first send a HEAD request to figure out whether the server supports HTTP range requests. This is determined by whether the response includes the Accept-Ranges header, with a non-zero value.
If the server supports HTTP range requests, then you can make a request for the resource, with the Range header set for example to a value of bytes=0-1023 (depends which format the Accept-Ranges header specified, in this case bytes)

Making Repeated Requests to API

I know how to make a regular API call using swift. What I am not able to understand is how to make the API call to be repeated until required.
I want to call the API every one second
API Call Code Snippet:
let url = URL(string: "https://api.darksky.net/forecast/34eaef38915078ea03c22bb9063bd7ea/37.8267,-122.4233")
let request = URLRequest(url: url!, cachePolicy: URLRequest.CachePolicy.reloadIgnoringCacheData, timeoutInterval: 10)
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: nil, delegateQueue: OperationQueue.main)
let task: URLSessionDataTask = session.dataTask(with: request, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) in
if let error = error {
print(error)
} else if let data = data,
let dataDictionary = try! JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
print("API Data:")
print(dataDictionary)
}
})
task.resume()
Note: This is not the actual API I will be calling
Ideally, for software solutions like financials you mentioned, the server must have support for some sort of Long Polling / websockets mechanism where once connection is established server feeds the client with new values whenever there are updates (refer : https://stackoverflow.com/a/12855533/1436617)
If server does not support : (Not the ideal solution) :
You can actually use recursion in this. On response (both success & failure) of the request again call the same function. That way you can continuously keep polling.
Remember to keep request timer short (5 or 10 seconds instead of 60 seconds) so that if there happens to be an network issue you can quickly make the next call.