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()
// 省略部分代码 ...
}
}
http服务源码分析的更多相关文章
- zookeeper源码分析之五服务端(集群leader)处理请求流程
leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...
- zookeeper源码分析之四服务端(单机)处理请求流程
上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...
- angular源码分析:$compile服务——directive他妈
一.directive的注册 1.我们知道,我们可以通过类似下面的代码定义一个指令(directive). var myModule = angular.module(...); myModule.d ...
- dubbo源码分析2-reference bean发起服务方法调用
dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...
- Fresco 源码分析(三) Fresco服务端处理(1) ImagePipeline为何物
4.3 服务端的处理 备注: 因为是分析,而不是设计,所以很多知识我们类似于插叙的方式叙述,就是用到了哪个知识点,我们再提及相关的知识点,如果分析到了最后,我想想是不是应该将这个架构按照设计的方式,重 ...
- Fresco 源码分析(二) Fresco客户端与服务端交互(3) 前后台打通
4.2.1.2.4 PipelineDraweeControllerBuilder.obtainController()源码分析 续 上节中我们提到两个核心的步骤 obtainDataSourceSu ...
- Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题
4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...
- dubbo注册服务IP解析异常及IP解析源码分析
在使用dubbo注册服务时会遇到IP解析错误导致无法正常访问. 比如: 本机设置的IP为172.16.11.111, 但实际解析出来的是180.20.174.11 这样就导致这个Service永远也无 ...
- 【Netty源码分析】客户端connect服务端过程
上一篇博客[Netty源码分析]Netty服务端bind端口过程 我们介绍了服务端绑定端口的过程,这一篇博客我们介绍一下客户端连接服务端的过程. ChannelFuture future = boos ...
随机推荐
- Java基础(六)
Spring依赖注入的几种方式 1.接口注入 2.setter方法注入 3.构造方法注入 Struts2的拦截器与过滤器的工作原理 1.Structs2拦截器是在访问某个Action或Action的某 ...
- 【Unity游戏开发】Android6.0以上的动态权限申请问题
一.引子 最近公司的游戏在做安全性测试,期间也暴露出了不少安全上的问题.虽然我们今天要说的权限申请和安全性相关不大,但是也会影响到游戏的使用体验等,所以本篇博客中马三就想和大家谈谈Android6.0 ...
- Redis在Window下的安装部署
一.下载 由于redis官方不支持windows,所以需要在github上下载window的版本:下载地址.redis约定版次版本号(即第一个小数点后的数字)为偶数的版本是稳定版本(如2.8,3.0) ...
- VS2010到VS2019各个版本的密钥
VS2019专业版和企业版的密钥 Visual Studio 2019 EnterpriseBF8Y8-GN2QH-T84XB-QVY3B-RC4DF Visual Studio 2019 Profe ...
- ubuntu安装gcc不同的版本
服务器ubuntu14.04安装ns3.29中,显示gcc版本过低 使用apt-get安装失败,ubuntu14.04默认安装gcc4.8.4,无法下载更高级的gcc版本 先找到资料1,脚本尝试了,下 ...
- JPA入门简介与搭建HelloWorld(附代码下载)
场景 在学习JPA之前先来了解下JDBC与各大数据库的关系. 很久之前出现了很多数据库比如Mysql.Oracle.SqlServer.DB2等.这就导致了应用程序要连哪个数据库就要使用哪个数据库的A ...
- 微信小程序API交互反馈,wx.showToast显示消息提示框
导读:wx.showToast(OBJECT) 显示消息提示框. OBJECT参数说明: 参数 类型 必填 说明 最低版本 title String 是 提示的内容 icon String 否 图标, ...
- Linux 定位进程对应的文件路径
ls -al /proc/[pid]/exe # 例如: [root@localhost ~]# ls -al /proc/9109/exe lrwxrwxrwx. 1 ibaboss ibaboss ...
- RMAN DUPLICATE DATABASE with SET NEWNAME failed: RMAN-05501 RMAN-05517 (Doc ID 387093.1)
RMAN DUPLICATE DATABASE with SET NEWNAME failed: RMAN-05501 RMAN-05517 (Doc ID 387093.1) APPLIES TO: ...
- Linux和Windows的区别
1. 软件与支持 • Windows 平台:数量和质量的优势,不过大部分为收费软件:由微软官方提供重要支持和服务: • Linux 平台:大都为开源自由软件,用户可以修改定制和再发布,由于基本免费没有 ...