使用Golang 搭建http web伺服器

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

Golang在搭建web伺服器方面的能力是毋庸置疑的。官方已經有提供net/http包為搭建http伺服器做準備。使用這個包能很簡單地對web的路由,靜態檔案,模版,cookie等資料進行設定。至於這個包是否好用,這個就見仁見智了。你可以從net包開始封裝一個web架構,當然也可以基於http包封裝一個web架構。但是不論你是打算怎麼樣做,瞭解基本的net/http包一定是你借鑒的基礎。

需求

我們要做兩個簡單的後台web系統。這個系統簡單到只有兩個頁面:登陸和首頁。

1 登陸頁面

 

登陸頁面需要提交使用者名稱和密碼,將使用者名稱和密碼和mysql資料庫中的使用者名稱密碼比對達到驗證的效果。mysql資料庫的go語言驅動推薦使用mymysql(https://github.com/ziutek/mymysql)。

 

當使用者名稱和密碼正確的時候,需要在cookie中種下使用者名稱和加密後的密鑰來進行cookie認證。我們不對cookie設定ExpireTime,這樣這個cookie的有效期間就是瀏覽器開啟到瀏覽器關閉的session期間。

 

另外,這個頁面還需要載入一個js。提交使用者名稱和密碼的是由js進行ajax post命令進行查詢的。

 

這個頁面需要載入css,進行頁面排版

 

2 首頁

首頁是非常簡單,但它是一個動態網頁面。

 

首先右上方的”歡迎登陸, 管理員:yejianfeng“。這裡的使用者名稱yejianfeng是根據不同的使用者會進行變化的。這裡需要用到模版,我們又會用到了一個模版包html/template。這個包的作用就是載入模版。

 

其次這個頁面也需要的是css,js(退出系統的刪除cookie操作由js控制)

路由

分析下訪問路徑,會有幾個檔案:

/admin/index -- 首頁

/login/index --登陸頁顯示

/ajax/login -- 登陸的ajax動作

/css/main.css -- 擷取main的css檔案

/js/login.js -- 擷取登陸的js檔案

 

在net/http包中,動態檔案的路由和靜態檔案的路由是分開的,動態檔案使用http.HandleFunc進行設定靜態檔案就需要使用到http.FileServer

package mainimport (    "net/http")func main() {    http.Handle("/css/", http.FileServer(http.Dir("template")))    http.Handle("/js/", http.FileServer(http.Dir("template")))        http.HandleFunc("/admin/", adminHandler)    http.HandleFunc("/login/",loginHandler)    http.HandleFunc("/ajax/",ajaxHandler)    http.HandleFunc("/",NotFoundHandler)    http.ListenAndServe(":8888", nil)}

這裡的http.FileServer(http.Dir("template"))的路徑是怎麼算的要注意下了。相對路徑都是從當前執行路徑路徑下開始算的,這裡設定的路徑樹是這樣:

關於http.HandleFunc如果不理解請參考我的上一篇文章

http://www.cnblogs.com/yjf512/archive/2012/08/22/2650873.html

 

處理器

這裡需要定製4個handler對應相應的一級路徑。我們將這些個handler都放入到route.go檔案中

頁面404處理器

main中的邏輯是當/admin/ /login/ /ajax/都不符合路徑的時候就進入404頁面處理器NotFoundHandler

func NotFoundHandler(w http.ResponseWriter, r *http.Request) {    if r.URL.Path == "/" {        http.Redirect(w, r, "/login/index", http.StatusFound)    }        t, err := template.ParseFiles("template/html/404.html")    if (err != nil) {        log.Println(err)    }    t.Execute(w, nil)}

 

這段邏輯是很清晰的:如果路徑是"/" 即訪問路徑是http://192.168.100.166:8888/ 的時候,訪問會跳轉到登陸頁面,否則當訪問路徑不滿足制定的路由,讀取404模版,顯示404。

 

強烈推薦使用template.ParseFile模版檔案解析

template包中也提供了Parse這類直接在代碼中寫上模版的函數。使用起來,你會發現這類函數遠沒有ParseFile好用,原因很明顯,如果你把模版寫在代碼中,一旦模版需要進行小細節的修改,也需要重新編譯。並且我們使用模版的目的就是將顯示邏輯和商務邏輯分離,Parse會導致整個代碼是不可維護!

當然有人會考慮到效率問題,一個是讀取檔案,一個是直接讀取記憶體。但是我覺得這點效率差別使用者根本不會察覺到,犧牲極小的效能保證工程性是很值得的。

 

ParseFile中的路徑問題也是很容易犯的問題。這裡的填寫相對路徑,則檔案是從當前執行的路徑開始算。

比如這個路徑,執行bin/webdemo,template/html/404.html就對應/go/gopath/template/html/404.html

 

這一步後續動作是在template/html檔案夾中建立404.html頁面

登陸頁面處理器

func loginHandler(w http.ResponseWriter, r *http.Request) {    pathInfo := strings.Trim(r.URL.Path, "/")    parts := strings.Split(pathInfo, "/")    var action = ""    if len(parts) > 1 {        action = strings.Title(parts[1]) + "Action"    }    login := &loginController{}    controller := reflect.ValueOf(login)    method := controller.MethodByName(action)    if !method.IsValid() {        method = controller.MethodByName(strings.Title("index") + "Action")    }    requestValue := reflect.ValueOf(r)    responseValue := reflect.ValueOf(w)    method.Call([]reflect.Value{responseValue, requestValue})}

 

 

根據MVC思想,對具體的邏輯內容使用不同的Controller,這裡定義了一個loginController, 使用reflect將pathInfo映射成為controller中的方法。

這樣做的好處顯而易見:

1 清晰的檔案邏輯。不同的一級目錄分配到不同的控制器,不同的控制器中有不同的方法處理。這些控制器放在一個獨立的檔案中,目錄結構清晰乾淨。

2 路由和商務邏輯分開。 main中的http.HandleFunc處理一級路由,route處理二級路由,controller處理商務邏輯,每個單元分開處理。

 

好了,下面需要定義loginContrller,我們另外起一個檔案loginController.go

package mainimport (    "net/http"    "html/template")type loginController struct {}func (this *loginController)IndexAction(w http.ResponseWriter, r *http.Request) {    t, err := template.ParseFiles("template/html/login/index.html")    if (err != nil) {        log.Println(err)    }    t.Execute(w, nil)}

 

 

下面需要建立template/html/login/index.html

這個檔案中包含mian.css, jquery.js, base.js。 建立這些css和js。具體的css和js內容請看github源碼

js中的邏輯是這樣寫的:當login表單提交的時候,會調用/ajax/login進行驗證操作,下面就開始寫ajax的處理器

ajax處理器

route中的ajaxHandler和loginHandler是大同小異,這裡就忽略不說了,具體說一下ajaxController

package mainimport (    "net/http"    "github.com/ziutek/mymysql/autorc"    _ "github.com/ziutek/mymysql/thrsafe"    "encoding/json")type Result struct{    Ret int    Reason string    Data interface{}}type ajaxController struct {}func (this *ajacController)LoginAction(w http.ResponseWriter, r *http.Request) {    w.Header().Set("content-type", "application/json")    err := r.ParseForm()    if err != nil {        OutputJson(w, 0, "參數錯誤", nil)        return    }        admin_name := r.FormValue("admin_name")    admin_password := r.FormValue("admin_password")        if admin_name == "" || admin_password == ""{        OutputJson(w, 0, "參數錯誤", nil)        return    }        db := mysql.New("tcp", "", "192.168.199.128", "root", "test", "webdemo")    if err := db.Connect(); err != nil {        OutputJson(w, 0, "資料庫操作失敗", nil)        return    }    defer db.Close()        rows, res, err := db.Query("select * from webdemo_admin where admin_name = '%s'", admin_name)    if err != nil {        OutputJson(w, 0, "資料庫操作失敗", nil)        return    }        name := res.Map("admin_password")    admin_password_db := rows[0].Str(name)        if admin_password_db != admin_password {        OutputJson(w, 0, "密碼輸入錯誤", nil)        return    }        // 存入cookie,使用cookie儲存    expiration := time.Unix(1, 0)    cookie := http.Cookie{Name: "admin_name", Value: rows[0].Str(res.Map("admin_name")), Path: "/"}    http.SetCookie(w, &cookie)        OutputJson(w, 1, "操作成功", nil)    return}func OutputJson(w http.ResponseWriter, ret int, reason string, i interface{}) {    out := &Result{ret, reason, i}    b, err := json.Marshal(out)    if err != nil {        return    }    w.Write(b)}

 

這段代碼有幾個地方可以看看:

如何設定header:

w.Header().Set("content-type", "application/json")

 

如何解析參數:

err := r.ParseForm()

admin_name := r.FormValue("admin_name")

 

如何串連資料庫

db := mysql.New("tcp", "", "192.168.199.128", "root", "test", "webdemo")

 

當然這裡得需要有個資料庫和資料表,建表和建表的sql檔案在github上能看到

create table webdemo_admin

(

admin_id int not null auto_increment,

admin_name varchar(32) not null,

admin_password varchar(32) not null,

primary key(admin_id)

);

 

如何設定cookie

cookie := http.Cookie{Name: "admin_name", Value: rows[0].Str(res.Map("admin_name")), Path: "/"}

http.SetCookie(w, &cookie)

 

首頁處理器

adminHandler的邏輯比其他Handler多的一個是需要 擷取cookie

cookie, err := r.Cookie("admin_name")

 

並且在傳遞給controller的時候需要將admin_name傳遞進去

func adminHandler(w http.ResponseWriter, r *http.Request) {    // 擷取cookie    cookie, err := r.Cookie("admin_name")    if err != nil || cookie.Value == ""{        http.Redirect(w, r, "/login/index", http.StatusFound)    }        pathInfo := strings.Trim(r.URL.Path, "/")    parts := strings.Split(pathInfo, "/")        admin := &adminController{}    controller := reflect.ValueOf(admin)    method := controller.MethodByName(action)    if !method.IsValid() {        method = controller.MethodByName(strings.Title("index") + "Action")    }    requestValue := reflect.ValueOf(r)    responseValue := reflect.ValueOf(w)    userValue := reflect.ValueOf(cookie.Value)    method.Call([]reflect.Value{responseValue, requestValue, Uservalue})}

其他的部分都是一樣的了。

它對應的Controller的Action是

type User struct {    UserName string}type adminController struct {}func (this *adminController)IndexAction(w http.ResponseWriter, r *http.Request, user string) {    t, err := template.ParseFiles("template/html/admin/index.html")    if (err != nil) {        log.Println(err)    }    t.Execute(w, &User{user})}

這裡就將user傳遞出去給admin/index模版

模版內部使用{{.UserName}}進行參數顯示

 

後記

至此,這個基本的webdemo就完成了。啟動服務之後,就會在8888連接埠開啟了web服務。

當然,這個web服務還有許多東西可以最佳化,個人資訊驗證,公用模板的提煉使用,資料庫的密碼使用密文等。但是這個例子中已經用到了搭建web伺服器最基本的幾個技能了。

 

原始碼

本文所涉及的原始碼放在github上

https://github.com/jianfengye/webdemo

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.