Vapor 3 - send a HTTPRequest - vapor

How do you send an API request in Vapor 3 with the HTTPRequest struct?
I tried variations of the following code..
var headers: HTTPHeaders = .init()
let body = HTTPBody(string: a)
let httpReq = HTTPRequest(
method: .POST,
url: URL(string: "/post")!,
headers: headers,
body: body)
let httpRes: EventLoopFuture<HTTPResponse> = HTTPClient.connect(hostname: "httpbin.org", on: req).map(to: HTTPResponse.self) { client in
return client.send(httpReq)
}
The compile error Cannot convert value of type '(HTTPClient) -> EventLoopFuture<HTTPResponse>' to expected argument type '(HTTPClient) -> _'
I have tried other variations of code that worked.
Vapor 3 Beta Example Endpoint Request
let client = try req.make(Client.self)
let response: Future<Response> = client.get("http://example.vapor.codes/json")
I read and re-read:
https://api.vapor.codes/http/latest/HTTP/Structs/HTTPRequest.html
https://api.vapor.codes/http/latest/HTTP/Classes/HTTPClient.html
https://docs.vapor.codes/3.0/http/client/

Your problem is .map(to: HTTPResponse.self). Map needs to transform its result into a new result regularly, like you would map an array. However, the result of your map-closure returns an EventLoopFuture<HTTPResponse>. This results in your map function returning an EventLoopFuture<EventLoopFuture<HTTPResponse>>.
To avoid this complexity, use flatMap.
var headers: HTTPHeaders = .init()
let body = HTTPBody(string: a)
let httpReq = HTTPRequest(
method: .POST,
url: URL(string: "/post")!,
headers: headers,
body: body)
let client = HTTPClient.connect(hostname: "httpbin.org", on: req)
let httpRes = client.flatMap(to: HTTPResponse.self) { client in
return client.send(httpReq)
}
EDIT:
If you want to use the Content APIs you can do so like this:
let data = httpRes.flatMap(to: ExampleData.self) { httpResponse in
let response = Response(http: httpResponse, using: req)
return try response.content.decode(ExampleData.self)
}

HTTPClient.connect returns Future<HTTPClient> and it is mapping to a Future<HTTPResponse> not a EventLoopFuture<HTTPResponse>.
If you're expecting a single HTTPResponse use HttpClient.send instead of HTTPClient.connect.
If you're expecting multiple HTTPResponses then .map(to: HTTPResponse.self) must be changed to properly map to a EventLoopFuture<HTTPResponse>

Related

Alamofire GET request with åäö (special characters), invalid url

