postprocess/finalize hook, custom error pages
This commit is contained in:
parent
f1f13faddf
commit
ff36326ec8
144
hyde.rb
144
hyde.rb
|
@ -1,47 +1,84 @@
|
||||||
require 'mime-types'
|
require 'mime-types'
|
||||||
require 'webrick'
|
require 'webrick'
|
||||||
require 'uri'
|
require 'uri'
|
||||||
|
require 'pp'
|
||||||
|
|
||||||
module Hyde
|
module Hyde
|
||||||
# Branding and version
|
# Branding and version
|
||||||
VERSION = "0.5 (alpha)"
|
VERSION = "0.5 (alpha)"
|
||||||
attr_reader :VERSION
|
attr_reader :VERSION
|
||||||
VLINE = "<ADDRESS>\n Hyde/#{Hyde::VERSION} on WEBrick/#{WEBrick::VERSION} (Ruby/#{RUBY_VERSION}/#{RUBY_RELEASE_DATE})\n </ADDRESS>"
|
VLINE = "Hyde/#{Hyde::VERSION} on WEBrick/#{WEBrick::VERSION} (Ruby/#{RUBY_VERSION}/#{RUBY_RELEASE_DATE})\n"
|
||||||
attr_reader :VLINE
|
attr_reader :VLINE
|
||||||
|
|
||||||
|
def error_template(errortext,backtrace)
|
||||||
|
<<HTMLEOF
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>#{WEBrick::HTMLUtils.escape(errortext)}</title>
|
||||||
|
<style> .header {background-color: #CC7878; padding: 0.5rem; border-bottom-width: 0.2rem; border-bottom-style: solid; border-bottom-color: #202222; overflow: auto;} .title { font-weight: bolder; font-size: 36px; margin: 0 0; text-shadow: 1px 1px 1px #202222, 2px 2px 2px #404444; float: left } body { margin: 0;
|
||||||
|
} .text { font-size 1rem; } .small { color: #7D7D7D; font-size: 12px;} .code { font-family: monospace; font-size: 0.7rem; } </style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<p class="title">HYDE</p>
|
||||||
|
<p style="float: right"><a href="https://adastra7.net/git/yessiest/hyde">Source code</a></p>
|
||||||
|
</div>
|
||||||
|
<div style="padding: 0.5rem">
|
||||||
|
<p class="text">#{WEBrick::HTMLUtils.escape(errortext)}</p>
|
||||||
|
<pre><code class="text code">
|
||||||
|
#{WEBrick::HTMLUtils.escape(backtrace) or "\n\n\n"}
|
||||||
|
</code></pre>
|
||||||
|
<hr/>
|
||||||
|
<p class="small">#{WEBrick::HTMLUtils.escape(VLINE)}</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
HTMLEOF
|
||||||
|
end
|
||||||
|
module_function :error_template
|
||||||
|
|
||||||
|
WEBrick::HTTPResponse.class_exec do
|
||||||
|
attr_accessor :recent_backtrace
|
||||||
|
public
|
||||||
|
def set_backtrace(backtrace)
|
||||||
|
@recent_backtrace = backtrace
|
||||||
|
end
|
||||||
|
def create_error_page
|
||||||
|
@body = Hyde.error_template(@reason_phrase,@recent_backtrace)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class Server < WEBrick::HTTPServer
|
class Server < WEBrick::HTTPServer
|
||||||
def initialize(config={},&setup)
|
def initialize(config={},&setup)
|
||||||
super(config)
|
super(config)
|
||||||
@hyde_pathspec = Hyde::Pathspec.new "/", &setup
|
@hyde_pathspec = Hyde::Pathspec.new "/", &setup
|
||||||
self.mount_proc '/' do |req,res|
|
self.mount_proc '/' do |req,res|
|
||||||
context = Hyde::Context.new(req.path, req, res)
|
context = Hyde::Context.new(req.path, req, res)
|
||||||
|
begin
|
||||||
while context and (not context.exit_loop) do
|
while context and (not context.exit_loop) do
|
||||||
context.exit_loop = true
|
context.exit_loop = true
|
||||||
context = catch :controlled_exit do
|
context = catch :controlled_exit do
|
||||||
@hyde_pathspec._match(context)
|
@hyde_pathspec._match(context)
|
||||||
context
|
context
|
||||||
end
|
end
|
||||||
|
while postprocessor = context.queue_postprocess.shift do
|
||||||
|
postprocessor.call(context)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
while finalizer = context.queue_finalize.shift do
|
||||||
|
finalizer.call(context)
|
||||||
|
end
|
||||||
|
rescue Exception => e
|
||||||
|
puts e.message
|
||||||
|
puts e.backtrace
|
||||||
|
res.set_backtrace "#{e.message} (#{e.class})\n#{e.backtrace.join "\n"}"
|
||||||
|
res.status = 500
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module ErrorPages
|
|
||||||
# 404 text
|
|
||||||
def error404(request, filepath)
|
|
||||||
request.response.status = 404
|
|
||||||
if request.handles.include? 404
|
|
||||||
request.response.body = request.handles[404].call(
|
|
||||||
filepath
|
|
||||||
)
|
|
||||||
else
|
|
||||||
request.response.body = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n<HTML>\n <HEAD><TITLE>File not found</TITLE></HEAD>\n <BODY>\n <H1>File not found</H1>\n #{filepath}\n <HR>\n #{Hyde::VLINE}\n </BODY>\n</HTML>"
|
|
||||||
end
|
|
||||||
request.response["Content-Type"] = 'text/html'
|
|
||||||
end
|
|
||||||
module_function :error404
|
|
||||||
end
|
|
||||||
|
|
||||||
# Interchangeable glob/regex/string pattern matching
|
# Interchangeable glob/regex/string pattern matching
|
||||||
module PatternMatching
|
module PatternMatching
|
||||||
def _prep_path(path,safe_regexp: true)
|
def _prep_path(path,safe_regexp: true)
|
||||||
|
@ -110,6 +147,19 @@ module Hyde
|
||||||
new_context.exit_loop = false
|
new_context.exit_loop = false
|
||||||
throw :controlled_exit, new_context
|
throw :controlled_exit, new_context
|
||||||
end
|
end
|
||||||
|
def die(code, message=nil, backtrace="")
|
||||||
|
@current_context.response.status = code
|
||||||
|
@current_context.response.set_backtrace(backtrace)
|
||||||
|
if not message then
|
||||||
|
message = WEBrick::HTTPStatus::StatusMessage[code]
|
||||||
|
end
|
||||||
|
if @current_context.codehandlers[code] then
|
||||||
|
@current_context.codehandlers[code].call(@current_context, message, backtrace)
|
||||||
|
else
|
||||||
|
@current_context.response.body = Hyde.error_template(message, backtrace)
|
||||||
|
end
|
||||||
|
throw :controlled_exit, @current_context
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Request wrapper class
|
# Request wrapper class
|
||||||
|
@ -119,24 +169,42 @@ module Hyde
|
||||||
@filepath = ""
|
@filepath = ""
|
||||||
@request = request
|
@request = request
|
||||||
@response = response
|
@response = response
|
||||||
@handles = {}
|
|
||||||
@indexlist = []
|
@indexlist = []
|
||||||
@vars = {}
|
@vars = {}
|
||||||
|
@codehandlers = {}
|
||||||
|
@queue_postprocess = []
|
||||||
|
@queue_finalize = []
|
||||||
@exit_loop = false
|
@exit_loop = false
|
||||||
end
|
end
|
||||||
def self.rewrite(pctx,newpath)
|
def self.rewrite(pctx,newpath)
|
||||||
newctx = self.new(newpath,pctx.request,pctx.response)
|
newctx = Context.new(newpath,pctx.request,pctx.response)
|
||||||
newctx.vars = pctx.vars
|
newctx.vars = pctx.vars
|
||||||
|
newctx.queue_finalize = pctx.queue_finalize.clone
|
||||||
|
newctx.queue_postprocess = pctx.queue_postprocess.clone
|
||||||
return newctx
|
return newctx
|
||||||
end
|
end
|
||||||
|
def enqueue_finalizer(dup: false, &block)
|
||||||
|
if block_given? then
|
||||||
|
if dup or not @queue_finalize.include? block then
|
||||||
|
@queue_finalize.append(block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def enqueue_postprocessor(&block)
|
||||||
|
if block_given? then
|
||||||
|
@queue_postprocess.append(block)
|
||||||
|
end
|
||||||
|
end
|
||||||
attr_reader :request
|
attr_reader :request
|
||||||
attr_reader :response
|
attr_reader :response
|
||||||
attr_accessor :filepath
|
attr_accessor :queue_finalize
|
||||||
|
attr_accessor :queue_postprocess
|
||||||
attr_accessor :path
|
attr_accessor :path
|
||||||
attr_accessor :handles
|
|
||||||
attr_accessor :indexlist
|
attr_accessor :indexlist
|
||||||
|
attr_accessor :filepath
|
||||||
attr_accessor :exit_loop
|
attr_accessor :exit_loop
|
||||||
attr_accessor :vars
|
attr_accessor :vars
|
||||||
|
attr_accessor :codehandlers
|
||||||
end
|
end
|
||||||
|
|
||||||
# Context object with safe path encapsulation
|
# Context object with safe path encapsulation
|
||||||
|
@ -146,15 +214,18 @@ module Hyde
|
||||||
@filepath = request.filepath
|
@filepath = request.filepath
|
||||||
@request = request.request
|
@request = request.request
|
||||||
@response = request.response
|
@response = request.response
|
||||||
@handles = request.handles
|
|
||||||
@indexlist = request.indexlist
|
@indexlist = request.indexlist
|
||||||
@exit_loop = request.exit_loop
|
@exit_loop = request.exit_loop
|
||||||
@vars = request.vars
|
@vars = request.vars
|
||||||
|
@codehandlers = request.codehandlers
|
||||||
|
@queue_postprocess = request.queue_postprocess
|
||||||
|
@queue_finalize = request.queue_finalize
|
||||||
end
|
end
|
||||||
undef :path=
|
undef :path=
|
||||||
undef :filepath=
|
undef :filepath=
|
||||||
undef :handles=
|
|
||||||
undef :indexlist=
|
undef :indexlist=
|
||||||
|
undef :queue_postprocess=
|
||||||
|
undef :queue_finalize=
|
||||||
end
|
end
|
||||||
|
|
||||||
# Handler classes
|
# Handler classes
|
||||||
|
@ -185,6 +256,7 @@ module Hyde
|
||||||
def _match(request)
|
def _match(request)
|
||||||
return super if @block
|
return super if @block
|
||||||
if _match? request.path, request then
|
if _match? request.path, request then
|
||||||
|
@current_context = request
|
||||||
match_path = _normalize_input(request.path).match(@path)[0]
|
match_path = _normalize_input(request.path).match(@path)[0]
|
||||||
filepath = request.filepath+match_path
|
filepath = request.filepath+match_path
|
||||||
begin
|
begin
|
||||||
|
@ -194,7 +266,7 @@ module Hyde
|
||||||
request.response.body = data
|
request.response.body = data
|
||||||
request.response["Content-Type"] = mimetype
|
request.response["Content-Type"] = mimetype
|
||||||
rescue Errno::ENOENT
|
rescue Errno::ENOENT
|
||||||
Hyde::ErrorPages::error404 request, request.request.path
|
die(404)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -282,10 +354,10 @@ module Hyde
|
||||||
class Pathspec
|
class Pathspec
|
||||||
include Hyde::PatternMatching
|
include Hyde::PatternMatching
|
||||||
include Hyde::Handlers
|
include Hyde::Handlers
|
||||||
|
include Hyde::PublicContextControlMethods
|
||||||
def initialize (path, root_path: nil, safe_regexp: true, &block)
|
def initialize (path, root_path: nil, safe_regexp: true, &block)
|
||||||
_prep_path path, safe_regexp: safe_regexp
|
_prep_path path, safe_regexp: safe_regexp
|
||||||
@chain = []
|
@chain = []
|
||||||
@handles = {}
|
|
||||||
@root_override = root_path
|
@root_override = root_path
|
||||||
@remap = false
|
@remap = false
|
||||||
self.instance_exec &block
|
self.instance_exec &block
|
||||||
|
@ -308,15 +380,23 @@ module Hyde
|
||||||
end
|
end
|
||||||
def index(list)
|
def index(list)
|
||||||
@indexlist = list if list.kind_of? Array
|
@indexlist = list if list.kind_of? Array
|
||||||
end
|
@indexlist = [list] if list.kind_of? String
|
||||||
def handle(code, &block)
|
|
||||||
@handles[code] = block
|
|
||||||
end
|
end
|
||||||
def preprocess(&block)
|
def preprocess(&block)
|
||||||
@preprocessor = block
|
@preprocessor = block
|
||||||
end
|
end
|
||||||
|
def postprocess(&block)
|
||||||
|
@postprocessor = block
|
||||||
|
end
|
||||||
|
def finalize(dup: false, &block)
|
||||||
|
@finalizer = block
|
||||||
|
@finalizer_dup = dup
|
||||||
|
end
|
||||||
def _match(request)
|
def _match(request)
|
||||||
|
@current_context = request
|
||||||
self.instance_exec request, &@preprocessor if @preprocessor
|
self.instance_exec request, &@preprocessor if @preprocessor
|
||||||
|
request.enqueue_postprocessor &@postprocessor if @preprocessor
|
||||||
|
request.enqueue_finalizer dup: @finalizer_dup, &@finalizer if @finalizer
|
||||||
if _match? request.path, request then
|
if _match? request.path, request then
|
||||||
match_path = _normalize_input(request.path).match(@path)
|
match_path = _normalize_input(request.path).match(@path)
|
||||||
next_path = match_path[0]
|
next_path = match_path[0]
|
||||||
|
@ -329,8 +409,7 @@ module Hyde
|
||||||
else
|
else
|
||||||
request.filepath = request.filepath+next_path+"/"
|
request.filepath = request.filepath+next_path+"/"
|
||||||
end
|
end
|
||||||
# parameter overrides overlaying
|
# redefine indexing parameters if they are defined for a pathspec
|
||||||
@handles.each_pair { |k,v| request.handles[k] = v }
|
|
||||||
request.indexlist = @indexlist if @indexlist
|
request.indexlist = @indexlist if @indexlist
|
||||||
# do directory indexing
|
# do directory indexing
|
||||||
if cut_path.match /^\/?$/ then
|
if cut_path.match /^\/?$/ then
|
||||||
|
@ -346,10 +425,11 @@ module Hyde
|
||||||
next_pathspec = @chain.find { |x| x._match? cut_path, request }
|
next_pathspec = @chain.find { |x| x._match? cut_path, request }
|
||||||
next_pathspec._match request if next_pathspec
|
next_pathspec._match request if next_pathspec
|
||||||
unless next_pathspec then
|
unless next_pathspec then
|
||||||
# throw 404 if nowhere to go
|
# die and throw up if nowhere to go
|
||||||
Hyde::ErrorPages::error404 request, request.request.path
|
die(404)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@current_context = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,6 +8,12 @@ server = Hyde::Server.new Port: 8000 do
|
||||||
preprocess do |ctx|
|
preprocess do |ctx|
|
||||||
puts "#{ctx} entered fully virtual directory!"
|
puts "#{ctx} entered fully virtual directory!"
|
||||||
end
|
end
|
||||||
|
postprocess do |ctx|
|
||||||
|
puts "#{ctx} reached endpoint!"
|
||||||
|
end
|
||||||
|
finalize do |ctx|
|
||||||
|
puts "#{ctx} finished processing!"
|
||||||
|
end
|
||||||
get "portal" do |ctx|
|
get "portal" do |ctx|
|
||||||
ctx.vars[:ass] = true
|
ctx.vars[:ass] = true
|
||||||
rewrite "/about/hyde"
|
rewrite "/about/hyde"
|
||||||
|
|
Loading…
Reference in New Issue