除去细节,理解 HTTP 构建的网络应用只要关注客户端和服务端的处理,服务器主要用来接收客户端的请求,然后返回响应,在接收请求并处理的过程中,最重要的莫过于路由 (Router)。
这里简单介绍如何使用 net/http
构建 HTTP 请求。
示例
如下是一个最简单的示例。
package main
import (
"fmt"
"net/http"
)
func IndexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello world")
}
func FoobarHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hi, foobar\n"))
}
func main() {
http.HandleFunc("/", IndexHandler)
http.Handle("/foobar", http.HandlerFunc(FoobarHandler))
http.ListenAndServe("127.0.0.1:8000", nil)
}
然后可以通过 curl http://127.0.0.1:8000
访问。
Handler
对于服务器来说,在接收请求的过程中,最重要的就是路由,也就是实现一个 Multiplexer
器,其中有一个内置的 DefautServeMux
,当然也可以自定义,其目的就是为了找到真正的处理函数 Handler
,并构建 Response
。
Handler 负责输出 HTTP 响应的头和正文,任何满足了 http.Handler
接口的对象都可作为一个处理器。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
Go 语言的 HTTP 包自带了几个函数用作常用处理器,比如 FileServer
NotFoundHandler
和 RedirectHandler
,如下是一个简单的示例。
package main
import (
"fmt"
"net/http"
"time"
)
type customHandler struct {}
func (cb *customHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Custom Handler!!! URI %s", r.URL.Path)
}
func main() {
var server *http.Server = &http.Server{
Addr: ":8080",
Handler: &customHandler{},
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
server.ListenAndServe()
}
错误处理
通常有几种方式。
http.Error()
在标准的 net/http
库中提供了一个 Error()
函数,可以通过如下方式使用。
func ServeHTTP(w http.ResponseWriter, r *http.Request) {
err := riskyFunc(r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
此时的错误信息将直接返回给用户,如果有些系统相关的信息返回那么可能会导致部分安全问题,其中 Error()
函数在 net/http/server.go
中定义,代码如下。
func Error(w ResponseWriter, error string, code int) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(code)
fmt.Fprintln(w, error)
}
自定义
实现也很简单,模仿上述的代码即可。
Middleware
现在多数 Web 组件允许通过组件式中间件 “过滤” 请求,也就是中间件的作用。
package main
import (
"fmt"
"net/http"
)
func LogMidWare(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("logging %#v\n", *r.URL)
h.ServeHTTP(w, r)
})
}
func main() {
http.Handle("/", LogMidWare(http.HandlerFunc(FoobarHandler)))
http.ListenAndServe("127.0.0.1:8000", nil)
}
源码解析
对应的代码在 $GOPATH/src/net/http
目录下,在 server.go
中,有默认的 Mux 实现,也就是 DefaultServeMux
。
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
如果用户没有创建自己的 MUX 那么实际使用的就是该变量。
Handler注册
默认调用的 Handler()
以及 HandleFunc()
实现如下。
func Handle(pattern string, handler Handler) {
DefaultServeMux.Handle(pattern, handler)
}
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
而根据如上的定义可知,真正调用的是 ServeMux
中的实现,上述两个接口真正调用的实际上是 ServeMux.Handle()
函数,而真正有效的是 mux.m[pattern] = muxEntry{h: handler, pattern: pattern}
这行代码。
监听服务端
在注册完用户需要的 Handler 之后,就开始调用 http.ListenAndServe()
监听端口并处理请求。除此之外,还提供了 TLS 版本的 http.ListenAndServeTLS()
接口。
ListenAndServe()
|-net.Listen() 开始监听
|-Server.Serve()
|-Accetp()
|-Server.newConn() 新建链接
|-conn.serve() 启动单独的协程开始处理
|-serverHandler.ServeHTTP() 真正的处理
|-ServeMux.ServeHTTP() 如果没有定义,那么使用默认的MUX
| |-ServeMux.Handler() 这里会从Request.URL.Path中取出请求的路径
| | |-ServeMux.handler()
| | |-Servemux.match() 匹配map表中的路由规则,也就是通过Handle()/HandleFunc()注册回调函数
| |-Handler.ServeHTTP() 这里实际上已经实现了MAP的功能,也就是路由
|-Handler.ServeHTTP() 如果在初始化时有自定义的Handler,这里会调用用户的代码
对于用户自定义的实现,有两种方式,一种是在新建 http.Server 时设置 Handler 成员,此时需要同时处理 URI 的路由规则。
另外一种,也是最常用的,自定义 MUX 以及 Handler 规则。
常用示例
TLS
服务端
服务端加载域证书对 domain-cert.pem
domain-key.pem
,分别为证书以及私钥,如下是最简单的实现。
package main
import (
"fmt"
"log"
"net/http"
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello world")
}
func main() {
http.HandleFunc("/", indexHandler)
http.ListenAndServeTLS(
"127.0.0.1:8000",
"out/domain-cert.pem",
"out/domain-key.pem",
nil,
)
}
如果私钥进行了加密,那么上述方式会报 failed to parse private key
的错误,此时就需要通过如下方式。
package main
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"log"
"net/http"
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello world")
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", indexHandler)
keyBytes, err := ioutil.ReadFile("out/domain-key.pem")
if err != nil {
log.Fatalf("Read private key file failed, %v\n", err)
}
val, _ := pem.Decode(keyBytes)
if x509.IsEncryptedPEMBlock(val) {
key, err := x509.DecryptPEMBlock(val, []byte("YourPassHere"))
if err != nil {
log.Fatalf("Decrypt private key failed, %v\n", err)
}
keyBytes = pem.EncodeToMemory(&pem.Block{
Type: val.Type, Bytes: key,
})
}
certBytes, err := ioutil.ReadFile("out/domain-cert.pem")
if err != nil {
log.Fatalf("Read certificate file failed, %v\n", err)
}
pair, err := tls.X509KeyPair(certBytes, keyBytes)
if err != nil {
log.Fatalf("Load key pair failed, %v\n", err)
}
cfg := &tls.Config{
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{
tls.CurveP521, tls.CurveP384, tls.CurveP256,
},
PreferServerCipherSuites: true,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
},
Certificates: []tls.Certificate{pair},
}
svr := http.Server{
Addr: "127.0.0.1:8000",
Handler: mux,
TLSConfig: cfg,
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
}
// 这里设置为空,会默认使用上述的证书
log.Fatal(svr.ListenAndServeTLS("", ""))
}
客户端
一个证书文件中是可以保存多个证书的,所以,如下文件中会循环读取。
package main
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"log"
"net/http"
)
func main() {
rest, err := ioutil.ReadFile("out/certs.pem")
if err != nil {
log.Fatalf("Read certificate file failed, %v\n", err)
}
var cert *pem.Block
certs := x509.NewCertPool()
for {
cert, rest = pem.Decode(rest)
if cert == nil {
break
}
if ok := certs.AppendCertsFromPEM(pem.EncodeToMemory(cert)); !ok {
log.Fatalf("Add cert failed.")
}
}
cfg := &tls.Config{
RootCAs: certs,
InsecureSkipVerify: false,
}
//cfg.BuildNameToCertificate()
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: cfg,
},
}
req, err := http.NewRequest("GET", "https://files.cargo.com:8000", nil)
if err != nil {
log.Fatalf("Create request failed, %v\n", err)
}
resp, err := client.Do(req)
if err != nil {
log.Fatalf("Get request failed, %v\n", err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Read body failed, %v\n", err)
}
log.Printf("response: %v", string(body))
}
常见错误
certificate relies on legacy Common Name field, use SANs...
建议不要使用 CommonName 需要使用 Subject Alternative Name 替换。
参考
为了防止由于压力过大导致雪崩,可以限制客户端的数量,详细可以参考 golang.org/x/net/netutil/listen.go
中关于 LimitListener
的实现。