File: api.rb

package info (click to toggle)
ruby-faye-websocket 0.12.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 240 kB
  • sloc: ruby: 1,230; makefile: 3
file content (176 lines) | stat: -rw-r--r-- 4,642 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
require File.expand_path('../api/event_target', __FILE__)
require File.expand_path('../api/event', __FILE__)

module Faye
  class WebSocket

    module API
      CONNECTING = 0
      OPEN       = 1
      CLOSING    = 2
      CLOSED     = 3

      CLOSE_TIMEOUT = 30

      include EventTarget

      extend Forwardable
      def_delegators :@driver, :version

      attr_reader :url, :ready_state, :buffered_amount

      def initialize(options = {})
        @ready_state = CONNECTING
        super()
        ::WebSocket::Driver.validate_options(options, [
          :headers, :extensions, :max_length, :ping, :proxy, :tls, :binary_data_format
        ])

        @driver = yield

        if headers = options[:headers]
          headers.each { |name, value| @driver.set_header(name, value) }
        end

        [*options[:extensions]].each do |extension|
          @driver.add_extension(extension)
        end

        @ping            = options[:ping]
        @ping_id         = 0
        @buffered_amount = 0

        @close_params = @close_timer = @ping_timer = @proxy = @stream = nil
        @onopen = @onmessage = @onclose = @onerror = nil

        @driver.on(:open)    { |e| open }
        @driver.on(:message) { |e| receive_message(e.data) }
        @driver.on(:close)   { |e| begin_close(e.reason, e.code, :wait_for_write => true) }

        @driver.on(:error) do |error|
          emit_error(error.message)
        end

        if @ping
          @ping_timer = EventMachine.add_periodic_timer(@ping) do
            @ping_id += 1
            ping(@ping_id.to_s)
          end
        end
      end

      def write(data)
        @stream.write(data)
      end

      def send(message)
        return false if @ready_state > OPEN

        case message
          when Numeric then
            @driver.text(message.to_s)
          when String then
            if message.encoding == Encoding::BINARY
              @driver.binary(message)
            else
              @driver.text(message)
            end
          when Array then
            @driver.binary(message)
          else
            false
        end
      end

      def ping(message = '', &callback)
        return false if @ready_state > OPEN
        @driver.ping(message, &callback)
      end

      def close(code = nil, reason = nil)
        code   ||= 1000
        reason ||= ''

        unless code == 1000 or (code >= 3000 and code <= 4999)
          raise ArgumentError, "Failed to execute 'close' on WebSocket: " +
                               "The code must be either 1000, or between 3000 and 4999. " +
                               "#{ code } is neither."
        end

        if @ready_state < CLOSING
          @close_timer = EventMachine.add_timer(CLOSE_TIMEOUT) { begin_close('', 1006) }
        end

        @ready_state = CLOSING unless @ready_state == CLOSED

        @driver.close(reason, code)
      end

      def protocol
        @driver.protocol || ''
      end

    private

      def open
        return unless @ready_state == CONNECTING
        @ready_state = OPEN
        event = Event.create('open')
        event.init_event('open', false, false)
        dispatch_event(event)
      end

      def receive_message(data)
        return unless @ready_state == OPEN
        event = Event.create('message', :data => data)
        event.init_event('message', false, false)
        dispatch_event(event)
      end

      def emit_error(message)
        return if @ready_state >= CLOSING

        event = Event.create('error', :message => message)
        event.init_event('error', false, false)
        dispatch_event(event)
      end

      def begin_close(reason, code, options = {})
        return if @ready_state == CLOSED
        @ready_state = CLOSING
        @close_params = [reason, code]

        if @stream
          if options[:wait_for_write]
            @stream.close_connection_after_writing
          else
            @stream.close_connection
          end
        else
          finalize_close
        end
      end

      def finalize_close
        return if @ready_state == CLOSED
        @ready_state = CLOSED

        EventMachine.cancel_timer(@close_timer) if @close_timer
        EventMachine.cancel_timer(@ping_timer) if @ping_timer

        reason = @close_params ? @close_params[0] : ''
        code   = @close_params ? @close_params[1] : 1006

        event = Event.create('close', :code => code, :reason => reason)
        event.init_event('close', false, false)
        dispatch_event(event)
      end

      def parse(data)
        worker = @proxy || @driver
        worker.parse(data)
      end
    end

  end
end