37.Go HTTP Server

Hello World

開(kāi)始使用Go編寫(xiě)Web服務(wù)器的典型方法是使用標(biāo)準(zhǔn)庫(kù)中的net/http模塊朴读。

如下代碼是最簡(jiǎn)單的HTTP服務(wù)器實(shí)現(xiàn),它對(duì)任何HTTP請(qǐng)求都響應(yīng)“Hello World”。

server.go:

package main

import (
    "log"
    "net/http"
)

func main() {
    // 所有URLs都被這個(gè)函數(shù)處理
    // http.HandleFunc使用了DefaultServeMux
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, world!"))
    })

    // Continue to process new requests until an error occurs
    log.Fatal(http.ListenAndServe(":8080", nil))
}

使用以下命令運(yùn)行服務(wù):

$ go run server.go

或者編譯后再運(yùn)行饲漾。

$ go build server.go
$ ./server

服務(wù)將偵聽(tīng)指定的端口(8080)。 可以使用任何HTTP客戶端對(duì)其進(jìn)行測(cè)試金赦。 這是cURL的示例:

curl -i http://localhost:8080/
HTTP/1.1 200 OK
Date: Wed, 20 Jul 2016 18:04:46 GMT
Content-Length: 13
Content-Type: text/plain; charset=utf-8

Hello, world!

按Ctrl+C結(jié)束進(jìn)程.

創(chuàng)建一個(gè)HTTPS服務(wù)器

生成證書(shū)

為了運(yùn)行HTTPS服務(wù)器蕴坪,需要一個(gè)證書(shū)。通過(guò)執(zhí)行以下命令块蚌,可以使用openssl生成自簽名證書(shū):

openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout key.pem -out cert.pem -subj“ /CN=example.com” -3650天
參數(shù)為:

  • req 使用證書(shū)申請(qǐng)工具
  • x509 創(chuàng)建自簽名證書(shū)
  • newkey rsa:4096 通過(guò)使用4096位密鑰長(zhǎng)度的RSA算法來(lái)創(chuàng)建新密鑰和證書(shū)
  • sha256 強(qiáng)制使用SHA256哈希算法,這些算法被主流瀏覽器認(rèn)為是安全的(截至2017年)
  • nodes 禁用私鑰的密碼保護(hù)膘格。如果沒(méi)有此參數(shù)峭范,則服務(wù)器每次啟動(dòng)時(shí)都必須詢問(wèn)你密碼
  • keyout 命名要在其中寫(xiě)入密鑰的文件
  • out 將文件寫(xiě)入證書(shū)的位置
  • subj 定義此證書(shū)對(duì)其有效的域名
  • days 該證書(shū)應(yīng)有效多少天, 約3650,10年

注意:可以使用自簽名證書(shū),例如用于內(nèi)部項(xiàng)目瘪贱,調(diào)試纱控,測(cè)試等辆毡。那里的任何瀏覽器都會(huì)提到此證書(shū)不安全。為了避免這種情況甜害,證書(shū)必須由證書(shū)頒發(fā)機(jī)構(gòu)簽名舶掖。免費(fèi)的證書(shū)頒發(fā)服務(wù)可以使用: https://letsencrypt.org

編寫(xiě)Go代碼

使用以下代碼來(lái)處理服務(wù)器的TLS配置。 cert.pem和key.pem是SSL證書(shū)和密鑰尔店,是使用上述命令生成的眨攘。

package main

import (
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, world!"))
    })

    log.Fatal(http.ListenAndServeTLS(":443","cert.pem","key.pem", nil))
}

自定義server和mux

package main

import (
    "log"
    "net/http"
)

func main() {

    // 創(chuàng)建mux來(lái)路由請(qǐng)求
    m := http.NewServeMux()

    // 處理所有請(qǐng)求
    m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, world!"))
    })

    // 啟動(dòng)服務(wù)監(jiān)聽(tīng)8000端口
    s := &http.Server{
        Addr:    ":8000",
        Handler: m,
    }

    // 持續(xù)處理請(qǐng)求直到錯(cuò)誤發(fā)生
    log.Fatal(s.ListenAndServe())
}

按Ctrl+C結(jié)束進(jìn)程

使用模板響應(yīng)HTTP請(qǐng)求

可以使用Go中的模板將響應(yīng)寫(xiě)入http.ResponseWriter。 如果你希望創(chuàng)建動(dòng)態(tài)頁(yè)面嚣州,這是一種便捷的工具期犬。

要了解模板,請(qǐng)參見(jiàn)文本和HTML模板章節(jié)避诽。

繼續(xù)使用簡(jiǎn)單的示例來(lái)利用html/template響應(yīng)HTTP請(qǐng)求:

package main

import(
    "html/template"
    "net/http"
    "log"
)

func main(){
    http.HandleFunc("/",WelcomeHandler)
    http.ListenAndServe(":8080",nil)
}

type User struct{
    Name string
    nationality string //unexported field.
}

func check(err error){
    if err != nil{
        log.Fatal(err)
    }
}

