I am trying to get all the information for an URLRequest in order to save it in a log. The problem comes when I try to do String(reflecting: request). This method only gives my the final URL but not all the the other information such us body, httpMethod, encoding, etc.
However, if I do po request, I can see all the information of the request.
Any hint?
Thanks
You can use the .description and .debugDescription of an object to outpout a description. These properties rely on the CustomStringConvertible and CustomDebugStringConvertible protocols respectively.
To change what is printed out you can define/redefine the conformance to the protocol. As a trivial example for URLRequest:
extension URLRequest: CustomDebugStringConvertible {
var debugDescription: String {
"""
URL Request for \(self)
full url \(self.url)
header fields \(self.allHTTPHeaderFields)
"""
}
}
let url = URL(string: "https://somedomain.com")
var request = URLRequest(url: url!)
request.addValue("ghuaidbwjkbdwd", forHTTPHeaderField: "Auhentication")
print(request.debugDescription)
will output
URL Request for https://somedomain.com
full url Optional(https://somedomain.com)
header fields Optional(["Auhentication": "ghuaidbwjkbdwd"])
Obviously you'd probably want to do something a bit more useful with the output but this should give you the tools.
When doing this for an object with a pre-defined conformance (eg. URLRequest, where it is defined in Foundation) you will get a warning about the previous implementation.
In this scenario, as the object already conforms to the description protocols, you can leave out re-conforming to it and just override the property. So in the above example you should actually use
extension URLRequest {
var debugDescription: String {
If the object you are working with is a class, you will need to mark this as an override.
If you want to get all the properties of the object you could use a Mirror. You could define this directly on the object with an extension, but if it is something you are likely to use in more than one place maybe implement it via a protocol with a default implementation:
protocol Reflectable {
func reflect()
}
extension Reflectable {
func reflect() {
let mirror = Mirror(reflecting: self)
print("\(Self.self) for: \(self)")
for item in mirror.children {
print("\(String(describing: item.label)): \(item.value)")
}
}
}
You can then subscribe URLRequest to this and either access the .reflect() method directly or build it into either CustomStringConvertible or CustomDebugStringConvertible
extension URLRequest: Reflectable {}
request.reflect()
providing an output of:
URLRequest for: https://somedomain.com
Optional("url"): Optional(https://somedomain.com)
Optional("cachePolicy"): 0
Optional("timeoutInterval"): 60.0
Optional("mainDocumentURL"): nil
Optional("networkServiceType"): NSURLRequestNetworkServiceType
Optional("allowsCellularAccess"): true
Optional("httpMethod"): Optional("GET")
Optional("allHTTPHeaderFields"): Optional(["Auhentication": "ghuaidbwjkbdwd"])
Optional("httpBody"): nil
Optional("httpBodyStream"): nil
Optional("httpShouldHandleCookies"): true
Optional("httpShouldUsePipelining"): false
Related
I am trying to write some code that lets me both validate that a string is in fact a valid web address to reach a remote server && be able to unwrap it safely into a url for usage.
From what gathered from various posts and Apple's source code:
URLComponents is a structure designed to parse URLs based on RFC 3986 and to construct URLs from their constituent parts.
and based on w3 schools:
A URL is a valid URL if at least one of the following conditions holds:
The URL is a valid URI reference [RFC3986]....
Would this code suffice to detect addresses that reach remote servers on the world wide web?
import Foundation
extension String {
/// Returns `nil` if a valid web address cannot be initialized from self
var url: URL? {
guard
let urlComponents = URLComponents(string: self),
let scheme = urlComponents.scheme,
isWebServerUrl(scheme: scheme),
let url = urlComponents.url
else {
return nil
}
return url
}
/// A web address normally starts with http:// or https:// (regular http protocol or secure http protocol).
private func isWebServerUrl(scheme: String) -> Bool {
(scheme == WebSchemes.http.rawValue || scheme == WebSchemes.https.rawValue)
}
}
Can you provide some feedback on this approach and let me know if there are any optimizations that can be made? or if it's incorrect?
Any and all comments are appreciated.
You could go even simpler and do
import Foundation
extension String {
/// Returns `nil` if a valid web address cannot be initialized from self
var url: URL? {
return URL(string: self)
}
/// A web address normally starts with http:// or https:// (regular http protocol or secure http protocol).
var isWebURL: Bool {
get {
guard let url = self.url else { return false }
return url.scheme == "http" || url.scheme == "https"
}
}
}
Explanation
Initializing a URL using a string will return nil if the string isn't a valid url, so you can get the url by just initing a URL object. Additionally, checking the scheme is super easy because URL has a property scheme that we can check against the required parameters.
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
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
Is it possible to use NSURLCache to cache responses when the URL includes a changing query item? For example, we add Mashery's required "sig=XXXXXX" query item, which changes for each request.
If not, is there a workaround?
Solved by subclassing NSURLCache and overriding its caching methods.
In each overridden method, I remove the query item from the request prior to calling the superclass' method.
For example:
override func storeCachedResponse(cachedResponse: NSCachedURLResponse, forRequest request: NSURLRequest) {
let strippedRequest = removeQueryItemFromRequest(self.queryItemName, request: request)
if let url = strippedRequest.URL {
let response = NSURLResponse(URL: url, MIMEType: cachedResponse.response.MIMEType, expectedContentLength: Int(cachedResponse.response.expectedContentLength), textEncodingName: cachedResponse.response.textEncodingName)
let newCachedResponse = NSCachedURLResponse(response: response, data: cachedResponse.data)
super.storeCachedResponse(newCachedResponse, forRequest: strippedRequest)
}
else {
super.storeCachedResponse(cachedResponse, forRequest: request)
}
}
self.queryItemName is a stored property passed in to a custom initializer.
I'm trying to design an API helper function for an app. The idea is that I'll be able to call the function from a viewController, using code such as:
let api = APIController();
api.request("get_product_list")
api.delegate = self
Here's the class so far:
import Foundation
protocol APIControllerProtocol {
func didReceiveAPIResults(originalRequest: String, status: Bool, data: String, message: String)
}
class APIController {
var delegate: APIControllerProtocol?
let url = "https://example.co.uk/api.php"
let session = NSURLSession.sharedSession()
let appID = "EXAMPLEAPPID";
let deviceID = "EXAMPLEDEVICE"
func request(req:String)-> Void {
let urlString = "\(url)?request=\(req)"
let combinedUrl = NSURL(string: urlString)
let request = NSMutableURLRequest(URL: combinedUrl!)
request.HTTPMethod = "POST"
let stringPost="app_id=\(appID)&device_id=\(deviceID)"
let data = stringPost.dataUsingEncoding(NSUTF8StringEncoding)
request.timeoutInterval = 60
request.HTTPBody=data
request.HTTPShouldHandleCookies=false
let task = session.dataTaskWithRequest(request) {
(data, response, error) -> Void in
do {
let jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers ) as! NSDictionary
let statusInt = jsonData["status"]! as! Int
let status = (statusInt == 1)
let data = String(jsonData["data"])
let message = String(jsonData["message"])
self.delegate?.didReceiveAPIResults(req,status: status,data: data,message: message)
} catch _ {
print("ERROR")
}
}
task.resume()
}
}
The difficulty I'm having is that the 'data' parameter might one of the following:
A string / number, such as the number of purchases a customer has made
An array of items, such as a list of products
A dictionary, such as the customer's details
I've set the data parameter to String as that allowed me to do some testing, but then converting it back into something usable for a tableView got very messy.
Are there any experts on here that can advise me the best way to do this? Perhaps showing me how I'd use the results in a cellForRowAtIndexPath method? Here's an example response from the API, in case it's useful:
{
"status":1,
"message":"",
"cached":0,
"generated":1447789113,
"data":[
{"product":"Pear","price":0.6},
{"product":"Apple","price":0.7},
{"product":"Raspberry","price":1.1}
]
}
One function doing too many things at once makes a really messy code. Also you don't want too many if statements or enums - your View controller will grow really fast.
Id suggest splitting request and parse logic. Your API class would be then responsible only for requests. It would return data to another class, that would be responsible for parsing. Then in the Parser class you could add methods like toDictionary() or toArray(), toArrayOfClasses() and so on. That would be the basic API structure.
If you want to expand it a little bit, you could add another class layer that would handle all that logic so your View Controller doesn't know if it uses API or another data source - this way you could easy implement new things in the future, like Core Data or migrate from your API class to some framework, maybe Parse.com - this layer gives you flexibility.
Example structure:
API - requests
Parser - parsing API response
DataManager - Send request to API and return parsed response.
or if you don't want this third point, you can just request & parse in the view controller.