public
Description: A very fast & simple Ruby web server
Homepage: http://code.macournoyer.com/thin/
Clone URL: git://github.com/macournoyer/thin.git
commit  dc607a67b9931ab6f1d9026e24e3311b49466c2f
tree    df2605e6c4560303e62713d7f01283301a58c6f6
parent  d019105fe46b732114e3f0bf2017ab294ac9e105
thin / lib / thin / request.rb
100644 108 lines (88 sloc) 3.091 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
require 'thin_parser'
require 'tempfile'
 
module Thin
  # Raised when an incoming request is not valid
  # and the server can not process it.
  class InvalidRequest < IOError; end
  
  # A request sent by the client to the server.
  class Request
    # Maximum request body size before it is moved out of memory
    # and into a tempfile for reading.
    MAX_BODY = 1024 * (80 + 32)
    BODY_TMPFILE = 'thin-body'.freeze
    
    # Freeze some HTTP header names
    SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze
    REMOTE_ADDR = 'REMOTE_ADDR'.freeze
    FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR'.freeze
    CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
    
    # Freeze some Rack header names
    RACK_INPUT = 'rack.input'.freeze
    RACK_VERSION = 'rack.version'.freeze
    RACK_ERRORS = 'rack.errors'.freeze
    RACK_MULTITHREAD = 'rack.multithread'.freeze
    RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
    RACK_RUN_ONCE = 'rack.run_once'.freeze
    
    # CGI-like request environment variables
    attr_reader :env
    
    # Unparsed data of the request
    attr_reader :data
    
    # Request body
    attr_reader :body
    
    def initialize
      @parser = HttpParser.new
      @data = ''
      @nparsed = 0
      @body = StringIO.new
      @env = {
        SERVER_SOFTWARE => SERVER,
        
        # Rack stuff
        RACK_INPUT => @body,
        
        RACK_VERSION => [0, 2],
        RACK_ERRORS => STDERR,
        
        RACK_MULTITHREAD => false,
        RACK_MULTIPROCESS => false,
        RACK_RUN_ONCE => false
      }
    end
    
    # Parse a chunk of data into the request environment
    # Raises a +InvalidRequest+ if invalid.
    # Returns +true+ if the parsing is complete.
    def parse(data)
      @data << data
      
      if @parser.finished? # Header finished, can only be some more body
        body << data
      else # Parse more header using the super parser
        @nparsed = @parser.execute(@env, @data, @nparsed)
 
        # Transfert to a tempfile if body is very big
        move_body_to_tempfile if @parser.finished? && content_length > MAX_BODY
      end
      
      
      if finished? # Check if header and body are complete
        @body.rewind
        true # Request is fully parsed
      else
        false # Not finished, need more data
      end
    end
    
    # +true+ if headers and body are finished parsing
    def finished?
      @parser.finished? && @body.size >= content_length
    end
    
    # Expected size of the body
    def content_length
      @env[CONTENT_LENGTH].to_i
    end
    
    # Close any resource used by the response
    def close
      @body.delete if @body.class == Tempfile
    end
    
    private
      def move_body_to_tempfile
        current_body = @body
        current_body.rewind
        @body = Tempfile.new(BODY_TMPFILE)
        @body.binmode
        @body << current_body.read
        @env[RACK_INPUT] = @body
      end
  end
end