From 303838c81398f3b07b6667d3f72359190f7fadc2 Mon Sep 17 00:00:00 2001 From: Adrian Salceanu Date: Sun, 14 Jul 2019 15:18:38 +0200 Subject: [PATCH] rendering support for custom status codes and headers, WebChannels fixes --- src/Genie.jl | 4 ++-- src/Plugins.jl | 5 +++-- src/Renderer.jl | 47 ++++++++++++++++++++++++++++------------------ src/Router.jl | 6 +++--- src/WebChannels.jl | 40 ++++++++++++++++++++++++++------------- 5 files changed, 64 insertions(+), 38 deletions(-) diff --git a/src/Genie.jl b/src/Genie.jl index 7ca132355..0a6384ab5 100755 --- a/src/Genie.jl +++ b/src/Genie.jl @@ -67,11 +67,11 @@ end """ - newapp(path = "."; autostart = true, fullstack = true, dbsupport = true) :: Nothing + newapp(path = "."; autostart = true, fullstack = false, dbsupport = true) :: Nothing Scaffolds a new Genie app. """ -function newapp(path = "."; autostart = true, fullstack = true, dbsupport = true) :: Nothing +function newapp(path = "."; autostart = true, fullstack = false, dbsupport = true) :: Nothing REPL.newapp(path, autostart = autostart, fullstack = fullstack, dbsupport = dbsupport) end const new_app = newapp diff --git a/src/Plugins.jl b/src/Plugins.jl index b8f9edb9b..5251c4625 100644 --- a/src/Plugins.jl +++ b/src/Plugins.jl @@ -12,9 +12,10 @@ const APP_FOLDER = Genie.APP_PATH const path_prefix = joinpath(@__DIR__, "..", FILES_FOLDER, "new_app") |> normpath |> relpath const FOLDERS = [ joinpath(path_prefix, APP_FOLDER), joinpath(path_prefix, "db"), - joinpath(path_prefix, "lib"), + joinpath(path_prefix, Genie.LIB_PATH), joinpath(path_prefix, PLUGINS_FOLDER), - joinpath(path_prefix, TASKS_FOLDER) ] + joinpath(path_prefix, TASKS_FOLDER), + joinpath(path_prefix, Genie.DOC_ROOT_PATH) ] function recursive_copy(path::String, dest::String; only_hidden = true, force = false) diff --git a/src/Renderer.jl b/src/Renderer.jl index f0428aa2a..85b55862f 100755 --- a/src/Renderer.jl +++ b/src/Renderer.jl @@ -24,24 +24,28 @@ const DEFAULT_CONTENT_TYPE = :html """ """ -function html(resource::Union{Symbol,String}, action::Union{Symbol,String}; layout::Union{Symbol,String} = Genie.config.renderer_default_layout_file, context::Module = @__MODULE__, vars...) :: Dict{Symbol,HTMLString} +function html(resource::Union{Symbol,String}, action::Union{Symbol,String}; layout::Union{Symbol,String} = Genie.config.renderer_default_layout_file, + context::Module = @__MODULE__, vars...) :: Dict{Symbol,HTMLString} Dict(:html => (Flax.html_renderer(resource, action; layout = layout, mod = context, vars...) |> Base.invokelatest)) end function html(data::String; context::Module = @__MODULE__, vars...) :: Dict{Symbol,HTMLString} Dict(:html => (Flax.html_renderer(data; mod = context, vars...) |> Base.invokelatest)) end -function html!(resource::Union{Symbol,String}, action::Union{Symbol,String}; layout::Union{Symbol,String} = Genie.config.renderer_default_layout_file, context::Module = @__MODULE__, vars...) :: HTTP.Response - html(resource, action; layout = layout, context = context, vars...) |> respond + + +""" +""" +function html!( resource::Union{Symbol,String}, action::Union{Symbol,String}; layout::Union{Symbol,String} = Genie.config.renderer_default_layout_file, + context::Module = @__MODULE__, status::Int = 200, headers::Dict{String,String} = Dict{String,String}(), vars...) :: HTTP.Response + respond(html(resource, action; layout = layout, context = context, vars...), status, headers) end -function html!(data::String; context::Module = @__MODULE__, vars...) :: HTTP.Response - html(data; context = context, vars...) |> respond +function html!(data::String; context::Module = @__MODULE__, status::Int = 200, headers::Dict{String,String} = Dict{String,String}(), vars...) :: HTTP.Response + respond(html(data; context = context, vars...), status, headers) end ### JSON RENDERING ### """ - json(resource::Symbol, action::Symbol, check_nulls::Vector{Pair{Symbol,Nullable}} = Vector{Pair{Symbol,Nullable}}(); vars...) :: Dict{Symbol,String} - Invokes the JSON renderer of the underlying configured templating library. """ function json(resource::Union{Symbol,String}, action::Union{Symbol,String}; context::Module = @__MODULE__, vars...) :: Dict{Symbol,JSONString} @@ -50,11 +54,15 @@ end function json(data) :: Dict{Symbol,JSONString} Dict(:json => JSON.json(data)) end -function json!(resource::Union{Symbol,String}, action::Union{Symbol,String}; context::Module = @__MODULE__, vars...) :: HTTP.Response - json(resource, action; mod = context, vars...) |> respond + + +""" +""" +function json!(resource::Union{Symbol,String}, action::Union{Symbol,String}; context::Module = @__MODULE__, status::Int = 200, headers::Dict{String,String} = Dict{String,String}(), vars...) :: HTTP.Response + respond(json(resource, action; mod = context, vars...), status, headers) end -function json!(data) :: HTTP.Response - json(data) |> respond +function json!(data; status::Int = 200, headers::Dict{String,String} = Dict{String,String}()) :: HTTP.Response + respond(json(data), status, headers) end ### REDIRECT RESPONSES ### @@ -64,11 +72,11 @@ end Sets redirect headers and prepares the `Response`. """ -function redirect_to(location::String, code = 302, headers = Dict{String,String}()) :: HTTP.Response +function redirect_to(location::String, code::Int = 302, headers::Dict{String,String} = Dict{String,String}()) :: HTTP.Response headers["Location"] = location respond(Dict{Symbol,String}(:plain => "Redirecting you to $location"), code, headers) end -function redirect_to(named_route::Symbol, code = 302, headers = Dict{String,String}()) :: HTTP.Response +function redirect_to(named_route::Symbol, code::Int = 302, headers::Dict{String,String} = Dict{String,String}()) :: HTTP.Response redirect_to(Genie.Router.link_to(named_route), code, headers) end @@ -89,7 +97,7 @@ end Constructs a `Response` corresponding to the content-type of the request. """ -function respond(body::Dict{Symbol,T}, code::Int = 200, headers = Dict{String,String}())::HTTP.Response where {T} +function respond(body::Dict{Symbol,T}, code::Int = 200, headers::Dict{String,String} = Dict{String,String}())::HTTP.Response where {T} sbody::String = if haskey(body, :json) headers["Content-Type"] = CONTENT_TYPES[:json] body[:json] @@ -123,19 +131,22 @@ end function respond(err::T, content_type::Union{Symbol,String} = Genie.Router.response_type(), code::Int = 500)::HTTP.Response where {T<:Exception} HTTP.Response(code, (isa(content_type, Symbol) ? ["Content-Type" => CONTENT_TYPES[content_type]] : ["Content-Type" => content_type]), body = string(err)) end - function respond(body::String, content_type::Union{Symbol,String} = Genie.Router.response_type(), code::Int = 200) :: HTTP.Response HTTP.Response(code, (isa(content_type, Symbol) ? ["Content-Type" => CONTENT_TYPES[content_type]] : ["Content-Type" => content_type]), body = body) end +function respond(body, code::Int = 200, headers = Dict{String,String}()) + HTTP.Response(code, [h for h in headers], body = string(body)) +end +function respond(f::Function, code::Int = 200, headers = Dict{String,String}()) + respond(f(), code, headers) +end ### ASSETS ### """ - http_error(status_code; id = "resource_not_found", code = "404-0001", title = "Not found", detail = "The requested resource was not found") - Constructs an error `Response`. """ -function http_error(status_code; id = "", code = "", title = "", msg = "") :: HTTP.Response +function http_error(status_code; id = "", code = "", title = "", msg = "") :: HTTP.Response # TODO: check this! respond(Dict(Genie.Router.response_type() => msg), status_code, Dict{String,String}()) end const error! = http_error diff --git a/src/Router.jl b/src/Router.jl index 75334ee9d..4a2b31e29 100755 --- a/src/Router.jl +++ b/src/Router.jl @@ -748,7 +748,7 @@ end """ """ function extract_request_params(req::HTTP.Request, params::Params) :: Nothing - req.method != POST && return nothing + req.method in [POST, PUT, PATCH] || return nothing params.collection[Genie.PARAMS_RAW_PAYLOAD] = String(req.body) @@ -771,7 +771,7 @@ end """ """ function content_type(req::HTTP.Request) :: String - get(HTTPUtils.req_headers_to_dict(req), "content-type", "") + get(Genie.HTTPUtils.req_headers_to_dict(req), "content-type", "") end function content_type() :: String content_type(_params_(Genie.PARAMS_REQUEST_KEY)) @@ -781,7 +781,7 @@ end """ """ function content_length(req::HTTP.Request) :: Int - parse(Int, get(HTTPUtils.req_headers_to_dict(req), "content-length", "0")) + parse(Int, get(Genie.HTTPUtils.req_headers_to_dict(req), "content-length", "0")) end function content_length() :: Int content_length(_params_(Genie.PARAMS_REQUEST_KEY)) diff --git a/src/WebChannels.jl b/src/WebChannels.jl index 933aa7243..afe2e0593 100755 --- a/src/WebChannels.jl +++ b/src/WebChannels.jl @@ -8,7 +8,7 @@ using Genie.Loggers const ClientId = UInt # web socket hash -const ChannelName = Union{String,Symbol} +const ChannelName = String mutable struct ChannelClient client::HTTP.WebSockets.WebSocket @@ -100,7 +100,7 @@ end Unsubscribes a web socket client `ws` from `channel`. """ function unsubscribe(ws::HTTP.WebSockets.WebSocket, channel::ChannelName) :: ChannelClientsCollection - haskey(CLIENTS, id(ws)) && delete!(CLIENTS[id(ws)].channels, channel) + haskey(CLIENTS, id(ws)) && deleteat!(CLIENTS[id(ws)].channels, CLIENTS[id(ws)].channels .== channel) pop_subscription(id(ws), channel) CLIENTS @@ -126,9 +126,13 @@ function unsubscribe_client(ws::HTTP.WebSockets.WebSocket) :: ChannelClientsColl end function unsubscribe_client(client_id::ClientId) :: ChannelClientsCollection unsubscribe_client(CLIENTS[client_id].client) + + CLIENTS end function unsubscribe_client(channel_client::ChannelClient) :: ChannelClientsCollection unsubscribe_client(channel_client.client) + + CLIENTS end @@ -136,11 +140,15 @@ function unsubscribe_disconnected_clients() :: ChannelClientsCollection for channel_client in disconnected_clients() unsubscribe_client(channel_client) end + + CLIENTS end function unsubscribe_disconnected_clients(channel::ChannelName) :: ChannelClientsCollection for channel_client in disconnected_clients(channel) unsubscribe(channel_client, channel) end + + CLIENTS end @@ -197,23 +205,29 @@ Pushes `msg` (and `payload`) to all the clients subscribed to the channels in `c function broadcast(channels::Union{ChannelName,Vector{ChannelName}}, msg::String) :: Bool isa(channels, Array) || (channels = ChannelName[channels]) - @sync @distributed for channel in channels - for client in SUBSCRIPTIONS[channel] - message(client, msg) + try + for channel in channels + for client in SUBSCRIPTIONS[channel] + message(client, msg) + end end + catch end true end function broadcast(channels::Union{ChannelName,Vector{ChannelName}}, msg::String, payload::Dict) :: Bool - isa(channels, Array) || (channels = ChannelName[channels]) + isa(channels, Array) || (channels = [channels]) - @sync @distributed for channel in channels - in(channel, keys(SUBSCRIPTIONS)) || continue + try + for channel in channels + in(channel, keys(SUBSCRIPTIONS)) || continue - for client in SUBSCRIPTIONS[channel] - message(client, ChannelMessage(channel, client, msg, payload) |> JSON.json) + for client in SUBSCRIPTIONS[channel] + message(client, ChannelMessage(channel, client, msg, payload) |> JSON.json) + end end + catch end true @@ -233,10 +247,10 @@ end """ Pushes `msg` (and `payload`) to `channel`. """ -function message(channel::ChannelName, msg::String, payload::Union{Dict,Nothing} = nothing) :: Nothing +function message(channel::ChannelName, msg::String, payload::Union{Dict,Nothing} = nothing) :: Bool payload == nothing ? - broadcast(ChannelName[channel], msg) : - broadcast(ChannelName[channel], msg, payload) + broadcast(channel, msg) : + broadcast(channel, msg, payload) end