polyphony 0.19 → 0.20
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.rubocop.yml +87 -1
- data/CHANGELOG.md +35 -0
- data/Gemfile.lock +17 -6
- data/README.md +200 -139
- data/Rakefile +4 -4
- data/TODO.md +35 -7
- data/bin/poly +11 -0
- data/docs/getting-started/getting-started.md +1 -1
- data/docs/summary.md +3 -0
- data/docs/technical-overview/exception-handling.md +94 -0
- data/docs/technical-overview/fiber-scheduling.md +99 -0
- data/examples/core/cancel.rb +8 -4
- data/examples/core/channel_echo.rb +18 -17
- data/examples/core/defer.rb +12 -0
- data/examples/core/enumerator.rb +4 -4
- data/examples/core/fiber_error.rb +9 -0
- data/examples/core/fiber_error_with_backtrace.rb +73 -0
- data/examples/core/fork.rb +6 -6
- data/examples/core/genserver.rb +16 -8
- data/examples/core/lock.rb +3 -3
- data/examples/core/move_on.rb +4 -3
- data/examples/core/move_on_twice.rb +5 -5
- data/examples/core/move_on_with_ensure.rb +8 -11
- data/examples/core/move_on_with_value.rb +14 -0
- data/examples/core/{multiple_spawn.rb → multiple_spin.rb} +5 -5
- data/examples/core/nested_cancel.rb +5 -5
- data/examples/core/{nested_multiple_spawn.rb → nested_multiple_spin.rb} +6 -6
- data/examples/core/nested_spin.rb +17 -0
- data/examples/core/pingpong.rb +21 -0
- data/examples/core/pulse.rb +4 -5
- data/examples/core/resource.rb +6 -4
- data/examples/core/resource_cancel.rb +6 -9
- data/examples/core/resource_delegate.rb +3 -3
- data/examples/core/sleep.rb +3 -3
- data/examples/core/sleep_spin.rb +19 -0
- data/examples/core/snooze.rb +32 -0
- data/examples/core/spin.rb +14 -0
- data/examples/core/{spawn_cancel.rb → spin_cancel.rb} +6 -7
- data/examples/core/spin_error.rb +17 -0
- data/examples/core/spin_error_backtrace.rb +30 -0
- data/examples/core/spin_uncaught_error.rb +15 -0
- data/examples/core/supervisor.rb +8 -8
- data/examples/core/supervisor_with_cancel_scope.rb +7 -7
- data/examples/core/supervisor_with_error.rb +8 -8
- data/examples/core/supervisor_with_manual_move_on.rb +6 -7
- data/examples/core/suspend.rb +13 -0
- data/examples/core/thread.rb +1 -1
- data/examples/core/thread_cancel.rb +9 -11
- data/examples/core/thread_pool.rb +18 -14
- data/examples/core/throttle.rb +7 -7
- data/examples/core/timeout.rb +3 -3
- data/examples/fs/read.rb +7 -9
- data/examples/http/config.ru +7 -3
- data/examples/http/cuba.ru +22 -0
- data/examples/http/happy_eyeballs.rb +6 -4
- data/examples/http/http_client.rb +1 -1
- data/examples/http/http_get.rb +1 -1
- data/examples/http/http_parse_experiment.rb +21 -16
- data/examples/http/http_proxy.rb +28 -26
- data/examples/http/http_server.rb +10 -10
- data/examples/http/http_server_forked.rb +6 -5
- data/examples/http/http_server_throttled.rb +3 -3
- data/examples/http/http_ws_server.rb +11 -11
- data/examples/http/https_raw_client.rb +1 -1
- data/examples/http/https_server.rb +8 -8
- data/examples/http/https_wss_server.rb +13 -11
- data/examples/http/rack_server.rb +2 -2
- data/examples/http/rack_server_https.rb +4 -4
- data/examples/http/rack_server_https_forked.rb +5 -5
- data/examples/http/websocket_secure_server.rb +6 -6
- data/examples/http/websocket_server.rb +5 -5
- data/examples/interfaces/pg_client.rb +4 -4
- data/examples/interfaces/pg_pool.rb +13 -6
- data/examples/interfaces/pg_transaction.rb +5 -4
- data/examples/interfaces/redis_channels.rb +15 -11
- data/examples/interfaces/redis_client.rb +2 -2
- data/examples/interfaces/redis_pubsub.rb +2 -1
- data/examples/interfaces/redis_pubsub_perf.rb +13 -9
- data/examples/io/backticks.rb +11 -0
- data/examples/io/cat.rb +4 -5
- data/examples/io/echo_client.rb +9 -4
- data/examples/io/echo_client_from_stdin.rb +20 -0
- data/examples/io/echo_pipe.rb +7 -8
- data/examples/io/echo_server.rb +8 -6
- data/examples/io/echo_server_with_timeout.rb +13 -10
- data/examples/io/echo_stdin.rb +3 -3
- data/examples/io/httparty.rb +2 -2
- data/examples/io/httparty_multi.rb +8 -4
- data/examples/io/httparty_threaded.rb +6 -2
- data/examples/io/io_read.rb +2 -2
- data/examples/io/irb.rb +16 -4
- data/examples/io/net-http.rb +3 -3
- data/examples/io/open.rb +17 -0
- data/examples/io/system.rb +3 -3
- data/examples/io/tcpserver.rb +15 -0
- data/examples/io/tcpsocket.rb +6 -5
- data/examples/performance/multi_snooze.rb +29 -0
- data/examples/performance/{perf_snooze.rb → snooze.rb} +7 -5
- data/examples/performance/snooze_raw.rb +39 -0
- data/ext/gyro/async.c +165 -0
- data/ext/gyro/child.c +167 -0
- data/ext/{ev → gyro}/extconf.rb +4 -3
- data/ext/gyro/gyro.c +316 -0
- data/ext/{ev/ev.h → gyro/gyro.h} +12 -7
- data/ext/gyro/gyro_ext.c +23 -0
- data/ext/{ev → gyro}/io.c +65 -57
- data/ext/{ev → gyro}/libev.h +0 -0
- data/ext/gyro/signal.c +117 -0
- data/ext/{ev → gyro}/socket.c +61 -6
- data/ext/gyro/timer.c +199 -0
- data/ext/libev/Changes +35 -0
- data/ext/libev/README +2 -1
- data/ext/libev/ev.c +213 -151
- data/ext/libev/ev.h +95 -88
- data/ext/libev/ev_epoll.c +26 -15
- data/ext/libev/ev_kqueue.c +11 -5
- data/ext/libev/ev_linuxaio.c +642 -0
- data/ext/libev/ev_poll.c +13 -8
- data/ext/libev/ev_port.c +5 -2
- data/ext/libev/ev_vars.h +14 -3
- data/ext/libev/ev_wrap.h +16 -0
- data/lib/ev_ext.bundle +0 -0
- data/lib/polyphony.rb +46 -50
- data/lib/polyphony/auto_run.rb +12 -0
- data/lib/polyphony/core/cancel_scope.rb +11 -7
- data/lib/polyphony/core/channel.rb +16 -9
- data/lib/polyphony/core/coprocess.rb +101 -51
- data/lib/polyphony/core/exceptions.rb +14 -12
- data/lib/polyphony/core/resource_pool.rb +21 -8
- data/lib/polyphony/core/supervisor.rb +10 -5
- data/lib/polyphony/core/sync.rb +7 -6
- data/lib/polyphony/core/thread.rb +4 -4
- data/lib/polyphony/core/thread_pool.rb +4 -4
- data/lib/polyphony/core/throttler.rb +6 -4
- data/lib/polyphony/extensions/core.rb +253 -0
- data/lib/polyphony/extensions/io.rb +28 -16
- data/lib/polyphony/extensions/openssl.rb +2 -1
- data/lib/polyphony/extensions/socket.rb +47 -52
- data/lib/polyphony/http.rb +4 -3
- data/lib/polyphony/http/agent.rb +68 -57
- data/lib/polyphony/http/server.rb +5 -5
- data/lib/polyphony/http/server/http1.rb +268 -0
- data/lib/polyphony/http/server/http2.rb +62 -0
- data/lib/polyphony/http/server/http2_stream.rb +104 -0
- data/lib/polyphony/http/server/rack.rb +64 -0
- data/lib/polyphony/http/server/request.rb +119 -0
- data/lib/polyphony/net.rb +26 -15
- data/lib/polyphony/postgres.rb +17 -13
- data/lib/polyphony/redis.rb +16 -15
- data/lib/polyphony/version.rb +1 -1
- data/lib/polyphony/websocket.rb +11 -4
- data/polyphony.gemspec +13 -9
- data/test/eg.rb +27 -0
- data/test/helper.rb +25 -0
- data/test/run.rb +5 -0
- data/test/test_async.rb +33 -0
- data/test/test_coprocess.rb +239 -77
- data/test/test_core.rb +95 -61
- data/test/test_gyro.rb +148 -0
- data/test/test_http_server.rb +313 -0
- data/test/test_io.rb +79 -27
- data/test/test_kernel.rb +22 -12
- data/test/test_signal.rb +36 -0
- data/test/test_timer.rb +24 -0
- metadata +89 -33
- data/examples/core/nested_async.rb +0 -17
- data/examples/core/next_tick.rb +0 -12
- data/examples/core/sleep_spawn.rb +0 -19
- data/examples/core/spawn.rb +0 -14
- data/examples/core/spawn_error.rb +0 -28
- data/examples/performance/perf_multi_snooze.rb +0 -21
- data/ext/ev/async.c +0 -168
- data/ext/ev/child.c +0 -169
- data/ext/ev/ev_ext.c +0 -23
- data/ext/ev/ev_module.c +0 -242
- data/ext/ev/signal.c +0 -119
- data/ext/ev/timer.c +0 -197
- data/lib/polyphony/core/fiber_pool.rb +0 -98
- data/lib/polyphony/extensions/kernel.rb +0 -169
- data/lib/polyphony/http/http1_adapter.rb +0 -254
- data/lib/polyphony/http/http2_adapter.rb +0 -157
- data/lib/polyphony/http/rack.rb +0 -25
- data/lib/polyphony/http/request.rb +0 -66
- data/test/test_ev.rb +0 -110
@@ -1,98 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
export :available,
|
4
|
-
:checked_out,
|
5
|
-
:reset!,
|
6
|
-
:size,
|
7
|
-
:run
|
8
|
-
|
9
|
-
require 'fiber'
|
10
|
-
|
11
|
-
# Array of available fibers
|
12
|
-
@pool = []
|
13
|
-
|
14
|
-
# Array of fibers in use
|
15
|
-
@checked_out = {}
|
16
|
-
|
17
|
-
# Fiber count
|
18
|
-
@count = 0
|
19
|
-
|
20
|
-
# Returns number of available fibers in pool
|
21
|
-
# @return [Integer] available fibers count
|
22
|
-
def available
|
23
|
-
@pool.size
|
24
|
-
end
|
25
|
-
|
26
|
-
def checked_out
|
27
|
-
@checked_out.size
|
28
|
-
end
|
29
|
-
|
30
|
-
# Returns size of fiber pool (including currently used fiber)
|
31
|
-
# @return [Integer] fiber pool size
|
32
|
-
def size
|
33
|
-
@count
|
34
|
-
end
|
35
|
-
|
36
|
-
def downsize
|
37
|
-
return if @count < 5
|
38
|
-
max_available = @count >= 5 ? @count / 5 : 2
|
39
|
-
if @pool.count > max_available
|
40
|
-
@pool.slice!(max_available, 50).each { |f| f.transfer :stop }
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
@downsize_timer = EV::Timer.new(5, 5)
|
45
|
-
@downsize_timer.start { downsize }
|
46
|
-
EV.unref
|
47
|
-
|
48
|
-
# Invokes the given block using a fiber taken from the fiber pool. If the pool
|
49
|
-
# is exhausted, a new fiber will be created.
|
50
|
-
# @return [Fiber]
|
51
|
-
def run(&block)
|
52
|
-
fiber = @pool.empty? ? new_fiber : @pool.shift
|
53
|
-
fiber.next_job = block
|
54
|
-
fiber
|
55
|
-
end
|
56
|
-
|
57
|
-
def reset!
|
58
|
-
@count = 0
|
59
|
-
@pool = []
|
60
|
-
@checked_out = {}
|
61
|
-
end
|
62
|
-
|
63
|
-
# Creates a new fiber to be added to the pool
|
64
|
-
# @return [Fiber] new fiber
|
65
|
-
def new_fiber
|
66
|
-
Fiber.new { fiber_loop }
|
67
|
-
end
|
68
|
-
|
69
|
-
# Runs a job-processing loop inside the current fiber
|
70
|
-
# @return [void]
|
71
|
-
def fiber_loop
|
72
|
-
fiber = Fiber.current
|
73
|
-
@count += 1
|
74
|
-
error = nil
|
75
|
-
loop do
|
76
|
-
job, fiber.next_job = fiber.next_job, nil
|
77
|
-
@checked_out[fiber] = true
|
78
|
-
fiber.cancelled = nil
|
79
|
-
|
80
|
-
job&.(fiber)
|
81
|
-
|
82
|
-
@pool << fiber
|
83
|
-
@checked_out.delete(fiber)
|
84
|
-
break if suspend == :stop
|
85
|
-
end
|
86
|
-
rescue => e
|
87
|
-
# uncaught error
|
88
|
-
error = e
|
89
|
-
ensure
|
90
|
-
@pool.delete(self)
|
91
|
-
@checked_out.delete(fiber)
|
92
|
-
@count -= 1
|
93
|
-
|
94
|
-
# We need to explicitly transfer control to reactor fiber, otherwise it will
|
95
|
-
# be transferred to the main fiber, which would normally be blocking on
|
96
|
-
# something
|
97
|
-
$__reactor_fiber__.transfer unless error
|
98
|
-
end
|
@@ -1,169 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'fiber'
|
4
|
-
require 'timeout'
|
5
|
-
|
6
|
-
CancelScope = import('../core/cancel_scope')
|
7
|
-
Coprocess = import('../core/coprocess')
|
8
|
-
Exceptions = import('../core/exceptions')
|
9
|
-
Supervisor = import('../core/supervisor')
|
10
|
-
Throttler = import('../core/throttler')
|
11
|
-
|
12
|
-
# Fiber extensions
|
13
|
-
class ::Fiber
|
14
|
-
attr_writer :cancelled
|
15
|
-
attr_accessor :next_job, :coprocess
|
16
|
-
|
17
|
-
def cancelled?
|
18
|
-
@cancelled
|
19
|
-
end
|
20
|
-
|
21
|
-
def schedule(value = nil)
|
22
|
-
EV.schedule_fiber(self, value)
|
23
|
-
end
|
24
|
-
|
25
|
-
# Associate a (pseudo-)coprocess with the main fiber
|
26
|
-
current.coprocess = Coprocess.new(current)
|
27
|
-
end
|
28
|
-
|
29
|
-
class ::Exception
|
30
|
-
SANITIZE_RE = /lib\/polyphony/.freeze
|
31
|
-
SANITIZE_PROC = proc { |l| l !~ SANITIZE_RE }
|
32
|
-
|
33
|
-
def cleanup_backtrace(caller = nil)
|
34
|
-
combined = caller ? backtrace + caller : backtrace
|
35
|
-
set_backtrace(combined.select(&SANITIZE_PROC))
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
class Pulser
|
40
|
-
def initialize(freq)
|
41
|
-
fiber = Fiber.current
|
42
|
-
@timer = EV::Timer.new(freq, freq)
|
43
|
-
@timer.start { fiber.transfer freq }
|
44
|
-
end
|
45
|
-
|
46
|
-
def await
|
47
|
-
suspend
|
48
|
-
rescue Exception => e
|
49
|
-
@timer.stop
|
50
|
-
raise e
|
51
|
-
end
|
52
|
-
|
53
|
-
def stop
|
54
|
-
@timer.stop
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
module ::Process
|
59
|
-
def self.detach(pid)
|
60
|
-
spin {
|
61
|
-
EV::Child.new(pid).await
|
62
|
-
}
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
require 'open3'
|
67
|
-
|
68
|
-
# Kernel extensions (methods available to all objects)
|
69
|
-
module ::Kernel
|
70
|
-
def after(duration, &block)
|
71
|
-
EV::Timer.new(freq, 0).start(&block)
|
72
|
-
end
|
73
|
-
|
74
|
-
def async(sym = nil, &block)
|
75
|
-
if sym
|
76
|
-
async_decorate(is_a?(Class) ? self : singleton_class, sym)
|
77
|
-
else
|
78
|
-
Coprocess.new(&block)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
# Converts a regular method into an async method, i.e. a method that returns a
|
83
|
-
# proc that eventually executes the original code.
|
84
|
-
# @param receiver [Object] object receiving the method call
|
85
|
-
# @param sym [Symbol] method name
|
86
|
-
# @return [void]
|
87
|
-
def async_decorate(receiver, sym)
|
88
|
-
sync_sym = :"sync_#{sym}"
|
89
|
-
receiver.alias_method(sync_sym, sym)
|
90
|
-
receiver.class_eval("def #{sym}(*args, &block); Coprocess.new { send(#{sync_sym.inspect}, *args, &block) }; end")
|
91
|
-
end
|
92
|
-
|
93
|
-
def cancel_after(duration, &block)
|
94
|
-
CancelScope.new(timeout: duration, mode: :cancel).(&block)
|
95
|
-
end
|
96
|
-
|
97
|
-
def spin(proc = nil, &block)
|
98
|
-
if proc.is_a?(Coprocess)
|
99
|
-
proc.run
|
100
|
-
else
|
101
|
-
Coprocess.new(&(block || proc)).run
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def every(freq, &block)
|
106
|
-
EV::Timer.new(freq, freq).start(&block)
|
107
|
-
end
|
108
|
-
|
109
|
-
def move_on_after(duration, &block)
|
110
|
-
CancelScope.new(timeout: duration).(&block)
|
111
|
-
end
|
112
|
-
|
113
|
-
def pulse(freq)
|
114
|
-
Pulser.new(freq)
|
115
|
-
end
|
116
|
-
|
117
|
-
def receive
|
118
|
-
Fiber.current.coprocess.receive
|
119
|
-
end
|
120
|
-
|
121
|
-
alias_method :sync_sleep, :sleep
|
122
|
-
def sleep(duration)
|
123
|
-
timer = EV::Timer.new(duration, 0)
|
124
|
-
timer.await
|
125
|
-
ensure
|
126
|
-
timer.stop
|
127
|
-
end
|
128
|
-
|
129
|
-
def supervise(&block)
|
130
|
-
Supervisor.new.await(&block)
|
131
|
-
end
|
132
|
-
|
133
|
-
alias_method :orig_system, :system
|
134
|
-
def system(*args)
|
135
|
-
Open3.popen2(*args) do |i, o, t|
|
136
|
-
i.close
|
137
|
-
o.read
|
138
|
-
end
|
139
|
-
rescue SystemCallError => e
|
140
|
-
nil
|
141
|
-
end
|
142
|
-
|
143
|
-
def throttled_loop(rate, count: nil, &block)
|
144
|
-
throttler = Throttler.new(rate)
|
145
|
-
if count
|
146
|
-
count.times { throttler.(&block) }
|
147
|
-
else
|
148
|
-
loop { throttler.(&block) }
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
def throttle(rate)
|
153
|
-
Throttler.new(rate)
|
154
|
-
end
|
155
|
-
|
156
|
-
def timeout(duration, opts = {}, &block)
|
157
|
-
CancelScope.new(**opts, timeout: duration).(&block)
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
module ::Timeout
|
162
|
-
def self.timeout(sec, klass = nil, message = nil, &block)
|
163
|
-
cancel_after(sec, &block)
|
164
|
-
rescue Exceptions::Cancel => e
|
165
|
-
error = klass ? klass.new(message) : ::Timeout::Error.new
|
166
|
-
error.set_backtrace(e.backtrace)
|
167
|
-
raise error
|
168
|
-
end
|
169
|
-
end
|
@@ -1,254 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
export_default :HTTP1Adapter
|
4
|
-
|
5
|
-
require 'http/parser'
|
6
|
-
|
7
|
-
Request = import('./request')
|
8
|
-
HTTP2 = import('./http2_adapter')
|
9
|
-
|
10
|
-
# HTTP1 protocol implementation
|
11
|
-
class HTTP1Adapter
|
12
|
-
# Initializes a protocol adapter instance
|
13
|
-
def initialize(conn, opts)
|
14
|
-
@conn = conn
|
15
|
-
@opts = opts
|
16
|
-
@parser = HTTP::Parser.new(self)
|
17
|
-
@parse_fiber = Fiber.new { parse_loop }
|
18
|
-
end
|
19
|
-
|
20
|
-
def protocol
|
21
|
-
'http/1.1'
|
22
|
-
end
|
23
|
-
|
24
|
-
# Parses incoming data, potentially firing parser callbacks. This loop runs on
|
25
|
-
# a separate fiber and is resumed only when the handler (client) loop asks for
|
26
|
-
# headers, or the request body, or waits for the request to be completed. The
|
27
|
-
# control flow is as follows (arrows represent control transfer between
|
28
|
-
# fibers):
|
29
|
-
#
|
30
|
-
# handler parse_loop
|
31
|
-
# get_headers --> ...
|
32
|
-
# @parser << @conn.readpartial(8192)
|
33
|
-
# ... <-- on_headers
|
34
|
-
#
|
35
|
-
# get_body --> ...
|
36
|
-
# ... <-- on_body
|
37
|
-
#
|
38
|
-
# consume_request --> ...
|
39
|
-
# @parser << @conn.readpartial(8192)
|
40
|
-
# ... <-- on_message_complete
|
41
|
-
#
|
42
|
-
def parse_loop
|
43
|
-
while (data = @conn.readpartial(8192))
|
44
|
-
break unless data
|
45
|
-
@parser << data
|
46
|
-
snooze
|
47
|
-
end
|
48
|
-
@calling_fiber.transfer nil
|
49
|
-
rescue SystemCallError, IOError => error
|
50
|
-
# ignore IO/system call errors
|
51
|
-
@calling_fiber.transfer nil
|
52
|
-
rescue Exception => error
|
53
|
-
# an error return value will be raised by the receiving fiber
|
54
|
-
@calling_fiber.transfer error
|
55
|
-
end
|
56
|
-
|
57
|
-
# request API
|
58
|
-
|
59
|
-
# Iterates over incoming requests. Requests are yielded once all headers have
|
60
|
-
# been received. It is left to the application to read the request body or
|
61
|
-
# diesregard it.
|
62
|
-
def each(&block)
|
63
|
-
can_upgrade = true
|
64
|
-
while @parse_fiber.alive? && (headers = get_headers)
|
65
|
-
if can_upgrade
|
66
|
-
# The connection can be upgraded only on the first request
|
67
|
-
return if upgrade_connection(headers, &block)
|
68
|
-
can_upgrade = false
|
69
|
-
end
|
70
|
-
|
71
|
-
@headers_sent = nil
|
72
|
-
block.(Request.new(headers, self))
|
73
|
-
|
74
|
-
if @parser.keep_alive?
|
75
|
-
@parsing = false
|
76
|
-
else
|
77
|
-
break
|
78
|
-
end
|
79
|
-
end
|
80
|
-
ensure
|
81
|
-
@conn.close rescue nil
|
82
|
-
end
|
83
|
-
|
84
|
-
# Reads headers for the next request. Transfers control to the parse loop,
|
85
|
-
# and resumes once the parse_loop has fired the on_headers callback
|
86
|
-
def get_headers
|
87
|
-
@parsing = true
|
88
|
-
@calling_fiber = Fiber.current
|
89
|
-
@parse_fiber.safe_transfer
|
90
|
-
end
|
91
|
-
|
92
|
-
# Reads a body chunk for the current request. Transfers control to the parse
|
93
|
-
# loop, and resumes once the parse_loop has fired the on_body callback
|
94
|
-
def get_body_chunk
|
95
|
-
@calling_fiber = Fiber.current
|
96
|
-
@read_body = true
|
97
|
-
@parse_fiber.safe_transfer
|
98
|
-
end
|
99
|
-
|
100
|
-
# Waits for the current request to complete. Transfers control to the parse
|
101
|
-
# loop, and resumes once the parse_loop has fired the on_message_complete
|
102
|
-
# callback
|
103
|
-
def consume_request
|
104
|
-
return unless @parsing
|
105
|
-
|
106
|
-
@calling_fiber = Fiber.current
|
107
|
-
@read_body = false
|
108
|
-
@parse_fiber.safe_transfer while @parsing
|
109
|
-
end
|
110
|
-
|
111
|
-
# Upgrades the connection to a different protocol, if the 'Upgrade' header is
|
112
|
-
# given. By default the only supported upgrade protocol is HTTP2. Additional
|
113
|
-
# protocols, notably WebSocket, can be specified by passing a hash to the
|
114
|
-
# :upgrade option when starting a server:
|
115
|
-
#
|
116
|
-
# opts = {
|
117
|
-
# upgrade: {
|
118
|
-
# websocket: Polyphony::Websocket.handler(&method(:ws_handler))
|
119
|
-
# }
|
120
|
-
# }
|
121
|
-
# Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) { |req| ... }
|
122
|
-
#
|
123
|
-
# @param headers [Hash] request headers
|
124
|
-
# @return [boolean] truthy if the connection has been upgraded
|
125
|
-
def upgrade_connection(headers, &block)
|
126
|
-
upgrade_protocol = headers['Upgrade']
|
127
|
-
return nil unless upgrade_protocol
|
128
|
-
|
129
|
-
if @opts[:upgrade] && @opts[:upgrade][upgrade_protocol.to_sym]
|
130
|
-
@opts[:upgrade][upgrade_protocol.to_sym].(@conn, headers)
|
131
|
-
return true
|
132
|
-
end
|
133
|
-
|
134
|
-
return nil unless upgrade_protocol == 'h2c'
|
135
|
-
|
136
|
-
# upgrade to HTTP/2
|
137
|
-
HTTP2.upgrade_each(@conn, @opts, http2_upgraded_headers(headers), &block)
|
138
|
-
true
|
139
|
-
end
|
140
|
-
|
141
|
-
# Returns headers for HTTP2 upgrade
|
142
|
-
# @param headers [Hash] request headers
|
143
|
-
# @return [Hash] headers for HTTP2 upgrade
|
144
|
-
def http2_upgraded_headers(headers)
|
145
|
-
headers.merge(
|
146
|
-
':scheme' => 'http',
|
147
|
-
':authority' => headers['Host'],
|
148
|
-
)
|
149
|
-
end
|
150
|
-
|
151
|
-
# HTTP parser callbacks, called in the context of @parse_fiber
|
152
|
-
|
153
|
-
# Resumes client fiber on receipt of all headers
|
154
|
-
# @param headers [Hash] request headers
|
155
|
-
# @return [void]
|
156
|
-
def on_headers_complete(headers)
|
157
|
-
headers[':path'] = @parser.request_url
|
158
|
-
headers[':method'] = @parser.http_method
|
159
|
-
@calling_fiber.transfer(headers)
|
160
|
-
end
|
161
|
-
|
162
|
-
# Resumes client fiber on receipt of body chunk
|
163
|
-
# @param chunk [String] body chunk
|
164
|
-
# @return [void]
|
165
|
-
def on_body(chunk)
|
166
|
-
@calling_fiber.transfer(chunk) if @read_body
|
167
|
-
end
|
168
|
-
|
169
|
-
# Resumes client fiber on request completion
|
170
|
-
# @return [void]
|
171
|
-
def on_message_complete
|
172
|
-
@parsing = false
|
173
|
-
@calling_fiber.transfer nil
|
174
|
-
end
|
175
|
-
|
176
|
-
# response API
|
177
|
-
|
178
|
-
# Sends response including headers and body. Waits for the request to complete
|
179
|
-
# if not yet completed. The body is sent using chunked transfer encoding.
|
180
|
-
# @param chunk [String] response body
|
181
|
-
# @param headers
|
182
|
-
def respond(chunk, headers)
|
183
|
-
consume_request if @parsing
|
184
|
-
data = format_headers(headers, empty_response: !chunk)
|
185
|
-
if chunk
|
186
|
-
data << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n0\r\n\r\n"
|
187
|
-
end
|
188
|
-
@conn << data
|
189
|
-
@headers_sent = true
|
190
|
-
end
|
191
|
-
|
192
|
-
DEFAULT_HEADERS_OPTS = {
|
193
|
-
empty_response: false,
|
194
|
-
consume_request: true
|
195
|
-
}
|
196
|
-
|
197
|
-
# Sends response headers. Waits for the request to complete if not yet
|
198
|
-
# completed. If empty_response is true(thy), the response status code will
|
199
|
-
# default to 204, otherwise to 200.
|
200
|
-
# @param headers [Hash] response headers
|
201
|
-
# @param empty_response [boolean] whether a response body will be sent
|
202
|
-
# @return [void]
|
203
|
-
def send_headers(headers, opts = DEFAULT_HEADERS_OPTS)
|
204
|
-
return if @headers_sent
|
205
|
-
|
206
|
-
consume_request if @parsing && opts[:consume_request]
|
207
|
-
@conn << format_headers(headers, opts[:empty_response])
|
208
|
-
@headers_sent = true
|
209
|
-
end
|
210
|
-
|
211
|
-
# Sends a response body chunk. If no headers were sent, default headers are
|
212
|
-
# sent using #send_headers. if the done option is true(thy), an empty chunk
|
213
|
-
# will be sent to signal response completion to the client.
|
214
|
-
# @param chunk [String] response body chunk
|
215
|
-
# @param done [boolean] whether the response is completed
|
216
|
-
# @return [void]
|
217
|
-
def send_body_chunk(chunk, done: false)
|
218
|
-
send_headers({}) unless @headers_sent
|
219
|
-
|
220
|
-
data = +"#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
|
221
|
-
data << "0\r\n\r\n" if done
|
222
|
-
@conn << data
|
223
|
-
end
|
224
|
-
|
225
|
-
# Finishes the response to the current request. If no headers were sent,
|
226
|
-
# default headers are sent using #send_headers.
|
227
|
-
# @return [void]
|
228
|
-
def finish
|
229
|
-
send_headers({}, true) unless @headers_sent
|
230
|
-
|
231
|
-
@conn << "0\r\n\r\n" if @body_sent
|
232
|
-
end
|
233
|
-
|
234
|
-
private
|
235
|
-
|
236
|
-
# Formats response headers. If empty_response is true(thy), the response
|
237
|
-
# status code will default to 204, otherwise to 200.
|
238
|
-
# @param headers [Hash] response headers
|
239
|
-
# @param empty_response [boolean] whether a response body will be sent
|
240
|
-
# @return [String] formatted response headers
|
241
|
-
def format_headers(headers, empty_response)
|
242
|
-
status = headers[':status'] || (empty_response ? 204 : 200)
|
243
|
-
data = empty_response ?
|
244
|
-
+"HTTP/1.1 #{status}\r\n" :
|
245
|
-
+"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
|
246
|
-
|
247
|
-
headers.each do |k, v|
|
248
|
-
next if k =~ /^:/
|
249
|
-
v.is_a?(Array) ?
|
250
|
-
v.each { |o| data << "#{k}: #{o}\r\n" } : data << "#{k}: #{v}\r\n"
|
251
|
-
end
|
252
|
-
data << "\r\n"
|
253
|
-
end
|
254
|
-
end
|