reel 0.0.0 → 0.0.1
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/.rspec +4 -0
- data/.travis.yml +10 -0
- data/Gemfile +5 -2
- data/{LICENSE → LICENSE.txt} +0 -0
- data/README.md +78 -17
- data/Rakefile +4 -0
- data/benchmarks/hello_goliath.rb +11 -0
- data/benchmarks/hello_node.js +8 -0
- data/benchmarks/hello_rack.ru +5 -0
- data/benchmarks/hello_reel.rb +12 -0
- data/bin/reel +14 -0
- data/examples/hello_world.rb +12 -0
- data/lib/reel.rb +15 -2
- data/lib/reel/connection.rb +103 -0
- data/lib/reel/logger.rb +21 -0
- data/lib/reel/request.rb +33 -0
- data/lib/reel/request/parser.rb +63 -0
- data/lib/reel/response.rb +68 -0
- data/lib/reel/server.rb +31 -0
- data/lib/reel/version.rb +1 -1
- data/logo.png +0 -0
- data/reel.gemspec +5 -4
- data/spec/reel/connection_spec.rb +52 -0
- data/spec/reel/server_spec.rb +49 -0
- data/spec/spec_helper.rb +46 -0
- data/tasks/rspec.rake +7 -0
- metadata +52 -18
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
-
source
|
1
|
+
source :rubygems
|
2
|
+
|
3
|
+
gem 'celluloid', :git => 'https://github.com/celluloid/celluloid.git'
|
4
|
+
gem 'celluloid-io', :git => 'https://github.com/celluloid/celluloid-io.git'
|
5
|
+
gem 'jruby-openssl' if defined? JRUBY_VERSION
|
2
6
|
|
3
7
|
# Specify your gem's dependencies in reel.gemspec
|
4
8
|
gemspec
|
5
9
|
|
6
|
-
gem 'celluloid-io', :git => 'https://github.com/tarcieri/celluloid-io.git'
|
data/{LICENSE → LICENSE.txt}
RENAMED
File without changes
|
data/README.md
CHANGED
@@ -1,29 +1,90 @@
|
|
1
|
-
|
1
|
+
![Reel](https://github.com/celluloid/reel/raw/master/logo.png)
|
2
|
+
=======
|
3
|
+
[![Build Status](https://secure.travis-ci.org/celluloid/reel.png?branch=master)](http://travis-ci.org/celluloid/reel)
|
2
4
|
|
3
|
-
|
5
|
+
Reel is a fast, non-blocking "evented" web server built on [http_parser.rb][parser],
|
6
|
+
[Celluloid::IO][celluloidio], and [nio4r][nio4r]. It's probably most similar to
|
7
|
+
[Goliath][goliath], but thanks to Celluloid also works great for multithreaded
|
8
|
+
applications and provides traditional multithreaded blocking I/O support too.
|
4
9
|
|
5
|
-
|
10
|
+
[parser]: https://github.com/tmm1/http_parser.rb
|
11
|
+
[celluloidio]: https://github.com/celluloid/celluloid-io
|
12
|
+
[nio4r]: https://github.com/tarcieri/nio4r
|
13
|
+
[Goliath]: http://postrank-labs.github.com/goliath/
|
6
14
|
|
7
|
-
|
15
|
+
Connections to Reel can be either non-blocking and handled entirely within
|
16
|
+
the Reel::Server thread, or the same connections can be dispatched to worker
|
17
|
+
threads where they will perform ordinary blocking IO. Reel provides no
|
18
|
+
built-in thread pool, however you can build one yourself using Celluloid::Pool,
|
19
|
+
or because Celluloid already pools threads to begin with, you can simply use
|
20
|
+
an actor per connection.
|
8
21
|
|
9
|
-
|
22
|
+
This gives you the best of both worlds: non-blocking I/O for when you're
|
23
|
+
primarily I/O bound, and threads for where you're compute bound.
|
10
24
|
|
11
|
-
|
25
|
+
### Is It Good?
|
12
26
|
|
13
|
-
|
27
|
+
Yes, but it has room for improvement. A "hello world" web server benchmark,
|
28
|
+
run on a 2GHz i7 (OS X 10.7.3). All servers used in a single-threaded mode.
|
14
29
|
|
15
|
-
|
30
|
+
Reel performance on various Ruby VMs:
|
16
31
|
|
17
|
-
|
32
|
+
```
|
33
|
+
# httperf --num-conns=50 --num-calls=1000
|
18
34
|
|
19
|
-
|
35
|
+
Ruby Version Throughput Latency
|
36
|
+
------------ ---------- -------
|
37
|
+
JRuby HEAD 5650 reqs/s (0.2 ms/req)
|
38
|
+
Ruby 1.9.3 5263 reqs/s (0.2 ms/req)
|
39
|
+
JRuby 1.6.7 4303 reqs/s (0.2 ms/req)
|
40
|
+
rbx HEAD 2288 reqs/s (0.4 ms/req)
|
41
|
+
```
|
20
42
|
|
21
|
-
|
43
|
+
Comparison with other web servers:
|
22
44
|
|
23
|
-
|
45
|
+
```
|
46
|
+
Web Server Throughput Latency
|
47
|
+
---------- ---------- -------
|
48
|
+
Goliath (0.9.4) 2058 reqs/s (0.5 ms/req)
|
49
|
+
Thin (1.2.11) 7502 reqs/s (0.1 ms/req)
|
50
|
+
Node.js (0.6.5) 11735 reqs/s (0.1 ms/req)
|
51
|
+
```
|
24
52
|
|
25
|
-
1.
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
53
|
+
All Ruby benchmarks done on Ruby 1.9.3. Latencies given are average-per-request
|
54
|
+
and are not amortized across all concurrent requests.
|
55
|
+
|
56
|
+
Usage
|
57
|
+
-----
|
58
|
+
|
59
|
+
Reel provides an extremely simple API:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
require 'reel'
|
63
|
+
|
64
|
+
Reel::Server.supervise("0.0.0.0", 3000) do |connection|
|
65
|
+
request = connection.request
|
66
|
+
puts "Client requested: #{request.method} #{request.url}"
|
67
|
+
connection.respond :ok, "hello, world"
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
Status
|
72
|
+
------
|
73
|
+
|
74
|
+
Reel is still in an extremely early stage of development and may be
|
75
|
+
missing a lot of features. It seems to be doing a rudimentary job of
|
76
|
+
speaking HTTP and has basic keep-alive support.
|
77
|
+
|
78
|
+
Contributing
|
79
|
+
------------
|
80
|
+
|
81
|
+
* Fork this repository on github
|
82
|
+
* Make your changes and send me a pull request
|
83
|
+
* If I like them I'll merge them
|
84
|
+
* If I've accepted a patch, feel free to ask for commit access
|
85
|
+
|
86
|
+
License
|
87
|
+
-------
|
88
|
+
|
89
|
+
Copyright (c) 2012 Tony Arcieri. Distributed under the MIT License. See
|
90
|
+
LICENSE.txt for further details.
|
data/Rakefile
CHANGED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Run with: ruby hello_goliath.rb -sv -e production
|
2
|
+
require 'goliath'
|
3
|
+
|
4
|
+
class Hello < Goliath::API
|
5
|
+
# default to JSON output, allow Yaml as secondary
|
6
|
+
use Goliath::Rack::Render, ['json', 'yaml']
|
7
|
+
|
8
|
+
def response(env)
|
9
|
+
[200, {}, "Hello World"]
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
/* Run with: node hello_node.js */
|
2
|
+
|
3
|
+
var http = require('http');
|
4
|
+
http.createServer(function (req, res) {
|
5
|
+
res.writeHead(200, {'Content-Type': 'text/plain'});
|
6
|
+
res.end('Hello World\n');
|
7
|
+
}).listen(1337, "127.0.0.1");
|
8
|
+
console.log('Server running at http://127.0.0.1:1337/');
|
data/bin/reel
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'reel'
|
4
|
+
|
5
|
+
host, port = "0.0.0.0", 3000
|
6
|
+
|
7
|
+
Reel::Logger.info "A Reel good HTTP server!"
|
8
|
+
Reel::Logger.info "Listening on #{host}:#{port}"
|
9
|
+
|
10
|
+
Reel::Server.supervise("0.0.0.0", 3000) do |connection|
|
11
|
+
p connection
|
12
|
+
end
|
13
|
+
|
14
|
+
sleep
|
data/lib/reel.rb
CHANGED
@@ -1,5 +1,18 @@
|
|
1
|
-
require
|
1
|
+
require 'http/parser'
|
2
|
+
require 'http'
|
3
|
+
require 'celluloid/io'
|
2
4
|
|
5
|
+
require 'reel/version'
|
6
|
+
|
7
|
+
require 'reel/connection'
|
8
|
+
require 'reel/logger'
|
9
|
+
require 'reel/request'
|
10
|
+
require 'reel/request/parser'
|
11
|
+
require 'reel/response'
|
12
|
+
require 'reel/server'
|
13
|
+
|
14
|
+
# A Reel good HTTP server
|
3
15
|
module Reel
|
4
|
-
#
|
16
|
+
# The method given was not understood
|
17
|
+
class UnsupportedMethodError < ArgumentError; end
|
5
18
|
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Reel
|
2
|
+
# A connection to the HTTP server
|
3
|
+
class Connection
|
4
|
+
class StateError < RuntimeError; end # wrong state for a given request
|
5
|
+
|
6
|
+
attr_reader :request
|
7
|
+
|
8
|
+
# Attempt to read this much data
|
9
|
+
BUFFER_SIZE = 4096
|
10
|
+
|
11
|
+
def initialize(socket)
|
12
|
+
@socket = socket
|
13
|
+
@keepalive = true
|
14
|
+
reset
|
15
|
+
|
16
|
+
@response_state = :header
|
17
|
+
@body_remaining = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
# Is the connection still active?
|
21
|
+
def alive?; @keepalive; end
|
22
|
+
|
23
|
+
def read_request
|
24
|
+
raise StateError, "can't read header" unless @request_state == :header
|
25
|
+
|
26
|
+
begin
|
27
|
+
until @parser.headers
|
28
|
+
@parser << @socket.readpartial(BUFFER_SIZE)
|
29
|
+
end
|
30
|
+
rescue IOError, Errno::ECONNRESET, Errno::EPIPE
|
31
|
+
@keepalive = false
|
32
|
+
@socket.close unless @socket.closed?
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
@request_state = :body
|
37
|
+
|
38
|
+
headers = {}
|
39
|
+
@parser.headers.each do |header, value|
|
40
|
+
headers[Http.canonicalize_header(header)] = value
|
41
|
+
end
|
42
|
+
|
43
|
+
if headers['Connection']
|
44
|
+
@keepalive = false if headers['Connection'] == 'close'
|
45
|
+
elsif @parser.http_version == "1.0"
|
46
|
+
@keepalive = false
|
47
|
+
end
|
48
|
+
|
49
|
+
@body_remaining = Integer(headers['Content-Length']) if headers['Content-Length']
|
50
|
+
@request = Request.new(@parser.http_method, @parser.url, @parser.http_version, headers, self)
|
51
|
+
end
|
52
|
+
|
53
|
+
def readpartial(size = BUFFER_SIZE)
|
54
|
+
if @body_remaining and @body_remaining > 0
|
55
|
+
chunk = @parser.chunk
|
56
|
+
unless chunk
|
57
|
+
@parser << @socket.readpartial(size)
|
58
|
+
chunk = @parser.chunk
|
59
|
+
return unless chunk
|
60
|
+
end
|
61
|
+
|
62
|
+
@body_remaining -= chunk.length
|
63
|
+
@body_remaining = nil if @body_remaining < 1
|
64
|
+
|
65
|
+
chunk
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def respond(response, body = nil)
|
70
|
+
if @keepalive
|
71
|
+
headers = {'Connection' => 'Keep-Alive'}
|
72
|
+
else
|
73
|
+
headers = {'Connection' => 'close'}
|
74
|
+
end
|
75
|
+
|
76
|
+
case response
|
77
|
+
when Symbol
|
78
|
+
response = Response.new(response, headers, body)
|
79
|
+
when Response
|
80
|
+
else raise TypeError, "invalid response: #{response.inspect}"
|
81
|
+
end
|
82
|
+
|
83
|
+
response.render(@socket)
|
84
|
+
rescue IOError, Errno::ECONNRESET, Errno::EPIPE
|
85
|
+
# The client disconnected early
|
86
|
+
@keepalive = false
|
87
|
+
ensure
|
88
|
+
if @keepalive
|
89
|
+
reset
|
90
|
+
@request_state = :header
|
91
|
+
else
|
92
|
+
@socket.close unless @socket.closed?
|
93
|
+
@request_state = :closed
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def reset
|
98
|
+
@request_state = :header
|
99
|
+
@parser = Request::Parser.new
|
100
|
+
@request = nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/reel/logger.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Reel
|
4
|
+
module Logger
|
5
|
+
@logger = ::Logger.new STDERR
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def logger=(logger)
|
9
|
+
@logger = logger
|
10
|
+
end
|
11
|
+
|
12
|
+
def logger
|
13
|
+
@logger
|
14
|
+
end
|
15
|
+
|
16
|
+
def debug(msg); @logger.debug(msg); end
|
17
|
+
def info(msg); @logger.info(msg); end
|
18
|
+
def warn(msg); @logger.warn(msg); end
|
19
|
+
def error(msg); @logger.error(msg); end
|
20
|
+
end
|
21
|
+
end
|
data/lib/reel/request.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module Reel
|
2
|
+
class Request
|
3
|
+
attr_accessor :method, :version, :url
|
4
|
+
METHODS = [:get, :head, :post, :put, :delete, :trace, :options, :connect, :patch]
|
5
|
+
|
6
|
+
def initialize(method, url, version = "1.1", headers = {}, connection = nil)
|
7
|
+
@method = method.to_s.downcase.to_sym
|
8
|
+
raise UnsupportedArgumentError, "unknown method: #{method}" unless METHODS.include? @method
|
9
|
+
|
10
|
+
@url, @version, @headers, @connection = url, version, headers, connection
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](header)
|
14
|
+
@headers[header]
|
15
|
+
end
|
16
|
+
|
17
|
+
def body
|
18
|
+
@body ||= begin
|
19
|
+
raise "no connection given" unless @connection
|
20
|
+
|
21
|
+
body = "" unless block_given?
|
22
|
+
while (chunk = @connection.readpartial)
|
23
|
+
if block_given?
|
24
|
+
yield chunk
|
25
|
+
else
|
26
|
+
body << chunk
|
27
|
+
end
|
28
|
+
end
|
29
|
+
body unless block_given?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Reel
|
2
|
+
# Parses incoming HTTP requests
|
3
|
+
class Request
|
4
|
+
class Parser
|
5
|
+
attr_reader :headers
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@parser = Http::Parser.new(self)
|
9
|
+
@headers = nil
|
10
|
+
@finished = false
|
11
|
+
@chunk = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def add(data)
|
15
|
+
@parser << data
|
16
|
+
end
|
17
|
+
alias_method :<<, :add
|
18
|
+
|
19
|
+
def headers?
|
20
|
+
!!@headers
|
21
|
+
end
|
22
|
+
|
23
|
+
def http_method
|
24
|
+
@parser.http_method.downcase.to_sym
|
25
|
+
end
|
26
|
+
|
27
|
+
def http_version
|
28
|
+
@parser.http_version.join(".")
|
29
|
+
end
|
30
|
+
|
31
|
+
def url
|
32
|
+
@parser.request_url
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Http::Parser callbacks
|
37
|
+
#
|
38
|
+
|
39
|
+
def on_headers_complete(headers)
|
40
|
+
@headers = headers
|
41
|
+
end
|
42
|
+
|
43
|
+
def on_body(chunk)
|
44
|
+
if @chunk
|
45
|
+
@chunk << chunk
|
46
|
+
else
|
47
|
+
@chunk = chunk
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def chunk
|
52
|
+
if (chunk = @chunk)
|
53
|
+
@chunk = nil
|
54
|
+
chunk
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def on_message_complete
|
59
|
+
@finished = true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Reel
|
2
|
+
class Response
|
3
|
+
# Use status code tables from the Http gem
|
4
|
+
STATUS_CODES = Http::Response::STATUS_CODES
|
5
|
+
SYMBOL_TO_STATUS_CODE = Http::Response::SYMBOL_TO_STATUS_CODE
|
6
|
+
CRLF = "\r\n"
|
7
|
+
|
8
|
+
attr_reader :status # Status has a special setter to coerce symbol names
|
9
|
+
attr_accessor :reason
|
10
|
+
|
11
|
+
def initialize(status, body_or_headers = nil, body = nil)
|
12
|
+
self.status = status
|
13
|
+
|
14
|
+
if body_or_headers and not body
|
15
|
+
@body = body_or_headers
|
16
|
+
@headers = {}
|
17
|
+
else
|
18
|
+
@body = body
|
19
|
+
@headers = body_or_headers
|
20
|
+
end
|
21
|
+
|
22
|
+
if @body
|
23
|
+
@headers['Content-Length'] ||= @body.length
|
24
|
+
end
|
25
|
+
|
26
|
+
# FIXME: real HTTP versioning
|
27
|
+
@version = "HTTP/1.1"
|
28
|
+
end
|
29
|
+
|
30
|
+
# Set the status
|
31
|
+
def status=(status, reason=nil)
|
32
|
+
case status
|
33
|
+
when Integer
|
34
|
+
@status = status
|
35
|
+
@reason ||= STATUS_CODES[status]
|
36
|
+
when Symbol
|
37
|
+
if code = SYMBOL_TO_STATUS_CODE[status]
|
38
|
+
self.status = code
|
39
|
+
else
|
40
|
+
raise ArgumentError, "unrecognized status symbol: #{status}"
|
41
|
+
end
|
42
|
+
else
|
43
|
+
raise TypeError, "invalid status type: #{status.inspect}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Write the response out to the wire
|
48
|
+
def render(socket)
|
49
|
+
socket << render_header
|
50
|
+
socket << @body
|
51
|
+
end
|
52
|
+
|
53
|
+
# Convert headers into a string
|
54
|
+
# FIXME: this should probably be factored elsewhere, SRP and all
|
55
|
+
def render_header
|
56
|
+
response_header = "#{@version} #{@status} #{@reason}#{CRLF}"
|
57
|
+
|
58
|
+
unless @headers.empty?
|
59
|
+
response_header << @headers.map do |header, value|
|
60
|
+
"#{header}: #{value}"
|
61
|
+
end.join(CRLF) << CRLF
|
62
|
+
end
|
63
|
+
|
64
|
+
response_header << CRLF
|
65
|
+
end
|
66
|
+
private :render_header
|
67
|
+
end
|
68
|
+
end
|
data/lib/reel/server.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Reel
|
2
|
+
class Server
|
3
|
+
include Celluloid::IO
|
4
|
+
|
5
|
+
def initialize(host, port, &callback)
|
6
|
+
# This is actually an evented Celluloid::IO::TCPServer
|
7
|
+
@server = TCPServer.new(host, port)
|
8
|
+
@callback = callback
|
9
|
+
run!
|
10
|
+
end
|
11
|
+
|
12
|
+
def finalize
|
13
|
+
@server.close
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
loop { handle_connection! @server.accept }
|
18
|
+
end
|
19
|
+
|
20
|
+
def handle_connection(socket)
|
21
|
+
connection = Connection.new(socket)
|
22
|
+
begin
|
23
|
+
connection.read_request
|
24
|
+
@callback[connection]
|
25
|
+
end while connection.alive?
|
26
|
+
rescue EOFError
|
27
|
+
# Client disconnected prematurely
|
28
|
+
# FIXME: should probably do something here
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/reel/version.rb
CHANGED
data/logo.png
ADDED
Binary file
|
data/reel.gemspec
CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |gem|
|
|
6
6
|
gem.email = ["tony.arcieri@gmail.com"]
|
7
7
|
gem.description = "A Celluloid::IO-powered HTTP server"
|
8
8
|
gem.summary = "A reel good HTTP server"
|
9
|
-
gem.homepage = "https://github.com/
|
9
|
+
gem.homepage = "https://github.com/celluloid/reel"
|
10
10
|
|
11
11
|
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
12
|
gem.files = `git ls-files`.split("\n")
|
@@ -15,9 +15,10 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = Reel::VERSION
|
17
17
|
|
18
|
-
gem.add_dependency 'celluloid-io',
|
19
|
-
gem.add_dependency '
|
18
|
+
gem.add_dependency 'celluloid-io', '>= 0.8.0'
|
19
|
+
gem.add_dependency 'http', '>= 0.2.0'
|
20
|
+
gem.add_dependency 'http_parser.rb', '>= 0.5.3'
|
20
21
|
|
21
22
|
gem.add_development_dependency 'rake'
|
22
|
-
gem.add_development_dependency 'rspec'
|
23
|
+
gem.add_development_dependency 'rspec'
|
23
24
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Reel::Connection do
|
4
|
+
it "reads requests without bodies" do
|
5
|
+
with_socket_pair do |client, connection|
|
6
|
+
client << ExampleRequest.new.to_s
|
7
|
+
request = connection.read_request
|
8
|
+
|
9
|
+
request.url.should eq "/"
|
10
|
+
request.version.should eq "1.1"
|
11
|
+
|
12
|
+
request['Host'].should eq "www.example.com"
|
13
|
+
request['Connection'].should eq "keep-alive"
|
14
|
+
request['User-Agent'].should eq "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.78 S"
|
15
|
+
request['Accept'].should eq "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
|
16
|
+
request['Accept-Encoding'].should eq "gzip,deflate,sdch"
|
17
|
+
request['Accept-Language'].should eq "en-US,en;q=0.8"
|
18
|
+
request['Accept-Charset'].should eq "ISO-8859-1,utf-8;q=0.7,*;q=0.3"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it "reads requests with bodies" do
|
23
|
+
with_socket_pair do |client, connection|
|
24
|
+
body = "Hello, world!"
|
25
|
+
example_request = ExampleRequest.new
|
26
|
+
example_request.body = body
|
27
|
+
|
28
|
+
client << example_request.to_s
|
29
|
+
request = connection.read_request
|
30
|
+
|
31
|
+
request.url.should eq "/"
|
32
|
+
request.version.should eq "1.1"
|
33
|
+
request['Content-Length'].should == body.length.to_s
|
34
|
+
request.body.should == example_request.body
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def with_socket_pair
|
39
|
+
host = '127.0.0.1'
|
40
|
+
port = 10103
|
41
|
+
|
42
|
+
server = TCPServer.new(host, port)
|
43
|
+
client = TCPSocket.new(host, port)
|
44
|
+
peer = server.accept
|
45
|
+
|
46
|
+
yield client, Reel::Connection.new(peer)
|
47
|
+
|
48
|
+
server.close
|
49
|
+
client.close
|
50
|
+
peer.close
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
describe Reel::Server do
|
5
|
+
let(:endpoint) { URI("http://#{example_addr}:#{example_port}#{example_url}") }
|
6
|
+
let(:response_body) { "ohai thar" }
|
7
|
+
|
8
|
+
it "receives HTTP requests and sends responses" do
|
9
|
+
handler_called = false
|
10
|
+
handler = proc do |connection|
|
11
|
+
handler_called = true
|
12
|
+
request = connection.request
|
13
|
+
request.method.should eq :get
|
14
|
+
request.version.should eq "1.1"
|
15
|
+
request.url.should eq example_url
|
16
|
+
|
17
|
+
connection.respond :ok, response_body
|
18
|
+
end
|
19
|
+
|
20
|
+
with_reel(handler) do
|
21
|
+
response = Net::HTTP.get endpoint
|
22
|
+
response.should eq response_body
|
23
|
+
end
|
24
|
+
|
25
|
+
handler_called.should be_true
|
26
|
+
end
|
27
|
+
|
28
|
+
it "echoes request bodies as response bodies" do
|
29
|
+
handler_called = false
|
30
|
+
handler = proc do |connection|
|
31
|
+
handler_called = true
|
32
|
+
request = connection.request
|
33
|
+
request.method.should eq :post
|
34
|
+
connection.respond :ok, request.body
|
35
|
+
end
|
36
|
+
|
37
|
+
with_reel(handler) do
|
38
|
+
http = Net::HTTP.new(endpoint.host, endpoint.port)
|
39
|
+
request = Net::HTTP::Post.new(endpoint.request_uri)
|
40
|
+
request['connection'] = 'close'
|
41
|
+
request.body = response_body
|
42
|
+
response = http.request(request)
|
43
|
+
response.should be_a Net::HTTPOK
|
44
|
+
response.body.should == response_body
|
45
|
+
end
|
46
|
+
|
47
|
+
handler_called.should be_true
|
48
|
+
end
|
49
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'reel'
|
3
|
+
|
4
|
+
def example_addr; '127.0.0.1'; end
|
5
|
+
def example_port; 1234; end
|
6
|
+
def example_url; "/example"; end
|
7
|
+
|
8
|
+
def with_reel(handler)
|
9
|
+
server = Reel::Server.new(example_addr, example_port, &handler)
|
10
|
+
yield server
|
11
|
+
ensure
|
12
|
+
server.terminate
|
13
|
+
end
|
14
|
+
|
15
|
+
class ExampleRequest
|
16
|
+
extend Forwardable
|
17
|
+
def_delegators :@headers, :[], :[]=
|
18
|
+
attr_accessor :path, :version, :body
|
19
|
+
|
20
|
+
def initialize(verb = :get, path = "/", version = "1.1", headers = {}, body = nil)
|
21
|
+
@verb = verb.to_s.upcase
|
22
|
+
@path = path
|
23
|
+
@version = "1.1"
|
24
|
+
@headers = {
|
25
|
+
'Host' => 'www.example.com',
|
26
|
+
'Connection' => 'keep-alive',
|
27
|
+
'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.78 S',
|
28
|
+
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
29
|
+
'Accept-Encoding' => 'gzip,deflate,sdch',
|
30
|
+
'Accept-Language' => 'en-US,en;q=0.8',
|
31
|
+
'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.3'
|
32
|
+
}.merge(headers)
|
33
|
+
|
34
|
+
@body = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
if @body && !@headers['Content-Length']
|
39
|
+
@headers['Content-Length'] = @body.length
|
40
|
+
end
|
41
|
+
|
42
|
+
"#{@verb} #{@path} HTTP/#{@version}\r\n" <<
|
43
|
+
@headers.map { |k, v| "#{k}: #{v}" }.join("\r\n") << "\r\n\r\n" <<
|
44
|
+
(@body ? @body : '')
|
45
|
+
end
|
46
|
+
end
|
data/tasks/rspec.rake
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-03-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: celluloid-io
|
16
|
-
requirement: &
|
16
|
+
requirement: &70219946901960 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,21 +21,32 @@ dependencies:
|
|
21
21
|
version: 0.8.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70219946901960
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
|
-
name:
|
27
|
-
requirement: &
|
26
|
+
name: http
|
27
|
+
requirement: &70219946917780 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version:
|
32
|
+
version: 0.2.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70219946917780
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: http_parser.rb
|
38
|
+
requirement: &70219946917260 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.5.3
|
33
44
|
type: :runtime
|
34
45
|
prerelease: false
|
35
|
-
version_requirements: *
|
46
|
+
version_requirements: *70219946917260
|
36
47
|
- !ruby/object:Gem::Dependency
|
37
48
|
name: rake
|
38
|
-
requirement: &
|
49
|
+
requirement: &70219946916780 !ruby/object:Gem::Requirement
|
39
50
|
none: false
|
40
51
|
requirements:
|
41
52
|
- - ! '>='
|
@@ -43,34 +54,54 @@ dependencies:
|
|
43
54
|
version: '0'
|
44
55
|
type: :development
|
45
56
|
prerelease: false
|
46
|
-
version_requirements: *
|
57
|
+
version_requirements: *70219946916780
|
47
58
|
- !ruby/object:Gem::Dependency
|
48
59
|
name: rspec
|
49
|
-
requirement: &
|
60
|
+
requirement: &70219946916200 !ruby/object:Gem::Requirement
|
50
61
|
none: false
|
51
62
|
requirements:
|
52
|
-
- -
|
63
|
+
- - ! '>='
|
53
64
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
65
|
+
version: '0'
|
55
66
|
type: :development
|
56
67
|
prerelease: false
|
57
|
-
version_requirements: *
|
68
|
+
version_requirements: *70219946916200
|
58
69
|
description: A Celluloid::IO-powered HTTP server
|
59
70
|
email:
|
60
71
|
- tony.arcieri@gmail.com
|
61
|
-
executables:
|
72
|
+
executables:
|
73
|
+
- reel
|
62
74
|
extensions: []
|
63
75
|
extra_rdoc_files: []
|
64
76
|
files:
|
65
77
|
- .gitignore
|
78
|
+
- .rspec
|
79
|
+
- .travis.yml
|
66
80
|
- Gemfile
|
67
|
-
- LICENSE
|
81
|
+
- LICENSE.txt
|
68
82
|
- README.md
|
69
83
|
- Rakefile
|
84
|
+
- benchmarks/hello_goliath.rb
|
85
|
+
- benchmarks/hello_node.js
|
86
|
+
- benchmarks/hello_rack.ru
|
87
|
+
- benchmarks/hello_reel.rb
|
88
|
+
- bin/reel
|
89
|
+
- examples/hello_world.rb
|
70
90
|
- lib/reel.rb
|
91
|
+
- lib/reel/connection.rb
|
92
|
+
- lib/reel/logger.rb
|
93
|
+
- lib/reel/request.rb
|
94
|
+
- lib/reel/request/parser.rb
|
95
|
+
- lib/reel/response.rb
|
96
|
+
- lib/reel/server.rb
|
71
97
|
- lib/reel/version.rb
|
98
|
+
- logo.png
|
72
99
|
- reel.gemspec
|
73
|
-
|
100
|
+
- spec/reel/connection_spec.rb
|
101
|
+
- spec/reel/server_spec.rb
|
102
|
+
- spec/spec_helper.rb
|
103
|
+
- tasks/rspec.rake
|
104
|
+
homepage: https://github.com/celluloid/reel
|
74
105
|
licenses: []
|
75
106
|
post_install_message:
|
76
107
|
rdoc_options: []
|
@@ -94,5 +125,8 @@ rubygems_version: 1.8.10
|
|
94
125
|
signing_key:
|
95
126
|
specification_version: 3
|
96
127
|
summary: A reel good HTTP server
|
97
|
-
test_files:
|
128
|
+
test_files:
|
129
|
+
- spec/reel/connection_spec.rb
|
130
|
+
- spec/reel/server_spec.rb
|
131
|
+
- spec/spec_helper.rb
|
98
132
|
has_rdoc:
|