class Rack::Handler::UnixRack
Public Class Methods
log(response_code, message='-', method='-', url='-', options={})
click to toggle source
# File lib/unixrack.rb, line 225 def self.log(response_code, message='-', method='-', url='-', options={}) return #TODO: Ignore the early return #TODO: I'm going to make this a config debug option, use your other middleware logging for now ip = @@client_ip || '-' now = Time.now duration = ((now.to_f - @@start_time.to_f) * 1000).to_i / 1000.0 puts "#{now.strftime('%Y-%m-%dT%H:%M:%S%z')},#{@@pid},#{ip},#{sprintf("%0.03f", duration)},#{response_code},\"#{message}\",#{method},\"#{url}\"" $stdout.flush end
run(app, options={})
click to toggle source
# File lib/unixrack.rb, line 303 def self.run(app, options={}) require 'socket' port = options[:Port] || 8080 host = options[:Hostname] || 'localhost' listen = options[:Host] || '127.0.0.1' allowed_ips = options[:allowed_ips] || [] server = TCPServer.new(listen, port) @@pid = $$ trap(:CHLD) do begin while true pid, status = Process.waitpid2(-1, Process::WNOHANG) if pid == nil break end @@start_time = Time.now log(-(status.exitstatus), 'child exited non-zero') if status.exitstatus != 0 #puts "#{pid}: exited - status #{status}" #$stdout.flush end rescue Errno::ECHILD end end trap(:TERM) { log(0, "Listener received TERM. Exiting."); exit! 0 } trap(:INT) { log(0, "Listener received INT. Exiting."); exit! 0 } if not @@chdir.empty? Dir.chdir @@chdir end while true begin conn = server.accept rescue Errno::EAGAIN, Errno::ECONNABORTED p "Connection interrupted on accept" $stdout.flush next rescue p "DRU" p $! p $!.backtrace $stdout.flush exit end pid = fork if pid != nil # We are in parent conn.close else # We are in child @@pid = $$ server.close @@start_time = Time.now trap(:ALRM) { log(0, "Child received ALARM during read_headers. Exiting."); exit! 2 } trap(:TERM) { log(0, "Child received TERM. Exiting."); exit! 0 } ::UnixRack::Alarm.alarm(5) # if no command received in 5 secs sock = ::UnixRack::Socket.new(conn) @@client_ip = sock.peeraddr.last if not sock.read_headers() send_error_response!(sock, 400, "Bad Request", "-", "-") end trap(:ALRM) { log(0, "Child received ALARM during response. Exiting."); exit! 2 } ::UnixRack::Alarm.alarm(120) # if command not handled in 120 seconds if not allowed_ips.empty? if not (allowed_ips.any? { |e| @@client_ip.include? e }) send_error_response!(sock, 403, "Forbidden", sock.hdr_method[0], sock.hdr_method[1]) end end if ['GET', 'POST', 'OPTIONS'].include?(sock.hdr_method[0]) env = {} if sock.hdr_method[0] == 'GET' content = StringIO.new("") content.set_encoding(Encoding::ASCII_8BIT) if content.respond_to?(:set_encoding) elsif sock.hdr_method[0] == 'POST' if not sock.headers.include?('Content-Length') send_error_response!(sock, 400, "Bad Request no content-length", sock.hdr_method[0], sock.hdr_method[1]) end if not sock.headers.include?('Content-Type') send_error_response!(sock, 400, "Bad Request no content-type", sock.hdr_method[0], sock.hdr_method[1]) end env["CONTENT_LENGTH"] = sock.headers['Content-Length'] env["CONTENT_TYPE"] = sock.headers['Content-Type'] # F the 1.1 if sock.headers.include?('Expect') if sock.headers['Expect'] == '100-continue' ::UnixRack::Socket.write_buff(sock.sock, "HTTP/1.1 100 Continue\r\n\r\n") else send_error_response!(sock, 417, "Expectation Failed", sock.hdr_method[0], sock.hdr_method[1]) end end # It is required that we read all of the content prior to responding content = sock.read_content content.set_encoding(Encoding::ASCII_8BIT) if content.respond_to?(:set_encoding) if content == nil send_error_response!(sock, 400, "Bad Request not enough content", sock.hdr_method[0], sock.hdr_method[1]) end elsif sock.hdr_method[0] == 'OPTIONS' content = StringIO.new("") content.set_encoding(Encoding::ASCII_8BIT) if content.respond_to?(:set_encoding) end app = ContentLength.new(app) env["REQUEST_METHOD"] = sock.hdr_method[0] uri_parts = sock.hdr_method[1].split("?", 2) if uri_parts.length != 2 uri_parts << "" end # If somebody wants to send me the big absoluteURI... # fine... what is wasting a few more cycles to chop it off if uri_parts[0].index('http://') == 0 uri_parts[0] = uri_parts[0].sub(/http:\/\/[^\/]+/, '') end env["SCRIPT_NAME"] = '' env["PATH_INFO"] = uri_parts[0] env["QUERY_STRING"] = uri_parts[1] env["SERVER_NAME"] = host env["SERVER_PORT"] = port env["REMOTE_ADDR"] = @@client_ip if sock.headers['User-Agent'] env["HTTP_USER_AGENT"] = sock.headers['User-Agent'] end if sock.headers['Cookie'] env["HTTP_COOKIE"] = sock.headers['Cookie'] end if sock.headers['Authorization'] env["HTTP_AUTHORIZATION"] = sock.headers['Authorization'] end if sock.headers['Range'] env["HTTP_RANGE"] = sock.headers['Range'] end if sock.headers['X-Real-IP'] env["HTTP_X_REAL_IP"] = sock.headers['Real-IP'] end if sock.headers['X-Forwarded-For'] env["HTTP_X_FORWARDED_FOR"] = sock.headers['X-Forwarded-For'] end if sock.headers['X-Forwarded-Proto'] env["HTTP_X_FORWARDED_PROTO"] = sock.headers['X-Forwarded-Proto'] end if sock.headers['Host'] env["HTTP_HOST"] = sock.headers['Host'] end if sock.headers['Origin'] env["Origin"] = sock.headers['Origin'] end env["HTTP_VERSION"] = "HTTP/1.1" if sock.headers['If-Modified-Since'] env["HTTP_IF_MODIFIED_SINCE"] = sock.headers['If-Modified-Since'] end env.update({"rack.version" => [1, 1], "rack.input" => content, "rack.errors" => $stderr, "rack.multithread" => false, "rack.multiprocess" => true, "rack.run_once" => true, "rack.url_scheme" => "http" }) # Reminder of how to do this for the future the '::' I always forget #::File.open('/tmp/dru', 'a') do # |f2| # f2.syswrite(env.inspect + "\n") #end status, headers, body = app.call(env) send_response!(sock, status, sock.hdr_method[0], sock.hdr_method[1], headers, body) end send_error_response!(sock, 500, "Server Error", sock.hdr_method[0], sock.hdr_method[1]) end end end
send_error_response!(sock, num, txt, method, url)
click to toggle source
# File lib/unixrack.rb, line 236 def self.send_error_response!(sock, num, txt, method, url) log(num, txt, method, url) bod = [ "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">", "<html><head>", "<title>#{num} #{txt}</title>", "</head><body>", "<h1>#{num} - #{txt}</h1>", "<hr>", "</body></html>" ] bod_txt = bod.join("\r\n") hdr = [ "HTTP/1.1 #{num} #{txt}", "Date: #{@@start_time.httpdate}", "Server: UnixRack", "Content-Length: #{bod_txt.length}", "Connection: close", "Content-Type: text/html; charset=iso-8859-1" ] hdr_txt = hdr.join("\r\n") res = hdr_txt + "\r\n\r\n" + bod_txt ::UnixRack::Socket.write_buff(sock.sock, res) ::UnixRack::Socket.close(sock.sock) exit! 0 end
send_response!(sock, status, method, url, headers, body)
click to toggle source
# File lib/unixrack.rb, line 269 def self.send_response!(sock, status, method, url, headers, body) out = [] msg = Rack::Utils::HTTP_STATUS_CODES[status.to_i] log(status, msg, method, url) hdr_ary = ["HTTP/1.1 #{status} #{msg}"] headers['Connection'] ||= 'close' headers.each do |k, vs| vs.split("\n").each { |v| hdr_ary << ["#{k}: #{v}"] } end hdr = hdr_ary.join("\r\n") out = [hdr, "\r\n\r\n"] body.each { |part| out << part.to_s } out_buff = out.join("") ::UnixRack::Socket.write_buff(sock.sock, out_buff) ::UnixRack::Socket.close(sock.sock) # Conforming to SPEC - I was noticing that Sinatra logging wasn't working body.close if body.respond_to? :close exit! 0 end
set_chdir(dir)
click to toggle source
Set this in config.ru when in daemon mode Why? It appears that the behaviour of most servers is to expect to be in a certain dir when run Or, another way, rackup daemon mode is a bit strict and does the old-school chdir to '/' as a daemon. the fact is people probably don't use rackup often
# File lib/unixrack.rb, line 221 def self.set_chdir(dir) @@chdir = dir end