thin 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of thin might be problematic. Click here for more details.

Files changed (91) hide show
  1. data/COPYING +18 -0
  2. data/README +32 -45
  3. data/Rakefile +66 -23
  4. data/bin/thin +76 -45
  5. data/doc/benchmarks.txt +64 -249
  6. data/doc/rdoc/created.rid +1 -1
  7. data/doc/rdoc/files/README.html +37 -100
  8. data/doc/rdoc/rdoc-style.css +5 -0
  9. data/example/config.ru +9 -0
  10. data/ext/thin_parser/common.rl +54 -0
  11. data/ext/thin_parser/ext_help.h +14 -0
  12. data/ext/thin_parser/extconf.rb +6 -0
  13. data/ext/thin_parser/parser.c +1199 -0
  14. data/ext/thin_parser/parser.h +49 -0
  15. data/ext/thin_parser/parser.rl +143 -0
  16. data/ext/thin_parser/thin.c +424 -0
  17. data/lib/rack/adapter/rails.rb +136 -0
  18. data/lib/rack/handler/thin.rb +13 -0
  19. data/lib/thin.rb +28 -12
  20. data/lib/thin/connection.rb +47 -0
  21. data/lib/thin/daemonizing.rb +5 -1
  22. data/lib/thin/headers.rb +3 -2
  23. data/lib/thin/logging.rb +6 -13
  24. data/lib/thin/request.rb +53 -133
  25. data/lib/thin/response.rb +21 -25
  26. data/lib/thin/server.rb +30 -94
  27. data/lib/thin/version.rb +2 -2
  28. data/lib/thin_parser.bundle +0 -0
  29. data/spec/daemonizing_spec.rb +94 -0
  30. data/spec/headers_spec.rb +35 -0
  31. data/spec/request_spec.rb +258 -0
  32. data/spec/response_spec.rb +40 -0
  33. data/spec/server_spec.rb +75 -0
  34. data/spec/spec_helper.rb +126 -0
  35. metadata +79 -99
  36. data/bin/thin_cluster +0 -53
  37. data/doc/rdoc/classes/Kernel.html +0 -182
  38. data/doc/rdoc/classes/Process.html +0 -175
  39. data/doc/rdoc/classes/Thin.html +0 -184
  40. data/doc/rdoc/classes/Thin/CGIWrapper.html +0 -438
  41. data/doc/rdoc/classes/Thin/Cluster.html +0 -392
  42. data/doc/rdoc/classes/Thin/Command.html +0 -221
  43. data/doc/rdoc/classes/Thin/CommandError.html +0 -154
  44. data/doc/rdoc/classes/Thin/Commands.html +0 -145
  45. data/doc/rdoc/classes/Thin/Daemonizable.html +0 -250
  46. data/doc/rdoc/classes/Thin/Daemonizable/ClassMethods.html +0 -203
  47. data/doc/rdoc/classes/Thin/DirHandler.html +0 -250
  48. data/doc/rdoc/classes/Thin/Handler.html +0 -195
  49. data/doc/rdoc/classes/Thin/Headers.html +0 -244
  50. data/doc/rdoc/classes/Thin/InvalidRequest.html +0 -150
  51. data/doc/rdoc/classes/Thin/Logging.html +0 -214
  52. data/doc/rdoc/classes/Thin/RailsHandler.html +0 -234
  53. data/doc/rdoc/classes/Thin/RailsServer.html +0 -175
  54. data/doc/rdoc/classes/Thin/Request.html +0 -379
  55. data/doc/rdoc/classes/Thin/Response.html +0 -311
  56. data/doc/rdoc/classes/Thin/Server.html +0 -381
  57. data/doc/rdoc/files/bin/thin.html +0 -188
  58. data/doc/rdoc/files/bin/thin_cluster.html +0 -175
  59. data/doc/rdoc/files/lib/thin/cgi_rb.html +0 -263
  60. data/doc/rdoc/files/lib/thin/cluster_rb.html +0 -263
  61. data/doc/rdoc/files/lib/thin/command_rb.html +0 -263
  62. data/doc/rdoc/files/lib/thin/consts_rb.html +0 -263
  63. data/doc/rdoc/files/lib/thin/daemonizing_rb.html +0 -263
  64. data/doc/rdoc/files/lib/thin/handler_rb.html +0 -263
  65. data/doc/rdoc/files/lib/thin/headers_rb.html +0 -263
  66. data/doc/rdoc/files/lib/thin/logging_rb.html +0 -263
  67. data/doc/rdoc/files/lib/thin/mime_types_rb.html +0 -263
  68. data/doc/rdoc/files/lib/thin/rails_rb.html +0 -263
  69. data/doc/rdoc/files/lib/thin/recipes_rb.html +0 -171
  70. data/doc/rdoc/files/lib/thin/request_rb.html +0 -171
  71. data/doc/rdoc/files/lib/thin/response_rb.html +0 -171
  72. data/doc/rdoc/files/lib/thin/server_rb.html +0 -171
  73. data/doc/rdoc/files/lib/thin/statuses_rb.html +0 -171
  74. data/doc/rdoc/files/lib/thin/version_rb.html +0 -171
  75. data/lib/thin/cgi.rb +0 -159
  76. data/lib/thin/cluster.rb +0 -147
  77. data/lib/thin/command.rb +0 -49
  78. data/lib/thin/commands/cluster/base.rb +0 -24
  79. data/lib/thin/commands/cluster/config.rb +0 -36
  80. data/lib/thin/commands/cluster/restart.rb +0 -35
  81. data/lib/thin/commands/cluster/start.rb +0 -40
  82. data/lib/thin/commands/cluster/stop.rb +0 -28
  83. data/lib/thin/commands/server/base.rb +0 -7
  84. data/lib/thin/commands/server/start.rb +0 -33
  85. data/lib/thin/commands/server/stop.rb +0 -29
  86. data/lib/thin/consts.rb +0 -37
  87. data/lib/thin/handler.rb +0 -57
  88. data/lib/thin/mime_types.rb +0 -619
  89. data/lib/thin/rails.rb +0 -44
  90. data/lib/thin/recipes.rb +0 -36
  91. data/lib/transat/parser.rb +0 -247
