Skip to content

Commit

Permalink
Revamped HTML rendering, Session fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Adrian Salceanu committed Nov 27, 2021
1 parent f9ef7e0 commit 8379ee4
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 49 deletions.
6 changes: 5 additions & 1 deletion assets/js/channels.js
Expand Up @@ -116,10 +116,14 @@ function parse_payload(json_data) {
console.log(json_data);
};

function subscription_ready() {
console.log("Subscription ready");
};

function subscribe() {
if (document.readyState === "complete" || document.readyState === "interactive") {
Genie.WebChannels.sendMessageTo(window.Genie.Settings.webchannels_default_route, window.Genie.Settings.webchannels_subscribe_channel);
console.log("Subscription ready");
window.subscription_ready();
} else {
console.log("Queuing subscription");
setTimeout(subscribe, Genie.Settings.webchannels_timeout);
Expand Down
2 changes: 1 addition & 1 deletion src/Renderer.jl
Expand Up @@ -361,7 +361,7 @@ end
Persists compiled Julia view data to file and returns the path
"""
function build_module(content::String, path::String, mod_name::String; output_path::Bool = true) :: String
function build_module(content::S, path::S, mod_name::S; output_path::Bool = true)::String where {S<:AbstractString}
module_path = joinpath(Genie.config.path_build, BUILD_NAME, mod_name)

isdir(dirname(module_path)) || mkpath(dirname(module_path))
Expand Down
7 changes: 6 additions & 1 deletion src/Router.jl
Expand Up @@ -862,7 +862,12 @@ Populates `params` with default environment vars.
function setup_base_params(req::HTTP.Request = HTTP.Request(), res::Union{HTTP.Response,Nothing} = req.response,
params::Dict{Symbol,Any} = Dict{Symbol,Any}()) :: Dict{Symbol,Any}
params[Genie.PARAMS_REQUEST_KEY] = req
params[Genie.PARAMS_RESPONSE_KEY] = res
params[Genie.PARAMS_RESPONSE_KEY] = if res === nothing
req.response = HTTP.Response()
req.response
else
res
end
params[Genie.PARAMS_POST_KEY] = Dict{Symbol,Any}()
params[Genie.PARAMS_GET_KEY] = Dict{Symbol,Any}()

Expand Down
23 changes: 19 additions & 4 deletions src/Sessions.jl
Expand Up @@ -93,12 +93,14 @@ end
Sets up the session functionality, if configured.
"""
function init()
function init() :: Nothing
@eval Genie.config.session_storage === nothing && (Genie.config.session_storage = :File)
@eval Genie.config.session_storage == :File && include(joinpath(@__DIR__, "session_adapters", "FileSession.jl"))

push!(Genie.Router.pre_match_hooks, Genie.Sessions.start)
push!(Genie.Router.pre_response_hooks, Genie.Sessions.persist)

nothing
end


Expand Down Expand Up @@ -131,7 +133,7 @@ Initiates a new default session object, generating a new session id.
- `res::HTTP.Response`: the response object
- `options::Dict{String,String}`: extra options for setting the session cookie, such as `Path` and `HttpOnly`
"""
function start(req::HTTP.Request, res::HTTP.Response, params::Dict{Symbol,Any} = Dict{Symbol,Any}(); options::Dict{String,Any} = Genie.config.session_options) :: Tuple{HTTP.Request,HTTP.Response,Dict{Symbol,Any}}
function start(req::HTTP.Request, res::HTTP.Response, params::Dict{Symbol,Any} = Dict{Symbol,Any}(); options::Dict{String,Any} = Genie.config.session_options) :: Tuple{HTTP.Request,HTTP.Response,Dict{Symbol,Any},Session}
session, res = start(id(req, res), req, res; options = options)

params[Genie.PARAMS_SESSION_KEY] = session
Expand All @@ -149,8 +151,9 @@ function start(req::HTTP.Request, res::HTTP.Response, params::Dict{Symbol,Any} =
end
end

req, res, params
req, res, params, session
end
const start! = start


"""
Expand Down Expand Up @@ -195,6 +198,15 @@ end
function get(key::Symbol, default::T) :: T where T
get(session(), key, default)
end
function get!(key::Symbol, default::T) :: T where T
get!(session(), key, default)
end
function get!(s::Session, key::Symbol, default::T) :: T where T
val = get(s, key, default)
set!(key, val)

val
end


"""
Expand Down Expand Up @@ -242,7 +254,10 @@ Returns the `Session` object associated with the current HTTP request.
"""
function session(params::Dict{Symbol,Any} = Genie.Router.params()) :: Sessions.Session
( (! haskey(params, Genie.PARAMS_SESSION_KEY) || params[Genie.PARAMS_SESSION_KEY] === nothing) ) &&
(params[Genie.PARAMS_SESSION_KEY] = Sessions.start(params[Genie.PARAMS_REQUEST_KEY], params[Genie.PARAMS_RESPONSE_KEY])[1])
(params = Sessions.start!(
Base.get(params, Genie.PARAMS_REQUEST_KEY, HTTP.Request()),
Base.get(params, Genie.PARAMS_RESPONSE_KEY, HTTP.Response())
)[3])

params[Genie.PARAMS_SESSION_KEY]
end
Expand Down
29 changes: 15 additions & 14 deletions src/genie_module.jl
Expand Up @@ -214,23 +214,24 @@ Here, a temporary one is generated for the current session if no other token is
`generate_if_missing` is true.
"""
function secret_token(generate_if_missing::Bool = true; context::Union{Module,Nothing} = nothing)
if context != nothing
@warn "secret_token not context-dependent any more; the context argument is deprecated"
end
if isempty(SECRET_TOKEN[])
isfile(joinpath(Genie.config.path_config, Genie.SECRETS_FILE_NAME)) &&
@eval include(joinpath(Genie.config.path_config, Genie.SECRETS_FILE_NAME))

if isempty(SECRET_TOKEN[]) && generate_if_missing
@warn "
No secret token is defined through `Genie.secret_token!(\"token\")`. Such a token
is needed to hash and to encrypt/decrypt sensitive data in Genie, including cookie
and session data.
if isempty(SECRET_TOKEN[]) && generate_if_missing
@warn "
No secret token is defined through `Genie.secret_token!(\"token\")`. Such a token
is needed to hash and to encrypt/decrypt sensitive data in Genie, including cookie
and session data.
If your app relies on cookies or sessions make sure you generate a valid token,
otherwise the encrypted data will become unreadable between app restarts.
If your app relies on cookies or sessions make sure you generate a valid token,
otherwise the encrypted data will become unreadable between app restarts.
You can resolve this issue by generating a valid `config/secrets.jl` file with a
random token, calling `Genie.Generator.write_secrets_file()`.
"
secret_token!()
You can resolve this issue by generating a valid `config/secrets.jl` file with a
random token, calling `Genie.Generator.write_secrets_file()`.
"
secret_token!()
end
end

SECRET_TOKEN[]
Expand Down
83 changes: 62 additions & 21 deletions src/renderers/Html.jl
Expand Up @@ -50,9 +50,11 @@ struct ParsedHTMLString <: AbstractString
end

function ParsedHTMLString(v::Vector{T}) where {T}
join(v)
join(v) |> ParsedHTMLString
end

Base.string(v::Vector{ParsedHTMLString}) = join(v)

ParsedHTMLString(args...) = ParsedHTMLString([args...])

Base.string(s::ParsedHTMLString) = s.data
Expand All @@ -68,9 +70,36 @@ import Base: (*)

# end ParsedHTMLStrings

const HTMLString = String
# HTMLStrings
struct HTMLString <: AbstractString
data::String
end

function HTMLString(v::Vector{T}) where {T}
join(v) |> HTMLString
end

Base.string(v::Vector{HTMLString}) = join(v)
Base.string(v::Vector{AbstractString}) = join(v)

HTMLString(args...) = HTMLString([args...])

Base.string(s::HTMLString) = s.data
Base.String(s::HTMLString) = string(s)

export HTMLString, html, doc, doctype, ParsedHTMLString
Base.iterate(s::HTMLString) = iterate(s.data)
Base.iterate(s::HTMLString, x::Int) = iterate(s.data, x)

Base.convert(::Type{HTMLString}, v::Vector{T}) where {T} = HTMLString(v)

import Base: (*)
(*)(s::HTMLString, t::HTMLString) = string(s.data, t.data)

# end HTMLStrings

# const HTMLString = String

export HTMLString, html, doc, doctype, ParsedHTMLString, html!
export @yield, collection, view!, for_each
export partial, template

Expand Down Expand Up @@ -339,7 +368,7 @@ end
Parses a view file, returning a rendering function. If necessary, the function is JIT-compiled, persisted and loaded into memory.
"""
function parseview(data::String; partial = false, context::Module = @__MODULE__) :: Function
function parseview(data::S; partial = false, context::Module = @__MODULE__)::Function where {S<:AbstractString}
data_hash = hash(data)
path = "Genie_" * string(data_hash)

Expand All @@ -365,7 +394,7 @@ end
Renders the string as an HTML view.
"""
function render(data::String; context::Module = @__MODULE__, layout::Union{String,Nothing} = nothing, vars...) :: Function
function render(data::S; context::Module = @__MODULE__, layout::Union{String,Nothing} = nothing, vars...)::Function where {S<:AbstractString}
Genie.Renderer.registervars(; context = context, vars...)

if layout !== nothing
Expand Down Expand Up @@ -410,7 +439,8 @@ function parsehtml(input::String; partial::Bool = true) :: String
end


function Genie.Renderer.render(::Type{MIME"text/html"}, data::String; context::Module = @__MODULE__, layout::Union{String,Nothing} = nothing, vars...) :: Genie.Renderer.WebRenderable
function Genie.Renderer.render(::Type{MIME"text/html"}, data::S; context::Module = @__MODULE__,
layout::Union{String,Nothing} = nothing, vars...)::Genie.Renderer.WebRenderable where {S<:AbstractString}
try
render(data; context = context, layout = layout, vars...) |> Genie.Renderer.WebRenderable
catch ex
Expand Down Expand Up @@ -464,22 +494,32 @@ Content-Type: text/html; charset=utf-8
```
"""
function html(data::String; context::Module = @__MODULE__, status::Int = 200, headers::Genie.Renderer.HTTPHeaders = Genie.Renderer.HTTPHeaders(),
layout::Union{String,Nothing,Genie.Renderer.FilePath} = nothing, forceparse::Bool = false, noparse::Bool = false, vars...) :: Genie.Renderer.HTTP.Response
layout::Union{String,Nothing,Genie.Renderer.FilePath} = nothing, forceparse::Bool = false, noparse::Bool = false, vars...) :: Genie.Renderer.HTTP.Response
isa(layout, Genie.Renderer.FilePath) && (layout = read(layout, String))

if (occursin(raw"$", data) || occursin("<%", data) || layout !== nothing || forceparse) && ! noparse
Genie.Renderer.WebRenderable(Genie.Renderer.render(MIME"text/html", data; context = context, layout = layout, vars...), status, headers) |> Genie.Renderer.respond
html(HTMLString(data); context = context, status = status, headers = headers, layout = layout, vars...)
else
html(ParsedHTMLString(data); context, status, headers, layout, vars...)
end
end

function html(data::HTMLString; context::Module = @__MODULE__, status::Int = 200, headers::Genie.Renderer.HTTPHeaders = Genie.Renderer.HTTPHeaders(),
layout::Union{String,Nothing,Genie.Renderer.FilePath} = nothing, forceparse::Bool = false, noparse::Bool = false, vars...) :: Genie.Renderer.HTTP.Response
Genie.Renderer.WebRenderable(Genie.Renderer.render(MIME"text/html", data; context = context, layout = layout, vars...), status, headers) |> Genie.Renderer.respond
end

function html(data::ParsedHTMLString; context::Module = @__MODULE__, status::Int = 200, headers::Genie.Renderer.HTTPHeaders = Genie.Renderer.HTTPHeaders(),
layout::Union{String,Nothing,Genie.Renderer.FilePath} = nothing, vars...) :: Genie.Renderer.HTTP.Response
function html(data::ParsedHTMLString; status::Int = 200,
headers::Genie.Renderer.HTTPHeaders = Genie.Renderer.HTTPHeaders(), vars...) :: Genie.Renderer.HTTP.Response
Genie.Renderer.WebRenderable(body = data.data, status = status, headers = headers) |> Genie.Renderer.respond
end

function html!(data::Union{S,Vector{S}}; status::Int = 200,
headers::Genie.Renderer.HTTPHeaders = Genie.Renderer.HTTPHeaders(),
vars...)::Genie.Renderer.HTTP.Response where {S<:AbstractString}
html(ParsedHTMLString(join(data)); headers, vars...)
end


"""
html(md::Markdown.MD; context::Module = @__MODULE__, status::Int = 200, headers::Genie.Renderer.HTTPHeaders = Genie.Renderer.HTTPHeaders(), layout::Union{String,Nothing} = nothing, forceparse::Bool = false, vars...) :: Genie.Renderer.HTTP.Response
Expand Down Expand Up @@ -755,7 +795,7 @@ end
Converts string view data to Julia code
"""
function string_to_julia(content::String; partial = true, f_name::Union{Symbol,Nothing} = nothing, prepend::String = "\n") :: String
function string_to_julia(content::S; partial = true, f_name::Union{Symbol,Nothing} = nothing, prepend::String = "\n")::String where {S<:AbstractString}
to_julia(content, parse_string, partial = partial, f_name = f_name, prepend = prepend)
end

Expand All @@ -765,8 +805,9 @@ end
Converts an input file to Julia code
"""
function to_julia(input::String, f::Union{Function,Nothing};
partial = true, f_name::Union{Symbol,Nothing} = nothing, prepend::String = "\n", extension = TEMPLATE_EXT) :: String
function to_julia(input::S, f::Union{Function,Nothing};
partial = true, f_name::Union{Symbol,Nothing} = nothing,
prepend::String = "\n", extension = TEMPLATE_EXT)::ParsedHTMLString where {S<:AbstractString}
f_name = (f_name === nothing) ? Genie.Renderer.function_name(string(input, partial)) : f_name

string("function $(f_name)(; $(Genie.Renderer.injectkwvars())) \n",
Expand Down Expand Up @@ -854,7 +895,7 @@ end
Parses a HTML file into Julia code.
"""
function parse_template(file_path::String; partial::Bool = true, extension = TEMPLATE_EXT) :: String
function parse_template(file_path::S; partial::Bool = true, extension = TEMPLATE_EXT)::ParsedHTMLString where {S<:AbstractString}
parse(read_template_file(file_path; extension = extension)::String; partial = partial)
end

Expand All @@ -864,17 +905,17 @@ end
Parses a HTML string into Julia code.
"""
function parse_string(data::String; partial::Bool = true, extension = TEMPLATE_EXT) :: String
function parse_string(data::S; partial::Bool = true, extension = TEMPLATE_EXT)::ParsedHTMLString where {S<:AbstractString}
parse(parse_embed_tags(data), partial = partial)
end


function parse(input::String; partial::Bool = true) :: String
function parse(input::S; partial::Bool = true)::ParsedHTMLString where {S<:AbstractString}
parsehtml(input, partial = partial)
end


function parse_embed_tags(code::String) :: String
function parse_embed_tags(code::S)::String where {S<:AbstractString}
replace(
replace(code, "<%"=>"""<script type="julia/eval">"""),
"%>"=>"""</script>""")
Expand Down Expand Up @@ -928,25 +969,25 @@ Generates a Julia function representing a "normal" HTML element: that is an elem
"""
function register_normal_element(elem::Union{Symbol,String}; context = @__MODULE__) :: Nothing
Core.eval(context, """
function $elem(f::Function, args...; attrs...) :: HTMLString
function $elem(f::Function, args...; attrs...) :: ParsedHTMLString
\"\"\"\$(normal_element(f, "$(string(elem))", [args...], Pair{Symbol,Any}[attrs...]))\"\"\"
end
""" |> Meta.parse)

Core.eval(context, """
function $elem(children::Union{String,Vector{String}} = "", args...; attrs...) :: HTMLString
function $elem(children::Union{String,Vector{String}} = "", args...; attrs...) :: ParsedHTMLString
\"\"\"\$(normal_element(children, "$(string(elem))", [args...], Pair{Symbol,Any}[attrs...]))\"\"\"
end
""" |> Meta.parse)

Core.eval(context, """
function $elem(children::Any, args...; attrs...) :: HTMLString
function $elem(children::Any, args...; attrs...) :: ParsedHTMLString
\"\"\"\$(normal_element(string(children), "$(string(elem))", [args...], Pair{Symbol,Any}[attrs...]))\"\"\"
end
""" |> Meta.parse)

Core.eval(context, """
function $elem(children::Vector{Any}, args...; attrs...) :: HTMLString
function $elem(children::Vector{Any}, args...; attrs...) :: ParsedHTMLString
\"\"\"\$(normal_element([string(c) for c in children], "$(string(elem))", [args...], Pair{Symbol,Any}[attrs...]))\"\"\"
end
""" |> Meta.parse)
Expand Down
7 changes: 6 additions & 1 deletion src/renderers/Js.jl
Expand Up @@ -115,10 +115,15 @@ function js(data::String; context::Module = @__MODULE__, status::Int = 200,
if (occursin(raw"$", data) || occursin("<%", data) || forceparse) && ! noparse
Genie.Renderer.WebRenderable(render(MIME"application/javascript", data; context = context, vars...), :javascript, status, headers) |> Genie.Renderer.respond
else
Genie.Renderer.WebRenderable(data, :javascript, status, headers) |> Genie.Renderer.respond
js!(data; status, headers) |> Genie.Renderer.respond
end
end

function js!(data::S; status::Int = 200,
headers::Genie.Renderer.HTTPHeaders = Genie.Renderer.HTTPHeaders("Content-Type" => Genie.Renderer.CONTENT_TYPES[:javascript]))::Genie.Renderer.HTTP.Response where {S<:AbstractString}
Genie.Renderer.WebRenderable(data, :javascript, status, headers) |> Genie.Renderer.respond
end


function js(viewfile::Genie.Renderer.FilePath; context::Module = @__MODULE__, status::Int = 200,
headers::Genie.Renderer.HTTPHeaders = Genie.Renderer.HTTPHeaders("Content-Type" => Genie.Renderer.CONTENT_TYPES[:javascript]), vars...) :: Genie.Renderer.HTTP.Response
Expand Down

0 comments on commit 8379ee4

Please sign in to comment.