yahns 1.12.5 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/Documentation/yahns-rackup.pod +0 -10
  3. data/Documentation/yahns_config.pod +3 -0
  4. data/GIT-VERSION-FILE +1 -1
  5. data/GIT-VERSION-GEN +1 -1
  6. data/NEWS +80 -0
  7. data/examples/init.sh +34 -9
  8. data/examples/logrotate.conf +5 -0
  9. data/examples/yahns.socket +17 -0
  10. data/examples/yahns@.service +50 -0
  11. data/examples/yahns_rack_basic.conf.rb +0 -6
  12. data/extras/autoindex.rb +3 -2
  13. data/extras/exec_cgi.rb +1 -0
  14. data/extras/try_gzip_static.rb +19 -5
  15. data/lib/yahns/chunk_body.rb +27 -0
  16. data/lib/yahns/fdmap.rb +7 -4
  17. data/lib/yahns/http_client.rb +39 -10
  18. data/lib/yahns/http_response.rb +41 -22
  19. data/lib/yahns/openssl_client.rb +7 -3
  20. data/lib/yahns/proxy_http_response.rb +132 -159
  21. data/lib/yahns/proxy_pass.rb +6 -170
  22. data/lib/yahns/queue_epoll.rb +1 -0
  23. data/lib/yahns/queue_kqueue.rb +1 -0
  24. data/lib/yahns/req_res.rb +164 -0
  25. data/lib/yahns/server.rb +2 -1
  26. data/lib/yahns/server_mp.rb +1 -1
  27. data/lib/yahns/version.rb +1 -1
  28. data/lib/yahns/wbuf.rb +5 -6
  29. data/lib/yahns/wbuf_common.rb +5 -10
  30. data/lib/yahns/wbuf_lite.rb +111 -0
  31. data/man/yahns-rackup.1 +29 -29
  32. data/man/yahns_config.5 +47 -35
  33. data/test/helper.rb +12 -0
  34. data/test/test_auto_chunk.rb +56 -0
  35. data/test/test_extras_exec_cgi.rb +1 -3
  36. data/test/test_extras_try_gzip_static.rb +30 -16
  37. data/test/test_output_buffering.rb +5 -1
  38. data/test/test_proxy_pass.rb +2 -2
  39. data/test/test_proxy_pass_no_buffering.rb +170 -0
  40. data/test/test_reopen_logs.rb +5 -1
  41. data/test/test_response.rb +42 -0
  42. data/test/test_server.rb +35 -0
  43. data/test/test_ssl.rb +0 -6
  44. data/test/test_tmpio.rb +4 -0
  45. data/test/test_wbuf.rb +11 -4
  46. metadata +10 -4
  47. data/lib/yahns/sendfile_compat.rb +0 -24
