polyphony-http 0.26 → 0.27

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.
@@ -1,78 +1,81 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export_default :Protocol
4
-
5
3
  require 'http/2'
4
+ require_relative './http2_stream'
6
5
 
7
- Stream = import './http2_stream'
6
+ module Polyphony
7
+ module HTTP
8
+ module Server
9
+ # HTTP2 server adapter
10
+ class HTTP2Adapter
11
+ def self.upgrade_each(socket, opts, headers, &block)
12
+ adapter = new(socket, opts, headers)
13
+ adapter.each(&block)
14
+ end
8
15
 
9
- # HTTP2 API
10
- class Protocol
11
- def self.upgrade_each(socket, opts, headers, &block)
12
- adapter = new(socket, opts, headers)
13
- adapter.each(&block)
14
- end
16
+ def initialize(conn, opts, upgrade_headers = nil)
17
+ @conn = conn
18
+ @opts = opts
19
+ @upgrade_headers = upgrade_headers
15
20
 
16
- def initialize(conn, opts, upgrade_headers = nil)
17
- @conn = conn
18
- @opts = opts
19
- @upgrade_headers = upgrade_headers
21
+ @interface = ::HTTP2::Server.new
22
+ @connection_fiber = Fiber.current
23
+ @interface.on(:frame, &method(:send_frame))
24
+ @streams = {}
25
+ end
20
26
 
21
- @interface = ::HTTP2::Server.new
22
- @connection_fiber = Fiber.current
23
- @interface.on(:frame, &method(:send_frame))
24
- @streams = {}
25
- end
27
+ def send_frame(data)
28
+ @conn << data
29
+ rescue Exception => e
30
+ @connection_fiber.transfer e
31
+ end
26
32
 
27
- def send_frame(data)
28
- @conn << data
29
- rescue Exception => e
30
- @connection_fiber.transfer e
31
- end
33
+ UPGRADE_MESSAGE = <<~HTTP.gsub("\n", "\r\n")
34
+ HTTP/1.1 101 Switching Protocols
35
+ Connection: Upgrade
36
+ Upgrade: h2c
32
37
 
33
- UPGRADE_MESSAGE = <<~HTTP.gsub("\n", "\r\n")
34
- HTTP/1.1 101 Switching Protocols
35
- Connection: Upgrade
36
- Upgrade: h2c
38
+ HTTP
37
39
 
38
- HTTP
40
+ def upgrade
41
+ @conn << UPGRADE_MESSAGE
42
+ settings = @upgrade_headers['HTTP2-Settings']
43
+ Fiber.current.schedule(nil)
44
+ @interface.upgrade(settings, @upgrade_headers, '')
45
+ ensure
46
+ @upgrade_headers = nil
47
+ end
39
48
 
40
- def upgrade
41
- @conn << UPGRADE_MESSAGE
42
- settings = @upgrade_headers['HTTP2-Settings']
43
- Fiber.current.schedule(nil)
44
- @interface.upgrade(settings, @upgrade_headers, '')
45
- ensure
46
- @upgrade_headers = nil
47
- end
49
+ # Iterates over incoming requests
50
+ def each(&block)
51
+ @interface.on(:stream) { |stream| start_stream(stream, &block) }
52
+ upgrade if @upgrade_headers
48
53
 
49
- # Iterates over incoming requests
50
- def each(&block)
51
- @interface.on(:stream) { |stream| start_stream(stream, &block) }
52
- upgrade if @upgrade_headers
54
+ while (data = @conn.readpartial(8192))
55
+ @interface << data
56
+ snooze
57
+ end
58
+ rescue SystemCallError, IOError
59
+ # ignore
60
+ ensure
61
+ finalize_client_loop
62
+ end
53
63
 
54
- while (data = @conn.readpartial(8192))
55
- @interface << data
56
- snooze
57
- end
58
- rescue SystemCallError, IOError
59
- # ignore
60
- ensure
61
- finalize_client_loop
62
- end
64
+ def start_stream(stream, &block)
65
+ stream = Stream.new(stream, &block)
66
+ @streams[stream] = true
67
+ end
63
68
 