@@ -0,0 +1,136 @@
1
+ # This as been submitted to Rack as a patch, tested and everything.
2
+ # Bug Christian Neukirchen at chneukirchen@gmail.com to apply the patch!
3
+
4
+ require 'cgi'
5
+
6
+ # Adapter to run a Rails app with any supported Rack handler.
7
+ # By default it will try to load the Rails application in the
8
+ # current directory in the development environment.
9
+ # Options:
10
+ # root: Root directory of the Rails app
11
+ # env: Rails environment to run in (development, production or test)
12
+ # Based on http://fuzed.rubyforge.org/ Rails adapter
13
+ module Rack
14
+ module Adapter
15
+ class Rails
16
+ def initialize(options={})
17
+ @root = options[:root] || Dir.pwd
18
+ @env = options[:env] || 'development'
19
+
20
+ load_application
21
+
22
+ @file_server = Rack::File.new(::File.join(RAILS_ROOT, "public"))
23
+ end
24
+
25
+ def load_application
26
+ ENV['RAILS_ENV'] = @env
27
+
28
+ require "#{@root}/config/environment"
29
+ require 'dispatcher'
30
+ end
31
+
32
+ # TODO refactor this in File#can_serve?(path) ??
33
+ def file?(path)
34
+ full_path = ::File.join(@file_server.root, Utils.unescape(path))
35
+ ::File.file?(full_path) && ::File.readable?(full_path)
36
+ end
37
+
38
+ def call(env)
39
+ # Serve the file if it's there
40
+ return @file_server.call(env) if file?(env['PATH_INFO'])
41
+
42
+ request = Request.new(env)
43
+ response = Response.new
44
+
45
+ session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
46
+ cgi = CGIWrapper.new(request, response)
47
+
48
+ Dispatcher.dispatch(cgi, session_options, response)
49
+
50
+ response.finish
51
+ end
52
+
53
+ protected
54
+
55
+ class CGIWrapper < ::CGI
56
+ def initialize(request, response, *args)
57
+ @request = request
58
+ @response = response
59
+ @args = *args
60
+ @input = request.body
61
+
62
+ super *args
63
+ end
64
+
65
+ def header(options = "text/html")
66
+ if options.is_a?(String)
67
+ @response['Content-Type'] = options unless @response['Content-Type']
68
+ else
69
+ @response['Content-Length'] = options.delete('Content-Length').to_s if options['Content-Length']
70
+
71
+ @response['Content-Type'] = options.delete('type') || "text/html"
72
+ @response['Content-Type'] += "; charset=" + options.delete('charset') if options['charset']
73
+
74
+ @response['Content-Language'] = options.delete('language') if options['language']
75
+ @response['Expires'] = options.delete('expires') if options['expires']
76
+
77
+ @response.status = options.delete('Status') if options['Status']
78
+
79
+ options.each { |k,v| @response[k] = v }
80
+
81
+ # Convert 'cookie' header to 'Set-Cookie' headers.
82
+ # According to http://www.faqs.org/rfcs/rfc2109.html:
83
+ # the Set-Cookie response header comprises the token
84
+ # Set-Cookie:, followed by a comma-separated list of
85
+ # one or more cookies.
86
+ if cookie = @response.header.delete('Cookie')
87
+ cookies = case cookie
88
+ when Array then cookie.collect { |c| c.to_s }.join(', ')
89
+ when Hash then cookie.collect { |_, c| c.to_s }.join(', ')
90
+ else cookie.to_s
91
+ end
92
+
93
+ cookies << ', ' + @output_cookies.each { |c| c.to_s }.join(', ') if @output_cookies
94
+
95
+ @response['Set-Cookie'] = cookies
96
+ end
97
+ end
98
+
99
+ ""
100
+ end
101
+
102
+ def params
103
+ @params ||= @request.params
104
+ end
105
+
106
+ def cookies
107
+ @request.cookies
108
+ end
109
+
110
+ def query_string
111
+ @request.query_string
112
+ end
113
+
114
+ # Used to wrap the normal args variable used inside CGI.
115
+ def args
116
+ @args
117
+ end
118
+
119
+ # Used to wrap the normal env_table variable used inside CGI.
120
+ def env_table
121
+ @request.env
122
+ end
123
+
124
+ # Used to wrap the normal stdinput variable used inside CGI.
125
+ def stdinput
126
+ @input
127
+ end
128
+
129
+ def stdoutput
130
+ STDERR.puts "stdoutput should not be used."
131
+ @response.body
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,13 @@
1
+ module Rack
2
+ module Handler
3
+ class Thin
4
+ def self.run(app, options={})
5
+ server = ::Thin::Server.new(options[:Host] || '0.0.0.0',
6
+ options[:Port] || 8080,
7
+ app)
8
+ yield server if block_given?
9
+ server.start!
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,19 +1,35 @@
1
- $:.unshift File.dirname(__FILE__)
1
+ $: << File.expand_path(File.dirname(__FILE__))
2
2
 
