Permalink
Browse files

Extract header-conversion code and update a bunch of documentation.

  • Loading branch information...
1 parent f5b8841 commit 5709892c269c22ba68ef943e77489160b736691c @seancribbs seancribbs committed Oct 23, 2011
View
1 .yardopts
@@ -0,0 +1 @@
+-M redcarpet
View
20 Gemfile
@@ -5,19 +5,21 @@ source :rubygems
gemspec
gem 'bundler'
-gem 'bluecloth'
-gem 'yard'
unless ENV['TRAVIS']
gem 'guard-rspec'
- case RbConfig::CONFIG['host_os']
- when /darwin/
- gem 'rb-fsevent'
- gem 'growl_notify'
- when /linux/
- gem 'rb-inotify'
- gem 'libnotify'
+ platform :mri do
+ gem 'redcarpet'
+
+ case RbConfig::CONFIG['host_os']
+ when /darwin/
+ gem 'rb-fsevent'
+ gem 'growl_notify'
+ when /linux/
+ gem 'rb-inotify'
+ gem 'libnotify'
+ end
end
end
View
104 README.md
@@ -11,40 +11,40 @@ toolkit for building HTTP-friendly applications. For example, it does
not provide a templating engine or a persistence layer; those choices
are up to you.
-**NOTE**: _Webmachine is NOT compatible with Rack._ This is
-intentional! Rack obscures HTTP in a way that makes it hard for
-Webmachine to do its job properly, and encourages people to add
-middleware that might break Webmachine's behavior. Rack is also built
-on the tradition of CGI, which is nice for backwards compatibility but
-also an antiquated paradigm and should be scuttled (IMHO). _Rack may
-be supported in the future, but only as a shim to support other web
-application servers._
+## A Note about Rack
+
+Webmachine has a Rack adapter -- thanks to Jamis Buck -- but when
+using it, we recommend you ensure that NO middleware is used. The
@matschaffer
matschaffer Jul 12, 2012

Are there any blog posts or other sources for these (no rack/minimal rack) statements? Would love to hear more before diving in to using this for a project.

@lgierth
lgierth Jul 12, 2012

I'm using Webmachine mostly with the Rack adapter, and had no problems whatsoever. I'm not using any Rack middleware that fiddles with HTTP concerns though. Only things like Rack::ShowExceptions, Rack::CommonLogger, Rack::Static and the like.

@seancribbs
seancribbs Jul 12, 2012

The comment is more an admonition not to mess with Webmachine's decision logic by inserting your own middleware. Rack encourages the model of DIY HTTP, whereas I intended Webmachine to be the "source of truth" w.r.t. the request-response flow. Tooling middlewares like the ones @lgierth mentions have no effect on the behavior of Webmachine and so should be ok (of course Rack::Static subverts WM, but you should probably use a traditional webserver for serving files).

Note also that things injected into the Rack environment will not be accessible from resources unless they are part of the normal HTTP request headers.

@matschaffer
matschaffer Jul 12, 2012

Did you settle on those pieces by trial and error or is there some heuristic to follow for what sort of middleware is compatible with Webmachine?

@matschaffer
matschaffer Jul 12, 2012

So stuff that manipulates the env is probably not compatible then. Thanks! What about the CGI vs newer approaches? Any sources on that statement?

@matschaffer
matschaffer Jul 12, 2012

@seancribbs this bit "Rack is also built on the tradition of CGI, which is nice for backwards compatibility but also an antiquated paradigm and should be scuttled (IMHO)" Curious to hear more about that especially with so many stacks that use CGI as a model (rack, wsgi, etc)

