@ledsun blog

無味の味は佳境に入らざればすなわち知れず

Async::WebSocket gemの素振り その7

Async::WebSocket gemの素振り その6 - @ledsun blogAsync::HTTP::Body::Hijack.wrapを使うようになりました。 今回はAsync::HTTP::Body::Hijack.wrapを展開します。

require "protocol/http/body/readable"
require "protocol/http/body/writable"
require "protocol/http/body/stream"
require 'async/websocket/connection'
require 'protocol/websocket/framer'
require 'protocol/websocket/extensions'
require "protocol/rack/request"
require "protocol/rack/adapter"

class MyReadable < ::Protocol::HTTP::Body::Readable
  def initialize(block, input)
    @block = block
    @input = input

    @task = nil
    @stream = nil
    @output = nil
  end

  # We prefer streaming directly as it's the lowest overhead.
  def stream?
    true
  end

  def call(stream)
    @block.call(stream)
  end

  attr :input

  # Has the producer called #finish and has the reader consumed the nil token?
  def empty?
    @output&.empty?
  end

  def ready?
    @output&.ready?
  end

  # Read the next available chunk.
  def read
    @output = ::Protocol::HTTP::Body::Writable.new
    @stream = ::Protocol::HTTP::Body::Stream.new(@input, @output)
    @block.call(@stream)
    @output.read
  end

  def inspect
    "\#<#{self.class} #{@block.inspect}>"
  end

  def to_s
    "<Hijack #{@block.class}>"
  end
end

class App
  def self.call(env)
    req = ::Protocol::Rack::Request[env]

    # Set a handler for received messages
    handler = lambda do |stream|
      framer = ::Protocol::WebSocket::Framer.new(stream)
      conn = ::Async::WebSocket::Connection.call framer,
                                                 nil,
                                                 ::Protocol::WebSocket::Extensions::Server.default

      # Echo back!
      while message = conn.read
        conn.write message
      end
    end
    body = MyReadable.new(handler, req.body)

    # Prepare response headers
    response_headers = ::Protocol::HTTP::Headers.new
    accept_nounce = req.headers[::Protocol::WebSocket::Headers::SEC_WEBSOCKET_KEY]&.first
    response_headers.add ::Protocol::WebSocket::Headers::SEC_WEBSOCKET_ACCEPT,
                         ::Protocol::WebSocket::Headers::Nounce.accept_digest(accept_nounce)

    # Return response
    res = ::Protocol::HTTP::Response.new(req.version, 101, response_headers, body, ::Protocol::WebSocket::Headers::PROTOCOL)
    Protocol::Rack::Adapter.make_response(env, res)
  end
end

run App

Readableクラスが増えて長くなりました。 Async::WebSocketへの依存が1つ減りました。 残るは::Async::WebSocket::Connection.callです。 ::Async::WebSocket::Connection.callを展開したら、もう少しシンプルにできそうです。