module Kirk class Server class Handler < Jetty::AbstractHandler java_import 'java.util.zip.GZIPInputStream' java_import 'java.util.zip.InflaterInputStream' # Trigger the autoload so that the first access to the class # does not happen in a thread. InputStream # Required environment variable keys REQUEST_METHOD = 'REQUEST_METHOD'.freeze SCRIPT_NAME = 'SCRIPT_NAME'.freeze PATH_INFO = 'PATH_INFO'.freeze QUERY_STRING = 'QUERY_STRING'.freeze SERVER_NAME = 'SERVER_NAME'.freeze SERVER_PORT = 'SERVER_PORT'.freeze LOCAL_PORT = 'LOCAL_PORT'.freeze CONTENT_TYPE = 'CONTENT_TYPE'.freeze CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze REQUEST_URI = 'REQUEST_URI'.freeze REMOTE_HOST = 'REMOTE_HOST'.freeze REMOTE_ADDR = 'REMOTE_ADDR'.freeze REMOTE_USER = 'REMOTE_USER'.freeze # Rack specific variable keys RACK_VERSION = 'rack.version'.freeze RACK_URL_SCHEME = 'rack.url_scheme'.freeze RACK_INPUT = 'rack.input'.freeze RACK_ERRORS = 'rack.errors'.freeze RACK_MULTITHREAD = 'rack.multithread'.freeze RACK_MULTIPROCESS = 'rack.multiprocess'.freeze RACK_RUN_ONCE = 'rack.run_once'.freeze HTTP_PREFIX = 'HTTP_'.freeze # Rack response header names CONTENT_TYPE_RESP = 'Content-Type' CONTENT_LENGTH_RESP = 'Content-Length' # Kirk information SERVER = "#{NAME} #{VERSION}".freeze SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze DEFAULT_RACK_ENV = { SERVER_SOFTWARE => SERVER, # Rack stuff RACK_ERRORS => STDERR, RACK_MULTITHREAD => true, RACK_MULTIPROCESS => false, RACK_RUN_ONCE => false, } CONTENT_LENGTH_TYPE_REGEXP = /^Content-(?:Type|Length)$/i def self.new(app) inst = super() inst.app = app inst end attr_accessor :app def handle(target, base_request, request, response) begin env = DEFAULT_RACK_ENV.merge( SCRIPT_NAME => "", PATH_INFO => request.get_path_info, REQUEST_URI => request.getRequestURI, REQUEST_METHOD => request.get_method || "GET", RACK_URL_SCHEME => request.get_scheme || "http", QUERY_STRING => request.get_query_string || "", SERVER_NAME => request.get_server_name || "", REMOTE_HOST => request.get_remote_host || "", REMOTE_ADDR => request.get_remote_addr || "", REMOTE_USER => request.get_remote_user || "", SERVER_PORT => request.get_server_port.to_s, LOCAL_PORT => request.get_local_port.to_s, RACK_VERSION => ::Rack::VERSION) # Process the content length if (content_length = request.get_content_length) >= 0 env[CONTENT_LENGTH] = content_length.to_s else env[CONTENT_LENGTH] = "0" end if (content_type = request.get_content_type) env[CONTENT_TYPE] = content_type unless content_type == '' end request.get_header_names.each do |header| next if header =~ CONTENT_LENGTH_TYPE_REGEXP value = request.get_header(header) header.tr! '-', '_' header.upcase! header = "#{HTTP_PREFIX}#{header}" env[header] = value unless env.key?(header) || value == '' end input = request.get_input_stream case env['HTTP_CONTENT_ENCODING'] when 'deflate' then input = InflaterInputStream.new(input) when 'gzip' then input = GZIPInputStream.new(input) end input = InputStream.new(input) env[RACK_INPUT] = input # Dispatch the request status, headers, body = @app.call(env) response.set_status(status.to_i) headers.each do |header, value| case header when CONTENT_TYPE_RESP response.set_content_type(value) when CONTENT_LENGTH_RESP response.set_content_length(value.to_i) else value.split("\n").each do |v| response.add_header(header, v) end end end buffer = response.get_output_stream body.each do |s| buffer.write(s.to_java_bytes) end body.close if body.respond_to?(:close) ensure input.recycle if input.respond_to?(:recycle) request.set_handled(true) end end end end end