64
- def start_stream(stream, &block)
65
- stream = Stream.new(stream, &block)
66
- @streams[stream] = true
67
- end
69
+ def finalize_client_loop
70
+ @interface = nil
71
+ @streams.each_key(&:stop)
72
+ @conn.close
73
+ end
68
74
 
69
- def finalize_client_loop
70
- @interface = nil
71
- @streams.each_key(&:stop)
72
- @conn.close
73
- end
74
-
75
- def close
76
- @conn.close
75
+ def close
76
+ @conn.close
77
+ end
78
+ end
79
+ end
77
80
  end
78
81
  end
@@ -1,135 +1,138 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export_default :StreamHandler
4
-
5
3
  require 'http/2'
6
-
7
- Request = import './request'
8
-
9
- # Manages an HTTP 2 stream
10
- class StreamHandler
11
- attr_accessor :__next__
12
-
13
- def initialize(stream, &block)
14
- @stream = stream
15
- @calling_fiber = Fiber.current
16
- @stream_fiber = Fiber.new { |req| handle_request(req, &block) }
17
-
18
- # Stream callbacks occur on the connection fiber (see HTTP2::Protocol#each).
19
- # The request handler is run on a separate fiber for each stream, allowing
20
- # concurrent handling of incoming requests on the same HTTP/2 connection.
21
- #
22
- # The different stream adapter APIs suspend the stream fiber, waiting for
23
- # stream callbacks to be called. The callbacks, in turn, transfer control to
24
- # the stream fiber, effectively causing the return of the adapter API calls.
25
- #
26
- # Note: the request handler is run once headers are received. Reading the
27
- # request body, if present, is at the discretion of the request handler.
28
- # This mirrors the behaviour of the HTTP/1 adapter.
29
- stream.on(:headers, &method(:on_headers))
30
- stream.on(:data, &method(:on_data))
31
- stream.on(:half_close, &method(:on_half_close))
32
- end
33
-
34
- def handle_request(request, &block)
35
- error = nil
36
- block.(request)
37
- @calling_fiber.transfer
38
- rescue Polyphony::MoveOn
39
- # ignore
40
- rescue Exception => e
41
- error = e
42
- ensure
43
- @done = true
44
- @calling_fiber.transfer error
45
- end
46
-
47
- def on_headers(headers)
48
- @request = Request.new(headers.to_h, self)
49
- @stream_fiber.transfer(@request)
50
- end
51
-
52
- def on_data(data)
53
- if @waiting_for_body_chunk
54
- @waiting_for_body_chunk = nil
55
- @stream_fiber.transfer(data)
56
- else
57
- @request.buffer_body_chunk(data)
58
- end
59
- end
60
-
61
- def on_half_close
62
- if @waiting_for_body_chunk
63
- @waiting_for_body_chunk = nil
64
- @stream_fiber.transfer(nil)
65
- elsif @waiting_for_half_close
66
- @waiting_for_half_close = nil
67
- @stream_fiber.transfer(nil)
68
- else
69
- @request.complete!
70
- end
71
- end
72
-
73
- def protocol
74
- 'h2'
75
- end
76
-
77
- def get_body_chunk
78
- # called in the context of the stream fiber
79
- return nil if @request.complete?
80
-
81
- @waiting_for_body_chunk = true
82
- # the chunk (or an exception) will be returned once the stream fiber is
83
- # resumed
84
- suspend
85
- ensure
86
- @waiting_for_body_chunk = nil
87
- end
88
-
89
- # Wait for request to finish
90
- def consume_request
91
- return if @request.complete?
92
-
93
- @waiting_for_half_close = true
94
- suspend
95
- ensure
96
- @waiting_for_half_close = nil
97
- end
98
-
99
- # response API
100
- def respond(chunk, headers)
101
- headers[':status'] ||= '200'
102
- @stream.headers(headers, end_stream: false)
103
- @stream.data(chunk, end_stream: true)
104
- @headers_sent = true
105
- end
106
-
107
- def send_headers(headers, empty_response = false)
108
- return if @headers_sent
109
-
110
- headers[':status'] ||= (empty_response ? 204 : 200).to_s
111
- @stream.headers(headers, end_stream: false)
112
- @headers_sent = true
113
- end
114
-
115
- def send_chunk(chunk, done: false)
116
- send_headers({}, false) unless @headers_sent
117
- @stream.data(chunk, end_stream: done)
118
- end
119
-
120
- def finish
121
- if @headers_sent
122
- @stream.close
123
- else
124
- headers[':status'] ||= '204'
125
- @stream.headers(headers, end_stream: true)
4
+ require_relative './request'
5
+
6
+ module Polyphony
7
+ module HTTP
8
+ module Server
9
+ # Manages an HTTP 2 stream
10
+ class HTTP2StreamHandler
11
+ attr_accessor :__next__
12
+
13
+ def initialize(stream, &block)
14
+ @stream = stream
15
+ @calling_fiber = Fiber.current
16
+ @stream_fiber = Fiber.new { |req| handle_request(req, &block) }
17
+
18
+ # Stream callbacks occur on the connection fiber (see HTTP2::Protocol#each).
19
+ # The request handler is run on a separate fiber for each stream, allowing
20
+ # concurrent handling of incoming requests on the same HTTP/2 connection.
21
+ #
22
+ # The different stream adapter APIs suspend the stream fiber, waiting for
23
+ # stream callbacks to be called. The callbacks, in turn, transfer control to
24
+ # the stream fiber, effectively causing the return of the adapter API calls.
25
+ #
26
+ # Note: the request handler is run once headers are received. Reading the
27
+ # request body, if present, is at the discretion of the request handler.
28
+ # This mirrors the behaviour of the HTTP/1 adapter.
29
+ stream.on(:headers, &method(:on_headers))
30
+ stream.on(:data, &method(:on_data))
31
+ stream.on(:half_close, &method(:on_half_close))
32
+ end
33
+
34
+ def handle_request(request, &block)
35
+ error = nil
36
+ block.(request)
37
+ @calling_fiber.transfer
38
+ rescue Polyphony::MoveOn
39
+ # ignore
40
+ rescue Exception => e
41
+ error = e
42
+ ensure
43
+ @done = true
44
+ @calling_fiber.transfer error
45
+ end
46
+
47
+ def on_headers(headers)
48
+ @request = Request.new(headers.to_h, self)
49
+ @stream_fiber.transfer(@request)
50
+ end
51
+
52
+ def on_data(data)
53
+ if @waiting_for_body_chunk
54
+ @waiting_for_body_chunk = nil
55
+ @stream_fiber.transfer(data)
56
+ else
57
+ @request.buffer_body_chunk(data)
58
+ end
59
+ end
60
+
61
+ def on_half_close
62
+ if @waiting_for_body_chunk
63
+ @waiting_for_body_chunk = nil
64
+ @stream_fiber.transfer(nil)
65
+ elsif @waiting_for_half_close
66
+ @waiting_for_half_close = nil
67
+ @stream_fiber.transfer(nil)
68
+ else
69
+ @request.complete!
70
+ end
71
+ end
72
+
73
+ def protocol
74
+ 'h2'
75
+ end
76
+
77
+ def get_body_chunk
78
+ # called in the context of the stream fiber
79
+ return nil if @request.complete?
80
+
81
+ @waiting_for_body_chunk = true
82
+ # the chunk (or an exception) will be returned once the stream fiber is
83
+ # resumed
84
+ suspend
85
+ ensure
86
+ @waiting_for_body_chunk = nil
87
+ end
88
+
89
+ # Wait for request to finish
90
+ def consume_request
91
+ return if @request.complete?
92
+
93
+ @waiting_for_half_close = true
94
+ suspend
95
+ ensure
96
+ @waiting_for_half_close = nil
97
+ end
98
+
99
+ # response API
100
+ def respond(chunk, headers)
101
+ headers[':status'] ||= '200'
102
+ @stream.headers(headers, end_stream: false)
103
+ @stream.data(chunk, end_stream: true)
104
+ @headers_sent = true
105
+ end
106
+
107
+ def send_headers(headers, empty_response = false)
108
+ return if @headers_sent
109
+
110
+ headers[':status'] ||= (empty_response ? 204 : 200).to_s
111
+ @stream.headers(headers, end_stream: false)
112
+ @headers_sent = true
113
+ end
114
+
115
+ def send_chunk(chunk, done: false)
116
+ send_headers({}, false) unless @headers_sent
117
+ @stream.data(chunk, end_stream: done)
118
+ end
119
+
120
+ def finish
121
+ if @headers_sent
122
+ @stream.close
123
+ else
124
+ headers[':status'] ||= '204'
125
+ @stream.headers(headers, end_stream: true)
126
+ end
127
+ end
128
+
129
+ def stop
130
+ return if @done
131
+
132
+ @stream.close
133
+ @stream_fiber.schedule(Polyphony::MoveOn.new)
134
+ end
135
+ end
126
136
  end
