Hacker News new | past | comments | ask | show | jobs | submit login
A Full Web Service with HTTP caching in 7 lines (slightlynew.blogspot.com)
19 points by lsb on Feb 24, 2009 | hide | past | favorite | 16 comments



Am I the only one who's tired of these "X in Y lines of code!" things? It's obviously always many more lines of code, just hidden away from view, so meh!

Come back when you've actually done some REAL WORK in insanely tight code, not just imported this and that lib.


Agreed, there's nothing here that couldn't have been done 5 years ago with perl and CPAN. But, I think this post is relevant for two big reasons:

1. Sinatra and Rack are amazing lightweight alternatives (especially for web services) compared to the giantness that is Rails.

2. Rack::Cache is seriously awesome (http://tomayko.com/src/rack-cache/) and deserves more attention...


Rack::Cache is neato for development, but you're better served using Varnish for production -- it acts as a caching reverse proxy to multiple machines, is insanely fast and battle-tested, it can be configured to serve stale content if the backend becomes unreachable, it supports ESI, and more!

And yes, Sinatra & Rack are super cool.


I'm using Sinatra et al for my thesis project, for a Master's in Latin, and minimizing the non-interesting bits of infrastructure helps a lot.

It's interesting to me for the same reason PHP is interesting: it helped me wrap an HTTP interface over an executable in Ada with its own homegrown database, and (sociologically) it's a social commentary on the bits of plumbing we've agreed upon as useful.


The source code is still 55 characters too many for a tweet.


Did you try getting it to fit? Even 'cheating' a bit by renaming the route and make the TTL 1 second I couldn't get it closer than -2 :/

I can get to +1 character left if I just make the route '/:n'! :)


I thought about it, but then I remembered you'd get something like the camping microframework:

     %w[rubygems active_record markaby metaid ostruct].each {|lib| require lib}
 module Camping;C=self;module Models;end;Models::Base=ActiveRecord::Base
 module Helpers;def R c,*args;p=/\(.+?\)/;args.inject(c.urls.detect{|x|x.
 scan(p).size==args.size}.dup){|str,a|str.gsub(p,(a.method(a.class.primary_key
 )[]rescue a).to_s)};end;def / p;File.join(@root,p) end;end;module Controllers
 module Base;include Helpers;attr_accessor :input,:cookies,:headers,:body,
 :status,:root;def method_missing(m,*args,&blk);str=m==:render ? markaview(
 *args,&blk):eval("markaby.#{m}(*args,&blk)");str=markaview(:layout){str
 }rescue nil;r(200,str.to_s);end;def r(s,b,h={});@status=s;@headers.merge!(h)
 @body=b;end;def redirect(c,*args);c=R(c,*args)if c.respond_to?:urls;r(302,'',
 'Location'=>self/c);end;def service(r,e,m,a);@status,@headers,@root=200,{},e[
 'SCRIPT_NAME'];@cookies=C.cookie_parse(e['HTTP_COOKIE']||e['COOKIE']);cook=
 @cookies.marshal_dump.dup;if ("POST"==e['REQUEST_METHOD'])and %r|\Amultipart\
 /form-data.*boundary=\"?([^\";,]+)\"?|n.match(e['CONTENT_TYPE']);return r(500,
 "No multipart/form-data supported.")else;@input=C.qs_parse(e['REQUEST_METHOD'
 ]=="POST"?r.read(e['CONTENT_LENGTH'].to_i):e['QUERY_STRING']);end;@body=
 method(m.downcase).call(*a);@headers["Set-Cookie"]=@cookies.marshal_dump.map{
 |k,v|"#{k}=#{C.escape(v)}; path=/"if v != cook[k]}.compact;self;end;def to_s
 "Status: #{@status}\n#{{'Content-Type'=>'text/html'}.merge(@headers).map{|k,v|
 v.to_a.map{|v2|"#{k}: #{v2}"}}.flatten.join("\n")}\n\n#{@body}";end;def \
 markaby;Class.new(Markaby::Builder){@root=@root;include Views;def tag!(*g,&b)
 [:href,:action].each{|a|(g.last[a]=self./(g.last[a]))rescue 0};super end}.new(
 instance_variables.map{|iv|[iv[1..-1].intern,instance_variable_get(iv)]},{})
 end;def markaview(m,*args,&blk);markaby.instance_eval{Views.instance_method(m
 ).bind(self).call(*args, &blk);self}.to_s;end;end;class R;include Base end
 class NotFound<R;def get(p);r(404,div{h1("#{C} Problem!")+h2("#{p} not found")
 });end end;class ServerError<R;def get(k,m,e);r(500,markaby.div{h1 "#{C} Prob\
 lem!";h2 "#{k}.#{m}";h3 "#{e.class} #{e.message}:";ul{e.backtrace.each{|bt|li(
 bt)}}})end end;class<<self;def R(*urls);Class.new(R){meta_def(:inherited){|c|
 c.meta_def(:urls){urls}}};end;def D(path);constants.each{|c|k=const_get(c)
 return k,$~[1..-1] if (k.urls rescue "/#{c.downcase}").find {|x|path=~/^#{x}\
 \/?$/}};[NotFound,[path]];end end end;class<<self;def escape(s);s.to_s.gsub(
 /([^ a-zA-Z0-9_.-]+)/n){'%'+$1.unpack('H2'*$1.size).join('%').upcase}.tr(' ',
 '+') end;def unescape(s);s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){[$1.
 delete('%')].pack('H*')} end;def qs_parse(qs,d='&;');OpenStruct.new((qs||'').
 split(/[#{d}] */n).inject({}){|hsh,p|k,v=p.split('=',2).map{|v|unescape(v)}
 hsh[k]=v unless v.empty?;hsh}) end;def cookie_parse(s);c=qs_parse(s,';,') end
 def run(r=$stdin,w=$stdout);w<<begin;k,a=Controllers.D "/#{ENV['PATH_INFO']}".
 gsub(%r!/+!,'/');m=ENV['REQUEST_METHOD']||"GET";k.class_eval{include C
 include Controllers::Base;include Models};o=k.new;o.service(r,ENV,m,a);rescue\
 =>e;Controllers::ServerError.new.service(r,ENV,"GET",[k,m,e]);end;end;end
 module Views; include Controllers; include Helpers end;end


That was the interesting thing for me: not that you couldn't do it in a shorter number of characters, but that it was 7 lines of code written for people to read, and incidentally for machines to execute.


There are many hundreds more lines in the background of the sinatra example the OP lists (the 7 lines, that is).


I love Sinatra. I find it very easy to use. I did a little web service that scraps the usage stats off an ISP page with it and Hpricot. I never had so much fun with Ruby!


Just remember, if you're in Ruby working with strings, you want a local variable, because MRI leaks memory otherwise. See http://groups.google.com/group/god-rb/msg/3f5b13d6d4fa6480


This was fixed in p287 right?


Thanks for the hint.


How about we all stop boasting about how many lines we can write something in, and go back to writing quality software? Who gives a fuck how many lines it is, only thing we should care about is if it's good or not.


Uh, novice here, and although I'm sure we all agree that quality of code > lack of lines of code, there's no reason to bash clever programming. Who cares if it's pragmatic -- that's obviously not the motivation.


Because we shouldn't ever have any fun? We shouldn't check out Blake's cool little micro-framework? We shouldn't use our own blogs for things we find interesting?




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: