Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add servedebug(...) with Revise support #134

Closed
wants to merge 1 commit into from
Closed
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
190 changes: 0 additions & 190 deletions Manifest.toml

This file was deleted.

1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
MIMEs = "6c6e2e6c-3030-632d-7369-2d6c69616d65"
RelocatableFolders = "05181044-ff0b-4ac5-8273-598c1e38db00"
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
Sockets = "6462fe0b-24de-5631-8697-dd941f90decc"

[compat]
Expand Down
6 changes: 3 additions & 3 deletions src/Oxygen.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ include("core.jl"); using .Core
export @get, @post, @put, @patch, @delete, @route, @cron,
@staticfiles, @dynamicfiles, staticfiles, dynamicfiles,
get, post, put, patch, delete, route,
serve, serveparallel, terminate, internalrequest,
serve, serveparallel, servedebug, terminate, internalrequest,
redirect, queryparams, binary, text, json, html, file,
configdocs, mergeschema, setschema, getschema, router,
enabledocs, disabledocs, isdocsenabled, starttasks, stoptasks,
resetstate, startcronjobs, stopcronjobs
end
clear_routes, resetstate, startcronjobs, stopcronjobs
end
124 changes: 117 additions & 7 deletions src/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ using HTTP
using Sockets
using JSON3
using Base
using Revise
# Revise has problems tracking this package
push!(Revise.dont_watch_pkgs, :Oxygen)

include("util.jl"); using .Util
include("fileutil.jl"); using .FileUtil
Expand All @@ -13,12 +16,15 @@ include("autodoc.jl"); using .AutoDoc
export @get, @post, @put, @patch, @delete, @route, @cron,
@staticfiles, @dynamicfiles, staticfiles, dynamicfiles,
get, post, put, patch, delete, route,
start, serve, serveparallel, terminate, internalrequest, file,
configdocs, mergeschema, setschema, getschema, router,
start, serve, servedebug, serveparallel, terminate,
internalrequest, file, configdocs,
mergeschema, setschema, getschema, router,
enabledocs, disabledocs, isdocsenabled, registermountedfolder,
starttasks, stoptasks, resetstate, startcronjobs, stopcronjobs
starttasks, stoptasks, clear_routes, resetstate,
startcronjobs, stopcronjobs

global const ROUTER = Ref{HTTP.Handlers.Router}(HTTP.Router())
global server_lock = ReentrantLock()
global const server = Ref{Union{HTTP.Server, Nothing}}(nothing)
global const timers = Ref{Vector{Timer}}([])

Expand All @@ -32,8 +38,16 @@ oxygen_title = raw"""

"""

function serverwelcome(host::String, port::Int)
function serverwelcome(host::String, port::Int; debug=false, report_watch=nothing)
printstyled(oxygen_title, color = :blue, bold = true)
if debug
@info "🐛 Debugging mode enabled"
if report_watch !== nothing
@info "👀 Watching filesystem for changes: $report_watch"
else
@info "👀 Watching filesystem for changes"
end
end
@info "✅ Started server: http://$host:$port"
@info "📖 Documentation: http://$host:$port$docspath"
end
Expand Down Expand Up @@ -139,6 +153,58 @@ function serve(; middleware::Vector=[], handler=stream_handler, host="127.0.0.1"
server[] # this value is returned if startserver() is ran in async mode
end

function InvokeLatestMiddleware(handler)
return function(req::HTTP.Request)
return Base.@invokelatest handler(req)
end
end

"""
servedebug(; watches=(autodetect), watch_all=true, revise_pause=0.02, revise_callbacks::Vector=[], middleware::Vector=[], middleware::Vector=[], handler=stream_handler, host="127.0.0.1", port=8080, serialize=true, async=false, catch_errors=true, kwargs...)

Start the debug webserver with your own custom request handler
"""
function servedebug(; watches=nothing, watch_all=true, revise_pause=0.02, revise_callbacks::Vector=[], middleware::Vector=[], handler=stream_handler, host="127.0.0.1", port=8080, serialize=true, catch_errors=true, kwargs...)
watch_dir = nothing
if watches === nothing
caller_file = String(stacktrace()[3].file)
caller_dir = dirname(caller_file)
project_dir = caller_dir
found = false
while true
if isfile(joinpath(project_dir, "Project.toml"))
found = true
break
end
parent = dirname(project_dir)
if parent == project_dir
break
end
project_dir = parent
end
if found
watch_dir = project_dir
else
watch_dir = caller_dir
end
watches = [watch_dir]
end

insert!(middleware, 1, InvokeLatestMiddleware)

function start(kwargs)
# recompose our middleware each time we restart the
# middleware so we can pick up changes
configured_middleware = setupmiddleware(middleware=middleware, serialize=serialize, catch_errors=catch_errors)
final_handler = Base.@invokelatest handler(configured_middleware)
HTTP.serve!(final_handler, host, port; kwargs...)
end

startdebugserver(host, port, kwargs, start
; watches=watches, watch_all=watch_all, revise_pause=revise_pause,
revise_callbacks=revise_callbacks, report_watch=watch_dir)
end

"""
serveparallel(; middleware::Vector=[], handler=stream_handler, host="127.0.0.1", port=8080, queuesize=1024, serialize=true, async=false, catch_errors=true, kwargs...)

Expand Down Expand Up @@ -201,6 +267,50 @@ function startserver(host, port, kwargs, async, start)
end
end

"""
Internal helper function to launch a debug server
"""
function startdebugserver(host, port, kwargs, start; watches, watch_all, revise_pause, revise_callbacks, report_watch=nothing)
addr = Sockets.InetAddr(host, port)
socket_server = Sockets.listen(addr)
try
serverwelcome(host, port, debug=true, report_watch=report_watch)
setup()
preprocessed_kwargs = preprocesskwargs(kwargs)
preprocessed_kwargs[:server] = socket_server
server[] = start(preprocessed_kwargs)
Revise.entr(watches; postpone=true, all=watch_all, pause=revise_pause) do
Core.println(Core.stderr, "Revising")
for callback in revise_callbacks
callback()
end
close(socket_server)
socket_server = Sockets.listen(addr)
preprocessed_kwargs[:server] = socket_server
server[] = start(preprocessed_kwargs)
end
starttasks()
registercronjobs()
startcronjobs()
try
wait(server[])
catch
println() # this pushes the "[ Info: Server on 127.0.0.1:8080 closing" to the next line
end
finally
# close server on exit
terminate()
# only reset state on exit if we aren't running asynchronously & are running it interactively
if isinteractive()
resetstate()
end
end
end

function clear_routes()
ROUTER[] = HTTP.Router()
end

"""
Reset all the internal state variables
"""
Expand Down Expand Up @@ -536,7 +646,7 @@ function register(httpmethod::String, route::Union{String,Function}, func::Funct
# case 1.) The request handler is an anonymous function (don't parse out path params)
if numfields <= 1
handle = function (req)
func()
Base.@invokelatest func()
end
# case 2.) This route has path params, so we need to parse parameters and pass them to the request handler
elseif hasPathParams && numfields > 2
Expand All @@ -546,12 +656,12 @@ function register(httpmethod::String, route::Union{String,Function}, func::Funct
# convert params to their designated type (if applicable)
pathParams = [parseparam(func_map[name], params[name]) for name in func_param_names]
# pass all parameters to handler in the correct order
func(req, pathParams...)
Base.@invokelatest func(req, pathParams...)
end
# case 3.) This function should only get passed the request object
else
handle = function (req)
func(req)
Base.@invokelatest func(req)
end
end

Expand Down
Loading