async-http-client icon indicating copy to clipboard operation
async-http-client copied to clipboard

Response data

Open MaximGolovlev opened this issue 4 years ago • 7 comments

I download pictures and save it on my server as data. Is there an easier way to get data from the response? in the previous version there was a simple command for this, I don't understand why it was necessary to complicate everything response.http.body.data

`import Vapor

class PictureManager {

static func saveImage(req: Request, externalUrl: String, name: String) throws -> EventLoopFuture<String> {

    var urlString = externalUrl

    if !urlString.contains("http") {
        urlString = (baseUrl + externalUrl)
    }
    
    return req.client.get("\(urlString)").flatMap { (response) in

        guard var body = response.body, let data = body.readData(length: body.readableBytes) else {
            return req.eventLoop.makeFailedFuture(Abort(.badRequest))
        }
        
        let imageData = ImageData(name: name, data: data, id: UUID())
        return imageData.save(on: req.db).flatMap({ req.eventLoop.tryFuture({ try imageData.url() }) })
    }
}

}`

MaximGolovlev avatar May 10 '21 11:05 MaximGolovlev

Do you like it better like this?

import NIOFoundationCompat

[...]

guard let data = response.body.map({ Data(buffer: $0) }) else {
    return req.eventLoop.makeFailedFuture(Abort(.badRequest))
}

...

weissi avatar May 10 '21 21:05 weissi

@MaximGolovlev does this help? Can we close this?

weissi avatar Jul 07 '21 13:07 weissi

Not the OP, but I'm at a loss on how to use the provided solution / workaround (?). What's req and why use it to post an Abort on it? Continuing on the example from the readme, how do I get the response body where it says // handle response?

import AsyncHTTPClient

let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
httpClient.get(url: "https://swift.org").whenComplete { result in
    switch result {
    case .failure(let error):
        // process error
    case .success(let response):
        if response.status == .ok {
            // handle response
        } else {
            // handle remote error
        }
    }
}

Bouke avatar Aug 20 '21 13:08 Bouke

@Bouke I think @weissi answer was in the context of how to use AsyncHTTPClient in the context of Vapor. In Vapor a req is an incoming http request. The http request exposes an instance of AsyncHTTPClient on it req.client. In the above example the user tries to make a call to another downstream service. If this call failed, the original incoming http request shall be aborted with the status code .badRequest.

Based on the default example you would get access to the response's data like so:

import AsyncHTTPClient

let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
httpClient.get(url: "https://swift.org").whenComplete { result in
    switch result {
    case .failure(let error):
        // process error
    case .success(let response):
        if response.status == .ok {
            // handle response
            guard let bodyAsByteBuffer = response.body else {
                // response has no body
            }
            let bodyAsData = Data(bodyAsByteBuffer)
        } else {
            // handle remote error
        }
    }
}

Does that help?

fabianfett avatar Aug 20 '21 13:08 fabianfett

I know this is an older thread, but I can't seem to find a single other question or answer out there that matches. I'm not sure if the APIs have changed, but today (using async-http-client 1.17.0), response.body is of type HTTPClientResponse.Body, not a single ByteBuffer which can be passed into the Data(buffer:) init from NIOFoundationCompat.

There does appear to be the collect(upTo:) method on HTTPClientResponse.Body which returns a ByteBuffer?, but its not clear to me how I would know the expected response length (and the convenience method for it on HTTPClientResponse is internal so I can't use that, but I guess I could reimplement it).

If I have to roll it myself I can but this feels like a gap to me - either in my understanding of how to do this or in the general Foundation compatibility

brianplattenburg-delta avatar Apr 26 '23 14:04 brianplattenburg-delta

upTo is not supposed to be the expected response length, it's a guard against you being DoS'd with a massive response. Put the maximum number of bytes you're willing to use for a single request body into the upTo parameter.

Note that you're using the new APIs, which default to streaming first.

Lukasa avatar Apr 26 '23 14:04 Lukasa

Got it, thanks! It was not obvious to me that this is more of a failsafe value than the actual expected content length.

brianplattenburg-delta avatar Apr 26 '23 14:04 brianplattenburg-delta