postprocess/finalize hook, custom error pages

This commit is contained in:
Yessiest 2023-08-23 04:14:28 +04:00
parent f1f13faddf
commit ff36326ec8
2 changed files with 124 additions and 38 deletions

144
hyde.rb
View File

@ -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

View File

@ -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"