class Tubesock

Easily interact with WebSocket connections over Rack. TODO: Example with pure Rack

Constants

HijackNotAvailable
VERSION

Public Class Methods

hijack(env) click to toggle source
# File lib/tubesock.rb, line 24
def self.hijack(env)
  if env['rack.hijack']
    env['rack.hijack'].call
    socket = env['rack.hijack_io']

    handshake = WebSocket::Handshake::Server.new
    handshake.from_rack env

    socket.write handshake.to_s

    self.new socket, handshake.version
  else
    raise Tubesock::HijackNotAvailable
  end
end
new(socket, version) click to toggle source
# File lib/tubesock.rb, line 10
def initialize(socket, version)
  @socket     = socket
  @version    = version

  @open_handlers    = []
  @message_handlers = []
  @close_handlers   = []
  @error_handlers   = []

  @close_on_error = true

  @active = true
end

Public Instance Methods

close() click to toggle source
# File lib/tubesock.rb, line 93
def close
  return unless @active

  @close_handlers.each(&:call)
  close!

  @active = false
end
close!() click to toggle source
# File lib/tubesock.rb, line 102
def close!
  if @socket.respond_to?(:closed?)
    @socket.close unless @socket.closed?
  else
    @socket.close
  end
end
closed?() click to toggle source
# File lib/tubesock.rb, line 110
def closed?
  @socket.closed?
end
keepalive() click to toggle source
# File lib/tubesock.rb, line 114
def keepalive
  thread = Thread.new do
    Thread.current.abort_on_exception = true
    loop do
      sleep 5
      send_data nil, :ping
    end
  end

  onclose do
    thread.kill
  end
end
listen() click to toggle source
# File lib/tubesock.rb, line 71
def listen
  keepalive
  Thread.new do
    Thread.current.abort_on_exception = true
    begin
      @open_handlers.each(&:call)
      each_frame do |data|
        @message_handlers.each do |h|
          begin
            h.call(data)
          rescue => e
            @error_handlers.each{|eh| eh.call(e,data)}
            close if @close_on_error
          end
        end
      end
    ensure
      close
    end
  end
end
onclose(&block) click to toggle source
# File lib/tubesock.rb, line 63
def onclose(&block)
  @close_handlers << block
end
onerror(&block) click to toggle source
# File lib/tubesock.rb, line 67
def onerror(&block)
  @error_handlers << block
end
onmessage(&block) click to toggle source
# File lib/tubesock.rb, line 59
def onmessage(&block)
  @message_handlers << block
end
onopen(&block) click to toggle source
# File lib/tubesock.rb, line 55
def onopen(&block)
  @open_handlers << block
end
prevent_close_on_error() click to toggle source
# File lib/tubesock.rb, line 40
def prevent_close_on_error
  @close_on_error = false
end
send_data(data, type = :text) click to toggle source
# File lib/tubesock.rb, line 44
def send_data data, type = :text
  frame = WebSocket::Frame::Outgoing::Server.new(
    version: @version,
    data: data,
    type: type
  )
  @socket.write frame.to_s
rescue IOError, Errno::EPIPE, Errno::ETIMEDOUT
  close
end

Private Instance Methods

each_frame() { |data| ... } click to toggle source
# File lib/tubesock.rb, line 129
def each_frame
  framebuffer = WebSocket::Frame::Incoming::Server.new(version: @version)
  while IO.select([@socket])
    if @socket.respond_to?(:recvfrom)
      data, _addrinfo = @socket.recvfrom(2000)
    else
      data, _addrinfo = @socket.readpartial(2000), @socket.peeraddr
    end
    break if data.empty?
    framebuffer << data
    while frame = framebuffer.next
      case frame.type
      when :close
        return
      when :text, :binary
        yield frame.data
      when :ping
        # According to https://tools.ietf.org/html/rfc6455#section-5.5.3:
        #   A Pong frame sent in response to a Ping frame must have identical "Application data" as
        #   found in the message body of the Ping frame being replied to.'
        send_data frame.data, :pong
      end
    end
  end
rescue Errno::EHOSTUNREACH, Errno::ETIMEDOUT, Errno::ECONNRESET, IOError, Errno::EBADF
  nil # client disconnected or timed out
end