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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ server = HTTP.serve!(router, "127.0.0.1", 8080)
Requests are decoded and validated before handlers run; return values are
validated and encoded from the documented responses. Generated stubs do not
authenticate requests — apply a `middleware` for that.
To pick up handler edits made with Revise.jl in a running server, wrap the
handlers with `Base.invokelatest` through `middleware`; see
[Developing handlers with Revise](https://juliacomputing.github.io/OpenAPI.jl/stable/servers/#Developing-handlers-with-Revise).

## Create a document from Julia declarations

Expand Down
26 changes: 26 additions & 0 deletions docs/src/servers.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,32 @@ handler contract — implementation module second, typed positional parameters,
typed-value-or-`HTTP.Response` returns — matches the shape OpenAPI.jl 0.2.x
users generated with `-g julia-server`.

## Developing handlers with Revise

[Revise.jl](https://github.com/timholy/Revise.jl) updates handler methods, but
a running server keeps calling the definitions that existed when it started.
HTTP.jl 2.x does not call handlers through `Base.invokelatest`, so a server
task does not see methods defined after it began. Restart the server, or wrap
each handler with `Base.invokelatest` through `middleware` while developing:

```julia
using Revise, HTTP
includet("Handlers.jl") # defines module Handlers
include("ExampleServer.jl")

router = HTTP.Router()
ExampleServer.register!(router, Handlers;
middleware = handler -> (request -> Base.invokelatest(handler, request)))
server = HTTP.serve!(router, "127.0.0.1", 8080)
```

Once Revise applies an edit to `Handlers.jl` (before your next REPL command,
or when you call `Revise.revise()`), the next request uses the new definition
without a restart.
Generated servers do not do this by default because `Base.invokelatest`
cannot be compiled with Julia's `--trim` option, and generated servers stay
trim-compatible. Leave this middleware out of trimmed builds.

## Choosing a response status

A plain return value uses the first documented success response. When an
Expand Down
35 changes: 35 additions & 0 deletions test/servergen.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1318,3 +1318,38 @@ end
close(server)
end
end

@testset "invokelatest middleware picks up redefined handlers" begin
document = """
openapi: 3.1.0
info: {title: Revisable, version: 1.0.0}
paths:
/greet:
get:
operationId: greet
responses:
"200":
description: greeting
content:
text/plain:
schema: {type: string}
"""
host = Module(:RevisableServerHost)
Base.include_string(host, OpenAPI.server(document; name = "RevisableServer"), "RevisableServer.jl")
S = Base.invokelatest(getfield, host, :RevisableServer)
impl = Module(:RevisableImpl)
Core.eval(impl, :(greet(request) = "v1"))
router = HTTP.Router()
Base.invokelatest(Base.invokelatest(getfield, S, :register!), router, impl;
middleware = handler -> (request -> Base.invokelatest(handler, request)))
server = HTTP.serve!(router, "127.0.0.1", 0; verbose = false)
try
url = "http://127.0.0.1:$(HTTP.port(server))/greet"
@test String(HTTP.get(url).body) == "v1"
# Revise redefines methods in place; the running server must use the new one.
Core.eval(impl, :(greet(request) = "v2"))
@test String(HTTP.get(url).body) == "v2"
finally
close(server)
end
end
Loading