@@ -0,0 +1,164 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2013-2016 all contributors <yahns-public@yhbt.net>
3
+ # License: GPL-3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
4
+ # frozen_string_literal: true
5
+ # Only used by Yahns::ProxyPass
6
+ require 'kcar' # gem install kcar
7
+ require 'kgio'
8
+
9
+ class Yahns::ReqRes < Kgio::Socket # :nodoc:
10
+ attr_accessor :resbuf
11
+ attr_accessor :proxy_trailers
12
+ attr_accessor :alive
13
+ attr_reader :proxy_pass
14
+
15
+ def req_start(c, req, input, chunked, proxy_pass)
16
+ @hdr = @resbuf = nil
17
+ @yahns_client = c
18
+ @rrstate = input ? [ req, input, chunked ] : req
19
+ @proxy_pass = proxy_pass
20
+ Thread.current[:yahns_queue].queue_add(self, Yahns::Queue::QEV_WR)
21
+ end
22
+
23
+ def yahns_step # yahns event loop entry point
24
+ c = @yahns_client
25
+ case req = @rrstate
26
+ when Kcar::Parser # reading response...
27
+ buf = Thread.current[:yahns_rbuf]
28
+
29
+ case resbuf = @resbuf # where are we at the response?
30
+ when nil # common case, catch the response header in a single read
31
+
32
+ case rv = kgio_tryread(0x2000, buf)
33
+ when String
34
+ if res = req.headers(@hdr = [], rv)
35
+ return c.proxy_response_start(res, rv, req, self)
36
+ else # ugh, big headers or tricked response
37
+ # we must reinitialize the thread-local rbuf if it may
38
+ # live beyond the current thread
39
+ buf = Thread.current[:yahns_rbuf] = ''.dup
40
+ @resbuf = rv
41
+ end
42
+ # continue looping in middle "case @resbuf" loop
43
+ when :wait_readable
44
+ return rv # spurious wakeup
45
+ when nil
46
+ return c.proxy_err_response(502, self, 'upstream EOF (headers)')
47
+ end # NOT looping here
48
+
49
+ when String # continue reading trickled response headers from upstream
50
+
51
+ case rv = kgio_tryread(0x2000, buf)
52
+ when String then res = req.headers(@hdr, resbuf << rv) and break
53
+ when :wait_readable then return rv
54
+ when nil
55
+ return c.proxy_err_response(502, self, 'upstream EOF (big headers)')
56
+ end while true
57
+ @resbuf = false
58
+
59
+ return c.proxy_response_start(res, resbuf, req, self)
60
+
61
+ when Yahns::WbufCommon # streaming/buffering the response body
62
+
63
+ return c.proxy_response_finish(req, self)
64
+
65
+ end while true # case @resbuf
66
+
67
+ when Array # [ (str|vec), rack.input, chunked? ]
68
+ send_req_body(req) # returns nil or :wait_writable
69
+ when String # buffered request header
70
+ send_req_buf(req)
71
+ end
72
+ rescue => e
73
+ # avoid polluting logs with a giant backtrace when the problem isn't
74
+ # fixable in code.
75
+ case e
76
+ when Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EPIPE
77
+ e.set_backtrace([])
78
+ end
79
+ c.proxy_err_response(502, self, e)
80
+ end
81
+
82
+ def send_req_body_chunk(buf)
83
+ case rv = String === buf ? kgio_trywrite(buf) : kgio_trywritev(buf)
84
+ when String, Array
85
+ buf.replace(rv) # retry loop on partial write
86
+ when :wait_writable, nil
87
+ # :wait_writable = upstream is reading slowly and making us wait
88
+ return rv
89
+ else
90
+ abort "BUG: #{rv.inspect} from kgio_trywrite*"
91
+ end while true
92
+ end
93
+
94
+ # returns :wait_readable if complete, :wait_writable if not
95
+ def send_req_body(req) # @rrstate == [ (str|vec), rack.input, chunked? ]
96
+ buf, input, chunked = req
97
+
98
+ # send the first buffered chunk or vector
99
+ rv = send_req_body_chunk(buf) and return rv # :wait_writable
100
+
101
+ # yay, sent the first chunk, now read the body!
102
+ rbuf = buf
103
+ if chunked
104
+ if String === buf # initial body
105
+ req[0] = buf = []
106
+ else
107
+ # try to reuse the biggest non-frozen buffer we just wrote;
108
+ rbuf = buf.max_by(&:size)
109
+ rbuf = ''.dup if rbuf.frozen? # unlikely...
110
+ end
111
+ end
112
+
113
+ # Note: input (env['rack.input']) is fully-buffered by default so
114
+ # we should not be waiting on a slow network resource when reading
115
+ # input. However, some weird configs may disable this on LANs
116
+ # and we may wait indefinitely on input.read here...
117
+ while input.read(0x2000, rbuf)
118
+ if chunked
119
+ buf[0] = "#{rbuf.size.to_s(16)}\r\n".freeze
120
+ buf[1] = rbuf
121
+ buf[2] = "\r\n".freeze
122
+ end
123
+ rv = send_req_body_chunk(buf) and return rv # :wait_writable
124
+ end
125
+
126
+ rbuf.clear # all done, clear the big buffer
127
+
128
+ # we cannot use respond_to?(:close) here since Rack::Lint::InputWrapper
129
+ # tries to prevent that (and hijack means all Rack specs go out the door)
130
+ case input
131
+ when Yahns::TeeInput, IO
132
+ input.close
133
+ end
134
+
135
+ # note: we do not send any trailer, they are folded into the header
136
+ # because this relies on full request buffering
137
+ # prepare_wait_readable is called by send_req_buf
138
+ chunked ? send_req_buf("0\r\n\r\n".freeze) : prepare_wait_readable
139
+ rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN
140
+ # no more reading off the client socket, just prepare to forward
141
+ # the rejection response from the upstream (if any)
142
+ @yahns_client.to_io.shutdown(Socket::SHUT_RD)
143
+ prepare_wait_readable
144
+ end
145
+
146
+ def prepare_wait_readable
147
+ @rrstate = Kcar::Parser.new
148
+ :wait_readable # all done sending the request, wait for response
149
+ end
150
+
151
+ # n.b. buf must be a detached string not shared with
152
+ # Thread.current[:yahns_rbuf] of any thread
153
+ def send_req_buf(buf)
154
+ case rv = kgio_trywrite(buf)
155
+ when String
156
+ buf = rv # retry inner loop
157
+ when :wait_writable
158
+ @rrstate = buf
159
+ return :wait_writable
160
+ when nil
161
+ return prepare_wait_readable
162
+ end while true
163
+ end
164
+ end # class ReqRes
data/lib/yahns/server.rb CHANGED
@@ -496,7 +496,8 @@ class Yahns::Server # :nodoc:
496
496
  def dropping(fdmap)
