iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 5
0
Modern Web

向 Rails 致敬!30天寫一個網頁框架,再拿來做一個 Todo List系列 第 5

[DAY 5] 復刻 Rails - 再替 Controller 做點加強

昨天我們將 MVC 的 C 打造了一個雛形,透過這個雛形我們了解到,靠著 Rack 處理每條 request,搭配 env 裡面的 PATH_INFO,就可以實現簡單的 Routing,透過 Routing 來分配任務到相對應的 Controller 和 Action

接著利用 Controller 來 new 一個物件,利用這個 Controller 物件來執行包含的 method,也就是 Action,但實際用起來會發現,還有些問題需要處理,今天就來看看那些問題吧

伺服器一直出現 Favicon.icoController Error

如果你在 Terminal 上執行 rackup -p 3001,你在 web server log 會看到上面出現這樣的錯誤訊息

NameError: wrong constant name Favicon.icoController

仔細讀這段錯誤訊息,會發現他的意思是找不到 /favicon.ico

那什麼是 favicon?

Favicon是favorites icon的縮寫,亦被稱為website icon(網頁圖示)、page icon(頁面圖示)或urlicon(URL圖示)。Favicon是與某個網站或網頁相關聯的圖示。網站設計者可以多種方式建立這種圖示,而目前也有很多網頁瀏覽器支援此功能。瀏覽器可以將favicon顯示於瀏覽器的網址列中,也可置於書籤列表的網站名前,還可以放在標籤式瀏覽介面中的頁標題前。
by wiki

其實這段是瀏覽器自動發送的 request,目的是要拿到你網站的 icon,但如果沒有設定的話,反而會一直噴錯誤訊息,這裡我們先用最簡單的方式來處理

# mavericks/lib/mavericks.rb

require "mavericks/version"
require "mavericks/routing"

module Mavericks
  class Error < StandardError; end

  class Application
    def call(env)
    
      # add this code
      if env["PATH_INFO"] == '/favicon.ico'
        return [404, {'Content-Type' => 'text/html'}, []]
      end

      klass, act =  get_controller_and_action(env)
      controller = klass.new(env)
      text = controller.send(act)
      [200, {'Content-Type' => 'text/html'},
       [text]]
    end
  end

  class Controller
    attr_reader :env

    def initialize(env)
      @env = env
    end
  end
end

利用簡單的檢查,把所有 favicon 的請求都先用 404 來回應 ,等到之後開始建立 view 以後,我們再來處理這個問題

不要將錯誤直接噴給使用者

依照目前 Mavericks 的設計,假如在 just_do 那邊瀏覽了還沒有定義的 controller 會發生什麼事情?例如像是 http://127.0.0.1:3001/

# 出現這樣的錯誤訊息

NameError at /
uninitialized constant Controller

因為我們還沒有設定首頁的機制,但其實使用者不應該看到這些畫面,正常來說應該要回應另外製作的 404 提示畫面,像是這樣


by github

那就一樣來修改一下程式碼吧

# mavericks/lib/mavericks.rb

def call(env)
  if env["PATH_INFO"] == '/favicon.ico'
    return [404, {'Content-Type' => 'text/html'}, []]
  end

  begin
    klass, act =  get_controller_and_action(env)
    controller = klass.new(env)
    text = controller.send(act)
    [200, {'Content-Type' => 'text/html'},
     [text]]
  rescue
    [404, {'Content-Type' => 'text/html'},
     ['This is a 404 page!!']]
  end
end

接著在瀏覽一次 http://127.0.0.1:3001/

This is a 404 page!!

哦!現在我們有 404 page 了,雖然還只是個字串,但至少不會再隨便噴錯給使用者看了

每個網站都需要一個首頁

我們暫時解決了噴錯的問題,但我們還是得讓開發者可以自訂首頁才對,問題來了,像http://127.0.0.1:3001/ 這樣的網址結構,既沒有 Controller,也沒有 Action 可以解析,那該怎麼辦?沒關係,我們可以利用前面提到的 PATH_INFO,來分辨這個 request 是不是對 'http://127.0.0.1:3001/' 這頁做 request

讓我們修改並整理一下程式碼

# mavericks/lib/mavericks.rb

class Application
  def call(env)
    return favicon if env["PATH_INFO"] == '/favicon.ico'
    return index if env["PATH_INFO"] == '/'

    begin
      klass, act =  get_controller_and_action(env)
      controller = klass.new(env)
      text = controller.send(act)
      [200, {'Content-Type' => 'text/html'},
       [text]]
    rescue
      [404, {'Content-Type' => 'text/html'},
       ['This is a 404 page!!']]
    end
  end

  private

  def index
    [200, {'Content-Type' => 'text/html'}, ['This is a index page']]
  end

  def favicon
    return [404, {'Content-Type' => 'text/html'}, []]
  end
end

所以現在的情況是,開發者沒有自訂首頁的話,我們就會給他一個預設首頁訊息,那如果開發者想要自訂首頁內容呢?那我們可以再修改一下程式碼,讓 Mavericks 預設可以去尋找有沒有 HomeController

# mavericks/lib/mavericks.rb

class Application
  def call(env)
    return favicon if env["PATH_INFO"] == '/favicon.ico'
    
    # 加上 env 變數
    return index(env) if env["PATH_INFO"] == '/'

    begin
      klass, act =  get_controller_and_action(env)
      controller = klass.new(env)
      text = controller.send(act)
      [200, {'Content-Type' => 'text/html'},
       [text]]
    rescue
      [404, {'Content-Type' => 'text/html'},
       ['This is a 404 page!!']]
    end
  end

  private

  # 如果開發者有 HomeController,就執行 HomeController#index 
  def index(env)
    begin
      home_klass = Object.const_get('HomeController')
      controller = home_klass.new(env)
      text = controller.send(:index)
      [200, {'Content-Type' => 'text/html'}, [text]]
    rescue NameError
      [200, {'Content-Type' => 'text/html'}, ['This is a index page']]
    end
  end

  def favicon
    return [404, {'Content-Type' => 'text/html'}, []]
  end
end

回到 just_do

太好了,現在開發者可以替購物車自訂首頁內容了,先建立 HomeController#index

# just_do/app/controllers/home_controller.rb

class HomeController < Mavericks::Controller
  def index
    'just do index page'
  end
end

別忘了要去 config 檔案裡面 require 新加入的 HomeController

# just_do/config/application.rb

require 'mavericks'
$LOAD_PATH << File.join(File.dirname(__FILE__), "..", "app", "controllers")
require 'tasks_controller'

# 加上這行
require 'home_controller'

module JustDo
  class Application < Mavericks::Application
  end
end

然後再一次瀏覽 http://127.0.0.1:3001/

會出現自訂的訊息

just do index page

酷!

開發者說...

雖然 Mavericks 已經有 Controller 可以使用了,也可以依照不同的頁面加上相對應的 Action,但開發起來有點麻煩耶,每次只要新增一個新的 Controller 檔案,就要自己手動加上 require,我記得 Rails 都不用呀...

嗯...好問題,Rails 的 Autoload 機制的確是一個相當方便的功能,只要新增檔案以後,連伺服器都不用重新開啟,網頁重整一下就好了,這也是 Rails 開發速度會那麼快的原因之一,既然是很重要的功能,那明天就來想辦法加到 Mavericks 上吧!


上一篇
[DAY 4] 復刻 Rails - 從 Controller 開始
下一篇
[DAY 6] 復刻 Rails - 關於 Autoloading
系列文
向 Rails 致敬!30天寫一個網頁框架,再拿來做一個 Todo List30

尚未有邦友留言

立即登入留言