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