yahns 0.0.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fff238b384ecd740912bd989b3f9f5940f1a8ed1
4
- data.tar.gz: 4f0a23620f3de110285e6f0da568dbb8448713ec
3
+ metadata.gz: 33525c7e184e8f29a1667fb401ce20bcda42f30e
4
+ data.tar.gz: 13d18fdee5cfbd47bb3d83046c5e388e06c08495
5
5
  SHA512:
6
- metadata.gz: 019a14bdaf0613f75c89b1b170f705373698645a115f06f260e008080f3cb1b365091a2cfface0695bc841b090fd1d528d35b0ea54f0fa4b74424c6d28fc4cd0
7
- data.tar.gz: ff28c0cee74dc2602ca0eb74dca63e9b98e84c28179f043db8875f29537b15a3d6df9b93439fbdc12cdfe6deb7006575248d7e54cce56351baefde378ba62494
6
+ metadata.gz: c1cdd01498c6a8719efa5e246783ca00fd7dd2c31caaf4e7a345cbd3716f67ab962ecd1dd3c39819cc74cb4e58900fa08c6497554dfeefb1152c7cabf89d1b40
7
+ data.tar.gz: 3b087200a116d46b7b7c4ebfc5e2c58f7315ff53c19ff02414b289258f43d40e0ecd79348ba96731bbd081d1bea6cd04695aeb18d0cfbcef96ec359db782600d
@@ -4,7 +4,7 @@
4
4
  CONSTANT = "Yahns::VERSION"
5
5
  RVF = "lib/yahns/version.rb"
6
6
  GVF = "GIT-VERSION-FILE"
7
- DEF_VER = "v0.0.3"
7
+ DEF_VER = "v1.0.0"
8
8
  vn = DEF_VER
9
9
 
10
10
  # First see if there is a version file (included in release tarballs),
data/HACKING CHANGED
@@ -4,6 +4,7 @@ development dependencies
4
4
  * minitest RubyGem (version 4 or 5, standard in Ruby 2.0+)
5
5
  * curl - http://curl.haxx.se/ - we don't trust our own Ruby abilities :>
6
6
  * dd(1) - standard POSIX tool - to feed curl
7
+ * ab - http://httpd.apache.org - for concurrent testing
7
8
  * GNU make - https://www.gnu.org/software/make/
8
9
  * git - http://www.git-scm.com/
9
10
  * ruby - http://www.ruby-lang.org/
@@ -42,7 +42,7 @@ class Autoindex
42
42
  return res if status.to_i != 404
43
43
 
44
44
  path_info = env["PATH_INFO"]
45
- path_info_ue = Rack::Utils.unescape(path_info)
45
+ path_info_ue = Rack::Utils.unescape(path_info, Encoding::BINARY)
46
46
 
47
47
  # reject requests to go up a level (browser takes care of it)
48
48
  path_info_ue =~ /\.\./ and return r(403)
@@ -112,15 +112,18 @@ class Autoindex
112
112
  rescue Errno::ENOENT, Errno::ENOTDIR # from Dir.open
113
113
  r(404)
114
114
  rescue => e
115
- r(500, e.message, env)
115
+ r(500, e, env)
116
116
  ensure
117
117
  dir.close if dir
118
118
  end
119
119
 
120
120
  def r(code, msg = nil, env = nil)
121
- if env && logger = env["rack.logger"]
121
+ if env && exc && logger = env["rack.logger"]
122
+ msg = exc.message
123
+ msg = msg.dump if /[[:cntrl:]]/ =~ msg # prevent code injection
122
124
  logger.warn("#{env['REQUEST_METHOD']} #{env['PATH_INFO']} " \
123
- "#{code} #{msg.inspect}")
125
+ "#{code} #{msg}")
126
+ exc.backtrace.each { |line| logger.warn(line) }
124
127
  end
125
128
 
126
129
  if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(code)
@@ -4,23 +4,26 @@
4
4
  #
5
5
  # if running under yahns, worker_processes is recommended to avoid conflicting
6
6
  # with the SIGCHLD handler in yahns.
7
+
8
+ # Be careful if using Rack::Deflater, this needs the following commit
9
+ # (currently in rack.git, not yet in 1.5.2):
10
+ # commit 7bda8d485b38403bf07f43793d37b66b7a8281d6
11
+ # (delfater: ensure that parent body is always closed)
12
+ # Otherwise you will get zombies from HEAD requests which accept compressed
13
+ # responses.
7
14
  class ExecCgi
8
15
  class MyIO < Kgio::Pipe
9
16
  attr_writer :my_pid
10
17
  attr_writer :body_tip
11
- attr_writer :chunked
12
18
 
13
19
  def each
14
20
  buf = @body_tip || ""
15
21
  if buf.size > 0
