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 6/7] proxy_pass: split out req_res into a separate file
  2016-05-16  1:43  7% [PATCH 0/7] proxy_pass cleanups Eric Wong
@ 2016-05-16  1:43  4% ` Eric Wong
  0 siblings, 0 replies; 3+ results
From: Eric Wong @ 2016-05-16  1:43 UTC (permalink / raw)
  To: yahns-public

This makes the ReqRes class easier-to-find and hopefully
maintain when using with other parts of yahns, although there
may be no reason to use this class outside of ProxyPass.
---
 lib/yahns/proxy_pass.rb | 157 +----------------------------------------------
 lib/yahns/req_res.rb    | 159 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 161 insertions(+), 155 deletions(-)
 create mode 100644 lib/yahns/req_res.rb

diff --git a/lib/yahns/proxy_pass.rb b/lib/yahns/proxy_pass.rb
index a2d7d81..8e0b742 100644
--- a/lib/yahns/proxy_pass.rb
+++ b/lib/yahns/proxy_pass.rb
@@ -3,166 +3,13 @@
 # License: GPL-3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
 # frozen_string_literal: true
 require 'socket'
-require 'kgio'
-require 'kcar' # gem install kcar
 require 'rack/request'
 require 'timeout'
 
 require_relative 'proxy_http_response'
+require_relative 'req_res'
 
 class Yahns::ProxyPass # :nodoc:
-  class ReqRes < Kgio::Socket # :nodoc:
-    attr_writer :resbuf
-    attr_accessor :proxy_trailers
-
-    def req_start(c, req, input, chunked)
-      @hdr = @resbuf = nil
-      @yahns_client = c
-      @rrstate = input ? [ req, input, chunked ] : req
-      Thread.current[:yahns_queue].queue_add(self, Yahns::Queue::QEV_WR)
-    end
-
-    def yahns_step # yahns event loop entry point
-      c = @yahns_client
-      case req = @rrstate
-      when Kcar::Parser # reading response...
-        buf = Thread.current[:yahns_rbuf]
-
-        case resbuf = @resbuf # where are we at the response?
-        when nil # common case, catch the response header in a single read
-
-          case rv = kgio_tryread(0x2000, buf)
-          when String
-            if res = req.headers(@hdr = [], rv)
-              return c.proxy_response_start(res, rv, req, self)
-            else # ugh, big headers or tricked response
-              # we must reinitialize the thread-local rbuf if it may
-              # live beyond the current thread
-              buf = Thread.current[:yahns_rbuf] = ''.dup
-              @resbuf = rv
-            end
-            # continue looping in middle "case @resbuf" loop
-          when :wait_readable
-            return rv # spurious wakeup
-          when nil then return c.proxy_err_response(502, self, nil, nil)
-          end # NOT looping here
-
-        when String # continue reading trickled response headers from upstream
-
-          case rv = kgio_tryread(0x2000, buf)
-          when String then res = req.headers(@hdr, resbuf << rv) and break
-          when :wait_readable then return rv
-          when nil then return c.proxy_err_response(502, self, nil, nil)
-          end while true
-
-          return c.proxy_response_start(res, resbuf, req, self)
-
-        when Yahns::WbufCommon # streaming/buffering the response body
-
-          # we assign wbuf for rescue below:
-          return c.proxy_response_finish(req, wbuf = resbuf, self)
-
-        end while true # case @resbuf
-
-      when Array # [ (str|vec), rack.input, chunked? ]
-        send_req_body(req) # returns nil or :wait_writable
-      when String # buffered request header
-        send_req_buf(req)
-      end
-    rescue => e
-      # avoid polluting logs with a giant backtrace when the problem isn't
-      # fixable in code.
-      case e
-      when Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EPIPE
-        e.set_backtrace([])
-      end
-      c.proxy_err_response(502, self, e, wbuf)
-    end
-
-    def send_req_body_chunk(buf)
-      case rv = String === buf ? kgio_trywrite(buf) : kgio_trywritev(buf)
-      when String, Array
-        buf.replace(rv) # retry loop on partial write
-      when :wait_writable, nil
-        # :wait_writable = upstream is reading slowly and making us wait
-        return rv
-      else
-        abort "BUG: #{rv.inspect} from kgio_trywrite*"
-      end while true
-    end
-
-    # returns :wait_readable if complete, :wait_writable if not
-    def send_req_body(req) # @rrstate == [ (str|vec), rack.input, chunked? ]
-      buf, input, chunked = req
-
-      # send the first buffered chunk or vector
-      rv = send_req_body_chunk(buf) and return rv # :wait_writable
-
-      # yay, sent the first chunk, now read the body!
-      rbuf = buf
-      if chunked
-        if String === buf # initial body
-          req[0] = buf = []
-        else
-          # try to reuse the biggest non-frozen buffer we just wrote;
-          rbuf = buf.max_by(&:size)
-          rbuf = ''.dup if rbuf.frozen? # unlikely...
-        end
-      end
-
-      # Note: input (env['rack.input']) is fully-buffered by default so
-      # we should not be waiting on a slow network resource when reading
-      # input.  However, some weird configs may disable this on LANs
-      # and we may wait indefinitely on input.read here...
-      while input.read(0x2000, rbuf)
-        if chunked
-          buf[0] = "#{rbuf.size.to_s(16)}\r\n".freeze
-          buf[1] = rbuf
-          buf[2] = "\r\n".freeze
-        end
-        rv = send_req_body_chunk(buf) and return rv # :wait_writable
-      end
-
-      rbuf.clear # all done, clear the big buffer
-
-      # we cannot use respond_to?(:close) here since Rack::Lint::InputWrapper
-      # tries to prevent that (and hijack means all Rack specs go out the door)
-      case input
-      when Yahns::TeeInput, IO
-        input.close
-      end
-
-      # note: we do not send any trailer, they are folded into the header
-      # because this relies on full request buffering
-      # prepare_wait_readable is called by send_req_buf
-      chunked ? send_req_buf("0\r\n\r\n".freeze) : prepare_wait_readable
-    rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN
-      # no more reading off the client socket, just prepare to forward
-      # the rejection response from the upstream (if any)
-      @yahns_client.to_io.shutdown(Socket::SHUT_RD)
-      prepare_wait_readable
-    end
-
-    def prepare_wait_readable
-      @rrstate = Kcar::Parser.new
-      :wait_readable # all done sending the request, wait for response
-    end
-
-    # n.b. buf must be a detached string not shared with
-    # Thread.current[:yahns_rbuf] of any thread
-    def send_req_buf(buf)
-      case rv = kgio_trywrite(buf)
-      when String
-        buf = rv # retry inner loop
-      when :wait_writable
-        @rrstate = buf
-        return :wait_writable
-      when nil
-        return prepare_wait_readable
-      end while true
-    end
-  end # class ReqRes
-
   def initialize(dest, opts = {})
     case dest
     when %r{\Aunix:([^:]+)(?::(/.*))?\z}
@@ -199,7 +46,7 @@ def init_path_vars(path)
 
   def call(env)
     # 3-way handshake for TCP backends while we generate the request header
-    rr = ReqRes.start(@sockaddr)
+    rr = Yahns::ReqRes.start(@sockaddr)
     c = env['rack.hijack'].call
 
     req = Rack::Request.new(env)
diff --git a/lib/yahns/req_res.rb b/lib/yahns/req_res.rb
new file mode 100644
index 0000000..3b0d298
--- /dev/null
+++ b/lib/yahns/req_res.rb
@@ -0,0 +1,159 @@
+# -*- encoding: binary -*-
+# 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
+# Only used by Yahns::ProxyPass
+require 'kcar' # gem install kcar
+require 'kgio'
+
+class Yahns::ReqRes < Kgio::Socket # :nodoc:
+  attr_writer :resbuf
+  attr_accessor :proxy_trailers
+
+  def req_start(c, req, input, chunked)
+    @hdr = @resbuf = nil
+    @yahns_client = c
+    @rrstate = input ? [ req, input, chunked ] : req
+    Thread.current[:yahns_queue].queue_add(self, Yahns::Queue::QEV_WR)
+  end
+
+  def yahns_step # yahns event loop entry point
+    c = @yahns_client
+    case req = @rrstate
+    when Kcar::Parser # reading response...
+      buf = Thread.current[:yahns_rbuf]
+
+      case resbuf = @resbuf # where are we at the response?
+      when nil # common case, catch the response header in a single read
+
+        case rv = kgio_tryread(0x2000, buf)
+        when String
+          if res = req.headers(@hdr = [], rv)
+            return c.proxy_response_start(res, rv, req, self)
+          else # ugh, big headers or tricked response
+            # we must reinitialize the thread-local rbuf if it may
+            # live beyond the current thread
+            buf = Thread.current[:yahns_rbuf] = ''.dup
+            @resbuf = rv
+          end
+          # continue looping in middle "case @resbuf" loop
+        when :wait_readable
+          return rv # spurious wakeup
+        when nil then return c.proxy_err_response(502, self, nil, nil)
+        end # NOT looping here
+
+      when String # continue reading trickled response headers from upstream
+
+        case rv = kgio_tryread(0x2000, buf)
+        when String then res = req.headers(@hdr, resbuf << rv) and break
+        when :wait_readable then return rv
+        when nil then return c.proxy_err_response(502, self, nil, nil)
+        end while true
+
+        return c.proxy_response_start(res, resbuf, req, self)
+
+      when Yahns::WbufCommon # streaming/buffering the response body
+
+        # we assign wbuf for rescue below:
+        return c.proxy_response_finish(req, wbuf = resbuf, self)
+
+      end while true # case @resbuf
+
+    when Array # [ (str|vec), rack.input, chunked? ]
+      send_req_body(req) # returns nil or :wait_writable
+    when String # buffered request header
+      send_req_buf(req)
+    end
+  rescue => e
+    # avoid polluting logs with a giant backtrace when the problem isn't
+    # fixable in code.
+    case e
+    when Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EPIPE
+      e.set_backtrace([])
+    end
+    c.proxy_err_response(502, self, e, wbuf)
+  end
+
+  def send_req_body_chunk(buf)
+    case rv = String === buf ? kgio_trywrite(buf) : kgio_trywritev(buf)
+    when String, Array
+      buf.replace(rv) # retry loop on partial write
+    when :wait_writable, nil
+      # :wait_writable = upstream is reading slowly and making us wait
+      return rv
+    else
+      abort "BUG: #{rv.inspect} from kgio_trywrite*"
+    end while true
+  end
+
+  # returns :wait_readable if complete, :wait_writable if not
+  def send_req_body(req) # @rrstate == [ (str|vec), rack.input, chunked? ]
+    buf, input, chunked = req
+
+    # send the first buffered chunk or vector
+    rv = send_req_body_chunk(buf) and return rv # :wait_writable
+
+    # yay, sent the first chunk, now read the body!
+    rbuf = buf
+    if chunked
+      if String === buf # initial body
+        req[0] = buf = []
+      else
+        # try to reuse the biggest non-frozen buffer we just wrote;
+        rbuf = buf.max_by(&:size)
+        rbuf = ''.dup if rbuf.frozen? # unlikely...
+      end
+    end
+
+    # Note: input (env['rack.input']) is fully-buffered by default so
+    # we should not be waiting on a slow network resource when reading
+    # input.  However, some weird configs may disable this on LANs
+    # and we may wait indefinitely on input.read here...
+    while input.read(0x2000, rbuf)
+      if chunked
+        buf[0] = "#{rbuf.size.to_s(16)}\r\n".freeze
+        buf[1] = rbuf
+        buf[2] = "\r\n".freeze
+      end
+      rv = send_req_body_chunk(buf) and return rv # :wait_writable
+    end
+
+    rbuf.clear # all done, clear the big buffer
+
+    # we cannot use respond_to?(:close) here since Rack::Lint::InputWrapper
+    # tries to prevent that (and hijack means all Rack specs go out the door)
+    case input
+    when Yahns::TeeInput, IO
+      input.close
+    end
+
+    # note: we do not send any trailer, they are folded into the header
+    # because this relies on full request buffering
+    # prepare_wait_readable is called by send_req_buf
+    chunked ? send_req_buf("0\r\n\r\n".freeze) : prepare_wait_readable
+  rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN
+    # no more reading off the client socket, just prepare to forward
+    # the rejection response from the upstream (if any)
+    @yahns_client.to_io.shutdown(Socket::SHUT_RD)
+    prepare_wait_readable
+  end
+
+  def prepare_wait_readable
+    @rrstate = Kcar::Parser.new
+    :wait_readable # all done sending the request, wait for response
+  end
+
+  # n.b. buf must be a detached string not shared with
+  # Thread.current[:yahns_rbuf] of any thread
+  def send_req_buf(buf)
+    case rv = kgio_trywrite(buf)
+    when String
+      buf = rv # retry inner loop
+    when :wait_writable
+      @rrstate = buf
+      return :wait_writable
+    when nil
+      return prepare_wait_readable
+    end while true
+  end
+end # class ReqRes

^ permalink raw reply related	[relevance 4%]

* [PATCH 0/7] proxy_pass cleanups
@ 2016-05-16  1:43  7% Eric Wong
  2016-05-16  1:43  4% ` [PATCH 6/7] proxy_pass: split out req_res into a separate file Eric Wong
  0 siblings, 1 reply; 3+ results
