Changeset 640

Show
Ignore:
Timestamp:
09/15/07 12:57:56 (1 year ago)
Author:
e.@brainspl.at
Message:

applying patch for controller exceptions to reroute to an exceptions controller. closes #178

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/app_generators/merb/merb_generator.rb

    r637 r640  
    2525      # copy skeleton 
    2626      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" 
    2828      m.file_copy_each %w( global_helper.rb ), "app/helpers" 
    2929      m.file_copy_each %w( application.erb ), "app/mailers/views/layout" 
    3030      m.file_copy_each %w( application.html.erb ), "app/views/layout" 
    31       m.file_copy_each %w( internal_server_error.html.erb not_found.html.erb ), "app/views/exceptions" 
     31      m.file_copy_each %w( not_found.html.erb ), "app/views/exceptions" 
    3232      m.file_copy_each %w( merb.jpg ), "public/images" 
    3333      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')%>&amp;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%>&amp;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 %> 
    42156 
    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  
    99    <div id="left-container"> 
    1010      <h3>Exception:</h3> 
    11       <p><%= exception %></p> 
     11      <p><%= params[:exception] %></p> 
    1212    </div> 
    1313 
  • trunk/lib/merb/controller.rb

    r639 r640  
    1515    include Merb::ControllerMixin 
    1616    include Merb::ResponderMixin 
    17     include Merb::ControllerExceptions::HTTPErrors 
     17    include Merb::ControllerExceptions 
    1818     
    1919    class << self 
     
    3232      end 
    3333       
    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'}
    3535        cont = new 
    36         cont.set_dispatch_variables(request, response, headers, status) 
     36        cont.set_dispatch_variables(request, response, status, headers) 
    3737        cont 
    3838      end 
    3939    end 
    4040     
    41     def set_dispatch_variables(request, response, headers, status) 
     41    def set_dispatch_variables(request, response, status, headers) 
    4242      if request.params.key?(_session_id_key) 
    4343        if Merb::Server.config[:session_id_cookie_only] 
     
    6666      if !self.class.callable_actions.include?(action.to_s) 
    6767        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}" 
    6969        else 
    70           raise NotFound, "Action '#{action}' was not found in #{self.class}" 
     70          raise ActionNotFound, "Action '#{action}' was not found in #{self.class}" 
    7171        end 
    7272      else 
     
    8080    end 
    8181       
    82     # Accessor for @_body. Please use status and never @body directly. 
     82    # Accessor for @_body. Please use body and never @body directly. 
    8383    def body 
    8484       @_body   
     
    158158    #     wrap_layout(part(TodoPart => :new) + part(TodoPart => :list)) 
    159159    #   end 
    160     #end 
     160    # end 
    161161    # 
    162162    def part(opts={}) 
  • trunk/lib/merb/dispatcher.rb

    r636 r640  
    11module Merb 
    22  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         
    37    class << self 
    48       
     
    1317      # Returns a 2 element tuple of: [controller, action] 
    1418      # 
    15       # ControllerExceptions are rescued here and returned 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
    1721      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) 
    6053        end 
    6154        [controller, action] 
    6255      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       
    64103    end # end class << self 
    65104  end 
  • trunk/lib/merb/exceptions.rb

    r608 r640  
    66module Merb 
    77  # 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. 
    109  # 
    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. 
    1313  # 
    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 
    1916  # 
    2017  #   def show 
     
    2421  #   end 
    2522  # 
    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 
    2733  #   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 
    2972  # 
    3073  # 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. 
    3375  # 
    34   # As an example we can create an exception called AdminAccessRequired.  
     76  # As an example we can create an exception called AdminAccessRequired. 
    3577  # 
    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 
    4085  #     end 
    4186  #   end 
     
    4691  #   <p>You tried to access <%= @tried_to_access %> but that URL is  
    4792  #   restricted to administrators.</p> 
     93  # 
    4894  module ControllerExceptions 
    4995    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 
    12798      end 
    12899    end 
    129100     
    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 
    171146        DEFAULT_TEMPLATE = Erubis::MEruby.new(File.read(File.join( 
    172147            MERB_SKELETON_DIR, 'app/views/exceptions/internal_server_error.html.erb'))) 
    173