16
- buf = "#{buf.size.to_s(16)}\r\n#{buf}\r\n" if @chunked
17
22
  yield buf
18
23
  end
19
24
  while tmp = kgio_read(8192, buf)
20
- tmp = "#{tmp.size.to_s(16)}\r\n#{tmp}\r\n" if @chunked
21
25
  yield tmp
22
26
  end
23
- yield("0\r\n\r\n") if @chunked
24
27
  self
25
28
  ensure
26
29
  # do this sooner, since the response body may be buffered, we want
@@ -46,7 +49,6 @@ class ExecCgi
46
49
  PASS_VARS = %w(
47
50
  CONTENT_LENGTH
48
51
  CONTENT_TYPE
49
- GATEWAY_INTERFACE
50
52
  AUTH_TYPE
51
53
  PATH_INFO
52
54
  PATH_TRANSLATED
@@ -88,9 +90,10 @@ class ExecCgi
88
90
  tmp = pipe.kgio_read(8192) or break
89
91
  head << tmp
90
92
  end
91
- head, body = head.split(/\r?\n\r?\n/)
93
+ head, body = head.split(/\r?\n\r?\n/, 2)
92
94
  pipe.body_tip = body
93
- pipe.chunked = false
95
+
96
+ env["HTTP_VERSION"] ||= "HTTP/1.0" # stop Rack::Chunked for HTTP/0.9
94
97
 
95
98
  headers = Rack::Utils::HeaderHash.new
96
99
  prev = nil
@@ -101,16 +104,6 @@ class ExecCgi
101
104
  end
102
105
  end
103
106
  status = headers.delete("Status") || 200
104
- unless headers.include?("Content-Length") ||
105
- headers.include?("Transfer-Encoding")
106
- case env['HTTP_VERSION']
107
- when 'HTTP/1.0', nil
108
- # server will drop connection anyways
109
- else
110
- headers["Transfer-Encoding"] = "chunked"
111
- pipe.chunked = true
112
- end
113
- end
114
107
  errbody = nil
115
108
  [ status, headers, pipe ]
116
109
  else
@@ -1,3 +1,4 @@
1
+ # -*- encoding: binary -*-
1
2
  # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
2
3
  # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
4
  require 'time'
@@ -11,9 +12,9 @@ class TryGzipStatic
11
12
  # attr_writer :sf_range
12
13
 
13
14
  # only used if the server does not handle #to_path,
14
- # yahns should never hit this
15
+ # we actually hit this if serving the gzipped file in the first place,
16
+ # _and_ Rack::Deflater is used in the middleware stack. Oh well...
15
17
  def each
16
- raise "we should never get here in yahns"
17
18
  buf = ""
18
19
  rsize = 8192
19
20
  if @sf_range
@@ -32,12 +33,12 @@ class TryGzipStatic
32
33
  end
33
34
 
34
35
  def initialize(root, default_type = 'text/plain')
35
- @root = root
36
+ @root = root.b
36
37
  @default_type = default_type
37
38
  end
38
39
 
39
40
  def fspath(env)
40
- path_info = Rack::Utils.unescape(env["PATH_INFO"])
41
+ path_info = Rack::Utils.unescape(env["PATH_INFO"], Encoding::BINARY)
41
42
  path_info =~ /\.\./ ? nil : "#@root#{path_info}"
42
43
  end
43
44
 
@@ -76,6 +77,7 @@ class TryGzipStatic
76
77
  "Last-Modified" => st.mtime.httpdate,
77
78
  "Accept-Ranges" => "bytes",
78
79
  }
80
+ h["Cache-Control"] = "no-transform" unless mime =~ %r{\Atext\/}
79
81
  h["Content-Encoding"] = "gzip" if gz_st
80
82
  h
81
83
  end
@@ -91,12 +93,12 @@ class TryGzipStatic
91
93
  begin
92
94
  st = File.stat(path)
93
95
  st.file? ? [ path, st ] : r(404)
94
- rescue Errno::ENOENT
96
+ rescue Errno::ENOENT, Errno::ENOTDIR
95
97
  r(404)
96
98
  rescue Errno::EACCES
97
99
  r(403)
98
100
  rescue => e
99
- r(500, e.message, env)
101
+ r(500, e, env)
100
102
  end
101
103
  end
102
104
 
@@ -127,7 +129,7 @@ class TryGzipStatic
127
129
  rescue Errno::ENOENT, Errno::EACCES
128
130
  head_no_gz(res, env, path, st)
129
131
  rescue => e
130
- r(500, e.message, env)
132
+ r(500, e, env)
131
133
  end
132
134
  end
133
135
  else # 416, 304
@@ -189,13 +191,18 @@ class TryGzipStatic
189
191
  rescue Errno::EACCES # could get here from a race
190
192
  r(403)
191
193
  rescue => e
