require 'forwardable' module Reel class Request extend Forwardable include RequestMixin def_delegators :@connection, :<<, :write, :respond, :finish_response attr_reader :body # request_info is a RequestInfo object including the headers and # the url, method and http version. # # Access it through the RequestMixin methods. def initialize(request_info, connection = nil) @request_info = request_info @connection = connection @finished = false @buffer = "" @body = RequestBody.new(self) @finished_read = false @websocket = nil end # Returns true if request fully finished reading def finished_reading?; @finished_read; end # When HTTP Parser marks the message parsing as complete, this will be set. def finish_reading! raise StateError, "already finished" if @finished_read @finished_read = true end # Fill the request buffer with data as it becomes available def fill_buffer(chunk) @buffer << chunk end # Read a number of bytes, looping until they are available or until # readpartial returns nil, indicating there are no more bytes to read def read(length = nil, buffer = nil) raise ArgumentError, "negative length #{length} given" if length && length < 0 return '' if length == 0 res = buffer.nil? ? '' : buffer.clear chunk_size = length.nil? ? @connection.buffer_size : length begin while chunk_size > 0 chunk = readpartial(chunk_size) break unless chunk res << chunk chunk_size = length - res.length unless length.nil? end rescue EOFError end return length && res.length == 0 ? nil : res end # Read a string up to the given number of bytes, blocking until some # data is available but returning immediately if some data is available def readpartial(length = nil) if length.nil? && @buffer.length > 0 slice = @buffer @buffer = "" else unless finished_reading? || (length && length <= @buffer.length) @connection.readpartial(length ? length - @buffer.length : Connection::BUFFER_SIZE) end if length slice = @buffer.slice!(0, length) else slice = @buffer @buffer = "" end end slice && slice.length == 0 ? nil : slice end # Can the current request be upgraded to a WebSocket? def websocket?; @request_info.websocket_request?; end # Return a Reel::WebSocket for this request, hijacking the socket from # the underlying connection def websocket @websocket ||= begin raise StateError, "can't upgrade this request to a websocket" unless websocket? WebSocket.new(@request_info, @connection.hijack_socket) end end end end