How to use `ParameterEncoding` together with `URLRequestConvertible`? - alamofire

I was migrating my code to Swift 3 and updated Alamofire to the swift 3 branch.
There, the suggested way of handling parameter encoding is to use one of the new ParameterEncoding conforming structs, such as URLEncoding, that has an encode method.
The problem is that this method now throws, which makes sense, but the URLRequestConvertible protocol still expects a urlRequest property that returns the constructed request, so we can't just call encode and return the result, nor return a nil.
What is the suggested way of handling this, if the router can't fail?

That's how Alamofire handles it internally :
open func request(
_ urlString: URLStringConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: [String: String]? = nil)
-> DataRequest
{
let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
do {
let encodedURLRequest = try encoding.encode(urlRequest, with: parameters)
return request(resource: encodedURLRequest)
} catch {
let request = self.request(resource: urlRequest)
request.delegate.error = error
return request
}
}
Basically, it uses the request without the parameters. I don't know how far it can be used in your own implementation (or How to migrate Alamofire router class to Swift 3? 's OP).
I'd suggest filing an issue with Alamofire if this is not possible. The new ParameterEncoding is literally two days old (PR 1465) and still in the 4.0.0 beta cycle.
In any case, using URLEncoding.encode() seldom fails if you set your own URLRequest because the only error thrown is when there's no URL provided with the request.
EDIT: Here you go, 4.0.0 released, and the issue's been fixed! (PR 1505). There have been other changes to URLRequestConvertible but all's in the Migration Guide and README.

Related

Access encodingResult when uploading with Alamofire 5

I'm trying to update my app to Alamofire 5 and having difficulties due to a hack-ish way I'm using it I guess.
Anyhow, I need background uploads and Alamofire is not really designed to do this. Even so, I was using it to create a properly formatted file containing multipart form so I can give it to the OS to upload in the background later.
I'll post the code doing this in Alamofire 4, my question is how can I get the url of the file I was previously getting with encodingResults?
// We're not actually going to upload photo via alamofire. It does not offer support for background uploads.
// Still we can use it to create a request and more importantly properly formatted file containing multipart form
Api.alamofire.upload(
multipartFormData: { multipartFormData in
multipartFormData.append(imageData, withName: "photo[image]", fileName: filename, mimeType: "image/jpg")
},
to: "http://", // if we give it a real url sometimes alamofire will attempt the first upload. I don't want to let it get to our servers but it fails if I feed it ""
usingThreshold: UInt64(0), // force alamofire to always write to file no matter how small the payload is
method: .post,
headers: Api.requestHeaders,
encodingCompletion: { encodingResult in
switch encodingResult {
case .success(let alamofireUploadTask, _, let url):
alamofireUploadTask.suspend()
defer { alamofireUploadTask.cancel() }
if let alamofireUploadFileUrl = url {
// we want to own the multipart file to avoid alamofire deleting it when we tell it to cancel its task
let fileUrl = ourFileUrl
do {
try FileManager.default.copyItem(at: alamofireUploadFileUrl, to: fileUrl)
// use the file we just created for a background upload
} catch {
}
}
case .failure:
// alamofire failed to encode the request file for some reason
}
}
)
Multipart encoding is fully integrated into the now-asynchronous request pipeline in Alamofire 5. That means there's no separate step to use. However, you can use the MultipartFormData type directly, just like you would in the request closure.
let data = MultipartFormData()
data.append(Data(), withName: "dataName")
try data.encode()

AlamoFire doesn't appear to be doing anything

I am attempting to write a simple scraper with AlamoFire, vis a vis a server-side Vapor back end. AlamoFire appears to be initiated properly, but I'm not getting any action from the callback handler.
import Routing
import Vapor
import Alamofire
public func routes(_ router: Router) throws {
router.get("scrape") { req -> String in
let stuff = Stuff(id: nil, sourcecode: "This saves to the database.")
stuff.save(on: req)
let q = Alamofire.request("http://sigh-fi.com/test.txt").responseString { response in
// None of this prints to the terminal.
print("Success: \(response.result.isSuccess)")
print("Request: \(String(describing: response.response))")
print("Result: \(String(describing: response.result))")
print("String: \(String(describing: response.result.value))")
// ideally I'd like to run...
// let morestuff = Stuff(id: nil, sourcecode: response.result.value)
let morestuff = Stuff(id: nil, sourcecode: "This doesn't save to the database, so I'm not even getting that far.")
morestuff.save(on: req)
}
print(q) // prints "GET http://sigh-fi.com/test.txt" as expected
return "okay"
}
}
Unfortunately I can't tell if this is a Vapor problem, an Alamofire or Swift problem. Any suggestions would be greatly appreciated.
So it turns out that Vapor has its own HTTP client library, and it seems to work just fine. Still unsure why Alamofire fell apart, but it's moot.
Thank you for your assistance, Nick.
https://docs.vapor.codes/3.0/http/client/

