Go语言搭建Web服务器
Go语言中提供了完善的net/http包,通过这些我们可以自己搭建一个本地web服务,同时还可以通过net/http包对cookie,静态文件的能够进行设置与操作。代码如下所示
import (
"net/http"
"fmt"
"strings"
"log"
)
func main() {
//设置访问的路由
http.HandleFunc("/",myGoWebService)
//设置监听端口
err := http.ListenAndServe(":9090",nil)
if err != nil {
log.Fatal("ListenAndServe: ",err)
}
}
func myGoWebService(rw http.ResponseWriter,request *http.Request) {
//解析传过来的参数,默认不会解析
request.ParseForm()
//打印信息
fmt.Println(request.Form)
//打印URL路径
fmt.Printf("path: %v \n",request.URL.Path)
//打印URL协议
fmt.Printf("scheme: %v \n",request.URL.Scheme)
for k,v := range request.Form {
fmt.Printf("key: %v\n",k)
fmt.Printf("value: %v \n",strings.Join(v,"")) //将相同k值的v进行合并
}
//输出信息到浏览器
fmt.Fprintf(rw,"Hello GoLang")
}
我们在浏览器输入地址:
[http://localhost:9090/?url_username=111&url_username=333&url_password=222](http://localhost:9090/?url_username=111&url_username=333&url_password=222)
浏览器上显示如图:
Http流程分析.png使用Go编写Web服务器只要调用http包中的 http.HandleFunc 函数与 http.ListenAndServe 函数就可以了。
我们先来了解以下关于服务端的几个概念
①Request:用户请求的信息,用来解析用户的请求,包括URL,cookie,post,get等。
②Response:服务器发送给客户端的信息。
③Conn:用户每次的请求连接
④Handler:服务器处理用户请求并生成返回信息的处理逻辑。
分析http包的运行机制
Go实现Web服务的工作模式的流程图
流程分析
① 创建Listen Socket, 监听指定的端口, 等待客户端请求到来。
② Listen Socket接受客户端的请求connect, 得到Client Socket(accept), 接下来通过Client Socket与客户端通信(request与response)。
③处理客户端的请求, 首先从Client Socket读取HTTP请求的协议头, 如果是POST方法, 还可能要读取客户端提交的数据, 然后交给相应的handler处理请求, handler处理完毕准备好客户端需要的数据, 通过ClientSocket写给客户端。
这个流程中,我们主要关注三个问题:
①如何监听端口?
②如何接收客户端请求(connect)?
③如何根据HTTP请求分配对应的handler?
再回到我们之前用Go语言写的本地web服务代码中,我们通过使用函数 http.ListenAndServe 来监听指定的端口号,在底层中是这样实现的:
// addr 指定端口号,handler 指定处理逻辑,可以为nil
func ListenAndServe(addr string, handler Handler) error {
//初始化一个server对象
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
func (srv *Server) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
//底层用TCP协议搭建了一个服务,然后监控我们设置的端口。
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
//处理接收客户端的请求信息(connect)
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
整个http处理过程源码:
//Serve接受Listener l上的传入连接,为每个连接创建一个新的服务goroutine。 服务goroutines读取请求,然后调用srv.Handler来回复它们。
//对于HTTP / 2支持,应在调用Serve之前将srv.TLSConfig初始化为提供的侦听器的TLS配置。 如果srv.TLSConfig为非零并且在Config.NextProtos中不包含字符串“h2”,则不启用HTTP / 2支持。
//服务始终返回非零错误。 关闭或关闭后,返回的错误是ErrServerClosed。
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
if fn := testHookServerServe; fn != nil {
fn(srv, l)
}
var tempDelay time.Duration // how long to sleep on accept failure
if err := srv.setupHTTP2_Serve(); err != nil {
return err
}
srv.trackListener(l, true)
defer srv.trackListener(l, false)
baseCtx := context.Background() // base is always background, per Issue 16220
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
//在for循环中处理请求
for {
//在这里通过Listener接收请求
rw, e := l.Accept()
if e != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := e.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
time.Sleep(tempDelay)
continue
}
return e
}
tempDelay = 0
//创建一个Conn 连接
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
//单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务
go c.serve(ctx)
}
}
在上面代码中我们可以看到用户的每一次请求都是在一个新的goroutine去服务,相互不影响,这就是Go的高并发体现。
那么如何具体分配handler到对应的函数中去呢?在上面代码中的 c.serve(ctx) 调用的是 func (c *conn) serve(ctx context.Context) 函数,其中在该函数中有下面这段代码(部分)
//解析request请求
w, err := c.readRequest(ctx)
//取出并分析resp,req
req := w.req
if req.expectsContinue() {
if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
// Wrap the Body reader with one that replies on the connection
req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
}
} else if req.Header.get("Expect") != "" {
w.sendExpectationFailed()
return
}
//映射url与handlefunc()
serverHandler{c.server}.ServeHTTP(w, w.req)
ServeHTTP的源码如下所示:
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler //获取相应的handler
if handler == nil {
//若ListenAndServe函数中的handler为nil,则handler为默认值DefaultServeMux
//该变量是用来匹配url跳转到相应的handle函数
//比如之前设置的 http.HandleFunc("/",myGoWebService),当请求的url为“/”时,就会跳转到 myGoWebService 函数
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{} // 全局选项处理程序响应“OPTIONS *”请求。
}
handler.ServeHTTP(rw, req) //调用 myGoWebService 函数,最后通过写入response的信息反馈到客户端。
}
上述代码可以用以下流程图来演示:一个http连接处理流程
一个http连接处理流程.png
网友评论