Class: Sockeye::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/sockeye/server.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host:, port:, secret_token:, authentication_method: nil) ⇒ Server

Returns a new instance of Server.



10
11
12
13
14
15
16
17
# File 'lib/sockeye/server.rb', line 10

def initialize(host:, port:, secret_token:, authentication_method: nil)
  self.connections = {}
  self.connection_map = {}
  self.host = host
  self.port = port
  self.secret_token = secret_token
  self.authentication_method = authentication_method
end

Instance Attribute Details

#authentication_methodObject

Returns the value of attribute authentication_method.



8
9
10
# File 'lib/sockeye/server.rb', line 8

def authentication_method
  @authentication_method
end

#connection_mapObject

Returns the value of attribute connection_map.



8
9
10
# File 'lib/sockeye/server.rb', line 8

def connection_map
  @connection_map
end

#connectionsObject

Returns the value of attribute connections.



8
9
10
# File 'lib/sockeye/server.rb', line 8

def connections
  @connections
end

#hostObject

Returns the value of attribute host.



8
9
10
# File 'lib/sockeye/server.rb', line 8

def host
  @host
end

#portObject

Returns the value of attribute port.



8
9
10
# File 'lib/sockeye/server.rb', line 8

def port
  @port
end

#secret_tokenObject

Returns the value of attribute secret_token.



8
9
10
# File 'lib/sockeye/server.rb', line 8

def secret_token
  @secret_token
end

Instance Method Details

#add_connection(identifier:, connection:) ⇒ Object

Add a connection to the list and add a map entry to link the connection object with an authenticated identifier



38
39
40
41
42
# File 'lib/sockeye/server.rb', line 38

def add_connection(identifier:, connection:)
  connections[identifier] = [] if connections[identifier].nil?
  connections[identifier] << connection
  connection_map[connection.object_id] = identifier
end

#authenticate(token) ⇒ Object

Call the supplied authentication method



31
32
33
# File 'lib/sockeye/server.rb', line 31

def authenticate(token)
  return self.authentication_method.call(token)
end

#deliver_to_many(payload:, identifiers:) ⇒ Object

Find all open connections associated with the specified identifiers then attempt to push the payload to each of them



58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/sockeye/server.rb', line 58

def deliver_to_many(payload:, identifiers:)
  identifiers.each do |identifier|
    identified_connections = connections[identifier]
    next unless identified_connections.is_a? Array
    identified_connections.each do |connection|
      begin
        connection.send({payload: payload, status: payload.dig(:status) || 200}.to_json, :type => :text)
      rescue
      end
    end
  end
end

#json_try_parse(data) ⇒ Object

Safely parse data as JSON, but return nil values on failure



21
22
23
24
25
26
27
# File 'lib/sockeye/server.rb', line 21

def json_try_parse(data)
  begin
    return JSON.parse(data, symbolize_names: true)
  rescue JSON::ParserError => e
    return nil
  end
end

#listenObject

Main server connection listener loop. Uses an EventMachine and websocket server to handle and abstract raw connections. Handles authentication and delivery actions for clients and pushers.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/sockeye/server.rb', line 75

def listen
  EM.run do
    WebSocket::EventMachine::Server.start(host: self.host, port: self.port) do |ws|

      # Called when a new message arrives at the server
      #
      ws.onmessage do |message, type|

        # Attempt to parse the received data as JSON
        #
        message_json = json_try_parse(message)
        if message_json.nil?
          ws.send({payload: "invalid message", status: 400}.to_json, :type => :text)
          ws.close
        else

          # Execute the appropriate action based on JSON action
          #
          case message_json[:action].to_sym

          # Handle authentication requests by calling the authentication
          # method supplied on server setup
          #
          when :authenticate
            authentication_result = authenticate(message_json[:payload])
            if authentication_result
              add_connection(identifier: authentication_result, connection: ws)
              ws.send({payload: "authenticated", status: 200}.to_json, :type => :text)
            else
              ws.send({payload: "authentication failure", status: 401}.to_json, :type => :text)
              ws.close
            end

          # Handle delivery requests by verifying the auth token supplied
          # then push out the payload to all connected specified clients
          #
          when :deliver
            if message_json[:secret_token] == self.secret_token
              deliver_to_many(payload: message_json[:payload], identifiers: [message_json[:identifiers]].flatten)
              ws.send({payload: "payload pushed", status: 201}.to_json, :type => :text)
              ws.close
            else
              ws.send({payload: "authentication failure", status: 401}.to_json, :type => :text)
              ws.close
            end

          else
            ws.send({payload: "invalid action", status: 405}.to_json, :type => :text)
            ws.close
          end

        end
      end

      # Cleanup connection lists when a connection is closed
      #
      ws.onclose do
        remove_connection(ws)
      end

    end
  end
end

#remove_connection(connection) ⇒ Object

Safely remove the specified connection from the connections lists



46
47
48
49
50
51
52
53
# File 'lib/sockeye/server.rb', line 46

def remove_connection(connection)
  identifier = connection_map[connection.object_id]
  if connections[identifier].is_a? Array
    connections[identifier].delete(connection)
    connections.delete(identifier) if connections[identifier].empty?
  end
  connection_map.delete(connection.object_id)
end