Skip to content

Instantly share code, notes, and snippets.

@melborne
Created August 7, 2012 11:42
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save melborne/3284736 to your computer and use it in GitHub Desktop.
Save melborne/3284736 to your computer and use it in GitHub Desktop.
Lack is a minified Rack just for study.
Lack is a minified Rack just for study.
class Lack::Builder
def self.parse_file(config) #8
cfgfile = ::File.read(config)
app = eval "Lack::Builder.new {\n" + cfgfile + "\n}.to_app", TOPLEVEL_BINDING, config
return app
end
def initialize(&block) #9
@use = []
instance_eval(&block)
end
def use(middleware, *args, &block) #10
@use << proc { |app| middleware.new(app, *args, &block) }
end
def run(app) #11
@run = app
end
def to_app #12
app = @run
@use.reverse.inject(app) { |a,e| e[a] }
end
end
module Lack::Handler
def self.default(options = {}) #17
Lack::Handler::Thin
rescue LoadError
Lack::Handler::WEBrick
end
autoload :WEBrick, "lack/handler/webrick"
autoload :Thin, "lack/handler/thin"
end
$:.unshift File.expand_path(File.join(*%w(.. lib)), File.dirname(__FILE__))
module Lack
autoload :Builder, "lack/builder"
autoload :Handler, "lack/handler"
autoload :Server, "lack/server"
end
#!/usr/bin/env ruby
require_relative "../lib/lack"
Lack::Server.start #0
class Lack::Server
def self.start #1
new.start
end
def start #2
server.run wrapped_app, options
end
def options #3
@options ||= parse_options(ARGV)
end
def parse_options(args) #4
default_options
end
def default_options #5
{
:environment => ENV['RACK_ENV'] || "development",
:pid => nil,
:Port => 9292,
:Host => "0.0.0.0",
:AccessLog => [],
:config => "config.ru"
}
end
def wrapped_app #6
@wrapped_app ||= build_app app
end
def app #7
@app = Lack::Builder.parse_file(self.options[:config])
end
def build_app(app) #13
middleware[options[:environment]].reverse.inject(app) { |a, mid| mid.new(a) }
end
def middleware #14
self.class.middleware
end
def self.middleware
@middleware ||= begin
m = Hash.new {|h,k| h[k] = []}
# m["deployment"].concat [
# [Rack::ContentLength],
# [Rack::Chunked],
# logging_middleware
# ]
# m["development"].concat m["deployment"] + [[Rack::ShowExceptions], [Rack::Lint]]
m
end
end
def server #16
@_server ||= Lack::Handler.default(options)
end
end
require "thin"
module Lack::Handler
class Thin
def self.run(app, options={})
server = ::Thin::Server.new(options[:Host] || '0.0.0.0',
options[:Port] || 8080,
app)
yield server if block_given?
server.start
end
def self.valid_options
{
"Host=HOST" => "Hostname to listen on (default: localhost)",
"Port=PORT" => "Port to listen on (default: 8080)",
}
end
end
end
require 'webrick'
require 'stringio'
module Lack::Handler
class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
def self.run(app, options={})
options[:BindAddress] = options.delete(:Host) if options[:Host]
@server = ::WEBrick::HTTPServer.new(options)
@server.mount "/", Lack::Handler::WEBrick, app
yield @server if block_given?
@server.start
end
def self.valid_options
{
"Host=HOST" => "Hostname to listen on (default: localhost)",
"Port=PORT" => "Port to listen on (default: 8080)",
}
end
def self.shutdown
@server.shutdown
@server = nil
end
def initialize(server, app)
super server
@app = app
end
def service(req, res)
env = req.meta_vars
env.delete_if { |k, v| v.nil? }
rack_input = StringIO.new(req.body.to_s)
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
env.update({#"rack.version" => Lack::VERSION,
"rack.input" => rack_input,
"rack.errors" => $stderr,
"rack.multithread" => true,
"rack.multiprocess" => false,
"rack.run_once" => false,
"rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
})
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
env["QUERY_STRING"] ||= ""
unless env["PATH_INFO"] == ""
path, n = req.request_uri.path, env["SCRIPT_NAME"].length
env["PATH_INFO"] = path[n, path.length-n]
end
env["REQUEST_PATH"] ||= [env["SCRIPT_NAME"], env["PATH_INFO"]].join
status, headers, body = @app.call(env)
begin
res.status = status.to_i
headers.each { |k, vs|
if k.downcase == "set-cookie"
res.cookies.concat vs.split("\n")
else
# Since WEBrick won't accept repeated headers,
# merge the values per RFC 1945 section 4.2.
res[k] = vs.split("\n").join(", ")
end
}
body.each { |part|
res.body << part
}
ensure
body.close if body.respond_to? :close
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment