yahns Ruby server user/dev discussion
 help / color / mirror / code / Atom feed
Search results ordered by [date|relevance]  view[summary|nested|Atom feed]
thread overview below | download mbox.gz: |
* [ANN] yahns 1.13.0 -_- sleepy app server for Ruby
@ 2016-08-05  7:44  5% Eric Wong
  0 siblings, 0 replies; 3+ results
From: Eric Wong @ 2016-08-05  7:44 UTC (permalink / raw)
  To: ruby-talk; +Cc: yahns-public

A Free Software, multi-threaded, non-blocking network
application server designed for low _idle_ power consumption.
It is primarily optimized for applications with occasional users
which see little or no traffic.  yahns currently hosts Rack/HTTP
applications, but may eventually support other application
types.  Unlike some existing servers, yahns is extremely
sensitive to fatal bugs in the applications it hosts.

* git clone git://yhbt.net/yahns
* https://yahns.yhbt.net/README
* https://yahns.yhbt.net/NEWS.atom.xml
* we only accept plain-text email yahns-public@yhbt.net
* and archive all the mail we receive: https://yhbt.net/yahns-public/
* nntp://news.public-inbox.org/inbox.comp.lang.ruby.yahns

lrg nabgure ubeevoyl-anzrq freire :>

Changes:

    yahns 1.13.0 - some user-visible improvements...

    And probably a billion new regressions!

    yahns now allows users to skip the Rack::Head, Rack::Chunked and
    Rack::ContentLength middlewares to ease migrating from/to other
    real-world Rack HTTP servers.  Most notably, our chunked
    encoding implementation is a bit faster than Rack::Chunked by
    taking advantage of the writev(2) syscall:

      https://yhbt.net/yahns-public/20160803031906.14553-4-e@80x24.org/

    There's also rack 2.x fixes in the test case and extras/ section
    (these incompatibilities did not affect existing users unless
    they use the wonky extras/ section).

    There's also some graceful shutdown fixes, the process title is
    now changed to display the number of live FDs.

    Of course, there's the usual round of documentation improvements
    which are systemd and OpenSSL setup-related this time around.

    However, the majority of changes (proxy_*, wbuf_lite), affect
    currently-unadvertised functionality which is subject to removal
    or incompatible config changes.  However, they are used to serve
    our mailing list archives at:

            https://yhbt.net/yahns-public/

    49 changes since yahns 1.12.5:
          proxy_pass: simplify writing request bodies upstream
          proxy_pass: hoist out proxy_res_headers method
          proxy_pass: simplify proxy_http_response
          proxy_pass: split out body and trailer reading in response
          proxy_pass: trim down proxy_response_finish, too
          proxy_pass: split out req_res into a separate file
          proxy_pass: fix resumes after complete buffering is unblocked
          proxy_pass: X-Forwarded-For appends to existing list
          proxy_pass: pass entire object to proxy_http_response
          proxy_pass: support "proxy_buffering: false"
          proxy_pass: remove unnecessary rescue
          req_res: store proxy_pass object here, instead
          proxy_pass: redo "proxy_buffering: false"
          wbuf: remove needless "busy" parameter
          Merge branch 'maint'
          extras/try_gzip_static: do not show backtrace on syscall errors
          wbuf: remove tmpdir parameter
          wbuf_lite: fix write retries for OpenSSL sockets
          test_proxy_pass_no_buffering: fix racy test
          queue_*: check for closed IO objects
          cleanup graceful shutdown handling
          proxy_pass: more descriptive error messages
          proxy_pass: fix HTTP/1.0 backends on EOF w/o buffering
          wbuf_common: reset offset counter when done
          extras/try_gzip_static: resolve symlinks
          test_ssl: remove unnecessary priv_key DH parameter
          openssl_client: wrap shutdown for graceful termination
          proxy_pass: keep trailer buffer on blocked client writes
          proxy_pass: avoid TOCTTOU race when unbuffering, too
          proxy_pass: avoid accessing logger in env after hijacking
          proxy_pass: avoid stuck responses in "proxy_buffering: false"
          extras: include status messages in responses
          update init and add systemd examples
          test_proxy_pass_no_buffering: exclude rb/ru files, too
          wbuf_lite: use StringIO instead of TmpIO
          wbuf_lite: truncate StringIO when done
          wbuf_lite: prevent clobbering responses
          wbuf_lite: unify EOF error handling
          wbuf_lite: reset sf_offset/sf_count consistently
          wbuf_lite: clear @busy flag when re-arming
          http_response: drop bodies for non-compliant responses
          fix rack 2.x compatibility bugs
          doc: add session cache usage to OpenSSL example
          test: skip some buffering tests on non-default values
          response: drop clients after HTTP responses of unknown length
          response: reduce stack overhead for parameter passing
          response: support auto-chunking for HTTP/1.1
          Revert "document Rack::Chunked/ContentLength semi-requirements"
          extras/exec_cgi: fix for HTTPoxy vulnerability

Please note the disclaimer:

  yahns is extremely sensitive to fatal bugs in the apps it hosts.  There
  is no (and never will be) any built-in "watchdog"-type feature to kill
  stuck processes/threads.  Each yahns process may be handling thousands
  of clients; unexpectedly killing the process will abort _all_ of those
  connections.  Lives may be lost!

  yahns hackers are not responsible for your application/library bugs.
  Use an application server which is tolerant of buggy applications
  if you cannot be bothered to fix all your fatal bugs.
-- 
EW

^ permalink raw reply	[relevance 5%]

* [PATCH 0/4] remove chunked/Content-Length requirement from apps
@ 2016-08-03  3:19  7% Eric Wong
  2016-08-03  3:19  5% ` [PATCH 3/4] response: support auto-chunking for HTTP/1.1 Eric Wong
  0 siblings, 1 reply; 3+ results
From: Eric Wong @ 2016-08-03  3:19 UTC (permalink / raw)
  To: yahns-public

This better aligns with behavior of other real-world Rack
servers such as puma and Thin.  We will also now use kgio writev
functionality to speed up writing "Transfer-Encoding: chunked"
to avoid string copies or extra syscalls for other chunking
solutions.

See [PATCH 3/4] for a small informal benchmark.

4 changes
  response: drop clients after HTTP responses of unknown length
  response: reduce stack overhead for parameter passing
  response: support auto-chunking for HTTP/1.1
  Revert "document Rack::Chunked/ContentLength semi-requirements"

 Documentation/yahns-rackup.pod    | 10 -------
 examples/yahns_rack_basic.conf.rb |  6 -----
 lib/yahns/chunk_body.rb           | 27 +++++++++++++++++++
 lib/yahns/http_client.rb          | 24 ++++++++---------
 lib/yahns/http_response.rb        | 47 +++++++++++++++++++++++---------
 test/test_auto_chunk.rb           | 56 +++++++++++++++++++++++++++++++++++++++
 test/test_extras_exec_cgi.rb      |  4 +--
 7 files changed, 130 insertions(+), 44 deletions(-)
 create mode 100644 lib/yahns/chunk_body.rb
 create mode 100644 test/test_auto_chunk.rb

-- 
EW

^ permalink raw reply	[relevance 7%]

* [PATCH 3/4] response: support auto-chunking for HTTP/1.1
  2016-08-03  3:19  7% [PATCH 0/4] remove chunked/Content-Length requirement from apps Eric Wong
@ 2016-08-03  3:19  5% ` Eric Wong
  0 siblings, 0 replies; 3+ results
From: Eric Wong @ 2016-08-03  3:19 UTC (permalink / raw)
  To: yahns-public

We might as well do it since puma and thin both do(*),
and we can still do writev for now to get some speedups
by avoiding Rack::Chunked overhead.

timing runs of "curl --no-buffer http://127.0.0.1:9292/ >/dev/null"
results in a best case drop from ~260ms to ~205ms on one VM
by disabling Rack::Chunked in the below config.ru

$ ruby -I lib bin/yahns-rackup -E none config.ru

==> config.ru <==
class Body
  STR = ' ' * 1024 * 16
  def each
    10000.times { yield STR }
  end
end

use Rack::Chunked if ENV['RACK_CHUNKED']
run(lambda do |env|
  [ 200, [ %w(Content-Type text/plain) ], Body.new ]
end)

(*) they can do Content-Length, but I don't think it's
    worth the effort at the server level.
---
 lib/yahns/chunk_body.rb    | 27 ++++++++++++++++++++++
 lib/yahns/http_client.rb   |  8 +++----
 lib/yahns/http_response.rb | 38 +++++++++++++++++++++----------
 test/test_auto_chunk.rb    | 56 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 113 insertions(+), 16 deletions(-)
 create mode 100644 lib/yahns/chunk_body.rb
 create mode 100644 test/test_auto_chunk.rb