func WelcomeHandler(w http.ResponseWriter, r *http.Request){
    if r.Method == "GET"{
        t,err := template.ParseFiles("welcomeform.html")
        check(err)
        t.Execute(w,nil)
    }else{
        r.ParseForm()
        myUser := User{}
        myUser.Name = r.Form.Get("entered_name")
        myUser.nationality = r.Form.Get("entered_nationality")
        t, err := template.ParseFiles("welcomeresponse.html")
        check(err)
        t.Execute(w,myUser)
    }
}

welcomeform.html:

<head>
    <title> Help us greet you </title>
</head>
<body>
    <form method="POST" action="/">
        Enter Name: <input type="text" name="entered_name">
        Enter Nationality: <input type="text" name="entered_nationality">
        <input type="submit" value="Greet me!">
    </form>
</body>

welcomeresponse.html:

<head>
    <title> Greetings, {{.Name}} </title>
</head>
<body>
    Greetings, {{.Name}}.<br>
    We know you are a {{.nationality}}!
</body>

注意:

  • 確保.html文件位于正確的目錄中。
  • 啟動(dòng)服務(wù)器后可以訪問(wèn)http://localhost:8080/
  • 提交表單后可以看到璃谨,模板包無(wú)法按預(yù)期解析struct中未導(dǎo)出的國(guó)籍字段沙庐。

使用ServeMux提供內(nèi)容

一個(gè)簡(jiǎn)單的靜態(tài)文件服務(wù)器如下所示:

package main

import (
    "net/http"
)

func main() {
    muxer := http.NewServeMux()
    fileServerCss := http.FileServer(http.Dir("src/css"))
    fileServerJs := http.FileServer(http.Dir("src/js"))
    fileServerHtml := http.FileServer(http.Dir("content"))
    muxer.Handle("/", fileServerHtml)
    muxer.Handle("/css", fileServerCss)
    muxer.Handle("/js", fileServerJs)
    http.ListenAndServe(":8080", muxer)
}

使用處理函數(shù)

HandleFunc在server mux(router)中注冊(cè)給定模式的處理程序功能。

可以傳遞匿名函數(shù)佳吞,如之前Hello World示例:

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hello, world!")
}

也我們也可以傳遞HandlerFunc類型拱雏。 換句話說(shuō),我們可以傳遞任何尊重以下簽名的函數(shù):
func FunctionName(w http.ResponseWriter底扳,req * http.Request)

我們重寫(xiě)前面的示例铸抑,將引用傳遞給先前定義的HandlerFunc。這是完整的示例:

package main

import (
    "fmt"
    "net/http"
)

// A HandlerFunc function
// Notice the signature of the function
func RootHandler(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintln(w, "Hello, world!")
}

func main() {
    // Here we pass the reference to the `RootHandler` handler function
    http.HandleFunc("/", RootHandler)
    panic(http.ListenAndServe(":8080", nil))
}

當(dāng)然可以為不同的路徑定義幾個(gè)函數(shù)處理程序衷模。

package main

import (
    "fmt"
    "log"
    "net/http"
)

func FooHandler(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintln(w, "Hello from foo!")
}

func BarHandler(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintln(w, "Hello from bar!")
}

func main() {
    http.HandleFunc("/foo", FooHandler)
    http.HandleFunc("/bar", BarHandler)

    log.Fatal(http.ListenAndServe(":8080", nil))
}

下面是使用cURL的測(cè)試結(jié)果:

$ curl -i localhost:8080/foo
HTTP/1.1 200 OK
Date: Wed, 20 Jul 2016 18:23:08 GMT
Content-Length: 16
Content-Type: text/plain; charset=utf-8

Hello from foo!

$ curl -i localhost:8080/bar
HTTP/1.1 200 OK
Date: Wed, 20 Jul 2016 18:23:10 GMT
Content-Length: 16
Content-Type: text/plain; charset=utf-8

Hello from bar!

$ curl -i localhost:8080/
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Wed, 20 Jul 2016 18:23:13 GMT
Content-Length: 19

404 page not found

獲取請(qǐng)求參數(shù)和正文

下面是一個(gè)開(kāi)發(fā)API時(shí)常見(jiàn)的簡(jiǎn)單示例鹊汛,該API區(qū)分請(qǐng)求的HTTP方法,獲取請(qǐng)求參數(shù)和正文:

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

type customHandler struct{}

// ServeHTTP實(shí)現(xiàn)了net/http包中的http.Handler接口
func (h customHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

    // ParseForm 方法解析請(qǐng)求參數(shù)并且讓r.Form可用
    r.ParseForm()

    // r.Form是map結(jié)構(gòu)的請(qǐng)求參數(shù), 它的類型是url.Values, 即map[string][]string
    queryMap := r.Form

    switch r.Method {
    case http.MethodGet:
        // 處理GET請(qǐng)求
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(fmt.Sprintf("Query string values: %s", queryMap)))
        return
    case http.MethodPost:
        // 處理POST請(qǐng)求
        body, err := ioutil.ReadAll(r.Body)
        if err != nil {
            // 解析request body時(shí)出錯(cuò)
            w.WriteHeader(http.StatusBadRequest)
            return
        }
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(fmt.Sprintf("Query string values: %s\nBody posted: %s", queryMap, body)))
        return
    }

    // 其他的HTTP methods(PUT,PATCH等)不被以上代碼處理,所有返回405錯(cuò)誤
    w.WriteHeader(http.StatusMethodNotAllowed)
}

