polyphony 0.23 → 0.24
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/Gemfile.lock +4 -10
- data/README.md +0 -4
- data/TODO.md +5 -56
- data/docs/README.md +4 -7
- data/examples/core/{channel_echo.rb → xx-channels.rb} +0 -0
- data/examples/core/{defer.rb → xx-deferring-an-operation.rb} +0 -0
- data/examples/core/{genserver.rb → xx-erlang-style-genserver.rb} +2 -2
- data/examples/core/{fork.rb → xx-forking.rb} +2 -0
- data/examples/core/xx-move_on.rb +23 -0
- data/examples/core/{pulse.rb → xx-recurrent-timer.rb} +3 -2
- data/examples/core/{resource_cancel.rb → xx-resource_cancel.rb} +1 -2
- data/examples/core/{resource_delegate.rb → xx-resource_delegate.rb} +0 -0
- data/examples/core/{wait_for_signal.rb → xx-signals.rb} +0 -0
- data/examples/core/{sleep.rb → xx-sleeping.rb} +0 -0
- data/examples/core/{spin_error_backtrace.rb → xx-spin_error_backtrace.rb} +5 -2
- data/examples/core/{supervisor.rb → xx-supervisors.rb} +0 -0
- data/examples/core/{thread_cancel.rb → xx-thread_cancel.rb} +0 -0
- data/examples/core/{thread_pool.rb → xx-thread_pool.rb} +0 -0
- data/examples/core/{throttle.rb → xx-throttling.rb} +0 -0
- data/examples/core/{timeout.rb → xx-timeout.rb} +0 -0
- data/examples/core/{lock.rb → xx-using-a-mutex.rb} +0 -0
- data/examples/io/{backticks.rb → xx-backticks.rb} +0 -0
- data/examples/io/{echo_client.rb → xx-echo_client.rb} +0 -1
- data/examples/io/{echo_client_from_stdin.rb → xx-echo_client_from_stdin.rb} +0 -1
- data/examples/io/{echo_pipe.rb → xx-echo_pipe.rb} +0 -0
- data/examples/io/{echo_server.rb → xx-echo_server.rb} +0 -0
- data/examples/io/{echo_server_with_timeout.rb → xx-echo_server_with_timeout.rb} +0 -0
- data/examples/io/{echo_stdin.rb → xx-echo_stdin.rb} +0 -0
- data/examples/io/xx-httparty.rb +13 -0
- data/examples/io/{irb.rb → xx-irb.rb} +0 -0
- data/examples/io/{net-http.rb → xx-net-http.rb} +0 -0
- data/examples/io/{open.rb → xx-open.rb} +0 -1
- data/examples/io/{system.rb → xx-system.rb} +0 -0
- data/examples/io/{tcpserver.rb → xx-tcpserver.rb} +0 -0
- data/examples/io/{tcpsocket.rb → xx-tcpsocket.rb} +0 -1
- data/examples/{fs/read.rb → performance/fs_read.rb} +0 -0
- data/examples/{core → performance}/mem-usage.rb +1 -0
- data/examples/performance/multi_snooze.rb +2 -0
- data/examples/performance/snooze.rb +2 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +5 -3
- data/examples/performance/thread-vs-fiber/threaded_server.rb +1 -1
- data/examples/{io/httparty_multi.rb → performance/thread-vs-fiber/xx-httparty_multi.rb} +16 -13
- data/examples/{io/httparty_threaded.rb → performance/thread-vs-fiber/xx-httparty_threaded.rb} +2 -2
- data/examples/performance/thread.rb +27 -0
- data/examples/{core → performance}/thread_pool_perf.rb +0 -0
- data/ext/gyro/extconf.rb +1 -0
- data/lib/polyphony/extensions/core.rb +0 -5
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +3 -9
- metadata +59 -167
- data/bin/poly +0 -11
- data/examples/core/cancel.rb +0 -13
- data/examples/core/enumerator.rb +0 -15
- data/examples/core/error_bubbling.rb +0 -35
- data/examples/core/fiber_error.rb +0 -9
- data/examples/core/fiber_error_with_backtrace.rb +0 -73
- data/examples/core/move_on.rb +0 -11
- data/examples/core/move_on_twice.rb +0 -16
- data/examples/core/move_on_with_ensure.rb +0 -13
- data/examples/core/move_on_with_value.rb +0 -14
- data/examples/core/multiple_spin.rb +0 -18
- data/examples/core/nested_cancel.rb +0 -40
- data/examples/core/nested_multiple_spin.rb +0 -20
- data/examples/core/nested_spin.rb +0 -19
- data/examples/core/pingpong.rb +0 -21
- data/examples/core/resource.rb +0 -30
- data/examples/core/sleep_spin.rb +0 -21
- data/examples/core/snooze.rb +0 -32
- data/examples/core/spin_error.rb +0 -17
- data/examples/core/spin_uncaught_error.rb +0 -16
- data/examples/core/supervisor_with_cancel_scope.rb +0 -23
- data/examples/core/supervisor_with_error.rb +0 -24
- data/examples/core/supervisor_with_manual_move_on.rb +0 -23
- data/examples/core/suspend.rb +0 -13
- data/examples/core/thread.rb +0 -27
- data/examples/http/config.ru +0 -7
- data/examples/http/cuba.ru +0 -22
- data/examples/http/happy_eyeballs.rb +0 -37
- data/examples/http/http2_raw.rb +0 -135
- data/examples/http/http_client.rb +0 -28
- data/examples/http/http_get.rb +0 -33
- data/examples/http/http_parse_experiment.rb +0 -123
- data/examples/http/http_proxy.rb +0 -83
- data/examples/http/http_server.js +0 -24
- data/examples/http/http_server.rb +0 -28
- data/examples/http/http_server_forked.rb +0 -29
- data/examples/http/http_server_graceful.rb +0 -27
- data/examples/http/http_server_simple.rb +0 -11
- data/examples/http/http_server_throttled.rb +0 -15
- data/examples/http/http_ws_server.rb +0 -37
- data/examples/http/https_raw_client.rb +0 -12
- data/examples/http/https_server.rb +0 -22
- data/examples/http/https_wss_server.rb +0 -39
- data/examples/http/rack_server.rb +0 -12
- data/examples/http/rack_server_https.rb +0 -19
- data/examples/http/rack_server_https_forked.rb +0 -27
- data/examples/http/websocket_secure_server.rb +0 -27
- data/examples/http/websocket_server.rb +0 -24
- data/examples/http/ws_page.html +0 -34
- data/examples/http/wss_page.html +0 -34
- data/examples/io/cat.rb +0 -12
- data/examples/io/httparty.rb +0 -10
- data/examples/io/io_read.rb +0 -9
- data/lib/ev_ext.bundle +0 -0
- data/lib/polyphony/http.rb +0 -16
- data/lib/polyphony/http/client/agent.rb +0 -131
- data/lib/polyphony/http/client/http1.rb +0 -129
- data/lib/polyphony/http/client/http2.rb +0 -180
- data/lib/polyphony/http/client/response.rb +0 -32
- data/lib/polyphony/http/client/site_connection_manager.rb +0 -109
- data/lib/polyphony/http/server.rb +0 -49
- data/lib/polyphony/http/server/http1.rb +0 -268
- data/lib/polyphony/http/server/http2.rb +0 -78
- data/lib/polyphony/http/server/http2_stream.rb +0 -136
- data/lib/polyphony/http/server/rack.rb +0 -64
- data/lib/polyphony/http/server/request.rb +0 -118
- data/lib/polyphony/websocket.rb +0 -59
- data/test/test_http_server.rb +0 -313
@@ -1,32 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
export_default :Response
|
4
|
-
|
5
|
-
require 'json'
|
6
|
-
|
7
|
-
# HTTP response
|
8
|
-
class Response
|
9
|
-
attr_reader :status_code, :headers
|
10
|
-
|
11
|
-
def initialize(adapter, status_code, headers)
|
12
|
-
@adapter = adapter
|
13
|
-
@status_code = status_code
|
14
|
-
@headers = headers
|
15
|
-
end
|
16
|
-
|
17
|
-
def body
|
18
|
-
@body ||= @adapter.body
|
19
|
-
end
|
20
|
-
|
21
|
-
def each_chunk(&block)
|
22
|
-
@adapter.each_chunk(&block)
|
23
|
-
end
|
24
|
-
|
25
|
-
def next_body_chunk
|
26
|
-
@adapter.next_body_chunk
|
27
|
-
end
|
28
|
-
|
29
|
-
def json
|
30
|
-
@json ||= ::JSON.parse(body)
|
31
|
-
end
|
32
|
-
end
|
@@ -1,109 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
export_default :SiteConnectionManager
|
4
|
-
|
5
|
-
ResourcePool = import '../../core/resource_pool'
|
6
|
-
HTTP1Adapter = import './http1'
|
7
|
-
HTTP2Adapter = import './http2'
|
8
|
-
|
9
|
-
# HTTP site connection pool
|
10
|
-
class SiteConnectionManager < ResourcePool
|
11
|
-
def initialize(uri_key)
|
12
|
-
@uri_key = uri_key
|
13
|
-
super(limit: 4)
|
14
|
-
end
|
15
|
-
|
16
|
-
# def method_missing(sym, *args)
|
17
|
-
# raise "Invalid method #{sym}"
|
18
|
-
# end
|
19
|
-
|
20
|
-
def acquire
|
21
|
-
Gyro.ref
|
22
|
-
prepare_first_connection if @size.zero?
|
23
|
-
super
|
24
|
-
ensure
|
25
|
-
Gyro.unref
|
26
|
-
# The size goes back to 0 only in case existing connections get into an
|
27
|
-
# error state and then get discarded
|
28
|
-
@state = nil if @size == 0
|
29
|
-
end
|
30
|
-
|
31
|
-
def prepare_first_connection
|
32
|
-
case @state
|
33
|
-
when nil
|
34
|
-
@state = :first_connection
|
35
|
-
create_first_connection
|
36
|
-
when :first_connection
|
37
|
-
@first_connection_queue << Fiber.current
|
38
|
-
suspend
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def create_first_connection
|
43
|
-
@first_connection_queue = []
|
44
|
-
# @first_connection_queue << Fiber.current
|
45
|
-
|
46
|
-
adapter = connect
|
47
|
-
@state = adapter.protocol
|
48
|
-
send(:"setup_#{@state}_allocator", adapter)
|
49
|
-
dequeue_first_connection_waiters
|
50
|
-
end
|
51
|
-
|
52
|
-
def setup_http1_allocator(adapter)
|
53
|
-
@size += 1
|
54
|
-
adapter.extend ResourceExtensions
|
55
|
-
@stock << adapter
|
56
|
-
@allocator = proc { connect }
|
57
|
-
end
|
58
|
-
|
59
|
-
def setup_http2_allocator(adapter)
|
60
|
-
@adapter = adapter
|
61
|
-
@limit = 20
|
62
|
-
@size += 1
|
63
|
-
stream_adapter = adapter.allocate_stream_adapter
|
64
|
-
stream_adapter.extend ResourceExtensions
|
65
|
-
@stock << stream_adapter
|
66
|
-
@allocator = proc { adapter.allocate_stream_adapter }
|
67
|
-
end
|
68
|
-
|
69
|
-
def dequeue_first_connection_waiters
|
70
|
-
return unless @first_connection_queue
|
71
|
-
|
72
|
-
@first_connection_queue.each(&:schedule)
|
73
|
-
@first_connection_queue = nil
|
74
|
-
end
|
75
|
-
|
76
|
-
def connect
|
77
|
-
socket = create_socket
|
78
|
-
protocol = socket_protocol(socket)
|
79
|
-
case protocol
|
80
|
-
when :http1
|
81
|
-
HTTP1Adapter.new(socket)
|
82
|
-
when :http2
|
83
|
-
HTTP2Adapter.new(socket)
|
84
|
-
else
|
85
|
-
raise "Unknown protocol #{protocol.inspect}"
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def socket_protocol(socket)
|
90
|
-
if socket.is_a?(OpenSSL::SSL::SSLSocket) && socket.alpn_protocol == 'h2'
|
91
|
-
:http2
|
92
|
-
else
|
93
|
-
:http1
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
SECURE_OPTS = { secure: true, alpn_protocols: ['h2', 'http/1.1'] }.freeze
|
98
|
-
|
99
|
-
def create_socket
|
100
|
-
case @uri_key[:scheme]
|
101
|
-
when 'http'
|
102
|
-
Polyphony::Net.tcp_connect(@uri_key[:host], @uri_key[:port])
|
103
|
-
when 'https'
|
104
|
-
Polyphony::Net.tcp_connect(@uri_key[:host], @uri_key[:port], SECURE_OPTS)
|
105
|
-
else
|
106
|
-
raise "Invalid scheme #{@uri_key[:scheme].inspect}"
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
@@ -1,49 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
export :serve, :listen, :accept_loop, :client_loop
|
4
|
-
|
5
|
-
Net = import('../net')
|
6
|
-
HTTP1 = import('./server/http1')
|
7
|
-
HTTP2 = import('./server/http2')
|
8
|
-
|
9
|
-
ALPN_PROTOCOLS = %w[h2 http/1.1].freeze
|
10
|
-
H2_PROTOCOL = 'h2'
|
11
|
-
|
12
|
-
def serve(host, port, opts = {}, &handler)
|
13
|
-
opts[:alpn_protocols] = ALPN_PROTOCOLS
|
14
|
-
server = Net.tcp_listen(host, port, opts)
|
15
|
-
accept_loop(server, opts, &handler)
|
16
|
-
end
|
17
|
-
|
18
|
-
def listen(host, port, opts = {})
|
19
|
-
opts[:alpn_protocols] = ALPN_PROTOCOLS
|
20
|
-
Net.tcp_listen(host, port, opts).tap do |socket|
|
21
|
-
socket.define_singleton_method(:each) do |&block|
|
22
|
-
MODULE.accept_loop(socket, opts, &block)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def accept_loop(server, opts, &handler)
|
28
|
-
loop do
|
29
|
-
client = server.accept
|
30
|
-
spin { client_loop(client, opts, &handler) }
|
31
|
-
rescue OpenSSL::SSL::SSLError
|
32
|
-
# disregard
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def client_loop(client, opts, &handler)
|
37
|
-
client.no_delay if client.respond_to?(:no_delay)
|
38
|
-
adapter = protocol_adapter(client, opts)
|
39
|
-
adapter.each(&handler)
|
40
|
-
ensure
|
41
|
-
client.close
|
42
|
-
end
|
43
|
-
|
44
|
-
def protocol_adapter(socket, opts)
|
45
|
-
use_http2 = socket.respond_to?(:alpn_protocol) &&
|
46
|
-
socket.alpn_protocol == H2_PROTOCOL
|
47
|
-
klass = use_http2 ? HTTP2 : HTTP1
|
48
|
-
klass.new(socket, opts)
|
49
|
-
end
|
@@ -1,268 +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')
|
9
|
-
Exceptions = import('../../core/exceptions')
|
10
|
-
|
11
|
-
# HTTP1 protocol implementation
|
12
|
-
class HTTP1Adapter
|
13
|
-
# Initializes a protocol adapter instance
|
14
|
-
def initialize(conn, opts)
|
15
|
-
@conn = conn
|
16
|
-
@opts = opts
|
17
|
-
@parser = HTTP::Parser.new(self)
|
18
|
-
end
|
19
|
-
|
20
|
-
def each(&block)
|
21
|
-
while (data = @conn.readpartial(8192))
|
22
|
-
return if handle_incoming_data(data, &block)
|
23
|
-
end
|
24
|
-
rescue SystemCallError, IOError
|
25
|
-
# ignore
|
26
|
-
ensure
|
27
|
-
finalize_client_loop
|
28
|
-
end
|
29
|
-
|
30
|
-
# return [Boolean] true if client loop should stop
|
31
|
-
def handle_incoming_data(data, &block)
|
32
|
-
@parser << data
|
33
|
-
snooze
|
34
|
-
while (request = @requests_head)
|
35
|
-
return true if upgrade_connection(request.headers, &block)
|
36
|
-
|
37
|
-
@requests_head = request.__next__
|
38
|
-
block.call(request)
|
39
|
-
return true unless request.keep_alive?
|
40
|
-
end
|
41
|
-
nil
|
42
|
-
end
|
43
|
-
|
44
|
-
def finalize_client_loop
|
45
|
-
# release references to various objects
|
46
|
-
@requests_head = @requests_tail = nil
|
47
|
-
@parser = nil
|
48
|
-
@conn.close
|
49
|
-
end
|
50
|
-
|
51
|
-
# Reads a body chunk for the current request. Transfers control to the parse
|
52
|
-
# loop, and resumes once the parse_loop has fired the on_body callback
|
53
|
-
def get_body_chunk
|
54
|
-
@waiting_for_body_chunk = true
|
55
|
-
@next_chunk = nil
|
56
|
-
while !@requests_tail.complete? && (data = @conn.readpartial(8192))
|
57
|
-
@parser << data
|
58
|
-
return @next_chunk if @next_chunk
|
59
|
-
|
60
|
-
snooze
|
61
|
-
end
|
62
|
-
nil
|
63
|
-
ensure
|
64
|
-
@waiting_for_body_chunk = nil
|
65
|
-
end
|
66
|
-
|
67
|
-
# Waits for the current request to complete. Transfers control to the parse
|
68
|
-
# loop, and resumes once the parse_loop has fired the on_message_complete
|
69
|
-
# callback
|
70
|
-
def consume_request
|
71
|
-
request = @requests_head
|
72
|
-
while (data = @conn.readpartial(8192))
|
73
|
-
@parser << data
|
74
|
-
return if request.complete?
|
75
|
-
|
76
|
-
snooze
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def protocol
|
81
|
-
version = @parser.http_version
|
82
|
-
"HTTP #{version.join('.')}"
|
83
|
-
end
|
84
|
-
|
85
|
-
def on_headers_complete(headers)
|
86
|
-
headers[':path'] = @parser.request_url
|
87
|
-
headers[':method'] = @parser.http_method
|
88
|
-
queue_request(Request.new(headers, self))
|
89
|
-
end
|
90
|
-
|
91
|
-
def queue_request(request)
|
92
|
-
if @requests_head
|
93
|
-
@requests_tail.__next__ = request
|
94
|
-
@requests_tail = request
|
95
|
-
else
|
96
|
-
@requests_head = @requests_tail = request
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
def on_body(chunk)
|
101
|
-
if @waiting_for_body_chunk
|
102
|
-
@next_chunk = chunk
|
103
|
-
@waiting_for_body_chunk = nil
|
104
|
-
else
|
105
|
-
@requests_tail.buffer_body_chunk(chunk)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def on_message_complete
|
110
|
-
@waiting_for_body_chunk = nil
|
111
|
-
@requests_tail.complete!(@parser.keep_alive?)
|
112
|
-
end
|
113
|
-
|
114
|
-
# Upgrades the connection to a different protocol, if the 'Upgrade' header is
|
115
|
-
# given. By default the only supported upgrade protocol is HTTP2. Additional
|
116
|
-
# protocols, notably WebSocket, can be specified by passing a hash to the
|
117
|
-
# :upgrade option when starting a server:
|
118
|
-
#
|
119
|
-
# opts = {
|
120
|
-
# upgrade: {
|
121
|
-
# websocket: Polyphony::Websocket.handler(&method(:ws_handler))
|
122
|
-
# }
|
123
|
-
# }
|
124
|
-
# Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) { |req| ... }
|
125
|
-
#
|
126
|
-
# @param headers [Hash] request headers
|
127
|
-
# @return [boolean] truthy if the connection has been upgraded
|
128
|
-
def upgrade_connection(headers, &block)
|
129
|
-
upgrade_protocol = headers['Upgrade']
|
130
|
-
return nil unless upgrade_protocol
|
131
|
-
|
132
|
-
upgrade_protocol = upgrade_protocol.downcase.to_sym
|
133
|
-
upgrade_handler = @opts[:upgrade] && @opts[:upgrade][upgrade_protocol]
|
134
|
-
return upgrade_with_handler(upgrade_handler, headers) if upgrade_handler
|
135
|
-
return upgrade_to_http2(headers, &block) if upgrade_protocol == :h2c
|
136
|
-
|
137
|
-
nil
|
138
|
-
end
|
139
|
-
|
140
|
-
def upgrade_with_handler(handler, headers)
|
141
|
-
@parser = @requests_head = @requests_tail = nil
|
142
|
-
handler.(@conn, headers)
|
143
|
-
true
|
144
|
-
end
|
145
|
-
|
146
|
-
def upgrade_to_http2(headers, &block)
|
147
|
-
@parser = @requests_head = @requests_tail = nil
|
148
|
-
HTTP2.upgrade_each(@conn, @opts, http2_upgraded_headers(headers), &block)
|
149
|
-
true
|
150
|
-
end
|
151
|
-
|
152
|
-
# Returns headers for HTTP2 upgrade
|
153
|
-
# @param headers [Hash] request headers
|
154
|
-
# @return [Hash] headers for HTTP2 upgrade
|
155
|
-
def http2_upgraded_headers(headers)
|
156
|
-
headers.merge(
|
157
|
-
':scheme' => 'http',
|
158
|
-
':authority' => headers['Host']
|
159
|
-
)
|
160
|
-
end
|
161
|
-
|
162
|
-
# response API
|
163
|
-
|
164
|
-
# Sends response including headers and body. Waits for the request to complete
|
165
|
-
# if not yet completed. The body is sent using chunked transfer encoding.
|
166
|
-
# @param body [String] response body
|
167
|
-
# @param headers
|
168
|
-
def respond(body, headers)
|
169
|
-
consume_request if @parsing
|
170
|
-
data = format_headers(headers, body)
|
171
|
-
if body
|
172
|
-
data << if @parser.http_minor == 0
|
173
|
-
body
|
174
|
-
else
|
175
|
-
"#{body.bytesize.to_s(16)}\r\n#{body}\r\n0\r\n\r\n"
|
176
|
-
end
|
177
|
-
end
|
178
|
-
@conn << data
|
179
|
-
end
|
180
|
-
|
181
|
-
DEFAULT_HEADERS_OPTS = {
|
182
|
-
empty_response: false,
|
183
|
-
consume_request: true
|
184
|
-
}.freeze
|
185
|
-
|
186
|
-
# Sends response headers. If empty_response is truthy, the response status
|
187
|
-
# code will default to 204, otherwise to 200.
|
188
|
-
# @param headers [Hash] response headers
|
189
|
-
# @param empty_response [boolean] whether a response body will be sent
|
190
|
-
# @return [void]
|
191
|
-
def send_headers(headers, opts = DEFAULT_HEADERS_OPTS)
|
192
|
-
@conn << format_headers(headers, !opts[:empty_response])
|
193
|
-
end
|
194
|
-
|
195
|
-
# Sends a response body chunk. If no headers were sent, default headers are
|
196
|
-
# sent using #send_headers. if the done option is true(thy), an empty chunk
|
197
|
-
# will be sent to signal response completion to the client.
|
198
|
-
# @param chunk [String] response body chunk
|
199
|
-
# @param done [boolean] whether the response is completed
|
200
|
-
# @return [void]
|
201
|
-
def send_chunk(chunk, done: false)
|
202
|
-
data = +"#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
|
203
|
-
data << "0\r\n\r\n" if done
|
204
|
-
@conn << data
|
205
|
-
end
|
206
|
-
|
207
|
-
# Finishes the response to the current request. If no headers were sent,
|
208
|
-
# default headers are sent using #send_headers.
|
209
|
-
# @return [void]
|
210
|
-
def finish
|
211
|
-
@conn << "0\r\n\r\n"
|
212
|
-
end
|
213
|
-
|
214
|
-
def close
|
215
|
-
@conn.close
|
216
|
-
end
|
217
|
-
|
218
|
-
private
|
219
|
-
|
220
|
-
# Formats response headers. If empty_response is true(thy), the response
|
221
|
-
# status code will default to 204, otherwise to 200.
|
222
|
-
# @param headers [Hash] response headers
|
223
|
-
# @param empty_response [boolean] whether a response body will be sent
|
224
|
-
# @return [String] formatted response headers
|
225
|
-
def format_headers(headers, body)
|
226
|
-
status = headers[':status'] || (body ? 200 : 204)
|
227
|
-
data = format_status_line(body, status)
|
228
|
-
|
229
|
-
headers.each do |k, v|
|
230
|
-
next if k =~ /^:/
|
231
|
-
|
232
|
-
format_header_lines(k, v)
|
233
|
-
end
|
234
|
-
data << "\r\n"
|
235
|
-
end
|
236
|
-
|
237
|
-
def format_header_lines(key, value)
|
238
|
-
if value.is_a?(Array)
|
239
|
-
value.each { |item| data << "#{key}: #{item}\r\n" }
|
240
|
-
else
|
241
|
-
data << "#{key}: #{value}\r\n"
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
|
-
def format_status_line(body, status)
|
246
|
-
if !body
|
247
|
-
empty_status_line(status)
|
248
|
-
else
|
249
|
-
with_body_status_line(status, body)
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
def empty_status_line(status)
|
254
|
-
if status == 204
|
255
|
-
+"HTTP/1.1 #{status}\r\n"
|
256
|
-
else
|
257
|
-
+"HTTP/1.1 #{status}\r\nContent-Length: 0\r\n"
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
def with_body_status_line(status, body)
|
262
|
-
if @parser.http_minor == 0
|
263
|
-
+"HTTP/1.0 #{status}\r\nContent-Length: #{body.bytesize}\r\n"
|
264
|
-
else
|
265
|
-
+"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
|
266
|
-
end
|
267
|
-
end
|
268
|
-
end
|