diff --git a/lib/yahns/chunk_body.rb b/lib/yahns/chunk_body.rb
new file mode 100644
index 0000000..6e56a18
--- /dev/null
+++ b/lib/yahns/chunk_body.rb
@@ -0,0 +1,27 @@
+# -*- encoding: binary -*-
+# Copyright (C) 2016 all contributors <yahns-public@yhbt.net>
+# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
+# frozen_string_literal: true
+class Yahns::ChunkBody
+  def initialize(body, vec)
+    @body = body
+    @vec = vec
+  end
+
+  def each
+    vec = @vec
+    vec[2] = "\r\n".freeze
+    @body.each do |chunk|
+      vec[0] = "#{chunk.bytesize.to_s(16)}\r\n"
+      vec[1] = chunk
+      # vec[2] never changes: "\r\n" above
+      yield vec
+    end
+    vec.clear
+    yield "0\r\n\r\n".freeze
+  end
+
+  def close
+    @body.close if @body.respond_to?(:close)
+  end
+end
diff --git a/lib/yahns/http_client.rb b/lib/yahns/http_client.rb
index 7a1bac1..d8154a4 100644
--- a/lib/yahns/http_client.rb
+++ b/lib/yahns/http_client.rb
@@ -190,10 +190,10 @@ def r100_done
       true
     else # :lazy, false
       env = @hs.env
-      hdr_only = env['REQUEST_METHOD'] == 'HEAD'.freeze
+      opt = http_response_prep(env)
       res = k.app.call(env)
       return :ignore if app_hijacked?(env, res)
-      http_response_write(res, hdr_only)
+      http_response_write(res, opt)
     end
   end
 
@@ -222,7 +222,7 @@ def app_call(input)
       env['SERVER_PORT'] = '443'.freeze
     end
 
-    hdr_only = env['REQUEST_METHOD'] == 'HEAD'.freeze
+    opt = http_response_prep(env)
     # run the rack app
     res = k.app.call(env)
     return :ignore if app_hijacked?(env, res)
@@ -232,7 +232,7 @@ def app_call(input)
     end
 
     # this returns :wait_readable, :wait_writable, :ignore, or nil:
-    http_response_write(res, hdr_only)
+    http_response_write(res, opt)
   end
 
   # called automatically by kgio_write
diff --git a/lib/yahns/http_response.rb b/lib/yahns/http_response.rb
index b157ee4..32d1a45 100644
--- a/lib/yahns/http_response.rb
+++ b/lib/yahns/http_response.rb
@@ -4,12 +4,14 @@
 # frozen_string_literal: true
 require_relative 'stream_file'
 require_relative 'wbuf_str'
+require_relative 'chunk_body'
 
 # Writes a Rack response to your client using the HTTP/1.1 specification.
 # You use it by simply doing:
 #
+#   opt = http_response_prep(env)
 #   res = rack_app.call(env)
-#   http_response_write(res, env['REQUEST_METHOD']=='HEAD')
+#   http_response_write(res, opt)
 #
 # Most header correctness (including Content-Length and Content-Type)
 # is the job of Rack, with the exception of the "Date" header.
@@ -120,14 +122,14 @@ def kv_str(buf, key, value)
 
   # writes the rack_response to socket as an HTTP response
   # returns :wait_readable, :wait_writable, :forget, or nil
-  def http_response_write(res, hdr_only)
+  def http_response_write(res, opt)
     status, headers, body = res
     offset = 0
     count = hijack = nil
-    k = self.class
-    alive = @hs.next? && k.persistent_connections
+    alive = @hs.next? && self.class.persistent_connections
     flags = MSG_DONTWAIT
     term = false
+    hdr_only, chunk_ok = opt
 
     if @hs.headers?
       code = status.to_i
@@ -161,6 +163,11 @@ def http_response_write(res, hdr_only)
           kv_str(buf, key, value)
         end
       end
+      if !term && chunk_ok
+        term = true
+        body = Yahns::ChunkBody.new(body, opt)
+        buf << "Transfer-Encoding: chunked\r\n".freeze
+      end
       alive &&= term
       buf << (alive ? "Connection: keep-alive\r\n\r\n".freeze
                     : "Connection: close\r\n\r\n".freeze)
@@ -173,7 +180,7 @@ def http_response_write(res, hdr_only)
         flags = MSG_DONTWAIT
         buf = rv # unlikely, hope the skb grows
       when :wait_writable, :wait_readable # unlikely
