mongrel.rb

来自「Amarok是一款在LINUX或其他类UNIX操作系统中运行的音频播放器软件。 」· RB 代码 · 共 807 行 · 第 1/2 页

RB
807
字号
# Copyright (c) 2005 Zed A. Shaw # You can redistribute it and/or modify it under the same terms as Ruby.## Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html # for more information.$mongrel_debug_client = falserequire 'rubygems'require 'socket'require 'http11'require 'tempfile'begin  require 'fastthread'rescue RuntimeError => e  warn "fastthread not loaded: #{ e.message }"rescue LoadErrorensure  require 'thread'endrequire 'stringio'require 'mongrel/cgi'require 'mongrel/handlers'require 'mongrel/command'require 'mongrel/tcphack'require 'yaml'require 'mongrel/configurator'require 'time'require 'etc'require 'uri'# Mongrel module containing all of the classes (include C extensions) for running# a Mongrel web server.  It contains a minimalist HTTP server with just enough# functionality to service web application requests fast as possible.module Mongrel  class URIClassifier    attr_reader :handler_map    # Returns the URIs that have been registered with this classifier so far.    # The URIs returned should not be modified as this will cause a memory leak.    # You can use this to inspect the contents of the URIClassifier.    def uris      @handler_map.keys    end    # Simply does an inspect that looks like a Hash inspect.    def inspect      @handler_map.inspect    end  end  # Used to stop the HttpServer via Thread.raise.  class StopServer < Exception; end  # Thrown at a thread when it is timed out.  class TimeoutError < Exception; end  # Every standard HTTP code mapped to the appropriate message.  These are  # used so frequently that they are placed directly in Mongrel for easy  # access rather than Mongrel::Const.  HTTP_STATUS_CODES = {      100  => 'Continue',     101  => 'Switching Protocols',     200  => 'OK',     201  => 'Created',     202  => 'Accepted',     203  => 'Non-Authoritative Information',     204  => 'No Content',     205  => 'Reset Content',     206  => 'Partial Content',     300  => 'Multiple Choices',     301  => 'Moved Permanently',     302  => 'Moved Temporarily',     303  => 'See Other',     304  => 'Not Modified',     305  => 'Use Proxy',     400  => 'Bad Request',     401  => 'Unauthorized',     402  => 'Payment Required',     403  => 'Forbidden',     404  => 'Not Found',     405  => 'Method Not Allowed',     406  => 'Not Acceptable',     407  => 'Proxy Authentication Required',     408  => 'Request Time-out',     409  => 'Conflict',     410  => 'Gone',     411  => 'Length Required',     412  => 'Precondition Failed',     413  => 'Request Entity Too Large',     414  => 'Request-URI Too Large',     415  => 'Unsupported Media Type',     500  => 'Internal Server Error',     501  => 'Not Implemented',     502  => 'Bad Gateway',     503  => 'Service Unavailable',     504  => 'Gateway Time-out',     505  => 'HTTP Version not supported'  }  # Frequently used constants when constructing requests or responses.  Many times  # the constant just refers to a string with the same contents.  Using these constants  # gave about a 3% to 10% performance improvement over using the strings directly.  # Symbols did not really improve things much compared to constants.  #  # While Mongrel does try to emulate the CGI/1.2 protocol, it does not use the REMOTE_IDENT,  # REMOTE_USER, or REMOTE_HOST parameters since those are either a security problem or   # too taxing on performance.  module Const    DATE = "Date".freeze    # This is the part of the path after the SCRIPT_NAME.  URIClassifier will determine this.    PATH_INFO="PATH_INFO".freeze    # This is the initial part that your handler is identified as by URIClassifier.    SCRIPT_NAME="SCRIPT_NAME".freeze    # The original URI requested by the client.  Passed to URIClassifier to build PATH_INFO and SCRIPT_NAME.    REQUEST_URI='REQUEST_URI'.freeze    REQUEST_PATH='REQUEST_PATH'.freeze    MONGREL_VERSION="1.0.1".freeze    MONGREL_TMP_BASE="mongrel".freeze    # The standard empty 404 response for bad requests.  Use Error4040Handler for custom stuff.    ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Mongrel #{MONGREL_VERSION}\r\n\r\nNOT FOUND".freeze    CONTENT_LENGTH="CONTENT_LENGTH".freeze    # A common header for indicating the server is too busy.  Not used yet.    ERROR_503_RESPONSE="HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze    # The basic max request size we'll try to read.    CHUNK_SIZE=(16 * 1024)    # This is the maximum header that is allowed before a client is booted.  The parser detects    # this, but we'd also like to do this as well.    MAX_HEADER=1024 * (80 + 32)    # Maximum request body size before it is moved out of memory and into a tempfile for reading.    MAX_BODY=MAX_HEADER    # A frozen format for this is about 15% faster    STATUS_FORMAT = "HTTP/1.1 %d %s\r\nConnection: close\r\n".freeze    CONTENT_TYPE = "Content-Type".freeze    LAST_MODIFIED = "Last-Modified".freeze    ETAG = "ETag".freeze    SLASH = "/".freeze    REQUEST_METHOD="REQUEST_METHOD".freeze    GET="GET".freeze    HEAD="HEAD".freeze    # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)    ETAG_FORMAT="\"%x-%x-%x\"".freeze    HEADER_FORMAT="%s: %s\r\n".freeze    LINE_END="\r\n".freeze    REMOTE_ADDR="REMOTE_ADDR".freeze    HTTP_X_FORWARDED_FOR="HTTP_X_FORWARDED_FOR".freeze    HTTP_IF_MODIFIED_SINCE="HTTP_IF_MODIFIED_SINCE".freeze    HTTP_IF_NONE_MATCH="HTTP_IF_NONE_MATCH".freeze    REDIRECT = "HTTP/1.1 302 Found\r\nLocation: %s\r\nConnection: close\r\n\r\n".freeze    HOST = "HOST".freeze  end  # Basically a Hash with one extra parameter for the HTTP body, mostly used internally.  class HttpParams < Hash    attr_accessor :http_body  end  # When a handler is found for a registered URI then this class is constructed  # and passed to your HttpHandler::process method.  You should assume that   # *one* handler processes all requests.  Included in the HttpRequest is a  # HttpRequest.params Hash that matches common CGI params, and a HttpRequest.body  # which is a string containing the request body (raw for now).  #  # The HttpRequest.initialize method will convert any request that is larger than  # Const::MAX_BODY into a Tempfile and use that as the body.  Otherwise it uses   # a StringIO object.  To be safe, you should assume it works like a file.  #  # The HttpHandler.request_notify system is implemented by having HttpRequest call  # HttpHandler.request_begins, HttpHandler.request_progress, HttpHandler.process during  # the IO processing.  This adds a small amount of overhead but lets you implement  # finer controlled handlers and filters.  class HttpRequest    attr_reader :body, :params    # You don't really call this.  It's made for you.    # Main thing it does is hook up the params, and store any remaining    # body data into the HttpRequest.body attribute.    def initialize(params, socket, dispatchers)      @params = params      @socket = socket      @dispatchers = dispatchers      content_length = @params[Const::CONTENT_LENGTH].to_i      remain = content_length - @params.http_body.length            # tell all dispatchers the request has begun      @dispatchers.each do |dispatcher|        dispatcher.request_begins(@params)       end unless @dispatchers.nil? || @dispatchers.empty?      # Some clients (like FF1.0) report 0 for body and then send a body.  This will probably truncate them but at least the request goes through usually.      if remain <= 0        # we've got everything, pack it up        @body = StringIO.new        @body.write @params.http_body        update_request_progress(0, content_length)      elsif remain > 0        # must read more data to complete body        if remain > Const::MAX_BODY          # huge body, put it in a tempfile          @body = Tempfile.new(Const::MONGREL_TMP_BASE)          @body.binmode        else          # small body, just use that          @body = StringIO.new         end        @body.write @params.http_body        read_body(remain, content_length)      end      @body.rewind if @body    end    # updates all dispatchers about our progress    def update_request_progress(clen, total)      return if @dispatchers.nil? || @dispatchers.empty?      @dispatchers.each do |dispatcher|        dispatcher.request_progress(@params, clen, total)       end     end    private :update_request_progress    # Does the heavy lifting of properly reading the larger body requests in     # small chunks.  It expects @body to be an IO object, @socket to be valid,    # and will set @body = nil if the request fails.  It also expects any initial    # part of the body that has been read to be in the @body already.    def read_body(remain, total)      begin        # write the odd sized chunk first        @params.http_body = read_socket(remain % Const::CHUNK_SIZE)        remain -= @body.write(@params.http_body)        update_request_progress(remain, total)        # then stream out nothing but perfectly sized chunks        until remain <= 0 or @socket.closed?          # ASSUME: we are writing to a disk and these writes always write the requested amount          @params.http_body = read_socket(Const::CHUNK_SIZE)          remain -= @body.write(@params.http_body)          update_request_progress(remain, total)        end      rescue Object        STDERR.puts "ERROR reading http body: #$!"        $!.backtrace.join("\n")        # any errors means we should delete the file, including if the file is dumped        @socket.close rescue Object        @body.delete if @body.class == Tempfile        @body = nil # signals that there was a problem      end    end     def read_socket(len)      if !@socket.closed?        data = @socket.read(len)        if !data          raise "Socket read return nil"        elsif data.length != len          raise "Socket read returned insufficient data: #{data.length}"        else          data        end      else        raise "Socket already closed when reading."      end    end    # Performs URI escaping so that you can construct proper    # query strings faster.  Use this rather than the cgi.rb    # version since it's faster.  (Stolen from Camping).    def self.escape(s)      s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {        '%'+$1.unpack('H2'*$1.size).join('%').upcase      }.tr(' ', '+')     end    # Unescapes a URI escaped string. (Stolen from Camping).    def self.unescape(s)      s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){        [$1.delete('%')].pack('H*')      }     end    # Parses a query string by breaking it up at the '&'     # and ';' characters.  You can also use this to parse    # cookies by changing the characters used in the second    # parameter (which defaults to '&;'.    def self.query_parse(qs, d = '&;')      params = {}      (qs||'').split(/[#{d}] */n).inject(params) { |h,p|        k, v=unescape(p).split('=',2)        if cur = params[k]          if cur.class == Array            params[k] << v          else            params[k] = [cur, v]          end        else          params[k] = v        end      }      return params    end  end  # This class implements a simple way of constructing the HTTP headers dynamically  # via a Hash syntax.  Think of it as a write-only Hash.  Refer to HttpResponse for  # information on how this is used.  #  # One consequence of this write-only nature is that you can write multiple headers  # by just doing them twice (which is sometimes needed in HTTP), but that the normal  # semantics for Hash (where doing an insert replaces) is not there.  class HeaderOut    attr_reader :out    attr_accessor :allowed_duplicates    def initialize(out)      @sent = {}      @allowed_duplicates = {"Set-Cookie" => true, "Set-Cookie2" => true,        "Warning" => true, "WWW-Authenticate" => true}      @out = out    end    # Simply writes "#{key}: #{value}" to an output buffer.    def[]=(key,value)      if not @sent.has_key?(key) or @allowed_duplicates.has_key?(key)        @sent[key] = true        @out.write(Const::HEADER_FORMAT % [key, value])      end    end  end  # Writes and controls your response to the client using the HTTP/1.1 specification.  # You use it by simply doing:  #  #  response.start(200) do |head,out|  #    head['Content-Type'] = 'text/plain'  #    out.write("hello\n")  #  end  #  # The parameter to start is the response code--which Mongrel will translate for you  # based on HTTP_STATUS_CODES.  The head parameter is how you write custom headers.  # The out parameter is where you write your body.  The default status code for   # HttpResponse.start is 200 so the above example is redundant.  #   # As you can see, it's just like using a Hash and as you do this it writes the proper  # header to the output on the fly.  You can even intermix specifying headers and   # writing content.  The HttpResponse class with write the things in the proper order  # once the HttpResponse.block is ended.  #  # You may also work the HttpResponse object directly using the various attributes available  # for the raw socket, body, header, and status codes.  If you do this you're on your own.  # A design decision was made to force the client to not pipeline requests.  HTTP/1.1   # pipelining really kills the performance due to how it has to be handled and how   # unclear the standard is.  To fix this the HttpResponse gives a "Connection: close"  # header which forces the client to close right away.  The bonus for this is that it  # gives a pretty nice speed boost to most clients since they can close their connection  # immediately.  #  # One additional caveat is that you don't have to specify the Content-length header  # as the HttpResponse will write this for you based on the out length.  class HttpResponse    attr_reader :socket    attr_reader :body    attr_writer :body    attr_reader :header    attr_reader :status    attr_writer :status    attr_reader :body_sent    attr_reader :header_sent    attr_reader :status_sent    def initialize(socket)      @socket = socket      @body = StringIO.new      @status = 404      @header = HeaderOut.new(StringIO.new)      @header[Const::DATE] = Time.now.httpdate      @body_sent = false

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?