func main() {
    // http.Handle類似于http.HandleFunc,使用了DefaultServeMux
    http.Handle("/", customHandler{})

    // Continue to process new requests until an error occurs
    log.Fatal(http.ListenAndServe(":8080", nil))
}

curl測(cè)試輸出:

$ curl -i 'localhost:8080?city=Seattle&state=WA' -H 'Content-Type: text/plain' -X GET
HTTP/1.1 200 OK
Date: Fri, 02 Sep 2016 16:36:24 GMT
Content-Length: 51
Content-Type: text/plain; charset=utf-8

Query string values: map[city:[Seattle] state:[WA]]%

$ curl -i 'localhost:8080?city=Seattle&state=WA' -H 'Content-Type: text/plain' -X POST -d "some post data"
HTTP/1.1 200 OK
Date: Fri, 02 Sep 2016 16:36:35 GMT
Content-Length: 79
Content-Type: text/plain; charset=utf-8

Query string values: map[city:[Seattle] state:[WA]]
Body posted: some post data%

$ curl -i 'localhost:8080?city=Seattle&state=WA' -H 'Content-Type: text/plain' -X PUT
HTTP/1.1 405 Method Not Allowed
Date: Fri, 02 Sep 2016 16:36:41 GMT
Content-Length: 0
Content-Type: text/plain; charset=utf-8

其他資源:

  • http.Handler接口
  • http.ResponseWriter
  • http.Request
  • 可以使用的方法和狀態(tài)常量

http.ServeMux提供了一個(gè)多路復(fù)用器阱冶,該多路復(fù)用器調(diào)用HTTP請(qǐng)求的處理程序刁憋。
標(biāo)準(zhǔn)庫(kù)多路復(fù)用器的替代方法包括:

Gorilla Mux

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市木蹬,隨后出現(xiàn)的幾起案子至耻,更是在濱河造成了極大的恐慌,老刑警劉巖镊叁,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尘颓,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡晦譬,警方通過(guò)查閱死者的電腦和手機(jī)疤苹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蛔添,“玉大人痰催,你說(shuō)我怎么就攤上這事兜辞。” “怎么了夸溶?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵逸吵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我缝裁,道長(zhǎng)扫皱,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任捷绑,我火速辦了婚禮韩脑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘粹污。我一直安慰自己段多,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布壮吩。 她就那樣靜靜地躺著进苍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸭叙。 梳的紋絲不亂的頭發(fā)上觉啊,一...
    開(kāi)封第一講書(shū)人閱讀 49,950評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音沈贝,去河邊找鬼杠人。 笑死,一個(gè)胖子當(dāng)著我的面吹牛宋下,可吹牛的內(nèi)容都是我干的嗡善。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼学歧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼滤奈!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起撩满,我...
    開(kāi)封第一講書(shū)人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蜒程,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后伺帘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體昭躺,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年伪嫁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了领炫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡张咳,死狀恐怖帝洪,靈堂內(nèi)的尸體忽然破棺而出似舵,到底是詐尸還是另有隱情,我是刑警寧澤葱峡,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布砚哗,位于F島的核電站,受9級(jí)特大地震影響砰奕,放射性物質(zhì)發(fā)生泄漏蛛芥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一军援、第九天 我趴在偏房一處隱蔽的房頂上張望仅淑。 院中可真熱鬧,春花似錦胸哥、人聲如沸涯竟。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)昆禽。三九已至,卻和暖如春蝇庭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捡硅。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工哮内, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人壮韭。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓北发,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親喷屋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子琳拨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • http http/cgi http/cookiejar http/fcgi http/httptest http...
    DevilRoshan閱讀 1,124評(píng)論 0 0
  • http 包實(shí)現(xiàn)了http客戶端與服務(wù)端的實(shí)現(xiàn) 1.創(chuàng)建http客戶端 2.客戶端發(fā)起get,post,postF...
    酷走天涯閱讀 1,198評(píng)論 0 1
  • 介紹在Go中,中間件可用于在處理程序函數(shù)之前和之后執(zhí)行代碼屯曹。 它使用單功能接口的能力狱庇。可以隨時(shí)引入而不會(huì)影響其他中...
    asdzxc閱讀 219評(píng)論 0 0
  • 上篇提到 DefaultServerMux 作為默認(rèn)的 HTTP Server 框架太過(guò)簡(jiǎn)單恶耽,缺少很多功能密任。這篇我...
    小小小超子閱讀 4,633評(píng)論 1 2
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭偷俭,有人歡樂(lè)有人憂愁浪讳,有人驚喜有人失落,有的覺(jué)得收獲滿滿有...
    陌忘宇閱讀 8,531評(píng)論 28 53