iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 20
0

我們現在已經有一個可以執行的指令,接下來就是要將 Web Server 啟動。在 Ruby 裡面我們可以透過兩種方式將 Rack 打開。

  1. 使用 config.ru 定義後,用 rackup 開啟
  2. 使用 Rack::Handler 自動挑選推薦的伺服器(Ex. Puma)開啟

在 Ruby on Rails 應該是使用前者,不過 Rails 也做了一些特殊處理讓我們也可以用 rails server 啟動,這邊我們要採用的是第二個方法。

Rack::Handler

因為 Ruby 已經定義了 Rack 這個通用的介面,因此不同的 Web Server 要能夠被 Rack 使用就會將自己向 Rack 註冊成為執行的主體,也因此 Rack 身上會帶有多個 Web Server 可以使用在預設的情況下會自動選擇一個。

因為 Puma、Unicorn 這類伺服器都是後來註冊的,並且會特地插入到備選列表的前方,因此我們可以直接使用 Rack::Handler.default 來選擇到 Puma。

打開 config/application.rb 稍微修改我們的伺服器讓他可以接收普通的 HTTP 請求。

class SimpleRPG
  include Singleton

  class << self
    def run(server, options = {})
      # 呼叫實例上的方法
      instance.start(server, options)
    end
  end

  def start(server, options = {})
    # TODO: 要透過 Server 選擇負責處理的物件
    # 透過 Rack::Handler 預設值啟動伺服器
    Rack::Handler.default.run(self, options)
  end

  # TODO: 實作專門的 HTTP 處理程式
  def call(_env)
    # 簡單回傳 Hello World
    [200, { 'Content-Type' => 'text/plain' }, ['Hello World']]
  end
end

現在,我們可以執行 bin/serve map 來把伺服器開啟,預設的 Port 是 9292 因此我們可以用 curl 指令驗證一下。

curl localhost:9292
# => Hello World

在上一篇中我們將 options[:Port] = port&.to_i 是因為 Rack 在設定 Port 是用 :Port 當作 Key 而不是 :port 有點跟平常習慣的 Ruby 使用方式有點差異。

Faye::WebSocket

在 Ruby 裡面並沒有原生的 WebSocket 套件,如果要靠自己實作處理這些行為的話是相當費時的,不過 Faye::WebSocket 已經幫我們封裝好了 websocket-driver 這個 Gem 我們只需要直接使用即可。

# app/servers/websocket_server.rb

# frozen_string_literal: true

module WebSocketServer
  UNSUPPORT_RESPONSE = [501, { 'Content-Type' => 'text/plain' }, ['Unsupport']].freeze

  def self.call(env)
    return UNSUPPORT_RESPONSE unless Faye::WebSocket.websocket?(env)

    ws = Faye::WebSocket.new(env)
    ws.rack_response
  end
end

我們先簡單實作一個可以接受 .call 行為的物件,當 Rack 啟動收到請求就會呼叫這個 .call 行為進行處理。

當我們要處理 WebSocket 之前,需要先了解是否為 WebSocket 連線,如果不是的話大多會再處理後結束連線(現代的伺服器會保持一段時間來重複使用),而 WebSocket 則要明確地告訴伺服器連線要轉換成持久的連線。

因此我們要先透過 Faye::WebSocket.websocket?(env) 來幫我們檢查傳入的資訊是否是 WebSocket 請求,如果不是的話我們就回應一個 Unsupport (不支援)的訊息。

接下來要調整一下我們的 SimpleRPG 物件讓他可以吃到 WebSocketServer:

# config/application.rb

# frozen_string_literal: true

require 'rubygems'
require 'bundler'
require 'singleton'

require 'app/servers/websocket_server'

Bundler.require

class SimpleRPG
  include Singleton

  class << self
    def run(server, options = {})
      instance.start(server, options)
    end
  end

  def start(server, options = {})
    # TODO: 要透過 Server 選擇負責處理的物件
    Rack::Handler.default.run(WebSocketServer, options)
  end
end

我們現在可以打開 Chrome 或者其他瀏覽器的主控台(Console)直接執行一段 JavaScript 連上我們剛寫好的伺服器(請記得關閉伺服器後重開)

var ws = new WebSocket('ws://localhost:9292')
ws.onopen = (ev) => console.log(ev)

如果有看到 JavaScript 事件產生,那麼就表示我們的伺服器已經成功支援 WebSocket 了!

我的個人部落格是弦而時習之平常會把自己發現的一些新技巧紀錄在上面,也歡迎大家來逛逛。


上一篇
Day 19 - 實作練習 - Server 雛形建立
下一篇
Day 21 - 實作練習 - Autoload 機制
系列文
從讀遊戲原始碼學做連線遊戲33

尚未有邦友留言

立即登入留言