I am trying in the Request Adapter of Alamofire to add a GET parameter. However in the request adapter I am only able to add HTTPHeader fields.
Currently my request adapter looks like:
// MARK: - RequestAdapter
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
if let url = urlRequest.url, url.lastPathComponent.hasPrefix(baseURLString) {
var urlRequest = urlRequest
// Want to inject param here
// e.g. urlRequest.addParam(param: "session", value: sessionToken")
return urlRequest
}
return urlRequest
}
I have a Router configured for the Paths but since I want my AuthHandler to be responsible to all Authentication related stuff I want to inject my sessionToken. This makes sure, together with RequestRetrier that any HTTP 401 related error is dealt with.
What is the best way to change the urlRequest?
Can you try
let params: Parameters = ["session": sessionToken]
return URLEncoding.default.encode(urlRequest, with: params)
(or)
return URLEncoding.queryString.encode(urlRequest, with: params)
Thanks
Sriram
Related
I'm getting an error while trying to encode a parameter into a request url.
Here is my function to get the request url:
func asURLRequest() throws -> URLRequest {
let url = try baseURL.asURL().appendingPathComponent(path)
var request = URLRequest(url: url)
request.method = method
if method == .get {
request = try URLEncodedFormParameterEncoder().encode(parameters, into: request)
} else if method == .post {
request = try JSONParameterEncoder().encode(parameters, into: request)
request.setValue("application/json", forHTTPHeaderField: "Accept")
}
return request
}
It is working when the parameter is a dictionary like ["id": 1]. The url would be:
http://.../api/v1/items/?id=1
I want to pass the parameter 1 only, so the url would be like this:
http://.../api/v1/items/1
But it doesn't work, I get this error from Alamofire:
requestRetryFailed(retryError:
Alamofire.AFError.requestRetryFailed(retryError:
Alamofire.AFError.parameterEncoderFailed(reason:
Alamofire.AFError.ParameterEncoderFailureReason.encoderFailed(error:
Alamofire.URLEncodedFormEncoder.Error.invalidRootObject("string("1")")))
What you want is a path encoding, not a query encoding or form encoding. There is no specific parameter encoder in Alamofire for path components (though there is an ongoing feature request). Usually people encode them into the path directly, so you can modify your code to do so directly by using a router and having each route encode its own parameters.
func encodeParameters(into request: URLRequest) throws -> URLRequest {
switch self {
case .someRoute(parameters):
return try URLEncodedFormParameterEncoder().encode(parameters, into: request)
case .pathParameterRoute(parameter):
var request = request
request.url?.appendPathComponent(parameter)
return request
}
}
The server returning a json file that is:
{"ctrl":{"code":400,"text":"Not valid Access token","ts":"2020-03-05T11:54:01.547Z"}}
Code:
public func startDownload(url: URL, pathURL: URL) {
let accessToken: String! = "Bearer \(Constants.access_token)"
self.dirURL = pathURL
var request = URLRequest(url: url)
guard let token = accessToken else { return }
request.addValue(token, forHTTPHeaderField: "Authorization")
downloadTask = backgroundSession.downloadTask(with: request)
downloadTask.resume()
}
FYI: access token is valid, it is working with Postman.
You're going to have a problem because, unfortunatelly, there's no good solution to this issue. Authorization is one of the Reserved HTTP Headers and setting it either in URLRequest header, or in URLSessionConfiguration.httpAdditionalHeaders may simply not work:
If you set a value for one of these reserved headers, the system may ignore the value you set, or overwrite it with its own value, or simply not send it.
One might expect you could provide this token in URLSessionTaskDelegate method urlSession(_:task:didReceive:completionHandler:) which handles authentication challenges, but in there you need to provide a URLCredential object, and sadly it doesn't have a constructor that takes a Bearer token, so that's a no-go.
So basically, short of writing your own URLProtocol implementation, your best bet would be to send the token in some additional, custom, header field and have the server grab it from there (if you have control over server code). Source
I'm using Moya library to handle networking layer, and I already have a custom plugin that add an authentication token to the header.
What I want to do is to make this plugin cancel the request and return a failure response (or throw an error) if the token is not available yet.
P.S. I extended the protocol TargetType to add extra variable that indicates if the target needs authentication or not, so I need to access these data to determine if the authentication token is needed in the header or not.
this is a snapshot of my custom plugin:
struct AuthTokenPlugin: PluginType {
let tokenClosure:()->String?
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
guard let target = target as? AuthorizebleTargetType, target.needsAuth else {
return request
}
guard let token = tokenClosure() else {
// Here where a failure response will be triggered or an error should be thrown
return ......
}
var request = request
request.addValue( "Token " + token, forHTTPHeaderField:"Authorization")
return request
}
}
P.S.2: throwing an error is not a good practice and it is not possible because the enclosing function "prepare(_:target:)" is not declared 'throws'.
I don't think that we can implement such logic with usage of protocol TargetType in cause his methods don't return Bool values and are not throw-marked.
Take a look at MoyaProvider init parameters. There is a requestClosure param in it. You can copy-paste and replace this parameter's default implementation with your own implementation which will check authorization header of Endpoint.
Default implementation of this closure:
final class func defaultRequestMapping(for endpoint: Endpoint, closure: RequestResultClosure) {
do {
let urlRequest = try endpoint.urlRequest()
closure(.success(urlRequest))
} catch MoyaError.requestMapping(let url) {
closure(.failure(MoyaError.requestMapping(url)))
} catch MoyaError.parameterEncoding(let error) {
closure(.failure(MoyaError.parameterEncoding(error)))
} catch {
closure(.failure(MoyaError.underlying(error, nil)))
}
}
UPD with my comment:
I suggest to check that if Endpoint has header with key “Authorization”, but it’s value is empty string, then call closure parameter with .failure case in requestClosure
(Using iOS 9, Swift 2, Alamofire 3)
My app interacts with a REST service that requires a session token in the header. If the session token isn't already available before manager.request() is called, there's no reason to send the request only to have it fail. So I'd like to abort the request with my own error, and have the request's chained response handler take care of it - i.e. the caller wouldn't know that the request was actually never sent to the server.
What's the best way to do this with Alamofire 3?
Any way to have the same effect as Request.cancel() but with custom error, as below?:
static func request(method: Alamofire.Method, _ URLString: URLStringConvertible, parameters: [String: AnyObject]? = nil) -> Request {
let api = apiManager.sharedInstance
guard let sessionToken = api.sessionToken else {
let req = api.alamofireManager.request(method, URLString, parameters)
req.cancel() // ***I'd like to do: req.cancelWithError(.NoSessionToken)
return req
}
// Create request and add session token to header
// ...
}
Short : Is there any way to modify the backed NSURLRequest (headers, body, or other) of the Request object after it has been created ?
Long : I have a custom Manager with startRequestsImmediately set to false. If my access token is currently being refreshed, all requests are waiting for refresh to finish, then they are resume.
Of course, I need to modify theirs HTTP headers to update access token before resuming them.
I can't just track directly NSURLRequests then recreating Requests object because I need to keep all completion closure previously set for those requests.
Is anyone find a way to do that ?
Thanks for your help.
You can use a RequestAdapter. Example:
class MyAdapter: RequestAdapter {
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
var urlRequest = urlRequest
urlRequest.setValue("Bar", forHTTPHeaderField: "X-Foo")
return urlRequest
}
}
// Then...
let manager = SessionManager()
manager.adapter = MyAdapter()
manager.request("https://httpbin.org/get")