Changeset 640
- Timestamp:
- 09/15/07 12:57:56 (1 year ago)
- Files:
-
- trunk/app_generators/merb/merb_generator.rb (modified) (1 diff)
- trunk/app_generators/merb/templates/app/controllers/exceptions.rb (added)
- trunk/app_generators/merb/templates/app/views/exceptions/internal_server_error.html.erb (modified) (1 diff)
- trunk/app_generators/merb/templates/app/views/exceptions/not_found.html.erb (modified) (1 diff)
- trunk/lib/merb/controller.rb (modified) (5 diffs)
- trunk/lib/merb/dispatcher.rb (modified) (2 diffs)
- trunk/lib/merb/exceptions.rb (modified) (4 diffs)
- trunk/lib/merb/mixins/controller.rb (modified) (1 diff)
- trunk/lib/merb/mixins/render.rb (modified) (4 diffs)
- trunk/lib/merb/mongrel_handler.rb (modified) (2 diffs)
- trunk/lib/merb/rack_adapter.rb (modified) (1 diff)
- trunk/lib/merb/request.rb (modified) (1 diff)
- trunk/specs/fixtures/controllers/dispatch_spec_controllers.rb (modified) (1 diff)
- trunk/specs/merb/merb_dispatch_spec.rb (modified) (2 diffs)
- trunk/specs/merb/merb_handler_spec.rb (modified) (1 diff)
- trunk/specs/merb/merb_render_spec.rb (modified) (1 diff)
- trunk/specs/merb/merb_responder_spec.rb (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/app_generators/merb/merb_generator.rb
r637 r640 25 25 # copy skeleton 26 26 m.file_copy_each %w( Rakefile ) 27 m.file_copy_each %w( application.rb ), "app/controllers"27 m.file_copy_each %w( application.rb exceptions.rb ), "app/controllers" 28 28 m.file_copy_each %w( global_helper.rb ), "app/helpers" 29 29 m.file_copy_each %w( application.erb ), "app/mailers/views/layout" 30 30 m.file_copy_each %w( application.html.erb ), "app/views/layout" 31 m.file_copy_each %w( internal_server_error.html.erbnot_found.html.erb ), "app/views/exceptions"31 m.file_copy_each %w( not_found.html.erb ), "app/views/exceptions" 32 32 m.file_copy_each %w( merb.jpg ), "public/images" 33 33 m.file_copy_each %w( master.css ), "public/stylesheets" trunk/app_generators/merb/templates/app/views/exceptions/internal_server_error.html.erb
r607 r640 1 <% if show_exception_details? %> 2 <html> 3 <head> 4 <title><%= @title %></title> 5 <style type="text/css"> 6 <!-- 7 table {border:0px} 8 tr {line-height:1.3} 9 td {font-size:.8em; padding:4px} 10 h1 {font-size:2em} 11 h1, td {font-family:'courier new', 'courier', monospace} 12 td.line {color:#000; text-align:center} 13 td.method {font-weight:bold; text-align:right} 14 td.file a {color:#000} 15 td.file a:hover {color:#F00} 16 tr.clickable:hover {background:#FFA} 17 .clickable {cursor: help } 18 #title {float: left;} 19 .source 20 { 21 border:1px #000 solid; 22 margin:2px; 23 padding:8px; 24 background:#F0F0F0; 25 } 26 27 --> 28 <% if @coderay %> 29 <%= CodeRay::Encoders[:html]::CSS.new.stylesheet %> 30 <% end %> 31 </style> 32 <script type="text/javascript"> 33 $ = function(el){ 34 if(el === null || el === undefined) 35 throw("Argument for $() must be a domRef/domId"); 36 if( el.constructor === String ) 37 el = document.getElementById(el); 38 if(el === null || !el.nodeType) 39 throw("Argument for $() not found in document tree."); 40 return el; 41 } 1 <html> 2 <head> 3 <meta http-equiv="Content-type" content="text/html; charset=utf-8"> 4 <title><%= @exception.name.humanize %></title> 5 <style type="text/css" media="screen"> 6 body { 7 font-family:arial; 8 font-size:11px; 9 } 10 h1 { 11 font-size:48px; 12 letter-spacing:-4px; 13 margin:0; 14 line-height:36px; 15 color:#333; 16 } 17 h1 sup { 18 font-size: 0.5em; 19 } 20 h1 sup.error_500, h1 sup.error_400 { 21 color:#990E05; 22 } 23 h1 sup.error_100, h1 sup.error_200 { 24 color:#00BF10; 25 } 26 h1 sup.error_300 { 27 /* pretty sure you cant 'see' status 300 28 errors but if you could I think they 29 would be blue */ 30 color:#1B2099; 31 } 32 h2 { 33 font-size:36px; 34 letter-spacing:-3px; 35 margin:0; 36 line-height:28px; 37 color:#444; 38 } 39 a, a:visited { 40 color:#00BF10; 41 } 42 .internalError { 43 width:800px; 44 margin:50px auto; 45 } 46 .header { 47 border-bottom:10px solid #333; 48 margin-bottom:1px; 49 background-image: url("data:image/gif;base64,R0lGODlhAwADAIAAAP///8zMzCH5BAAAAAAALAAAAAADAAMAAAIEBHIJBQA7"); 50 padding:20px; 51 } 52 table.trace { 53 width:100%; 54 font-family:courier, monospace; 55 letter-spacing:-1px; 56 border-collapse: collapse; 57 border-spacing:0; 58 } 59 table.trace tr td{ 60 padding:0; 61 height:26px; 62 font-size:13px; 63 vertical-align:middle; 64 } 65 table.trace tr.file{ 66 border-top:2px solid #fff; 67 background-color:#F3F3F3; 68 } 69 table.trace tr.source { 70 background-color:#F8F8F8; 71 display:none; 72 } 73 table.trace .open tr.source { 74 display:table-row; 75 } 76 table.trace tr.file td.expand { 77 width:23px; 78 background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAXCAIAAABvSEP3AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAdVJREFUeNqMVL+TwUAYxaRIOlEhlZHGDAUzzOQ61+AqXMV1lJSU7q/QRqm8KFUcJTNn5qJkaPyoKKVz7y4mF8na5Kt29tt9+/Z97/u81+vVQ4r9frdarS6Xi7ETDIZisRjxMGPfmk4niNPpZE+xLAugbPaZ53nzvtfMBe/3+/3dbuehBrAKhZdUKkVAWa9Xsiybv0CPZDJZLr/qa5/BwgwRjYqOKIvFYjQa/aNommZh0Ww2K5UqzwfoQOPxaLPZ3FAmk0+7lplMpt1u53J5OpBOR0eZEE9wHJfP5zud93g88QhluwWbjW+5VOmKBgKBer3eaDTDYeGBQF8+x7rqIYoiPgixWJazpA6HA+MSxRArkUgMh0M409g8Ho8+9wYxxCqVSq1W26EDHGM2m4HOHQrEc38f/Yn7cLmlIRhBENzcx8cVRZnPZ/YUep2BWkjTIfA+PKVpZAXR5QxsjiqCKvGEqqp443w+0dvy17swqD0HB3S73V5PpkNg1qBqt8kwGCjmPkinM0QJbIoEa7U6UG6ToVgs4V9G2g0ESoP5Aoi7KYX5oCgf8IKbkvn9/mr1LRQKESamzgJy0g0tSZIuB3nuGqRU9Vv9C4sKkUhEkp4soxvxI8AAhWrrtXa3X8EAAAAASUVORK5CYII=); 79 background-position:top left; 80 background-repeat:no-repeat; 81 } 82 table.trace .open tr.file td.expand { 83 width:19px; 84 background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAB1CAIAAAAqdO2mAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAXZJREFUeNrslK1ywkAUhcMOBomEOiSdqLxEBJX0NaijOsjyHGGmCGyQQYaiiiw4gktkcOmZbpsuuzQ/M5XnqJ2d3S/n3nM3rTzPLUP7/Tt0+pLcGQwG3W53OLyHzPMtjYL7q9UqSRLrD4E1Gj1orCvKYuFHUWTVkOM44/HjDcp8/lL4r6NerzeZPMm1KFw0QkDn83m5fP2lHA4fNQvRtNvtjsfDd0WzmSfb2e/fdTqdOvdh/HLJZLOn0+d2HJ+KRGzbdl23EpFlmed5cp2maRzHQq1lvQ5KMi6EUZBGfup6E1pTfd+vrGW7jbQ2C9hTt9BpqNyIWaAwAy6xg2eBz5iRC/NomiZhGN5sqmnkauo0BUGgVQoBjQ80oCACgNQdZHfTYBkF2mxCtWWAqunWpahxIDUt3QYUxIFQpJHyIWpXjinabKbbwItMHT+NyjchrP8QKaSQQgoppJBCCimkkEIKKaSQQgoppJBCCimkkEIKKaSo+hRgAEFD17X08O2NAAAAAElFTkSuQmCC); 85 background-position:top left; 86 background-repeat:no-repeat; 87 } 88 table.trace tr.source td.collapse { 89 width:19px; 90 background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAB1CAIAAAAqdO2mAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAVxJREFUeNrs0zFygkAUBmBlUkgJHdABlQwVkVJKKUxBYWbkALTxMJwhltyDFkss03IF8pudIcwaDaDl/6pd2P327b7d+eHwMXs4lNkzggoVKlSoUKFChQoVKlSoUKFChQoVKlSoUKFChQqVEYqm6ft9+qiSJEkYho7jTlcw2fd9NOI4nq4gEdFwXXe1Cqco63VkWVbXRTqLhTpOwQRpF7quR1E0TgGhqvLKUFCyoQqG/rks3O6kZKW/eRFpevOCoGTXVTcMQ5EyxyDEkML1c5RzuZOICIyXqn7JBVez6282MWrx731HOv2qB8Hri2lamNk0DfpVVdV1Peodappmmua8bdvzuc7zfNprzrLMth1FnGh/X8MjCAIQv/cFz/+65PcDh7rbvYv2ZUfdj+PxsyzLgVl0hKwgTqeqKApx2LeOc7t98zyv/1FWOgvx9RPii23bmL9cetJ8Ed8CDAC6aFW8bCzFhwAAAABJRU5ErkJggg==); 91 background-position:bottom left; 92 background-repeat:no-repeat; 93 background-color:#6F706F; 94 } 95 table.trace tr td.path { 96 padding-left:10px; 97 } 98 table.trace tr td.code { 99 padding-left:35px; 100 white-space: pre; 101 line-height:9px; 102 padding-bottom:10px; 103 } 104 table.trace tr td.code em { 105 font-weight:bold; 106 color:#00BF10; 107 } 108 table.trace tr td.code .more { 109 color:#666; 110 } 111 table.trace tr td.line { 112 width:30px; 113 text-align:right; 114 padding-right:4px; 115 } 116 .footer { 117 margin-top:5px; 118 font-size:11px; 119 color:#444; 120 text-align:right; 121 } 122 </style> 123 </head> 124 <body> 125 <div class="internalError"> 126 127 <div class="header"> 128 <h1><%= @exception.name.humanize %> <sup class="error_<%= @exception.class::STATUS %>"><%= @exception.class::STATUS %></sup></h1> 129 <% if show_details = ::Merb::Server.config[:exception_details] -%> 130 <h2><%= @exception.message %></h2> 131 <% else -%> 132 <h2>Sorry about that...</h2> 133 <% end -%> 134 </div> 135 136 <% if show_details %> 137 <table class="trace"> 138 <% @exception.backtrace.each_with_index do |line, index| %> 139 <tbody class="close"> 140 <tr class="file"> 141 <td class="expand"> 142 </td> 143 <td class="path"> 144 <%= (line.match(/^([^:]+)/)[1] rescue 'unknown').sub(/\/((opt|usr)\/local\/lib\/(ruby\/)?(gems\/)?(1.8\/)?(gems\/)?|.+\/app\/)/, '') %> in "<strong><%= line.match(/:in `(.+)'$/)[1] rescue '?' %></strong>" 145 </td> 146 <td class="line"> 147 <a href="txmt://open?url=file://<%=file = (line.match(/^([^:]+)/)[1] rescue 'unknown')%>&line=<%= lineno = line.match(/:([0-9]+):/)[1] rescue '?' %>"><%=lineno%></a> 148 </td> 149 </tr> 150 <tr class="source"> 151 <td class="collapse"> 152 </td> 153 <td class="code" colspan="2"><% (__caller_lines__(file, lineno, 5) rescue []).each do |llineno, lcode, lcurrent| %> 154 <a href="txmt://open?url=file://<%=file%>&line=<%=llineno%>"><%= llineno %></a><%='<em>' if llineno==lineno.to_i %><%= lcode.size > 90 ? lcode[0..90]+'<span class="more">......</span>' : lcode %><%='</em>' if llineno==lineno.to_i %> 155 <% end %> 42 156 43 var toggle = function(el){ 44 el = $(el); 45 var visible = el.style.display != 'none'; // visible? boolean 46 if(visible){ 47 el.style.display = 'none'; // hide 48 }else{ 49 el.style.display = ''; // unhide 50 } 51 } 52 </script> 53 </head> 54 <body> 55 <div> 56 <h1><img src="data:image/jpg;base64,/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAJQAA/+4ADkFk\nb2JlAGTAAAAAAf/bAIQADQkJCQoJDQoKDRMMCwwTFhENDREWGhUVFhUVGhkU\nFhUVFhQZGR0fIB8dGScnKionJzk4ODg5QEBAQEBAQEBAQAEODAwOEA4RDw8R\nFA4RDhQVERISERUgFRUXFRUgKB0ZGRkZHSgjJiAgICYjLCwoKCwsNzc1NzdA\nQEBAQEBAQEBA/8AAEQgAJgAtAwEiAAIRAQMRAf/EAT8AAAEFAQEBAQEBAAAA\nAAAAAAMAAQIEBQYHCAkKCwEAAQUBAQEBAQEAAAAAAAAAAQACAwQFBgcICQoL\nEAABBAEDAgQCBQcGCAUDDDMBAAIRAwQhEjEFQVFhEyJxgTIGFJGhsUIjJBVS\nwWIzNHKC0UMHJZJT8OHxY3M1FqKygyZEk1RkRcKjdDYX0lXiZfKzhMPTdePz\nRieUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm9jdHV2d3h5ent8fX5/cRAAIC\nAQIEBAMEBQYHBwYFNQEAAhEDITESBEFRYXEiEwUygZEUobFCI8FS0fAzJGLh\ncoKSQ1MVY3M08SUGFqKygwcmNcLSRJNUoxdkRVU2dGXi8rOEw9N14/NGlKSF\ntJXE1OT0pbXF1eX1VmZ2hpamtsbW5vYnN0dXZ3eHl6e3x//aAAwDAQACEQMR\nAD8A9OVDK6xRQYa02axuEBvnB7x8Fm/W36x19HqoxmAvyswkMraHFwY2N7va\nCe/5Vy/Seu29RvdXaI2CW6EBw4OhGnZAk9Einrbuvn03Oaw1lvmCCP8ANXPW\n9Zz7shlrM9zKBLnN3Fmg1gnj8An61m14eCbAN9j4ZVWJJc5x2gQPivO3ZnUM\nXLtfY8by73sDmuHw9pPCZRPVNgdH13F+teJU51WbZpX9O4xpPEho4juug9Wr\n0vW3t9Lbv9SRt2xO7dxEL5+blW5I2W2FrdsSJJgdj4r0P1W/+Nf6Un1p9PZJ\nnf8AaPU2+P0fwThdVaLF3TlfXZvUsr63ZIpZY4Y9VDKTWwuIY5vqEe3uXFx+\nSn0XBupy9t+loqDXy1zTJLST7tefJbVDLsjLycwO9Sy/09ANQafUJP8A4IrG\nQGufXltEAjZYPA900kkeCRoXA+sHSeoTXn41xJxnB9bY3e4HRcTbVsfB2h7j\ntDWkkAT7jrPjC9FzCMfpJZU5wDGgN7wNI4HZcJdiNrvP2t+62SNg9vedxnRO\nj2WyQfZy2oOA5kbfGedF0jb8w/Uq2ra/25NcGD9F1dk6/BqrYNODY5rrve4+\n2rGpBcR3Mr0j9iVf83vscD1J3bZMep/NelM+Hsn5oqa3RG0uuf6b9j9dsCRI\n+lx4haGT9g1FgZ6nc0n/AKrSEkko/Kg7vO9XtsxzV+y6a8xjtxu+hWWwPY0f\npBMnv2XN52ZmbyLunYocCZNl1bpdENmS0wOdUkkPsT9rL6rfbf8AnJiPzvT+\nzfpPVrZ6ezb6Ttv0e26OTzC9U3uk+z88ACRu+hz8YSSR6q6P/9k=\n"/> 57 <%= @title %></h1> 58 </div> 59 60 <table class="main"> 61 <thead> 62 <tr> 63 <td class="method">Method</td> 64 <td class="line">Line</td> 65 <td class="file">File</td> 66 </tr> 67 </thead> 68 <% @backtrace.each do |lines, hash, file, lineno, meth| %> 69 <tr id="line_<%= hash %>" onclick="toggle('source_<%= hash %>'); return false"; class="clickable" style="background:rgb(250,250,<%= @colors.shift %>);"> 70 <td class="method"><%= meth %></td> 71 <td class="line"><%= lineno %></td> 72 <td class="file"><%= file %></td> 73 </tr> 74 <tr id="source_<%= hash %>" <%= @coderay? " class='CodeRay' " : ''%> style="display:none;"> 75 <td colspan="3"> 76 <div class="source"> 77 <table> 78 <tr><td colspan='2'><a href='txmt://open?url=file://<%=file%>&line=<%=lineno%>'>Open in TextMate</a></td></tr> 79 <% lines.each do |llineno, lcode, lcurrent| %> 80 <tr class="source"<%= 'style="background:#faa;"' if lcurrent %>> 81 <td><%= llineno %></td> 82 <td> 83 <%= @coderay ? CodeRay.scan(lcode, :ruby).html : "<pre>\#{lcode}</pre>" %> 84 </td> 85 </tr> 86 <% end %> 87 </table> 88 </div> 89 </td> 90 </tr> 91 <% end %> 92 </table> 93 </body> 94 </html> 95 <% else %> 96 <h1>500 Internal Server Error</h1> 97 <% end %> 157 </td> 158 </tr> 159 </tbody> 160 <% end %> 161 </table> 162 <script type="text/javascript" charset="utf-8"> 163 // swop the open & closed classes 164 els = document.getElementsByTagName('td'); 165 for(i=0; i<els.length; i++){ 166 if(els[i].className=='expand' || els[i].className=='collapse'){ 167 els[i].onclick = function(e){ 168 tbody = this.parentNode.parentNode; 169 if(tbody.className=='open'){ 170 tbody.className='closed'; 171 }else{ 172 tbody.className='open'; 173 } 174 } 175 } 176 } 177 </script> 178 <% end %> 179 <div class="footer"> 180 lots of love, from <a href="#">merb</a> 181 </div> 182 </div> 183 </body> 184 </html> trunk/app_generators/merb/templates/app/views/exceptions/not_found.html.erb
r536 r640 9 9 <div id="left-container"> 10 10 <h3>Exception:</h3> 11 <p><%= exception%></p>11 <p><%= params[:exception] %></p> 12 12 </div> 13 13 trunk/lib/merb/controller.rb
r639 r640 15 15 include Merb::ControllerMixin 16 16 include Merb::ResponderMixin 17 include Merb::ControllerExceptions ::HTTPErrors17 include Merb::ControllerExceptions 18 18 19 19 class << self … … 32 32 end 33 33 34 def build(request, response = StringIO.new, headers = {'Content-Type' =>'text/html'}, status = 200)34 def build(request, response = StringIO.new, status=200, headers={'Content-Type' => 'text/html'}) 35 35 cont = new 36 cont.set_dispatch_variables(request, response, headers, status)36 cont.set_dispatch_variables(request, response, status, headers) 37 37 cont 38 38 end 39 39 end 40 40 41 def set_dispatch_variables(request, response, headers, status)41 def set_dispatch_variables(request, response, status, headers) 42 42 if request.params.key?(_session_id_key) 43 43 if Merb::Server.config[:session_id_cookie_only] … … 66 66 if !self.class.callable_actions.include?(action.to_s) 67 67 if self.class.public_instance_methods.include?(action.to_s) 68 raise NotFound, "Action '#{action}' is not a public method of #{self.class}"68 raise ActionNotFound, "Action '#{action}' is not a public method of #{self.class}" 69 69 else 70 raise NotFound, "Action '#{action}' was not found in #{self.class}"70 raise ActionNotFound, "Action '#{action}' was not found in #{self.class}" 71 71 end 72 72 else … … 80 80 end 81 81 82 # Accessor for @_body. Please use statusand never @body directly.82 # Accessor for @_body. Please use body and never @body directly. 83 83 def body 84 84 @_body … … 158 158 # wrap_layout(part(TodoPart => :new) + part(TodoPart => :list)) 159 159 # end 160 # end160 # end 161 161 # 162 162 def part(opts={}) trunk/lib/merb/dispatcher.rb
r636 r640 1 1 module Merb 2 2 class Dispatcher 3 4 DEFAULT_ERROR_TEMPLATE = Erubis::MEruby.new(File.read(File.join( 5 MERB_SKELETON_DIR, 'app/views/exceptions/internal_server_error.html.erb'))) 6 3 7 class << self 4 8 … … 13 17 # Returns a 2 element tuple of: [controller, action] 14 18 # 15 # ControllerExceptions are rescued here and re turned as the controller.16 # In that case the function will return [exception, :error_response]19 # ControllerExceptions are rescued here and redispatched. 20 # Exceptions still return [controller, action] 17 21 def handle(http_request, response) 18 begin 19 start = Time.now 20 request = Merb::Request.new(http_request) 21 MERB_LOGGER.info("Params: #{request.params.inspect}") 22 MERB_LOGGER.info("Cookies: #{request.cookies.inspect}") 23 # user friendly error messages 24 if request.route_params.empty? 25 raise ControllerExceptions::NotFound, "No routes match the request" 26 elsif request.controller_name.nil? 27 raise ControllerExceptions::NotFound, "Route matched, but route did not specify a controller" 28 end 29 MERB_LOGGER.debug("Routed to: #{request.route_params.inspect}") 30 31 klass = request.controller_class 32 33 # Hint for users 34 unless klass.respond_to?(:build) 35 raise NoMethodError, "The controller class, '#{klass}' was not derived from the Application class (or Merb::AbstractController)" 36 end 37 38 controller = klass.build(request, response) 39 action = request.action 40 41 controller._benchmarks[:setup_time] = Time.now - start 42 if @@use_mutex 43 @@mutex.synchronize { controller.dispatch(action) } 44 else 45 controller.dispatch(action) 46 end 47 rescue Object => exception 48 # Wrap non-ControllerExceptions in an InternalServerError object 49 # to allow users the possibility to create their own 50 # internal_server_error.erb files 51 unless exception.kind_of?(ControllerExceptions::Base) 52 exception = ControllerExceptions::InternalServerError.new(exception) 53 end 54 # set_env for access to session, params, etc 55 exception.set_env(request, response, controller) 56 exception.call_action 57 58 controller = exception 59 action = :error_response 22 start = Time.now 23 request = Merb::Request.new(http_request) 24 MERB_LOGGER.info("Params: #{request.params.inspect}") 25 MERB_LOGGER.info("Cookies: #{request.cookies.inspect}") 26 # user friendly error messages 27 if request.route_params.empty? 28 raise ControllerExceptions::NotFound, "No routes match the request" 29 elsif request.controller_name.nil? 30 raise ControllerExceptions::NotFound, "Route matched, but route did not specify a controller" 31 end 32 MERB_LOGGER.debug("Routed to: #{request.route_params.inspect}") 33 # set controller class and the action to call 34 klass = request.controller_class 35 dispatch_action(klass, request.action, request, response) 36 rescue => exception 37 exception = controller_exception(exception) 38 dispatch_exception(request, response, exception) 39 end 40 41 private 42 43 # setup the controller and call the chosen action 44 def dispatch_action(klass, action, request, response, status=200) 45 # build controller 46 controller = klass.build(request, response, status) 47 # complete setup benchmarking 48 #controller._benchmarks[:setup_time] = Time.now - start 49 if @@use_mutex 50 @@mutex.synchronize { controller.dispatch(action) } 51 else 52 controller.dispatch(action) 60 53 end 61 54 [controller, action] 62 55 end 63 56 57 # Re-route the current request to the Exception controller 58 # if it is available, and try to render the exception nicely 59 # if it is not available then just render a simple text error 60 def dispatch_exception(request, response, exception) 61 klass = Exceptions rescue Controller 62 request.params[:exception] = exception 63 request.params[:action] = exception.name 64 dispatch_action(klass, exception.name, request, response, exception.class::STATUS) 65 rescue => dispatch_issue 66 dispatch_issue = controller_exception(dispatch_issue) 67 # when no action/template exist for an exception, or an 68 # exception occurs on an InternalServerError the message is 69 # rendered as simple text. 70 # ControllerExceptions raised from exception actions are 71 # dispatched back into the Exceptions controller 72 if dispatch_issue.is_a?(ControllerExceptions::NotFound) 73 dispatch_default_exception(klass, request, response, exception) 74 elsif dispatch_issue.is_a?(ControllerExceptions::InternalServerError) 75 dispatch_default_exception(klass, request, response, dispatch_issue) 76 else 77 exception = dispatch_issue 78 retry 79 end 80 end 81 82 # if no custom actions are available to render an exception 83 # then the errors will end up here for processing 84 def dispatch_default_exception(klass, request, response, e) 85 controller = klass.build(request, response, e.class::STATUS) 86 if e.is_a? ControllerExceptions::Redirection 87 controller.headers.merge!('Location' => e.message) 88 controller.instance_variable_set("@_body", %{ }) #fix 89 else 90 @exception = e # for ERB 91 controller.instance_variable_set("@_body", DEFAULT_ERROR_TEMPLATE.result(binding)) 92 end 93 [controller, e.name] 94 end 95 96 # Wraps any non-ControllerException errors in an 97 # InternalServerError ready for displaying over HTTP 98 def controller_exception(e) 99 e.kind_of?(ControllerExceptions::Base) ? 100 e : ControllerExceptions::InternalServerError.new(e) 101 end 102 64 103 end # end class << self 65 104 end trunk/lib/merb/exceptions.rb
r608 r640 6 6 module Merb 7 7 # ControllerExceptions are a way of simplifying controller code by placing 8 # exceptional logic elsewhere. ControllerExceptions are mini-controllers 9 # which have only one action: error_response 8 # exceptional logic back into the MVC pattern. 10 9 # 11 # Additionally all ControllerExceptions have an HTTP status code associated 12 # with them which is sent to the browser when it is rendered. 10 # When a ControllerException is raised within your application merb will 11 # attempt to re-route the request to your Exceptions controller to render 12 # the error in a friendly mannor. 13 13 # 14 # ControllerExceptions::Base is the abstract base class of all 15 # ControllerExceptions. Derived from Base are exceptions for each HTTP 16 # status code. These are addressed ControllerExceptions::HTTPErrors. 17 # 18 # These exceptions can be raised by your controller. For example 14 # For example you might have an action in your app that raises NotFound 15 # if a some resource was not available 19 16 # 20 17 # def show … … 24 21 # end 25 22 # 26 # The NotFound exception will look for a template at 23 # This would halt execution of your action and re-route it over to your 24 # Exceptions controller which might look something like 25 # 26 # class Exceptions < Application 27 # def not_found 28 # render :layout => :none 29 # end 30 # end 31 # 32 # As usual the not_found action will look for a template in 27 33 # app/views/exceptions/not_found.html.erb 28 # If the template is not found then a simple message will be sent. 34 # 35 # Note: All standard ControllerExceptions have an HTTP status code associated 36 # with them which is sent to the browser when the action it is rendered. 37 # 38 # Note: If you do not specifiy how to handle raised ControllerExceptions 39 # or an unhandlable exception occurs within your customised exception action 40 # then they will be rendered using the built-in error template 41 # in development mode this "built in" template will show stack-traces for 42 # any of the ServerError family of exceptions (you can force the stack-trace 43 # to display in production mode using the :exception_details config option in 44 # merb.yml) 45 # 46 # 47 # Internal Exceptions 48 # 49 # Any other rogue errors (not ControllerExceptions) that occur during the 50 # execution of you app will be converted into the ControllerException 51 # InternalServerError, and like all ControllerExceptions can be caught 52 # on your Exceptions controller. 53 # 54 # InternalServerErrors return status 500, a common use for cusomizing this 55 # action might be to send emails to the development team, warning that their 56 # application have exploded. Mock example: 57 # 58 # def internal_server_error 59 # MySpecialMailer.deliver( 60 # "team@cowboys.com", 61 # "Exception occured at #{Time.now}", 62 # params[:exception]) 63 # render :inline => 'Something is wrong, but the team are on it!' 64 # end 65 # 66 # Note: The special param[:exception] is available in all Exception actions 67 # and contains the ControllerException that was raised (this is handy if 68 # you want to display the associated message or display more detailed info) 69 # 70 # 71 # Extending ControllerExceptions 29 72 # 30 73 # To extend the use of the ControllerExceptions one may extend any of the 31 # HTTPError classes. One must then add the special 'action' method called 32 # error_response. 74 # HTTPError classes. 33 75 # 34 # As an example we can create an exception called AdminAccessRequired. 76 # As an example we can create an exception called AdminAccessRequired. 35 77 # 36 # class AdminAccessRequired < Merb::ControllerExceptions::Unauthorized 37 # def error_response 38 # @tried_to_access request.uri 39 # render :layout => 'error_page' 78 # class AdminAccessRequired < Merb::ControllerExceptions::Unauthorized; end 79 # 80 # Add the required action to our Exceptions controller 81 # 82 # class Exceptions < Application 83 # def admin_access_required 84 # render 40 85 # end 41 86 # end … … 46 91 # <p>You tried to access <%= @tried_to_access %> but that URL is 47 92 # restricted to administrators.</p> 93 # 48 94 module ControllerExceptions 49 95 class Base < StandardError 50 include Merb::RenderMixin 51 include Merb::ControllerMixin 52 include Merb::ResponderMixin 53 attr_accessor :_benchmarks, :thrown_content 54 55 56 def set_env(request, response, controller = nil) 57 @request = request 58 @response = response 59 @controller = controller 60 @_benchmarks = {} 61 @thrown_content = ::Merb::AbstractController.default_thrown_content 62 end 63 64 def call_action 65 @_body = error_response 66 end 67 68 def _layout 69 ::Merb::Server.config[:exception_layout] || 'application' 70 end 71 72 # when rendering errors we first try to simply "render" 73 # for a NotAcceptable error this would get the template found in 74 # views/exceptions/not_acceptable.html.erb 75 # if that template is not found or there is an error in the template 76 # a simple hard-coded text version is displayed 77 def error_response 78 render 79 rescue MissingTemplateError 80 render_simple 81 rescue => e 82 HTTPErrors::InternalServerError.new(e).render_simple 83 end 84 85 # this is the simple 'hard-coded' inline template for errors 86 # it is kept simple since it is a last resort for displaying errors 87 def render_simple(details = message) 88 headers['Content-Type'] = 'text/plain' 89 "Error #{status} #{self.class.name.split('::').last}! #{details}" 90 end 91 92 # it is not always prudent to display all error details to the client 93 # by default this returns true for development, false for production 94 def show_exception_details? 95 ::Merb::Server.config[:exception_details] 96 end 97 98 # Below are methods which make the exception act like a controller 99 100 def headers 101 if @controller 102 @controller.headers 103 else 104 @_headers ||= {'Content-Type' => 'text/html'} 105 end 106 end 107 attr_reader :request, :response 108 def params; @controller.params if @controller; end 109 def cookies; @controller.cookies if @controller; end 110 def session; @controller.session if @controller; end 111 def status; self.class::STATUS; end 112 def body; @_body; end 113 114 # catch any method calls that the controller responds to 115 # and delegate them back to the controller. 116 def method_missing(sym, *args, &blk) 117 if @controller.respond_to? sym 118 @controller.send(sym, *args, &blk) 119 else 120 super 121 end 122 end 123 124 # pass requests onto controller 125 def respond_to?(sym) 126 super || @controller.respond_to?(sym) 96 def name 97 self.class.to_s.snake_case.split('::').last 127 98 end 128 99 end 129 100 130 module HTTPErrors 131 class Continue < Merb::ControllerExceptions::Base; STATUS = 100; end 132 class SwitchingProtocols < Merb::ControllerExceptions::Base; STATUS = 101; end 133 class OK < Merb::ControllerExceptions::Base; STATUS = 200; end 134 class Created < Merb::ControllerExceptions::Base; STATUS = 201; end 135 class Accepted < Merb::ControllerExceptions::Base; STATUS = 202; end 136 class NonAuthoritativeInformation < Merb::ControllerExceptions::Base; STATUS = 203; end 137 class NoContent < Merb::ControllerExceptions::Base; STATUS = 204; end 138 class ResetContent < Merb::ControllerExceptions::Base; STATUS = 205; end 139 class PartialContent < Merb::ControllerExceptions::Base; STATUS = 206; end 140 class MultipleChoices < Merb::ControllerExceptions::Base; STATUS = 300; end 141 class MovedPermanently < Merb::ControllerExceptions::Base; STATUS = 301; end 142 class MovedTemporarily < Merb::ControllerExceptions::Base; STATUS = 302; end 143 class SeeOther < Merb::ControllerExceptions::Base; STATUS = 303; end 144 class NotModified < Merb::ControllerExceptions::Base; STATUS = 304; end 145 class UseProxy < Merb::ControllerExceptions::Base; STATUS = 305; end 146 class BadRequest < Merb::ControllerExceptions::Base; STATUS = 400; end 147 class Unauthorized < Merb::ControllerExceptions::Base; STATUS = 401; end 148 class PaymentRequired < Merb::ControllerExceptions::Base; STATUS = 402; end 149 class Forbidden < Merb::ControllerExceptions::Base; STATUS = 403; end 150 class NotFound < Merb::ControllerExceptions::Base; STATUS = 404; end 151 class MethodNotAllowed < Merb::ControllerExceptions::Base; STATUS = 405; end 152 class NotAcceptable < Merb::ControllerExceptions::Base; STATUS = 406; end 153 class ProxyAuthenticationRequired < Merb::ControllerExceptions::Base; STATUS = 407; end 154 class RequestTimeout < Merb::ControllerExceptions::Base; STATUS = 408; end 155 class Conflict < Merb::ControllerExceptions::Base; STATUS = 409; end 156 class Gone < Merb::ControllerExceptions::Base; STATUS = 410; end 157 class LengthRequired < Merb::ControllerExceptions::Base; STATUS = 411; end 158 class PreconditionFailed < Merb::ControllerExceptions::Base; STATUS = 412; end 159 class RequestEntityTooLarge < Merb::ControllerExceptions::Base; STATUS = 413; end 160 class RequestURITooLarge < Merb::ControllerExceptions::Base; STATUS = 414; end 161 class UnsupportedMediaType < Merb::ControllerExceptions::Base; STATUS = 415; end 162 # :nodoc For Internal Server Error 500 See below 163 class NotImplemented < Merb::ControllerExceptions::Base; STATUS = 501; end 164 class BadGateway < Merb::ControllerExceptions::Base; STATUS = 502; end 165 class ServiceUnavailable < Merb::ControllerExceptions::Base; STATUS = 503; end 166 class GatewayTimeout < Merb::ControllerExceptions::Base; STATUS = 504; end 167 class HTTPVersionNotSupported < Merb::ControllerExceptions::Base; STATUS = 505; end 168 169 class InternalServerError < Merb::ControllerExceptions::Base 170 STATUS = 500 101 class Informational < Merb::ControllerExceptions::Base; end 102 class Continue < Merb::ControllerExceptions::Informational; STATUS = 100; end 103 class SwitchingProtocols < Merb::ControllerExceptions::Informational; STATUS = 101; end 104 class Successful < Merb::ControllerExceptions::Base; end 105 class OK < Merb::ControllerExceptions::Successful; STATUS = 200; end 106 class Created < Merb::ControllerExceptions::Successful; STATUS = 201; end 107 class Accepted < Merb::ControllerExceptions::Successful; STATUS = 202; end 108 class NonAuthoritativeInformation < Merb::ControllerExceptions::Successful; STATUS = 203; end 109 class NoContent < Merb::ControllerExceptions::Successful; STATUS = 204; end 110 class ResetContent < Merb::ControllerExceptions::Successful; STATUS = 205; end 111 class PartialContent < Merb::ControllerExceptions::Successful; STATUS = 206; end 112 class Redirection < Merb::ControllerExceptions::Base; end 113 class MultipleChoices < Merb::ControllerExceptions::Redirection; STATUS = 300; end 114 class MovedPermanently < Merb::ControllerExceptions::Redirection; STATUS = 301; end 115 class MovedTemporarily < Merb::ControllerExceptions::Redirection; STATUS = 302; end 116 class SeeOther < Merb::ControllerExceptions::Redirection; STATUS = 303; end 117 class NotModified < Merb::ControllerExceptions::Redirection; STATUS = 304; end 118 class UseProxy < Merb::ControllerExceptions::Redirection; STATUS = 305; end 119 class ClientError < Merb::ControllerExceptions::Base; end 120 class BadRequest < Merb::ControllerExceptions::ClientError; STATUS = 400; end 121 class Unauthorized < Merb::ControllerExceptions::ClientError; STATUS = 401; end 122 class PaymentRequired < Merb::ControllerExceptions::ClientError; STATUS = 402; end 123 class Forbidden < Merb::ControllerExceptions::ClientError; STATUS = 403; end 124 class NotFound < Merb::ControllerExceptions::ClientError; STATUS = 404; end 125 class ActionNotFound < Merb::ControllerExceptions::NotFound; end 126 class TemplateNotFound < Merb::ControllerExceptions::NotFound; end 127 class LayoutNotFound < Merb::ControllerExceptions::NotFound; end 128 class MethodNotAllowed < Merb::ControllerExceptions::ClientError; STATUS = 405; end 129 class NotAcceptable < Merb::ControllerExceptions::ClientError; STATUS = 406; end 130 class ProxyAuthenticationRequired < Merb::ControllerExceptions::ClientError; STATUS = 407; end 131 class RequestTimeout < Merb::ControllerExceptions::ClientError; STATUS = 408; end 132 class Conflict < Merb::ControllerExceptions::ClientError; STATUS = 409; end 133 class Gone < Merb::ControllerExceptions::ClientError; STATUS = 410; end 134 class LengthRequired < Merb::ControllerExceptions::ClientError; STATUS = 411; end 135 class PreconditionFailed < Merb::ControllerExceptions::ClientError; STATUS = 412; end 136 class RequestEntityTooLarge < Merb::ControllerExceptions::ClientError; STATUS = 413; end 137 class RequestURITooLarge < Merb::ControllerExceptions::ClientError; STATUS = 414; end 138 class UnsupportedMediaType < Merb::ControllerExceptions::ClientError; STATUS = 415; end 139 class ServerError < Merb::ControllerExceptions::Base; end 140 class NotImplemented < Merb::ControllerExceptions::ServerError; STATUS = 501; end 141 class BadGateway < Merb::ControllerExceptions::ServerError; STATUS = 502; end 142 class ServiceUnavailable < Merb::ControllerExceptions::ServerError; STATUS = 503; end 143 class GatewayTimeout < Merb::ControllerExceptions::ServerError; STATUS = 504; end 144 class HTTPVersionNotSupported < Merb::ControllerExceptions::ServerError; STATUS = 505; end 145 class InternalServerError < Merb::ControllerExceptions::ServerError; STATUS = 500 171 146 DEFAULT_TEMPLATE = Erubis::MEruby.new(File.read(File.join( 172 147 MERB_SKELETON_DIR, 'app/views/exceptions/internal_server_error.html.erb'))) 173