I'm trying to send an API get request with alamofire, but when i use the following url: https://stuntmans.evosale.se/admin/api/dräkt , the special character in "dräkt" is returning an invalid url as response. How would i go about using special characters with alamofire?
Thanks in advance!
Here is a part of the code:
let headers: HTTPHeaders = [
.authorization(username: "Username", password: "Password"),
.accept("application/json")]
AF.request(https://stuntmans.evosale.se/admin/api/dräkt, headers: headers).validate().responseJSON { response in
You could percent encode the URL with URLComponents
var components = URLComponents(string: "https://stuntmans.evosale.se")!
components.path = "/admin/api/dräkt"
guard let url = components.url else { return }
AF.request(url, headers: headers).validate().responseJSON { response in
Solved this by replacing the characters "åäö" with the corresponding characters they're supposed to represent in url, below is the code:
var result = Url
zip(["å", "ä", "ö"],["%C3%A5", "%C3%A4", "%C3%B6"]).forEach {
result = result.replacingOccurrences(of: $0, with: $1, options: .literal)
}
print (result) // the result

Alamofire not creating autnentication header to send credentials

I'm attempting to access an api using my username and api key. An example they give, which I believe is .NET, is:
Public Sub GET_Products()
Dim request As HttpWebRequest = WebRequest.Create("https://api.ssactivewear.com/v2/products/?style=39")
request.Method = "GET"
request.Credentials = New NetworkCredential("YOurCustomerNumber", "YourAPIKey")
Try
Dim response As HttpWebResponse = request.GetResponse
Dim StreamReader As New StreamReader(response.GetResponseStream())
Result = StreamReader.ReadToEnd
If response.StatusCode = HtppStatusCode.OK Then
Products = serializer.Deserialize(Of List(Of Sku))(Result)
Else
End If
Catch ex As Exception
End Try
End Sub
I've used the following to test the request for a response:
guard let url = URL(string: "https://api.ssactivewear.com/v2/products/") else { return }
let username = "myusername"
let password = "myapikey"
AF.request(url).authenticate(username: username, password: password).responseJSON { response in
print("Request: \(String(describing: response.request))")
print("Response: \(String(describing: response.response))")
print("HeaderFields: \(String(describing: response.request?.allHTTPHeaderFields))")
if let json = response.value {
print("JSON: \(json)")
//self.responseText.text = "JSON: \(json)"
}
if let error = response.error {
print("ERROR: \(error.localizedDescription)")
//self.responseText.text = "ERROR: \(error.localizedDescription)"
}
}
This fails authentication because no authentication header is sent. I believe I read this is expected behavior but didn't find a solution.
create APIMiddle class:
class APIManager
{
class func headers() -> HTTPHeaders
{
let headers: HTTPHeaders = [
"Content-Type": "application/json",
"Accept": "application/json"
]
return headers
}
}
your api call:
let request = AF.request(path, method: .post, parameters: params, encoding: JSONEncoding.default, headers: APIManager.headers(), interceptor: nil)
request.responseDecodable(of: UserModel?.self) {(resposnse) in
let user = resposnse.value
print(user)
}
Alamofire's authenticate method adds a URLCredential to the Request which is used to respond to server challenges. If the server never sends a challenge for those credentials, the credentials will never be used. api.ssactivewear.com appears to use HTTP Basic auth, which should work fine, but I couldn't find any specific documentation about that. There may be other requirements to properly make a request to the API. I suggest you investigate those requirements as well as the actual network traffic being sent to see what's actually happening.

-1103 Error Domain=NSURLErrorDomain Code=-1103 "resource exceeds maximum size" iOS 13

We are facing the following networking error when the response is somehow large(14kb) on iOS 13.
[-1103] Error Domain=NSURLErrorDomain Code=-1103 "resource exceeds maximum size"
As we are using Alamofire, this problem is treated as error result which breaks our treatments of the results.
The strange thing is that if we use NSURLSession directly, though this error is still seen from logging, we don't actually receive it in the callback of
session.dataTask(with: request) { value, response, error in ... }
So the result can treated correctly.
This problem is never seen before. Anyone has got some idea on that ?
With the help of the Slack community, we find the answer is that
on iOS13, it is not allowed to add a body in GET request. To make it work again, we can either switch to a POST/PUT request or add body value via url parameters of the GET request.
Pass query parameters in GET request like the following:
let parameters: Parameters = [
"param": value
]
Alamofire.request(urlString, method: .get, parameters: parameters, encoding: URLEncoding.queryString)
I have face same issue and find out the solution.
You can't pass parameter in body while using GET.
Either use POST method if API support or pass it in URL like below.
AnyURL?Parameter=Value&Parameter=Value
Finally found the answer. For GET services I was trying to add an httpBody. Something like this:
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
} catch let error {
errorCompletion(error)
return
}
The solution was just to add an if to avoid that chunk of code if httpMethod is a GET. Seems like an iOS 13 new behavior and the error message given by Swift definitely not helps at all
Alamofire: You should try this!
Alamofire.request(urlString, method: .get, parameters: parameters, encoding: URLEncoding.queryString)
Just avoid the httpBody for the GET API request.
if requestType != .get{
request.httpBody = data
}
#OR
For GET request append parameter into URL instead of the HTTP body
Use the below extension to create a query parameter from the dictionary.
extension NSObject {
func buildQueryString(fromDictionary parameters: [String:String]) -> String {
var urlVars = [String]()
for (var k, var v) in parameters {
let characters = (CharacterSet.urlQueryAllowed as NSCharacterSet).mutableCopy() as! NSMutableCharacterSet
characters.removeCharacters(in: "&")
v = v.addingPercentEncoding(withAllowedCharacters: characters as CharacterSet)!
k = k.addingPercentEncoding(withAllowedCharacters: characters as CharacterSet)!
urlVars += [k + "=" + "\(v)"]
}
return (!urlVars.isEmpty ? "?" : "") + urlVars.joined(separator: "&")
}
}
I used default url encoding instead of default json encoding and it's worked for me.
Alamofire.request(url, method: .get, parameters: param, encoding: URLEncoding.default)
OR
If you using URLRequestConvertible
enum NetworkRouter: URLRequestConvertible {
case someCase(lang:String)
var method: HTTPMethod {
return .get
}
var parameters: Parameters? {
switch self {
case .someCase(let param):
return ["lang": param.lang]
default:
return nil
}
}
var url: URL {
switch self {
case .someCase(let param):
return URL(string: Constants.baseURL + Constants.endPoint)!
default:
return URL(string: Constants.baseURL)!
}
}
var encoding: ParameterEncoding {
return URLEncoding.default
}
func asURLRequest() throws -> URLRequest {
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = method.rawValue
return try encoding.encode(urlRequest, with: parameters)
}
}
I got that issue because i pass empty parameters in Alamofire when send get request. So, instead of sending empty parameters i simply replace it for nil.
My fix is I only set .parameters to nil, then everything works fine. Because in Swift it still initialize the value of .parameters.
self.request.parameters = nil
Here you might have missing the method of the URL request that you are passing to data task. You have to add POST/PUT/DELETE to the URL request method parameter like below.
var request: URLRequest = URLRequest(url: SOME_VALID_URL)
request.body = SOME_VALID_DATA_IN_BYTES
request.method = "post" --> You are missing this.
I only see this issue when I build with Xcode 11. If I revert back to Xcode 10.3 I do not have the same issue anymore. While not a forever fix, if you're needing to push out code you can revert until you have time to fix it.

Alamofire swift4 InvalidURL

Note: Test Api Used
I am using the latest 4.0 version of Alamofire with Swift4.0 and am trying to make a api request (GET) however what I get is a FAIULRE: INvalid URL
func getFlightData(airportCode: String, minutesBehind: String, minutesAhead:String){
let securityToken: String = "Basic YWFnZTQxNDAxMjgwODYyNDk3NWFiYWNhZjlhNjZjMDRlMWY6ODYyYTk0NTFhYjliNGY1M2EwZWJiOWI2ZWQ1ZjYwOGM="
var headers: HTTPHeaders = [:]
headers["Authorization"] = securityToken
var params: Parameters = [:]
params["city"] = airportCode
params["minutesBehind"] = minutesBehind
params["minuteAhead"] = minutesAhead
Alamofire.request("https://api.qa.alaskaair.com/1/airports/‬SEA/flights/flightInfo?city=SEA&minutesBehind=10&minutesAhead=120", method: .get, headers: headers).responseJSON { (response) in
print(response)
}
The result I get is:
FAILURE: invalidURL("https://api.qa.alaskaair.com/1/airports/‬SEA/flights/flightInfo?city=SEA&minutesBehind=10&minutesAhead=120")
Do i need to encode my url? if yes then how? The same request works great with the URL provided by Alamofire documentation:
Alamofire.request("https://httpbin.org/get")
You need to encode parameters like this:
let parameters: Parameters = ["city": "SEA", "minutesBehind" : "10"] // Add all parameters
Alamofire.request("https://api.qa.alaskaair.com/1/airports/‬SEA/flights/flightInfo", parameters: parameters)

Postman Body Raw Request To Swift Alamofire

I'm trying to re-create this Postman settings for posting in Alamofire. This is my first time to see an API that requires both Parameters and a body with Raw Json.
I'm done with gathering and formatting my data (either in Json using SwiftyJSON or Dictionary [String : Any] / Parameters) for the said requirement.
While I did see a similar question to this: Postman request to Alamofire request but it doesn't have a valid answer. Assume that I'm quite experienced with posting/getting/etc data from various API but I just don't know how to pass raw data just like in the photo above. Please check out my comments too in the code.
Here's what I'm doing with my function for this request:
/** Apply to job with Shift.
* This service function creates a json data for applying.
*/
func someFuncService(_ job: Job, daySchedules: [(Int, String, Schedule)], withBlock completion: #escaping JobServiceCommonCallBack) {
AuthService.someFunc { (currentCustomer, accessToken) in
guard let lalala = currentCustomer?.id,
let accessT = accessToken else {
completion(LalaErrors.currentCustomerError)
return
}
guard let jobId = job.id else {
completion(LalaErrors.modelError)
return
}
let coreService = LalaCoreService()
let applicantEndpoint = LalaCoreService.Endpoint.Applicant
let parameters = [
"param1" : customerId,
"param2" : jobId,
"accessToken" : accessToken,
"shift" : self.generateDataFromDaySchedules(daySchedules) // this returns [String : Any], can be printed into Json using JSON(x)
] as Parameters
GPLog(classSender: self, log: "FINAL PARAMETER: \(parameters)")
coreService.request = Alamofire.request(
applicantEndpoint,
method: .post,
parameters: parameters,
encoding: URLEncoding.default, // I already have tried .httpbody too.
headers: nil
)
coreService.request {
(response, result) in
if let error = result?.error {
if response!.statusCode == 500 {
completion(GPKitError.newError(description: "Failed to apply. Please contact the admin."))
return
}
completion(error)
return
}
// Success
completion(nil)
return
}
}
}
EDIT: So the question is, what I'm doing wrong here? API returns me status code 500 internal server error.
coreService.request = Alamofire.request(
applicantEndpoint,
method: .post,
parameters: parameters,
encoding: URLEncoding.default, // I already have tried .httpbody too.
headers: nil
)
should be
coreService.request = Alamofire.request(
applicantEndpoint + accessToken,
method: .post,
parameters: parameters,
encoding: JSONEncoding.default,
headers: nil
)

Resources