3
3
  require 'fileutils'
4
4
  require 'timeout'
5
5
  require 'stringio'
6
6
 
7
+ require 'rubygems'
8
+ require 'eventmachine'
9
+
7
10
  require 'thin/version'
8
- require 'thin/consts'
9
11
  require 'thin/statuses'
10
- require 'thin/mime_types'
11
- require 'thin/logging'
12
- require 'thin/daemonizing'
13
- require 'thin/server'
14
- require 'thin/request'
15
- require 'thin/headers'
16
- require 'thin/response'
17
- require 'thin/handler'
18
- require 'thin/cgi'
19
- require 'thin/rails'
12
+
13
+ module Thin
14
+ NAME = 'thin'.freeze
15
+ SERVER = "#{NAME} #{VERSION::STRING}".freeze
16
+
17
+ autoload :Connection, 'thin/connection'
18
+ autoload :Daemonizable, 'thin/daemonizing'
19
+ autoload :Logging, 'thin/logging'
20
+ autoload :Headers, 'thin/headers'
21
+ autoload :Request, 'thin/request'
22
+ autoload :Response, 'thin/response'
23
+ autoload :Server, 'thin/server'
24
+ end
25
+
26
+ require 'rack'
27
+
28
+ module Rack
29
+ module Handler
30
+ autoload :Thin, 'rack/handler/thin'
31
+ end
32
+ module Adapter
33
+ autoload :Rails, 'rack/adapter/rails'
34
+ end
35
+ end
@@ -0,0 +1,47 @@
1
+ require 'socket'
2
+
3
+ module Thin
4
+ class Connection < EventMachine::Connection
5
+ include Logging
6
+
7
+ attr_accessor :app
8
+
9
+ def post_init
10
+ @request = Request.new
11
+ @response = Response.new
12
+ end
13
+
14
+ def receive_data(data)
15
+ process if @request.parse(data)
16
+ rescue InvalidRequest => e
17
+ log "Invalid request"
18
+ log_error e
19
+ trace { data }
20
+ close_connection
21
+ end
22
+
23
+ def process
24
+ env = @request.env
25
+
26
+ # Add client info to the request env
27
+ env[Request::REMOTE_ADDR] = env[Request::FORWARDED_FOR] || Socket.unpack_sockaddr_in(get_peername)[1]
28
+
29
+ # Process the request
30
+ @response.status, @response.headers, @response.body = @app.call(env)
31
+
32
+ # Send the response
33
+ send_data @response.head
34
+ @response.body.rewind
35
+ send_data @response.body.read
36
+
37
+ close_connection_after_writing
38
+
39
+ rescue Object => e
40
+ log "Unexpected error while processing request: #{e.message}"
41
+ log_error e
42
+ close_connection rescue nil
43
+ ensure
44
+ @response.close rescue nil
45
+ end
46
+ end
47
+ end
@@ -42,6 +42,10 @@ module Thin
42
42
  base.extend ClassMethods
