Go语言备忘录(3):net/http包的使用模式和源码解析
本文是晚辈对net/http包的一点浅显的理解,文中如有错误的地方请前辈们指出,以免误导!
转摘本文也请注明出处:Go语言备忘录(3):net/http包的使用模式和源码解析,多谢!
目录:
一、http包的3个关键类型:
Handler接口:所有请求的处理器、路由ServeMux都满足该接口;
- type Handler interface {
- ServeHTTP(ResponseWriter, *Request)
- }
ServeMux结构体:HTTP请求的多路转接器(路由),它负责将每一个接收到的请求的URL与一个注册模式的列表进行匹配,并调用和URL最匹配的模式的处理器。它内部用一个map来保存所有处理器Handler
- http包有一个包级别变量DefaultServeMux,表示默认路由:var DefaultServeMux = NewServeMux(),使用包级别的http.Handle()、http.HandleFunc()方法注册处理器时都是注册到该路由中;
- ServeMux结构体有ServeHTTP()方法(满足Handler接口),主要用于间接调用它所保存的处理器的ServeHTTP()方法
http.HandlerFunc函数类型:它满足Handler接口
- type HandlerFunc func(ResponseWriter, *Request)
- //实现Handler接口的ServeHTTP方法
- func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
- f(w, r) //调用自身
- }
二、HTTP服务器的使用模式:
处理函数:只要函数的签名为 func(w http.ResponseWriter, r *http.Request) ,均可作为处理函数,即它可以被转换为http.HandlerFunc函数类型;
- var addr = flag.String("addr", ":8080", "http server address")
- //1.不带参数处理函数
- func serveHome(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != "/" {
- http.NotFound(w, r)
- return
- }
- http.ServeFile(w, r, "home.html")
- }
- //2.带参数处理函数,闭包函数隐式转换为http.HandlerFunc函数类型
- func myHandler(s string) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != "/" {
- http.NotFound(w, r)
- return
- }
- http.ServeFile(w, r, s) //使用参数s
- }
- }
- func main() {
- flag.Parse()
- //向默认路由注册处理器函数
- http.HandleFunc("/", serveHome) //或http.Handle("/", http.HandlerFunc(serveHome))
- http.Handle("/file",myHandler("somefile"))
- err := http.ListenAndServe(*addr, nil) //启动监听,第二个参数nil表示使用默认路由DefaultServeMux中注册的处理器
- if err != nil {
- log.Fatalln("ListenAndServe: ", err)
- }
- }
- func main() {
- mux := http.NewServeMux() //新建一个自定义的路由
- mux.Handle("/file",myHandler("somefile"))
- mux.HandleFunc("/", serveHome)
- err := http.ListenAndServe(*addr,mux) //启动监听
- if err != nil {
- log.Fatalln("ListenAndServe: ", err)
- }
- }
- mux := http.NewServeMux()
- mux.Handle("/file",myHandler("somefile"))
- mux.HandleFunc("/", serveHome)
- s := &http.Server{
- Addr: ":8080",
- Handler: mux, //指定路由或处理器,不指定时为nil,表示使用默认的路由DefaultServeMux
- ReadTimeout: 10 * time.Second,
- WriteTimeout: 10 * time.Second,
- MaxHeaderBytes: 1 << 20,
- ConnState: //指定连接conn的状态改变时的处理函数
- //....
- }
- log.Fatal(s.ListenAndServe())
接下来,我们就跟踪源码来仔细的分析下整个执行过程。
1.使用http.ListenAndServe()方法启动服务,它根据给定参数构造Server类型,然后调用server.ListenAndServe()
- func ListenAndServe(addr string, handler Handler) error {
- server := &Server{Addr: addr, Handler: handler}
- return server.ListenAndServe()
- }
2.而server.ListenAndServe()方法内部调用net.Listen("tcp", addr),该方法内部又调用net.ListenTCP()创建并返回一个监听器net.Listener,如下的ln;
- 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)})
- }
- 因为TCPListener实现了Listener接口,所以tcpKeepAliveListener也实现了Listener接口,并且它重写了Accept()方法,目的是为了调用SetKeepAlive(true),让操作系统为收到的每一个连接启动发送keepalive消息(心跳,为了保持连接不断开)。
- 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(3 * time.Minute) //发送周期
- return tc, nil
- }
- func (srv *Server) Serve(l net.Listener) error {
- defer l.Close()
- if fn := testHookServerServe; fn != nil {
- fn(srv, l)
- }
- var tempDelay time.Duration //重试间隔
- if err := srv.setupHTTP2_Serve(); err != nil {
- return err
- }
- srv.trackListener(l, true) //缓存该监听器
- defer srv.trackListener(l, false) //从缓存中删除当前监听器
- baseCtx := context.Background()
- ctx := context.WithValue(baseCtx, ServerContextKey, srv) //新建一个context用来管理每个连接conn的Go程
- for {
- rw, e := l.Accept() //调用tcpKeepAliveListener对象的 Accept() 方法
- if e != nil {
- select {
- case <-srv.getDoneChan():
- return ErrServerClosed //退出Serve方法,并执行延迟调用(从缓存中删除当前监听器)
- default:
- }
- //如果发生了net.Error错误,则隔一段时间就重试一次,间隔时间每次翻倍,最大为1秒
- if ne, ok := e.(net.Error); ok && ne.Temporary() {
- if tempDelay == 0 {
- tempDelay = 5 * time.Millisecond
- } else {
- tempDelay *= 2
- }
- if max := 1 * time.Second; tempDelay > max {
- tempDelay = max
- }
- srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
- time.Sleep(tempDelay)
- continue
- }
- return e
- }
- tempDelay = 0
- c := srv.newConn(rw) //该方法根据net.Conn、srv构造了一个新的http.conn类型
- c.setState(c.rwc, StateNew) //缓存该连接的状态,如果方法:Server.ConnState(net.Conn, ConnState)不为nil,就根据当前连接的状态执行它
- go c.serve(ctx)
- }
- }
- 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)
- }
6.如上源码可以看到,当 handler == nil 时使用默认的DefaultServeMux路由,否则使用在第1步中为Serve指定了的Handler;然后调用该Handler的ServeHTTP方法(该Handler一般被设置为路由ServeMux类型);
- 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) //规范化请求的路径格式,查找最匹配的Handler
- h.ServeHTTP(w, r)
- }
- type HandlerFunc func(ResponseWriter, *Request)
- //实现Handler接口的ServeHTTP方法
- func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
- f(w, r) //调用自身
- }
http包自带了几个创建常用处理器的函数:FileServer,NotFoundHandler、RedirectHandler、StripPrefix、TimeoutHandler。
而RedirectHandler函数就是用来重定向的:它返回一个请求处理器,该处理器会对每个请求都使用状态码code重定向到网址url
- func main() {
- mux := http.NewServeMux()
- mux.Handle("/to",http.RedirectHandler("http://example.org", 307))
- err := http.ListenAndServe(*addr,mux) //启动监听
- if err != nil {
- log.Fatalln("ListenAndServe: ", err)
- }
- }
Get、Head、Post和PostForm函数发出HTTP/ HTTPS请求。
- resp, err := http.Get("http://example.com/")
- ...
- resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)
- ...
- resp, err := http.PostForm("http://example.com/form",
- url.Values{"key": {"Value"}, "id": {"123"}})
程序在使用完回复后必须关闭回复的主体。
- resp, err := http.Get("http://example.com/")
- if err != nil {
- // handle error
- }
- defer resp.Body.Close()
- body, err := ioutil.ReadAll(resp.Body)
- // ...
要管理HTTP客户端的头域、重定向策略和其他设置,创建一个Client:
- client := &http.Client{
- CheckRedirect: redirectPolicyFunc,
- }
- resp, err := client.Get("http://example.com")
- // ...
- req, err := http.NewRequest("GET", "http://example.com", nil)
- // ...
- req.Header.Add("If-None-Match", `W/"wyzzy"`)
- resp, err := client.Do(req)
- // ...
要管理代理、TLS配置、keep-alive、压缩和其他设置,创建一个Transport:
- tr := &http.Transport{
- TLSClientConfig: &tls.Config{RootCAs: pool},
- DisableCompression: true,
- }
- client := &http.Client{Transport: tr}
- resp, err := client.Get("https://example.com")
Client和Transport类型都可以安全的被多个go程同时使用。出于效率考虑,应该一次建立、尽量重用。
Go语言备忘录(3):net/http包的使用模式和源码解析的更多相关文章
- Go语言备忘录:net/http包的使用模式和源码解析
本文是晚辈对net/http包的一点浅显的理解,文中如有错误的地方请前辈们指出,以免误导! 转摘本文也请注明出处:Go语言备忘录:net/http包的使用模式和源码解析,多谢! 目录: 一.http ...
- C/C++编程笔记:C语言制作情侣必备《爱情电子相册》,源码解析!
今天是521,就分享一个程序员必会的——情侣回忆杀<爱情电子相册>吧!话不多说,先上思路,后接源码! 具备能力: 1.基本可视化编程 1.1 initgraph(800,600); 1.2 ...
- Go语言备忘录:基本数据结构
本文内容是本人对Go语言的变量.常量.数组.切片.映射.结构体的备忘录,记录了关键的相关知识点,以供翻查. 文中如有错误的地方请大家指出,以免误导!转摘本文也请注明出处,多谢! 参考书籍<Go语 ...
- Go语言备忘录:反射的原理与使用详解
目录: 预备知识 reflect.Typeof.reflect.ValueOf Value.Type 动态调用 通过反射可以修改原对象 实现类似“泛型”的功能 1.预备知识: Go的变量都是静态类 ...
- Go语言备忘录(2):反射的原理与使用详解
本文内容是本人对Go语言的反射原理与使用的备忘录,记录了关键的相关知识点,以供翻查. 文中如有错误的地方请大家指出,以免误导!转摘本文也请注明出处:Go语言备忘录(2):反射的原理与使用详解,多谢! ...
- Go语言备忘录(1):基本数据结构
本文内容是本人对Go语言的变量.常量.数组.切片.映射.结构体的备忘录,记录了关键的相关知识点,以供翻查. 文中如有错误的地方请大家指出,以免误导!转摘本文也请注明出处:Go语言备忘录(1):基本数据 ...
- R语言·文本挖掘︱Rwordseg/rJava两包的安装(安到吐血)
每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- R语言·文本挖掘︱Rwordseg/rJava ...
- R语言︱文本挖掘之中文分词包——Rwordseg包(原理、功能、详解)
每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- 笔者寄语:与前面的RsowballC分词不同的 ...
- 《快学 Go 语言》第 16 课 —— 包管理 GOPATH 和 Vendor
到目前位置我们一直在编写单文件代码,只有一个 main.go 文件.本节我们要开始朝完整的项目结构迈进,需要使用 Go 语言的模块管理功能来组织很多的代码文件. 细数 Go 语言的历史发展,模块管理经 ...
随机推荐
- IIS应用程序池自动回收作业
vb: appPoolName = WScript.Arguments() Set oWebAdmin = GetObject("winmgmts:root\WebAdministratio ...
- sharepoint 2010 GetUserProfileByName 5566
After some further investigation i found that it's actually the "ASP.NET Impersonation" th ...
- 「HNOI 2014」 画框
题目链接 戳我 \(Solution\) 这一题很像最小乘积生成树.只是把\(kruskal\)变为了\(km\)/费用流 现在来讲一讲最小乘积生成树.首先将\(\sum a_i\)和\(\sum b ...
- Android下拉刷新完全解析
http://blog.csdn.net/guolin_blog/article/details/9255575 http://www.cnblogs.com/loonggg/p/3201505.ht ...
- HAOI2014 走出金字塔
题目链接:戳我 找规律. 不过为了方便,每次我们计算入口和某一个出口之间需要花费的体力值的时候,不妨把x较小的假设成塔顶,这样的话另一个就不需要分类讨论了. 详细请看代码 #include<io ...
- Collection 集合框架
1. Collection 集合框架:在实际开发中,传统的容器(数组)在进行增.删等操作算法和具体业务耦合在一起,会增加程序的开发难度:这时JDK提供了这样的容器---Collection 集合框架, ...
- python之爬虫(二)爬虫的原理
在上文中我们说了:爬虫就是请求网站并提取数据的自动化程序.其中请求,提取,自动化是爬虫的关键!下面我们分析爬虫的基本流程 爬虫的基本流程 发起请求通过HTTP库向目标站点发起请求,也就是发送一个Req ...
- cookie的优缺点
优点 :极高的扩展性和可用性 1.通过良好的编程,控制保存在cookie中的session对象的大小. 2.通过加密和安全传输技术(ssl),减少cookie被破解的可能性 3.只有cookie中存 ...
- Android 中 DrawerLayout + ViewPager 怎么解决滑动冲突?
DrawerLayout 是 Android 官方的侧滑菜单控件,而 ViewPager 相信大家都很熟悉了.今天这里就讲一下当在 DrawerLayout 中嵌套 ViewPager 时,要如何解决 ...
- leetcode-830-Positions of Large Groups
题目描述: In a string S of lowercase letters, these letters form consecutive groups of the same characte ...