class Mongrel::HttpResponse NO_CLOSE_STATUS_FORMAT = "HTTP/1.1 %d %s\r\n".freeze def send_status_no_connection_close(content_length=@body.length) if not @status_sent @header['Content-Length'] = content_length unless @status == 304 write(NO_CLOSE_STATUS_FORMAT % [@status, Mongrel::HTTP_STATUS_CODES[@status]]) @status_sent = true end end end class MerbHandler < Mongrel::HttpHandler @@file_only_methods = ["GET","HEAD"] # take the name of a directory and use that as the doc root or public # directory of your site. This is set to the root of your merb app + '/public' # by default. def initialize(dir, opts = {}) @files = Mongrel::DirHandler.new(dir,false) end # process incoming http requests and do a number of things # 1. check for rails style cached pages. add .html to the # url and see if there is a static file in public that matches. # serve that file directly without invoking Merb and be done with it. # 2. Serve static asset and html files directly from public/ if # they exist. # 3. If none of the above apply, we take apart the request url # and feed it into Merb::RouteMatcher to let it decide which # controller and method will serve the request. # 4. after the controller has done its thing, we check for the # X-SENDFILE header. if you set this header to the path to a file # in your controller then mongrel will serve the file directly # and your controller can go on processing other requests. def process(request, response) start = Time.now if response.socket.closed? return end MERB_LOGGER.info("\nRequest: REQUEST_URI: #{request.params[Mongrel::Const::REQUEST_URI]} (#{Time.now.strftime("%Y-%m-%d %H:%M:%S")})") # Rails style page caching. Check the public dir first for # .html pages and serve directly. Otherwise fall back to Merb # routing and request dispatching. path_info = request.params[Mongrel::Const::PATH_INFO] page_cached = path_info + ".html" get_or_head = @@file_only_methods.include? request.params[Mongrel::Const::REQUEST_METHOD] if get_or_head and @files.can_serve(path_info) # File exists as-is so serve it up MERB_LOGGER.info("Serving static file: #{path_info}") @files.process(request,response) elsif get_or_head and @files.can_serve(page_cached) # Possible cached page, serve it up MERB_LOGGER.info("Serving static file: #{page_cached}") request.params[Mongrel::Const::PATH_INFO] = page_cached @files.process(request,response) else begin # dLet Merb:Dispatcher find the route and call the filter chain and action controller = nil controller, action = Merb::Dispatcher.handle(request, response) MERB_LOGGER.info("Routing to controller: #{controller.class} action: #{action}\nRoute Recognition & Parsing HTTP Input took: #{Time.now - start} seconds") rescue Object => e response.start(500) do |head,out| head["Content-Type"] = "text/html" MERB_LOGGER.info(Merb.exception(e)) out << Merb.html_exception(e) end return end sendfile, clength = nil response.status = controller.status # check for the X-SENDFILE header from your Merb::Controller # and serve the file directly instead of buffering. controller.headers.each do |k, v| if k =~ /^X-SENDFILE$/i sendfile = v elsif k =~ /^CONTENT-LENGTH$/i clength = v.to_i else [*v].each do |vi| response.header[k] = vi end end end if sendfile MERB_LOGGER.info("X-SENDFILE: #{sendfile}\nComplete Request took: #{Time.now - start} seconds") file_status = File.stat(sendfile) response.status = 200 # Set the last modified times as well and etag for all files response.header[Mongrel::Const::LAST_MODIFIED] = file_status.mtime.httpdate # Calculated the same as apache, not sure how well the works on win32 response.header[Mongrel::Const::ETAG] = Mongrel::Const::ETAG_FORMAT % [file_status.mtime.to_i, file_status.size, file_status.ino] # send a status with out content length response.send_status(file_status.size) response.send_header response.send_file(sendfile) elsif controller.body.respond_to? :read response.send_status(clength) response.send_header while chunk = controller.body.read(16384) response.write(chunk) end if controller.body.respond_to? :close controller.body.close end elsif Proc === controller.body controller.body.call else MERB_LOGGER.info("Response status: #{response.status}\nComplete Request took: #{Time.now - start} seconds\n\n") # render response from successful controller body = (controller.body.to_s rescue '') response.send_status(body.length) response.send_header response.write(body) end end end end