From: Eric Wong @ 2016-05-16  1:43 UTC (permalink / raw)
  To: yahns-public

A bunch of cleanups to hopefully make the proxy_pass-related
code a little saner and easier-to-follow.

I introduced at least one bug during this series which got fixed
in 7/7.

Extra sets of eyes to review would be greatly appreciated, thanks!

And this is running live and serving critical information to
readers of https://yhbt.net/ in all its glory!

Once I'm reasonably satisfied with this; I'll continue work
on making "proxy_buffering: false" work, so slow-client-capable
upstreams can generate gigantic (hundreds of megabytes!)
responses without filesystem overhead.

But first, I think I should work on making those gigantic
responses cheaper in terms of memory/CPU usage outside of
yahns.  This is git-http-backend for serving mega repos
over smart HTTP, yahns is already great for dumb HTTP
git clones.

 lib/yahns/proxy_http_response.rb | 236 +++++++++++++++------------------------
 lib/yahns/proxy_pass.rb          | 171 +---------------------------
 lib/yahns/req_res.rb             | 159 ++++++++++++++++++++++++++
 3 files changed, 252 insertions(+), 314 deletions(-)

Eric Wong (7):
      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

^ permalink raw reply	[relevance 7%]

Results 1-3 of 3 | reverse | options above
-- pct% links below jump to the message on this page, permalinks otherwise --
2016-05-16  1:43  7% [PATCH 0/7] proxy_pass cleanups Eric Wong
2016-05-16  1:43  4% ` [PATCH 6/7] proxy_pass: split out req_res into a separate file 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).