polyphony-http 0.26 → 0.27

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76672787719c4ccfd1cd92917b206e11d512341aa2186f3e3c22089dfd1df440
4
- data.tar.gz: c7ba3c2b3f61ed1626f299a8e5b142fca52a6beafed7aca3805ca7f1dfe42a8c
3
+ metadata.gz: 7d801a50af8ee9175a493eaacd5420e77e83b8afca771d68e3816e932ffd3ba4
4
+ data.tar.gz: aec82ce54ea3f047ef572c5b5bf624536ec06cb3eba4cde96b83d8a70835930b
5
5
  SHA512:
6
- metadata.gz: 06fe3a7fa8eb7d265367269d8f4f4c92854e84c18de2ee301d73f0755f8c4b24ac6b04e332a2b5fabac7b4ff8e7776966800aae1574cd2b2c5b6b42d9866f5b3
7
- data.tar.gz: 2912df5bf797b51bb25d83c4ce5bbe81823aa31a3536a2f76124e3d66801086519784f0ea8584e6cf8ea51343d3c5fb8b7b34d323199c3a79c694ae2bb4c833e
6
+ metadata.gz: 91addbdbae045ceb39459f7bc8699685a5c8269d413f87888c96982ba470525b9d43b58e79c7600394eb0ba3b195948ebe9dc9162ffcea2e4f22c13d08fe1aee
7
+ data.tar.gz: 6ba0bd3b38b22d1925c3b43d869646ba8173acaf999d4df84b47c69b093f04071cd91f159c454e6a23389f6e2ddee60b9005e85d4d5b8d33c6b887da7f3ec658
@@ -0,0 +1,18 @@
1
+ name: Tests
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v1
10
+ - uses: actions/setup-ruby@v1
11
+ with:
12
+ ruby-version: 2.6.5
13
+ - name: Install dependencies
14
+ run: |
15
+ gem install bundler
16
+ bundle install
17
+ - name: Run tests
18
+ run: bundle exec rake test
@@ -1,6 +1,10 @@
1
+ ## 0.27 2020-04-14
2
+
3
+ * Remove modulation dependency
4
+
1
5
  ## 0.26 2020-03-03
2
6
 
3
- * Fix "Server#listen"
7
+ * Fix `Server#listen`
4
8
 
5
9
  ## 0.25 2020-02-19
6
10
 
@@ -1,11 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony-http (0.26)
4
+ polyphony-http (0.27)
5
5
  http-2 (~> 0.10.0)
6
6
  http_parser.rb (~> 0.6.0)
7
- modulation (~> 1.0)
8
- polyphony (~> 0.32)
7
+ polyphony (~> 0.38)
9
8
  rack (~> 2.0.8)
10
9
  websocket (~> 1.2.8)
11
10
 
@@ -25,10 +24,9 @@ GEM
25
24
  builder
26
25
  minitest (>= 5.0)
27
26
  ruby-progressbar
28
- modulation (1.0)
29
- polyphony (0.32)
30
- modulation (~> 1.0)
27
+ polyphony (0.38)
31
28
  rack (2.0.9)
29
+ rake (12.3.3)
32
30
  ruby-progressbar (1.10.1)
33
31
  simplecov (0.17.1)
34
32
  docile (~> 1.1)
@@ -45,7 +43,8 @@ DEPENDENCIES
45
43
  minitest (~> 5.11.3)
46
44
  minitest-reporters (~> 1.4.2)
47
45
  polyphony-http!
46
+ rake (~> 12.3.3)
48
47
  simplecov (~> 0.17.1)
49
48
 
50
49
  BUNDLED WITH