192
- r(500, e.message, env)
194
+ r(500, e, env)
193
195
  end
194
196
 
195
- def r(code, msg = nil, env = nil)
196
- if env && logger = env["rack.logger"]
197
+ def r(code, exc = nil, env = nil)
198
+ if env && exc && logger = env["rack.logger"]
199
+ msg = exc.message if exc.respond_to?(:message)
200
+ msg = msg.dump if /[[:cntrl:]]/ =~ msg # prevent code injection
197
201
  logger.warn("#{env['REQUEST_METHOD']} #{env['PATH_INFO']} " \
198
- "#{code} #{msg.inspect}")
202
+ "#{code} #{msg}")
203
+ if exc.respond_to?(:backtrace)
204
+ exc.backtrace.each { |line| logger.warn(line) }
205
+ end
199
206
  end
200
207
 
201
208
  if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(code)
@@ -15,11 +15,18 @@ require_relative 'wbuf_str'
15
15
  module Yahns::HttpResponse # :nodoc:
16
16
  include Unicorn::HttpResponse
17
17
 
18
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx"
19
+ MTX = Mutex.new
20
+ def httpdate
21
+ MTX.synchronize { super }
22
+ end
23
+ end
24
+
18
25
  # avoid GC overhead for frequently used-strings:
19
26
  CONN_KA = "Connection: keep-alive\r\n\r\n"
20
27
  CONN_CLOSE = "Connection: close\r\n\r\n"
21
28
  Z = ""
22
- CCC_RESPONSE_START = [ 'HTTP', '/1.1 ' ].map!(&:freeze)
29
+ CCC_RESPONSE_START = [ 'HTTP', '/1.1 ' ]
23
30
  RESPONSE_START = CCC_RESPONSE_START.join('')
24
31
  R100_RAW = "HTTP/1.1 100 Continue\r\n\r\n"
25
32
  R100_CCC = "100 Continue\r\n\r\nHTTP/1.1 "
@@ -119,6 +119,14 @@ def cloexec_pipe
119
119
  IO.pipe.each { |io| io.close_on_exec = true }
120
120
  end
121
121
 
122
+ def require_exec(cmd)
123
+ ENV["PATH"].split(/:/).each do |path|
124
+ return true if File.executable?("#{path}/#{cmd}")
125
+ end
126
+ skip "#{cmd} not found in PATH"
127
+ false
128
+ end
129
+
122
130
  require 'yahns'
123
131
 
124
132
  # needed for parallel (MT) tests)
@@ -32,6 +32,7 @@ class TestClientExpire < Testcase
32
32
  end
33
33
 
34
34
  def test_client_expire
35
+ require_exec "ab"
35
36
  nr = 32
36
37
  err = @err
37
38
  cfg = Yahns::Config.new
@@ -74,6 +75,7 @@ class TestClientExpire < Testcase
74
75
 
75
76
  # test EMFILE handling
76
77
  def test_client_expire_desperate
78
+ require_exec "ab"
77
79
  err = @err
78
80
  cfg = Yahns::Config.new
79
81
  host, port = @srv.addr[3], @srv.addr[1]
@@ -17,7 +17,8 @@ class TestExtrasExecCGI < Testcase
17
17
  pid = mkserver(cfg) do
18
18
  require './extras/exec_cgi'
19
19
  cfg.instance_eval do
20
- app(:rack, ExecCgi.new(RUNME)) { listen "#{host}:#{port}" }
20
+ stack = Rack::ContentLength.new(Rack::Chunked.new(ExecCgi.new(RUNME)))
21
+ app(:rack, stack) { listen "#{host}:#{port}" }
21
22
  stderr_path err.path
22
23
  worker_processes 1
23
24
  end
@@ -96,7 +97,8 @@ class TestExtrasExecCGI < Testcase
96
97
  pid = mkserver(cfg) do
97
98
  require './extras/exec_cgi'
98
99
  cfg.instance_eval do
99
- app(:rack, ExecCgi.new(RUNME)) { listen "#{host}:#{port}" }
100
+ stack = Rack::ContentLength.new(Rack::Chunked.new(ExecCgi.new(RUNME)))
101
+ app(:rack, stack) { listen "#{host}:#{port}" }
100
102
  stderr_path err.path
101
103
  worker_processes 1
102
104
  end
@@ -133,7 +135,8 @@ class TestExtrasExecCGI < Testcase
133
135
  Yahns::HttpClient.__send__(:include, TrywriteBlocked)
134
136
  require './extras/exec_cgi'
135
137
  cfg.instance_eval do
136
- app(:rack, ExecCgi.new(RUNME)) { listen "#{host}:#{port}" }
138
+ stack = Rack::ContentLength.new(Rack::Chunked.new(ExecCgi.new(RUNME)))
139
+ app(:rack, stack) { listen "#{host}:#{port}" }
137
140
  stderr_path err.path
