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

 

其中,两个参数,一个是表示响应的接口,另一个表示请求。具体方法先忽略:
type ResponseWriter interface {
}
使用时,这个函数指这定地址和对应的handler
func ListenAndServe(addr string, handler Handler)

 再看下http包内的一个重要函数,Handle,可见,传入的是一个监听的http path,第二个参数是上述的handler.

func Handle(pattern string, handler Handler)

看一下如何使用的:

 
使用接口形式的Handle + ListenAndServe
type ImpHandler struct {}

func (h ImpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {  // 实现方法
w.Write([]byte("haha"))
} func main() {
http.Handle("/", ImpHandler{})
http.ListenAndServe(":12345", nil )
}
这里,http消息来了应该是在底层直接调用对应的ServeHTTP。具体是怎么调到的,一层层来看。
首先看下http.Handle做了什么。
 
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
可见,这个Handle函数底层封装了一个对象,其实是对此对象DefaultServeMux进行调用。
这类型如下:
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool // whether any patterns contain hostnames
} type muxEntry struct {
explicit bool
h Handler
pattern string
}

可见,http的path和对应的处理handler的关系以muxEntry维护在这个默认的hash表m中。http.Handle传入的两个参数以hash形式保存在内部的全局变量DefaultServeMux中。

到此,只是在http业务层面上将相关信息保存下,最后在http请求来时的ListenAndServe中,才进行连接的处理。
 
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}

同样,ListenAndServe本身只是一个对外接口,内部也有相应对象Server进行封装。前面说过这个方法是处理连接层面的事,那么这个server就是tcp server的一个抽象

另一方面,这里又传入了一个handler,这是干吗用的?这里传的是nil,后面再看。
 
func (srv *Server) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr) // 创建监听了
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

可见,这里直接就监听TCP连接了。其中的ln是个Listener接口,代码这样写比较漂亮:

// Multiple goroutines may invoke methods on a Listener simultaneously.
type Listener interface {
// Accept waits for and returns the next connection to the listener.
Accept() (Conn, error) // Close closes the listener.
// Any blocked Accept operations will be unblocked and return errors.
Close() error // Addr returns the listener's network address.
Addr() Addr
} // 这里实现得比较好,覆盖了一个Accept方法,在其中加入了keepAlived的选项。其他两个方法仍旧使用原listener的
type tcpKeepAliveListener struct {
*net.TCPListener // 外层可直接调它的方法不需要指定成员
} func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod( * time.Minute)
return tc, nil
}
继续看Server的连接监听处理:
 
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
if fn := testHookServerServe; fn != nil {
fn(srv, l)
}
var tempDelay time.Duration // how long to sleep on accept failure if err := srv.setupHTTP2_Serve(); err != nil {
return err
} ////////////////skip
for {
rw, e := l.Accept() // 取出一个连接,对应accept
if e != nil {
if ne, ok := e.(net.Error); ok && ne.Temporary() {
if tempDelay == {
tempDelay = * time.Millisecond
} else {
tempDelay *=
}
if max := * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
time.Sleep(tempDelay)
continue
}
return e
}
tempDelay =
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)
}
}

可见,调用Listener的Accept()后,形成一个抽象的连接,再启单独协程去处理它。

协程内读出对应的数据后,会进行如下调用,此调用将http的业务与底层的tcp连接结合了起来:
 
        serverHandler{c.server}.ServeHTTP(w, w.req)

看下面,最终回调回去了。

// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
srv *Server
} 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)
} // 最终回到最开始注册Handle的地方,进行ServeHTTP的调用
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(, ) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
 
最终调用到了上文的DefaultServeMux中来。
 
以上是http一的基础的结构,下面是一些衍生的用法。
 
使用HandleFunc + ListenAndServe
func main() {
fmt.Println("Hello.")
http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
w.Write([]byte("haha2"))
}) http.ListenAndServe(":12346", nil )
}

其中,func可使用闭包也可不用。

看下面代码:
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
} // HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}

可见,HandlerFunc只是对Handler的封装,下面同样是通过DefaultServeMux来进行。

这里的重点是以下的写法,用一个函数来实现某个接口,虽然这接口底层仍然是调用函数本身,这样就可以直接用函数和之前的接口匹配:
 
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}

实际上的效果是,明明只写了一个函数func(ResponseWriter, *Request),但其他代码却可以通过golang的隐式接口方式通过另一个你不知道的函数调用你!这里,不知道的函数就是ServeHTTP

Handle掌握了,这里的HandleFunc就容易了。
 
更进一步,ServeMux也是可以使用自定义的值。这时,传入http.ListenAndServe的第二个参数就是这个mux。
func NewServeMux() *ServeMux { return new(ServeMux) }