43
43
  end
44
44
 
45
+ def pid
46
+ File.exist?(pid_file) ? open(pid_file).read : nil
47
+ end
48
+
45
49
  # Turns the current script into a daemon process that detaches from the console.
46
50
  def daemonize
47
51
  raise ArgumentError, 'You must specify a pid_file to deamonize' unless @pid_file
@@ -84,7 +88,7 @@ module Thin
84
88
  module ClassMethods
85
89
  # Kill the process which PID is stored in +pid_file+.
86
90
  def kill(pid_file, timeout=60)
87
- if File.exist?(pid_file) && pid = open(pid_file).read
91
+ if pid = open(pid_file).read
88
92
  pid = pid.to_i
89
93
  print "Sending INT signal to process #{pid} ... "
90
94
  begin
@@ -1,7 +1,8 @@
1
1
  module Thin
2
2
  # Acts like a Hash, but allows duplicated keys
3
3
  class Headers
4
- @@allowed_duplicates = %w(Set-Cookie Set-Cookie2 Warning WWW-Authenticate)
4
+ HEADER_FORMAT = "%s: %s\r\n".freeze
5
+ ALLOWED_DUPLICATES = %w(Set-Cookie Set-Cookie2 Warning WWW-Authenticate).freeze
5
6
 
6
7
  def initialize
7
8
  @sent = {}
@@ -9,7 +10,7 @@ module Thin
9
10
  end
10
11
 
11
12
  def []=(key, value)
12
- if @sent.has_key?(key) && !@@allowed_duplicates.include?(key)
13
+ if @sent.has_key?(key) && !ALLOWED_DUPLICATES.include?(key)
13
14
  # If we don't allow duplicate for that field
14
15
  # we overwrite the one that is already there
15
16
  @items.assoc(key)[1] = value
@@ -1,10 +1,7 @@
1
1
  module Thin
2
2
  # To be included into classes to allow some basic logging
3
- # that can be silented (+silent+) or made more verbose (+trace+).
3
+ # that can be silented (+silent+) or made more verbose ($DEBUG=true).
4
4
  module Logging
5
- # Output extra info about the request, response, errors and stuff like that.
6
- attr_writer :trace
7
-
8
5
  # Don't output any message if +true+.
9
6
  attr_accessor :silent
10
7
 
@@ -14,17 +11,13 @@ module Thin
14
11
  puts msg unless @silent
15
12
  end
16
13
 
17
- # Log a message to the console (no line feed)
18
- def logc(msg)
19
- unless @silent
20
- print msg
21
- STDOUT.flush # Make sure the msg is shown right away
22
- end
23
- end
24
-
25
14
  # Log a message to the console if tracing is activated
26
15
  def trace(msg=nil)
27
- puts msg || yield if @trace && !@silent
16
+ puts msg || yield if $DEBUG && !@silent
17
+ end
18
+
19
+ def log_error(e)
20
+ trace { "#{e}\n\t" + e.backtrace.join("\n\t") }
28
21
  end
29
22
  end
30
23
  end
@@ -1,151 +1,71 @@
1
+ require 'thin_parser'
2
+
1
3
  module Thin
2
4
  # Raised when an incoming request is not valid
3
5
  # and the server can not process it.
