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.

Files changed (59) hide show
  1. data/.gemtest +0 -0
  2. data/COPYING +55 -0
  3. data/History.txt +69 -0
  4. data/LICENSE +26 -0
  5. data/Manifest.txt +57 -0
  6. data/README.md +60 -0
  7. data/Rakefile +10 -0
  8. data/TODO +5 -0
  9. data/bin/puma +15 -0
  10. data/examples/builder.rb +29 -0
  11. data/examples/camping/README +3 -0
  12. data/examples/camping/blog.rb +294 -0
  13. data/examples/camping/tepee.rb +149 -0
  14. data/examples/httpd.conf +474 -0
  15. data/examples/mime.yaml +3 -0
  16. data/examples/mongrel.conf +9 -0
  17. data/examples/monitrc +57 -0
  18. data/examples/random_thrash.rb +19 -0
  19. data/examples/simpletest.rb +52 -0
  20. data/examples/webrick_compare.rb +20 -0
  21. data/ext/puma_http11/Http11Service.java +13 -0
  22. data/ext/puma_http11/ext_help.h +15 -0
  23. data/ext/puma_http11/extconf.rb +5 -0
  24. data/ext/puma_http11/http11_parser.c +1225 -0
  25. data/ext/puma_http11/http11_parser.h +63 -0
  26. data/ext/puma_http11/http11_parser.java.rl +159 -0
  27. data/ext/puma_http11/http11_parser.rl +146 -0
  28. data/ext/puma_http11/http11_parser_common.rl +54 -0
  29. data/ext/puma_http11/org/jruby/mongrel/Http11.java +241 -0
  30. data/ext/puma_http11/org/jruby/mongrel/Http11Parser.java +486 -0
  31. data/ext/puma_http11/puma_http11.c +482 -0
  32. data/lib/puma.rb +18 -0
  33. data/lib/puma/cli.rb +131 -0
  34. data/lib/puma/const.rb +132 -0
  35. data/lib/puma/events.rb +36 -0
  36. data/lib/puma/gems.rb +20 -0
  37. data/lib/puma/mime_types.yml +616 -0
  38. data/lib/puma/server.rb +419 -0
  39. data/lib/puma/thread_pool.rb +95 -0
  40. data/lib/puma/utils.rb +44 -0
  41. data/lib/rack/handler/puma.rb +33 -0
  42. data/puma.gemspec +37 -0
  43. data/tasks/gem.rake +22 -0
  44. data/tasks/java.rake +12 -0
  45. data/tasks/native.rake +25 -0
  46. data/tasks/ragel.rake +20 -0
  47. data/test/lobster.ru +4 -0
  48. data/test/mime.yaml +3 -0
  49. data/test/test_http10.rb +27 -0
  50. data/test/test_http11.rb +151 -0
  51. data/test/test_persistent.rb +159 -0
  52. data/test/test_rack_handler.rb +10 -0
  53. data/test/test_rack_server.rb +107 -0
  54. data/test/test_thread_pool.rb +102 -0
  55. data/test/test_unix_socket.rb +34 -0
  56. data/test/test_ws.rb +97 -0
  57. data/test/testhelp.rb +41 -0
  58. data/tools/trickletest.rb +45 -0
  59. metadata +165 -0
@@ -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
@@ -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
@@ -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"]
@@ -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
@@ -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]
@@ -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')
@@ -0,0 +1,4 @@
1
+ require 'rack/lobster'
2
+
3
+ use Rack::ShowExceptions
4
+ run Rack::Lobster.new
@@ -0,0 +1,3 @@
1
+ ---
2
+ .jpeg: image/jpeg
3
+ .png: image/test
@@ -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
@@ -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