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.
- data/COPYING +18 -0
- data/README +32 -45
- data/Rakefile +66 -23
- data/bin/thin +76 -45
- data/doc/benchmarks.txt +64 -249
- data/doc/rdoc/created.rid +1 -1
- data/doc/rdoc/files/README.html +37 -100
- data/doc/rdoc/rdoc-style.css +5 -0
- data/example/config.ru +9 -0
- data/ext/thin_parser/common.rl +54 -0
- data/ext/thin_parser/ext_help.h +14 -0
- data/ext/thin_parser/extconf.rb +6 -0
- data/ext/thin_parser/parser.c +1199 -0
- data/ext/thin_parser/parser.h +49 -0
- data/ext/thin_parser/parser.rl +143 -0
- data/ext/thin_parser/thin.c +424 -0
- data/lib/rack/adapter/rails.rb +136 -0
- data/lib/rack/handler/thin.rb +13 -0
- data/lib/thin.rb +28 -12
- data/lib/thin/connection.rb +47 -0
- data/lib/thin/daemonizing.rb +5 -1
- data/lib/thin/headers.rb +3 -2
- data/lib/thin/logging.rb +6 -13
- data/lib/thin/request.rb +53 -133
- data/lib/thin/response.rb +21 -25
- data/lib/thin/server.rb +30 -94
- data/lib/thin/version.rb +2 -2
- data/lib/thin_parser.bundle +0 -0
- data/spec/daemonizing_spec.rb +94 -0
- data/spec/headers_spec.rb +35 -0
- data/spec/request_spec.rb +258 -0
- data/spec/response_spec.rb +40 -0
- data/spec/server_spec.rb +75 -0
- data/spec/spec_helper.rb +126 -0
- metadata +79 -99
- data/bin/thin_cluster +0 -53
- data/doc/rdoc/classes/Kernel.html +0 -182
- data/doc/rdoc/classes/Process.html +0 -175
- data/doc/rdoc/classes/Thin.html +0 -184
- data/doc/rdoc/classes/Thin/CGIWrapper.html +0 -438
- data/doc/rdoc/classes/Thin/Cluster.html +0 -392
- data/doc/rdoc/classes/Thin/Command.html +0 -221
- data/doc/rdoc/classes/Thin/CommandError.html +0 -154
- data/doc/rdoc/classes/Thin/Commands.html +0 -145
- data/doc/rdoc/classes/Thin/Daemonizable.html +0 -250
- data/doc/rdoc/classes/Thin/Daemonizable/ClassMethods.html +0 -203
- data/doc/rdoc/classes/Thin/DirHandler.html +0 -250
- data/doc/rdoc/classes/Thin/Handler.html +0 -195
- data/doc/rdoc/classes/Thin/Headers.html +0 -244
- data/doc/rdoc/classes/Thin/InvalidRequest.html +0 -150
- data/doc/rdoc/classes/Thin/Logging.html +0 -214
- data/doc/rdoc/classes/Thin/RailsHandler.html +0 -234
- data/doc/rdoc/classes/Thin/RailsServer.html +0 -175
- data/doc/rdoc/classes/Thin/Request.html +0 -379
- data/doc/rdoc/classes/Thin/Response.html +0 -311
- data/doc/rdoc/classes/Thin/Server.html +0 -381
- data/doc/rdoc/files/bin/thin.html +0 -188
- data/doc/rdoc/files/bin/thin_cluster.html +0 -175
- data/doc/rdoc/files/lib/thin/cgi_rb.html +0 -263
- data/doc/rdoc/files/lib/thin/cluster_rb.html +0 -263
- data/doc/rdoc/files/lib/thin/command_rb.html +0 -263
- data/doc/rdoc/files/lib/thin/consts_rb.html +0 -263
- data/doc/rdoc/files/lib/thin/daemonizing_rb.html +0 -263
- data/doc/rdoc/files/lib/thin/handler_rb.html +0 -263
- data/doc/rdoc/files/lib/thin/headers_rb.html +0 -263
- data/doc/rdoc/files/lib/thin/logging_rb.html +0 -263
- data/doc/rdoc/files/lib/thin/mime_types_rb.html +0 -263
- data/doc/rdoc/files/lib/thin/rails_rb.html +0 -263
- data/doc/rdoc/files/lib/thin/recipes_rb.html +0 -171
- data/doc/rdoc/files/lib/thin/request_rb.html +0 -171
- data/doc/rdoc/files/lib/thin/response_rb.html +0 -171
- data/doc/rdoc/files/lib/thin/server_rb.html +0 -171
- data/doc/rdoc/files/lib/thin/statuses_rb.html +0 -171
- data/doc/rdoc/files/lib/thin/version_rb.html +0 -171
- data/lib/thin/cgi.rb +0 -159
- data/lib/thin/cluster.rb +0 -147
- data/lib/thin/command.rb +0 -49
- data/lib/thin/commands/cluster/base.rb +0 -24
- data/lib/thin/commands/cluster/config.rb +0 -36
- data/lib/thin/commands/cluster/restart.rb +0 -35
- data/lib/thin/commands/cluster/start.rb +0 -40
- data/lib/thin/commands/cluster/stop.rb +0 -28
- data/lib/thin/commands/server/base.rb +0 -7
- data/lib/thin/commands/server/start.rb +0 -33
- data/lib/thin/commands/server/stop.rb +0 -29
- data/lib/thin/consts.rb +0 -37
- data/lib/thin/handler.rb +0 -57
- data/lib/thin/mime_types.rb +0 -619
- data/lib/thin/rails.rb +0 -44
- data/lib/thin/recipes.rb +0 -36
- 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
|
data/lib/thin.rb
CHANGED
@@ -1,19 +1,35 @@
|
|
1
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
data/lib/thin/daemonizing.rb
CHANGED
@@ -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
|
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
|
data/lib/thin/headers.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
module Thin
|
2
2
|
# Acts like a Hash, but allows duplicated keys
|
3
3
|
class Headers
|
4
|
-
|
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) &&
|
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
|
data/lib/thin/logging.rb
CHANGED
@@ -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 (
|
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
|
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
|
data/lib/thin/request.rb
CHANGED
@@ -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 <
|
6
|
+
class InvalidRequest < IOError; end
|
5
7
|
|
6
|
-
# A request made to the server.
|
7
8
|
class Request
|
8
|
-
|
9
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
133
|
-
@
|
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
|
-
@
|
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
|