设为首页 收藏本站
查看: 1135|回复: 0

[经验分享] golang http server分析(一)

[复制链接]

尚未签到

发表于 2017-12-8 08:56:02 | 显示全部楼层 |阅读模式
  golang中使用的http协议版本是RFC2616
  对于一个http服务来讲,需要兼容新旧版本的http协议,http1.0/2.0,以及https的支持,http的通信是建立在tcp连接基础上的通信。
  现在协议有了,连接通信也有了,还剩一个问题就是如何处理client request请求,这个问题可以分为路由和具体逻辑实现,下面看看在golang中是如何解决这些问题的。
  路由部分
  在golang中有个Handler的概念,一个URL对应一个Handler,在Handler中处理request的具体逻辑,对应关系保存在一个map结构中



type ServeMux struct {
mu    sync.RWMutex
m     map[string]muxEntry //key是URL匹配字符串,muxEntry是对应的处理handler
hosts bool // 路由匹配时,是否包含host
}
  Handler分为一般类型Handler和特殊类型Handler,特殊类型Handler是指包含特定功能处理的Handler,
  比如redirectHandler用来处理302跳转、NotFoundHandler用来处理404请求等。
  Handler定义如下:



// Handler类型定义
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
//一般Handler是一函数体,实现了Handler接口,通过该函数可以将一个自定义函数转换为Handler
type HandlerFunc func(ResponseWriter, *Request)
// 绑定对象就是自定义函数本身,通过在ServerHTTP中调用函数本身,实现了钩子功能。
// 也就是说,当程序调用Handler.ServerHTTP()方法的时候,实际上是调用的跟Handler绑定的自定义函数
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
// 调用绑定对象函数
    f(w, r)
}
  明确了Handler的定义,接下来就要看看如何注册Handler了,Handler的注册是通过HandleFunc()函数实现的,在HandleFunc中调用ServerMux的HandleFunc()
  方法将一个自定义的方法转换为一个一般Handler,最后再调用Server.Mux的handle()方法,完成URL与Handler的绑定,下面详细看看handle()的实现,



func (mux *ServeMux) Handle(pattern string, handler Handler) {
// 加一个写锁
    mux.mu.Lock()
defer mux.mu.Unlock()
// url匹配字符串不能为空
if pattern == "" {
panic("http: invalid pattern " + pattern)
}
// handler不能为空
if handler == nil {
panic("http: nil handler")
}
// 对应关系没有被注册过
if mux.m[pattern].explicit {
panic("http: multiple registrations for " + pattern)
}
// 添加到map
mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}
// 判断是否以hosts开头的url
if pattern[0] != '/' {
mux.hosts = true
}
// 如果URL以字符/结尾,则多注册注册一个redirectHandler,访问/tree时重定向到/tree/
n := len(pattern)
if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
path := pattern
if pattern[0] != '/' {
path = pattern[strings.Index(pattern, "/"):]
}
url := &url.URL{Path: path}
mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
}
}
  redirectHandle定义:



// redirectHandler是一个结构体,实现了Handler接口
type redirectHandler struct {
url  string
code int
}
func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request) {
// 重定向,设置location,status
    Redirect(w, r, rh.url, rh.code)
}
  通信部分
  http请求过程实质上是一个tcp连接通信,具体通过socket接口编码实现,socket部分另起文章详细说明,这里只做简单介绍,socket操作流程如下:
DSC0000.png

  在golang中的使用,通过listenAndServer()函数一步完成



func (srv *Server) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
// 创建socket文件描述符,绑定ip:port,改变socket状态为监听状态
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
// 启动服务,处理连接请求,tcpKeepAliveListener是一个长连接
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
  在svr.Server()函数中会循环Accept() tcp 连接请求,每当读取到一个tcp conn就启动一个goroutine去处理连接信息,
  对于http服务来讲,在goroutine中完成了http request的处理流程如下
DSC0001.png

  readRequest():从tcp conn中读取一个http request,完成了http请求的heand以及body的解析依据http协议对请求信息的校验,详细过程如下
  1. 设置tcp conn读取数据超时时间,设置请求头数据大小限制,过滤http1.0 post请求后多添加的空格
  2. 读取请求信息并格式化
  3. 校验请求头信息是否合法
  4. 封装成一个response返回,下面详细介绍
  获取到请求信息,接下来就该调用URL对应的具体逻辑了,通过Handler.ServerHTTP()完成了对Handler的调用,主要操作如下:
  1. 根据请求信息host,path在serverMux中查找对应的Handler
  2. 如果找不到对应Handler,会返回一个NotFoundHandler
  3. 调用handler.ServerHttp()
  finishRequest():关闭http request请求处理,主要操作如下
  1. 刷掉bufio.writer里的数据
  2. 关闭chunkWriter()写入流
  3. 刷掉conn缓冲流里的数据
  4. 关闭tcp连接
  接下来介绍下http是如何完成数据的读写的,对数据的读写操作本质上都是在对socket fd进行操作,通过connReader结构体包装好了对net.Con的操作,
  这样就可以通过对connReader的操作最终作用到net.Con,而在net.Con中完成了fd的读写操作,golang中io部分很多地方都用来这种思想,通过包装器包装好需要操作的对象,在调用过程中只需要操作包装器就可以了。
  下面看看golang中是如何实现的,拿解析请求函数举例来看看读取数据的过程,先看看函数定义:



func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *Request, err error) {
tp := newTextprotoReader(b)
req = new(Request)
var s string
if s, err = tp.ReadLine(); err != nil {
return nil, err
}
...
}
  没错请求的处理是通过bufio.Reader.ReadLine()一行一行的从tcp conn中读取出来的,在看看这个函数是怎么被调用的



c.r = &connReader{r: c.rwc} // connReader就是上面提到的tcp连接包装器
c.bufr = newBufioReader(c.r) // 定义一个读缓存io流
c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10) //定义一个写缓存io流
// 调用解析请求函数
req, err := readRequest(c.bufr, keepHostHeader)
  接下来看看写操作过程,http中通过response对象将数据写入tcp连接的,并对写入流做了优化操作,对response的使用如下



w = &response{
conn:          c, //当前tcp conn
req:           req, // 当前请求对应的request对象
    reqBody:       req.Body,
handlerHeader: make(Header), //初始化header头存储块
contentLength: -1, // 内容长度
}
w.cw.res = w // 第二层缓冲流,通过chunkWriter将内容写入conn缓冲区
w.w = newBufioWriterSize(&w.cw, bufferBeforeChunkingSize) // 第一层缓冲流,通过buio writer将数据写入chunkWriter
  封装好response对象后,通过serverHandler{c.server}.ServeHTTP(w, w.req)方法将对象传入Handler中,最终装换为一个http.ResponseWriter对象执行,本质上
  都是io.Writer对象
  总结:
  这篇文字主要介绍了http包中的路由和通信部分,由于通信部分涉及到一些socket通信的问题,只是简单的提了一下,后面会专门针对socket总结一篇文章,http包中
  Request和Response没有详细展开介绍,基本上对http协议规范的实现都在这两个里面体现,还有前文提到的http服务需要支持的一些特性也都没有提及到,后面陆续来吧。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-422037-1-1.html 上篇帖子: libevent(六)http server 下篇帖子: angular2 学习笔记 ( server-side rendering, angular universal, 服务端渲染 )
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表