51
- 2.1.3
50
+ 2.1.4
@@ -1,51 +1,56 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export :serve, :listen, :accept_loop, :client_loop
4
-
5
- Net = Polyphony::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
- ensure
17
- server.close
18
- end
19
-
20
- def listen(host, port, opts = {})
21
- opts[:alpn_protocols] = ALPN_PROTOCOLS
22
- Net.tcp_listen(host, port, opts).tap do |socket|
23
- socket.define_singleton_method(:each) do |&block|
24
- MODULE.accept_loop(socket, opts, &block)
3
+ require_relative './server/http1'
4
+ require_relative './server/http2'
5
+
6
+ module Polyphony
7
+ module HTTP
8
+ module Server
9
+ ALPN_PROTOCOLS = %w[h2 http/1.1].freeze
10
+ H2_PROTOCOL = 'h2'
11
+
12
+ class << self
13
+ def serve(host, port, opts = {}, &handler)
14
+ opts[:alpn_protocols] = ALPN_PROTOCOLS
15
+ server = Net.tcp_listen(host, port, opts)
16
+ accept_loop(server, opts, &handler)
17
+ ensure
18
+ server.close
19
+ end
20
+
21
+ def listen(host, port, opts = {})
22
+ opts[:alpn_protocols] = ALPN_PROTOCOLS
23
+ Net.tcp_listen(host, port, opts).tap do |socket|
24
+ socket.define_singleton_method(:each) do |&block|
25
+ ::Polyphony::HTTP::Server.accept_loop(socket, opts, &block)
26
+ end
27
+ end
28
+ end
29
+
30
+ def accept_loop(server, opts, &handler)
31
+ loop do
32
+ client = server.accept
33
+ spin { client_loop(client, opts, &handler) }
34
+ rescue OpenSSL::SSL::SSLError
35
+ # disregard
36
+ end
37
+ end
38
+
39
+ def client_loop(client, opts, &handler)
40
+ client.no_delay if client.respond_to?(:no_delay)
41
+ adapter = protocol_adapter(client, opts)
42
+ adapter.each(&handler)
43
+ ensure
44
+ client.close
45
+ end
46
+
47
+ def protocol_adapter(socket, opts)
48
+ use_http2 = socket.respond_to?(:alpn_protocol) &&
49
+ socket.alpn_protocol == H2_PROTOCOL
50
+ klass = use_http2 ? HTTP2Adapter : HTTP1Adapter
51
+ klass.new(socket, opts)
52
+ end
53
+ end
25
54
  end
26
55
  end
27
56
  end
28
-
29
- def accept_loop(server, opts, &handler)
30
- loop do
31
- client = server.accept
32
- spin { client_loop(client, opts, &handler) }
33
- rescue OpenSSL::SSL::SSLError
34
- # disregard
35
- end
36
- end
37
-
38
- def client_loop(client, opts, &handler)
39
- client.no_delay if client.respond_to?(:no_delay)
40
- adapter = protocol_adapter(client, opts)
41
- adapter.each(&handler)
42
- ensure
43
- client.close
44
- end
45
-
46
- def protocol_adapter(socket, opts)
47
- use_http2 = socket.respond_to?(:alpn_protocol) &&
48
- socket.alpn_protocol == H2_PROTOCOL
49
- klass = use_http2 ? HTTP2 : HTTP1
50
- klass.new(socket, opts)
51
- end
@@ -1,267 +1,270 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export_default :HTTP1Adapter
4
-
5
3
  require 'http/parser'
