yahns 0.0.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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