127
137
  end
128
-
129
- def stop
130
- return if @done
131
-
132
- @stream.close
133
- @stream_fiber.schedule(Polyphony::MoveOn.new)
134
- end
135
138
  end
@@ -1,118 +1,122 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export_default :Request
4
-
5
3
  require 'uri'
6
4
 
7
- # HTTP request
8
- class Request
9
- attr_reader :headers, :adapter
10
- attr_accessor :__next__
11
-
12
- def initialize(headers, adapter)
13
- @headers = headers
14
- @adapter = adapter
15
- end
16
-
17
- def protocol
18
- @protocol = @adapter.protocol
19
- end
20
-
21
- def method
22
- @method ||= @headers[':method']
23
- end
24
-
25
- def scheme
26
- @scheme ||= @headers[':scheme']
27
- end
28
-
29
- def uri
30
- @uri ||= URI.parse(@headers[':path'] || '')
31
- end
32
-
33
- def path
34
- @path ||= uri.path
35
- end
36
-
37
- def query_string
38
- @query_string ||= uri.query
39
- end
40
-
41
- def query
42
- return @query if @query
43
-
44
- @query = (q = uri.query) ? split_query_string(q) : {}
45
- end
46
-
47
- def split_query_string(query)
48
- query.split('&').each_with_object({}) do |kv, h|
49
- k, v = kv.split('=')
50
- h[k.to_sym] = URI.decode_www_form_component(v)
51
- end
52
- end
53
-
54
- def buffer_body_chunk(chunk)
55
- @buffered_body_chunks ||= []
56
- @buffered_body_chunks << chunk
57
- end
58
-
59
- def each_chunk(&block)
60
- if @buffered_body_chunks
61
- @buffered_body_chunks.each(&block)
62
- @buffered_body_chunks = nil
5
+ module Polyphony
6
+ module HTTP
7
+ module Server
8
+ # HTTP request
9
+ class Request
10
+ attr_reader :headers, :adapter
11
+ attr_accessor :__next__
12
+
13
+ def initialize(headers, adapter)
14
+ @headers = headers
15
+ @adapter = adapter
16
+ end
17
+
18
+ def protocol
19
+ @protocol = @adapter.protocol
20
+ end
21
+
22
+ def method
23
+ @method ||= @headers[':method']
24
+ end
25
+
26
+ def scheme
27
+ @scheme ||= @headers[':scheme']
28
+ end
29
+
30
+ def uri
31
+ @uri ||= URI.parse(@headers[':path'] || '')
32
+ end
33
+
34
+ def path
35
+ @path ||= uri.path
36
+ end
37
+
38
+ def query_string
39
+ @query_string ||= uri.query
40
+ end
41
+
42
+ def query
43
+ return @query if @query
44
+
45
+ @query = (q = uri.query) ? split_query_string(q) : {}
46
+ end
47
+
48
+ def split_query_string(query)
49
+ query.split('&').each_with_object({}) do |kv, h|
50
+ k, v = kv.split('=')
51
+ h[k.to_sym] = URI.decode_www_form_component(v)
52
+ end
53
+ end
54
+
55
+ def buffer_body_chunk(chunk)
56
+ @buffered_body_chunks ||= []
57
+ @buffered_body_chunks << chunk
58
+ end
59
+
60
+ def each_chunk(&block)
61
+ if @buffered_body_chunks
62
+ @buffered_body_chunks.each(&block)
63
+ @buffered_body_chunks = nil
64
+ end
65
+ while !@message_complete && (chunk = @adapter.get_body_chunk)
66
+ yield chunk
67
+ end
68
+ end
69
+
70
+ def complete!(keep_alive = nil)
71
+ @message_complete = true
72
+ @keep_alive = keep_alive
73
+ end
74
+
75
+ def complete?
76
+ @message_complete
77
+ end
78
+
79
+ def consume
80
+ @adapter.consume_request
81
+ end
82
+
83
+ def keep_alive?
84
+ @keep_alive
85
+ end
86
+
87
+ def read
88
+ buf = @buffered_body_chunks ? @buffered_body_chunks.join : +''
89
+ while (chunk = @adapter.get_body_chunk)
90
+ buf << chunk
91
+ end
92
+ buf
93
+ end
94
+
95
+ def respond(body, headers = {})
96
+ @adapter.respond(body, headers)
97
+ @headers_sent = true
98
+ end
99
+
100
+ def send_headers(headers = {}, empty_response = false)
101
+ return if @headers_sent
102
+
103
+ @headers_sent = true
104
+ @adapter.send_headers(headers, empty_response: empty_response)
105
+ end
106
+
107
+ def send_chunk(body, done: false)
108
+ send_headers({}) unless @headers_sent
109
+
110
+ @adapter.send_chunk(body, done: done)
111
+ end
112
+ alias_method :<<, :send_chunk
113
+
114
+ def finish
115
+ send_headers({}) unless @headers_sent
116
+
117
+ @adapter.finish
118
+ end
119
+ end
63
120
  end
