http 服务源码分析
多读go的源码,可以加深对go语言的理解和认知,今天分享一下http相关的源码部分
在不使用第三方库的情况下,我们可以很容易的的用go实现一个http服务,
package main import ( "fmt" "net/http" ) func IndexHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "hello world ! ") } func main() { http.HandleFunc("/", IndexHandler) if err := http.ListenAndServe(":9100", nil); err != nil { panic(err) } }
直接在浏览器里访问9100端口就可以返回 hello world !
go已经把所有的细节封装好了,我们只需要自己去写Handler实现就够了。源码简单来说做了以下几件事:
- 把我们自定义的Handler方法添加到默认路由
DefaultServeMux
的Map里比如:http.HandleFunc("/", IndexHandler)
(btw: go语言的map是非线程安全的,可以在http源码里看到官方的处理方式); - 启动一个tcp服务监听9100端口,等待http调用;
- 当监听到有http调用时,启动一个协程来处理这个请求,这个是go的http服务快的一个重要原因,把请求内容转换成http.Request, 把当前连接封装http.RespnseWriter;
- 默认路由
DefaultServeMux
根据request的path找到相应的Handler,把 request和 responseWriter传给Handler 进行业务逻辑处理,response响应信息write给客户端;
ServeMux & Handler
http 包的默认路由 DefaultServeMux
是 ServeMux
结构休的实例http.HandleFunc("/", IndexHandler)
的调用,会把path信息和自定义的方法信息保存到 DefaultServeMux
的 m map[string]muxEntry
变量里
我们看一下ServeMux
的定义:
type ServeMux struct { mu sync.RWMutex m map[string]muxEntry es []muxEntry // slice of entries sorted from longest to shortest. hosts bool // whether any patterns contain hostnames } type muxEntry struct { h Handler pattern string }
ServeMux
中保存了path
和Handler
的对应关系,也是路由关系。
Handler
muxEntry
中的 h Handler
对就的就是我们自定义的Handler方法比如,我们自己例子中的方法 func IndexHandler(w http.ResponseWriter, r *http.Request)
细心的同学可能会问 Handler是一个接口,但是我们只是定义了一个方法,这是怎么转换的呢?
接口Halder设置了签名规则,也就是我们自定义的处理方法
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
go语言中所有的自定义类型都可以实现自己的方法,http包是用一个自定义的func来去实现了Handler接口,
type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
然后在ServerMux
的方法HandleFunc
处理的时候会把 handler func(ResponseWriter, *Request)
转换成 HandlerFunc
, 如下所示:
// HandleFunc registers the handler function for the given pattern. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { if handler == nil { panic("http: nil handler") } mux.Handle(pattern, HandlerFunc(handler)) }
ServerMux
结构中还有一个读写锁 mu sync.RWMutex
mu就是用来处理多线程下map的安全访问的。
查找&调用 Handler
得到自定义的handler方法,就是去map中根据path匹配得到Handler
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) { mux.mu.RLock() defer mux.mu.RUnlock() // Host-specific pattern takes precedence over generic ones if mux.hosts { h, pattern = mux.match(host + path) } if h == nil { h, pattern = mux.match(path) } if h == nil { h, pattern = NotFoundHandler(), "" } return } func (mux *ServeMux) match(path string) (h Handler, pattern string) { // Check for exact match first. v, ok := mux.m[path] if ok { return v.h, v.pattern } // Check for longest valid match. mux.es contains all patterns // that end in / sorted from longest to shortest. for _, e := range mux.es { if strings.HasPrefix(path, e.pattern) { return e.h, e.pattern } } return nil, "" }
ServeMux
实现了 Handler
接口,也是默认的路由调用的具体规则实现的地方,他的 ServeHTTP
方法处理方式就是得到自定义的handler方法,并调用我们自定义的方法:
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { if r.RequestURI == "*" { if r.ProtoAtLeast(1, 1) { w.Header().Set("Connection", "close") } w.WriteHeader(StatusBadRequest) return } h, _ := mux.Handler(r) h.ServeHTTP(w, r) }
接口Halder设置了签名规则,也就是我们自定义的处理方法
比如下面的代码,函数IndexHandler就是我们自定义的方法,返回给客户端请求一个 hello world !
字符串。中间请求是如何调用到我们自定义的方法的具体逻辑都是http包提供的,但是一点也不神秘,
http.HandleFunc("/", IndexHandler) // IndexHandler 我们自己定义的Handler方法 func IndexHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "hello world ! ") }
type Handler interface { ServeHTTP(ResponseWriter, *Request) } // type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
http ListenAndServe
说完 ServeMux
是如何结合 Handler
接口,来实现路由和调用后,就要说一下,http服务是如何得到客户端传入的信息,封装requet和rresponse的。
在启动程序的时候http.ListenAndServe
, 有两个参数,第一个是指写端口号,第二个是处理逻辑,如果我们没有给定处理逻辑,会使用默认的处理DefaultServeMux
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { handler := sh.srv.Handler if handler == nil { handler = DefaultServeMux } if req.RequestURI == "*" && req.Method == "OPTIONS" { handler = globalOptionsHandler{} } handler.ServeHTTP(rw, req) }
ListenAndServe
方法打开tcp端口进行监听,然后把Listener
传给srv.Serve
方法
func (srv *Server) ListenAndServe() error { // 省略部分代码 ... ln, err := net.Listen("tcp", addr) if err != nil { return err } return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) }
具体要说一下 Service
方法,这个方法中,对监听tcp请求,然后把请求的客户端连接进行封装,
func (srv *Server) Serve(l net.Listener) error { // 省略部分代码 ... ctx := context.WithValue(baseCtx, ServerContextKey, srv) for { rw, e := l.Accept() // 省略部分代码 ... tempDelay = 0 c := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can return go c.serve(ctx) } }
把客户端的请求封装成一个Conn,然后启动一个协程go c.serve(ctx)来处理这个连接请求,这就是http包快的一个重要原因
,每一个连接就是一个协程。客户端可以先和服务器进行连接,然后利用这个conn来多次发送http请求,这样,就可以减少每次的进行连接而提高一些速度。像一些rpc里就是利用这点去实现的双向的stream流,比如我之前的帖子go微服务框架go-micro深度学习(五) stream 调用过程详解,他就是建立一个tcp连接,然后基于这个conn,发送多个request,返回多次respose数据。
// Serve a new connection. func (c *conn) serve(ctx context.Context) { c.remoteAddr = c.rwc.RemoteAddr().String() ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr()) // 省略部分代码 ... // 循环读取请求 ... for { // 读取请求数据,封装response w, err := c.readRequest(ctx) if c.r.remain != c.server.initialReadLimitSize() { // If we read any bytes off the wire, we're active. c.setState(c.rwc, StateActive) } // 省略部分代码 ... // 路由调用自定义的方法,把封装好的responseWrite和 request传到方法内 serverHandler{c.server}.ServeHTTP(w, w.req) w.cancelCtx() if c.hijacked() { return } w.finishRequest() // 省略部分代码 ... } }
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
软件测试精品文章汇总
测试基础 python测试开发库及项目 谷歌如何测试软件 python工具书籍下载-持续更新 2018软件测试标准汇总下载 python测试开发自学每周一练 python测试工具开发自学每周一练-2018-06 软件测试工具书籍与面试题汇总下载(持续更新) python测试开发自动化测试数据分析人工智能自学每周一练-2018-07 为什么选择软件测试作为职业? 软件测试快速入门1简介,基础知识和重要性 软件测试快速入门2软件测试职业生涯 软件测试快速入门3原则 实战 在项目中学习软件测试1网银测试 在项目中学习软件测试2安全测试 性能测试 软件测试专家工具包2性能测试 python3测试工具开发快速入门教程12性能测试简介 python标准模块介绍-性能测试工具boom 性能测试工具locustio 2018最佳12个开源或免费web服务器和客户端性能测试工具 multi-
- 下一篇
基于LDAP的通讯录(阿里云邮)搜索查询的实现
为了让用户用好新零售风险防控中心,尽可能给用户提供方便…… 说吧,要什么? 就是阿里云邮箱的那个通讯录,能够查询邮件组的接口。 又是烦阿里云邮的同学,你要的目前没有,HSF、HTTP也都没有,不过感谢同学还是给我指了个方向,开放的LDAP协议,我的f**k,书到用时方恨少,各种百度谷歌…… 概念性的百科知识那些什么的大家请自学哈。 ldap java apihttp://directory.apache.org/api/java-api.html ⭐️UnboundID LDAP sdk for javahttps://docs.ldap.com/ldap-sdk/docs/index.html 我这次用的是我直接借力UnboundID LDAP去实现的,下面直接把结果和代码晒给大家(springboot工程): 个人邮箱查询结果 邮件组信息查询结
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Mario游戏-低调大师作品
- 2048小游戏-低调大师作品
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程