ftw 0.0.7 → 0.0.8
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.
- data/README.md +21 -12
- data/lib/ftw/singleton.rb +12 -0
- data/lib/ftw/version.rb +1 -1
- data/lib/ftw/websocket/constants.rb +1 -0
- data/lib/ftw/websocket/rack.rb +42 -0
- data/lib/ftw/websocket/writer.rb +4 -1
- data/lib/rack/handler/ftw.rb +30 -2
- metadata +1 -1
data/README.md
CHANGED
@@ -62,13 +62,29 @@ I do not plan on exposing any direct means for invoking SPDY.
|
|
62
62
|
|
63
63
|
## Server API
|
64
64
|
|
65
|
-
|
65
|
+
I have implemented a rack server, Rack::Handler::FTW. It does not comply fully
|
66
|
+
with the Rack spec. See 'Rack Compliance Issues' below.
|
66
67
|
|
67
|
-
|
68
|
+
Under the FTW rack handler, there is an environment variable added,
|
69
|
+
"ftw.connection". This will be a FTW::Connection you can use for CONNECT,
|
70
|
+
Upgrades, etc.
|
68
71
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
+
There's also a websockets wrapper, FTW::WebSockets::Rack, that will help you
|
73
|
+
specifically with websocket requests and such.
|
74
|
+
|
75
|
+
## Rack Compliance issues
|
76
|
+
|
77
|
+
Due to some awkward and bad requirements - specifically those around the
|
78
|
+
specified behavior of 'rack.input' - I can't support the rack specification fully.
|
79
|
+
|
80
|
+
The 'rack.input' must be an IO-like object supporting #rewind which rewinds to
|
81
|
+
the beginning of the request.
|
82
|
+
|
83
|
+
For high-data connections (like uploads, HTTP CONNECT, and HTTP Upgrade), it's
|
84
|
+
not practical to hold the entire history of time in a buffer. We'll run out of
|
85
|
+
memory, you crazy!
|
86
|
+
|
87
|
+
Details here: https://github.com/rack/rack/issues/347
|
72
88
|
|
73
89
|
## Other Projects
|
74
90
|
|
@@ -79,10 +95,3 @@ Here are some related projects that I have no affiliation with:
|
|
79
95
|
* https://github.com/lifo/cramp - real-time web framework (async, websockets)
|
80
96
|
* https://github.com/igrigorik/em-http-request - HTTP client for EventMachine
|
81
97
|
* https://github.com/geemus/excon - http client library
|
82
|
-
|
83
|
-
## Missing Features
|
84
|
-
|
85
|
-
* No Rack support, for now. There are technical requirements the Rack SPEC that
|
86
|
-
prevent rack applications from really servicing uploads, HTTP Upgrades, etc.
|
87
|
-
Details here: https://github.com/rack/rack/issues/347
|
88
|
-
|
data/lib/ftw/singleton.rb
CHANGED
@@ -1,10 +1,22 @@
|
|
1
1
|
require "ftw/namespace"
|
2
2
|
|
3
|
+
# A mixin that provides singleton-ness
|
4
|
+
#
|
5
|
+
# Usage:
|
6
|
+
#
|
7
|
+
# class Foo
|
8
|
+
# extend FTW::Singleton
|
9
|
+
#
|
10
|
+
# ...
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# foo = Foo.singleton
|
3
14
|
module FTW::Singleton
|
4
15
|
def self.included(klass)
|
5
16
|
raise ArgumentError.new("In #{klass.name}, you want to use 'extend #{self.name}', not 'include ...'")
|
6
17
|
end # def included
|
7
18
|
|
19
|
+
# Create a singleton instance of this class.
|
8
20
|
def singleton
|
9
21
|
@instance ||= self.new
|
10
22
|
return @instance
|
data/lib/ftw/version.rb
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
# The opcode definitions come from:
|
6
6
|
# http://tools.ietf.org/html/rfc6455#section-11.8
|
7
7
|
module FTW::WebSocket::Constants
|
8
|
+
# websocket uuid, used in hash signing of websocket responses (RFC6455)
|
8
9
|
WEBSOCKET_ACCEPT_UUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
9
10
|
|
10
11
|
# Indication that this frame is a continuation in a fragmented message
|
data/lib/ftw/websocket/rack.rb
CHANGED
@@ -3,11 +3,29 @@ require "ftw/websocket/parser"
|
|
3
3
|
require "base64" # stdlib
|
4
4
|
require "digest/sha1" # stdlib
|
5
5
|
|
6
|
+
# A websocket helper for Rack
|
7
|
+
#
|
8
|
+
# An example with Sinatra:
|
9
|
+
#
|
10
|
+
# get "/websocket/echo" do
|
11
|
+
# ws = FTW::WebSocket::Rack.new(env)
|
12
|
+
# stream(:keep_open) do |out|
|
13
|
+
# ws.each do |payload|
|
14
|
+
# # 'payload' is the text payload of a single websocket message
|
15
|
+
# # publish it back to the client
|
16
|
+
# ws.publish(payload)
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
# ws.rack_response
|
20
|
+
# end
|
6
21
|
class FTW::WebSocket::Rack
|
7
22
|
include FTW::WebSocket::Constants
|
8
23
|
|
9
24
|
private
|
10
25
|
|
26
|
+
# Create a new websocket rack helper... thing.
|
27
|
+
#
|
28
|
+
# @param rack_env the 'env' bit given to your Rack application
|
11
29
|
def initialize(rack_env)
|
12
30
|
@env = rack_env
|
13
31
|
@handshake_errors = []
|
@@ -28,16 +46,28 @@ class FTW::WebSocket::Rack
|
|
28
46
|
@parser = FTW::WebSocket::Parser.new
|
29
47
|
end # def initialize
|
30
48
|
|
49
|
+
# Test values for equality. This is used in handshake tests.
|
31
50
|
def expect_equal(expected, actual, message)
|
32
51
|
if expected != actual
|
33
52
|
@handshake_errors << message
|
34
53
|
end
|
35
54
|
end # def expected
|
36
55
|
|
56
|
+
# Is this a valid handshake?
|
37
57
|
def valid?
|
38
58
|
return @handshake_errors.empty?
|
39
59
|
end # def valid?
|
40
60
|
|
61
|
+
# Get the response Rack is expecting.
|
62
|
+
#
|
63
|
+
# If this was a valid websocket request, it will return a response
|
64
|
+
# that completes the HTTP portion of the websocket handshake.
|
65
|
+
#
|
66
|
+
# If this was an invalid websocket request, it will return a
|
67
|
+
# 400 status code and descriptions of what failed in the body
|
68
|
+
# of the response.
|
69
|
+
#
|
70
|
+
# @return [number, hash, body]
|
41
71
|
def rack_response
|
42
72
|
if valid?
|
43
73
|
# Return the status, headers, body that is expected.
|
@@ -58,6 +88,15 @@ class FTW::WebSocket::Rack
|
|
58
88
|
end
|
59
89
|
end # def rack_response
|
60
90
|
|
91
|
+
# Enumerate each websocket payload (message).
|
92
|
+
#
|
93
|
+
# The payload of each message will be yielded to the block.
|
94
|
+
#
|
95
|
+
# Example:
|
96
|
+
#
|
97
|
+
# ws.each do |payload|
|
98
|
+
# puts "Received: #{payload}"
|
99
|
+
# end
|
61
100
|
def each
|
62
101
|
connection = @env["ftw.connection"]
|
63
102
|
while true
|
@@ -68,6 +107,9 @@ class FTW::WebSocket::Rack
|
|
68
107
|
end
|
69
108
|
end # def each
|
70
109
|
|
110
|
+
# Publish a message over this websocket.
|
111
|
+
#
|
112
|
+
# @param message Publish a string message to the websocket.
|
71
113
|
def publish(message)
|
72
114
|
writer = FTW::WebSocket::Writer.singleton
|
73
115
|
writer.write_text(@env["ftw.connection"], message)
|
data/lib/ftw/websocket/writer.rb
CHANGED
@@ -29,7 +29,10 @@ class FTW::WebSocket::Writer
|
|
29
29
|
include FTW::WebSocket::Constants
|
30
30
|
extend FTW::Singleton
|
31
31
|
|
32
|
-
#
|
32
|
+
# A list of valid modes. Used to validate input in #write_text.
|
33
|
+
#
|
34
|
+
# In :server mode, payloads are not masked. In :client mode, payloads
|
35
|
+
# are masked. Masking is described in RFC6455.
|
33
36
|
VALID_MODES = [:server, :client]
|
34
37
|
|
35
38
|
private
|
data/lib/rack/handler/ftw.rb
CHANGED
@@ -23,35 +23,56 @@ class Rack::Handler::FTW
|
|
23
23
|
include FTW::Protocol
|
24
24
|
include FTW::CRLF
|
25
25
|
|
26
|
+
# The version of the rack specification supported by this handler.
|
26
27
|
RACK_VERSION = [1,1]
|
28
|
+
|
29
|
+
# A string constant value (used to avoid typos).
|
27
30
|
REQUEST_METHOD = "REQUEST_METHOD".freeze
|
31
|
+
# A string constant value (used to avoid typos).
|
28
32
|
SCRIPT_NAME = "SCRIPT_NAME".freeze
|
33
|
+
# A string constant value (used to avoid typos).
|
29
34
|
PATH_INFO = "PATH_INFO".freeze
|
35
|
+
# A string constant value (used to avoid typos).
|
30
36
|
QUERY_STRING = "QUERY_STRING".freeze
|
37
|
+
# A string constant value (used to avoid typos).
|
31
38
|
SERVER_NAME = "SERVER_NAME".freeze
|
39
|
+
# A string constant value (used to avoid typos).
|
32
40
|
SERVER_PORT = "SERVER_PORT".freeze
|
33
41
|
|
42
|
+
# A string constant value (used to avoid typos).
|
34
43
|
RACK_DOT_VERSION = "rack.version".freeze
|
44
|
+
# A string constant value (used to avoid typos).
|
35
45
|
RACK_DOT_URL_SCHEME = "rack.url_scheme".freeze
|
46
|
+
# A string constant value (used to avoid typos).
|
36
47
|
RACK_DOT_INPUT = "rack.input".freeze
|
48
|
+
# A string constant value (used to avoid typos).
|
37
49
|
RACK_DOT_ERRORS = "rack.errors".freeze
|
50
|
+
# A string constant value (used to avoid typos).
|
38
51
|
RACK_DOT_MULTITHREAD = "rack.multithread".freeze
|
52
|
+
# A string constant value (used to avoid typos).
|
39
53
|
RACK_DOT_MULTIPROCESS = "rack.multiprocess".freeze
|
54
|
+
# A string constant value (used to avoid typos).
|
40
55
|
RACK_DOT_RUN_ONCE = "rack.run_once".freeze
|
56
|
+
# A string constant value (used to avoid typos).
|
41
57
|
FTW_DOT_CONNECTION = "ftw.connection".freeze
|
42
58
|
|
59
|
+
# This method is invoked when rack starts this as the server.
|
43
60
|
def self.run(app, config)
|
44
61
|
server = self.new(app, config)
|
45
62
|
server.run
|
46
|
-
end
|
63
|
+
end # def self.run
|
47
64
|
|
48
65
|
private
|
49
66
|
|
67
|
+
# setup a new rack server
|
50
68
|
def initialize(app, config)
|
51
69
|
@app = app
|
52
70
|
@config = config
|
53
|
-
end
|
71
|
+
end # def initialize
|
54
72
|
|
73
|
+
# Run the server.
|
74
|
+
#
|
75
|
+
# Connections are farmed out to threads.
|
55
76
|
def run
|
56
77
|
# {:environment=>"development", :pid=>nil, :Port=>9292, :Host=>"0.0.0.0",
|
57
78
|
# :AccessLog=>[], :config=>"/home/jls/projects/ruby-ftw/examples/test.ru",
|
@@ -72,6 +93,11 @@ class Rack::Handler::FTW
|
|
72
93
|
end
|
73
94
|
end # def run
|
74
95
|
|
96
|
+
# Handle a new connection.
|
97
|
+
#
|
98
|
+
# This method parses http requests and passes them on to #handle_request
|
99
|
+
#
|
100
|
+
# @param connection The FTW::Connection being handled.
|
75
101
|
def handle_connection(connection)
|
76
102
|
while true
|
77
103
|
begin
|
@@ -86,6 +112,8 @@ class Rack::Handler::FTW
|
|
86
112
|
connection.disconnect("Fun")
|
87
113
|
end # def handle_connection
|
88
114
|
|
115
|
+
# Handle a request. This will set up the rack 'env' and invoke the
|
116
|
+
# application associated with this handler.
|
89
117
|
def handle_request(request, connection)
|
90
118
|
path, query = request.path.split("?", 2)
|
91
119
|
env = {
|