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