00README

00README
This is the talk that I just gave at the LA Ruby Meetup, minutes ago.
It was on the Watts not-framework. Watts can be found at
http://github.com/pete/watts , which has documentation and examples.
The "slide deck" is the text file below, which was run through a small
Ruby script that recognized "-- $header" as delimiters.
Enjoy.
Update 2013-01-28: There was a bug in Watts, and in this presentation,
specifically that unknown methods ought to be responded to with a "501 Not
Implemented" rather than a "400 Bad Request". I've left the talk as-is (with
the exception that I have put [[2013 edit! ...]] in a couple of places).
Watts was updated two years ago to remedy the bug:
https://github.com/pete/watts/commit/01af6ef1c7fb720024c4b032685261c0c6bba194
slideshow.rb
#! /usr/bin/env ruby
# This is an ugly hacky script that I wrote an hour before I had to use for a
# different presentation, and was more recently used to run the slideshow for
# this LA Ruby Meetup talk. It is not very good Ruby; it's a script. It's
# probably buggy. But running it will show the slideshow from the plain-text
# 'talk' file.
require 'rubygems'
require 'colorize'
def cook!
system "stty sane"
end
def raw!
system "stty -icanon min 1"
end
def title str
" #{str} ".center((ENV['COLUMNS'] || 80).to_i, '=').
colorize(:color => :light_white,
:background => :blue,
:mode => :bold)
end
def show_slide slide
#if ENV['COLUMNS'] && ENV['COLUMNS'] != '80'
#puts slide.first
#IO.popen("fmt -s -w#{ENV['COLUMNS'] || 80}", 'w') { |i|
#i.puts *slide[1..-1]
#}
#else
puts *slide
#end
end
slides = []
File.readlines('talk').each { |line|
if /^--(.*)/.match(line)
slides << [title($1)]
else
slides.last << line
end
}
slides << [title("It's over!"), "Have fun.", "Any (more) questions?"]
trap('INT') { cook!; exit }
raw!
i = 0
loop {
system "clear"
show_slide slides[i]
case $stdin.read(1)
when ' ', 'n', 'j', "\n"
i += 1 if i < slides.length - 1
when 'b', 'p', 'k'
i -= 1 if i > 0
system "clear"
when 'q'
cook!; exit
end
}
talk
-- I'm Pete.
Hi.
-- This is a talk on Watts.
# http://github.com/pete/watts
class Hi < Watts::App
class Res < Watts::Resource
get { "Hello, World!\n" }
end
end
-- It's also about simplicity and abstractions.
"Abstraction focuses on reducing complexity, generalizing the concrete, and
abridge or summarize. Indirection is a necessary evil used to create pluggable
systems."
-- Zed "I am a Jerk on the Internet But Frequently Correct" Shaw
[It's a good read, overall:
http://www.zedshaw.com/essays/indirection_is_not_abstraction.html]
-- ...Finally, it's also about HTTP and REST.
The boring stuff. But I won't go too in depth. Promise. I'll keep the talk,
like Watts itself, simple. If you want the gory details, have a look at
http://www.ietf.org/rfc/rfc2616.txt .
-- I don't have a choice but to make it simple.
These "slides" are just sections of a specially delimited text file that a hacky
Ruby script is spitting out onto the screen. I don't know how to operate
Powerpoint or Keynote or what-have-you. I'm writing this in a text editor.
So the slides have to be text-only and 80x24. Simple.
[All the links (and probably the whole deck) will be put into a gist or
something. Don't worry if you miss anything.]
-- Watts: NOT ANOTHER WEB FRAMEWORK
Don't let this get out, but it's not really a web framework in the strictest
sense. It just makes a sincere and simple attempt to represent resources (in
the HTTP sense) in a Ruby-friendly way, to provide some pattern-matching for
paths to these resources, and to...nope, that's it. Rack handles HTTP requests
and responses, and Watts handles HTTP resources. No ORM, no ERB, nothing. If
you want that stuff, you can require ActiveRecord or Sequel or ERB.
-- What are we trying to pull with this HTTP protocol?
"HTTP allows basic hypermedia access to resources available from diverse
applications." -- RFC "Please Friggin' Read Me if You Write Webapps" 2616
-- Why's HTTP good for this?
HTTP's inherent niceness comes from a few properties:
* It's simple.
* It has a central abstraction ("the resources") and defines some means
for interacting with it.
* It is easy to implement a trivial client or server (although not a
performant one).
-- Central abstraction?
HTTP is all about resources:
A [resource is a] network data object or service that can be identified
by a URI, as defined in section 3.2. Resources may be available in
multiple representations (e.g. multiple languages, data formats, size,
and resolutions) or vary in other ways.
-- RFC "Surprisingly Pretty Readable" 2616, Section 1.3
It defines what they are, how to identify them (URIs) and how to interact with
them.
-- They had a reason to make HTTP.
-- It's good to comply with HTTP.
People who write clients, people who write tools based on the clients written by
the previously mentioned people, people who write servers, all of those people
are happier when the software they use and the servers and clients they interact
with act as expected.
-- Sinatra has problems:
Sinatra:
$ cat > sinatra-test.rb
require 'sinatra'
get '/' do
"Hello World!\n"
end
^D
$ ruby sinatra-test.rb
== Sinatra/1.2.0 has taken the stage on 4567 for [ LONG FRIGGIN LINE ]
>> Thin web server (v1.2.7 codename No Hup)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:4567, CTRL+C to stop
-- Let's try that out!
$ curl http://localhost:4567/
# Prints the greeeting! Sinatra's log:
# 127.0.0.1 - - [10/Mar/2011 17:26:04] "GET / HTTP/1.1" 200 13 0.0012
$ curl -XOPTIONS http://localhost:4567/
# Error? What's the log say?
127.0.0.1 - - [10/Mar/2011 17:26:13] "OPTIONS / HTTP/1.1" 404 413 0.0003
404? That resource exists!
$ curl -v -XWTFMAN http://localhost:9292/
127.0.0.1 - - [10/Mar/2011 18:00:30] "WTFMAN / HTTP/1.1" 404 412 0.0004
"WTFMAN" isn't even an HTTP method!
-- Okay, what about Rails?
[Making a basic Rails 3.0.5 app with a resource at / omitted. SO MUCH OUTPUT.]
$ grep hello config/routes.rb
root :to => "hello#index"
$ rackup config.ru
[2011-03-10 17:53:41] INFO WEBrick 1.3.1
[2011-03-10 17:53:41] INFO ruby 1.9.2 (2010-08-18) [linux]
[2011-03-10 17:53:41] INFO WEBrick::HTTPServer#start: pid=4645 port=9292
-- Hate to break it to you:
$ curl http://localhost:9292/
127.0.0.1 - - [10/Mar/2011 17:54:44] "GET / HTTP/1.1" 200 - 0.8944
$ curl -v -XOPTIONS http://localhost:9292/ 2>&1 | grep -i 'allow:'
# Nope, nothing, sorry, it acts exactly like a GET.
127.0.0.1 - - [10/Mar/2011 17:54:52] "OPTIONS / HTTP/1.1" 200 - 0.0109
$ curl -XWTFMAN http://localhost:9292/
127.0.0.1 - - [10/Mar/2011 17:55:25] "WTFMAN / HTTP/1.1" 500 54812 0.0272
# ...Yes, that's 54k of HTML it gives you.
-- RFC2616:
You're supposed to respond with an "Allow:" header that contains a list of
allowed methods. The appropriate response to an invalid HTTP method is "405
Method Not Allowed" and an "Allow:" header. And you're supposed to spit back a
400 instead of a 404 or a 500 when the server can't even understand the request.
[[2013 edit! It turns out you're supposed to return a 501 if your server
does not understand an HTTP method. This was fixed after this presentation.]]
-- Watts:
$ cat > config.ru
require 'watts'
class Hi < Watts::App
class Res < Watts::Resource
get { "Hello, World!\n" }
end
resource '/', Res
end
run Hi.new
$ rackup config.ru -p 8080
...
-- I think we're doing it right.
$ curl http://localhost:8080/
$ curl -v -XOPTIONS http://localhost:8080/ 2>&1 | grep Allow:
< Allow: GET
$ curl -v -XWTFMAN http://localhost:8080/ 2>&1 | grep 400
< HTTP/1.1 400 Bad Request
400 Bad Request.
[[2013 edit! As I said above, this now gives a "501 Not Implemented" error in
Watts, per the RFC.]]
Well, that's not so bad at all.
-- Camping:
No, I didn't try out Camping. Sorry.
-- I love _why as much as the next guy.
Gonna call this a failure on my part.
-- How does Sinatra deal with resources?
It doesn't. It uses method names that match HTTP method names, and you give it
paths. Sinatra doesn't deal in resources, and so it doesn't keep track of them.
get '/' do ... end
put '/' do ... end
# etc.
It looks like the HTTP request, but it doesn't model resources the way they
could be in a nice OO language like Ruby. By design, Sinatra is method- and
path-centric, ignoring the central abstraction of HTTP and the niceness of Ruby.
-- How does Rails deal with resources?
Rails is heavily MVC-based; this is one of the good points of the framework.
You define "routes", and they map to controllers, which are roughly like
resources. This is a pretty good approach!
The only problem is the minor fumbling around Controller methods versus HTTP
methods, and how routes deal with them. I don't see a reason that "405 Method
Not Allowed" couldn't be handled properly, so it's a bug in Rails rather than an
architectural flaw, and can probably be fixed.
-- How does Watts deal with resources?
Watts provides a Resource class; you inherit from it and it will (mostly) handle
HTTP for you:
class SomeResource < Watts::Resource
get { "Hey, man. text/plain your style?" }
put { request.body.do_something_with_it_i_guess }
end
-- How does Watts deal with paths?
Paths are the part of the URI that your app definitely needs to be concerned
with for routing requests to the right resources. Watts handles it by providing
some pattern-matching, defined by your app:
class Asdf < Watts::Resource; end # No methods! Ouch.
class Jkl
get { |arg, number| "Arg was #{arg}, number was #{number}" }
put { |arg, number| ... }
end
class SomeApp < Watts::App
resource('/', SomeResource) {
# /asdf exists, but you can't do anything with it.
resource('asdf', Asdf)
# You can GET or PUT /jkl/anything-here/325:
resource(['jkl', :another_arg, /^0-9+$/], Jkl)
}
end
-- How does Watts deal with...?
It doesn't. It deals in apps and resources, and that's it. It's simple, I told
you.
$ find lib -name '*.rb' | xargs wc -l
323 lib/watts.rb
26 lib/watts/monkey_patching.rb
349 total
Most of that's documentation.
-- Why make Watts?
I wanted to be able to do web development in a framework that I could fit in my
head, but that I wouldn't need to, because it stays out of the way. It's
powering my (rarely updated) blog. I wanted something that would run under
1.8.7 and 1.9.x. I wanted something that wouldn't take up ALL COMPUTING
RESOURCES for apps.
So, I made it because I wanted to use it.
-- Simplicity!
Simple like the pyramids. It made the code easy to write, it keeps it easy to
read and maintain, and keeps the bug count down. There's less to think about.
"There are two ways of constructing a software design. One way is to make it so
simple that there are obviously no deficiencies. And the other way is to make it
so complicated that there are no obvious deficiencies."
-- Sir Charles Antony Richard "I Wrote Quicksort, Pay Attention" Hoare
-- Bringing back simple!
Sinatra is cool, but at 5k lines, it's getting less and less minimalist. If you
can find the right abstraction and remember what your software is supposed to
do, then it's pretty easy to get lots accomplished with very little code.
I don't think anyone's ever called Rails' codebase simple.
-- I'm almost done.
I don't know if I'm running under or over time at this point. I have trouble
sticking to the script.
-- TODO:
* I'm sure Watts has bugs. Oren found one! It works for me, though!
* I don't know of any features it's missing that it should have, but they may
exist.