class Toycol::Server

Constants

BACKLOG
CHUNK_SIZE

Public Class Methods

new(app, **options) click to toggle source
# File lib/toycol/server.rb, line 14
def initialize(app, **options)
  @app  = app
  @path = options[:Path]
  @port = options[:Port]
  @env  = default_env
  @returned_status  = nil
  @returned_headers = nil
  @returned_body    = nil
end
run(app, **options) click to toggle source
# File lib/toycol/server.rb, line 9
def run(app, **options)
  new(app, **options).run
end

Public Instance Methods

run() click to toggle source
# File lib/toycol/server.rb, line 24
def run
  verify_file_path!
  server = UNIXServer.new @path
  server.listen BACKLOG

  loop do
    trap(:INT) { exit }

    socket = server.accept

    request_message = []
    request_message << socket.readpartial(CHUNK_SIZE) until socket.eof?
    request_message = request_message.join
    assign_parsed_attributes!(request_message)

    @returned_status, @returned_headers, @returned_body = @app.call(@env)

    socket.puts response_message
    socket.close_write
    socket.close
  end
end

Private Instance Methods

assign_parsed_attributes!(request_message) click to toggle source
# File lib/toycol/server.rb, line 104
def assign_parsed_attributes!(request_message)
  request_line, *request_headers, request_body = request_message.split("\r\n").reject(&:empty?)
  request_method, request_path, = request_line.split
  request_path, query_string    = request_path.split("?")

  @env[REQUEST_METHOD] = request_method
  @env[PATH_INFO]      = request_path
  @env[QUERY_STRING]   = query_string || ""
  @env[CONTENT_LENGTH]

  request_headers.each do |request_header|
    k, v = request_header.split(":").map(&:strip)
    @env[k.tr("-", "_").upcase.to_s] = v
  end

  @env[RACK_INPUT] = stringio(request_body)
end
default_env() click to toggle source
# File lib/toycol/server.rb, line 49
def default_env
  {
    PATH_INFO         => "",
    QUERY_STRING      => "",
    REQUEST_METHOD    => "",
    SERVER_NAME       => "toycol_server",
    SERVER_PORT       => @port.to_s,
    CONTENT_LENGTH    => "0",
    RACK_VERSION      => Rack::VERSION,
    RACK_INPUT        => stringio(""),
    RACK_ERRORS       => $stderr,
    RACK_MULTITHREAD  => false,
    RACK_MULTIPROCESS => false,
    RACK_RUN_ONCE     => false,
    RACK_URL_SCHEME   => "http"
  }
end
response_body() click to toggle source
# File lib/toycol/server.rb, line 81
def response_body
  res = []
  @returned_body.each { |body| res << body }
  res.join
end
response_headers() click to toggle source
# File lib/toycol/server.rb, line 75
def response_headers
  @returned_headers["Content-Length"] = response_body.size unless @returned_headers["Content-Length"]

  @returned_headers.map { |k, v| "#{k}: #{v}" }.join("\r\n") + "\r\n"
end
response_message() click to toggle source
# File lib/toycol/server.rb, line 67
def response_message
  "#{response_status_code}#{response_headers}\r\n#{response_body}"
end
response_status_code() click to toggle source
# File lib/toycol/server.rb, line 71
def response_status_code
  "HTTP/1.1 #{@returned_status} #{DEFAULT_HTTP_STATUS_CODES[@returned_status.to_i] || "CUSTOM"}\r\n"
end
stringio(body = "") click to toggle source
# File lib/toycol/server.rb, line 87
def stringio(body = "")
  StringIO.new(body).set_encoding("ASCII-8BIT")
end
verify_file_path!() click to toggle source
# File lib/toycol/server.rb, line 91
def verify_file_path!
  return unless File.exist? @path

  begin
    bound_file = UNIXSocket.new @path
  rescue SystemCallError, IOError
    File.unlink @path
  else
    bound_file.close
    raise "[Toycol] Address already in use: #{@path}"
  end
end