Alamofire Parse Response Data when validate fails

So the API I'm working with will sometimes send an error message in the response body when a request fails. This is located in response.data. Sometimes it's JSON, sometimes it's a string. I'm using the validate method so result.value is nil when an error occurs.
Is there a way of having Alamofire serialize the data from NSData to a string or for JSON to [ String : AnyObject ] like it would if the response was successful?
I would like to keep using the validate method.
EDIT:
Here's a link to a feature request I started on the Alamofire GitHub project.
https://github.com/Alamofire/Alamofire/issues/1459
There is not currently. I'm actually working on this very feature in Alamofire 4 right now. In Alamofire 3, you'll have to parse the response.data yourself if you get that validation error. In Alamofire 4, you'll at least have access to the response.data at the time of validation as well as be able to customize the Error that is generated by validation.
Most likely what the final solution will be is the ability to check in validation if you know there's going to be an error (checking response status code and headers). Then based on the type of error, you could parse the response.data to extract the error message from the server and throw a VERY SPECIFIC error from validation. This is most likely what the new system will allow. This way you could identify OAuth2 access token errors right in validation and throw your own custom error rather than having to use a convoluted system of response serializers to do it.
Swift 4
If you get an error, you can try parsing the response data as a string or as json.
import Alamofire
import SwiftyJSON
Alamofire.request("http://domain/endpoint", method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil)
.validate()
.responseJSON(completionHandler: { response in
if let error = response.error {
if let data = response.data {
if let errorString = String(bytes: data, encoding: .utf8) {
print("Error string from server: \(errorString)")
} else {
print("Error json from server: \(JSON(data))")
}
} else {
print("Error message from Alamofire: \(error.localizedDescription)")
}
}
guard let data = response.result.value else {
print("Unable to parse response data")
return
}
print("JSON from server: \(JSON(data))")
})

AlamoFire request() only working in viewDidLoad()

Can anyone explain why my request to a URL with the following AlamoFire request only works in my viewDidLoad() method?
request(.GET, URLString: "https://mySecureUrl")
.responseJSON { (_, _, json, error) in
let jsonEntries = JSON(json!)
}
I have added the AlamoFire swift files to my project. Whenever I add the above lines of code to any method, I never get to the code below
let jsonEntries = JSON(json!)
The program just skips over the responseJSON altogether. Any ideas why?
If you use Swift2 you must use Alamofire swift2 branch from his github : https://github.com/Alamofire/Alamofire/tree/swift-2.0
And as mentioned in the Alamofire readme, the json response is encapsulated in result parameter like this :
Alamofire.request(.GET, "http://httpbin.org/get")
.responseJSON { _, _, result in
print("Response JSON: \(result.value)")
}

Custom NSURLProtocol to show/hide NetworkActivityIndicator

I'm working with Alamofire. Following up on Mattt's comment in one of the closed issues on GitHub, I attempted a NSURLProtocol-based mechanism to set the UIApplication.sharedApplication().networkActivityIndicatorVisible flag.
However, after registering my custom protocol with Alamofire's underlying NSURLSessionConfiguration I got stuck pretty quickly since Alamofire doesn't expose much of its SessionDelegate class.
Is there a simple way to notify the custom NSURLProtocol the request has completed without reproducing much of the already existing implementation of Alamofire inside my NSURLProtocol?
A different way of doing it (not implementing NSURLProtocol-way) would be creating an API which would have an executeRequest method:
func executeRequest(method: Alamofire.Method, url: NSURL, parameters: [String: String]?, headers: [String : String]?) {
// Show activity indicator on status bar
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
let request = manager.request(method, url, parameters: parameters, encoding: .JSON, headers: headers)
.responseJSON {
response in
...
// Hide activity indicator on status bar
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}
}
Of course, all your requests would have to be executed using the newly created API.