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