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
@@ -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
+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
@@ -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
@@ -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,
@@ -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 }
@@ -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}.
@@ -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
@@ -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
@@ -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.