webmachine 0.2.0 → 0.3.0
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/Gemfile +11 -7
- data/Guardfile +1 -1
- data/README.md +88 -36
- data/Rakefile +10 -0
- data/lib/webmachine/adapters/mongrel.rb +9 -13
- data/lib/webmachine/adapters/rack.rb +50 -0
- data/lib/webmachine/adapters/webrick.rb +2 -0
- data/lib/webmachine/chunked_body.rb +43 -0
- data/lib/webmachine/decision/conneg.rb +2 -1
- data/lib/webmachine/decision/flow.rb +4 -2
- data/lib/webmachine/decision/helpers.rb +4 -0
- data/lib/webmachine/dispatcher.rb +15 -2
- data/lib/webmachine/dispatcher/route.rb +4 -0
- data/lib/webmachine/fiber18.rb +88 -0
- data/lib/webmachine/headers.rb +16 -1
- data/lib/webmachine/media_type.rb +3 -0
- data/lib/webmachine/request.rb +17 -7
- data/lib/webmachine/resource.rb +2 -1
- data/lib/webmachine/resource/authentication.rb +35 -0
- data/lib/webmachine/resource/callbacks.rb +15 -10
- data/lib/webmachine/response.rb +5 -4
- data/lib/webmachine/streaming.rb +44 -8
- data/lib/webmachine/translation.rb +7 -0
- data/lib/webmachine/version.rb +5 -1
- data/spec/tests.org +24 -1
- data/spec/webmachine/adapters/rack_spec.rb +64 -0
- data/spec/webmachine/chunked_body_spec.rb +30 -0
- data/spec/webmachine/decision/helpers_spec.rb +11 -0
- data/spec/webmachine/dispatcher_spec.rb +13 -0
- data/spec/webmachine/request_spec.rb +5 -0
- data/spec/webmachine/resource/authentication_spec.rb +68 -0
- data/webmachine.gemspec +6 -3
- metadata +251 -58
data/Gemfile
CHANGED
@@ -9,13 +9,17 @@ gem 'bundler'
|
|
9
9
|
unless ENV['TRAVIS']
|
10
10
|
gem 'guard-rspec'
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
platform :mri do
|
13
|
+
gem 'redcarpet'
|
14
|
+
|
15
|
+
case RbConfig::CONFIG['host_os']
|
16
|
+
when /darwin/
|
17
|
+
gem 'rb-fsevent'
|
18
|
+
gem 'growl_notify'
|
19
|
+
when /linux/
|
20
|
+
gem 'rb-inotify'
|
21
|
+
gem 'libnotify'
|
22
|
+
end
|
19
23
|
end
|
20
24
|
end
|
21
25
|
|
data/Guardfile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
gemset = ENV['RVM_GEMSET'] || 'webmachine'
|
2
2
|
gemset = "@#{gemset}" unless gemset.to_s == ''
|
3
3
|
|
4
|
-
rvms = %W[
|
4
|
+
rvms = %W[ 1.8.7 1.9.2 jruby rbx ].map {|v| "#{v}#{gemset}" }
|
5
5
|
|
6
6
|
guard 'rspec', :cli => "--color --profile", :growl => true, :rvm => rvms do
|
7
7
|
watch(%r{^lib/webmachine/locale/.+$}) { "spec" }
|
data/README.md
CHANGED
@@ -11,14 +11,14 @@ toolkit for building HTTP-friendly applications. For example, it does
|
|
11
11
|
not provide a templating engine or a persistence layer; those choices
|
12
12
|
are up to you.
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
Webmachine
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
14
|
+
## A Note about Rack
|
15
|
+
|
16
|
+
Webmachine has a Rack adapter -- thanks to Jamis Buck -- but when
|
17
|
+
using it, we recommend you ensure that NO middleware is used. The
|
18
|
+
behaviors that are encapsulated in Webmachine could be broken by
|
19
|
+
middlewares that sit above it, and there is no way to detect them at
|
20
|
+
runtime. _Caveat emptor_. That said, Webmachine should behave properly
|
21
|
+
when given a clear stack.
|
22
22
|
|
23
23
|
## Getting Started
|
24
24
|
|
@@ -26,25 +26,25 @@ Webmachine is very young, but it's still easy to construct an
|
|
26
26
|
application for it!
|
27
27
|
|
28
28
|
```ruby
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
29
|
+
require 'webmachine'
|
30
|
+
# Require any of the files that contain your resources here
|
31
|
+
require 'my_resource'
|
32
|
+
|
33
|
+
# Point all URIs at the MyResource class
|
34
|
+
Webmachine::Dispatcher.add_route(['*'], MyResource)
|
35
|
+
|
36
|
+
# Start the server, binds to port 8080 using WEBrick
|
37
|
+
Webmachine.run
|
38
38
|
```
|
39
39
|
|
40
40
|
Your resource will look something like this:
|
41
41
|
|
42
42
|
```ruby
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
43
|
+
class MyResource < Webmachine::Resource
|
44
|
+
def to_html
|
45
|
+
"<html><body>Hello, world!</body></html>"
|
46
|
+
end
|
47
|
+
end
|
48
48
|
```
|
49
49
|
|
50
50
|
Run the first file and your application is up. That's all there is to
|
@@ -54,19 +54,44 @@ might want to enable "gzip" compression on your resource, for which
|
|
54
54
|
you can simply add an `encodings_provided` callback method:
|
55
55
|
|
56
56
|
```ruby
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
57
|
+
class MyResource < Webmachine::Resource
|
58
|
+
def encodings_provided
|
59
|
+
{"gzip" => :encode_gzip, "identity" => :encode_identity}
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_html
|
63
|
+
"<html><body>Hello, world!</body></html>"
|
64
|
+
end
|
65
|
+
end
|
66
66
|
```
|
67
67
|
|
68
68
|
There are many other HTTP features exposed to your resource through
|
69
|
-
|
69
|
+
{Webmachine::Resource::Callbacks}. Give them a try!
|
70
|
+
|
71
|
+
### Configurator
|
72
|
+
|
73
|
+
There's a configurator that allows you to set the ip address and port
|
74
|
+
bindings as well as a different webserver adapter. You can also add
|
75
|
+
your routes in a block. Both of these call return the `Webmachine`
|
76
|
+
module, so you could chain them if you like.
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
require 'webmachine'
|
80
|
+
require 'my_resource'
|
81
|
+
|
82
|
+
Webmachine.routes do
|
83
|
+
add ['*'], MyResource
|
84
|
+
end
|
85
|
+
|
86
|
+
Webmachine.configure do |config|
|
87
|
+
config.ip = '127.0.0.1'
|
88
|
+
config.port = 3000
|
89
|
+
config.adapter = :Mongrel
|
90
|
+
end
|
91
|
+
|
92
|
+
# Start the server.
|
93
|
+
Webmachine.run
|
94
|
+
```
|
70
95
|
|
71
96
|
## Features
|
72
97
|
|
@@ -75,14 +100,14 @@ callbacks. Give them a try!
|
|
75
100
|
* Most callbacks can interrupt the decision flow by returning an
|
76
101
|
integer response code. You generally only want to do this when new
|
77
102
|
information comes to light, requiring a modification of the response.
|
78
|
-
* Supports WEBrick and Mongrel (1.2pre+). Other host
|
79
|
-
investigated.
|
80
|
-
* Streaming/chunked response bodies are permitted as Enumerables
|
103
|
+
* Supports WEBrick and Mongrel (1.2pre+), and a Rack shim. Other host
|
104
|
+
servers are being investigated.
|
105
|
+
* Streaming/chunked response bodies are permitted as Enumerables,
|
106
|
+
Procs, or Fibers!
|
81
107
|
* Unlike the Erlang original, it does real Language negotiation.
|
82
108
|
|
83
109
|
## Problems/TODOs
|
84
110
|
|
85
|
-
* Support streamed responses as Fibers.
|
86
111
|
* Command-line tools, and general polish.
|
87
112
|
* Tracing is exposed as an Array of decisions visited on the response
|
88
113
|
object. You should be able to turn this off and on, and visualize
|
@@ -90,6 +115,33 @@ callbacks. Give them a try!
|
|
90
115
|
|
91
116
|
## Changelog
|
92
117
|
|
118
|
+
### 0.3.0 November 9, 2011
|
119
|
+
|
120
|
+
0.3.0 introduces some new features, refactorings, and now has 100%
|
121
|
+
documentation coverage! Among the new features are minimal Rack
|
122
|
+
compatibility, streaming responses via Fibers and a friendlier route
|
123
|
+
definition syntax. Added Jamis Buck as a committer. Thank you for your
|
124
|
+
contributions!
|
125
|
+
|
126
|
+
* Chunked bodies are now wrapped in a way that works on webservers
|
127
|
+
that don't automatically produce them.
|
128
|
+
* HTTP Basic Authentication is easy to add to resources, just include
|
129
|
+
`Webmachine::Resource::Authentication`.
|
130
|
+
* Routes are a little less painful to add, you can now specify them
|
131
|
+
with `Webmachine.routes` which will be evaled into the `Dispatcher`.
|
132
|
+
* The new default port is 8080.
|
133
|
+
* Rack is minimally supported as a host server. _Don't put middleware
|
134
|
+
above Webmachine!_
|
135
|
+
* Fibers can be used as streamed response bodies.
|
136
|
+
* `Dispatcher#add_route` will now return the added `Route` instance.
|
137
|
+
* The header-conversion code for CGI-style servers has been extracted
|
138
|
+
into `Webmachine::Headers`.
|
139
|
+
* `Route#path_spec` is now public so that applications can inspect
|
140
|
+
existing routes, perhaps for URL generation.
|
141
|
+
* `Request#query` now uses `CGI.unescape` so '+' characters are
|
142
|
+
correctly parsed.
|
143
|
+
* YARD documentation has 100% coverage.
|
144
|
+
|
93
145
|
### 0.2.0 September 11, 2011
|
94
146
|
|
95
147
|
0.2.0 includes an adapter for Mongrel and a central place for
|
data/Rakefile
CHANGED
@@ -1,6 +1,16 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rubygems/package_task'
|
3
3
|
|
4
|
+
begin
|
5
|
+
require 'yard'
|
6
|
+
require 'yard/rake/yardoc_task'
|
7
|
+
YARD::Rake::YardocTask.new do |doc|
|
8
|
+
doc.files = Dir["lib/**/*.rb"] + ['README.md']
|
9
|
+
doc.options = ["-m", "markdown"]
|
10
|
+
end
|
11
|
+
rescue LoadError
|
12
|
+
end
|
13
|
+
|
4
14
|
def gemspec
|
5
15
|
$webmachine_gemspec ||= Gem::Specification.load("webmachine.gemspec")
|
6
16
|
end
|
@@ -4,6 +4,7 @@ require 'webmachine/headers'
|
|
4
4
|
require 'webmachine/request'
|
5
5
|
require 'webmachine/response'
|
6
6
|
require 'webmachine/dispatcher'
|
7
|
+
require 'webmachine/chunked_body'
|
7
8
|
|
8
9
|
module Webmachine
|
9
10
|
module Adapters
|
@@ -26,9 +27,11 @@ module Webmachine
|
|
26
27
|
config.join
|
27
28
|
end
|
28
29
|
|
30
|
+
# A Mongrel handler for Webmachine
|
29
31
|
class Handler < ::Mongrel::HttpHandler
|
32
|
+
# Processes an individual request from Mongrel through Webmachine.
|
30
33
|
def process(wreq, wres)
|
31
|
-
header =
|
34
|
+
header = Webmachine::Headers.from_cgi(wreq.params)
|
32
35
|
|
33
36
|
request = Webmachine::Request.new(wreq.params["REQUEST_METHOD"],
|
34
37
|
URI.parse(wreq.params["REQUEST_URI"]),
|
@@ -55,29 +58,22 @@ module Webmachine
|
|
55
58
|
wres.write response.body
|
56
59
|
wres.socket.flush
|
57
60
|
when Enumerable
|
58
|
-
response.body.each { |part|
|
61
|
+
Webmachine::ChunkedBody.new(response.body).each { |part|
|
59
62
|
wres.write part
|
60
63
|
wres.socket.flush
|
61
64
|
}
|
62
65
|
else
|
63
66
|
if response.body.respond_to?(:call)
|
64
|
-
|
65
|
-
|
67
|
+
Webmachine::ChunkedBody.new(Array(response.body.call)).each { |part|
|
68
|
+
wres.write part
|
69
|
+
wres.socket.flush
|
70
|
+
}
|
66
71
|
end
|
67
72
|
end
|
68
73
|
ensure
|
69
74
|
response.body.close if response.body.respond_to? :close
|
70
75
|
end
|
71
76
|
end
|
72
|
-
|
73
|
-
def http_headers(env, headers)
|
74
|
-
env.inject(headers) do |h,(k,v)|
|
75
|
-
if k =~ /^HTTP_(\w+)$/
|
76
|
-
h[$1.tr("_", "-")] = v
|
77
|
-
end
|
78
|
-
h
|
79
|
-
end
|
80
|
-
end
|
81
77
|
end
|
82
78
|
end
|
83
79
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rack/request'
|
2
|
+
require 'webmachine/version'
|
3
|
+
require 'webmachine/headers'
|
4
|
+
require 'webmachine/request'
|
5
|
+
require 'webmachine/response'
|
6
|
+
require 'webmachine/dispatcher'
|
7
|
+
|
8
|
+
module Webmachine
|
9
|
+
module Adapters
|
10
|
+
# A minimal "shim" adapter to allow Webmachine to interface with Rack. The
|
11
|
+
# intention here is to allow Webmachine to run under Rack-compatible
|
12
|
+
# web-servers, like unicorn and pow, and is not intended to allow Webmachine
|
13
|
+
# to be "plugged in" to an existing Rack app as middleware.
|
14
|
+
#
|
15
|
+
# To use this adapter, create a config.ru file and populate it like so:
|
16
|
+
#
|
17
|
+
# require 'webmachine/adapters/rack'
|
18
|
+
#
|
19
|
+
# # put your own Webmachine resources in another file:
|
20
|
+
# require 'my/resources'
|
21
|
+
#
|
22
|
+
# run Webmachine::Adapters::Rack.new
|
23
|
+
#
|
24
|
+
# Servers like pow and unicorn will read config.ru by default and it should
|
25
|
+
# all "just work".
|
26
|
+
class Rack
|
27
|
+
# Handles a Rack-based request.
|
28
|
+
# @param [Hash] env the Rack environment
|
29
|
+
def call(env)
|
30
|
+
headers = Webmachine::Headers.from_cgi(env)
|
31
|
+
|
32
|
+
rack_req = ::Rack::Request.new env
|
33
|
+
request = Webmachine::Request.new(rack_req.request_method,
|
34
|
+
URI.parse(rack_req.url),
|
35
|
+
headers,
|
36
|
+
rack_req.body)
|
37
|
+
|
38
|
+
response = Webmachine::Response.new
|
39
|
+
Webmachine::Dispatcher.dispatch request, response
|
40
|
+
|
41
|
+
response.headers['Server'] = [Webmachine::SERVER_STRING, "Rack/#{::Rack.version}"].join(" ")
|
42
|
+
|
43
|
+
body = response.body.respond_to?(:call) ? response.body.call : response.body
|
44
|
+
body = body.is_a?(String) ? [ body ] : body
|
45
|
+
|
46
|
+
[response.code.to_i, response.headers, body || []]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -21,7 +21,9 @@ module Webmachine
|
|
21
21
|
Thread.new { server.start }.join
|
22
22
|
end
|
23
23
|
|
24
|
+
# WEBRick::HTTPServer that is run by the WEBrick adapter.
|
24
25
|
class Server < ::WEBrick::HTTPServer
|
26
|
+
# Handles a request
|
25
27
|
def service(wreq, wres)
|
26
28
|
header = Webmachine::Headers.new
|
27
29
|
wreq.each {|k,v| header[k] = v }
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Webmachine
|
2
|
+
# {ChunkedBody} is used to wrap an {Enumerable} object (like an enumerable
|
3
|
+
# {Response#body}) so it yields proper chunks for chunked transfer encoding.
|
4
|
+
#
|
5
|
+
# case response.body
|
6
|
+
# when String
|
7
|
+
# socket.write(response.body)
|
8
|
+
# when Enumerable
|
9
|
+
# Webmachine::ChunkedBody.new(response.body).each do |chunk|
|
10
|
+
# socket.write(chunk)
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# This is needed for Ruby webservers which don't do the chunking themselves.
|
15
|
+
class ChunkedBody
|
16
|
+
# Delimiter for chunked encoding
|
17
|
+
CRLF = "\r\n"
|
18
|
+
|
19
|
+
# Final chunk in any chunked-encoding response
|
20
|
+
FINAL_CHUNK = "0#{CRLF}#{CRLF}"
|
21
|
+
|
22
|
+
# Creates a new {ChunkedBody} from the given {Enumerable}.
|
23
|
+
# @param [Enumerable] body the enumerable response body
|
24
|
+
# @return [ChunkedBody] the wrapped response body
|
25
|
+
def initialize(body)
|
26
|
+
@body = body
|
27
|
+
end
|
28
|
+
|
29
|
+
# Calls the given block once for each chunk, passing that chunk as a
|
30
|
+
# parameter.
|
31
|
+
# Returns an {Enumerator} if no block is given.
|
32
|
+
def each
|
33
|
+
return self.to_enum unless block_given?
|
34
|
+
|
35
|
+
@body.each do |chunk|
|
36
|
+
size = chunk.bytesize
|
37
|
+
next if size == 0
|
38
|
+
yield([size.to_s(16), CRLF, chunk, CRLF].join)
|
39
|
+
end
|
40
|
+
yield(FINAL_CHUNK)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -87,6 +87,7 @@ module Webmachine
|
|
87
87
|
# equals the tag, or if it exactly equals a prefix of the
|
88
88
|
# tag such that the first tag character following the prefix
|
89
89
|
# is "-".
|
90
|
+
# @api private
|
90
91
|
def language_match(range, tag)
|
91
92
|
range.downcase == tag.downcase || tag =~ /^#{Regexp.escape(range)}\-/i
|
92
93
|
end
|
@@ -215,7 +216,7 @@ module Webmachine
|
|
215
216
|
end
|
216
217
|
end
|
217
218
|
|
218
|
-
# Like a {PriorityList}, but for {
|
219
|
+
# Like a {PriorityList}, but for {MediaType}s, since they have
|
219
220
|
# parameters in addition to q.
|
220
221
|
# @private
|
221
222
|
class MediaTypeList < PriorityList
|
@@ -6,8 +6,9 @@ require 'webmachine/translation'
|
|
6
6
|
module Webmachine
|
7
7
|
module Decision
|
8
8
|
# This module encapsulates all of the decisions in Webmachine's
|
9
|
-
# flow-chart. These invoke {Resource
|
10
|
-
# appropriate response code, headers, and body for
|
9
|
+
# flow-chart. These invoke {Webmachine::Resource::Callbacks} methods to
|
10
|
+
# determine the appropriate response code, headers, and body for
|
11
|
+
# the response.
|
11
12
|
#
|
12
13
|
# This module is included into {FSM}, which drives the processing
|
13
14
|
# of the chart.
|
@@ -390,6 +391,7 @@ module Webmachine
|
|
390
391
|
decision_test(resource.delete_resource, true, :m20b, 500)
|
391
392
|
end
|
392
393
|
|
394
|
+
# Did the DELETE complete?
|
393
395
|
def m20b
|
394
396
|
decision_test(resource.delete_completed?, true, :o20, 202)
|
395
397
|
end
|
@@ -5,6 +5,7 @@ module Webmachine
|
|
5
5
|
module Decision
|
6
6
|
# Methods that assist the Decision {Flow}.
|
7
7
|
module Helpers
|
8
|
+
# Pattern for quoted headers
|
8
9
|
QUOTED = /^"(.*)"$/
|
9
10
|
|
10
11
|
# Determines if the response has a body/entity set.
|
@@ -28,6 +29,8 @@ module Webmachine
|
|
28
29
|
response.body = case body
|
29
30
|
when String # 1.8 treats Strings as Enumerable
|
30
31
|
resource.send(encoder, resource.send(charsetter, body))
|
32
|
+
when Fiber
|
33
|
+
FiberEncoder.new(resource, encoder, charsetter, body)
|
31
34
|
when Enumerable
|
32
35
|
EnumerableEncoder.new(resource, encoder, charsetter, body)
|
33
36
|
else
|
@@ -84,6 +87,7 @@ module Webmachine
|
|
84
87
|
end
|
85
88
|
end
|
86
89
|
|
90
|
+
# Adds caching-related headers to the response.
|
87
91
|
def add_caching_headers
|
88
92
|
if etag = resource.generate_etag
|
89
93
|
response.headers['ETag'] = ensure_quoted_header(etag)
|