4
- class InvalidRequest < StandardError; end
6
+ class InvalidRequest < IOError; end
5
7
 
6
- # A request made to the server.
7
8
  class Request
8
- # HTTP headers that should not have a +HTTP_+ prefixed to the CGI variable name
9
- HTTP_LESS_HEADERS = %w(Content-Length Content-Type).freeze
10
- # Methods that might contain a body.
11
- BODYFUL_METHODS = %w(POST PUT).freeze
9
+ MAX_HEADER = 1024 * (80 + 32)
10
+ MAX_HEADER_MSG = 'Header longer than allowed'.freeze
12
11
 
13
- # We control max length of different part of the request
14
- # to prevent attack and resource overflow.
15
- MAX_FIELD_NAME_LENGTH = 256
16
- MAX_FIELD_VALUE_LENGTH = 80 * 1024
17
- MAX_REQUEST_URI_LENGTH = 1024 * 12
18
- MAX_FRAGMENT_LENGTH = 1024
19
- MAX_REQUEST_PATH_LENGTH = 1024
20
- MAX_QUERY_STRING_LENGTH = 1024 * 10
21
- MAX_HEADER_LENGTH = 1024 * (80 + 32)
22
-
23
- attr_reader :body, :params, :verb, :path
12
+ SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze
13
+ REMOTE_ADDR = 'REMOTE_ADDR'.freeze
14
+ FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR'.freeze
24
15
 
