protocol-http 0.20.1 → 0.21.0
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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 01dc23ef2870a6572cc986fd3dac2de5902ec3283fb5a380d9f24b1193516d68
|
4
|
+
data.tar.gz: ca6bf9694e01884a6d2c2e9ab5e85c54d64c4f150881cf57fb2ba1ed80fa0b9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d71145aa0b55861140b181c2351501cd9eec840b747abd4d5d48841a4e8d2df0bb5d2e5fa0c0c83be98a51775e92160cd52690806c5b1443808f7d6386cdc568
|
7
|
+
data.tar.gz: f2f603776458e27ed0b2148fce7c9f9ac419417c001e2ea42270248df651c34f524b6646926847a8aa93139509b8bd72d86c875a3b341e9c16eb0a124ecfd6a5
|
data/STREAMING.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# Streaming
|
2
|
+
|
3
|
+
Streaming is a complex topic given that most server's are not scalable in this respect. A variety of solutions exist to work around limited server implementations, but they impose significant burden on the implementation and are generally incompatible with implementations of HTTP which are not request per connection. We propose to simplify the current Rack implementation to support streaming in a consistent way, but also allow for backwards compatibility with existing servers.
|
4
|
+
|
5
|
+
## Current Implementation
|
6
|
+
|
7
|
+
Rack currently supports streaming using `rack.hijack`. There are two ways to hijack the underlying socket for a given request: a partial hijack and a full hijack. Not all servers support both modes of operation and neither method is fully compatible with HTTP/2.
|
8
|
+
|
9
|
+
### Partial Hijack
|
10
|
+
|
11
|
+
A partial hijack allows the web server to respond with the response status, headers, and then yields the stream back to the application code. It does this by using a special `rack.hijack` header:
|
12
|
+
|
13
|
+
``` ruby
|
14
|
+
def call(env)
|
15
|
+
body = proc do |stream|
|
16
|
+
stream.write("Hello World")
|
17
|
+
stream.close
|
18
|
+
end
|
19
|
+
|
20
|
+
if env['rack.hijack?']
|
21
|
+
return [200, {'rack.hijack' => body}, nil]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
```
|
25
|
+
|
26
|
+
- It is not clear whether the body needs to handle the formatting of the response, i.e. chunked encoding, compression, transfer encoding, etc.
|
27
|
+
- It mixes `rack.` specific headers with normal HTTP headers which is computationally expensive for the server to detect.
|
28
|
+
- It's not clear what the returned body should be.
|
29
|
+
|
30
|
+
### Full Hijack
|
31
|
+
|
32
|
+
A full hijack allows the web server to completely take control of the underlying socket. This occurs at the point the `rack.hijack` is invoked.
|
33
|
+
|
34
|
+
``` ruby
|
35
|
+
def call(env)
|
36
|
+
if hijack = env['rack.hijack']
|
37
|
+
socket = hijack.call
|
38
|
+
|
39
|
+
Thread.new do
|
40
|
+
socket.write("Hello World")
|
41
|
+
socket.close
|
42
|
+
end
|
43
|
+
|
44
|
+
return nil # ???
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
- The socket might not actually be a raw socket, if the server is using TLS.
|
50
|
+
- Extreme care is required in order to handle the protocol correctly.
|
51
|
+
- It's not clear if such a design can work with anything other than HTTP/1.
|
52
|
+
- The user code **must** return from the `call(env)` method in order for the server continue handling requests.
|
53
|
+
- It's not clear what the response should be.
|
54
|
+
|
55
|
+
## Typical Scenarios
|
56
|
+
|
57
|
+
There are several typical scenarios where `rack.hijack` has been used.
|
58
|
+
|
59
|
+
### ActionCable
|
60
|
+
|
61
|
+
ActionCable uses full hijack in order to negotiate and communicate using WebSocket protocol. Such a design requires the ActionCable server to run in the same process as the web server.
|
62
|
+
|
63
|
+
### MessageBus
|
64
|
+
|
65
|
+
MessageBus uses full hijack in order to keep the socket alive and feed messages occasionally.
|
66
|
+
|
67
|
+
## Supporting HTTP/2
|
68
|
+
|
69
|
+
It is possible to support partial hijack in HTTP/2. The hijacked stream is multiplexed the same as any other connection. However, such a design cannot be thread safe without significant design trade-offs.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright,
|
3
|
+
# Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
4
|
#
|
5
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -25,8 +25,8 @@ require_relative 'wrapper'
|
|
25
25
|
module Protocol
|
26
26
|
module HTTP
|
27
27
|
module Body
|
28
|
-
# Invokes a callback once the body has
|
29
|
-
class
|
28
|
+
# Invokes a callback once the body has completed, either successfully or due to an error.
|
29
|
+
class Completable < Wrapper
|
30
30
|
def self.wrap(message, &block)
|
31
31
|
if body = message&.body and !body.empty?
|
32
32
|
message.body = self.new(message.body, block)
|
@@ -60,6 +60,21 @@ module Protocol
|
|
60
60
|
nil
|
61
61
|
end
|
62
62
|
|
63
|
+
# Should the internal mechanism prefer to use {call}?
|
64
|
+
# @returns [Boolean]
|
65
|
+
def stream?
|
66
|
+
false
|
67
|
+
end
|
68
|
+
|
69
|
+
# Write the body to the given stream.
|
70
|
+
def call(stream)
|
71
|
+
while chunk = self.read
|
72
|
+
stream.write(chunk)
|
73
|
+
end
|
74
|
+
ensure
|
75
|
+
stream.close($!)
|
76
|
+
end
|
77
|
+
|
63
78
|
# Read all remaining chunks into a buffered body and close the underlying input.
|
64
79
|
def finish
|
65
80
|
# Internally, this invokes `self.each` which then invokes `self.close`.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: protocol-http
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.21.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-11-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: covered
|
@@ -64,11 +64,13 @@ files:
|
|
64
64
|
- ".gitignore"
|
65
65
|
- ".rspec"
|
66
66
|
- README.md
|
67
|
+
- STREAMING.md
|
67
68
|
- bake.rb
|
68
69
|
- gems.rb
|
69
70
|
- lib/protocol/http.rb
|
70
71
|
- lib/protocol/http/accept_encoding.rb
|
71
72
|
- lib/protocol/http/body/buffered.rb
|
73
|
+
- lib/protocol/http/body/completable.rb
|
72
74
|
- lib/protocol/http/body/deflate.rb
|
73
75
|
- lib/protocol/http/body/digestable.rb
|
74
76
|
- lib/protocol/http/body/file.rb
|
@@ -78,7 +80,6 @@ files:
|
|
78
80
|
- lib/protocol/http/body/reader.rb
|
79
81
|
- lib/protocol/http/body/rewindable.rb
|
80
82
|
- lib/protocol/http/body/stream.rb
|
81
|
-
- lib/protocol/http/body/streamable.rb
|
82
83
|
- lib/protocol/http/body/wrapper.rb
|
83
84
|
- lib/protocol/http/content_encoding.rb
|
84
85
|
- lib/protocol/http/cookie.rb
|
@@ -121,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
122
|
- !ruby/object:Gem::Version
|
122
123
|
version: '0'
|
123
124
|
requirements: []
|
124
|
-
rubygems_version: 3.
|
125
|
+
rubygems_version: 3.1.2
|
125
126
|
signing_key:
|
126
127
|
specification_version: 4
|
127
128
|
summary: Provides abstractions to handle HTTP protocols.
|