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
|