多读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 包的默认路由 DefaultServeMuxServeMux 结构休的实例

http.HandleFunc("/", IndexHandler) 的调用,会把path信息和自定义的方法信息保存到 DefaultServeMuxm 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 中保存了pathHandler 的对应关系,也是路由关系。

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()
// 省略部分代码 ...
}
}

http服务源码分析的更多相关文章

  1. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  2. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  3. angular源码分析:$compile服务——directive他妈

    一.directive的注册 1.我们知道,我们可以通过类似下面的代码定义一个指令(directive). var myModule = angular.module(...); myModule.d ...

  4. dubbo源码分析2-reference bean发起服务方法调用

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  5. Fresco 源码分析(三) Fresco服务端处理(1) ImagePipeline为何物

    4.3 服务端的处理 备注: 因为是分析,而不是设计,所以很多知识我们类似于插叙的方式叙述,就是用到了哪个知识点,我们再提及相关的知识点,如果分析到了最后,我想想是不是应该将这个架构按照设计的方式,重 ...

  6. Fresco 源码分析(二) Fresco客户端与服务端交互(3) 前后台打通

    4.2.1.2.4 PipelineDraweeControllerBuilder.obtainController()源码分析 续 上节中我们提到两个核心的步骤 obtainDataSourceSu ...

  7. Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题

    4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...

  8. dubbo注册服务IP解析异常及IP解析源码分析

    在使用dubbo注册服务时会遇到IP解析错误导致无法正常访问. 比如: 本机设置的IP为172.16.11.111, 但实际解析出来的是180.20.174.11 这样就导致这个Service永远也无 ...

  9. 【Netty源码分析】客户端connect服务端过程

    上一篇博客[Netty源码分析]Netty服务端bind端口过程 我们介绍了服务端绑定端口的过程,这一篇博客我们介绍一下客户端连接服务端的过程. ChannelFuture future = boos ...

随机推荐

  1. 21.决策树(ID3/C4.5/CART)

    总览 算法   功能  树结构  特征选择  连续值处理 缺失值处理  剪枝  ID3  分类  多叉树  信息增益   不支持 不支持  不支持 C4.5  分类  多叉树  信息增益比   支持 ...

  2. Less(6)

    1.先判断注入类型 (1)首先看到要求,要求传一个ID参数,并且要求是数字型的:?id=1 (2)再输入?id=1' (3)再输入?id=1 and 1=1 (4)再输入?id=1 and 1=2 ( ...

  3. celery配置

    celery配置 celery的官方文档其实相对还是写的很不错的.但是在一些深层次的使用上面却显得杂乱甚至就没有某些方面的介绍, 通过我的一个测试环境的settings.py来说明一些使用celery ...

  4. Git push error: http 411的解决方案总结

    目录 一.事故现场 二.事故原因 三.解决方案 一.事故现场 git push 的时候报错如下: 将分支推送到远程存储库时遇到错误:Git failed with a fatal error, the ...

  5. 【Unity游戏开发】Android6.0以上的动态权限申请问题

    一.引子 最近公司的游戏在做安全性测试,期间也暴露出了不少安全上的问题.虽然我们今天要说的权限申请和安全性相关不大,但是也会影响到游戏的使用体验等,所以本篇博客中马三就想和大家谈谈Android6.0 ...

  6. vue安装遇到的5个报错小结

    前言 这篇博文不会教你怎么安装vue,但会告知安装过程中可能遇到的5个问题 2017年我写过一篇安装vue的博客,详情:https://www.cnblogs.com/tu-0718/p/752109 ...

  7. 三行Jquery代码实现简单的选项卡

    今晚,我们来用实现一个简单的选项卡切换代码,主要代码只有两行. 效果: 思路:通过切换JQuery控制div的显隐和样式的改变 其中那个一个div显示,其余全隐藏 实现: <!DOCTYPE h ...

  8. RocketMQ(二):producer客户端实践

    MQ解耦了生产者和消费者,前提是有一个稳定强大的消息服务,我们只管与之通信即可. 所以,和MqServer通信是什么样的?难否? 0. 发送端demo /** * This class demonst ...

  9. 假设高度已知,请写出三栏布局,其中左右各为300px 中间自适用

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSPhotoLibraryAddUsageDescription key with a string value

    iOS10 11之后遇到这种报错的小伙伴们请注意啦: 你会发现网上一大堆博客和论坛都是让你在  Info.plist 里面要涉及隐私数据时要添加一句“提示语”.于是打开 Info.plist,点击 + ...