64
- while !@message_complete && (chunk = @adapter.get_body_chunk)
65
- yield chunk
66
- end
67
- end
68
-
69
- def complete!(keep_alive = nil)
70
- @message_complete = true
71
- @keep_alive = keep_alive
72
- end
73
-
74
- def complete?
75
- @message_complete
76
- end
77
-
78
- def consume
79
- @adapter.consume_request
80
- end
81
-
82
- def keep_alive?
83
- @keep_alive
84
- end
85
-
86
- def read
87
- buf = @buffered_body_chunks ? @buffered_body_chunks.join : +''
88
- while (chunk = @adapter.get_body_chunk)
89
- buf << chunk
90
- end
91
- buf
92
- end
93
-
94
- def respond(body, headers = {})
95
- @adapter.respond(body, headers)
96
- @headers_sent = true
97
- end
98
-
99
- def send_headers(headers = {}, empty_response = false)
100
- return if @headers_sent
101
-
102
- @headers_sent = true
103
- @adapter.send_headers(headers, empty_response: empty_response)
104
- end
105
-
106
- def send_chunk(body, done: false)
107
- send_headers({}) unless @headers_sent
108
-
109
- @adapter.send_chunk(body, done: done)
110
- end
111
- alias_method :<<, :send_chunk
112
-
113
- def finish
114
- send_headers({}) unless @headers_sent
115
-
116
- @adapter.finish
117
121
  end
118
- end
122
+ end