Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions Manifest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# This file is machine-generated - editing it directly is not advised

julia_version = "1.9.2"
manifest_format = "2.0"
project_hash = "c46963b102670aecccdbc101e6269347b2e132f5"

[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[deps.BitFlags]]
git-tree-sha1 = "43b1a4a8f797c1cddadf60499a8a077d4af2cd2d"
uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35"
version = "0.1.7"

[[deps.CodecZlib]]
deps = ["TranscodingStreams", "Zlib_jll"]
git-tree-sha1 = "9c209fb7536406834aa938fb149964b985de6c83"
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
version = "0.7.1"

[[deps.ConcurrentUtilities]]
deps = ["Serialization", "Sockets"]
git-tree-sha1 = "584a4f8d88293ad5eaa5de6dff3e5066b9d47b14"
repo-rev = "npr-pool-size"
repo-url = "https://github.com/JuliaServices/ConcurrentUtilities.jl.git"
uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb"
version = "2.3.0"

[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[deps.ExceptionUnwrapping]]
deps = ["Test"]
git-tree-sha1 = "e90caa41f5a86296e014e148ee061bd6c3edec96"
uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4"
version = "0.1.9"

[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[deps.JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "abc9885a7ca2052a736a600f7fa66209f96506e1"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.4.1"

[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[deps.LoggingExtras]]
deps = ["Dates", "Logging"]
git-tree-sha1 = "cedb76b37bc5a6c702ade66be44f831fa23c681e"
uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36"
version = "1.0.0"

[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[deps.MbedTLS]]
deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "Random", "Sockets"]
git-tree-sha1 = "03a9b9718f5682ecb107ac9f7308991db4ce395b"
uuid = "739be429-bea8-5141-9913-cc70e7f3736d"
version = "1.1.7"

[[deps.MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
version = "2.28.2+0"

[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
version = "2022.10.11"

[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
version = "1.2.0"

[[deps.OpenSSL]]
deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "OpenSSL_jll", "Sockets"]
git-tree-sha1 = "51901a49222b09e3743c65b8847687ae5fc78eb2"
uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c"
version = "1.4.1"

[[deps.OpenSSL_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl"]
git-tree-sha1 = "cae3153c7f6cf3f069a853883fd1919a6e5bab5b"
uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
version = "3.0.9+0"

[[deps.Preferences]]
deps = ["TOML"]
git-tree-sha1 = "7eb1686b4f04b82f96ed7a4ea5890a4f0c7a09f1"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.4.0"

[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[deps.Random]]
deps = ["SHA", "Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
version = "0.7.0"

[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[deps.SimpleBufferStream]]
git-tree-sha1 = "874e8867b33a00e784c8a7e4b60afe9e037b74e1"
uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7"
version = "1.1.0"

[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
version = "1.0.3"

[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[deps.TranscodingStreams]]
deps = ["Random", "Test"]
git-tree-sha1 = "9a6ae7ed916312b41236fcef7e0af564ef934769"
uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
version = "0.9.13"

[[deps.URIs]]
git-tree-sha1 = "074f993b0ca030848b897beff716d93aca60f06a"
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
version = "1.4.2"

[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
version = "1.2.13+0"
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[compat]
CodecZlib = "0.7"
ConcurrentUtilities = "2.2"
ConcurrentUtilities = "2.3"
ExceptionUnwrapping = "0.1"
LoggingExtras = "0.4.9,1"
MbedTLS = "0.6.8, 0.7, 1"
Expand Down
87 changes: 72 additions & 15 deletions src/Connections.jl
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,64 @@ const MBEDTLS_POOL = Ref{CPool{MbedTLS.SSLContext}}()
const OPENSSL_POOL = Ref{CPool{OpenSSL.SSLStream}}()
const OTHER_POOL = Lockable(IdDict{Type, CPool}())

"""
HTTP.Connections.metrics([nothing]) -> IdDict{Type,Metrics}

Return a dictionary of connection metrics, keyed by the connection type, for the default global pool.
"""
function metrics(pool::Nothing=nothing)
return IdDict{Type,Metrics}(
Sockets.TCPSocket => Metrics(TCP_POOL[]),
MbedTLS.SSLContext => Metrics(MBEDTLS_POOL[]),
OpenSSL.SSLStream => Metrics(OPENSSL_POOL[]),
(Base.@lock OTHER_POOL.lock (k => Metrics(v) for (k, v) in OTHER_POOL[]))...,
)
end

"""
HTTP.Connections.metrics(pool::Pool) -> IdDict{Type,Metrics}

Return a dictionary of connection metrics, keyed by the connection type, for the given `pool`.
"""
function metrics(pool::Pool)
return IdDict{Type,Metrics}(
Sockets.TCPSocket => Metrics(pool.tcp),
MbedTLS.SSLContext => Metrics(pool.mbedtls),
OpenSSL.SSLStream => Metrics(pool.openssl),
(Base.@lock pool.lock (k => Metrics(v) for (k, v) in pool.other))...,
)
end

Base.@kwdef struct Metrics
limit::Int
in_use::Int
in_pool::Int
end

"""
Metrics(cpool::$CPool)

Metrics for the given connection pool:
- `limit`: the maximum number of connections allowed to be in-use at the same time.
- `in_use`: the number of connections currently in use.
- `in_pool`: the number of connections available for re-use.
"""
function Metrics(cpool::CPool)
return Metrics(
limit=ConcurrentUtilities.Pools.max(cpool),
in_use=ConcurrentUtilities.Pools.permits(cpool),
in_pool=ConcurrentUtilities.Pools.depth(cpool),
)
end

function Base.show(io::IO, m::Metrics)
print(io, "Metrics(")
print(io, "limit=", m.limit)
print(io, ", in_use=", m.in_use)
print(io, ", in_pool=", m.in_pool)
print(io, ")")
end

getpool(::Nothing, ::Type{Sockets.TCPSocket}) = TCP_POOL[]
getpool(::Nothing, ::Type{MbedTLS.SSLContext}) = MBEDTLS_POOL[]
getpool(::Nothing, ::Type{OpenSSL.SSLStream}) = OPENSSL_POOL[]
Expand Down Expand Up @@ -451,22 +509,21 @@ function newconnection(::Type{T},
keepalive::Bool=true,
kw...) where {T <: IO}
connection_limit_warning(connection_limit)
return acquire(
getpool(pool, T),
(host, port, require_ssl_verification, keepalive, true);
forcenew=forcenew,
isvalid=c->connection_isvalid(c, Int(idle_timeout))) do
Connection(host, port,
idle_timeout, require_ssl_verification, keepalive,
connect_timeout > 0 ?
try_with_timeout(_ ->
getconnection(T, host, port;
require_ssl_verification=require_ssl_verification, keepalive=keepalive, kw...),
connect_timeout) :
getconnection(T, host, port;
require_ssl_verification=require_ssl_verification, keepalive=keepalive, kw...)
)
function connect(timeout)
if timeout > 0
try_with_timeout(timeout) do _
getconnection(T, host, port; require_ssl_verification=require_ssl_verification, keepalive=keepalive, kw...)
end
else
getconnection(T, host, port; require_ssl_verification=require_ssl_verification, keepalive=keepalive, kw...)
end
end
newconn() = Connection(host, port, idle_timeout, require_ssl_verification, keepalive, connect(connect_timeout))
key = (host, port, require_ssl_verification, keepalive, true)
return acquire(
newconn, getpool(pool, T), key;
forcenew=forcenew, isvalid=c->connection_isvalid(c, Int(idle_timeout)),
)
end

function releaseconnection(c::Connection{T}, reuse; pool::Union{Nothing, Pool}=nothing, kw...) where {T}
Expand Down
59 changes: 59 additions & 0 deletions test/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,65 @@ end
end
end

@testset "Connections.metrics" begin
# Test that `metrics` function returns the expected values as we use / release connections.
# Initialise a pool, and check that it is empty and has the expected limit
pool = HTTP.Pool(3)
for (T, v) in HTTP.Connections.metrics(pool)
@test v.limit == 3
@test v.in_pool == 0
@test v.in_use == 0
end

TCP = Sockets.TCPSocket
# After a request, check the connection is put in the pool for reuse
HTTP.get("https://$httpbin/get"; pool=pool, socket_type_tls=TCP)
metrics = HTTP.Connections.metrics(pool)
@test metrics[TCP].in_pool == 1
@test metrics[TCP].in_use == 0

# A second request should use this same connection and put it back again
HTTP.get("https://$httpbin/get"; pool=pool, socket_type_tls=TCP)
metrics = HTTP.Connections.metrics(pool)
@test metrics[TCP].in_pool == 1
@test metrics[TCP].in_use == 0

# Force a new connection -- the one in the pool should remain there.
c1 = HTTP.Connections.newconnection(TCP, httpbin, ""; forcenew=true, connect_timeout=3, pool=pool)
metrics = HTTP.Connections.metrics(pool)
@test metrics[TCP].in_pool == 1
@test metrics[TCP].in_use == 1

# Get another "new connection", since we didn't force new, this should use the one from the pool.
c2 = HTTP.Connections.newconnection(TCP, httpbin, ""; forcenew=false, connect_timeout=3, pool=pool)
metrics = HTTP.Connections.metrics(pool)
@test metrics[TCP].in_pool == 0
@test metrics[TCP].in_use == 2

# Release the first connection back to the pool
HTTP.Connections.releaseconnection(c1, true; pool=pool)
metrics = HTTP.Connections.metrics(pool)
@test metrics[TCP].in_pool == 1
@test metrics[TCP].in_use == 1

# Release the second connection but do not put back in the pool
HTTP.Connections.releaseconnection(c1, false; pool=pool)
metrics = HTTP.Connections.metrics(pool)
@test metrics[TCP].in_pool == 1
@test metrics[TCP].in_use == 0

# Test another connection type
SSL = MbedTLS.SSLContext
@test metrics[SSL].limit == 3
@test metrics[SSL].in_pool == 0
@test metrics[SSL].in_use == 0
HTTP.get("https://$httpbin/get"; pool=pool, socket_type_tls=SSL)
metrics = HTTP.Connections.metrics(pool)
@test metrics[SSL].limit == 3
@test metrics[SSL].in_pool == 1
@test metrics[SSL].in_use == 0
end

@testset "Retry all resolved IP addresses" begin
# See issue https://github.com/JuliaWeb/HTTP.jl/issues/672
# Bit tricky to test, but can at least be tested if localhost
Expand Down