Vapor: How to Get Response URL using async-http / Vapor client

Something I'm running into when using Vapor to crawl sites is if I enter a url like http://website(dot)com and it redirects to, I need to grab that final response URL to know what the final page URL is.

I can do this with URLSession by getting a URLResponse but I'm not seeing something similar in the Vapor client. I tried using the Vapor client and the async-http package it's built on and I can't find the page URL listed in the response.

If I send a request for a URL using the Vapor client that redirects, I get a status = 200 OK (not a 301) from the response and it gets the body content from the final URL. So it is following through to the final URL, I just can't figure out how to get that URL info.

Any pointers would be appreciated, thank you.

Example getting this info using URLSession

if let url = URL(string: self) {
            do {
                let(_, response) = try await url)
                if let responseURL = response.url {
                    result = responseURL.description
                    return result
            } catch {

Where the following is the first line of the response via URLResponse, and you can easily use response.url to grab the below "URL" value:

<NSHTTPURLResponse: 0x600000210c00> { URL: } { Status Code: 200, Headers {

Unfortunately Async HTTP Client doesn't have great support for viewing redirect "history", which means it also doesn't provide a good way of finding out what the last request was to generate a response. Do you mind filing an issue on that project?

Hey @lukasa I can file an issue, I found a bit of a workaround in the meantime, posting here in case anyone else has this issue.

Turn off client redirects. This is how you get the Location: "some url" in the headers.

// Set this in configure.swift
 app.http.client.configuration.redirectConfiguration = .disallow  // That turns off redirect follow, stops on redirect which is how you grab the location url

Then you search the headers for the key "Location" and get the value, this is a rough version using an extension of ClientResponse. It accounts for some quirks I'm finding when making requests.

// Stores header values
struct HeaderResponse: Codable {
    var status: String?
    var location: String?

extension ClientResponse {
    func getResponse() async throws -> HeaderResponse {
        let response = self
        var result = HeaderResponse()
        let headers = response.headers
        // Check status, location is sometimes uppercase so accounting for that
        if headers.contains(name: "location") || headers.contains(name: "Location") {
            for (key,value) in headers where key.lowercased() == "location" {
                result.location = value

        // for some reason the Vapor Response.status options (ex: .permanentRedirect) is not passing a BOOL test when a redirect has happened so we have to do this primitive way of getting the description and checking a string

        // Not exhaustive and needs more status codes
        if response.status.description == "301 Moved Permanently" {
            result.status = "301"
        } else {
            result.status = "200"
        return result