25
- # For debugging and trace.
26
- # When +trace+ is set to true, +raw+ will be populated with
27
- # the raw request.
28
- attr_accessor :trace, :raw
29
-
30
- def initialize
31
- @params = {
32
- 'GATEWAY_INTERFACE' => CGI_VERSION,
33
- 'HTTP_VERSION' => HTTP_VERSION,
34
- 'SERVER_PROTOCOL' => HTTP_VERSION
35
- }
36
- @body = StringIO.new
37
- @raw = ''
38
- @trace = false
39
- end
40
-
41
- # Parse the headers and body from the +content+ buffer.
42
- def parse!(content)
43
- parse_headers! content
44
- parse_body! content if BODYFUL_METHODS.include?(verb)
45
- rescue InvalidRequest => e
46
- raise
47
- rescue Object => e
48
- raise InvalidRequest, e.message
49
- end
50
-
51
- # Parse the request headers from the socket into CGI like variables.
52
- # Parse the request according to http://www.w3.org/Protocols/rfc2616/rfc2616.html.
53
- # Parse env variables according to http://www.ietf.org/rfc/rfc3875.
54
- # Raises an InvalidRequest error when the request is not valid, because:
55
- # * no valid request line
56
- # * uri, path or header is too long
57
- def parse_headers!(content)
58
- if matches = readline(content).match(/^([A-Z]+) (.*?)(?:#(.*))? HTTP/)
59
- @verb, uri, fragment = matches[1,3]
60
- else
61
- raise InvalidRequest, 'No valid request line found'
62
- end
63
-
64
- raise InvalidRequest, 'No method specified' unless @verb
65
- raise InvalidRequest, 'No URI specified' unless uri
66
-
67
- # Validate various length for security
68
- raise InvalidRequest, 'URI too long' if uri.size > MAX_REQUEST_URI_LENGTH
69
- raise InvalidRequest, 'Fragment too long' if fragment && fragment.size > MAX_FRAGMENT_LENGTH
16
+ CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
70
17
 
71
- if matches = uri.match(/^(.*?)(?:\?(.*))?$/)
72
- @path, query_string = matches[1,2]
73
- else
74
- raise InvalidRequest, "No valid path found in #{uri}"
75
- end
18
+ RACK_INPUT = 'rack.input'.freeze
19
+ RACK_VERSION = 'rack.version'.freeze
20
+ RACK_ERRORS = 'rack.errors'.freeze
21
+ RACK_MULTITHREAD = 'rack.multithread'.freeze
22
+ RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
23
+ RACK_RUN_ONCE = 'rack.run_once'.freeze
76
24
 
77
- raise InvalidRequest, 'Request path too long' if @path.size > MAX_REQUEST_PATH_LENGTH
78
- raise InvalidRequest, 'Query string path too long' if query_string && query_string.size > MAX_QUERY_STRING_LENGTH
79
-
80
- @params['REQUEST_URI'] = uri
81
- @params['FRAGMENT'] = fragment if fragment
82
- @params['REQUEST_PATH'] =
83
- @params['PATH_INFO'] = @path
84
- @params['SCRIPT_NAME'] = '/'
85
- @params['REQUEST_METHOD'] = @verb
86
- @params['QUERY_STRING'] = query_string if query_string
25
+ attr_reader :env, :data, :body
87
26
 
88
- # Parse all headers from 'Something-Weird' into @params['HTTP_SOMETHING_WEIRD']
89
- headers_size = 0
90
- until content.eof?
91
- line = readline(content)
92
- headers_size += line.size
27
+ def initialize
28
+ @parser = HttpParser.new
29
+ @data = ''
30
+ @nparsed = 0
31
+ @body = StringIO.new
32
+ @env = {
33
+ SERVER_SOFTWARE => SERVER,
93
34
 
94
- break if ?\r == line[0] # Reached the end of the headers
95
- if matches = line.match(/^([\w\-]+): (.*)$/)
96
- name, value = matches[1,2]
97
- raise InvalidRequest, 'Header name too long' if name.size > MAX_FIELD_NAME_LENGTH
98
- raise InvalidRequest, 'Header value too long' if value.size > MAX_FIELD_VALUE_LENGTH
99
- # Transform headers into a HTTP_NAME => value hash
100
- prefix = HTTP_LESS_HEADERS.include?(name) ? '' : 'HTTP_'
101
- params["#{prefix}#{name.upcase.gsub('-', '_')}"] = value.chomp
102
- else
103
- raise InvalidRequest, "Expected header : #{line}"
104
- end
105
- end
106
-
107
- raise InvalidRequest, 'Headers too long' if headers_size > MAX_HEADER_LENGTH
108
-
109
- @params['SERVER_NAME'] = @params['HTTP_HOST'].split(':')[0] if @params['HTTP_HOST']
110
- end
111
-
112
- # Parse the request body by chunks.
113
- # We assume the Content-Length is valid and is the actual size of the body.
114
- # This is garanteed when used behind a proxy server like Nginx:
115
- # Note that when using the HTTP Proxy Module (or even when using FastCGI), the entire client
116
- # request will be buffered in nginx before being passed on to the backend proxied servers.
117
- # As a result, upload progress meters will not function correctly if they work by measuring
118
- # the data received by the backend servers.
119
- # - http://wiki.codemongers.com/NginxHttpProxyModule
120
- # On Apache w/ mod_proxy, you need to install mod_accel : http://sysoev.ru/en/
121
- def parse_body!(content)
122
- length = content_length
123
- while @body.size < length
124
- chunk = content.readpartial(CHUNK_SIZE)
125
- break unless chunk && chunk.size > 0
126
- @body << chunk
127
- end
128
-
129
- @body.rewind
35
+ # Rack stuff
36
+ RACK_INPUT => @body,
37
+
38
+ RACK_VERSION => [0, 1],
39
+ RACK_ERRORS => STDERR,
40
+
41
+ RACK_MULTITHREAD => false,
42
+ RACK_MULTIPROCESS => false,
43
+ RACK_RUN_ONCE => false
44
+ }
130
45
  end
131
46
 
132
- def close
133
- @body.close
47
+ def parse(data)
48
+ @data << data
49
+
50
+ if @parser.finished? # Header finished, can only be some more body
51
+ body << data
52
+ elsif @data.size > MAX_HEADER
53
+ raise InvalidRequest, MAX_HEADER_MSG
54
+ else # Parse more header
55
+ @nparsed = @parser.execute(@env, @data, @nparsed)
56
+ end
57
+
58
+ # Check if header and body are complete
59
+ if @parser.finished? && body.size >= content_length
60
+ body.rewind
61
+ return true
62
+ end
63
+
64
+ false # Not finished, need more data
134
65
  end
135
66
 
136
67
  def content_length
137
- @params['CONTENT_LENGTH'].to_i
68
+ @env[CONTENT_LENGTH].to_i
138
69
  end
139
-
140
- def to_s
141
- "#{@params['REQUEST_METHOD']} #{@params['REQUEST_URI']}"
142
- end
143
-
144
- private
145
- def readline(io)
146
- out = io.gets(LF)
147
- @raw << out if @trace # Build a gigantic string to later print trace for the request
148
- out
149
- end
150
- end
70
+ end
151
71
  end