TeensyMud - 'A ruby mud server.'

Browse repository
back

connection.rb

#
# file:: connection.rb
# author:: Jon A. Lambert
# version:: 2.8.0
# date:: 01/19/2006
#
# This source code copyright (C) 2005, 2006 by Jon A. Lambert
# All rights reserved.
#
# Released under the terms of the TeensyMUD Public License
# See LICENSE file for additional information.
#
$:.unshift "lib" if !$:.include? "lib"
$:.unshift "vendor" if !$:.include? "vendor"

require 'network/session'
require 'network/sockio'
require 'network/lineio'
require 'network/packetio'
require 'network/protocol/protocolstack'

# The connection class maintains a socket connection with a
# reactor and handles all events dispatched by the reactor.
#
class Connection < Session
logger 'DEBUG'
attr_reader :server, :initdone, :pstack, :sockio, :addr, :host
attr_accessor :inbuffer, :outbuffer # filters need to this in charmode

# Create a new connection object
# [+server+] The reactor this connection is associated with.
# [+sock+] The socket for this connection.
# [+returns+] A connection object.
def initialize(server, sock)
super(server, sock)
case @server.service_io
when :lineio
@sockio = LineIO.new(@sock)
when :packetio
@sockio = PacketIO.new(@sock)
else
@sockio = SockIO.new(@sock)
end
@inbuffer = "" # buffer lines waiting to be processed
@outbuffer = "" # buffer lines waiting to be output
if @server.service_filters.include? :telnetfilter
@initdone = false # keeps silent until we're done with negotiations
else
@initdone = true
end
@pstack = ProtocolStack.new(self)
end

# init is called before using the connection.
# [+returns+] true is connection is properly initialized
def init
@host, @addr = @sock.peeraddr.slice(2..3)
@connected = true
@server.register(self)
log.info "(#{self.object_id}) New Connection on '#{@host}(#{@addr})'"
@pstack.filter_call(:init,nil)
true
rescue Exception
log.error "(#{self.object_id}) Connection#init"
log.error $!
false
end

# handle_input is called to order a connection to process any input
# waiting on its socket. Input is parsed into lines based on the
# occurance of the CRLF terminator and pushed into a buffer
# which is a list of lines. The buffer expands dynamically as input
# is processed. Input that has yet to see a CRLF terminator
# is left in the connection's inbuffer.
def handle_input
buf = @sockio.read
raise(EOFError) if !buf || buf.empty?
buf = @pstack.filter_call(:filter_in,buf)
if @server.service_io == :packetio ||
@server.service_type == :client
publish(buf)
else
@inbuffer << buf
if @initdone # Just let buffer fill until we indicate we're done
# negotiating. Set by calling initdone from TelnetFilter
while p = @inbuffer.index("\n")
ln = @inbuffer.slice!(0..p).chop
publish(ln)
end
end
end
rescue EOFError, Errno::ECONNRESET, Errno::ECONNABORTED
@closing = true
publish(:disconnected)
unsubscribe_all
log.info "(#{self.object_id}) Connection '#{@host}(#{@addr})' disconnecting"
log.error $!
rescue Exception
@closing = true
publish(:disconnected)
unsubscribe_all
log.error "(#{self.object_id}) Connection#handle_input disconnecting"
log.error $!
end

# handle_output is called to order a connection to process any output
# waiting on its socket.
def handle_output
@outbuffer = @pstack.filter_call(:filter_out,@outbuffer)
done = @sockio.write(@outbuffer)
@outbuffer = ""
if done
@write_blocked = false
else
@write_blocked = true
end
rescue EOFError, Errno::ECONNABORTED, Errno::ECONNRESET
@closing = true
publish(:disconnected)
unsubscribe_all
log.info "(#{self.object_id}) Connection '#{@host}(#{@addr})' disconnecting"
rescue Exception
@closing = true
publish(:disconnected)
unsubscribe_all
log.error "(#{self.object_id}) Connection#handle_output disconnecting"
log.error $!
end

# handle_close is called to when an close event occurs for this session.
def handle_close
@connected = false
publish(:disconnected)
unsubscribe_all
log.info "(#{self.object_id}) Connection '#{@host}(#{@addr})' closing"
@server.unregister(self)
# @sock.shutdown # odd errors thrown with this
@sock.close
rescue Exception
log.error "(#{self.object_id}) Connection#handle_close closing"
log.error $!
end

# handle_oob is called when an out of band data event occurs for this
# session.
def handle_oob
buf = @sockio.read_urgent
log.debug "(#{self.object_id}) Connection urgent data received - '#{buf[0]}'"
@pstack.set(:urgent, true)
buf = @pstack.filter_call(:filter_in,buf)
rescue Exception
log.error "(#{self.object_id}) Connection#handle_oob"
log.error $!
end

# This is called from TelnetFilter when we are done with negotiations.
# The event :initdone wakens observer to begin user activity
def set_initdone
@initdone = true
publish(:initdone)
end


# Update will be called when the object the connection is observing
# wants to notify us of a change in state or new message.
# When a new connection is accepted in acceptor that connection
# is passed to the observer of the acceptor which allows the client
# to attach an observer to the connection and make the connection
# an observer of that object. We need to keep both sides interest
# in each other limited to a narrow but flexible interface to
# prevent tight coupling.
#
# This supports the following:
# [:quit] - This symbol message from the client is a request to
# close the Connection. It is handled here.
# [String] - A String is assumed to be output and placed in our
# @outbuffer.
def update(msg)
case msg
when :quit
handle_output
@closing = true
when :reconnecting
unsubscribe_all
log.info "(#{self.object_id}) Connection '#{@host}(#{@addr})' closing for reconnection"
@server.unregister(self)
# @sock.shutdown # odd errors thrown with this
@sock.close
when String
sendmsg(msg)
else
log.error "(#{self.object_id}) Connection#update - unknown message '#{@msg.inspect}'"
end
rescue
# We squash and print out all exceptions here. There is no reason to
# throw these back at out subscribers.
log.error $!
end

# [+attrib+] - A Symbol not handled here is assumed to be a query and
# its handling is delegated to the ProtocolStack, the result
# of which is a pair immediately sent back to as a message
# to the client.
#
# <pre>
# client -> us
# :echo
# us -> ProtocolStack
# query(:echo)
# ProtocolStack -> us
# [:echo, true]
# us -> client
# [:echo, true]
# </pre>
#
def query(attrib)
@pstack.query(attrib)
end

# [+attrib,val+] - An Array not handled here is assumed to be a set command and
# its handling is delegated to the ProtocolStack.
#
# <pre>
# client -> us
# [:color, true]
# us -> ProtocolStack
# set(:color, true)
# </pre>
#
def set(attrib, val)
@pstack.set(attrib, val)
end


# sendmsg places a message on the Connection's output buffer.
# [+msg+] The message, a reference to a buffer
def sendmsg(msg)
@outbuffer << msg
@write_blocked = true # change status to write_blocked
end

end