+behaviors that are encapsulated in Webmachine could be broken by
+middlewares that sit above it, and there is no way to detect them at
+runtime. _Caveat emptor_. That said, Webmachine should behave properly
+when given a clear stack.
## Getting Started
Webmachine is very young, but it's still easy to construct an
application for it!
```ruby
- require 'webmachine'
- # Require any of the files that contain your resources here
- require 'my_resource'
-
- # Point all URIs at the MyResource class
- Webmachine::Dispatcher.add_route(['*'], MyResource)
-
- # Start the server, binds to port 8080 using WEBrick
- Webmachine.run
+require 'webmachine'
+# Require any of the files that contain your resources here
+require 'my_resource'
+
+# Point all URIs at the MyResource class
+Webmachine::Dispatcher.add_route(['*'], MyResource)
+
+# Start the server, binds to port 8080 using WEBrick
+Webmachine.run
```
Your resource will look something like this:
```ruby
- class MyResource < Webmachine::Resource
- def to_html
- "<html><body>Hello, world!</body></html>"
- end
- end
+class MyResource < Webmachine::Resource
+ def to_html
+ "<html><body>Hello, world!</body></html>"
+ end
+end
```
Run the first file and your application is up. That's all there is to
@@ -54,39 +54,43 @@ might want to enable "gzip" compression on your resource, for which
you can simply add an `encodings_provided` callback method:
```ruby
- class MyResource < Webmachine::Resource
- def encodings_provided
- {"gzip" => :encode_gzip, "identity" => :encode_identity}
- end
-
- def to_html
- "<html><body>Hello, world!</body></html>"
- end
- end
+class MyResource < Webmachine::Resource
+ def encodings_provided
+ {"gzip" => :encode_gzip, "identity" => :encode_identity}
+ end
+
+ def to_html
+ "<html><body>Hello, world!</body></html>"
+ end
+end
```
There are many other HTTP features exposed to your resource through
-callbacks. Give them a try!
+{Webmachine::Resource::Callbacks}. Give them a try!
### Configurator
There's a configurator that allows you to set the ip address and port
-bindings as well as a different webserver adapter.
+bindings as well as a different webserver adapter. You can also add
+your routes in a block. Both of these call return the `Webmachine`
+module, so you could chain them if you like.
```ruby
- require 'webmachine'
- require 'my_resource'
-
- Webmachine::Dispatcher.add_route(['*'], MyResource)
-
- Webmachine.configure do |config|
- config.ip = '127.0.0.1'
- config.port = 3000
- config.adapter = :Mongrel
- end
-
- # Start the server.
- Webmachine.run
+require 'webmachine'
+require 'my_resource'
+
+Webmachine.routes do
+ add ['*'], MyResource
+end
+
+Webmachine.configure do |config|
+ config.ip = '127.0.0.1'
+ config.port = 3000
+ config.adapter = :Mongrel
+end
+
+# Start the server.
+Webmachine.run
```
## Features
@@ -96,14 +100,14 @@ bindings as well as a different webserver adapter.
* Most callbacks can interrupt the decision flow by returning an
integer response code. You generally only want to do this when new
information comes to light, requiring a modification of the response.
-* Supports WEBrick and Mongrel (1.2pre+). Other host servers are being
- investigated.
-* Streaming/chunked response bodies are permitted as Enumerables or Procs.
+* Supports WEBrick and Mongrel (1.2pre+), and a Rack shim. Other host
+ servers are being investigated.
+* Streaming/chunked response bodies are permitted as Enumerables,
+ Procs, or Fibers!
* Unlike the Erlang original, it does real Language negotiation.
## Problems/TODOs
-* Support streamed responses as Fibers.
* Command-line tools, and general polish.
* Tracing is exposed as an Array of decisions visited on the response
object. You should be able to turn this off and on, and visualize
View
13 lib/webmachine/adapters/mongrel.rb
@@ -27,9 +27,11 @@ def self.run
config.join
end
+ # A Mongrel handler for Webmachine
class Handler < ::Mongrel::HttpHandler
+ # Processes an individual request from Mongrel through Webmachine.
def process(wreq, wres)
- header = http_headers(wreq.params, Webmachine::Headers.new)
+ header = Webmachine::Headers.from_cgi(wreq.params)
request = Webmachine::Request.new(wreq.params["REQUEST_METHOD"],
URI.parse(wreq.params["REQUEST_URI"]),
@@ -72,15 +74,6 @@ def process(wreq, wres)
response.body.close if response.body.respond_to? :close
end
end
-
- def http_headers(env, headers)
- env.inject(headers) do |h,(k,v)|
- if k =~ /^HTTP_(\w+)$/
- h[$1.tr("_", "-")] = v
- end
- h
- end
- end
end
end
end
View
12 lib/webmachine/adapters/rack.rb
@@ -10,7 +10,7 @@ module Adapters
# A minimal "shim" adapter to allow Webmachine to interface with Rack. The
# intention here is to allow Webmachine to run under Rack-compatible
# web-servers, like unicorn and pow, and is not intended to allow Webmachine
- # to be "plugged in" to an existin Rack app as middleware.
+ # to be "plugged in" to an existing Rack app as middleware.
#
# To use this adapter, create a config.ru file and populate it like so:
#
@@ -24,14 +24,10 @@ module Adapters
# Servers like pow and unicorn will read config.ru by default and it should
# all "just work".
class Rack
+ # Handles a Rack-based request.
+ # @param [Hash] env the Rack environment
def call(env)
- headers = Webmachine::Headers.new
- env.each do |key, value|
- if key =~ /^HTTP_/
- key = $'.gsub(/_/, "-")
- headers[key] = value
- end
- end
+ headers = Webmachine::Headers.from_cgi(env)
rack_req = ::Rack::Request.new env
request = Webmachine::Request.new(rack_req.request_method,
View
2 lib/webmachine/adapters/webrick.rb
@@ -21,7 +21,9 @@ def self.run
Thread.new { server.start }.join
end
+ # WEBRick::HTTPServer that is run by the WEBrick adapter.
class Server < ::WEBrick::HTTPServer
+ # Handles a request
def service(wreq, wres)
header = Webmachine::Headers.new
wreq.each {|k,v| header[k] = v }
View
3 lib/webmachine/chunked_body.rb
@@ -13,7 +13,10 @@ module Webmachine
#
# This is needed for Ruby webservers which don't do the chunking themselves.
class ChunkedBody
+ # Delimiter for chunked encoding
CRLF = "\r\n"
+
+ # Final chunk in any chunked-encoding response
FINAL_CHUNK = "0#{CRLF}#{CRLF}"
# Creates a new {ChunkedBody} from the given {Enumerable}.
View
1 lib/webmachine/decision/flow.rb
@@ -391,6 +391,7 @@ def m20
decision_test(resource.delete_resource, true, :m20b, 500)
end
+ # Did the DELETE complete?
def m20b
decision_test(resource.delete_completed?, true, :o20, 202)
end
View
17 lib/webmachine/headers.rb
@@ -1,18 +1,33 @@
module Webmachine
# Case-insensitive Hash of Request headers
class Headers < ::Hash
+ # Convert CGI-style Hash into Request headers
+ # @param [Hash] env a hash of CGI-style env/headers
+ def self.from_cgi(env)
+ env.inject(new) do |h,(k,v)|
+ if k =~ /^HTTP_(\w+)$/
+ h[$1.tr("_", "-")] = v
+ end
+ h
+ end
+ end
+
+ # Fetch a header
def [](key)
super transform_key(key)
end
+ # Set a header
def []=(key,value)
super transform_key(key), value
end
+ # Delete a header
def delete(key)
super transform_key(key)
end
-
+
+ # Select matching headers
def grep(pattern)
self.class[select { |k,_| pattern === k }]
end
View
6 webmachine.gemspec
@@ -21,22 +21,22 @@ Gem::Specification.new do |gem|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
gem.add_runtime_dependency(%q<i18n>, [">= 0.4.0"])
gem.add_development_dependency(%q<rspec>, ["~> 2.6.0"])
- gem.add_development_dependency(%q<yard>, ["~> 0.6.7"])
+ gem.add_development_dependency(%q<yard>, ["~> 0.7.3"])
gem.add_development_dependency(%q<rake>)
gem.add_development_dependency(%q<mongrel>, ['~>1.2.beta'])
gem.add_development_dependency(%q<rack>)
else
gem.add_dependency(%q<i18n>, [">= 0.4.0"])
gem.add_dependency(%q<rspec>, ["~> 2.6.0"])
- gem.add_dependency(%q<yard>, ["~> 0.6.7"])
+ gem.add_dependency(%q<yard>, ["~> 0.7.3"])
gem.add_dependency(%q<rake>)
gem.add_dependency(%q<mongrel>, ['~>1.2.beta'])
gem.add_dependency(%q<rack>)
end
else
gem.add_dependency(%q<i18n>, [">= 0.4.0"])
gem.add_dependency(%q<rspec>, ["~> 2.6.0"])
- gem.add_dependency(%q<yard>, ["~> 0.6.7"])
+ gem.add_dependency(%q<yard>, ["~> 0.7.3"])
gem.add_dependency(%q<rake>)
gem.add_dependency(%q<mongrel>, ['~>1.2.beta'])
gem.add_dependency(%q<rack>)

0 comments on commit 5709892

Please sign in to comment.