reel 0.2.0 → 0.3.0.pre

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of reel might be problematic. Click here for more details.

data/.travis.yml CHANGED
@@ -1,10 +1,9 @@
1
1
  rvm:
2
- - 1.9.2
3
2
  - 1.9.3
4
- # - ruby-head http_parser.rb not working :(
3
+ - ruby-head
5
4
  - jruby-19mode
6
5
  - jruby-head
7
- # - rbx-19mode choking on string encodings
6
+ - rbx-19mode
8
7
 
9
8
  notifications:
10
9
  irc: "irc.freenode.org#celluloid"
data/CHANGES.md CHANGED
@@ -1,5 +1,15 @@
1
- HEAD
2
- ----
1
+ 0.3.0
2
+ -----
3
+ * Reel::App: Sinatra-like DSL for defining Reel apps using Octarine
4
+ * Chunked upload support
5
+ * Lots of additional work on the Rack adapter
6
+ * Expose websockets through Rack as rack.websocket
7
+ * Performance optimization work
8
+ * Bugfix: Send CRLF after chunks
9
+ * Bugfix: Increase TCP connection backlog to 1024
10
+
11
+ 0.2.0
12
+ -----
3
13
  * Initial WebSockets support via Reel::WebSocket
4
14
  * Experimental Rack adapter by Alberto Fernández-Capel
5
15
  * Octarine (Sinatra-like DSL) support by Grant Rodgers
