polyphony-http 0.26 → 0.27

Sign up to get free protection for your applications and to get access to all the features.
@@ -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