这个ServeMux,本身又是隐式实现了Handler。

再次回到这里,可见最终是调到了ServerMux这里:
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)
}

总结下:

http包给外面提供了三个层次的接口,每个层次暴露的东西不一样:
第一层: 只需要关心处理逻辑,直接以HandleFunc实现;
第二层: 以Handle实现,这一层,对外额外暴露了一个Handler接口,需要用户关注一个ServeHTTP的函数;底层仍然是通过DefaultMux来实现。
第三层: 对外暴露了一个ServeMux,处理请求的方法注册到这个ServeMux上,将ServeMux传入。
 

golang的http分析的更多相关文章

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

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

  2. golang https server分析

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

  3. 基于Golang的逃逸分析(Language Mechanics On Escape Analysis)

    何为逃逸分析 在编译程序优化理论中,逃逸分析是一种确定指针动态范围的方法——分析在程序的哪些地方可以访问到指针.它涉及到指针分析和形状分析. 当一个变量(或对象)在子程序中被分配时,一个指向变量的指针 ...

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

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

  5. golang http server分析(一)

    http:http请求过程实质上是一个tcp连接通信,具体通过socket接口编码实现 在go中是通过listenAndServer()方法对外提供了一个http服务,在该方法中完成了socket的通 ...

  6. golang标准库分析之net/rpc

    net/rpc是golang提供的一个实现rpc的标准库.

  7. golang socket 实现分析(一)

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

  8. golang内存对齐分析(转载)

    问题 type Part1 struct { a bool b int32 c int8 d int64 e byte } 在开始之前,希望你计算一下 Part1 共占用的大小是多少呢? func m ...

  9. Golang闭包案例分析与普通函数对比

    闭包案例 package main import ( "fmt" "strings" //记住一定引入strings包 ) //①编写一个函数makeSuffi ...

随机推荐

  1. 反汇编看c++引用

    继续反汇编系列,本次使用vc2008在x86体系下分析c++中的引用. 定义一个引用类型和将一个变量转换成引用类型一样吗? 引用比指针安全,真的是这样吗,对引用不理解的话比指针还危险. 为什么要用常量 ...

  2. DevExpress 控件使用之GridControl基本属性设置

    DEV控件:gridControl常用属性设置     1.隐藏最上面的GroupPanel(实现方法两种)     ①代码实现:gridView1.OptionsView.ShowGroupPane ...

  3. Dijkstra算法——单源最短路径问题

    学习一个点到其余各个顶点的最短路径--单源最短路径 Dijkstra算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法.是从一个顶点到其余各顶点的最短路径算法,解决的是有向 ...

  4. mysql 常用命令集锦

    Mysql安装目录数据库目录/var/lib/mysql/配置文件/usr/share/mysql(mysql.server命令及配置文件)相关命令/usr/bin(mysqladmin mysqld ...

  5. 第八讲:I/O虚拟化

    一.I/O虚拟化的产生 服务器单个千兆以太网端口肯定能够支持单个应用,但是当被分割为10个.15个或者更多的服务器负载时(这其中包括网络.存储以及服务器之间的流量)可能就不够用了. 当遇到I/O瓶颈时 ...

  6. 每天一个Linux命令(18)--locate命令

    locate让使用者可以很快速的搜寻档案系统内是否有指定的档案.器方法是先建立一个包括系统内所有档案名称及路径的数据库,之后当寻找时就需查询这个数据库,而不必实际深入档案系统之中了.在一般的 ditr ...

  7. mfc---ActiveX控件

    AvtiveX控件可看做是一个极小的服务器应用程序,不能独立运行,必须嵌入到某个容器程序中 容器应用程序:可以嵌入或链接对象的应用程序 服务器应用程序:创建对象并且当对象被双击时,可以被启动的应用程序 ...

  8. C#7.0之ref locals and returns (局部变量和引用返回,之前欠大家的,现在补上)

    废话不多说,直接进入正题. 首先我们知道 ref关键字是将值传递变为引用传递 那么我们先来看看ref locals(ref局部变量) 列子代码如下: static void Main(string[] ...

  9. [Selenium With C#基础教程] Lesson-07 复选框

    作者:Surpassme 来源:http://www.jianshu.com/p/98ede43da3c3 声明:本文为原创文章,如需转载请在文章页面明显位置给出原文链接,谢谢.   [作者:Surp ...

  10. UI 自定义视图 ,视图管理器

    一>自定义label - textField 视图 自定义视图:系统标准UI之外,自己组合而出的新的视图 iOS 提供了很多UI组件 ,借助它们,我们可以做各种程序 尽管如此,实际开发中,我们还 ...