data/README.md CHANGED
@@ -1,14 +1,16 @@
1
1
  ![Reel](https://github.com/celluloid/reel/raw/master/logo.png)
2
2
  =======
3
3
  [![Build Status](https://secure.travis-ci.org/celluloid/reel.png?branch=master)](http://travis-ci.org/celluloid/reel)
4
+ [![Dependency Status](https://gemnasium.com/celluloid/reel.png)](https://gemnasium.com/celluloid/reel)
5
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/celluloid/reel)
4
6
 
5
7
  Reel is a fast, non-blocking "evented" web server built on [http_parser.rb][parser],
6
- [libwebsocket][websockets],[ Celluloid::IO][celluloidio], and [nio4r][nio4r]. Thanks
8
+ [websocket_parser][websockets], [Celluloid::IO][celluloidio], and [nio4r][nio4r]. Thanks
7
9
  to Celluloid, Reel also works great for multithreaded applications and provides
8
10
  traditional multithreaded blocking I/O support too.
9
11
 
10
12
  [parser]: https://github.com/tmm1/http_parser.rb
11
- [websockets]: https://github.com/imanel/websocket-ruby
13
+ [websockets]: https://github.com/afcapel/websocket_parser
12
14
  [celluloidio]: https://github.com/celluloid/celluloid-io
13
15
  [nio4r]: https://github.com/tarcieri/nio4r
14
16
 
@@ -24,9 +26,10 @@ primarily I/O bound, and threads for where you're compute bound.
24
26
 
25
27
  ### Is it any good?
26
28
 
27
- [Yes](http://news.ycombinator.com/item?id=3067434),
28
- but it has room for improvement. A "hello world" web server benchmark,
29
- run on a 2GHz i7 (OS X 10.7.3). All servers used in a single-threaded mode.
29
+ [Yes](http://news.ycombinator.com/item?id=3067434)
30
+
31
+ Here's a "hello world" web server benchmark, run on a 2GHz i7 (OS X 10.7.3).
32
+ All servers used in a single-threaded mode.
30
33
 
31
34
  Reel performance on various Ruby VMs:
32
35
 
@@ -35,10 +38,9 @@ Reel performance on various Ruby VMs:
35
38
 
36
39
  Ruby Version Throughput Latency
37
40
  ------------ ---------- -------
38
- JRuby HEAD 5650 reqs/s (0.2 ms/req)
39
- Ruby 1.9.3 5263 reqs/s (0.2 ms/req)
40
- JRuby 1.6.7 4303 reqs/s (0.2 ms/req)
41
+ JRuby 1.7.0 3978 req/s (0.3 ms/req)
41
42
  rbx HEAD 2288 reqs/s (0.4 ms/req)
43
+ Ruby 1.9.3 2071 req/s (0.5 ms/req)
42
44
  ```
43
45
 
44
46
  Comparison with other web servers:
@@ -54,10 +56,15 @@ Node.js (0.6.5) 11735 reqs/s (0.1 ms/req)
54
56
  All Ruby benchmarks done on Ruby 1.9.3. Latencies given are average-per-request
55
57
  and are not amortized across all concurrent requests.
56
58
 
57
- Usage
58
- -----
59
+ API
60
+ ---
61
+
62
+ Reel also provides a "bare metal" API which was used in the benchmarks above.
63
+ Here are some examples:
64
+
65
+ ### Block Form
59
66
 
60
- Reel provides an extremely simple API:
67
+ Reel lets you pass a block to initialize which receives connections:
61
68
 
62
69
  ```ruby
63
70
  require 'reel'
@@ -67,11 +74,11 @@ Reel::Server.supervise("0.0.0.0", 3000) do |connection|
67
74
  case request
68
75
  when Reel::Request
69
76
  puts "Client requested: #{request.method} #{request.url}"
70
- connection.respond :ok, "hello, world"
77
+ request.respond :ok, "Hello, world!"
71
78
  when Reel::WebSocket
72
79
  puts "Client made a WebSocket request to: #{request.url}"
73
- request << "Hello there"
74
- connection.close
80
+ request << "Hello everyone out there in WebSocket land"
81
+ request.close
75
82
  break
76
83
  end
77
84
  end
@@ -82,12 +89,91 @@ When we read a request from the incoming connection, we'll either get back
82
89
  a Reel::Request object, indicating a normal HTTP connection, or a
83
90
  Reel::WebSocket object for WebSockets connections.
84
91
 
85
- Status
86
- ------
92
+ ### Subclass Form
93
+
94
+ You can also subclass Reel, which allows additional customizations:
95
+
96
+ ```ruby
97
+ require 'reel'
98
+
99
+ class MyServer < Reel::Server
100
+ def initialize(host = "127.0.0.1", port = 3000)
101
+ super(host, port, &method(:on_connection))
102
+ end
103
+
104
+ def on_connection(connection)
105
+ while request = connection.request
106
+ case request
107
+ when Reel::Request
108
+ handle_request(request)
109
+ when Reel::WebSocket
110
+ handle_websocket(request)
111
+ end
112
+ end
113
+ end
114
+
115
+ def handle_request(request)
116
+ request.respond :ok, "Hello, world!"
117
+ end
118
+
119
+ def handle_websocket(sock)
120
+ sock << "Hello everyone out there in WebSocket land!"
121
+ sock.close
122
+ end
123
+ end
124
+
125
+ MyServer.run
126
+ ```
127
+
128
+ Framework Adapters
129
+ ------------------
130
+ ### Rack
131
+
132
+ Reel can be used as a standard Rack server via the "reel" command line
133
+ application. Please be aware that Rack support is experimental and that there
134
+ are potential complications between using large numbers of rack middlewares
135
+ and the limited 4kB stack depth of Ruby Fibers, which are used extensively
136
+ by Celluloid. In addition, the Rack specification mandates that request bodies
137
+ are rewindable, which prevents streaming request bodies as the spec dictates
138
+ they must be written to disk.
139
+
140
+ To really leverage Reel's capabilities, you must use Reel via its own API,
141
+ or another Ruby library with direct Reel support.
142
+
143
+ ### Webmachine
144
+
145
+ The most notable library with native Reel support is
146
+ [webmachine-ruby](https://github.com/seancribbs/webmachine-ruby),
147
+ an advanced HTTP framework for Ruby with a complete state machine for proper
148
+ processing of HTTP/1.1 requests. Together with Reel, Webmachine provides
149
+ full streaming support for both requests and responses.
150
+
151
+ To use Reel with Webmachine, add the following to your Gemfile:
152
+
153
+ ```ruby
154
+ gem 'webmachine', git: 'git://github.com/seancribbs/webmachine-ruby.git'
155
+ ```
156
+
157
+ Then use `config.adapter = :Reel` when configuring a Webmachine app, e.g:
158
+
159
+ ```ruby
160
+ MyApp = Webmachine::Application.new do |app|
161
+ app.routes do
162
+ add ['*'], MyHome
163
+ end
164
+
165
+ app.configure do |config|
166
+ config.ip = MYAPP_IP
167
+ config.port = MYAPP_PORT
168
+ config.adapter = :Reel
169
+ end
170
+ end
171
+
172
+ MyApp.run
173
+ ```
87
174
 
88
- Reel is still in an extremely early stage of development and may be
89
- missing a lot of features. It seems to be doing a rudimentary job of
90
- speaking HTTP and has basic keep-alive support.
175
+ See the [Webmachine documentation](http://rubydoc.info/gems/webmachine/frames/file/README.md)
176
+ for further information
91
177
 
92
178
  Contributing
93
179
  ------------
data/bin/reel CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'reel'
4
+ require 'optparse'
4
5
 
5
6
  options = {}
6
7
 
@@ -55,4 +56,4 @@ Reel::Logger.info "Listening on #{handler[:host]}:#{handler[:port]}"
55
56
 
56
57
  handler.start
57
58
 
58
- sleep
59
+ sleep
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'reel'
4
+
5
+ app = Rack::Builder.new do
6
+ map '/' do
7
+ run lambda { |env|
8
+ body = Reel::ChunkStream.new do |body|
9
+ # sending a payload to make sure browsers will render chunks as received
10
+ body << "<html>#{' '*1024}\n"
11
+ ('A'..'Z').each do |l|
12
+ body << "<div>#{l}</div>\n"
13
+ sleep 0.5
14
+ end
15
+ body << "</html>\n"
16
+ body.finish
17
+ end
18
+ [200, {
19
+ 'Content-Type' => 'text/html'
20
+ }, body]
21
+ }
22
+ end
23
+ end.to_app
24
+
25
+ Rack::Handler::Reel.run app, Port: 9292
@@ -1,3 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # Run with: bundle exec examples/hello_world.rb
3
+
1
4
  require 'rubygems'
2
5
  require 'bundler/setup'
3
6
  require 'reel'
@@ -5,7 +8,7 @@ require 'reel'
5
8
  addr, port = '127.0.0.1', 1234
6
9
 
7
10
  puts "*** Starting server on #{addr}:#{port}"
8
- Reel::Server.new(addr, port) do |connection|
11
+ Reel::Server.run(addr, port) do |connection|
9
12
  # To use keep-alive with Reel, use a while loop that repeatedly calls
10
13
  # connection.request and consumes connection objects
11
14
  while request = connection.request
@@ -0,0 +1,157 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'reel'
4
+
5
+
6
+ class RoundtripServer
7
+ include Celluloid
8
+ include Celluloid::Notifications
9
+
10
+ def initialize
11
+ async.run
12
+ end
13
+
14
+ def run
15
+ now = Time.now.to_f
16
+ sleep now.ceil - now + 0.001
17
+ every(1) do
18
+ publish 'read_message'
19
+ end
20
+ end
21
+ end
22
+
23
+ class Writer
24
+ include Celluloid
25
+ include Celluloid::Notifications
26
+ include Celluloid::Logger
27
+
28
+ def initialize(websocket)
29
+ info "Writing to socket"
30
+ @socket = websocket
31
+ subscribe('write_message', :new_message)
32
+ end
33
+
34
+ def new_message(topic, new_time)
35
+ @socket << new_time.inspect
36
+ rescue Reel::SocketError
37
+ info "WS client disconnected"
38
+ terminate
39
+ end
40
+
41
+ end
42
+
43
+ class Reader
44
+ include Celluloid
45
+ include Celluloid::Notifications
46
+ include Celluloid::Logger
47
+
48
+ def initialize(websocket)
49
+ info "Reading socket"
50
+ @socket = websocket
51
+ subscribe('read_message', :new_message)
52
+ end
53
+
54
+ def new_message(topic)
55
+ msg = @socket.read
56
+ publish 'write_message', msg
57
+ rescue Reel::SocketError, EOFError
58
+ info "WS client disconnected"
59
+ terminate
60
+ end
61
+ end
62
+
63
+ class WebServer < Reel::Server
64
+ include Celluloid::Logger
65
+
66
+ def initialize(host = "0.0.0.0", port = 9000)
67
+ info "Roundtrip example starting on #{host}:#{port}"
68
+ super(host, port, &method(:on_connection))
69
+ end
70
+
71
+ def on_connection(connection)
72
+ while request = connection.request
73
+ case request
74
+ when Reel::Request
75
+ route_request connection, request
76
+ when Reel::WebSocket
77
+ info "Received a WebSocket connection"
78
+ route_websocket request
79
+ end
80
+ end
81
+ end
82
+
83
+ def route_request(connection, request)
84
+ if request.url == "/"
85
+ return render_index(connection)
86
+ end
87
+
88
+ info "404 Not Found: #{request.path}"
89
+ connection.respond :not_found, "Not found"
90
+ end
91
+
92
+ def route_websocket(socket)
93
+ if socket.url == "/ws"
94
+ Writer.new(socket)
95
+ Reader.new(socket)
96
+ else
97
+ info "Received invalid WebSocket request for: #{socket.url}"
98
+ socket.close
99
+ end
100
+ end
101
+
102
+ def render_index(connection)
103
+ info "200 OK: /"
104
+ connection.respond :ok, <<-HTML
105
+ <!doctype html>
106
+ <html lang="en">
107
+ <head>
108
+ <meta charset="utf-8">
109
+ <title>Reel WebSockets roundtrip example</title>
110
+ <style>
111
+ body {
112
+ font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
113
+ font-weight: 300;
114
+ text-align: center;
115
+ }
116
+
117
+ #content {
118
+ width: 800px;
119
+ margin: 0 auto;
120
+ background: #EEEEEE;
121
+ padding: 1em;
122
+ }
123
+ </style>
124
+ </head>
125
+ <body>
126
+ <div id="content">
127
+ <h1>Roundtrip communication with websockets</h1>
128
+ <div>
129
+ <input id="text_input" type="text" name="q" value="" autocomplete="off"/>
130
+ Latest message is: <span id="current-time">...</span></div>
131
+ </div>
132
+ </body>
133
+ <script>
134
+ var SocketKlass = "MozWebSocket" in window ? MozWebSocket : WebSocket;
135
+ var ws = new SocketKlass('ws://' + window.location.host + '/ws');
136
+ ws.onmessage = function(msg){
137
+ document.getElementById('current-time').innerHTML = msg.data;
138
+ };
139
+ var input = document.getElementById("text_input");
140
+ input.focus();
141
+ input.onkeydown = function(evt) {
142
+ var evt = evt || window.event;
143
+ if (evt.keyCode === 13) {
144
+ ws.send(input.value);console.log(input.value);
145
+ input.value = "";
146
+ }
147
+ };
148
+ </script>
149
+ </html>
150
+ HTML
151
+ end
152
+ end
153
+
154
+ RoundtripServer.supervise_as :roundtrip_server
155
+ WebServer.supervise_as :reel
156
+
157
+ sleep
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'reel'
4
+
5
+ Connections = []
6
+ Body = DATA.read
7
+ app = Rack::Builder.new do
8
+ map '/' do
9
+ run lambda { |env|
10
+ [200, {'Content-Type' => 'text/html'}, [Body]]
11
+ }
12
+ end
13
+
14
+ map '/subscribe' do
15
+ run lambda { |env|
16
+ body = Reel::EventStream.new do |socket|
17
+ Connections << socket
18
+ socket.on_error { Connections.delete socket }
19
+ end
20
+ [200, {'Content-Type' => 'text/event-stream'}, body]
21
+ }
22
+ end
23
+
24
+ map '/wall' do
25
+ run lambda { |env|
26
+ msg = env['PATH_INFO'].gsub(/\/+/, '').strip
27
+ msg = Time.now if msg.empty?
28
+ Connections.each { |s| s.data msg }
29
+ [200, {'Content-Type' => 'text/html'}, ["Sent \"#{msg}\" to #{Connections.size} clients"]]
30
+ }
31
+ end
32
+ end.to_app
33
+
34
+ Rack::Handler::Reel.run app, Port: 9292
35
+
36
+ __END__
37
+ <!doctype html>
38
+ <html lang="en">
39
+ <body>
40
+ <div id="content">Waiting for messages...</div>
41
+ </body>
42
+ <script type="text/javascript">
43
+ var evs = new EventSource('/subscribe');
44
+ evs.onmessage = function(e){
45
+ document.getElementById('content').innerHTML = e.data;
46
+ }
47
+ </script>
48
+ </html>