- 原文地址:How I built a web server using Go — and on ChromeOS
- 原文做者:Peter GleesonFollow
- 譯文出自:掘金翻譯計劃
- 譯者:xiaoyusilen
- 校對者:nicebug,steinliber
圖片來自 WikiMedia javascript
有時會有人問我:「你究竟爲何要用 Chromebook 作 Web 開發呢?」。你們彷佛不相信我可以在一臺定位爲簡單易用的機器上學習全棧 Web 開發。前端
事實上我對在聖誕打折季買的這玩意沒有抱太大的指望。我以爲它就是個帶有編輯器和瀏覽器的低成本設備,能夠隨時隨地學習前端開發和看 YouTube。此外,我也十分熱衷於「雲計算」這個概念,它表明着將來的趨勢。java
事實證實,這個小的機器竟然有帶給我意外驚喜的本事。它的啓動速度實在是快,電池續航能力很強,而且在無處不在的「雲」的幫助下,你幾乎能夠作全部你能夠在其餘機器上完成的事情。另外,我選擇的機型有一個觸摸屏,它能夠向後翻折不一樣的角度而成爲一個平板電腦,或者像「賬篷」同樣立起來,或者擺成任何你以爲看着很酷的姿式。node
在過去幾個星期中,我對後端開發更感興趣(一部分緣由是由於我對 CSS 實在是抓狂)。我學習了關於如何在 Chromebook 上安裝 Ubuntu Linux(若是我理解的正確的話,ChromeOS 就是基於 Linux 內核基礎上開發的)。原本我是要安裝 Ubuntu 的,可是它涉及到切換到開發者模式的步驟,而且須要抹掉本地存儲而且要關閉 ChromeOS 中全部出色的安全功能。因爲以上緣由我決定找其餘解決方案。react
我發現 ChromeOS 運行的特別好。Google 已經在一些 Chromebook 機型上安裝了一些 Android 應用,除了設計和用戶體驗不是很好以外,Android 手機上能夠運行的任何程序均可以順利地在 ChromeOS 上運行。例如,我安裝了一個叫 Termux 的應用,它是一個在 Android 上不須要 root 權限的 Linux 模擬器。最近我一直在擺弄這個模擬器,如今我能夠告訴你,Fredrik Fornwall 作的這東西太棒了,令我印象深入。linux
我照着 Aurélien Giraud 寫的幾篇文章開始搭建環境。驚喜的是,還沒用一杯咖啡的工夫,我就在 Chromebook 上運行起了 Node.js 的服務和一個 NeDB 數據庫,並且根本不須要切換到開發者模式。若是你有個安卓設備,我強烈建議你收藏下 Aurélien 的教程而且照着試試。不須要多久,就能在手機上運行起來一個 Node.js 服務。android
雖然如今我用 Node 用的很爽,可是我也對一些寫服務端的語言感興趣,打算挑出幾個做爲深刻研究的備選語言。Go 是我正在學習的語言之一,它是 Google 在 2009 年推出的。如今已經變得十分熱門,名列 2016 年年度編程語言之中。ios
Go 在某些方面很像 C 和 C++,而且它的設計確實受到了它們的影響。然而,建立 Go 的主要動機是不喜歡這些歷史悠久語言的複雜性。所以,Go 特地設計成一種更容易使用的語言。git
例如,Go 語言中沒有「while」循環。涉及到循環的時候,你有且只有一個選擇:就是「for」循環。github
//一個經典的「while」循環
for i < 1000 {
//循環體
i++
}複製代碼
Go 語言中類型推導是可選的。你能夠用標準寫法聲明而且初始化一個變量,也能夠用簡易的方法來隱式的賦值。或採起一個快捷方式和隱式分配類型。
var x int = 2
//等同於
x := 2複製代碼
「if」和「else」的語句很簡單:
x := 5
if x > 10 {
fmt.Println("Greater than 10")
} else {
fmt.Println("Less than or equal to 10")
}複製代碼
同時 Go 的編譯速度也很快,而且標準庫中也提供了各類有用的包,這些包在網上都有很棒的文檔。而且它們在不少項目中被使用,包括一些家喻戶曉的名字例如 Google,Dropbox,Soundcloud,Twitch 以及 Uber。
我認爲若是 Go 對這些公司來講都足夠好的話,那麼可能也值得你看一看。對於任何一個準備邁出他後端開發的第一步的人而言,我結合在 Termux 上使用 Go 的經驗整理出了一些教程。若是你有一個 Android 設備,或者有一臺在 Google Play 有訪問權限的 Chromebook 上的,那麼安裝而且運行 Termux,咱們就能夠開始了。
若是你有一個常規的 Linux 設備,也可使用 Termux!Termux的教程對於任何支持 Go 的平臺都是通用的。
像其餘的 Android 應用同樣,Termux 只須要到應用商店搜索並點擊安裝,能夠十分簡單的下載安裝到你的設備上。裝好以後打開它你就會看見一個簡潔的空命令行。這裏我強烈推薦使用物理鍵盤(內置,USB 或者藍牙鍵盤均可以),若是手頭沒有鍵盤,那麼推薦你去下載一個叫「Hacker’s Keyboard」的安卓軟件。
正如 Aurélien 去年的教程中所說,Termux 不多被預裝。因此在終端中運行如下命令:
$ apt update
$ apt upgrade
$ apt install coreutils複製代碼
好。如今全部的東西都是最新的了,coreutils 將會幫助你更容易的切換到對應的文件目錄。讓咱們看看咱們如今在目錄中的哪一個位置。
$ pwd複製代碼
這個命令會返回一個路徑,會展現當前所在目錄的位置。若是咱們沒有在 /home 下,那讓咱們到「home」文件夾下看看那裏面有什麼:
$ cd $HOME && ls複製代碼
好,讓咱們爲 Go 教程新建一個目錄,而後到那個目錄去。而後咱們能夠建立一個文件叫作「server.go」。
$ mkdir go-tutorial && cd go-tutorial
$ touch server.go複製代碼
若是咱們輸入「ls」,咱們能夠在目錄中看到這個文件。如今,讓咱們先找一個文本編輯器。Aurélien 的教程推薦你使用 Vim,若是你喜歡用它,那就儘管用它。這裏還有一個對待「初學者更加友好」的編輯器 nano。咱們安裝它,而後打開咱們的 server.go 文件。
$ apt install nano
$ nano server.go複製代碼
棒!如今咱們能夠敲儘量多的咱們喜歡的代碼了。可是在咱們開始以前,讓咱們先安裝一下 Go 編譯器,由於咱們須要編譯器才能使咱們的代碼工做。使用 Ctrl+X 退出 nano,而後在命令行中輸入:
$ apt install golang複製代碼
如今,讓咱們回到 nano,而後開始寫咱們的服務端的代碼。
咱們將寫一個簡單的程序來啓動一個提供 HTML 頁面的服務,這個頁面讓用戶輸入密碼登陸而且能夠看到歡迎信息(或者若是密碼錯誤的話會看到「對不起,請重試」這類的消息)。在 nano 中,咱們寫入如下代碼:
//搭建一個 Web 服務
package main
import (
"fmt"
"net/http"
)複製代碼
咱們目前所作的是建立了一個包。Go 程序一般是在包中運行的。這是存儲和組織代碼的一種方式,而且讓你能夠更好更方便的調用其餘包中的方法。事實上,這也是咱們接下來要作的事情。咱們已經告訴 Go 導入「fmt」包以及標準庫中「net」包下的「http」包。這些包中的方法可讓咱們可使用「格式化 I/O」以及處理 HTTP 請求和響應。
如今,讓咱們在網上作這個東西。咱們繼續寫下如下代碼:
func main() {
http.ListenAndServe(":8080",nil)
fmt.Println("Server is listening at port 8080")
}複製代碼
像 C,C++,Java 等等,Go 程序從一個 main() 函數開始。咱們已經告訴服務器去監聽 8080 端口的請求(能夠任意選擇一個不一樣的數字),而且打印一個信息讓咱們知道它正在作什麼。
好了!讓咱們保存這個文件(Ctrl+O),退出(Ctrl+X)而後運行咱們的程序。在命令行中輸入:
go run server.go複製代碼
這個命令將會讓 Go 編譯器編譯而且運行這個程序。短暫的暫停後,程序應該運行了。你將但願看到如下輸出:
Server is listening at port 8080複製代碼
棒!你的服務器正在監聽 8080 端口的請求,不幸的是,它不知道如何處理它接收到的請求,由於咱們沒有告訴它如何迴應。這就是下一步,使用 Ctrl+C 結束服務程序,而後在 nano 中從新打開 server.go。
咱們須要服務器去「處理」請求,而後返回適當的響應。幸運的是,咱們導入的「http」包使這些變得很容易。
爲了可讀性更好,咱們在 import() 和 main() 之間插入如下代碼。咱們能夠在 main() 下面繼續寫代碼,實際上在任意位置都是能夠的,只要你喜歡就好。
不管如何,讓咱們來寫一個處理函數。
func handler (write http.ResponseWriter, req *http.Request) {
fmt.Fprint(write, "<h1>Hello!</h1>")
}複製代碼
這個函數有兩個參數,write 和 req。這兩個參數的類型被定義爲在「http」包中定義的 ResponseWriter 和 *Request,而後咱們讓服務中寫一些 HTML 做爲響應。
爲了使用這個函數,咱們須要在 main() 函數中調用它,添加下面這些加粗的代碼:
func main() {
http.ListenAndServe(":8080",nil)
fmt.Println("Server is listening at port 8080")
http.HandleFunc("/", handler)
}複製代碼
咱們添加的這一行從「http」包中調用 HandleFunc()。這個方法須要兩個參數。第一個參數是一個字符串,第二個使用咱們剛剛寫的 handle() 函數。咱們讓服務器用 handle() 處理對 web 根目錄下「/」的全部請求。
保存而且關閉 server.go,而後到控制檯,再次啓動服務。
go run server.go複製代碼
一樣,咱們應該看到輸出信息,讓咱們知道服務器正在監聽請求。那麼,爲何咱們不發送請求呢?打開你的 Web 瀏覽器而且訪問 http://localhost:8080/。
Chromebook 對於其餘瀏覽器的使用有着較大的限制,可是我發現 Chrome 在鏈接到任何本地端口的時候會有些很差用。從應用商店中下載 Mozilla Firefox for Android 能夠解決這個問題。
或者,你想徹底留在 Termux(爲何不呢?),那就試試 Lynx。這是 1992 年推出的一個基於文本的瀏覽器。這裏沒有圖片,沒有 CSS,固然也沒有 JavaScript。不過對於本教程來講是徹底夠用的,安裝並運行它:
$ apt install lynx
$ lynx localhost:8080複製代碼
在 Termux 中運行的 Lynx 瀏覽器中查看 Medium 的主頁
若是一切順利,你應該在你選擇的瀏覽器中看到一個標題「Hello!」。若是沒有,回到 nano 而後檢查 server.go 的代碼。我第一次發現的錯誤包括在 import() 語句使用大括號 {},而不是括號。還有搞錯了一些看上去像是點的逗號(也許我應該用 Ctrl+Alt+「+」來放大 Termux 中的字)。
咱們的服務如今用一個較短的 HTML 來響應 HTTP 請求。雖然算不上是下一個 Facebook,可是比咱們以前距離更近了一些。咱們來讓它變得更有趣一點。
總結一下:咱們要作一個頁面,要求用戶輸入密碼。若是密碼輸入錯誤,用戶會收到一條警告消息。若是密碼輸入正確,用戶就會看到一個「歡迎!」的消息。由於它是你本身機器上的服務,因此只有你知道密碼,所以它是一個很是獨特的網站。
首先,咱們把 HTML 響應變得更有趣一些。讓咱們回到咱們以前寫的 handler()
。粘貼全部如下的代碼,以粗體替代已經存在的內容(所有都在一行)。必定要當心引用的部分!我在開始和結束的地方用了雙引號,在 HTML 的部分用了單引號。確保一致。
func handler (write http.ResponseWriter, req *http.Request) {
fmt.Fprint(write, "<h1>Login</h1><form action='/log-in/' method='POST'> Password:<br> <input type='password' name='pass'><br> <input type='submit' value='Go!'></form>")
}複製代碼
當咱們運行服務的時候,HTML 應該呈現如下頁面:
前臺:Mozilla Firefox for Android;後臺:Lynx for Termux。
如今我感受我已經有點熟悉HTML了。簡單來講,咱們有一個頭和一個表單。表單的「action」屬性被設爲「/log-in/」它的方法被設置爲 POST。有兩個輸入字段:一個用於輸入密碼,另外一個用於提交表單。密碼字段被叫作「pass」。咱們稍後會用到這些名字。
如今若是咱們輸入密碼而且提交會發生什麼?咱們要向服務器發出另外一個 HTTP請求(「/log-in/」),所以咱們須要寫另外一個處理這個請求的方法。回到 Termux,在你選擇的編輯器中打開 server.go。
咱們要再寫另外一個函數(就我而言,我會在 handler() 和 main() 之間寫,可是你能夠按照適合你的方法去作)。這是另外一個處理 HTTP 「/log-in/」請求的方法,這是在用戶提交咱們以前作的表單時發出的。
func loginHandler (write http.ResponseWriter, req *http.Request){
password := req.FormValue("pass")
if password == "let-me-in" {
fmt.Fprint(write, "<h1>Welcome!</h1>")
} else {
fmt.Fprint(write, "<h3>Wrong password! Try again.</h3>")
}
}複製代碼
和以前同樣,這個方法有兩個參數,write 和 req,它們也被定義爲「http」包中已定義的相同類型。
而後咱們建立一個叫作 password 的變量,咱們把它設置成等於請求表單中「pass」的值。注意使用「:=」的隱式類型賦值,咱們能夠這樣作是由於密碼字段的值將始終做爲字符串發送。
接下來是一個「if」語句,使用「==」比較運算符來檢查密碼是否與「let-me-in」的一致。這固然取決於咱們如何定義正確的密碼。你能夠把這個字符串改爲任何你喜歡的。
若是字符串是相同的,你就登陸成功了!如今,咱們輸出了一個無聊的「歡迎」的消息。咱們接下來將會修改這個。
不然,若是字符串不一致,咱們就會輸出「重試」的消息。一樣,咱們可使這個變得更加有趣。首先,若是密碼錶單仍然可供用戶使用,這將是有用的。添加如下加粗的代碼。是和以前的 HTML 同樣形式的密碼:
func loginHandler (write http.ResponseWriter, req *http.Request){
password := req.FormValue("pass")
if password == "let-me-in" {
fmt.Fprint(write, "<h1>Welcome!</h1>")
} else {
fmt.Fprint(write, "**<h1>Login</h1><form action='/log-in/' method='POST'> Password:<br> <input type='password' name='pass'><br> <input type='submit' value='Go!'></form>**<h3 **style='color: white; background-color: red'**>Wrong password! Try again.</h3>")
}
}複製代碼
我還在「重試」消息裏添加了一些簡單的樣式。你也能夠不加,可是爲何不呢?讓咱們也對「歡迎」消息作一樣的處理:
func loginHandler (write http.ResponseWriter, req *http.Request){
password := req.FormValue("pass")
if password == "let-me-in" {
fmt.Fprint(write, "**<h1 style='color: white; background-color: navy; font-size: 72px'>**Welcome!</h1>")
} else {
fmt.Fprint(write, "<h1>Login</h1><form action='/log-in/' method='POST'> Password:<br> <input type='password' name='pass'><br> <input type='submit' value='Go!'></form><h3 style='color: white; background-color: red'>Wrong password! Try again.</h3>")
}
}複製代碼
差很少了!咱們寫了 loginHandler() 函數,但在咱們的 main() 函數中沒有引用它。添加如下加粗的代碼:
func main() {
http.ListenAndServe(":8080",nil)
fmt.Println("Server is listening at port 8080")
http.HandleFunc("/", handler)
http.HandleFunc("/log-in/", loginHandler)
}複製代碼
至此,咱們已經告訴服務若是它接收到一個「/log-in/」的請求(這將隨時發生在用戶點擊提交按鈕的時候),它使用 loginHandle()
方法作出響應。咱們已經完成了!server.go 的所有代碼應該與如下代碼一致:
//搭建一個 Web 服務
package main
import (
"fmt"
"net/http"
)
func handler (write http.ResponseWriter, req *http.Request) {
fmt.Fprint(write, "**<**h1>Login</h1><form action='/log-in/' method='POST'> Password:<br> <input type='password' name='pass'><br> <input type='submit' value='Go!'></form>")
}
func loginHandler (write http.ResponseWriter, req *http.Request){
password := req.FormValue("pass")
if password == "let-me-in" {
fmt.Fprint(write, "<h1 style='color: white; background-color: navy; font-size: 72px'>Welcome!</h1>")
} else {
fmt.Fprint(write, "<h1>Login</h1><form action='/log-in/' method='POST'> Password:<br> <input type='password' name='pass'><br> <input type='submit' value='Go!'></form><h3 style='color: white; background-color: red'>Wrong password! Try again.</h3>")
}
}
func main() {
http.ListenAndServe(":8080",nil)
fmt.Println("Server is listening at port 8080")
http.HandleFunc("/", handler)
http.HandleFunc("/log-in/", loginHandler)
}複製代碼
保存而且退出 nano,而後到命令行,咱們讓 Go 編譯器去編譯咱們的服務程序。這個命令只須要編譯程序一次,此後咱們就能夠隨時運行它。
go build server.go複製代碼
給它一點時間去編譯,而後輸入下面的命令:
./server複製代碼
你應該看到和以前同樣的「監聽」信息。如今,若是你打開瀏覽器而且輸入 http://localhost:8080,你將會被要求輸入密碼。若是咱們輸入的不正確,咱們就會看到下面的界面:
不對!
反之,若是咱們輸入正確的密碼:
Firfox 看上去彷佛比 Lynx 更熱情一些…
若是你已經看了這篇文章,我但願你能夠喜歡這個教程,並發現它對你有幫助。我把讀者放在和我同樣的位置上 — 對於web全棧開發十分感興趣而且打算學習服務端知識的新手。
固然,咱們在這裏建立的這個簡單的登陸頁面還有很長的路要走。你不會像咱們作的同樣將 HTML 寫入 handler 函數中(我正打算看看 Go 的 HTML 包有一些不錯的可選的模板),也不會在「if」語句中寫出正確的密碼。最好有一個存儲密碼和用戶名的數據庫,你的服務器每次收到登陸請求時會去查詢。
爲此,Termux 提供了一個 SQLite 包,而且 Node.js 中提供了各類數據庫的包。這個教程的一個很酷的延展方向是能夠去建立一個保存用戶名以及對應密碼的數據庫,而且容許新的用戶加入。你須要添加另一個輸入項,並修改 loginHanlder() 函數。
我已經表達了我對於 Termux 的觀點 — 它很棒,我但願它可以適於用更多的應用。不光是 Go 和 Node.js,我一樣用它成功的寫過而且編譯和運行了簡單的 C,C++,CoffeeScript,PHP 以及 Python 3.6等語言的代碼,而且仍然有一些其餘語言我沒有嘗試過(有人試過 Erlang/Lua/PicoLisp嗎?)
至於 Go,第一次使用令我很是滿意。我喜歡它專一於簡易性,而且我喜歡它的語法,並且它的文檔很容易理解,它讓我能夠根據個人理解去開發。一個初學者的意見是有價值的,這點就像是 C++ 和 Python 的結合。在某種程度上,這可能剛好是它的意義所在!
感謝你們的閱讀,首先,這是一篇 Go 語言的入門文章,不過做者的代碼有一點小問題,發表前我已經向做者提出問題,暫時尚未收到回覆,收到回覆後會在文章中更新,如今根據個人理解稍做分析,這是做者的最後一段代碼:
func main() {
http.ListenAndServe(":8080",nil)
fmt.Println("Server is listening at port 8080")
http.HandleFunc("/", handler)
http.HandleFunc("/log-in/", loginHandler)
}複製代碼
監聽是阻塞的執行,內部一直 runloop 等待網絡請求,不退出。因此監聽一旦打開,後續代碼都不會執行,直到按 ctrl+c 強制結束。這一點,咱們從 ListenAndServe
的源碼中看出:
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}複製代碼
所以做者的代碼中執行到 http.ListenAndServe(":8080",nil)
後,後續代碼都不會繼續執行。因此這裏應該先設置訪問路由,再監聽端口。不然這段代碼是沒法出現預期效果的。修改後代碼以下:
func main() {
http.HandleFunc("/", handler)
http.HandleFunc("/log-in/", loginHandler)
fmt.Println("Server is listening at port 8080")
http.ListenAndServe(":8080",nil)
}複製代碼
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃。