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操作流程如下:

在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的处理流程如下

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服务需要支持的一些特性也都没有提及到,后面陆续来吧。

golang http server分析(一)的更多相关文章

  1. golang https server分析

    https: HTTPS是http安全版本的实现,在http与tcp之间加了一层ssl/tls安全传输协议 为了防止请求被监听.篡改.冒充,在tls实现过程中引入了数字证书机制,数字证书由第三方权威机 ...

  2. golang 性能优化分析:benchmark 结合 pprof

    前面 2 篇 golang 性能优化分析系列文章: golang 性能优化分析工具 pprof (上) golang 性能优化分析工具 pprof (下) 一.基准测试 benchmark 简介 在 ...

  3. Golang Http Server源码阅读

    建议看这篇文章前先看一下net/http文档 http://golang.org/pkg/net/http/ net.http包里面有很多文件,都是和http协议相关的,比如设置cookie,head ...

  4. Golang源码分析之目录详解

    开源项目「go home」聚焦Go语言技术栈与面试题,以协助Gopher登上更大的舞台,欢迎go home~ 导读 学习Go语言源码的第一步就是了解先了解它的目录结构,你对它的源码目录了解多少呢? 目 ...

  5. [译]SQL Server分析服务的权限配置

    简介: 本文介绍如何配置SSAS数据库和cube相关维度的安全设置. 相对数据引擎来说,在Management Studio中配置分析服务的安全设置基本没什么区别.但是也会有一些限制,比如SSAS的权 ...

  6. golang的http分析

    首先,要认识一个贯穿始终的接口http.Handler type Handler interface { ServeHTTP(ResponseWriter, *Request) }   其中,两个参数 ...

  7. 使用loki+ mtail + grafana + prometheus server分析应用问题

    loki 是一个方便的类似prometheus 的log 系统,mtail 是一个方便的日志提取工具, 可以暴露为http 服务——支持导出prometheus metrics 环境准备 docker ...

  8. 高性能server分析 - Hadoop的RpcServer

    一.Listener Listener线程,当Server处于运行状态时,其负责监听来自客户端的连接,并使用Select模式处理Accept事件. 同时,它开启了一个空闲连接(Idle Connect ...

  9. golang socket 实现分析(一)

    socket:tcp/udp.ip构成了网络通信的基石,tcp/ip是面向连接的通信协议 要求建立连接时进行3次握手确保连接已被建立,关闭连接时需要4次通信来保证客户端和,服务端都已经关闭 在通信过程 ...

随机推荐

  1. C++编程练习(9)----“图的存储结构以及图的遍历“(邻接矩阵、深度优先遍历、广度优先遍历)

    图的存储结构 1)邻接矩阵 用两个数组来表示图,一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中边或弧的信息. 2)邻接表 3)十字链表 4)邻接多重表 5)边集数组 本文只用代码实现用 ...

  2. Kafka概念入门(一)

    序:如何保证kafka全局消息有序? 比如,有100条有序数据,生产者发送到kafka集群,kafka的分片有4个,可能的情况就是一个分片保存0-25,一个保存25-50......这样消息在kafk ...

  3. Socket.io+Nodejs通讯实例

    具体源码:Socket 目录结构 D:. │ package.json │ server.js │ └─public index.html socket.io.js 需要的条件 socket.io.j ...

  4. Python - Python2与Python3合理共存Windows平台

    Install Python2 and Python3 Python 2.7.13 - Windows x86-64 MSI installer Python 3.6.0 - Windows x86- ...

  5. java 多线程之卖票两种方式

    1.通过extends Thread /* 需求:简单的卖票,多个窗口同时买票 (共用资源) 创建线程的第二种方式:实现Runnable接口 步骤: 1,定义类实现Runnable接口 2,覆盖/重写 ...

  6. javase基础回顾(二)LinkedList需要注意的知识点 阅读源码收获

    我们在学习这一块内容时需要注意的一个问题是 集合中存放的依然是对象的引用而不是对象本身. List接口扩展了Collection并声明存储一系列元素的类集的特性.使用一个基于零的下标,元素可以通过它们 ...

  7. Android Weekly Notes Issue #245

    Android Weekly Issue #245 February 19th, 2017 Android Weekly Issue #245 本期内容: 写好单元测试的几条原则; 如何mock Ko ...

  8. 关于OpenGL和DX学习的取舍

    大家多知道左右就肯定要与显卡打交道.两大图形图像IPA.OpenGL(图形),DX(图形,声音,键盘控制,网络) OpenGL的兴起可能取决于苹果公司的适用,吸引看大部分开发者适用,它有跨平台的有点. ...

  9. WP8.1开发中对于XAML中一些语言的学习(1);

    以前在学习WP开发的时候,看到视频中说到程序在创建之初,MainPaige.xaml页面上有一些代码: <Page x:Class="草案.MainPage" xmlns=& ...

  10. mongodb 安装到创建用户,认证auth,httpinterface

    今天花了一天时间来解开这个mongodb的谜团,如果有遇到了其他的问题,可以咨询我. #开始 2.6.10安装方式 不同版本后面设置用户权限方式有所差异#下载这个版本的mongodb mongodb- ...