lib/reel/connection.rb in reel-0.2.0 vs lib/reel/connection.rb in reel-0.3.0.pre

- old
+ new

@@ -1,10 +1,19 @@ module Reel # A connection to the HTTP server class Connection + include HTTPVersionsMixin + include ConnectionMixin + class StateError < RuntimeError; end # wrong state for a given operation + CONNECTION = 'Connection'.freeze + TRANSFER_ENCODING = 'Transfer-Encoding'.freeze + KEEP_ALIVE = 'Keep-Alive'.freeze + CLOSE = 'close'.freeze + CHUNKED = 'chunked'.freeze + attr_reader :socket, :parser # Attempt to read this much data BUFFER_SIZE = 4096 @@ -14,11 +23,10 @@ @keepalive = true @parser = Request::Parser.new reset_request @response_state = :header - @body_remaining = nil end # Is the connection still active? def alive?; @keepalive; end @@ -29,22 +37,10 @@ def detach @attached = false self end - # Obtain the IP address of the remote connection - def remote_ip - @socket.peeraddr(false)[3] - end - alias_method :remote_addr, :remote_ip - - # Obtain the hostname of the remote connection - def remote_host - # NOTE: Celluloid::IO does not yet support non-blocking reverse DNS - @socket.peeraddr(true)[2] - end - # Reset the current request state def reset_request(state = :header) @request_state = state @header_buffer = "" # Buffer headers in case of an upgrade request @parser.reset @@ -56,15 +52,13 @@ req = Request.read(self) case req when Request @request_state = :body - @keepalive = false if req['Connection'] == 'close' || req.version == "1.0" - @body_remaining = Integer(req['Content-Length']) if req['Content-Length'] + @keepalive = false if req[CONNECTION] == CLOSE || req.version == HTTP_VERSION_1_0 when WebSocket @request_state = @response_state = :websocket - @body_remaining = nil @socket = nil else raise "unexpected request type: #{req.class}" end req @@ -77,23 +71,39 @@ # Read a chunk from the request def readpartial(size = BUFFER_SIZE) raise StateError, "can't read in the `#{@request_state}' state" unless @request_state == :body - if @body_remaining and @body_remaining > 0 + chunk = @parser.chunk + unless chunk || @parser.finished? + @parser << @socket.readpartial(size) chunk = @parser.chunk - unless chunk - @parser << @socket.readpartial(size) - chunk = @parser.chunk - return unless chunk - end + end - @body_remaining -= chunk.length - @body_remaining = nil if @body_remaining < 1 + chunk + end - chunk + # read length bytes from request body + 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? ? 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 # Send a response back to the client # Response can be a symbol indicating the status code or a Reel::Response def respond(response, headers_or_body = {}, body = nil) @@ -105,13 +115,13 @@ headers = {} body = headers_or_body end if @keepalive - headers['Connection'] = 'Keep-Alive' + headers[CONNECTION] = KEEP_ALIVE else - headers['Connection'] = 'close' + headers[CONNECTION] = CLOSE end case response when Symbol response = Response.new(response, headers, body) @@ -120,11 +130,11 @@ end response.render(@socket) # Enable streaming mode - if response.headers['Transfer-Encoding'] == "chunked" and response.body.nil? + if response.headers[TRANSFER_ENCODING] == CHUNKED and response.body.nil? @response_state = :chunked_body end rescue IOError, Errno::ECONNRESET, Errno::EPIPE # The client disconnected early @keepalive = false @@ -138,26 +148,25 @@ end # Write body chunks directly to the connection def write(chunk) raise StateError, "not in chunked body mode" unless @response_state == :chunked_body - chunk_header = chunk.bytesize.to_s(16) + Response::CRLF - @socket << chunk_header - @socket << chunk - @socket << Response::CRLF + chunk_header = chunk.bytesize.to_s(16) + @socket << chunk_header + Response::CRLF + @socket << chunk + Response::CRLF end alias_method :<<, :write # Finish the response and reset the response state to header def finish_response raise StateError, "not in body state" if @response_state != :chunked_body - @socket << "0" << Response::CRLF * 2 + @socket << "0#{Response::CRLF * 2}" @response_state = :header end # Close the connection def close @keepalive = false - @socket.close + @socket.close unless @socket.closed? end end end