6
-
7
- Request = import('./request')
8
- HTTP2 = import('./http2')
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
- end
18
-
19
- def each(&block)
20
- while (data = @conn.readpartial(8192))
21
- return if handle_incoming_data(data, &block)
22
- end
23
- rescue SystemCallError, IOError
24
- # ignore
25
- ensure
26
- finalize_client_loop
27
- end
28
-
29
- # return [Boolean] true if client loop should stop
30
- def handle_incoming_data(data, &block)
31
- @parser << data
32
- snooze
33
- while (request = @requests_head)
34
- return true if upgrade_connection(request.headers, &block)
35
-
36
- @requests_head = request.__next__
37
- block.call(request)
38
- return true unless request.keep_alive?
39
- end
40
- nil
41
- end
42
-
43
- def finalize_client_loop
44
- # release references to various objects
45
- @requests_head = @requests_tail = nil
46
- @parser = nil
47
- @conn.close
48
- end
49
-
50
- # Reads a body chunk for the current request. Transfers control to the parse
51
- # loop, and resumes once the parse_loop has fired the on_body callback
52
- def get_body_chunk
53
- @waiting_for_body_chunk = true
54
- @next_chunk = nil
55
- while !@requests_tail.complete? && (data = @conn.readpartial(8192))
56
- @parser << data
57
- return @next_chunk if @next_chunk
58
-
59
- snooze
60
- end
61
- nil
62
- ensure
63
- @waiting_for_body_chunk = nil
64
- end
65
-
66
- # Waits for the current request to complete. Transfers control to the parse
67
- # loop, and resumes once the parse_loop has fired the on_message_complete
68
- # callback
69
- def consume_request
70
- request = @requests_head
71
- while (data = @conn.readpartial(8192))
72
- @parser << data
73
- return if request.complete?
74
-
75
- snooze
76
- end
77
- end
78
-
79
- def protocol
80
- version = @parser.http_version
81
- "HTTP #{version.join('.')}"
82
- end
83
-
84
- def on_headers_complete(headers)
85
- headers[':path'] = @parser.request_url
86
- headers[':method'] = @parser.http_method
87
- queue_request(Request.new(headers, self))
88
- end
89
-
90
- def queue_request(request)
91
- if @requests_head
92
- @requests_tail.__next__ = request
93
- @requests_tail = request
94
- else
95
- @requests_head = @requests_tail = request
96
- end
97
- end
98
-
99
- def on_body(chunk)
100
- if @waiting_for_body_chunk
101
- @next_chunk = chunk
102
- @waiting_for_body_chunk = nil
103
- else
104
- @requests_tail.buffer_body_chunk(chunk)
105
- end
106
- end
107
-
108
- def on_message_complete
109
- @waiting_for_body_chunk = nil
110
- @requests_tail.complete!(@parser.keep_alive?)
111
- end
112
-
113
- # Upgrades the connection to a different protocol, if the 'Upgrade' header is
114
- # given. By default the only supported upgrade protocol is HTTP2. Additional
115
- # protocols, notably WebSocket, can be specified by passing a hash to the
116
- # :upgrade option when starting a server:
117
- #
118
- # opts = {
119
- # upgrade: {
120
- # websocket: Polyphony::Websocket.handler(&method(:ws_handler))
121
- # }
122
- # }
123
- # Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) { |req| ... }
124
- #
125
- # @param headers [Hash] request headers
126
- # @return [boolean] truthy if the connection has been upgraded
127
- def upgrade_connection(headers, &block)
128
- upgrade_protocol = headers['Upgrade']
129
- return nil unless upgrade_protocol
130
-
131
- upgrade_protocol = upgrade_protocol.downcase.to_sym
132
- upgrade_handler = @opts[:upgrade] && @opts[:upgrade][upgrade_protocol]
133
- return upgrade_with_handler(upgrade_handler, headers) if upgrade_handler
134
- return upgrade_to_http2(headers, &block) if upgrade_protocol == :h2c
135
-
136
- nil
137
- end
138
-
139
- def upgrade_with_handler(handler, headers)
140
- @parser = @requests_head = @requests_tail = nil
141
- handler.(@conn, headers)
142
- true
143
- end
144
-
145
- def upgrade_to_http2(headers, &block)
146
- @parser = @requests_head = @requests_tail = nil
147
- HTTP2.upgrade_each(@conn, @opts, http2_upgraded_headers(headers), &block)
148
- true
149
- end
150
-
151
- # Returns headers for HTTP2 upgrade
152
- # @param headers [Hash] request headers
153
- # @return [Hash] headers for HTTP2 upgrade
154
- def http2_upgraded_headers(headers)
155
- headers.merge(
156
- ':scheme' => 'http',
157
- ':authority' => headers['Host']
158
- )
159
- end
160
-
161
- # response API
162
-
163
- # Sends response including headers and body. Waits for the request to complete
164
- # if not yet completed. The body is sent using chunked transfer encoding.
165
- # @param body [String] response body
166
- # @param headers
167
- def respond(body, headers)
168
- consume_request if @parsing
169
- data = format_headers(headers, body)
170
- if body
171
- data << if @parser.http_minor == 0
172
- body
173
- else
174
- "#{body.bytesize.to_s(16)}\r\n#{body}\r\n0\r\n\r\n"
175
- end
176
- end
177
- @conn << data
178
- end
179
-
180
- DEFAULT_HEADERS_OPTS = {
181
- empty_response: false,
182
- consume_request: true
183
- }.freeze
184
-
185
- # Sends response headers. If empty_response is truthy, the response status
186
- # code will default to 204, otherwise to 200.
187
- # @param headers [Hash] response headers
188
- # @param empty_response [boolean] whether a response body will be sent
189
- # @return [void]
190
- def send_headers(headers, opts = DEFAULT_HEADERS_OPTS)
191
- @conn << format_headers(headers, !opts[:empty_response])
192
- end
193
-
194
- # Sends a response body chunk. If no headers were sent, default headers are
195
- # sent using #send_headers. if the done option is true(thy), an empty chunk
196
- # will be sent to signal response completion to the client.
197
- # @param chunk [String] response body chunk
198
- # @param done [boolean] whether the response is completed
199
- # @return [void]
200
- def send_chunk(chunk, done: false)
201
- data = +"#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
202
- data << "0\r\n\r\n" if done
203
- @conn << data
204
- end
205
-
206
- # Finishes the response to the current request. If no headers were sent,
207
- # default headers are sent using #send_headers.
208
- # @return [void]
209
- def finish
210
- @conn << "0\r\n\r\n"
211
- end
212
-
213
- def close
214
- @conn.close
215
- end
216
-
217
- private
218
-
219
- # Formats response headers. If empty_response is true(thy), the response
220
- # status code will default to 204, otherwise to 200.
221
- # @param headers [Hash] response headers
222
- # @param empty_response [boolean] whether a response body will be sent
223
- # @return [String] formatted response headers
224
- def format_headers(headers, body)
225
- status = headers[':status'] || (body ? 200 : 204)
226
- data = format_status_line(body, status)
227
-
228
- headers.each do |k, v|
229
- next if k =~ /^:/
230
-
231
- format_header_lines(k, v)
232
- end
233
- data << "\r\n"
234
- end
235
-
236
- def format_header_lines(key, value)
237
- if value.is_a?(Array)
238
- value.inject(+'') { |data, item| data << "#{key}: #{item}\r\n" }
239
- else
240
- "#{key}: #{value}\r\n"
241
- end
242
- end
243
-
244
- def format_status_line(body, status)
245
- if !body
246
- empty_status_line(status)
247
- else
248
- with_body_status_line(status, body)
249
- end
250
- end
251
-
252
- def empty_status_line(status)
253
- if status == 204
254
- +"HTTP/1.1 #{status}\r\n"
255
- else
256
- +"HTTP/1.1 #{status}\r\nContent-Length: 0\r\n"
257
- end
258
- end
259
-
260
- def with_body_status_line(status, body)
261
- if @parser.http_minor == 0
262
- +"HTTP/1.0 #{status}\r\nContent-Length: #{body.bytesize}\r\n"
263
- else
264
- +"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
4
+ require_relative './request'
5
+ require_relative './http2'
6
+
7
+ module Polyphony
8
+ module HTTP
9
+ module Server
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
+ end
18
+
19
+ def each(&block)
20
+ while (data = @conn.readpartial(8192))
21
+ return if handle_incoming_data(data, &block)
22
+ end
23
+ rescue SystemCallError, IOError
24
+ # ignore
25
+ ensure
26
+ finalize_client_loop
27
+ end
28
+
29
+ # return [Boolean] true if client loop should stop
30
+ def handle_incoming_data(data, &block)
31
+ @parser << data
32
+ snooze
33
+ while (request = @requests_head)
34
+ return true if upgrade_connection(request.headers, &block)
35
+
36
+ @requests_head = request.__next__
37
+ block.call(request)
38
+ return true unless request.keep_alive?
39
+ end
40
+ nil
41
+ end
42
+
43
+ def finalize_client_loop
44
+ # release references to various objects
45
+ @requests_head = @requests_tail = nil
46
+ @parser = nil
47
+ @conn.close
48
+ end
49
+
50
+ # Reads a body chunk for the current request. Transfers control to the parse
51
+ # loop, and resumes once the parse_loop has fired the on_body callback
52
+ def get_body_chunk
53
+ @waiting_for_body_chunk = true
54
+ @next_chunk = nil
55
+ while !@requests_tail.complete? && (data = @conn.readpartial(8192))
56
+ @parser << data
57
+ return @next_chunk if @next_chunk
58
+
59
+ snooze
60
+ end
61
+ nil
62
+ ensure
63
+ @waiting_for_body_chunk = nil
64
+ end
65
+
66
+ # Waits for the current request to complete. Transfers control to the parse
67
+ # loop, and resumes once the parse_loop has fired the on_message_complete
68
+ # callback
69
+ def consume_request
70
+ request = @requests_head
71
+ while (data = @conn.readpartial(8192))
72
+ @parser << data
73
+ return if request.complete?
74
+
75
+ snooze
76
+ end
77
+ end
78
+
79
+ def protocol
80
+ version = @parser.http_version
81
+ "HTTP #{version.join('.')}"
82
+ end
83
+
84
+ def on_headers_complete(headers)
85
+ headers[':path'] = @parser.request_url
86
+ headers[':method'] = @parser.http_method
87
+ queue_request(Request.new(headers, self))
88
+ end
89
+
90
+ def queue_request(request)
91
+ if @requests_head
92
+ @requests_tail.__next__ = request
93
+ @requests_tail = request
94
+ else
95
+ @requests_head = @requests_tail = request
96
+ end
97
+ end
98
+
99
+ def on_body(chunk)
100
+ if @waiting_for_body_chunk
101
+ @next_chunk = chunk
102
+ @waiting_for_body_chunk = nil
103
+ else
104
+ @requests_tail.buffer_body_chunk(chunk)
105
+ end
106
+ end
107
+
108
+ def on_message_complete
109
+ @waiting_for_body_chunk = nil
110
+ @requests_tail.complete!(@parser.keep_alive?)
111
+ end
112
+
113
+ # Upgrades the connection to a different protocol, if the 'Upgrade' header is
114
+ # given. By default the only supported upgrade protocol is HTTP2. Additional
115
+ # protocols, notably WebSocket, can be specified by passing a hash to the
116
+ # :upgrade option when starting a server:
117
+ #
118
+ # opts = {
119
+ # upgrade: {
120
+ # websocket: Polyphony::Websocket.handler(&method(:ws_handler))
121
+ # }
122
+ # }
123
+ # Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) { |req| ... }
124
+ #
125
+ # @param headers [Hash] request headers
126
+ # @return [boolean] truthy if the connection has been upgraded
127
+ def upgrade_connection(headers, &block)
128
+ upgrade_protocol = headers['Upgrade']
129
+ return nil unless upgrade_protocol
130
+
131
+ upgrade_protocol = upgrade_protocol.downcase.to_sym
132
+ upgrade_handler = @opts[:upgrade] && @opts[:upgrade][upgrade_protocol]
133
+ return upgrade_with_handler(upgrade_handler, headers) if upgrade_handler
134
+ return upgrade_to_http2(headers, &block) if upgrade_protocol == :h2c
135
+
136
+ nil
137
+ end
138
+
139
+ def upgrade_with_handler(handler, headers)
140
+ @parser = @requests_head = @requests_tail = nil
141
+ handler.(@conn, headers)
142
+ true
143
+ end
144
+
145
+ def upgrade_to_http2(headers, &block)
146
+ @parser = @requests_head = @requests_tail = nil
147
+ HTTP2Adapter.upgrade_each(@conn, @opts, http2_upgraded_headers(headers), &block)
148
+ true
149
+ end
150
+
151
+ # Returns headers for HTTP2 upgrade
152
+ # @param headers [Hash] request headers
153
+ # @return [Hash] headers for HTTP2 upgrade
154
+ def http2_upgraded_headers(headers)
155
+ headers.merge(
156
+ ':scheme' => 'http',
157
+ ':authority' => headers['Host']
158
+ )
159
+ end
160
+
161
+ # response API
162
+
163
+ # Sends response including headers and body. Waits for the request to complete
164
+ # if not yet completed. The body is sent using chunked transfer encoding.
165
+ # @param body [String] response body
166
+ # @param headers
167
+ def respond(body, headers)
168
+ consume_request if @parsing
169
+ data = format_headers(headers, body)
170
+ if body
171
+ data << if @parser.http_minor == 0
172
+ body
173
+ else
174
+ "#{body.bytesize.to_s(16)}\r\n#{body}\r\n0\r\n\r\n"
175
+ end
176
+ end
177
+ @conn << data
178
+ end
179
+
180
+ DEFAULT_HEADERS_OPTS = {
181
+ empty_response: false,
182
+ consume_request: true
183
+ }.freeze
184
+
185
+ # Sends response headers. If empty_response is truthy, the response status
186
+ # code will default to 204, otherwise to 200.
187
+ # @param headers [Hash] response headers
188
+ # @param empty_response [boolean] whether a response body will be sent
189
+ # @return [void]
190
+ def send_headers(headers, opts = DEFAULT_HEADERS_OPTS)
191
+ @conn << format_headers(headers, !opts[:empty_response])
192
+ end
193
+
194
+ # Sends a response body chunk. If no headers were sent, default headers are
195
+ # sent using #send_headers. if the done option is true(thy), an empty chunk
196
+ # will be sent to signal response completion to the client.
197
+ # @param chunk [String] response body chunk
198
+ # @param done [boolean] whether the response is completed
199
+ # @return [void]
200
+ def send_chunk(chunk, done: false)
201
+ data = +"#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
202
+ data << "0\r\n\r\n" if done
203
+ @conn << data
204
+ end
205
+
206
+ # Finishes the response to the current request. If no headers were sent,
207
+ # default headers are sent using #send_headers.
208
+ # @return [void]
209
+ def finish
210
+ @conn << "0\r\n\r\n"
211
+ end
212
+
213
+ def close
214
+ @conn.close
215
+ end
216
+
217
+ private
218
+
219
+ # Formats response headers. If empty_response is true(thy), the response
220
+ # status code will default to 204, otherwise to 200.
221
+ # @param headers [Hash] response headers
222
+ # @param empty_response [boolean] whether a response body will be sent
223
+ # @return [String] formatted response headers
224
+ def format_headers(headers, body)
225
+ status = headers[':status'] || (body ? 200 : 204)
226
+ data = format_status_line(body, status)
227
+
228
+ headers.each do |k, v|
229
+ next if k =~ /^:/
230
+
231
+ format_header_lines(k, v)
232
+ end
233
+ data << "\r\n"
234
+ end
235
+
236
+ def format_header_lines(key, value)
237
+ if value.is_a?(Array)
238
+ value.inject(+'') { |data, item| data << "#{key}: #{item}\r\n" }
239
+ else
240
+ "#{key}: #{value}\r\n"
241
+ end
242
+ end
243
+
244
+ def format_status_line(body, status)
245
+ if !body
246
+ empty_status_line(status)
247
+ else
248
+ with_body_status_line(status, body)
249
+ end
250
+ end
251
+
252
+ def empty_status_line(status)
253
+ if status == 204
254
+ +"HTTP/1.1 #{status}\r\n"
255
+ else
256
+ +"HTTP/1.1 #{status}\r\nContent-Length: 0\r\n"
257
+ end
258
+ end
259
+
260
+ def with_body_status_line(status, body)
261
+ if @parser.http_minor == 0
262
+ +"HTTP/1.0 #{status}\r\nContent-Length: #{body.bytesize}\r\n"
263
+ else
264
+ +"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
265
+ end
266
+ end
267
+ end
265
268
  end
266
269
  end
267
270
  end