public
Description: A very fast & simple Ruby web server
Homepage: http://code.macournoyer.com/thin/
Clone URL: git://github.com/macournoyer/thin.git
Move request body to a Tempfile if too big (> 112 MB)
Remove useless check for max header size in Request (already done in the 
parser)
macournoyer (author)
Sat Jan 19 20:34:52 -0800 2008
commit  6aa930c5f5f05f200d2861ced10bb1e3f33149b4
tree    925628206ed8056fe638efa2e324d41f54c2bf7f
parent  766890e0b31548f074758f87f22e84b5239c1aa7
...
 
 
 
 
1
2
3
...
1
2
3
4
5
6
7
0
@@ -1,3 +1,7 @@
0
+== 0.5.5 Pony release
0
+ * Move request body to a Tempfile if too big (> 112 MB)
0
+ * Remove useless check for max header size in Request (already done in the parser)
0
+
0
 == 0.5.4 Flying Mustard release
0
  * Don't read the full body, use direct streaming when sending response.
0
    See: Response#each
...
42
43
44
 
45
46
47
...
42
43
44
45
46
47
48
0
@@ -42,6 +42,7 @@ module Thin
0
       log_error e
0
       close_connection rescue nil
0
     ensure
0
+ @request.close rescue nil
0
       @response.close rescue nil
0
     end
0
   end
...
1
 
2
3
4
...
7
8
9
10
11
 
 
 
 
12
 
13
14
15
16
17
18
 
19
20
21
...
23
24
25
26
 
 
 
 
 
 
 
 
27
28
29
...
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
...
1
2
3
4
5
...
8
9
10
 
 
11
12
13
14
15
16
17
18
19
 
20
21
22
23
24
25
...
27
28
29
 
30
31
32
33
34
35
36
37
38
39
40
...
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
0
@@ -1,4 +1,5 @@
0
 require 'thin_parser'
0
+require 'tempfile'
0
 
0
 module Thin
0
   # Raised when an incoming request is not valid
0
@@ -7,15 +8,18 @@ module Thin
0
   
0
   # A request sent by the client to the server.
0
   class Request
0
- MAX_HEADER = 1024 * (80 + 32)
0
- MAX_HEADER_MSG = 'Header longer than allowed'.freeze
0
+ # Maximum request body size before it is moved out of memory
0
+ # and into a tempfile for reading.
0
+ MAX_BODY = 1024 * (80 + 32)
0
+ BODY_TMPFILE = 'thin-body'.freeze
0
 
0
+ # Freeze some HTTP header names
0
     SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze
0
     REMOTE_ADDR = 'REMOTE_ADDR'.freeze
0
     FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR'.freeze
0
-
0
     CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
0
 
0
+ # Freeze some Rack header names
0
     RACK_INPUT = 'rack.input'.freeze
0
     RACK_VERSION = 'rack.version'.freeze
0
     RACK_ERRORS = 'rack.errors'.freeze
0
@@ -23,7 +27,14 @@ module Thin
0
     RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
0
     RACK_RUN_ONCE = 'rack.run_once'.freeze
0
     
0
- attr_reader :env, :data, :body
0
+ # CGI-like request environment variables
0
+ attr_reader :env
0
+
0
+ # Unparsed data of the request
0
+ attr_reader :data
0
+
0
+ # Request body
0
+ attr_reader :body
0
     
0
     def initialize
0
       @parser = HttpParser.new
0
@@ -45,28 +56,45 @@ module Thin
0
       }
0
     end
0
     
0
+ # Parse a chunk of data into the request environment
0
+ # Raises a +InvalidRequest+ if invalid.
0
+ # Returns +true+ if the parsing is complete.
0
     def parse(data)
0
       @data << data
0
       
0
       if @parser.finished? # Header finished, can only be some more body
0
         body << data
0
- elsif @data.size > MAX_HEADER # Oho! very big header, must be a mean person
0
- raise InvalidRequest, MAX_HEADER_MSG
0
       else # Parse more header using the super parser
0
        @nparsed = @parser.execute(@env, @data, @nparsed)
0
+ # Transfert to a tempfile if body is very big
0
+ move_body_to_tempfile if @parser.finished? && content_length > MAX_BODY
0
       end
0
       
0
       # Check if header and body are complete
0
- if @parser.finished? && body.size >= content_length
0
- body.rewind
0
+ if @parser.finished? && @body.size >= content_length
0
+ @body.rewind
0
      return true # Request is fully parsed
0
      end
0
       
0
       false # Not finished, need more data
0
     end
0
     
0
+ # Expected size of the body
0
     def content_length
0
       @env[CONTENT_LENGTH].to_i
0
     end
0
+
0
+ def close
0
+ @body.close if @body === Tempfile
0
+ end
0
+
0
+ private
0
+ def move_body_to_tempfile
0
+ current_body = @body
0
+ @body = Tempfile.new(BODY_TMPFILE)
0
+ @body.binmode
0
+ @body << current_body unless current_body.size.zero?
0
+ @env[RACK_INPUT] = @body
0
+ end
0
   end
0
 end
0
\ No newline at end of file
...
119
120
121
 
122
123
124
...
188
189
190
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
192
193
...
119
120
121
122
123
124
125
...
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
0
@@ -119,6 +119,7 @@ EOS
0
 
0
     request.body.rewind
0
     request.body.read.should == 'name=marc&email=macournoyer@gmail.com'
0
+ request.body.class.should == StringIO
0
     
0
     request.should validate_with_lint
0
   end
0
@@ -188,6 +189,26 @@ EOS
0
     request.should validate_with_lint
0
   end
0
   
0
+ it "should move body to tempfile when too big" do
0
+ body = 'X' * (Request::MAX_BODY + 1)
0
+
0
+ request = R(<<-EOS.chomp, true)
0
+POST /postit HTTP/1.1
0
+Host: localhost:3000
0
+Content-Type: text/html
0
+Content-Length: #{body.size}
0
+
0
+#{body}
0
+EOS
0
+
0
+ request.body.class.should == Tempfile
0
+ end
0
+
0
+ it "should raise error when header is too big" do
0
+ big_headers = "X-Test: X\r\n" * (1024 * (80 + 32))
0
+ proc { R("GET / HTTP/1.1\r\n#{big_headers}\r\n") }.should raise_error(InvalidRequest)
0
+ end
0
+
0
   it "should be faster then #{max_parsing_time = 0.2} ms" do
0
     body = <<-EOS.chomp
0
 POST /postit HTTP/1.1

Comments

    No one has commented yet.