497
497
  if drop_acceptors[0] || fdmap.size > 0
498
498
  timeout = @shutdown_expire < Yahns.now ? -1 : @shutdown_timeout
499
- fdmap.desperate_expire(timeout)
499
+ n = fdmap.desperate_expire(timeout)
500
+ $0 = "yahns quitting, #{n} FD(s) remain"
500
501
  true
501
502
  else
502
503
  false
@@ -159,7 +159,7 @@ module Yahns::ServerMP # :nodoc:
159
159
  def mp_sig_handle(watch, alive)
160
160
  # not performance critical
161
161
  watch.delete_if { |io| io.to_io.closed? }
162
- if r = IO.select(watch, nil, nil, alive ? nil : 0.01)
162
+ if r = IO.select(watch, nil, nil, alive ? nil : 0.1)
163
163
  r[0].each(&:yahns_step)
164
164
  end
165
165
  case @sig_queue.shift
data/lib/yahns/version.rb CHANGED
@@ -1 +1 @@
1
- Yahns::VERSION = '1.12.5'.freeze # :nodoc:
1
+ Yahns::VERSION = '1.13.0'.freeze # :nodoc:
data/lib/yahns/wbuf.rb CHANGED
@@ -31,15 +31,13 @@ require_relative 'wbuf_common'
31
31
  class Yahns::Wbuf # :nodoc:
32
32
  include Yahns::WbufCommon
33
33
  attr_reader :busy
34
- attr_reader :wbuf_persist
35
34
 
36
- def initialize(body, persist, tmpdir, busy)
35
+ def initialize(body, persist)
37
36
  @tmpio = nil
38
- @tmpdir = tmpdir
39
37
  @sf_offset = @sf_count = 0
40
38
  @wbuf_persist = persist # whether or not we keep the connection alive
41
- @body = body
42
- @busy = busy # may be false
39
+ @body = body # something we call #close on when done writing
40
+ @busy = false
43
41
  end
44
42
 
45
43
  def wbuf_writev(buf)
@@ -59,7 +57,8 @@ class Yahns::Wbuf # :nodoc:
59
57
  @busy = rv
60
58
  end until @busy
61
59
 
62
- @tmpio ||= Yahns::TmpIO.new(@tmpdir)
60
+ @tmpio ||= Yahns::TmpIO.new(c.class.output_buffer_tmpdir)
61
+ # n.b.: we rely on O_APPEND in TmpIO, here
63
62
  @sf_count += String === buf ? @tmpio.write(buf) : wbuf_writev(buf)
64
63
 
65
64
  # we spent some time copying to the FS, try to write to
@@ -2,14 +2,6 @@
2
2
  # Copyright (C) 2013-2016 all contributors <yahns-public@yhbt.net>
3
3
  # License: GPL-3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
4
4
  # frozen_string_literal: true
5
- begin
6
- raise LoadError, "SENDFILE_BROKEN env set" if ENV["SENDFILE_BROKEN"]
7
- require 'sendfile'
8
- rescue LoadError
9
- require_relative 'sendfile_compat'
10
- IO.__send__ :include, Yahns::SendfileCompat
11
- end
12
-
13
5
  module Yahns::WbufCommon # :nodoc:
14
6
  # returns true / false for persistent/non-persistent connections
15
7
  # returns :wait_*able when blocked
@@ -19,7 +11,10 @@ module Yahns::WbufCommon # :nodoc:
19
11
  def wbuf_flush(client)
20
12
  case rv = client.trysendfile(@tmpio, @sf_offset, @sf_count)
21
13
  when Integer
22
- return wbuf_close(client) if (@sf_count -= rv) == 0 # all sent!
14
+ if (@sf_count -= rv) == 0 # all sent!
15
+ @sf_offset = 0
16
+ return wbuf_close(client)
17
+ end
23
18
 
24
19
  @sf_offset += rv # keep going otherwise
