ftw 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- 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 = {
|