开发 web 服务程序
文章目录
简介
开发简单 web 服务程序 cloudgo,了解 web 服务器工作原理。
开发环境
- CentOS7
- go 1.9.4 linux/amd64
Go的http包
使用http包编写的简单web服务器
下面是一个简单的web服务器,实现在客户端访问http://127.0.0.1:9090/
的时候响应内容为Hello World!
package main
import (
"fmt"
"net/http"
"strings"
"log"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World!"))
}) //设置访问的路由
err := http.ListenAndServe(":9090", nil) //设置监听的端口
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
从上面的代码可以看到,要编写一个Web服务器很简单,首先调用http.HandleFunc()
设置路由和响应处理函数,调用http.ListenAndServe()
去监听端口,等待客户端访问即可。那http包又为我们做了什么呢,接下来我将分析一下http包的代码执行流程。
http包源码分析
http包有关路由部分
根据上面代码,首先是调用了http.HandleFunc()
,它的定义如下,实现了将传入的处理响应函数与对应的path进行匹配。
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
所以在Handle()
函数中默认的路由是怎样匹配的呢,先看下面的两个struct,它们存放了默认的路由规则
type ServeMux struct {
mu sync.RWMutex //锁机制,因为请求会涉及到并发处理
m map[string]muxEntry //路由规则,使用map将string对应mux实体,这里的string是注册的路由表达式
hosts bool //是否在任意的规则中带有host信息
}
type muxEntry struct {
explicit bool //是否精确匹配
h Handler //这个路由表达式对应的处理响应函数
pattern string //匹配字符串
}
根据http.HandleFunc()
中的代码,执行了mux.Handle()
,这个函数对传入的path进行解析,然后向ServeMux
中添加路由规则
// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("http: invalid pattern " + pattern)
}
if handler == nil {
panic("http: nil handler")
}
if mux.m[pattern].explicit {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
//增加一个新的匹配规则
mux.m[pattern] = muxEntry{
explicit: true, h: handler, pattern: pattern}
//根据path的第一个字母判断是否有host
if pattern[0] != '/' {
mux.hosts = true
}
// Helpful behavior:
// If pattern is /tree/, insert an implicit permanent redirect for /tree.
// It can be overridden by an explicit registration.
n := len(pattern)
if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
// If pattern contains a host name, strip it and use remaining
// path for redirect.
path := pattern
if pattern[0] != '/' {
// In pattern, at least the last character is a '/', so
// strings.Index can't be -1.
path = pattern[strings.Index(pattern, "/"):]
}
url := &url.URL{
Path: path}
mux.m[pattern[0:n-1]] = muxEntry{
h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
}
}
既然添加了路由规则,那么如果客户端进行访问,是怎样查找到对应的路由规则呢,对过程mux.ServerHTTP->mux.Handler->mux.handler->mux.match
进行追踪,找到了路由匹配函数match()
。这个函数的实现解释了为什么会匹配最长的最佳匹配,比如传入/user/hh,不是先匹配/user/,而是匹配了/user/hh。
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
var n = 0
for k, v := range mux.m {
if !pathMatch(k, path) {
continue
}
//如果匹配到了一个规则,还会继续匹配并且判断path的长度是否最长
if h == nil || len(k) > n {
n = len(k)
h = v.h
pattern = v.pattern
}
}
return
}
http包有关监听与服务部分
接下来就是执行http.ListenAndServe()
,可以发现这个函数首先实例化了Server
,接着调用了Server.ListenAndServe()
。
func ListenAndServe(addr string, handler Handler) error {
server := &Server