25
20
  when :wait_writable, :wait_readable
@@ -48,7 +43,7 @@ module Yahns::WbufCommon # :nodoc:
48
43
  if @wbuf_persist.respond_to?(:call) # hijack
49
44
  client.response_hijacked(@wbuf_persist) # :ignore
50
45
  else
51
- @wbuf_persist # true or false or Yahns::StreamFile
46
+ @wbuf_persist # true, false, :ignore, or Yahns::StreamFile
52
47
  end
53
48
  end
54
49
  end
@@ -0,0 +1,111 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2016 all contributors <yahns-public@yhbt.net>
3
+ # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
4
+ # frozen_string_literal: true
5
+ require 'stringio'
6
+ require_relative 'wbuf_common'
7
+
8
+ # This is only used for "proxy_buffering: false"
9
+ class Yahns::WbufLite # :nodoc:
10
+ include Yahns::WbufCommon
11
+ attr_reader :busy
12
+ attr_writer :req_res
13
+
14
+ def initialize(req_res)
15
+ @tmpio = nil
16
+ @sf_offset = @sf_count = 0
17
+ @wbuf_persist = :ignore
18
+ @busy = false
19
+ @req_res = req_res
20
+ end
21
+
22
+ def wbuf_write(c, buf)
23
+ buf = buf.join if Array === buf
24
+ # try to bypass the VFS layer and write directly to the socket
25
+ # if we're all caught up
26
+ case rv = c.kgio_trywrite(buf)
27
+ when String
28
+ buf = rv # retry in loop
29
+ when nil
30
+ return # yay! hopefully we don't have to buffer again
31
+ when :wait_writable, :wait_readable
32
+ @busy = rv
33
+ end until @busy
34
+
35
+ @tmpio ||= StringIO.new(''.dup) # relies on encoding: binary above
36
+ @tmpio.seek(0, 2) # fake O_APPEND behavior
37
+ @sf_count += @tmpio.write(buf)
38
+
39
+ # we spent some time copying to the FS, try to write to
40
+ # the socket again in case some space opened up...
41
+ case rv = c.trysendio(@tmpio, @sf_offset, @sf_count)
42
+ when Integer
43
+ @sf_count -= rv
44
+ @sf_offset += rv
45
+ when :wait_writable, :wait_readable
46
+ @busy = rv
47
+ return rv
48
+ else
49
+ raise "BUG: #{rv.nil? ? 'EOF' : rv.inspect} on " \
50
+ "tmpio.size=#{@tmpio.size} " \
51
+ "sf_offset=#@sf_offset sf_count=#@sf_count"
52
+ end while @sf_count > 0
53
+
54
+ # we're all caught up, try to save some memory if we can help it.
55
+ wbuf_abort
56
+ @busy = false
57
+ nil
58
+ rescue
59
+ @req_res = @req_res.close if @req_res
60
+ raise
61
+ end
62
+
63
+ def wbuf_flush(client)
64
+ case rv = client.trysendio(@tmpio, @sf_offset, @sf_count)
65
+ when Integer
66
+ return wbuf_close(client) if (@sf_count -= rv) == 0 # all sent!
67
+ @sf_offset += rv # keep going otherwise
68
+ when :wait_writable, :wait_readable
69
+ return rv
70
+ else
71
+ raise "BUG: #{rv.nil? ? 'EOF' : rv.inspect} on " \
72
+ "tmpio.size=#{@tmpio.size} " \
73
+ "sf_offset=#@sf_offset sf_count=#@sf_count"
74
+ end while @sf_count > 0
75
+ wbuf_close(client)
76
+ rescue
77
+ @wbuf_persist = false # ensure a hijack response is not called
78
+ @req_res = @req_res.close if @req_res
79
+ wbuf_close(client)
80
+ raise
81
+ end
82
+
83
+ # called by Yahns::HttpClient#step_write
84
+ def wbuf_close(client)
85
+ wbuf_abort if @tmpio
86
+
87
+ # resume the event loop when @blocked is empty
88
+ # The actual Yahns::ReqRes#yahns_step is actually read/write-event
89
+ # agnostic, and we should actually watch for writability here since
90
+ # the req_res socket itself could be completely drained of readable
91
+ # data and just waiting for another request (which we don't support, yet)
92
+ if @req_res
93
+ @busy = false
94
+ client.hijack_cleanup
95
+ Thread.current[:yahns_queue].queue_mod(@req_res, Yahns::Queue::QEV_WR)
96
+ return :ignore
97
+ end
98
+ @wbuf_persist
99
+ rescue
100
+ @req_res = @req_res.close if @req_res
101
+ raise
102
+ end
103
+
104
+ def wbuf_abort
105
+ @sf_offset = @sf_count = 0
106
+ # we can safely truncate since this is a StringIO, we cannot do this
107
+ # with a real file because zero-copy with sendfile means truncating
108
+ # a while could clobber in-flight data
109
+ @tmpio.truncate(0)
110
+ end
111
+ end
data/man/yahns-rackup.1 CHANGED
@@ -1,4 +1,4 @@
1
- .\" Automatically generated by Pod::Man 2.25 (Pod::Simple 3.16)
1
+ .\" Automatically generated by Pod::Man 2.28 (Pod::Simple 3.28)
2
2
  .\"
