# HTTP.jl client layer

**URL:** https://discourse.julialang.org/t/http-jl-client-layer/128098
**Category:** General Usage
**Tags:** http
**Created:** [April 15, 2025, 1:51pm UTC](https://discourse.julialang.org/t/http-jl-client-layer/128098 "2025-04-15T13:51:31Z")
**Posts on this page:** 5
**Page:** 1

<div class="post-metadata">

### Author: ![ohmsweetohm1](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ohmsweetohm1/32/49126_2.png) [@ohmsweetohm1](https://discourse.julialang.org/u/ohmsweetohm1)
#### Post date: [April 15, 2025, 1:51pm UTC](https://discourse.julialang.org/t/http-jl-client-layer/128098/1 "2025-04-15T13:51:31Z")

</div>

I’m trying to implement an auth layer for HTTP.jl

```julia
module ApiClient

import HTTP
import Dates
import JSON

Base.@kwdef struct TokenInfo
    access_token::String = ""
    expires_at::Dates.DateTime = Dates.now(Dates.UTC) + Dates.Second(0)
    token_type::String = "Bearer"
end

struct JWTConfig
    client_id::String
    client_secret::String
    token_url::String
    token_info::Ref{TokenInfo}
end

function getToken(jwt_config::JWTConfig)
    response = HTTP.post(
        jwt_config.token_url,
        ["Content-Type" => "application/json"],
        JSON.json(
            Dict(
                "grant_type" => "client_credentials",
                "client_id" => jwt_config.client_id,
                "client_secret" => jwt_config.client_secret,
            ),
        ),
    )
    response_data = JSON.parse(String(response.body))
    return TokenInfo(
        response_data["access_token"],
        Dates.now(Dates.UTC) + Dates.Second(response_data["expires_in"]),
        response_data["token_type"],
    )
end

function AuthLayer(handler)
    # return handler function
    return function (req; jwt_config::JWTConfig, kwargs...)
        # we add a custom header with stringified auth creds
        try
            # Try the original request
            HTTP.setheader(req, "Authorization" => "Bearer $(jwt_config.token_info[].access_token)")
            return handler(req; kwargs...)
        catch e
            # Check if it's a 401 error
            if e isa HTTP.StatusError && e.status == 401
                # Refresh the token and retry the request
                jwt_config.token_info[] = getToken(jwt_config)
                HTTP.setheader(req, "Authorization" => "Bearer $(jwt_config.token_info[].access_token)")
                try
                    return handler(req; kwargs...)
                catch
                    throw(e)
                end
            else
                # If it's not a 401 error, rethrow the exception
                throw(e)
            end
        end
    end
end

HTTP.@client [AuthLayer]

end

```

But somehow this does not work correctly. I can access the token and resource I want, but when I first access the token I get half the response (literary the JSON body is cut off). If I switch on debugging I see, that the HTTP response is weird with many fields doubled, e.g. two times `Content-Length: 107` and `Content-Length: 26699`.

How can I call the token requresh url when a request fails? Is this not supported?

---

<div class="post-metadata">

### Author: ![quinnj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/quinnj/32/11_2.png) [@quinnj](https://discourse.julialang.org/u/quinnj)
#### Post date: [April 15, 2025, 2:00pm UTC](https://discourse.julialang.org/t/http-jl-client-layer/128098/2 "2025-04-15T14:00:06Z")

</div>

Sorry for a bit of a rushed response, but yes, it can be a little tricky when trying to retry requests like this. You can check out some of the machinery we use/do in the RetryRequest layer here: [HTTP.jl/src/clientlayers/RetryRequest.jl at ef9a169c5471e14558409a649057d773c09451f6 · JuliaWeb/HTTP.jl · GitHub](https://github.com/JuliaWeb/HTTP.jl/blob/ef9a169c5471e14558409a649057d773c09451f6/src/clientlayers/RetryRequest.jl#L63)

---

<div class="post-metadata">

### Author: ![ohmsweetohm1](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ohmsweetohm1/32/49126_2.png) [@ohmsweetohm1](https://discourse.julialang.org/u/ohmsweetohm1)
#### Post date: [April 15, 2025, 2:04pm UTC](https://discourse.julialang.org/t/http-jl-client-layer/128098/3 "2025-04-15T14:04:53Z")

</div>

```julia
function AuthLayer(handler)
    # return handler function
    return function (req; jwt_config::JWTConfig, kwargs...)
        try
            # Try the original request
            HTTP.setheader(req, "Authorization" => "Bearer $(jwt_config.token_info[].access_token)")
            return handler(req; kwargs...)
        catch e
            # Check if it's a 401 error
            if e isa HTTP.StatusError && e.status == 401
                HTTP.reset!(req.response) # reset the original request for retry
                # Refresh the token and retry the request
                jwt_config.token_info[] = getToken(jwt_config)
                HTTP.setheader(req, "Authorization" => "Bearer $(jwt_config.token_info[].access_token)")
                try
                    return handler(req; kwargs...)
                catch
                    throw(e)
                end
            else
                # If it's not a 401 error, rethrow the exception
                throw(e)
            end
        end
    end
end

```

---

<div class="post-metadata">

### Author: ![ohmsweetohm1](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ohmsweetohm1/32/49126_2.png) [@ohmsweetohm1](https://discourse.julialang.org/u/ohmsweetohm1)
#### Post date: [May 6, 2025, 2:40pm UTC](https://discourse.julialang.org/t/http-jl-client-layer/128098/4 "2025-05-06T14:40:47Z")

</div>

@quinnj I wanted to have the auth layer as the last one but I get an error:

```julia
HTTP.@client (last = AuthLayer) ()

```

```julia
ERROR: MethodError: no method matching keys(::typeof(ApiClient.AuthLayer))
The function `keys` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  keys(::MathOptInterface.Utilities.IndexMap)
   @ MathOptInterface C:\JuliaPackages\packages\MathOptInterface\JdLrc\src\Utilities\copy\index_map.jl:98
  keys(::Base.TermInfo)
   @ Base terminfo.jl:232
  keys(::LibPQ.PQConversions)
   @ LibPQ C:\JuliaPackages\packages\LibPQ\i4yBe\src\typemaps.jl:259
  ...

Stacktrace:
  [1] eachindex(itrs::Function)
    @ Base .\abstractarray.jl:318
  [2] iterate(A::Base.Iterators.Reverse{typeof(ApiClient.AuthLayer)})
    @ Base.Iterators .\iterators.jl:143
  [3] _foldl_impl(op::Base.BottomRF{…}, init::Function, itr::Base.Iterators.Reverse{…})
    @ Base .\reduce.jl:56
  [4] foldl_impl(op::Base.BottomRF{…}, nt::Function, itr::Base.Iterators.Reverse{…})
    @ Base .\reduce.jl:48
  [5] mapfoldr_impl(f::Function, op::Function, nt::Function, itr::Function)
    @ Base .\reduce.jl:204
  [6] mapfoldr(f::Function, op::Function, itr::Function; init::Function)
    @ Base .\reduce.jl:223
  [7] foldr(op::Function, itr::Function; kw::@Kwargs{init::HTTP.ConnectionRequest.var"#connections#4"{…}})
    @ Base .\reduce.jl:242
  [8] stack(observelayers::Bool, requestlayers::Function, streamlayers::Tuple{})
    @ HTTP C:\JuliaPackages\packages\HTTP\MIUdD\src\HTTP.jl:447

```

---

<div class="post-metadata">

### Author: ![ohmsweetohm1](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ohmsweetohm1/32/49126_2.png) [@ohmsweetohm1](https://discourse.julialang.org/u/ohmsweetohm1)
#### Post date: [May 6, 2025, 2:44pm UTC](https://discourse.julialang.org/t/http-jl-client-layer/128098/5 "2025-05-06T14:44:21Z")

</div>

Sry, my bad!

> **[API Reference · HTTP.jl](https://juliaweb.github.io/HTTP.jl/stable/reference/#HTTP.%40client)**

> a NamedTuple with keys `first` and `last` can be provided with values being a **collection** of layers.

```julia
HTTP.@client (last = [AuthLayer]) ()

```