-        if k.output_buffering
+        if self.class.output_buffering
           alive = hijack ? hijack : alive
           rv = response_header_blocked(buf, body, alive, offset, count)
           body = nil # ensure we do not close body in ensure
@@ -193,19 +200,19 @@ def http_response_write(res, hdr_only)
     end
 
     wbuf = rv = nil
-    body.each do |chunk|
+    body.each do |x|
       if wbuf
-        rv = wbuf.wbuf_write(self, chunk)
+        rv = wbuf.wbuf_write(self, x)
       else
-        case rv = kgio_trywrite(chunk)
+        case rv = String === x ? kgio_trywrite(x) : kgio_trywritev(x)
         when nil # all done, likely and good!
           break
-        when String
-          chunk = rv # hope the skb grows when we loop into the trywrite
+        when String, Array
+          x = rv # hope the skb grows when we loop into the trywrite
         when :wait_writable, :wait_readable
-          if k.output_buffering
+          if self.class.output_buffering
             wbuf = Yahns::Wbuf.new(body, alive)
-            rv = wbuf.wbuf_write(self, chunk)
+            rv = wbuf.wbuf_write(self, x)
             break
           else
             response_wait_write(rv) or return :close
@@ -278,4 +285,11 @@ def http_100_response(env)
       return rv
     end while true
   end
+
+  # must be called before app dispatch, since the app can
+  # do all sorts of nasty things to env
+  def http_response_prep(env)
+    [ env['REQUEST_METHOD'] == 'HEAD'.freeze, # hdr_only
+      env['HTTP_VERSION'] == 'HTTP/1.1'.freeze ] # chunk_ok
+  end
 end
diff --git a/test/test_auto_chunk.rb b/test/test_auto_chunk.rb
new file mode 100644
index 0000000..a97fe26
--- /dev/null
+++ b/test/test_auto_chunk.rb
@@ -0,0 +1,56 @@
+# Copyright (C) 2013-2016 all contributors <yahns-public@yhbt.net>
+# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
+# frozen_string_literal: true
+require_relative 'server_helper'
+
+class TestAutoChunk < Testcase
+  ENV["N"].to_i > 1 and parallelize_me!
+  include ServerHelper
+  alias setup server_helper_setup
+  alias teardown server_helper_teardown
+
+  def test_auto_head
+    err = @err
+    cfg = Yahns::Config.new
+    host, port = @srv.addr[3], @srv.addr[1]
+    cfg.instance_eval do
+      GTL.synchronize do
+        app = Rack::Builder.new do
+          use Rack::ContentType, "text/plain"
+          run(lambda do |env|
+            [ 200, {}, %w(a b c) ]
+          end)
+        end
+        app(:rack, app) { listen "#{host}:#{port}" }
+      end
+      logger(Logger.new(err.path))
+    end
+    pid = mkserver(cfg)
+    s = TCPSocket.new(host, port)
+    s.write("GET / HTTP/1.0\r\n\r\n")
+    assert s.wait(30), "IO wait failed"
+    buf = s.read
+    assert_match %r{\r\n\r\nabc\z}, buf
+    s.close
+
+    s = TCPSocket.new(host, port)
+    s.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
+    buf = ''.dup
+    Timeout.timeout(30) do
+      until buf =~ /\r\n\r\n1\r\na\r\n1\r\nb\r\n1\r\nc\r\n0\r\n\r\n\z/
+        buf << s.readpartial(16384)
+      end
+    end
+    assert_match(%r{^Transfer-Encoding: chunked\r\n}, buf)
+    s.close
+
+    Net::HTTP.start(host, port) do |http|
+      req = Net::HTTP::Get.new("/")
+      res = http.request(req)
+      assert_equal 200, res.code.to_i
+      assert_equal 'abc', res.body
+    end
+  ensure
+    quit_wait(pid)
+  end
+end
-- 
EW


^ permalink raw reply related	[relevance 5%]

Results 1-3 of 3 | reverse | options above
-- pct% links below jump to the message on this page, permalinks otherwise --
2016-08-03  3:19  7% [PATCH 0/4] remove chunked/Content-Length requirement from apps Eric Wong
2016-08-03  3:19  5% ` [PATCH 3/4] response: support auto-chunking for HTTP/1.1 Eric Wong
2016-08-05  7:44  5% [ANN] yahns 1.13.0 -_- sleepy app server for Ruby Eric Wong

Code repositories for project(s) associated with this public inbox

	https://yhbt.net/yahns.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).