3
3
  .\" Standard preamble:
4
4
  .\" ========================================================================
@@ -38,6 +38,8 @@
38
38
  . ds PI \(*p
39
39
  . ds L" ``
40
40
  . ds R" ''
41
+ . ds C`
42
+ . ds C'
41
43
  'br\}
42
44
  .\"
43
45
  .\" Escape single quotes in literal strings from groff's Unicode transform.
@@ -48,17 +50,24 @@
48
50
  .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
49
51
  .\" entries marked with X<> in POD. Of course, you'll have to process the
50
52
  .\" output yourself in some meaningful fashion.
51
- .ie \nF \{\
52
- . de IX
53
- . tm Index:\\$1\t\\n%\t"\\$2"
53
+ .\"
54
+ .\" Avoid warning from groff about undefined register 'F'.
55
+ .de IX
54
56
  ..
55
- . nr % 0
56
- . rr F
57
- .\}
58
- .el \{\
59
- . de IX
57
+ .nr rF 0
58
+ .if \n(.g .if rF .nr rF 1
59
+ .if (\n(rF:(\n(.g==0)) \{
60
+ . if \nF \{
61
+ . de IX
62
+ . tm Index:\\$1\t\\n%\t"\\$2"
60
63
  ..
64
+ . if !\nF==2 \{
65
+ . nr % 0
66
+ . nr F 2
67
+ . \}
68
+ . \}
61
69
  .\}
70
+ .rr rF
62
71
  .\"
63
72
  .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
64
73
  .\" Fear. Run. Save yourself. No user-serviceable parts.
@@ -124,7 +133,7 @@
124
133
  .\" ========================================================================
125
134
  .\"
126
135
  .IX Title "YAHNS-RACKUP 1"
127
- .TH YAHNS-RACKUP 1 "1994-10-02" "yahns 1.12.4.8.gd5d0" "yahns user manual"
136
+ .TH YAHNS-RACKUP 1 "1994-10-02" "yahns 1.12.5.48.g013d" "yahns user manual"
128
137
  .\" For nroff, turn off justification. Always turn off hyphenation; it makes
129
138
  .\" way too many mistakes in technical documents.
130
139
  .if n .ad l
@@ -140,7 +149,7 @@ A \fIrackup\fR\|(1)\-like command to launch Rack applications using yahns.
140
149
  It is expected to start in your application root (\s-1APP_ROOT\s0).
141
150
  .SH "RACKUP FILE"
142
151
  .IX Header "RACKUP FILE"
143
- This defaults to \*(L"config.ru\*(R" in \s-1APP_ROOT\s0. It should be the same
152
+ This defaults to \*(L"config.ru\*(R" in \s-1APP_ROOT. \s0 It should be the same
144
153
  file used by \fIrackup\fR\|(1) and other Rack launchers, it uses the
145
154
  *Rack::Builder* \s-1DSL\s0 documented at:
146
155
  .PP
@@ -157,8 +166,8 @@ default timeout. Lower this if you run out of FDs.
157
166
  Default: 15 (seconds)
158
167
  .IP "\-O listen=ADDRESS[,ADDRESS...]" 4
159
168
  .IX Item "-O listen=ADDRESS[,ADDRESS...]"
160
- Listens on a given \s-1ADDRESS\s0. \s-1ADDRESS\s0 may be in the form of
161
- \&\s-1HOST:PORT\s0 or \s-1PATH\s0, \s-1HOST:PORT\s0 is taken to mean a \s-1TCP\s0 socket
169
+ Listens on a given \s-1ADDRESS. ADDRESS\s0 may be in the form of
170
+ \&\s-1HOST:PORT\s0 or \s-1PATH, HOST:PORT\s0 is taken to mean a \s-1TCP\s0 socket
162
171
  and \s-1PATH\s0 is meant to be a path to a \s-1UNIX\s0 domain socket.
163
172
  Defaults to \*(L"0.0.0.0:9292\*(R" (all addresses on \s-1TCP\s0 port 9292).
164
173
  Multiple addresses may be separated with commas.
@@ -204,11 +213,11 @@ Unless specified via stderr_path and stdout_path, stderr and stdout will
204
213
  also be redirected to \*(L"/dev/null\*(R".
205
214
  .IP "\-E, \-\-env \s-1RACK_ENV\s0" 4
206
215
  .IX Item "-E, --env RACK_ENV"
207
- Run under the given \s-1RACK_ENV\s0. See the \*(L"\s-1RACK\s0 \s-1ENVIRONMENT\s0\*(R" section
216
+ Run under the given \s-1RACK_ENV. \s0 See the \*(L"\s-1RACK ENVIRONMENT\*(R"\s0 section
208
217
  for more details.
209
218
  .IP "\-o, \-\-host \s-1HOST\s0" 4
210
219
  .IX Item "-o, --host HOST"
211
- Listen on a \s-1TCP\s0 socket belonging to \s-1HOST\s0, default is
220
+ Listen on a \s-1TCP\s0 socket belonging to \s-1HOST,\s0 default is
212
221
  \&\*(L"0.0.0.0\*(R" (all addresses).
213
222
  If specified multiple times on the command-line, only the
214
223
  last-specified value takes effect.
@@ -216,7 +225,7 @@ This option only exists for compatibility with the \fIrackup\fR\|(1) command,
216
225
  use of \*(L"\-l\*(R"/\*(L"\-\-listen\*(R" switch is recommended instead.
217
226
  .IP "\-p, \-\-port \s-1PORT\s0" 4
218
227
  .IX Item "-p, --port PORT"
219
- Listen on the specified \s-1TCP\s0 \s-1PORT\s0, default is 9292.
228
+ Listen on the specified \s-1TCP PORT,\s0 default is 9292.
220
229
  If specified multiple times on the command-line, only the last-specified
221
230
  value takes effect.
222
231
  This option only exists for compatibility with the \fIrackup\fR\|(1) command,
@@ -256,24 +265,15 @@ See Rack documentation for a description of the rackup file format.
256
265
  The \s-1RACK_ENV\s0 variable is set by the aforementioned \-E switch.
257
266
  If \s-1RACK_ENV\s0 is already set, it will be used unless \-E is used.
258
267
  See rackup documentation for more details.
259
- .SH "CAVEATS"
260
- .IX Header "CAVEATS"
261
- yahns is strict about buggy, non-compliant Rack applications.
262
- Some existing servers work fine without \*(L"Content-Length\*(R" or
263
- \&\*(L"Transfer-Encoding: chunked\*(R" response headers enforced by Rack::Lint.
264
- Forgetting these headers with yahns causes clients to stall as they
265
- assume more data is coming. Loading the Rack::ContentLength and/or
266
- Rack::Chunked middlewares will set the necessary response headers
267
- and fix your app.
268
268
  .SH "CONTACT"
269
269
  .IX Header "CONTACT"
270
- All feedback welcome via plain-text mail to mailto:yahns\-public@yhbt.net <mailto:yahns-public@yhbt.net>
270
+ All feedback welcome via plain-text mail to <mailto:yahns\-public@yhbt.net>
271
271
  No subscription is necessary to post to the mailing list.
272
- List archives are available at http://yhbt.net/yahns\-public/ <http://yhbt.net/yahns-public/>
272
+ List archives are available at <http://yhbt.net/yahns\-public/>
273
273
  .SH "COPYRIGHT"
274
274
  .IX Header "COPYRIGHT"
275
- Copyright (C) 2013\-2016 all contributors mailto:yahns\-public@yhbt.net <mailto:yahns-public@yhbt.net>
276
- License: \s-1GPL\-3\s0.0+ http://www.gnu.org/licenses/gpl\-3.0.txt <http://www.gnu.org/licenses/gpl-3.0.txt>
275
+ Copyright (C) 2013\-2016 all contributors <mailto:yahns\-public@yhbt.net>
276
+ License: \s-1GPL\-3.0+ \s0<http://www.gnu.org/licenses/gpl\-3.0.txt>
277
277
  .SH "SEE ALSO"
278
278
  .IX Header "SEE ALSO"
279
279
  \&\fIyahns\fR\|(1), \fIyahns_config\fR\|(5),