Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .github/workflows/CompatHelper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- name: Install Julia
uses: julia-actions/setup-julia@v2
with:
version: '1.10'
version: '1.11'
arch: ${{ runner.arch }}
- name: "Add the General registry via Git"
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
fail-fast: false
matrix:
version:
- '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'.
- '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'.
- '1' # Leave this line unchanged. '1' will automatically expand to the latest stable 1.x release of Julia.
# - 'nightly'
os:
Expand Down
10 changes: 6 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name = "Oxygen"
uuid = "df9a0d86-3283-4920-82dc-4555fc0d1d8b"
version = "1.10.2"
authors = ["Nathan Ortega <nate.ortega95@gmail.com>"]
repo = "https://github.com/OxygenFramework/Oxygen.jl.git"
version = "1.10.2"

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

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

[compat]
Bonito = "^4"
Bonito = "^5"
CairoMakie = "^0.13, 0.15"
DataStructures = "^0.18.15, 0.19"
Dates = "^1"
HTTP = "^1.8"
HTTP = "^2.4"
JSON = "^1.3"
LRUCache = "^1.6"
MIMEs = "^1"
Expand All @@ -55,8 +56,9 @@ Statistics = "^1"
StructTypes = "^1"
Suppressor = "^0.2.6"
TimeZones = "^1.20.0"
URIs = "1.6.1"
WGLMakie = "^0.11, 0.13"
julia = "^1.10"
julia = "^1.11"

[extras]
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
Expand Down
33 changes: 6 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ end
end

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

### Websocket Handlers
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.
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.

- `@websocket` and `websocket()` don't require a type definition on the first argument, they assume it's a websocket.
- `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.
Expand Down Expand Up @@ -1053,33 +1053,12 @@ dynamicfiles("content", "dynamic")
# start the web server
serve()
```
## Performance Tips

Disabling the internal logger can provide some massive performance gains, which can be helpful in some scenarios.
Anecdotally, i've seen a 2-3x speedup in `serve()` and a 4-5x speedup in `serveparallel()` performance.

```julia
# This is how you disable internal logging in both modes
serve(access_log=nothing)
serveparallel(access_log=nothing)
```

## Logging

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.

