Class: Tep::WebSocket::Connection

Inherits:
Object
  • Object
show all
Defined in:
lib/tep/websocket/connection.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(driver) ⇒ Connection

Returns a new instance of Connection.



22
23
24
25
26
# File 'lib/tep/websocket/connection.rb', line 22

def initialize(driver)
  @driver = driver
  @fd     = driver.fd
  @idle_timeout_seconds = 300
end

Instance Attribute Details

#driverObject

Returns the value of attribute driver.



20
21
22
# File 'lib/tep/websocket/connection.rb', line 20

def driver
  @driver
end

#fdObject

Returns the value of attribute fd.



20
21
22
# File 'lib/tep/websocket/connection.rb', line 20

def fd
  @fd
end

#idle_timeout_secondsObject

Returns the value of attribute idle_timeout_seconds.



20
21
22
# File 'lib/tep/websocket/connection.rb', line 20

def idle_timeout_seconds
  @idle_timeout_seconds
end

Class Method Details

.dispatch_close(driver, code, reason) ⇒ Object



145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/tep/websocket/connection.rb', line 145

def self.dispatch_close(driver, code, reason)
  evt = Tep::WebSocket::Event.new
  evt.code = code
  evt.reason = reason
  driver.h_close.handle_event(evt)
  # Auto-cleanup: any Broadcast subscription or Presence row
  # keyed on this connection's fd gets dropped. Both calls
  # are no-op-safe when nothing was tracked (zero matches).
  # Apps that still call unsubscribe_fd / untrack_by_fd
  # explicitly stay correct -- the second call finds 0 matches.
  Tep::Broadcast.unsubscribe_fd(driver.fd)
  Tep::Presence.untrack_by_fd(driver.fd)
  0
end

.dispatch_frame(driver, frame) ⇒ Object

Route a parsed frame to the right handler.



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/tep/websocket/connection.rb', line 90

def self.dispatch_frame(driver, frame)
  op = frame.opcode
  if op == Tep::WebSocket::OPCODE_TEXT
    Connection.dispatch_message(driver, frame.payload, true)
  elsif op == Tep::WebSocket::OPCODE_BINARY
    Connection.dispatch_message(driver, frame.payload, false)
  elsif op == Tep::WebSocket::OPCODE_PING
    # Auto-pong with the ping's payload (§5.5.3).
    driver.pong(frame.payload)
    Connection.dispatch_ping(driver, frame.payload)
  elsif op == Tep::WebSocket::OPCODE_PONG
    Connection.dispatch_pong(driver, frame.payload)
  elsif op == Tep::WebSocket::OPCODE_CLOSE
    code = 0
    reason = ""
    if frame.payload.length >= 2
      code = (frame.payload[0].ord << 8) | frame.payload[1].ord
      if frame.payload.length > 2
        reason = frame.payload[2, frame.payload.length - 2]
      end
    end
    # Echo the close back (§5.5.1) then dispatch.
    driver.close(code == 0 ? Tep::WebSocket::CLOSE_NORMAL : code, reason)
    Connection.dispatch_close(driver, code, reason)
  end
  0
end

.dispatch_message(driver, data, text) ⇒ Object



124
125
126
127
128
129
# File 'lib/tep/websocket/connection.rb', line 124

def self.dispatch_message(driver, data, text)
  evt = Tep::WebSocket::Event.new
  evt.data = data
  driver.h_message.handle_event(evt)
  0
end

.dispatch_open(driver) ⇒ Object



118
119
120
121
122
# File 'lib/tep/websocket/connection.rb', line 118

def self.dispatch_open(driver)
  evt = Tep::WebSocket::Event.new
  driver.h_open.handle_event(evt)
  0
end

.dispatch_ping(driver, data) ⇒ Object



131
132
133
134
135
136
# File 'lib/tep/websocket/connection.rb', line 131

def self.dispatch_ping(driver, data)
  evt = Tep::WebSocket::Event.new
  evt.data = data
  driver.h_ping.handle_event(evt)
  0
end

.dispatch_pong(driver, data) ⇒ Object



138
139
140
141
142
143
# File 'lib/tep/websocket/connection.rb', line 138

def self.dispatch_pong(driver, data)
  evt = Tep::WebSocket::Event.new
  evt.data = data
  driver.h_pong.handle_event(evt)
  0
end

Instance Method Details

#runObject

Drive the recv loop. Returns 0 on clean close, -1 on error. Idempotent across multiple frames per recv: a single sphttp_recv_into_frame fill may contain several complete frames; Connection consumes them all before parking again.

The caller (Tep::Server::Scheduled.write_response) owns the fd lifecycle – run() never calls sphttp_close. On clean close OR error the server’s handle_connection closes the fd via its usual exit path.



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/tep/websocket/connection.rb', line 41

def run
  # Synthetic open event before the first recv -- handlers
  # often want to send a welcome message.
  Connection.dispatch_open(@driver)

  while true
    ready = Tep::Scheduler.io_wait(@fd, Tep::Scheduler::READ, @idle_timeout_seconds)
    if ready == 0
      # Timeout: close 1001 going-away.
      @driver.close(Tep::WebSocket::CLOSE_GOING_AWAY, "idle timeout")
      return 0
    end

    n = Sock.sphttp_recv_into_frame(@fd)
    if n <= 0
      # EOF or error: dispatch close without sending one back
      # (peer already gone) and exit.
      Connection.dispatch_close(@driver, Tep::WebSocket::CLOSE_GOING_AWAY, "")
      if n == 0
        return 0
      end
      return -1
    end

    # Parse + dispatch as many complete frames as possible
    # from this recv.
    state = Tep::WebSocket::ConnectionState.new
    state.start = 0
    state.avail = n
    while true
      r = Tep::WebSocket::Frame.parse_from_buf(state.start, state.avail)
      if r.outcome == "need"
        break
      end
      if r.outcome == "close"
        @driver.close(r.close_code, "protocol error")
        return 0
      end
      Connection.dispatch_frame(@driver, r.frame)
      state.start = state.start + r.consumed
      if state.start >= state.avail
        break
      end
    end
  end
  0
end

#set_idle_timeout(seconds) ⇒ Object



28
29
30
# File 'lib/tep/websocket/connection.rb', line 28

def set_idle_timeout(seconds)
  @idle_timeout_seconds = seconds
end