polyphony 0.19 → 0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|