You can read more about the logging options [here](https://juliaweb.github.io/HTTP.jl/stable/reference/#HTTP.@logfmt_str)

```julia
# Uses the default logging format
serve()

# Customize the logging format
serve(access_log=logfmt"[$time_iso8601] \"$request\" $status")

# Disable internal request logging
serve(access_log=nothing)
```
HTTP.jl 2.x no longer does per-request access logging in the server, so the `access_log` keyword
Comment thread
ndortega marked this conversation as resolved.
(and the `logfmt"..."` format macro) from earlier Oxygen versions is gone. The `access_log` kwarg
is still accepted by `serve()` and `serveparallel()` for backwards compatibility, but it is ignored.
If you need request logging, add it yourself with a small [middleware](#middleware) function.

## Middleware

Expand Down
35 changes: 7 additions & 28 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ end
end

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

### Websocket Handlers
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.
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.

- `@websocket` and `websocket()` don't require a type definition on the first argument, they assume it's a websocket.
- `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.
Expand Down Expand Up @@ -1053,33 +1053,12 @@ dynamicfiles("content", "dynamic")
# start the web server
serve()
```
## Performance Tips

Disabling the internal logger can provide some massive performance gains, which can be helpful in some scenarios.
Anecdotally, i've seen a 2-3x speedup in `serve()` and a 4-5x speedup in `serveparallel()` performance.

```julia
# This is how you disable internal logging in both modes
serve(access_log=nothing)
serveparallel(access_log=nothing)
```

## Logging

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.

You can read more about the logging options [here](https://juliaweb.github.io/HTTP.jl/stable/reference/#HTTP.@logfmt_str)

```julia
# Uses the default logging format
serve()

# Customize the logging format
serve(access_log=logfmt"[$time_iso8601] \"$request\" $status")

# Disable internal request logging
serve(access_log=nothing)
```
HTTP.jl 2.x no longer does per-request access logging in the server, so the `access_log` keyword
(and the `logfmt"..."` format macro) from earlier Oxygen versions is gone. The `access_log` kwarg
is still accepted by `serve()` and `serveparallel()` for backwards compatibility, but it is ignored.
If you need request logging, add it yourself with a small [middleware](#middleware) function.

## Middleware

Expand Down Expand Up @@ -1110,7 +1089,7 @@ function CorsMiddleware(handler)
return function(req::HTTP.Request)
println("CORS middleware")
# determine if this is a pre-flight request from the browser
if HTTP.method(req)=="OPTIONS"
if req.method=="OPTIONS"
return HTTP.Response(200, CORS_HEADERS)
else
return handler(req) # passes the request to the AuthMiddleware
Expand Down
8 changes: 5 additions & 3 deletions ext/ProtoBufExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ Decode a protobuf message from the body of an HTTP request.
- The decoded protobuf message of the specified type.
"""
function protobuf(request::HTTP.Request, type::Type{T}) :: T where {T}
io = IOBuffer(request.body)
body = request.body isa HTTP.EmptyBody ? UInt8[] : copy(request.body)
io = IOBuffer(body)
return decode(ProtoDecoder(io), type)
end

function protobuf(response::HTTP.Response, type::Type{T}) :: T where {T}
io = IOBuffer(response.body)
body = response.body isa HTTP.EmptyBody ? UInt8[] : copy(response.body)
io = IOBuffer(body)
return decode(ProtoDecoder(io), type)
end

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


end
end
5 changes: 3 additions & 2 deletions src/Oxygen.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ end
include("core.jl"); using .Core
include("instances.jl"); using .Instances

import HTTP: Request, Response, Stream, WebSocket, queryparams
import HTTP: Request, Response, Stream
import HTTP.WebSockets: WebSocket
using .Core: ServerContext, History, Server, Nullable, HOFRouter
using .Core: GET, POST, PUT, DELETE, PATCH
using .Core: GET, POST, PUT, DELETE, PATCH, queryparams

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

Expand Down
6 changes: 3 additions & 3 deletions src/autodoc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ end
Helper function used to determine if a type is a custom struct and whether or not
we should do a recursive dive and conversion to openapi schema.

Excludes built-in types from Base, Core, Dates, and HTTP.Messages modules.
Excludes built-in types from Base, Core, Dates, and HTTP modules.
Handles Union types by checking if any constituent type is a custom struct.

# Examples
Expand All @@ -683,8 +683,8 @@ function is_custom_struct(T::Type) :: Bool
return any(is_custom_struct, Base.uniontypes(T))
end

# Exclude types from Base, Core, Dates, and HTTP.Messages
if T.name.module ∉ (Base, Core, Dates, HTTP.Messages)
# Exclude types from Base, Core, Dates, and HTTP
if T.name.module ∉ (Base, Core, Dates, HTTP)
return isstructtype(T) || isabstracttype(T)
end

Expand Down
2 changes: 1 addition & 1 deletion src/constants.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const METHOD_ALIASES :: Dict{String,String} = Dict(

const TYPE_ALIASES :: Dict{String, Type} = Dict(
WEBSOCKET => HTTP.WebSockets.WebSocket,
STREAM => HTTP.Streams.Stream
STREAM => HTTP.Stream
)

const SWAGGER_VERSION :: String = "swagger@5.7.2"
Expand Down
40 changes: 24 additions & 16 deletions src/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ include("repeattasks.jl"); @reexport using .RepeatTasks
include("metrics.jl"); @reexport using .Metrics
include("reflection.jl"); @reexport using .Reflection
include("extractors.jl"); @reexport using .Extractors
using .Extractors: Form # Prefer over HTTP.Form
include("autodoc.jl"); @reexport using .AutoDoc

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

"""
Convert the `HTTP.peeraddr` result (a `Reseau.TCP.SocketAddr`-like struct with a
`.ip` tuple of octets, or `nothing` if unavailable) into a `Sockets.IPAddr`.
"""
function peeraddr_to_ip(addr) :: IPAddr
addr === nothing && return ip"0.0.0.0"
octets = addr.ip
if length(octets) == 4
return IPv4(octets...)
else
groups = ntuple(i -> UInt16(octets[2i - 1]) << 8 | UInt16(octets[2i]), 8)
return IPv6(groups...)
end
end

function stream_handler(middleware::Function)
return function (stream::HTTP.Stream)
# extract the caller's ip address
ip, _ = Sockets.getpeername(stream)
ip = peeraddr_to_ip(HTTP.peeraddr(stream))
# build up a streamhandler to handle our incoming requests
handle_stream = HTTP.streamhandler(middleware |> decorate_request(ip, stream))
# handle the incoming request
Expand Down Expand Up @@ -379,19 +395,13 @@ end


"""
Used to overwrite defaults to any incoming keyword arguments
Removes deprecated keys from incoming keyword arguments, currently: :stream, :access_log, and :queuesize.
"""
function preprocesskwargs(kwargs)
kwargs_dict = Dict{Symbol,Any}(kwargs)

# always set to streaming mode (regardless of what was passed)
kwargs_dict[:stream] = true

# user passed no loggin preferences - use defualt logging format
if isempty(kwargs_dict) || !haskey(kwargs_dict, :access_log)
kwargs_dict[:access_log] = logfmt"$time_iso8601 - $remote_addr:$remote_port - \"$request\" $status"
end

delete!(kwargs_dict, :stream)
delete!(kwargs_dict, :access_log)
delete!(kwargs_dict, :queuesize)
return kwargs_dict
end

Expand Down Expand Up @@ -436,8 +446,7 @@ function DocsMiddleware(docsrouter::Router, docspath::String)
else
response = handle(req)
end
format_response!(req, response)
return req.response
return format_response(req, response)
end
end
end
Expand All @@ -451,8 +460,7 @@ function DefaultSerializer(catch_errors::Bool; show_errors::Bool)
return function (req::HTTP.Request)
return handlerequest(catch_errors; show_errors) do
response = handle(req)
format_response!(req, response)
return req.response
return format_response(req, response)
end
end
end
Expand Down
Loading
Loading