Class: WEBrick::HTTPServer

Inherits:
Object
  • Object
show all
Defined in:
lib/webtube/webrick.rb,
lib/webtube/webrick.rb

Instance Method Summary collapse

Constructor Details

#initialize(config = {}, default = Config::HTTP) ⇒ HTTPServer

Returns a new instance of HTTPServer.



30
31
32
33
34
# File 'lib/webtube/webrick.rb', line 30

def initialize config = {}, default = Config::HTTP
  orig_initialize_before_webtube_integration config, default
  @webtubes = Webtube::Vital_Statistics.new @logger
  return
end

Instance Method Details

#accept_webtube(request, response, listener, session: nil, context: nil) ⇒ Object

Given a [[request]] and a [[response]] object, as prepared by a [[WEBrick::HTTPServer]] for processing in a portlet, attempt to accept the client’s request to establish a WebSocket connection. The [[request]] must actually contain such a request; see [[websocket_upgrade_request?]].

The attempt will fail in the theoretical case the client and the server can’t agree on the protocol version to use. In such a case, [[accept_webtube]] will prepare a 426 ‘Upgrade required’ response, explaining in plain text what the problem is and advertising, using the

[Sec-WebSocket-Version]

header field, the protocol

version (specifically, 13) it is prepared to speak. When this happens, the WebSocket session will never be set up and no [[listener]] events will be called.

Note that [[accept_webtube]] will manipulate [[response]] and return immediately. The actual WebSocket session will begin once WEBrick attempts to deliver the [[response]], and will be marked by the newly constructed [[Webtube]] instance delivering an [[onopen]] event to [[listener]].

Also note that the loop to process incoming WebSocket frames will hog the whole thread; in order to deliver asynchronous messages over the WebSocket,

[Webtube#send_message]

needs to be called from another

thread. (For synchronous messages, it can safely be called from the handlers inside [[listener]].)

See [[Webtube#run]] for a list of the supported methods for the [[listener]].



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
# File 'lib/webtube/webrick.rb', line 98

def accept_webtube request, response, listener,
    session: nil, context: nil
  # Check that the client speaks our version
  unless (request['Sec-WebSocket-Version'] || '').
      strip.split(/\s*,\s*/).
      include? '13' then
    @logger.error "Sec-WebSocket-Version mismatch"
    response.status, response.reason_phrase =
        '426', 'Upgrade required'
    response['Content-type'] = 'text/plain'
    response['Sec-WebSocket-Version'] = '13'
        # advertise the version we speak
    response.body = "This WebSocket server only speaks " +
        "version 13 of the protocol, as specified by " +
        "RFC 6455.\n"
  else
    response.status, response.reason_phrase =
        '101', 'Hello WebSocket'
    response['Upgrade'] = 'websocket'
    response['Sec-WebSocket-Accept'] =
        OpenSSL::Digest::SHA1.base64digest(
            request['Sec-WebSocket-Key'] +
            '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
    response['Sec-WebSocket-Version'] = '13'
    response.keep_alive = false
        # so that WEBrick will close the TCP socket when
        # we're done
    vital_statistics = self.webtubes
    (class << response; self; end).instance_eval do
      # We'll need to deliver the [[Connection: Upgrade]]
      # header; unfortunately, HTTPResponse#setup_header
      # would munge it if we set this header field in the
      # ordinary way.  Accordingly, we'll have to override
      # the method.
      define_method :setup_header do ||
        super()
        @header['connection'] = 'Upgrade'
        return
      end

      # Replace [[response.send_body]] with the WS engine.
      # WEBrick will call it automatically after sending the
      # response header.
      #
      # Also notify the server's attached
      # [[Webtube::Vital_Statistics]] instance so that
      # server shutdown could also close all pending
      # Webtubes.
      define_method :send_body do |socket|
        webtube = Webtube.new socket, true,
            close_socket: false
        begin
          vital_statistics.birth webtube
          webtube.header = request
          webtube.session = session
          webtube.context = context
          # Reassign us from the WEBrick's thread group to
          # the one maintained by
          # [[Webtube::Vital_Statistics]].
          vital_statistics.thread_group.add Thread.current
          # And now, run!
          webtube.run listener
        ensure
          vital_statistics.death webtube
        end
        return
      end
    end
  end
  return
end

#mount_webtube(dir, listener) ⇒ Object



202
203
204
205
# File 'lib/webtube/webrick.rb', line 202

def mount_webtube dir, listener
  mount dir, HTTPServlet::WebtubeHandler.new(self, listener)
  return
end

#orig_initialize_before_webtube_integrationObject

Attach a [[Webtube::Vital_Statistics]] to new

[WEBrick::HTTPServer]

instances so that the live

webtubes could be closed upon shutdown



29
# File 'lib/webtube/webrick.rb', line 29

alias orig_initialize_before_webtube_integration initialize

#orig_shutdown_before_webtube_integrationObject



54
# File 'lib/webtube/webrick.rb', line 54

alias orig_shutdown_before_webtube_integration shutdown

#shutdownObject



55
56
57
58
59
60
61
62
63
64
# File 'lib/webtube/webrick.rb', line 55

def shutdown
  # We'll need to call the original shutdown code first, for
  # we want to stop accepting new Webtube connections before
  # 'close all Webtube connections' will have a proper,
  # thread-safe meaning.
  orig_shutdown_before_webtube_integration
  webtubes.close_all
  webtubes.thread_group.list.each &:join
  return
end

#webtubesObject



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/webtube/webrick.rb', line 36

def webtubes
  result = @webtubes
  # Usually, this should be it.
  if result.nil? then # ... but ...
    # Well, it would seem that our extended constructor was
    # not called.  How could this have happened?
    result = @webtubes = Webtube::Vital_Statistics.new
    @logger.warn "@webtubes (in a WEBrick::HTTPServer) " +
        "has not been set up before accessing it.  I " +
        "have attempted to correct this ex post facto, " +
        "but doing it now is a race condition, and I may " +
        "have lost track of some webtubes as a result.  " +
        "The next time, please load webtube/webrick.rb " +
        "/before/ instantiating your WEBrick::HTTPServer."
  end
  return result
end