puma 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- data/.gemtest +0 -0
- data/COPYING +55 -0
- data/History.txt +69 -0
- data/LICENSE +26 -0
- data/Manifest.txt +57 -0
- data/README.md +60 -0
- data/Rakefile +10 -0
- data/TODO +5 -0
- data/bin/puma +15 -0
- data/examples/builder.rb +29 -0
- data/examples/camping/README +3 -0
- data/examples/camping/blog.rb +294 -0
- data/examples/camping/tepee.rb +149 -0
- data/examples/httpd.conf +474 -0
- data/examples/mime.yaml +3 -0
- data/examples/mongrel.conf +9 -0
- data/examples/monitrc +57 -0
- data/examples/random_thrash.rb +19 -0
- data/examples/simpletest.rb +52 -0
- data/examples/webrick_compare.rb +20 -0
- data/ext/puma_http11/Http11Service.java +13 -0
- data/ext/puma_http11/ext_help.h +15 -0
- data/ext/puma_http11/extconf.rb +5 -0
- data/ext/puma_http11/http11_parser.c +1225 -0
- data/ext/puma_http11/http11_parser.h +63 -0
- data/ext/puma_http11/http11_parser.java.rl +159 -0
- data/ext/puma_http11/http11_parser.rl +146 -0
- data/ext/puma_http11/http11_parser_common.rl +54 -0
- data/ext/puma_http11/org/jruby/mongrel/Http11.java +241 -0
- data/ext/puma_http11/org/jruby/mongrel/Http11Parser.java +486 -0
- data/ext/puma_http11/puma_http11.c +482 -0
- data/lib/puma.rb +18 -0
- data/lib/puma/cli.rb +131 -0
- data/lib/puma/const.rb +132 -0
- data/lib/puma/events.rb +36 -0
- data/lib/puma/gems.rb +20 -0
- data/lib/puma/mime_types.yml +616 -0
- data/lib/puma/server.rb +419 -0
- data/lib/puma/thread_pool.rb +95 -0
- data/lib/puma/utils.rb +44 -0
- data/lib/rack/handler/puma.rb +33 -0
- data/puma.gemspec +37 -0
- data/tasks/gem.rake +22 -0
- data/tasks/java.rake +12 -0
- data/tasks/native.rake +25 -0
- data/tasks/ragel.rake +20 -0
- data/test/lobster.ru +4 -0
- data/test/mime.yaml +3 -0
- data/test/test_http10.rb +27 -0
- data/test/test_http11.rb +151 -0
- data/test/test_persistent.rb +159 -0
- data/test/test_rack_handler.rb +10 -0
- data/test/test_rack_server.rb +107 -0
- data/test/test_thread_pool.rb +102 -0
- data/test/test_unix_socket.rb +34 -0
- data/test/test_ws.rb +97 -0
- data/test/testhelp.rb +41 -0
- data/tools/trickletest.rb +45 -0
- metadata +165 -0
data/lib/puma/utils.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Puma
|
2
|
+
module Utils
|
3
|
+
# Performs URI escaping so that you can construct proper
|
4
|
+
# query strings faster. Use this rather than the cgi.rb
|
5
|
+
# version since it's faster. (Stolen from Camping).
|
6
|
+
def self.escape(s)
|
7
|
+
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
8
|
+
'%'+$1.unpack('H2'*$1.size).join('%').upcase
|
9
|
+
}.tr(' ', '+')
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
# Unescapes a URI escaped string. (Stolen from Camping).
|
14
|
+
def self.unescape(s)
|
15
|
+
s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
|
16
|
+
[$1.delete('%')].pack('H*')
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
# Parses a query string by breaking it up at the '&'
|
21
|
+
# and ';' characters. You can also use this to parse
|
22
|
+
# cookies by changing the characters used in the second
|
23
|
+
# parameter (which defaults to '&;'.
|
24
|
+
def self.query_parse(qs, d = '&;')
|
25
|
+
params = {}
|
26
|
+
|
27
|
+
qs.split(/[#{d}] */n).each do |p|
|
28
|
+
k, v = unescape(p).split('=', 2)
|
29
|
+
|
30
|
+
if cur = params[k]
|
31
|
+
if cur.kind_of? Array
|
32
|
+
params[k] << v
|
33
|
+
else
|
34
|
+
params[k] = [cur, v]
|
35
|
+
end
|
36
|
+
else
|
37
|
+
params[k] = v
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
return params
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rack/handler'
|
2
|
+
require 'puma'
|
3
|
+
|
4
|
+
module Rack
|
5
|
+
module Handler
|
6
|
+
module Puma
|
7
|
+
DEFAULT_OPTIONS = {:Host => '0.0.0.0', :Port => 8080, :Threads => '0:16'}
|
8
|
+
|
9
|
+
def self.run(app, options = {})
|
10
|
+
options = DEFAULT_OPTIONS.merge(options)
|
11
|
+
server = ::Puma::Server.new(app)
|
12
|
+
min, max = options[:Threads].split(':', 2)
|
13
|
+
|
14
|
+
server.add_tcp_listener options[:Host], options[:Port]
|
15
|
+
server.min_threads = Integer(min)
|
16
|
+
server.max_threads = Integer(max)
|
17
|
+
yield server if block_given?
|
18
|
+
|
19
|
+
server.run.join
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.valid_options
|
23
|
+
{
|
24
|
+
"Host=HOST" => "Hostname to listen on (default: localhost)",
|
25
|
+
"Port=PORT" => "Port to listen on (default: 8080)",
|
26
|
+
"Threads=MIN:MAX" => "min:max threads to use (default 0:16)"
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
register :puma, Puma
|
32
|
+
end
|
33
|
+
end
|
data/puma.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "puma"
|
5
|
+
s.version = "0.8.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Evan Phoenix"]
|
9
|
+
s.date = "2011-10-25"
|
10
|
+
s.description = "Puma is a small library that provides a very fast and concurrent HTTP 1.1 server for Ruby web applications. It is designed for running rack apps only.\n\nWhat makes Puma so fast is the careful use of an Ragel extension to provide fast, accurate HTTP 1.1 protocol parsing. This makes the server scream without too many portability issues."
|
11
|
+
s.email = ["evan@phx.io"]
|
12
|
+
s.executables = ["puma"]
|
13
|
+
s.extensions = ["ext/puma_http11/extconf.rb"]
|
14
|
+
s.extra_rdoc_files = ["History.txt", "Manifest.txt"]
|
15
|
+
s.files = ["COPYING", "History.txt", "LICENSE", "Manifest.txt", "README.md", "Rakefile", "TODO", "bin/puma", "examples/builder.rb", "examples/camping/README", "examples/camping/blog.rb", "examples/camping/tepee.rb", "examples/httpd.conf", "examples/mime.yaml", "examples/mongrel.conf", "examples/monitrc", "examples/random_thrash.rb", "examples/simpletest.rb", "examples/webrick_compare.rb", "ext/puma_http11/Http11Service.java", "ext/puma_http11/ext_help.h", "ext/puma_http11/extconf.rb", "ext/puma_http11/puma_http11.c", "ext/puma_http11/http11_parser.c", "ext/puma_http11/http11_parser.h", "ext/puma_http11/http11_parser.java.rl", "ext/puma_http11/http11_parser.rl", "ext/puma_http11/http11_parser_common.rl", "ext/puma_http11/org/jruby/mongrel/Http11.java", "ext/puma_http11/org/jruby/mongrel/Http11Parser.java", "lib/puma.rb", "lib/puma/cli.rb", "lib/puma/const.rb", "lib/puma/events.rb", "lib/puma/gems.rb", "lib/puma/mime_types.yml", "lib/puma/server.rb", "lib/puma/thread_pool.rb", "lib/puma/utils.rb", "lib/rack/handler/puma.rb", "puma.gemspec", "tasks/gem.rake", "tasks/java.rake", "tasks/native.rake", "tasks/ragel.rake", "test/lobster.ru", "test/mime.yaml", "test/test_http10.rb", "test/test_http11.rb", "test/test_persistent.rb", "test/test_rack_handler.rb", "test/test_rack_server.rb", "test/test_thread_pool.rb", "test/test_unix_socket.rb", "test/test_ws.rb", "test/testhelp.rb", "tools/trickletest.rb", ".gemtest"]
|
16
|
+
s.rdoc_options = ["--main", "README.md"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = "puma"
|
19
|
+
s.rubygems_version = "1.8.10"
|
20
|
+
s.summary = "Puma is a small library that provides a very fast and concurrent HTTP 1.1 server for Ruby web applications"
|
21
|
+
s.test_files = ["test/test_http10.rb", "test/test_http11.rb", "test/test_persistent.rb", "test/test_rack_handler.rb", "test/test_rack_server.rb", "test/test_thread_pool.rb", "test/test_unix_socket.rb", "test/test_ws.rb"]
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
s.specification_version = 3
|
25
|
+
|
26
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
27
|
+
s.add_development_dependency(%q<rake-compiler>, ["~> 0.7.0"])
|
28
|
+
s.add_development_dependency(%q<hoe>, ["~> 2.10"])
|
29
|
+
else
|
30
|
+
s.add_dependency(%q<rake-compiler>, ["~> 0.7.0"])
|
31
|
+
s.add_dependency(%q<hoe>, ["~> 2.10"])
|
32
|
+
end
|
33
|
+
else
|
34
|
+
s.add_dependency(%q<rake-compiler>, ["~> 0.7.0"])
|
35
|
+
s.add_dependency(%q<hoe>, ["~> 2.10"])
|
36
|
+
end
|
37
|
+
end
|
data/tasks/gem.rake
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'hoe'
|
2
|
+
|
3
|
+
HOE = Hoe.spec 'puma' do
|
4
|
+
self.rubyforge_name = 'puma'
|
5
|
+
self.readme_file = "README.md"
|
6
|
+
developer 'Evan Phoenix', 'evan@phx.io'
|
7
|
+
|
8
|
+
spec_extras[:extensions] = ["ext/puma_http11/extconf.rb"]
|
9
|
+
spec_extras[:executables] = ['puma']
|
10
|
+
|
11
|
+
extra_dev_deps << ['rake-compiler', "~> 0.7.0"]
|
12
|
+
|
13
|
+
clean_globs.push('test_*.log', 'log')
|
14
|
+
end
|
15
|
+
|
16
|
+
file "#{HOE.spec.name}.gemspec" => ['Rakefile', 'tasks/gem.rake'] do |t|
|
17
|
+
puts "Generating #{t.name}"
|
18
|
+
File.open(t.name, 'w') { |f| f.puts HOE.spec.to_ruby }
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "Generate or update the standalone gemspec file for the project"
|
22
|
+
task :gemspec => ["#{HOE.spec.name}.gemspec"]
|
data/tasks/java.rake
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
if ENV['JAVA']
|
2
|
+
|
3
|
+
require 'rake/javaextensiontask'
|
4
|
+
|
5
|
+
# build http11 java extension
|
6
|
+
Rake::JavaExtensionTask.new('http11', HOE.spec) do |ext|
|
7
|
+
ext.java_compiling do |gs|
|
8
|
+
gs.dependencies.delete gs.dependencies.find { |d| d.name == 'daemons' }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
data/tasks/native.rake
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# use rake-compiler for building the extension
|
2
|
+
require 'rake/extensiontask'
|
3
|
+
|
4
|
+
# build http11 C extension
|
5
|
+
Rake::ExtensionTask.new('puma_http11', HOE.spec) do |ext|
|
6
|
+
# define target for extension (supporting fat binaries)
|
7
|
+
if RUBY_PLATFORM =~ /mingw|mswin/ then
|
8
|
+
RUBY_VERSION =~ /(\d+\.\d+)/
|
9
|
+
ext.lib_dir = "lib/#{$1}"
|
10
|
+
elsif ENV['CROSS']
|
11
|
+
# define cross-compilation tasks when not on Windows.
|
12
|
+
ext.cross_compile = true
|
13
|
+
ext.cross_platform = ['i386-mswin32', 'i386-mingw32']
|
14
|
+
|
15
|
+
ext.cross_compiling do |gs|
|
16
|
+
gs.dependencies.delete gs.dependencies.find { |d| d.name == 'daemons' }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# cleanup versioned library directory
|
21
|
+
CLEAN.include 'lib/{1.8,1.9}'
|
22
|
+
end
|
23
|
+
|
24
|
+
# ensure things are built prior testing
|
25
|
+
task :test => [:compile]
|
data/tasks/ragel.rake
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
# the following tasks ease the build of C file from Ragel one
|
3
|
+
|
4
|
+
file 'ext/http11/http11_parser.c' => ['ext/http11/http11_parser.rl'] do |t|
|
5
|
+
begin
|
6
|
+
sh "ragel #{t.prerequisites.last} -C -G2 -o #{t.name}"
|
7
|
+
rescue
|
8
|
+
fail "Could not build wrapper using Ragel (it failed or not installed?)"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
file 'ext/http11/org/jruby/puma/Http11Parser.java' => ['ext/http11/http11_parser.java.rl'] do |t|
|
13
|
+
begin
|
14
|
+
sh "ragel #{t.prerequisites.last} -J -G2 -o #{t.name}"
|
15
|
+
rescue
|
16
|
+
fail "Could not build wrapper using Ragel (it failed or not installed?)"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
task :ragel => (defined?(JRUBY_VERSION) ? 'ext/http11/org/jruby/puma/Http11Parser.java' : 'ext/http11/http11_parser.c')
|
data/test/lobster.ru
ADDED
data/test/mime.yaml
ADDED
data/test/test_http10.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'test/testhelp'
|
2
|
+
|
3
|
+
class Http10ParserTest < Test::Unit::TestCase
|
4
|
+
include Puma
|
5
|
+
|
6
|
+
def test_parse_simple
|
7
|
+
parser = HttpParser.new
|
8
|
+
req = {}
|
9
|
+
http = "GET / HTTP/1.0\r\n\r\n"
|
10
|
+
nread = parser.execute(req, http, 0)
|
11
|
+
|
12
|
+
assert nread == http.length, "Failed to parse the full HTTP request"
|
13
|
+
assert parser.finished?, "Parser didn't finish"
|
14
|
+
assert !parser.error?, "Parser had error"
|
15
|
+
assert nread == parser.nread, "Number read returned from execute does not match"
|
16
|
+
|
17
|
+
assert_equal '/', req['REQUEST_PATH']
|
18
|
+
assert_equal 'HTTP/1.0', req['HTTP_VERSION']
|
19
|
+
assert_equal '/', req['REQUEST_URI']
|
20
|
+
assert_equal 'GET', req['REQUEST_METHOD']
|
21
|
+
assert_nil req['FRAGMENT']
|
22
|
+
assert_nil req['QUERY_STRING']
|
23
|
+
|
24
|
+
parser.reset
|
25
|
+
assert parser.nread == 0, "Number read after reset should be 0"
|
26
|
+
end
|
27
|
+
end
|
data/test/test_http11.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
# Copyright (c) 2011 Evan Phoenix
|
2
|
+
# Copyright (c) 2005 Zed A. Shaw
|
3
|
+
|
4
|
+
require 'test/testhelp'
|
5
|
+
|
6
|
+
include Puma
|
7
|
+
|
8
|
+
class Http11ParserTest < Test::Unit::TestCase
|
9
|
+
|
10
|
+
def test_parse_simple
|
11
|
+
parser = HttpParser.new
|
12
|
+
req = {}
|
13
|
+
http = "GET / HTTP/1.1\r\n\r\n"
|
14
|
+
nread = parser.execute(req, http, 0)
|
15
|
+
|
16
|
+
assert nread == http.length, "Failed to parse the full HTTP request"
|
17
|
+
assert parser.finished?, "Parser didn't finish"
|
18
|
+
assert !parser.error?, "Parser had error"
|
19
|
+
assert nread == parser.nread, "Number read returned from execute does not match"
|
20
|
+
|
21
|
+
assert_equal '/', req['REQUEST_PATH']
|
22
|
+
assert_equal 'HTTP/1.1', req['HTTP_VERSION']
|
23
|
+
assert_equal '/', req['REQUEST_URI']
|
24
|
+
assert_equal 'GET', req['REQUEST_METHOD']
|
25
|
+
assert_nil req['FRAGMENT']
|
26
|
+
assert_nil req['QUERY_STRING']
|
27
|
+
|
28
|
+
parser.reset
|
29
|
+
assert parser.nread == 0, "Number read after reset should be 0"
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_parse_dumbfuck_headers
|
33
|
+
parser = HttpParser.new
|
34
|
+
req = {}
|
35
|
+
should_be_good = "GET / HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n"
|
36
|
+
nread = parser.execute(req, should_be_good, 0)
|
37
|
+
assert_equal should_be_good.length, nread
|
38
|
+
assert parser.finished?
|
39
|
+
assert !parser.error?
|
40
|
+
|
41
|
+
nasty_pound_header = "GET / HTTP/1.1\r\nX-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgEBBAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n\tRA==\r\n\t-----END CERTIFICATE-----\r\n\r\n"
|
42
|
+
parser = HttpParser.new
|
43
|
+
req = {}
|
44
|
+
#nread = parser.execute(req, nasty_pound_header, 0)
|
45
|
+
#assert_equal nasty_pound_header.length, nread
|
46
|
+
#assert parser.finished?
|
47
|
+
#assert !parser.error?
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_parse_error
|
51
|
+
parser = HttpParser.new
|
52
|
+
req = {}
|
53
|
+
bad_http = "GET / SsUTF/1.1"
|
54
|
+
|
55
|
+
error = false
|
56
|
+
begin
|
57
|
+
nread = parser.execute(req, bad_http, 0)
|
58
|
+
rescue => details
|
59
|
+
error = true
|
60
|
+
end
|
61
|
+
|
62
|
+
assert error, "failed to throw exception"
|
63
|
+
assert !parser.finished?, "Parser shouldn't be finished"
|
64
|
+
assert parser.error?, "Parser SHOULD have error"
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_fragment_in_uri
|
68
|
+
parser = HttpParser.new
|
69
|
+
req = {}
|
70
|
+
get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
|
71
|
+
assert_nothing_raised do
|
72
|
+
parser.execute(req, get, 0)
|
73
|
+
end
|
74
|
+
assert parser.finished?
|
75
|
+
assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
|
76
|
+
assert_equal 'posts-17408', req['FRAGMENT']
|
77
|
+
end
|
78
|
+
|
79
|
+
# lame random garbage maker
|
80
|
+
def rand_data(min, max, readable=true)
|
81
|
+
count = min + ((rand(max)+1) *10).to_i
|
82
|
+
res = count.to_s + "/"
|
83
|
+
|
84
|
+
if readable
|
85
|
+
res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40)
|
86
|
+
else
|
87
|
+
res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20)
|
88
|
+
end
|
89
|
+
|
90
|
+
return res
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
def test_horrible_queries
|
95
|
+
parser = HttpParser.new
|
96
|
+
|
97
|
+
# then that large header names are caught
|
98
|
+
10.times do |c|
|
99
|
+
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
|
100
|
+
assert_raises Puma::HttpParserError do
|
101
|
+
parser.execute({}, get, 0)
|
102
|
+
parser.reset
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# then that large mangled field values are caught
|
107
|
+
10.times do |c|
|
108
|
+
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
|
109
|
+
assert_raises Puma::HttpParserError do
|
110
|
+
parser.execute({}, get, 0)
|
111
|
+
parser.reset
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# then large headers are rejected too
|
116
|
+
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
|
117
|
+
get << "X-Test: test\r\n" * (80 * 1024)
|
118
|
+
assert_raises Puma::HttpParserError do
|
119
|
+
parser.execute({}, get, 0)
|
120
|
+
parser.reset
|
121
|
+
end
|
122
|
+
|
123
|
+
# finally just that random garbage gets blocked all the time
|
124
|
+
10.times do |c|
|
125
|
+
get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
|
126
|
+
assert_raises Puma::HttpParserError do
|
127
|
+
parser.execute({}, get, 0)
|
128
|
+
parser.reset
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
|
136
|
+
def test_query_parse
|
137
|
+
res = Utils.query_parse("zed=1&frank=#{Utils.escape('&&& ')}")
|
138
|
+
assert res["zed"], "didn't get the request right"
|
139
|
+
assert res["frank"], "no frank"
|
140
|
+
assert_equal "1", res["zed"], "wrong result"
|
141
|
+
assert_equal "&&& ", Utils.unescape(res["frank"]), "wrong result"
|
142
|
+
|
143
|
+
res = Utils.query_parse("zed=1&zed=2&zed=3&frank=11;zed=45")
|
144
|
+
assert res["zed"], "didn't get the request right"
|
145
|
+
assert res["frank"], "no frank"
|
146
|
+
assert_equal 4,res["zed"].length, "wrong number for zed"
|
147
|
+
assert_equal "11",res["frank"], "wrong number for frank"
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'puma'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
class TestPersistent < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@valid_request = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
|
7
|
+
@close_request = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n"
|
8
|
+
@http10_request = "GET / HTTP/1.0\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
|
9
|
+
@keep_request = "GET / HTTP/1.0\r\nHost: test.com\r\nContent-Type: text/plain\r\nConnection: Keep-Alive\r\n\r\n"
|
10
|
+
|
11
|
+
@headers = { "X-Header" => "Works" }
|
12
|
+
@body = ["Hello"]
|
13
|
+
@simple = lambda { |env| [200, @headers, @body] }
|
14
|
+
@server = Puma::Server.new @simple
|
15
|
+
@server.add_tcp_listener "127.0.0.1", 9988
|
16
|
+
@server.run
|
17
|
+
|
18
|
+
@client = TCPSocket.new "127.0.0.1", 9988
|
19
|
+
end
|
20
|
+
|
21
|
+
def teardown
|
22
|
+
@client.close
|
23
|
+
@server.stop(true)
|
24
|
+
end
|
25
|
+
|
26
|
+
def lines(count, s=@client)
|
27
|
+
str = ""
|
28
|
+
count.times { str << s.gets }
|
29
|
+
str
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_one_with_content_length
|
33
|
+
@client << @valid_request
|
34
|
+
sz = @body[0].size.to_s
|
35
|
+
|
36
|
+
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
|
37
|
+
assert_equal "Hello", @client.read(5)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_two_back_to_back
|
41
|
+
@client << @valid_request
|
42
|
+
sz = @body[0].size.to_s
|
43
|
+
|
44
|
+
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
|
45
|
+
assert_equal "Hello", @client.read(5)
|
46
|
+
|
47
|
+
@client << @valid_request
|
48
|
+
sz = @body[0].size.to_s
|
49
|
+
|
50
|
+
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
|
51
|
+
assert_equal "Hello", @client.read(5)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_chunked
|
55
|
+
@body << "Chunked"
|
56
|
+
|
57
|
+
@client << @valid_request
|
58
|
+
|
59
|
+
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHello\r\n7\r\nChunked\r\n0\r\n\r\n", lines(10)
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_no_chunked_in_http10
|
63
|
+
@body << "Chunked"
|
64
|
+
|
65
|
+
@client << @http10_request
|
66
|
+
|
67
|
+
assert_equal "HTTP/1.0 200 OK\r\nX-Header: Works\r\nConnection: close\r\n\r\n", lines(4)
|
68
|
+
assert_equal "HelloChunked", @client.read
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_hex
|
72
|
+
str = "This is longer and will be in hex"
|
73
|
+
@body << str
|
74
|
+
|
75
|
+
@client << @valid_request
|
76
|
+
|
77
|
+
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHello\r\n#{str.size.to_s(16)}\r\n#{str}\r\n0\r\n\r\n", lines(10)
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_client11_close
|
82
|
+
@client << @close_request
|
83
|
+
sz = @body[0].size.to_s
|
84
|
+
|
85
|
+
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nConnection: close\r\nContent-Length: #{sz}\r\n\r\n", lines(5)
|
86
|
+
assert_equal "Hello", @client.read(5)
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_client10_close
|
90
|
+
@client << @http10_request
|
91
|
+
sz = @body[0].size.to_s
|
92
|
+
|
93
|
+
assert_equal "HTTP/1.0 200 OK\r\nX-Header: Works\r\nConnection: close\r\nContent-Length: #{sz}\r\n\r\n", lines(5)
|
94
|
+
assert_equal "Hello", @client.read(5)
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_one_with_keep_alive_header
|
98
|
+
@client << @keep_request
|
99
|
+
sz = @body[0].size.to_s
|
100
|
+
|
101
|
+
assert_equal "HTTP/1.0 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
|
102
|
+
assert_equal "Hello", @client.read(5)
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_persistent_timeout
|
106
|
+
@server.persistent_timeout = 2
|
107
|
+
@client << @valid_request
|
108
|
+
sz = @body[0].size.to_s
|
109
|
+
|
110
|
+
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
|
111
|
+
assert_equal "Hello", @client.read(5)
|
112
|
+
|
113
|
+
sleep 3
|
114
|
+
|
115
|
+
assert_raises EOFError do
|
116
|
+
@client.read_nonblock(1)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_app_sets_content_length
|
121
|
+
@body = ["hello", " world"]
|
122
|
+
@headers['Content-Length'] = "11"
|
123
|
+
|
124
|
+
@client << @valid_request
|
125
|
+
|
126
|
+
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: 11\r\n\r\n",
|
127
|
+
lines(4)
|
128
|
+
assert_equal "hello world", @client.read(11)
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_allow_app_to_chunk_itself
|
132
|
+
@headers = {'Transfer-Encoding' => "chunked" }
|
133
|
+
|
134
|
+
@body = ["5\r\nhello\r\n0\r\n\r\n"]
|
135
|
+
|
136
|
+
@client << @valid_request
|
137
|
+
|
138
|
+
assert_equal "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n\r\n", lines(7)
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
def test_two_requests_in_one_chunk
|
143
|
+
@server.persistent_timeout = 3
|
144
|
+
|
145
|
+
req = @valid_request.to_s
|
146
|
+
req << "GET /second HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
|
147
|
+
|
148
|
+
@client << req
|
149
|
+
|
150
|
+
sz = @body[0].size.to_s
|
151
|
+
|
152
|
+
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
|
153
|
+
assert_equal "Hello", @client.read(5)
|
154
|
+
|
155
|
+
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
|
156
|
+
assert_equal "Hello", @client.read(5)
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|