Skip to content

Commit 9e114cb

Browse files
authored
Upgrade to HTTP.jl 2.x (#302)
* Upgrade to HTTP.jl 2.x * Increases minimum Julia version to 1.11 * Deprecates :stream, :access_log, and :queuesize * Creates new name queryparams(...) shadowing and dispatching to URIs rather than re-exporting from HTTP.jl * Fix issue with staticfiles only serving once, due to HTTP.jl consuming its BytesBody * Move from lowercasing header names to using canonical header names
1 parent 42087bf commit 9e114cb

30 files changed

Lines changed: 242 additions & 218 deletions

.github/workflows/CompatHelper.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
- name: Install Julia
1414
uses: julia-actions/setup-julia@v2
1515
with:
16-
version: '1.10'
16+
version: '1.11'
1717
arch: ${{ runner.arch }}
1818
- name: "Add the General registry via Git"
1919
run: |

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
fail-fast: false
2525
matrix:
2626
version:
27-
- '1.10' # Replace this with the minimum Julia version that your package supports. E.g. if your package requires Julia 1.5 or higher, change this to '1.5'.
27+
- '1.11' # Replace this with the minimum Julia version that your package supports. E.g. if your package requires Julia 1.5 or higher, change this to '1.5'.
2828
- '1' # Leave this line unchanged. '1' will automatically expand to the latest stable 1.x release of Julia.
2929
# - 'nightly'
3030
os:

Project.toml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
name = "Oxygen"
22
uuid = "df9a0d86-3283-4920-82dc-4555fc0d1d8b"
3+
version = "1.10.2"
34
authors = ["Nathan Ortega <nate.ortega95@gmail.com>"]
45
repo = "https://github.com/OxygenFramework/Oxygen.jl.git"
5-
version = "1.10.2"
66

77
[deps]
88
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
@@ -16,6 +16,7 @@ RelocatableFolders = "05181044-ff0b-4ac5-8273-598c1e38db00"
1616
Sockets = "6462fe0b-24de-5631-8697-dd941f90decc"
1717
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
1818
StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
19+
URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
1920

2021
[weakdeps]
2122
Bonito = "824d6782-a2ef-11e9-3a09-e5662e0c26f8"
@@ -36,11 +37,11 @@ TimeZonesExt = "TimeZones"
3637
WGLMakieExt = ["WGLMakie", "Bonito"]
3738

3839
[compat]
39-
Bonito = "^4"
40+
Bonito = "^5"
4041
CairoMakie = "^0.13, 0.15"
4142
DataStructures = "^0.18.15, 0.19"
4243
Dates = "^1"
43-
HTTP = "^1.8"
44+
HTTP = "^2.4"
4445
JSON = "^1.3"
4546
LRUCache = "^1.6"
4647
MIMEs = "^1"
@@ -55,8 +56,9 @@ Statistics = "^1"
5556
StructTypes = "^1"
5657
Suppressor = "^0.2.6"
5758
TimeZones = "^1.20.0"
59+
URIs = "1.6.1"
5860
WGLMakie = "^0.11, 0.13"
59-
julia = "^1.10"
61+
julia = "^1.11"
6062

6163
[extras]
6264
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

README.md

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ end
104104
end
105105

106106
# Websocket Handler
107-
@websocket "/ws" function(ws::HTTP.WebSocket)
107+
@websocket "/ws" function(ws::HTTP.WebSockets.WebSocket)
108108
...
109109
end
110110
```
@@ -153,7 +153,7 @@ Stream handlers are used to stream data. They are defined using the `@stream` ma
153153
- You need to explicitly include the type definition so Oxygen can identify this as a `Stream` handler
154154

155155
### Websocket Handlers
156-
Websocket handlers are used to handle websocket connections. They are defined using the `@websocket` macro or the `websocket()` function and accept a `HTTP.WebSocket` object as the first argument. These handlers support both function and do-block syntax.
156+
Websocket handlers are used to handle websocket connections. They are defined using the `@websocket` macro or the `websocket()` function and accept a `HTTP.WebSockets.WebSocket` object as the first argument. These handlers support both function and do-block syntax.
157157

158158
- `@websocket` and `websocket()` don't require a type definition on the first argument, they assume it's a websocket.
159159
- `Websocket` handlers can also be assigned with the `@get` macro or `get()` function, because the websocket protocol requires a `GET` request to initiate the handshake.
@@ -1053,33 +1053,12 @@ dynamicfiles("content", "dynamic")
10531053
# start the web server
10541054
serve()
10551055
```
1056-
## Performance Tips
1057-
1058-
Disabling the internal logger can provide some massive performance gains, which can be helpful in some scenarios.
1059-
Anecdotally, i've seen a 2-3x speedup in `serve()` and a 4-5x speedup in `serveparallel()` performance.
1060-
1061-
```julia
1062-
# This is how you disable internal logging in both modes
1063-
serve(access_log=nothing)
1064-
serveparallel(access_log=nothing)
1065-
```
1066-
10671056
## Logging
10681057

1069-
Oxygen provides a default logging format but allows you to customize the format using the `access_log` parameter. This functionality is available in both the `serve()` and `serveparallel()` functions.
1070-
1071-
You can read more about the logging options [here](https://juliaweb.github.io/HTTP.jl/stable/reference/#HTTP.@logfmt_str)
1072-
1073-
```julia
1074-
# Uses the default logging format
1075-
serve()
1076-
1077-
# Customize the logging format
1078-
serve(access_log=logfmt"[$time_iso8601] \"$request\" $status")
1079-
1080-
# Disable internal request logging
1081-
serve(access_log=nothing)
1082-
```
1058+
HTTP.jl 2.x no longer does per-request access logging in the server, so the `access_log` keyword
1059+
(and the `logfmt"..."` format macro) from earlier Oxygen versions is gone. The `access_log` kwarg
1060+
is still accepted by `serve()` and `serveparallel()` for backwards compatibility, but it is ignored.
1061+
If you need request logging, add it yourself with a small [middleware](#middleware) function.
10831062

10841063
## Middleware
10851064

docs/src/index.md

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ end
104104
end
105105

106106
# Websocket Handler
107-
@websocket "/ws" function(ws::HTTP.WebSocket)
107+
@websocket "/ws" function(ws::HTTP.WebSockets.WebSocket)
108108
...
109109
end
110110
```
@@ -153,7 +153,7 @@ Stream handlers are used to stream data. They are defined using the `@stream` ma
153153
- You need to explicitly include the type definition so Oxygen can identify this as a `Stream` handler
154154

155155
### Websocket Handlers
156-
Websocket handlers are used to handle websocket connections. They are defined using the `@websocket` macro or the `websocket()` function and accept a `HTTP.WebSocket` object as the first argument. These handlers support both function and do-block syntax.
156+
Websocket handlers are used to handle websocket connections. They are defined using the `@websocket` macro or the `websocket()` function and accept a `HTTP.WebSockets.WebSocket` object as the first argument. These handlers support both function and do-block syntax.
157157

158158
- `@websocket` and `websocket()` don't require a type definition on the first argument, they assume it's a websocket.
159159
- `Websocket` handlers can also be assigned with the `@get` macro or `get()` function, because the websocket protocol requires a `GET` request to initiate the handshake.
@@ -1053,33 +1053,12 @@ dynamicfiles("content", "dynamic")
10531053
# start the web server
10541054
serve()
10551055
```
1056-
## Performance Tips
1057-
1058-
Disabling the internal logger can provide some massive performance gains, which can be helpful in some scenarios.
1059-
Anecdotally, i've seen a 2-3x speedup in `serve()` and a 4-5x speedup in `serveparallel()` performance.
1060-
1061-
```julia
1062-
# This is how you disable internal logging in both modes
1063-
serve(access_log=nothing)
1064-
serveparallel(access_log=nothing)
1065-
```
1066-
10671056
## Logging
10681057

1069-
Oxygen provides a default logging format but allows you to customize the format using the `access_log` parameter. This functionality is available in both the `serve()` and `serveparallel()` functions.
1070-
1071-
You can read more about the logging options [here](https://juliaweb.github.io/HTTP.jl/stable/reference/#HTTP.@logfmt_str)
1072-
1073-
```julia
1074-
# Uses the default logging format
1075-
serve()
1076-
1077-
# Customize the logging format
1078-
serve(access_log=logfmt"[$time_iso8601] \"$request\" $status")
1079-
1080-
# Disable internal request logging
1081-
serve(access_log=nothing)
1082-
```
1058+
HTTP.jl 2.x no longer does per-request access logging in the server, so the `access_log` keyword
1059+
(and the `logfmt"..."` format macro) from earlier Oxygen versions is gone. The `access_log` kwarg
1060+
is still accepted by `serve()` and `serveparallel()` for backwards compatibility, but it is ignored.
1061+
If you need request logging, add it yourself with a small [middleware](#middleware) function.
10831062

10841063
## Middleware
10851064

@@ -1110,7 +1089,7 @@ function CorsMiddleware(handler)
11101089
return function(req::HTTP.Request)
11111090
println("CORS middleware")
11121091
# determine if this is a pre-flight request from the browser
1113-
if HTTP.method(req)=="OPTIONS"
1092+
if req.method=="OPTIONS"
11141093
return HTTP.Response(200, CORS_HEADERS)
11151094
else
11161095
return handler(req) # passes the request to the AuthMiddleware

ext/ProtoBufExt.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ Decode a protobuf message from the body of an HTTP request.
2121
- The decoded protobuf message of the specified type.
2222
"""
2323
function protobuf(request::HTTP.Request, type::Type{T}) :: T where {T}
24-
io = IOBuffer(request.body)
24+
body = request.body isa HTTP.EmptyBody ? UInt8[] : copy(request.body)
25+
io = IOBuffer(body)
2526
return decode(ProtoDecoder(io), type)
2627
end
2728

2829
function protobuf(response::HTTP.Response, type::Type{T}) :: T where {T}
29-
io = IOBuffer(response.body)
30+
body = response.body isa HTTP.EmptyBody ? UInt8[] : copy(response.body)
31+
io = IOBuffer(body)
3032
return decode(ProtoDecoder(io), type)
3133
end
3234

@@ -92,4 +94,4 @@ function extract(param::Param{ProtoBuffer{T}}, request::LazyRequest) :: ProtoBuf
9294
end
9395

9496

95-
end
97+
end

src/Oxygen.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ end
1111
include("core.jl"); using .Core
1212
include("instances.jl"); using .Instances
1313

14-
import HTTP: Request, Response, Stream, WebSocket, queryparams
14+
import HTTP: Request, Response, Stream
15+
import HTTP.WebSockets: WebSocket
1516
using .Core: ServerContext, History, Server, Nullable, HOFRouter
16-
using .Core: GET, POST, PUT, DELETE, PATCH
17+
using .Core: GET, POST, PUT, DELETE, PATCH, queryparams
1718

1819
const CONTEXT :: Ref{ServerContext} = Ref(ServerContext())
1920

src/autodoc.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,7 @@ end
663663
Helper function used to determine if a type is a custom struct and whether or not
664664
we should do a recursive dive and conversion to openapi schema.
665665
666-
Excludes built-in types from Base, Core, Dates, and HTTP.Messages modules.
666+
Excludes built-in types from Base, Core, Dates, and HTTP modules.
667667
Handles Union types by checking if any constituent type is a custom struct.
668668
669669
# Examples
@@ -683,8 +683,8 @@ function is_custom_struct(T::Type) :: Bool
683683
return any(is_custom_struct, Base.uniontypes(T))
684684
end
685685

686-
# Exclude types from Base, Core, Dates, and HTTP.Messages
687-
if T.name.module (Base, Core, Dates, HTTP.Messages)
686+
# Exclude types from Base, Core, Dates, and HTTP
687+
if T.name.module (Base, Core, Dates, HTTP)
688688
return isstructtype(T) || isabstracttype(T)
689689
end
690690

src/constants.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const METHOD_ALIASES :: Dict{String,String} = Dict(
4343

4444
const TYPE_ALIASES :: Dict{String, Type} = Dict(
4545
WEBSOCKET => HTTP.WebSockets.WebSocket,
46-
STREAM => HTTP.Streams.Stream
46+
STREAM => HTTP.Stream
4747
)
4848

4949
const SWAGGER_VERSION :: String = "swagger@5.7.2"

src/core.jl

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ include("repeattasks.jl"); @reexport using .RepeatTasks
2626
include("metrics.jl"); @reexport using .Metrics
2727
include("reflection.jl"); @reexport using .Reflection
2828
include("extractors.jl"); @reexport using .Extractors
29+
using .Extractors: Form # Prefer over HTTP.Form
2930
include("autodoc.jl"); @reexport using .AutoDoc
3031

3132
export start, serve, serveparallel, terminate,
@@ -168,7 +169,7 @@ function serve(ctx::ServerContext;
168169
# The cleanup of resources are put at the topmost level in `methods.jl`
169170
try
170171
return startserver(ctx; host, port, show_banner, docs, metrics, parallel, async, kwargs, start=(kwargs) ->
171-
HTTP.serve!(handle_stream, host, port; kwargs...))
172+
HTTP.listen!(handle_stream, host, port; kwargs...))
172173
finally
173174
if ctx.service.eager_revise[] !== nothing && async == false
174175
close(ctx.service.eager_revise[])
@@ -270,10 +271,25 @@ This is our root stream handler used in both serve() and serveparallel().
270271
This function determines how we handle all incoming requests
271272
"""
272273

274+
"""
275+
Convert the `HTTP.peeraddr` result (a `Reseau.TCP.SocketAddr`-like struct with a
276+
`.ip` tuple of octets, or `nothing` if unavailable) into a `Sockets.IPAddr`.
277+
"""
278+
function peeraddr_to_ip(addr) :: IPAddr
279+
addr === nothing && return ip"0.0.0.0"
280+
octets = addr.ip
281+
if length(octets) == 4
282+
return IPv4(octets...)
283+
else
284+
groups = ntuple(i -> UInt16(octets[2i - 1]) << 8 | UInt16(octets[2i]), 8)
285+
return IPv6(groups...)
286+
end
287+
end
288+
273289
function stream_handler(middleware::Function)
274290
return function (stream::HTTP.Stream)
275291
# extract the caller's ip address
276-
ip, _ = Sockets.getpeername(stream)
292+
ip = peeraddr_to_ip(HTTP.peeraddr(stream))
277293
# build up a streamhandler to handle our incoming requests
278294
handle_stream = HTTP.streamhandler(middleware |> decorate_request(ip, stream))
279295
# handle the incoming request
@@ -379,19 +395,13 @@ end
379395

380396

381397
"""
382-
Used to overwrite defaults to any incoming keyword arguments
398+
Removes deprecated keys from incoming keyword arguments, currently: :stream, :access_log, and :queuesize.
383399
"""
384400
function preprocesskwargs(kwargs)
385401
kwargs_dict = Dict{Symbol,Any}(kwargs)
386-
387-
# always set to streaming mode (regardless of what was passed)
388-
kwargs_dict[:stream] = true
389-
390-
# user passed no loggin preferences - use defualt logging format
391-
if isempty(kwargs_dict) || !haskey(kwargs_dict, :access_log)
392-
kwargs_dict[:access_log] = logfmt"$time_iso8601 - $remote_addr:$remote_port - \"$request\" $status"
393-
end
394-
402+
delete!(kwargs_dict, :stream)
403+
delete!(kwargs_dict, :access_log)
404+
delete!(kwargs_dict, :queuesize)
395405
return kwargs_dict
396406
end
397407

@@ -436,8 +446,7 @@ function DocsMiddleware(docsrouter::Router, docspath::String)
436446
else
437447
response = handle(req)
438448
end
439-
format_response!(req, response)
440-
return req.response
449+
return format_response(req, response)
441450
end
442451
end
443452
end
@@ -451,8 +460,7 @@ function DefaultSerializer(catch_errors::Bool; show_errors::Bool)
451460
return function (req::HTTP.Request)
452461
return handlerequest(catch_errors; show_errors) do
453462
response = handle(req)
454-
format_response!(req, response)
455-
return req.response
463+
return format_response(req, response)
456464
end
457465
end
458466
end

0 commit comments

Comments
 (0)