Skip to content

Commit

Permalink
Merge branch 'originalmachine-reel' into reel
Browse files Browse the repository at this point in the history
Conflicts:
	webmachine.gemspec
  • Loading branch information
tarcieri committed Aug 18, 2012
2 parents 0011488 + be7cf45 commit 5f26d09
Show file tree
Hide file tree
Showing 47 changed files with 1,877 additions and 140 deletions.
77 changes: 70 additions & 7 deletions README.md
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ application for it!
```ruby ```ruby
require 'webmachine' require 'webmachine'
# Require any of the files that contain your resources here # Require any of the files that contain your resources here
require 'my_resource' require 'my_resource'


# Create an application which encompasses routes and configruation # Create an application which encompasses routes and configruation
MyApp = Webmachine::Application.new do |app| MyApp = Webmachine::Application.new do |app|
Expand Down Expand Up @@ -63,7 +63,7 @@ class MyResource < Webmachine::Resource
def encodings_provided def encodings_provided
{"gzip" => :encode_gzip, "identity" => :encode_identity} {"gzip" => :encode_gzip, "identity" => :encode_identity}
end end

def to_html def to_html
"<html><body>Hello, world!</body></html>" "<html><body>Hello, world!</body></html>"
end end
Expand All @@ -85,7 +85,7 @@ object, `Webmachine.application` will return a global one.
```ruby ```ruby
require 'webmachine' require 'webmachine'
require 'my_resource' require 'my_resource'

Webmachine.application.routes do Webmachine.application.routes do
add ['*'], MyResource add ['*'], MyResource
end end
Expand All @@ -95,11 +95,52 @@ Webmachine.application.configure do |config|
config.port = 3000 config.port = 3000
config.adapter = :Mongrel config.adapter = :Mongrel
end end

# Start the server. # Start the server.
Webmachine.application.run Webmachine.application.run
``` ```


### Visual debugger

It can be hard to understand all of the decisions that Webmachine
makes when servicing a request to your resource, which is why we have
the "visual debugger". In development, you can turn on tracing of the
decision graph for a resource by implementing the `#trace?` callback
so that it returns true:

```ruby
class MyTracedResource < Webmachine::Resource
def trace?
true
end

# The rest of your callbacks...
end
```

Then enable the visual debugger resource by adding a route to your
configuration:

```ruby
Webmachine.application.routes do
# This can be any path as long as it ends with '*'
add ['trace', '*'], Webmachine::Trace::TraceResource
# The rest of your routes...
end
```

Now when you visit your traced resource, a trace of the request
process will be recorded in memory. Open your browser to `/trace` to
list the recorded traces and inspect the result. The response from your
traced resource will also include the `X-Webmachine-Trace-Id` that you
can use to lookup the trace. It might look something like this:

