yahns.git  about / heads / tags
sleepy, multi-threaded, non-blocking application server for Ruby
blob 0d7bb43974ef0613e4d20d9892725b4d61ae454b 3544 bytes (raw)
$ git show the_metal:lib/yahns/the_metal.rb	# shows this blob on the CLI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
 
# -*- encoding: binary -*-
# Copyright (C) 2014, all contributors
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
#
# ref: git://github.com/tenderlove/the_metal.git
require 'the_metal'
require 'the_metal/response'
require 'the_metal/request'

class Yahns::TheMetal # :nodoc:

  # wrapper around a normal socket with full output buffering support
  class OutSock
    attr_reader :wbuf, :last_rv

    def initialize(io, persist)
      @io = io
      @wbuf = false
      @last_rv = nil
      k = io.class # Yahns::TheMetal + Yahns::HttpContext
      if k.output_buffering
        # args for Yahns::Wbuf.new
        @wbuf = [ nil, persist, k.output_buffer_tmpdir ]
      end
    end

    # TODO: MSG_MORE support?
    def write(buf)
      case wbuf = @wbuf
      when Yahns::Wbuf
        # client is reading slowly, write to buffer instead
        @last_rv = wbuf.wbuf_write(@io, buf)
      else
        case rv = @io.kgio_trywrite(buf)
        when nil
          return # all done, likely and good!
        when String
          buf = rv # hope the skb grows when we loop into the trywrite
        when :wait_writable, :wait_readable
          if @wbuf
            @wbuf = Yahns::Wbuf.new(*@wbuf)
            @last_rv = @wbuf.wbuf_write(@io, buf) # write-in-full behavior
            return
          else
            @io.__send__("kgio_#{rv}") # wait for writability
          end
        end while true
      end
    end
  end # OutSock

  class MetalClient < Yahns::HttpClient

    # returns :wait_readable, :wait_writable, :close, :ignore,
    # called from yahns_step
    def app_call(input) # override existing Rack 1.x-oriented method
      k = self.class
      env = @hs.env
      env[REMOTE_ADDR] = @kgio_addr
      env[RACK_HIJACK] = hijack_proc(env)
      env[RACK_INPUT] = input

      # TODO: check_client_connection support
      persist = @hs.next? && k.persistent_connections
      headers = {
        "Date" => httpdate, # FIXME: may be wrong by the time a header is sent
        "Connection" => persist ? "keep-alive" : "close"
      }
      req = TheMetal::Request.new(env)
      out = OutSock.new(self, persist)
      res = TheMetal::Response.new(200, headers, out)
      k.app.call(req, res) # may raise during OutSock#write

      if wbuf = out.wbuf
        # we may need to wait for writability:
        wbuf_maybe(wbuf, out.last_rv)
      else
        http_response_done(persist)
      end
    rescue
      :close
    end
  end # MetalClient

  attr_reader :preload

  # enforce a single instance for each app
  def self.instance_key(*args)
    app = args[0]
    app.object_id
  end

  def initialize(app, opts = {})
    @app = app
    @preload = opts[:preload]
    @app.start_app if @preload
  end

  def config_context
    ctx_class = Class.new(MetalClient)
    ctx_class.extend(Yahns::HttpContext)
    ctx_class.http_ctx_init(self)
    ctx_class
  end

  # allow different HttpContext instances to have different Rack defaults
  def app_defaults
    {
      # logger is set in http_context
      "rack.errors" => $stderr,
      "rack.multiprocess" => true,
      "rack.multithread" => true,
      "rack.run_once" => false,
      "rack.hijack?" => true,
      "rack.version" => [ 666, 666 ], # \m/etal\m/
      "SCRIPT_NAME" => "",

      # this is not in the Rack spec, but some apps may rely on it
      "SERVER_SOFTWARE" => "yahns"
    }
  end

  def app_after_fork
    @app.start_app unless @preload
    @app
  end
end

# register ourselves
Yahns::Config::APP_CLASS[:the_metal] = Yahns::TheMetal

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