postprocess/finalize hook, custom error pages
This commit is contained in:
		
							parent
							
								
									f1f13faddf
								
							
						
					
					
						commit
						ff36326ec8
					
				
							
								
								
									
										148
									
								
								hyde.rb
								
								
								
								
							
							
						
						
									
										148
									
								
								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