lib/qeweney/rack.rb



# frozen_string_literal: true

module Qeweney
  class RackRequestAdapter
    def initialize(env)
      @env = env
      @response_headers = {}
      @response_body = []
    end

    def request_headers
      request_http_headers.merge(
        ':scheme' => @env['rack.url_scheme'],
        ':method' => @env['REQUEST_METHOD'].downcase,
        ':path' => request_path_from_env
      )
    end

    def request_path_from_env
      path = File.join(@env['SCRIPT_NAME'], @env['PATH_INFO'])
      path = path + "?#{@env['QUERY_STRING']}" if @env['QUERY_STRING']
      path
    end

    def request_http_headers
      headers = {}
      @env.each do |k, v|
        next unless k =~ /^HTTP_(.+)$/

        headers[Regexp.last_match(1).downcase.gsub('_', '-')] = v
      end
      headers
    end

    def respond(req, body, headers)
      @response_body << body
      @response_headers = headers
    end

    def send_headers(req, headers, empty_response: nil)
      @response_headers = headers
    end

    def send_chunk(req, body, done: false)
      @response_body << body
    end

    def finish(req)
    end

    def rack_response
      @status = @response_headers.delete(':status')
      [
        @status,
        @response_headers,
        @response_body
      ]
    end
  end

  def self.rack(&block)
    proc do |env|
      adapter = RackRequestAdapter.new(env)
      req = Request.new(adapter.request_headers, adapter)
      block.(req)
      adapter.rack_response
    end
  end

  # TODO: integrate in env
  # Implements a rack input stream:
  # https://www.rubydoc.info/github/rack/rack/master/file/SPEC#label-The+Input+Stream
  class InputStream
    def initialize(request)
      @request = request
    end

    def gets; end

    def read(length = nil, outbuf = nil); end

    def each(&block)
      @request.each_chunk(&block)
    end

    def rewind; end
  end

  def self.rack_env_from_request(request)
    Hash.new do |h, k|
      h[k] = rack_env_value_from_request(request, k)
    end
  end

  # TODO: improve conformance to rack spec
  # TODO: set values like port, scheme etc from actual connection
  RACK_ENV = {
    'SCRIPT_NAME'                    => '',
    'rack.version'                   => [1, 3],
    'SERVER_PORT'                    => '80', # ?
    'rack.url_scheme'                => 'http', # ?
    'rack.errors'                    => STDERR, # ?
    'rack.multithread'               => false,
    'rack.run_once'                  => false,
    'rack.hijack?'                   => false,
    'rack.hijack'                    => nil,
    'rack.hijack_io'                 => nil,
    'rack.session'                   => nil,
    'rack.logger'                    => nil,
    'rack.multipart.buffer_size'     => nil,
    'rack.multipar.tempfile_factory' => nil
  }

  HTTP_HEADER_RE = /^HTTP_(.+)$/.freeze

  def self.rack_env_value_from_request(request, key)
    case key
    when 'REQUEST_METHOD' then request.method.upcase
    when 'PATH_INFO'      then request.path
    when 'QUERY_STRING'   then request.query_string || ''
    when 'SERVER_NAME'    then request.headers['host']
    when 'rack.input'     then InputStream.new(request)
    when HTTP_HEADER_RE   then request.headers[$1.gsub('_', '-').downcase]
    else                       RACK_ENV[key]
    end
  end
end