這節以及後面的習題中,你的任務是把前面建立的遊戲做成網頁版。這是本書的最後三個章節,這些內容對你來說難度會相當大,你要在上面花些時間才能做出來。在你開始這節練習以前,你必須已經成功地完成過了《習題46》的內容,正確安裝了 **RubyGems**,而且學會瞭如何安裝軟體套件以及如何建立專案骨架。如果你不記得這些內容,就回到《習題46》重新複習一遍。 ## 安裝 Sinatra 在建立你的第一個網頁應用程式之前,你需要安裝一個「Web框架」,它的名字叫 **Sinatra**。所謂的「框架」通常是指「讓某件事情做起來更容易的軟體套件」。在網頁應用的世界裡,人們建立了各種各樣的「網頁框架」,用來解決他們在建立網站時碰到的問題,然後把這些解決方案用軟體套件的方式發佈出來,這樣你就可以利用它們引導建立你自己的專案了。 可選的框架類型有很多很多,不過在這裡我們將使用 Sinatra 框架。你可以先學會它,等到差不多的時候再去接觸其它的框架,不過 Sinatra 本身挺不錯的,所以就算你一直使用也沒關係。 使用 `gem` 安裝 Sinatra: ~~~ $ gem install sinatra Fetching: tilt-1.3.2.gem (100%) Fetching: sinatra-1.2.6.gem (100%) Successfully installed tilt-1.3.2 Successfully installed sinatra-1.2.6 2 gems installed Installing ri documentation for tilt-1.3.2... Installing ri documentation for sinatra-1.2.6... Installing RDoc documentation for tilt-1.3.2... Installing RDoc documentation for sinatra-1.2.6... ~~~ ## 寫一個簡單的「Hello World」專案 現在你將做一個非常簡單的「Hello World」專案出來,首先你要建立一個專案目錄: ~~~ $ cd projects $ bundle gem gothonweb ~~~ 你最終的目的是把《習題42》中的遊戲做成一個 web 應用,所以你的專案名稱叫做 `gothonweb`,不過在此之前,你需要建立一個最基本的 Sinatra應用,將下面的代碼放到`lib/gothonweb.rb`中: ~~~ require_relative "gothonweb/version" require "sinatra" module Gothonweb get '/' do greeting = "Hello, World!" return greeting end end ~~~ 然後使用下面的方法來運行這個 web 程式: ~~~ $ ruby lib/gothonweb.rb == Sinatra/1.2.6 has taken the stage on 4567 for development with backup from WEBrick [2011-07-18 11:27:07] INFO WEBrick 1.3.1 [2011-07-18 11:27:07] INFO ruby 1.9.2 (2011-02-18) [x86_64-linux] [2011-07-18 11:27:07] INFO WEBrick::HTTPServer#start: pid=6599 port=4567 ~~~ 最後,使用你的網頁瀏覽器,打開 URL `http://localhost:4567/`,你應該看到兩樣東西,首先是瀏覽器裡顯示了 `Hello, world!`,然後是你的命令行終端顯示了如下的輸出: ~~~ 127.0.0.1 - - [18/Jul/2011 11:29:10] "GET / HTTP/1.1" 200 12 0.0015 localhost - - [18/Jul/2011:11:29:10 EDT] "GET / HTTP/1.1" 200 12 - -> / 127.0.0.1 - - [18/Jul/2011 11:29:10] "GET /favicon.ico HTTP/1.1" 404 447 0.0008 localhost - - [18/Jul/2011:11:29:10 EDT] "GET /favicon.ico HTTP/1.1" 404 447 - -> /favicon.ico ~~~ 這些是 Sinatra 印出的 log 資訊,從這些資訊你可以看出服務器有在運行,而且能了解到程式在瀏覽器背後做了些什麼事情。這些資訊還有助於你發現程式的問題。例如在最後一行它告訴你瀏覽器試圖存取 `/favicon.ico`,但是這個文件並不存在,因此它返回的狀態碼是 `404 Not Found`。 到這裡,我還沒有講到任何 web 相關的工作原理,因為首先你需要完成準備工作,以便後面的學習能順利進行,接下來的兩節習題中會有詳細的解釋。我會要求你用各種方法把你的 Sinatra 應用程式弄壞,然後再將其重新構建起來:這樣做的目的是讓你明白運行 Sinatra 程式需要準備好哪些東西。 ## 發生了什麼事情? 在瀏覽器訪問到你的網頁應用程式時,發生了下面一些事情: 1. 瀏覽器通過網路連接到你自己的電腦,它的名字叫做 `localhost`,這是一個標準稱謂,表示的誰就是網路中你自己的這台電腦,不管它實際名字是什麼,你都可以使用 `localhost`來訪問。它使用到`port 4567`。 2. 連接成功以後,瀏覽器對 lib/gothonweb.rb`這個應用程式發出了HTTP請求(request),要求訪問URL`/`,這通常是一個網站的第一個URL。 3. 在`lib/gothonweb.rb` 裡,我們有一個程式碼區段,裡面包含了 URL 的匹配關係。我們這裡只定義了一組匹配,那就是「/」。它的含義是:如果有人使用瀏覽器訪問 `/` 這一級目錄,Sinatra 將找到它,從而用它處理這個瀏覽器請求。 4. Sinatra 呼叫匹配到的程式碼區段,這段程式碼只簡單的回傳了一個字串傳回給瀏覽器。 5. 最後 Sinatra 完成了對於瀏覽器請求的處理將響應(response)回傳給瀏覽器,於是你就看到了現在的頁面。 確定你真的弄懂了這些,你需要畫一個示意圖,來理清資訊是如何從瀏覽器傳遞到 Sinata,再到 `/`區段,再回到你的瀏覽器的。 ## 修正錯誤 第一步,把第 6 行的 `greeting` 變數刪掉,然後重新刷瀏覽器。你應該會看到一個錯誤畫面,你可以通過這一頁豐富的資訊看出你的程式崩潰的原因。當然你已經知道出錯的原因是 `greeting`的賦值遺失了,不過 Sinatra還是會給你一個挺好的錯誤頁面,讓你能找到出錯的具體位置。試試在這個錯誤頁面上做以下操作: 1. 看看 `sinatra.error` 變數。 2. 看看 `REQUEST_` 變數裡的資訊。裡面哪些知識是你已經熟悉了的。這是瀏覽器發給你的 gothonweb 應用程式的資訊。這些知識對於日常網頁瀏覽沒有什麼用處,但現在你要學會這些東西,以便寫出web應用程式來。 ## 建立基本的模板 你已經試過用各種方法把這個Sinatra 程式改錯,不過你有沒有注意到「Hello World」不是一個好 HTML 網頁呢?這是一個 web 應用,所以需要一個合適的HTML 響應頁面才對。為了達到這個目的,下一步你要做的是將「Hello World」以較大的綠色字體顯示出來。 第一步是建立一個 `lib/views/index.erb` 檔案,內容如下: ~~~ <html> <head> <title>Gothons Of Planet Percal #25</title> </head> <body> <% if greeting %> <p>I just wanted to say <em style="color: green; font-size: 2em;"><%= greeting %></em>. <% else %> <em>Hello</em>, world! <% end %> </body> </html> ~~~ 什麼是一個 `.erb` 的檔案?ERB 的全名是 **E**mbedded **R**u**b**y。`.erb` 檔案其實是一個內嵌一點 Ruby 程式碼的 HTML。如果你學過HTML的話,這些內容你看上去應該很熟悉。如果你沒學過HTML,那你應該去研究一下,試著用HTML寫幾個網頁,從而知道它的運作原理。既然這是一個 `erb` 模版,Sinatra 就會在模板中找到對應的位置,將參數的內容填充到模板中。例如每一個出現 ` 的位置,內容都會被替換成對應這個變數名的參數。 為了讓你的 `lib/gothonweb.rb` 處理模板,你需要寫一寫程式碼,告訴Sinatra 到哪裡去找到模板進行加載,以及如何渲染(render)這個模板,按下面的方式修改你的檔案: ~~~ require_relative "gothonweb/version" require "sinatra" require "erb" module Gothonweb get '/' do greeting = "Hello, World!" erb :index, :locals => {:greeting => greeting} end end ~~~ 特別注意我改了 `/` 這個程式碼區段最後一行的內容,這樣它就可以呼叫 `erb` 然後把 greeting 變數傳給它。 改好上面的程式後,刷新一下瀏覽器中的網頁,你應該會看到一條和之前不同的綠色資訊輸出。你還可以在瀏覽器中通過「查看原始碼(View Source)」看到模板被渲染成了標準有效的HTML 原始碼。 這麼講也許有些太快了,我來詳細解釋一下模板的運作原理吧: 1. 在 `lib/gothonweb.rb` 你添加了一個 `erb` 函式呼叫。 2. 這個 `erb` 函式知道怎麼載入 `lib/views` 目錄夾裡的 `.erb` 的檔案。它知道去抓哪些檔案(在這個例子裡是 `index.erb`)。因為你傳了一個參數進去(`erb :index …`)。 3. 現在,當瀏覽器讀取 `/` 且 `lib/gothonweb.eb` 匹配然後執行 `get '/' do` 區段,它再也沒有只是回傳字串 `greeting`,而是呼叫 `erb` 然後傳入 `greeting` 作為一個變數。 4. 最後,你讓 `lib/views/index.erb` 去檢查 `greeting` 這個變數,如果這個變數存在的話,就印出變數裡的內容。如果不存在的話,就會印出一個預設的訊息。 要深入理解這個過程,你可以修改 `greeting 變數以及 HTML ,看看會友什麼效果。然後也創作另外一個叫做`lib/views/foo.erb`的模板。然後把`erb :index`改成`erb :foo`。從這個過程中你也可以看到,你傳入給`erb`的第一個參數只要匹配到`lib/views`下的`.erb` 檔案名稱,就可以被渲染出來了。 ## 加分習題 1. 到 [Sinatra](http://www.sinatrarb.com/) 這個框架的官方網站去閱讀更多文件。 2. 實驗一下你在上述網站中看到的所有東西,包括他們的範例程式碼。 3. 閱讀有關於 HTML5 和 CSS3 相關的東西,自己練習寫幾個 `.html` 和 `.css` 文件。 4. 如果你有一個懂 **Rails** 的朋友可以幫你的畫,你可以自己試著使用 Rails 完成一下習題 50,51,52,看看結果會是什麼樣子。