138
141
  worker_processes 1
139
142
  end
@@ -172,6 +172,11 @@ class TestExtrasTryGzipStatic < Testcase
172
172
  end
173
173
  assert_nil body
174
174
  end
175
+
176
+ Net::HTTP.start(host, port) do |http|
177
+ res = http.request(Net::HTTP::Get.new('/COPYING/foo'))
178
+ assert_equal 404, res.code.to_i
179
+ end
175
180
  ensure
176
181
  quit_wait(pid)
177
182
  end
@@ -13,10 +13,15 @@ Gem::Specification.new do |s|
13
13
  s.add_dependency(%q<kgio>, '~> 2.8')
14
14
  s.add_dependency(%q<sleepy_penguin>, '~> 3.2')
15
15
  s.add_dependency(%q<sendfile>, '~> 1.2.1')
16
- s.add_dependency(%q<unicorn>, '~> 4.6.3')
16
+ s.add_dependency(%q<unicorn>, '~> 4.6', '>= 4.6.3')
17
+
18
+ # minitest is standard in Ruby 2.0, 4.3 is packaged with Ruby 2.0.0,
19
+ # 4.7.5 with 2.1. We work with minitest 5, too. 6.x does not exist
20
+ # at the time of this writing. We should always be compatible with
21
+ # minitest (or test-unit) library packaged with the latest official
22
+ # Matz Ruby release.
23
+ s.add_development_dependency(%q<minitest>, '>= 4.3', '< 6.0')
17
24
 
18
- # minitest is standard in Ruby 2.0
19
- s.add_development_dependency(%q<minitest>, '~> 4.3', '~> 5.0')
20
25
  s.homepage = "http://yahns.yhbt.net/README"
21
26
  s.licenses = "GPLv3+"
22
27
  end
metadata CHANGED
@@ -1,91 +1,97 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yahns
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - yahns hackers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-10 00:00:00.000000000 Z
11
+ date: 2014-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: kgio
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '2.8'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.8'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: sleepy_penguin
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '3.2'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.2'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: sendfile
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ~>
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: 1.2.1
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ~>
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: 1.2.1
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: unicorn
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ~>
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '4.6'
62
+ - - ">="
60
63
  - !ruby/object:Gem::Version
61
64
  version: 4.6.3
62
65
  type: :runtime
63
66
  prerelease: false
64
67
  version_requirements: !ruby/object:Gem::Requirement
65
68
  requirements:
66
- - - ~>
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: '4.6'
72
+ - - ">="
67
73
  - !ruby/object:Gem::Version
68
74
  version: 4.6.3
69
75
  - !ruby/object:Gem::Dependency
70
76
  name: minitest
71
77
  requirement: !ruby/object:Gem::Requirement
72
78
  requirements:
73
- - - ~>
79
+ - - ">="
74
80
  - !ruby/object:Gem::Version
75
81
  version: '4.3'
76
- - - ~>
82
+ - - "<"
77
83
  - !ruby/object:Gem::Version
78
- version: '5.0'
84
+ version: '6.0'
79
85
  type: :development
80
86
  prerelease: false
81
87
  version_requirements: !ruby/object:Gem::Requirement
82
88
  requirements:
83
- - - ~>
89
+ - - ">="
84
90
  - !ruby/object:Gem::Version
85
91
  version: '4.3'
86
- - - ~>
92
+ - - "<"
87
93
  - !ruby/object:Gem::Version
88
- version: '5.0'
94
+ version: '6.0'
89
95
  description: |-
90
96
  A Free Software, multi-threaded, non-blocking network application server
91
97
  designed for low _idle_ power consumption. It is primarily optimized
@@ -100,7 +106,7 @@ executables:
100
106
  extensions: []
101
107
  extra_rdoc_files: []
102
108
  files:
103
- - .gitignore
109
+ - ".gitignore"
104
110
  - COPYING
105
111
  - Documentation/.gitignore
106
112
  - Documentation/GNUmakefile
@@ -206,17 +212,17 @@ require_paths:
206
212
  - lib
207
213
  required_ruby_version: !ruby/object:Gem::Requirement
208
214
  requirements:
209
- - - '>='
215
+ - - ">="
210
216
  - !ruby/object:Gem::Version
211
217
  version: '0'
212
218
  required_rubygems_version: !ruby/object:Gem::Requirement
213
219
  requirements:
214
- - - '>='
220
+ - - ">="
215
221
  - !ruby/object:Gem::Version
216
222
  version: '0'
217
223
  requirements: []
218
224
  rubyforge_project:
219
- rubygems_version: 2.1.9
225
+ rubygems_version: 2.2.0
220
226
  signing_key:
221
227
  specification_version: 4
222
228
  summary: sleepy, multi-threaded, non-blocking application server