polyphony-http 0.24

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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +56 -0
  3. data/CHANGELOG.md +6 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +51 -0
  6. data/LICENSE +21 -0
  7. data/README.md +47 -0
  8. data/Rakefile +20 -0
  9. data/TODO.md +59 -0
  10. data/bin/poly +11 -0
  11. data/docs/README.md +38 -0
  12. data/docs/summary.md +60 -0
  13. data/examples/cuba.ru +22 -0
  14. data/examples/happy_eyeballs.rb +37 -0
  15. data/examples/http2_raw.rb +135 -0
  16. data/examples/http_client.rb +28 -0
  17. data/examples/http_get.rb +33 -0
  18. data/examples/http_parse_experiment.rb +123 -0
  19. data/examples/http_proxy.rb +83 -0
  20. data/examples/http_server.js +24 -0
  21. data/examples/http_server.rb +21 -0
  22. data/examples/http_server_forked.rb +29 -0
  23. data/examples/http_server_graceful.rb +27 -0
  24. data/examples/http_server_simple.rb +11 -0
  25. data/examples/http_server_throttled.rb +15 -0
  26. data/examples/http_server_timeout.rb +35 -0
  27. data/examples/http_ws_server.rb +37 -0
  28. data/examples/https_raw_client.rb +12 -0
  29. data/examples/https_server.rb +22 -0
  30. data/examples/https_wss_server.rb +39 -0
  31. data/examples/rack_server.rb +12 -0
  32. data/examples/rack_server_https.rb +19 -0
  33. data/examples/rack_server_https_forked.rb +27 -0
  34. data/examples/websocket_secure_server.rb +27 -0
  35. data/examples/websocket_server.rb +24 -0
  36. data/examples/ws_page.html +34 -0
  37. data/examples/wss_page.html +34 -0
  38. data/lib/polyphony/http.rb +16 -0
  39. data/lib/polyphony/http/client/agent.rb +131 -0
  40. data/lib/polyphony/http/client/http1.rb +129 -0
  41. data/lib/polyphony/http/client/http2.rb +180 -0
  42. data/lib/polyphony/http/client/response.rb +32 -0
  43. data/lib/polyphony/http/client/site_connection_manager.rb +109 -0
  44. data/lib/polyphony/http/server.rb +49 -0
  45. data/lib/polyphony/http/server/http1.rb +267 -0
  46. data/lib/polyphony/http/server/http2.rb +78 -0
  47. data/lib/polyphony/http/server/http2_stream.rb +135 -0
  48. data/lib/polyphony/http/server/rack.rb +64 -0
  49. data/lib/polyphony/http/server/request.rb +118 -0
  50. data/lib/polyphony/http/version.rb +7 -0
  51. data/lib/polyphony/websocket.rb +59 -0
  52. data/polyphony-http.gemspec +34 -0
  53. data/test/coverage.rb +45 -0
  54. data/test/eg.rb +27 -0
  55. data/test/helper.rb +35 -0
  56. data/test/run.rb +5 -0
  57. data/test/test_http_server.rb +313 -0
  58. metadata +245 -0
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+ require 'http/2'
6
+
7
+ # Response = import '../../lib/polyphony/http/client/response'
8
+
9
+ url = 'https://realiteq.net/?q=time'
10
+ uri = URI(url)
11
+ uri_key = { scheme: uri.scheme, host: uri.host, port: uri.port }
12
+
13
+ ctx = {
14
+ method: :GET,
15
+ uri: uri,
16
+ opts: {},
17
+ retry: 0
18
+ }
19
+
20
+ SECURE_OPTS = { secure: true, alpn_protocols: ['h2', 'http/1.1'] }.freeze
21
+ socket = Polyphony::Net.tcp_connect(uri_key[:host], uri_key[:port], SECURE_OPTS)
22
+
23
+ puts 'connected'
24
+
25
+ $client = HTTP2::Client.new
26
+ $client.on(:frame) { |bytes| socket << bytes }
27
+ $client.on(:frame_received) do |frame|
28
+ puts "Received frame: #{frame.inspect}"
29
+ end
30
+ # $client.on(:frame_sent) do |frame|
31
+ # puts "Sent frame: #{frame.inspect}"
32
+ # end
33
+
34
+ reader = spin do
35
+ while (data = socket.readpartial(8192))
36
+ $client << data
37
+ snooze
38
+ end
39
+ end
40
+
41
+ stream = $client.new_stream
42
+
43
+ $headers = nil
44
+ $done = nil
45
+ @buffered_chunks = []
46
+
47
+ @waiting_headers_fiber = nil
48
+ @waiting_chunk_fiber = nil
49
+ @waiting_done_fiber = nil
50
+
51
+ # send request
52
+ headers = {
53
+ ':method' => ctx[:method].to_s,
54
+ ':scheme' => ctx[:uri].scheme,
55
+ ':authority' => [ctx[:uri].host, ctx[:uri].port].join(':'),
56
+ ':path' => ctx[:uri].request_uri,
57
+ 'User-Agent' => 'curl/7.54.0'
58
+ }
59
+ headers.merge!(ctx[:opts][:headers]) if ctx[:opts][:headers]
60
+
61
+ if ctx[:opts][:payload]
62
+ stream.headers(headers, end_stream: false)
63
+ stream.data(ctx[:opts][:payload], end_stream: true)
64
+ else
65
+ stream.headers(headers, end_stream: true)
66
+ end
67
+
68
+ stream.on(:headers) { |headers|
69
+ puts "got headers"
70
+ # if @waiting_headers_fiber
71
+ # @waiting_headers_fiber.transfer headers.to_h
72
+ # else
73
+ $headers = headers.to_h
74
+ # end
75
+ }
76
+ stream.on(:data) { |chunk|
77
+ puts "got data"
78
+ # if @waiting_chunk_fiber
79
+ # @waiting_chunk_fiber&.transfer c
80
+ # else
81
+ @buffered_chunks << chunk
82
+ # end
83
+ }
84
+
85
+ def close
86
+ puts "got close"
87
+ $done = true
88
+ end
89
+
90
+ stream.on(:close) { close }
91
+ # @waiting_done_fiber&.transfer
92
+ # }
93
+
94
+ stream.on(:active) { puts "* active" }
95
+ stream.on(:half_close) { puts "* half_close" }
96
+
97
+
98
+
99
+ # wait for response
100
+ # unless $headers
101
+ # @waiting_headers_fiber = Fiber.current
102
+ # $headers = suspend
103
+ # end
104
+ # p $headers
105
+ # response = Response.new(self, $headers[':status'].to_i, $headers)
106
+ # p response
107
+
108
+ puts "waiting for response"
109
+ while !$done
110
+ puts "waiting..."
111
+ sleep 1
112
+ end
113
+ puts "done"
114
+
115
+ # def body
116
+ # @waiting_chunk_fiber = Fiber.current
117
+ # body = +''
118
+ # while !$done
119
+ # chunk = suspend
120
+ # body << chunk
121
+ # end
122
+ # body
123
+ # rescue => e
124
+ # p e
125
+ # puts e.backtrace.join("\n")
126
+ # end
127
+ # end
128
+
129
+ # adapter = StreamAdapter.new
130
+ # resp = adapter.request(ctx)
131
+ # puts "*" * 40
132
+ # p resp
133
+
134
+ # body = resp.body
135
+ # p body
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/http'
5
+
6
+ Exception.__disable_sanitized_backtrace__ = true
7
+
8
+ TIME_URI = 'https://ui.realiteq.net/'
9
+
10
+ def get_server_time
11
+ json = Polyphony::HTTP::Agent.get(TIME_URI, query: { q: :time }).json
12
+ puts "*" * 40
13
+ p json
14
+ end
15
+
16
+ X = 1
17
+ puts "Making #{X} requests..."
18
+ t0 = Time.now
19
+ supervise do |s|
20
+ X.times {
21
+ s.spin {
22
+ get_server_time
23
+ }
24
+ }
25
+ end
26
+ # get_server_time
27
+ elapsed = Time.now - t0
28
+ puts "count: #{X} elapsed: #{elapsed} rate: #{X / elapsed} reqs/s"
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/http'
5
+ require 'polyphony'
6
+
7
+ Exception.__disable_sanitized_backtrace__ = true
8
+
9
+ resp = Polyphony::HTTP::Agent.get('https://realiteq.net/?q=time')
10
+ puts "*" * 40
11
+ puts resp.body
12
+
13
+ __END__
14
+
15
+ X = 1
16
+ Y = 1
17
+ t0 = Time.now
18
+ supervise { |s|
19
+ X.times {
20
+ s.spin {
21
+ Y.times {
22
+ resp = Polyphony::HTTP::Agent.get('http://about.gitlab.com/')
23
+ puts "*" * 40
24
+ p resp.headers
25
+ puts "*" * 40
26
+ puts resp.body
27
+ # puts "body size: #{resp.body.bytesize}"
28
+ }
29
+ }
30
+ }
31
+ }
32
+ elapsed = Time.now - t0
33
+ puts "\nelapsed: #{elapsed} rate: #{(X * Y) / elapsed}"
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ require 'http/parser'
7
+ require 'fiber'
8
+
9
+ i, o = IO.pipe
10
+
11
+ class ParseLoop
12
+ def initialize(conn)
13
+ @parser = HTTP::Parser.new(self)
14
+ @conn = conn
15
+ @parse_fiber = Fiber.new do
16
+ while (data = conn.readpartial(8192))
17
+ @parser << data
18
+ snooze
19
+ end
20
+ rescue StandardError => e
21
+ conn.close
22
+ e
23
+ ensure
24
+ @message_in_train = nil
25
+ end
26
+ @state = nil
27
+ end
28
+
29
+ def on_headers_complete(headers)
30
+ @calling_fiber.transfer(headers)
31
+ end
32
+
33
+ def on_body(chunk)
34
+ @calling_fiber.transfer(chunk) if @read_body
35
+ end
36
+
37
+ def on_message_begin
38
+ @message_in_train = true
39
+ end
40
+
41
+ def on_message_complete
42
+ @message_in_train = nil
43
+ @calling_fiber.transfer nil
44
+ end
45
+
46
+ def parse_headers
47
+ @calling_fiber = Fiber.current
48
+ @parse_fiber.safe_transfer
49
+ end
50
+
51
+ def parse_body_chunk
52
+ @calling_fiber = Fiber.current
53
+ @read_body = true
54
+ @parse_fiber.safe_transfer
55
+ end
56
+
57
+ def consume_request
58
+ return unless @message_in_train
59
+
60
+ @calling_fiber = Fiber.current
61
+ @read_body = false
62
+ @parse_fiber.safe_transfer while @message_in_train
63
+ end
64
+
65
+ def alive?
66
+ @parse_fiber.alive?
67
+ end
68
+
69
+ def busy?
70
+ @message_in_train
71
+ end
72
+ end
73
+
74
+ def handle(parser)
75
+ headers = parser.parse_headers
76
+ return unless headers
77
+
78
+ puts "headers: #{headers.inspect}"
79
+ headers['Content-Length']
80
+ # if content_length && (content_length.to_i < 1000)
81
+ while (chunk = parser.parse_body_chunk)
82
+ puts "chunk: #{chunk.inspect}"
83
+ end
84
+ # else
85
+ # parser.consume_request
86
+ # end
87
+ puts 'end of request'
88
+ rescue StandardError => e
89
+ puts "error: #{e.inspect}"
90
+ raise e
91
+ end
92
+
93
+ writer = spin do
94
+ o << "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 6\r\n\r\n"
95
+ o << 'Hello!'
96
+
97
+ o << "POST / HTTP/1.1\r\nHost: example.com\r\n\r\n"
98
+
99
+ # o << "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 8192\r\n\r\n"
100
+ # o << ("Bye!" * 2048)
101
+
102
+ o << "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\n"
103
+ o << 'Bye!'
104
+
105
+ begin
106
+ (o << ('BLAH' * 100_000))
107
+ rescue StandardError
108
+ nil
109
+ end
110
+ o.close
111
+ end
112
+
113
+ begin
114
+ parse_loop = ParseLoop.new(i)
115
+ while parse_loop.alive?
116
+ puts '*' * 40
117
+ handle(parse_loop)
118
+ end
119
+ rescue StandardError => e
120
+ writer.stop
121
+ puts "#{e.class}: #{e.message}"
122
+ puts e.backtrace
123
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/http'
5
+ require 'localhost/authority'
6
+
7
+ # p Polyphony::HTTP::Agent.get('https://ui.realiteq.net/', q: :time)
8
+
9
+ BASE_URL = 'http://realiteq.net'
10
+
11
+ CACHE = {}.freeze
12
+
13
+ def proxy(uri, opts)
14
+ now = Time.now
15
+ uri = BASE_URL + uri
16
+ entry = CACHE[uri]
17
+ return entry[:response] if entry && entry[:expires] >= now
18
+
19
+ puts "proxy => #{uri} (#{opts.inspect})"
20
+ response = Polyphony::HTTP::Agent.get(uri, opts)
21
+ # CACHE[uri] = {
22
+ # expires: now + 60,
23
+ # response: response
24
+ # }
25
+ response
26
+ end
27
+
28
+ HEADERS_BLACK_LIST = %w[
29
+ Transfer-Encoding Date Server Connection Content-Length Cache-Control
30
+ :method :authority :scheme :path
31
+ ].freeze
32
+
33
+ def sanitize_headers(headers)
34
+ headers.reject { |k, _v| HEADERS_BLACK_LIST.include?(k) }
35
+ end
36
+
37
+ def sanitize_html(html)
38
+ # html.gsub('http://martigny-le-comte.fr/', '/')
39
+ end
40
+
41
+ # authority = Localhost::Authority.fetch
42
+
43
+ rsa_cert = OpenSSL::X509::Certificate.new(
44
+ IO.read('../reality/aws/config/ssl/full_chain.pem')
45
+ )
46
+ rsa_pkey = OpenSSL::PKey.read(
47
+ IO.read('../reality/aws/config/ssl/private_key.pem')
48
+ )
49
+ ctx = OpenSSL::SSL::SSLContext.new
50
+ ctx.add_certificate(rsa_cert, rsa_pkey)
51
+
52
+ opts = {
53
+ reuse_addr: true,
54
+ dont_linger: true,
55
+ secure_context: ctx # authority.server_context
56
+ }
57
+
58
+ spin do
59
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
60
+ puts "#{req.method} #{req.uri}"
61
+ puts "headers <: #{req.headers.inspect}"
62
+ # h = {
63
+ # uri: req.uri.to_s,
64
+ # protocol: req.protocol,
65
+ # headers: req.headers,
66
+ # body: req.body,
67
+ # }
68
+ response = proxy(req.uri.to_s, headers: sanitize_headers(req.headers))
69
+ headers = sanitize_headers(response[:headers])
70
+ body = response[:body]
71
+ # puts "body class: #{body.class}"
72
+ puts "headers >: #{response[:headers].inspect}"
73
+ # body = sanitize_html(body) if headers['Content-Type'] =~ /text\/html/
74
+ req.respond(body, headers)
75
+ rescue StandardError => e
76
+ puts 'error'
77
+ p e
78
+ puts e.backtrace.join("\n")
79
+ end
80
+ end
81
+
82
+ puts "pid: #{Process.pid}"
83
+ puts 'Listening on port 1234...'
@@ -0,0 +1,24 @@
1
+ // For the sake of comparing performance, here's a node.js-based HTTP server
2
+ // doing roughly the same thing as http_server. Preliminary benchmarking shows
3
+ // the ruby version has a throughput (req/s) of about 2/3 of the JS version.
4
+
5
+ const http = require('http');
6
+
7
+ const MSG = 'Hello World';
8
+
9
+ const server = http.createServer((req, res) => {
10
+ // let requestCopy = {
11
+ // method: req.method,
12
+ // request_url: req.url,
13
+ // headers: req.headers
14
+ // };
15
+
16
+ // res.writeHead(200, { 'Content-Type': 'application/json' });
17
+ // res.end(JSON.stringify(requestCopy));
18
+
19
+ res.writeHead(200);
20
+ res.end(MSG)
21
+ });
22
+
23
+ server.listen(1235);
24
+ console.log('Listening on port 1235');
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/http'
5
+
6
+ opts = {
7
+ reuse_addr: true,
8
+ dont_linger: true
9
+ }
10
+
11
+ spin do
12
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
13
+ req.respond("Hello world!\n")
14
+ rescue Exception => e
15
+ p e
16
+ end
17
+ end
18
+
19
+ puts "pid: #{Process.pid}"
20
+ puts 'Listening on port 1234...'
21
+ suspend