![preview calls at decision](http://seancribbs-skitch.s3.amazonaws.com/Webmachine_Trace_2156885920-20120625-100153.png)

Refer to
[examples/debugger.rb](/seancribbs/webmachine-ruby/blob/master/examples/debugger.rb)
for an example of how to enable the debugger.

## Features ## Features


* Handles the hard parts of content negotiation, conditional * Handles the hard parts of content negotiation, conditional
Expand All @@ -112,13 +153,12 @@ Webmachine.application.run
* Streaming/chunked response bodies are permitted as Enumerables, * Streaming/chunked response bodies are permitted as Enumerables,
Procs, or Fibers! Procs, or Fibers!
* Unlike the Erlang original, it does real Language negotiation. * Unlike the Erlang original, it does real Language negotiation.
* Includes the visual debugger so you can look through the decision
graph to determine how your resources are behaving.


## Problems/TODOs ## Problems/TODOs


* Command-line tools, and general polish. * 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
the decisions on the sequence diagram.


## LICENSE ## LICENSE


Expand All @@ -128,6 +168,29 @@ LICENSE for details.


## Changelog ## Changelog


### 1.0.0 July 7, 2012

1.0.0 is a major feature release that finally includes the visual
debugger, some nice cookie support, and some new extension
points. Added Peter Johanson and Armin Joellenbeck as
contributors. Thank you for your contributions!

* A cookie parsing and manipulation API was added.
* Conneg headers now accept any amount of whitespace around commas,
including none.
* `Callbacks#handle_exception` was added so that resources can handle
exceptions that they generate and produce more friendly responses.
* Chunked and non-chunked response bodies in the Rack adapter were
fixed.
* The WEBrick example was updated to use the new API.
* `Dispatcher` was refactored so that you can modify how resources
are initialized before dispatching occurs.
* `Route` now includes the `Translation` module so that exception
messages are properly rendered.
* The visual debugger was added (more details in the README).
* The `Content-Length` header will always be set inside Webmachine and
is no longer reliant on the adapter to set it.

### 0.4.2 March 22, 2012 ### 0.4.2 March 22, 2012


0.4.2 is a bugfix release that corrects a few minor issues. Added Lars 0.4.2 is a bugfix release that corrects a few minor issues. Added Lars
Expand Down
19 changes: 19 additions & 0 deletions Rakefile
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -32,6 +32,25 @@ task :release => :gem do
system "gem push pkg/#{gemspec.name}-#{gemspec.version}.gem" system "gem push pkg/#{gemspec.name}-#{gemspec.version}.gem"
end end


desc "Cleans up white space in source files"
task :clean_whitespace do
no_file_cleaned = true

Dir["**/*.rb"].each do |file|
contents = File.read(file)
cleaned_contents = contents.gsub(/([ \t]+)$/, '')
unless cleaned_contents == contents
no_file_cleaned = false
puts " - Cleaned #{file}"
File.open(file, 'w') { |f| f.write(cleaned_contents) }
end
end

if no_file_cleaned
puts "No files with trailing whitespace found"
end
end

require 'rspec/core' require 'rspec/core'
require 'rspec/core/rake_task' require 'rspec/core/rake_task'


Expand Down
32 changes: 32 additions & 0 deletions examples/debugger.rb
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,32 @@
require 'webmachine'
require 'webmachine/trace'

class MyTracedResource < Webmachine::Resource
def trace?; true; end

def resource_exists?
case request.query['e']
when 'true'
true
when 'fail'
raise "BOOM"
else
false
end
end

def to_html
"<html>You found me.</html>"
end
end

# Webmachine::Trace.trace_store = :pstore, "./trace"

TraceExample = Webmachine::Application.new do |app|
app.routes do
add ['trace', '*'], Webmachine::Trace::TraceResource
add [], MyTracedResource
end
end

TraceExample.run
8 changes: 6 additions & 2 deletions examples/webrick.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ def to_html
end end
end end


Webmachine::Dispatcher.add_route([], HelloResource) App = Webmachine::Application.new do |app|
app.routes do
add [], HelloResource
end
end


Webmachine.run App.run
1 change: 1 addition & 0 deletions lib/webmachine.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
require 'webmachine/dispatcher' require 'webmachine/dispatcher'
require 'webmachine/application' require 'webmachine/application'
require 'webmachine/resource' require 'webmachine/resource'
require 'webmachine/trace'
require 'webmachine/version' require 'webmachine/version'


# Webmachine is a toolkit for making well-behaved HTTP applications. # Webmachine is a toolkit for making well-behaved HTTP applications.
Expand Down
1 change: 1 addition & 0 deletions lib/webmachine/adapters.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ module Webmachine
# application servers. # application servers.
module Adapters module Adapters
autoload :Mongrel, 'webmachine/adapters/mongrel' autoload :Mongrel, 'webmachine/adapters/mongrel'
autoload :Reel, 'webmachine/adapters/reel'
end end
end end
19 changes: 16 additions & 3 deletions lib/webmachine/adapters/rack.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require 'webmachine/request' require 'webmachine/request'
require 'webmachine/response' require 'webmachine/response'
require 'webmachine/dispatcher' require 'webmachine/dispatcher'
require 'webmachine/chunked_body'


module Webmachine module Webmachine
module Adapters module Adapters
Expand Down Expand Up @@ -58,10 +59,22 @@ def call(env)


response.headers['Server'] = [Webmachine::SERVER_STRING, "Rack/#{::Rack.version}"].join(" ") response.headers['Server'] = [Webmachine::SERVER_STRING, "Rack/#{::Rack.version}"].join(" ")


body = response.body.respond_to?(:call) ? response.body.call : response.body rack_status = response.code
body = body.is_a?(String) ? [ body ] : body rack_headers = response.headers.flattened("\n")
rack_body = case response.body
when String # Strings are enumerable in ruby 1.8
[response.body]
else
if response.body.respond_to?(:call)
Webmachine::ChunkedBody.new(Array(response.body.call))
elsif response.body.respond_to?(:each)
Webmachine::ChunkedBody.new(response.body)
else
[response.body.to_s]
end
end


[response.code.to_i, response.headers.flattened("\n"), body || []] [rack_status, rack_headers, rack_body]
end end


# Wraps the Rack input so it can be treated like a String or # Wraps the Rack input so it can be treated like a String or
Expand Down
20 changes: 10 additions & 10 deletions lib/webmachine/application.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@


module Webmachine module Webmachine
# How to get your Webmachine app running: # How to get your Webmachine app running:
# #
# MyApp = Webmachine::Application.new do |app| # MyApp = Webmachine::Application.new do |app|
# app.routes do # app.routes do
# add ['*'], AssetResource # add ['*'], AssetResource
# end # end
# #
# app.configure do |config| # app.configure do |config|
# config.port = 8888 # config.port = 8888
# end # end
# end # end
# #
# MyApp.run # MyApp.run
# #
class Application class Application
extend Forwardable extend Forwardable


Expand All @@ -32,17 +32,17 @@ class Application
# #
# An instance of application contains Adapter configuration and # An instance of application contains Adapter configuration and
# a Dispatcher instance which can be configured with Routes. # a Dispatcher instance which can be configured with Routes.
# #
# @param [Webmachine::Configuration] configuration # @param [Webmachine::Configuration] configuration
# a Webmachine::Configuration # a Webmachine::Configuration
# #
# @yield [app] # @yield [app]
# a block in which to configure this Application # a block in which to configure this Application
# @yieldparam [Application] # @yieldparam [Application]
# the Application instance being initialized # the Application instance being initialized
def initialize(configuration = Configuration.default) def initialize(configuration = Configuration.default, dispatcher = Dispatcher.new)
@configuration = configuration @configuration = configuration
@dispatcher = Dispatcher.new @dispatcher = dispatcher


yield self if block_given? yield self if block_given?
end end
Expand All @@ -66,10 +66,10 @@ def adapter_class


# Evaluates the passed block in the context of {Webmachine::Dispatcher} # Evaluates the passed block in the context of {Webmachine::Dispatcher}
# for use in adding a number of routes at once. # for use in adding a number of routes at once.
# #
# @return [Application, Array<Route>] # @return [Application, Array<Route>]
# self if configuring, or an Array of Routes otherwise # self if configuring, or an Array of Routes otherwise
# #
# @see Webmachine::Dispatcher#add_route # @see Webmachine::Dispatcher#add_route
def routes(&block) def routes(&block)
if block_given? if block_given?
Expand Down
2 changes: 1 addition & 1 deletion lib/webmachine/decision/flow.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module Decision
# This module encapsulates all of the decisions in Webmachine's # This module encapsulates all of the decisions in Webmachine's
# flow-chart. These invoke {Webmachine::Resource::Callbacks} methods to # flow-chart. These invoke {Webmachine::Resource::Callbacks} methods to
# determine the appropriate response code, headers, and body for # determine the appropriate response code, headers, and body for
# the response. # the response.
# #
# This module is included into {FSM}, which drives the processing # This module is included into {FSM}, which drives the processing
# of the chart. # of the chart.
Expand Down
20 changes: 16 additions & 4 deletions lib/webmachine/decision/fsm.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ class FSM
def initialize(resource, request, response) def initialize(resource, request, response)
@resource, @request, @response = resource, request, response @resource, @request, @response = resource, request, response
@metadata = {} @metadata = {}
initialize_tracing
end end


# Processes the request, iteratively invoking the decision methods in {Flow}. # Processes the request, iteratively invoking the decision methods in {Flow}.
def run def run
state = Flow::START state = Flow::START
trace_request(request)
loop do loop do
response.trace << state trace_decision(state)
result = send(state) result = send(state)
case result case result
when Fixnum # Response code when Fixnum # Response code
Expand All @@ -38,9 +40,8 @@ def run
Webmachine.render_error(400, request, response, :message => malformed.message) Webmachine.render_error(400, request, response, :message => malformed.message)
respond(400) respond(400)
rescue Exception => e # Handle all exceptions without crashing the server rescue Exception => e # Handle all exceptions without crashing the server
response.end_state = state
code = resource.handle_exception(e) code = resource.handle_exception(e)
code = (100...600).include?(code) ? (code) : (500) code = (100...600).include?(code) ? (code) : (500)
respond(code) respond(code)
end end


Expand All @@ -57,9 +58,20 @@ def respond(code, headers={})
end end
response.code = code response.code = code
resource.finish_request resource.finish_request
# TODO: add logging/tracing ensure_content_length
trace_response(response)
end end


# When tracing is disabled, this does nothing.
def trace_decision(state); end
# When tracing is disabled, this does nothing.
def trace_request(request); end
# When tracing is disabled, this does nothing.
def trace_response(response); end

def initialize_tracing
extend Trace::FSM if Trace.trace?(resource)
end
end # class FSM end # class FSM
end # module Decision end # module Decision
end # module Webmachine end # module Webmachine
Loading

0 comments on commit 5f26d09

Please sign in to comment.