8

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:

rustyMagnet
  • 3,479
  • 1
  • 31
  • 41
  • *I have tried other variations of code that worked* - so you got it working some way but want to use `HTTPRequest` instead? – LinusGeffarth Mar 19 '19 at 16:14
  • 1
    hey @LinusGeffarth. I found a simpler way by using the higher level wrapper named `Client`. I found the comments for this `Protocol` were inside the code file of `Client.swift` very helpful. – rustyMagnet Mar 20 '19 at 14:31
  • 1
    For my future self (and anyone else): you need to do `req.client().post(....)`, see docs in `Client.swift`, like rustyMagnet described above. – LinusGeffarth Feb 09 '21 at 17:04

2 Answers2

7

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)
}
JoannisO
  • 875
  • 7
  • 13
  • Really nice @JoannisO. How do you access the actual content of the response and transform the `Future` to `Future`? Reference to your answer in https://stackoverflow.com/questions/48589415/vapor-3-beta-example-endpoint-request ? I can't seem to access `response.content.decode` using that style. – rustyMagnet Mar 20 '19 at 10:06
  • 1
    That's right, because you're using more low level APIs. HTTPResponse is the raw representation of a response. The Vapor "Content" APIs are a high level API. So you'll need to use the high level Vapor Client APIs to get access to these features. However, if you insist on using the lower level HTTPClient, you can do that. See my update in the post. – JoannisO Mar 20 '19 at 12:07
  • Thanks @JoannisO. Works perfectly. I noticed you put your `Worker` as `app`. Was that a mistake? Or am I mistaken by using `req`? – rustyMagnet Mar 20 '19 at 15:43
  • @rustyMagnet yes, that was a mistake on my part. I was testing this in `main.swift` where you can use the Application in the same fashion as it's also a container. – JoannisO Mar 22 '19 at 07:51
2

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>

Noremac
  • 554
  • 2
  • 7