小哥之哥 小哥之哥
首页
    • Prometheus
    • Kubertenes
    • Docker
    • MySQL
  • Go
  • Python
  • Vue
  • Jenkins
  • ELK
  • LDAP
  • 随笔
  • 最佳实践
  • 博客搭建
  • 问题杂谈
关于
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

小哥之哥

运维扫地僧
首页
    • Prometheus
    • Kubertenes
    • Docker
    • MySQL
  • Go
  • Python
  • Vue
  • Jenkins
  • ELK
  • LDAP
  • 随笔
  • 最佳实践
  • 博客搭建
  • 问题杂谈
关于
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • GO

    • GO语言基础

      • GO语言学习总结系列
      • GO语言HTTP标准库
        • GO语言gin框架总结
        • Gin框架集成Swagger
        • zap日志框架
      • GO语言项目实战

    • 编程
    • GO
    • GO语言基础
    tchua
    2023-03-23
    目录

    GO语言HTTP标准库

    # 一、HTTP协议

    # 1.1 初识
    • http: 超文本传输协议(HyperText Transfer Protocol),它是基于TCP协议的应用层传输协议,简单来说就是客户端和服务端进行数据传输的一种规则。
    • 无状态: HTTP协议本身不会对发送过的请求和相应的通信状态进行持久化处理。这样做的目的是为了保持HTTP协议的简单性,从而能够快速处理大量的事务, 提高效率,如果需要保存状态需要引用其他技术,如cookie。
    • 无连接: 每次连接只处理一个请求,早期带宽和计算资源有限,这么做是为了追求传输速度快,后来通过Connection: Keep-Alive实现长连接。
    # 1.2 HTTP Request
    HTTP协议使用TCP协议进行传输,在应用层协议发起交互之前,首先是TCP的三次握手。完成TCP三次握手后,客户端会向Web服务器发出一个请求报文,客户端发送的HTTP请求到Web服务器时,请求消息主要包含四部分:请求行、请求头、空行、请求体
    
    1
    • HTTP请求消息格式

    image-20211224171526363

    # 1.2.1 请求方法
    • GET 请求获取Request-URI所标识的资源(资源放在uri)
    GET /v1/562f25980001b1b106000338.jpg HTTP/1.1
    Host    www.hostname.com
    User-Agent    Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
    Accept    image/webp,image/*,*/*;q=0.8
    Referer    www.hostname.com
    Accept-Encoding    gzip, deflate, sdch
    Accept-Language    zh-CN,zh;q=0.8
    
    1
    2
    3
    4
    5
    6
    7
    • POST 向URI提交数据(例如提交表单或上传数据)资源在请求正文中
    • HEAD
    • PUT
    • DELETE
    • OPTIONS
    • PATCH

    1.4 URL

    URI: uniform resource identifier,统一资源标识符,用来唯一的标识一个资源
    URL: uniform resource locator,统一资源定位器,它是一种具体的URI,指明了如何locate这个资源
    
    1
    2
    • URL举例: http://www.losinx.com:18080/news/tech/43253.html?id=432&name=f43s#pic

    image-20211224173630106

    # 1.2.2 请求头信息(Headers)
    Header 解释 示例
    Accept 指定客户端能够接收的内容类型 Accept: text/plain, text/html
    Accept-Charset 浏览器可以接受的字符编码集 Accept-Charset: iso-8859-5
    Accept-Encoding 指定浏览器可以支持的web服务器返回内容压缩编码类型 Accept-Encoding: compress, gzip
    Accept-Language 浏览器可接受的语言 Accept-Language: en,zh
    Authorization HTTP授权的授权证书 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
    Cache-Control 指定请求和响应遵循的缓存机制 Cache-Control: no-cache
    Connection 表示是否需要持久连接(HTTP 1.1默认进行持久连接) Connection: close
    Cookie HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器 Cookie: $Version=1; Skin=new;
    Content-Length 请求的内容长度 Content-Length: 348
    Content-Type 指定正文(body)的数据格式 Content-Type: application/x-www-form-urlencoded
    User-Agent 浏览器信息 Mozilla/5.0 (Windows NT 6.1; Win64; x64)
    • 常见Content-Type

      • application/x-www-form-urlencoded

        ​ 1) 浏览器的原生form表单,如果不设置 Content-Type 属性,则默认以 application/x-www-form-urlencoded 方式传输数据

        ​ 2)正文例如:name=manu&message=this_is_great

      1. multipart/form-data

        ​ 1) 上传文件时使用multipart/form-data,支持多种文件格式

        ​ 2) 正文例如: name="text"name="file"; filename="chrome.png"Content-Type: image/png... content of chrome.png

      2. application/json

        ​ 1) 正文例如:{"title":"test","sub":[1,2,3]}

      3. text/xml

        正文例如:<?xml version="1.0"?>examples.getStateName

    # 1.2.3 请求正文
    • GET请求没有请求正文

    • POST可以包含GET

      POST /post?id=1234&page=1 HTTP/1.1
      Content-Type: application/x-www-form-urlencoded
      
      name=manu&message=this_is_great
      
      1
      2
      3
      4
    # 1.3 http response

    image-20211224175347957

    # 1.3.1 常见状态码
    code phrase 说明
    200 Ok 请求成功
    400 Bad Request 客户端有语法错误,服务端不理解
    401 Unauthorized 请求未经授权
    403 Forbidden 服务端拒绝提供服务
    404 Not Found 请求资源不存在
    500 Internal Server Error 服务器发生不可预期的错误
    503 Server Unavailable 服务器当前有问题,过段时间可能恢复
    # 1.3.2 响应头
    Header 解释 示例
    Allow 对某网络资源的有效的请求行为 Allow: GET, HEAD
    Date 原始服务器消息发出的时间 Date: Tue, 15 Nov 2010 08:12:31 GMT
    Content-Encoding 服务器支持的返回内容压缩编码类型 Content-Encoding: gzip
    Content-Language 响应体的语言 Content-Language: en,zh
    Content-Length 响应体的长度 Content-Length: 348
    Cache-Control 指定请求和响应遵循的缓存机制 Cache-Control: no-cache
    Content-Type 返回内容的MIME类型 Content-Type: text/html; charset=utf-8
    # 1.3.3 响应正文
    HTTP/1.1 200 OK     						-- 状态行
    Date: Fri, 22 May 2009 06:07:21 GMT			-- 消息爆头 
    Content-Type: text/html; charset=UTF-8 
    											-- 空行
    <html> 										-- 响应正文
    	<head></head>
    	 <body>
    		 <!--body goes here--> 
    	</body> 
    </html>
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    # 二、GO语言HTTP标准库

    # 2.1 http server and client演示
    # 2.1.1 GET请求
    • http server
    import (
    	"net/http"  // 导入go语言自带http包
        "fmt"
    )
    // 固定函数签名
    // 从http.Request 读出各种请求参数 ResponseWriter把内容写回到Pesponse
    func HelloHandler(w http.ResponseWriter,r *http.Request) {
        // 打印客户端信息
    	fmt.Printf("host %s\n",r.Host)
    	// 请求方法
    	fmt.Printf("method %s\n",r.Method)
    	// 请求头
    	fmt.Println("Header")
    	for k,v := range r.Header {
    		fmt.Printf("%s = %v\n",k,v)
    	}
        fmt.Fprint(w,"Hello Boy") // 把返回的内容写入http.ResponseWriter
    }
    
    func main() {
        // 请求路由 
        // HelloHandler 请求路由要执行的函数
        http.HandleFunc("/",HelloHandler)
        // 阻塞,会为每一个请求创建一个协成去处理
        // nil 指的是使用默认的Handler
        // 通过查看net/http的源码可以发现,Handler是一个接口,需要实现方法 ServeHTTP ,也就是说,只要传入任何实现了 ServerHTTP 接口的实例,所有的HTTP请求,就都交给了该实例处理了。
        http.ListenAndServer(":18080",nil)
    }
    
    """ 客户端信息
    服务端打印:
    host 127.0.0.1:18080
    method GET
    Header
    User-Agent = [Go-http-client/1.1]
    Accept-Encoding = [gzip]
    
    """
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    • http client(GET请求)
    import (
    	"net/http"
        "io"
        "os"
    )
    
    func main(){
        // resp 可以拿到相应相关信息
        resp,err := http.Get("http://127.0.0.1:18080")
        if err != nil {
            panic(err)
        } else {
            // 一定要调用resp.Bode.Close(),因为每一个请求服务端都会创建一个协程去处理。
            defer resp.Bode.Close()
            // 打印出请求内容
    		// 请求协议
    		fmt.Printf("proto:%s\n",resp.Proto)
    		// 请求状态码
    		fmt.Printf("status:%s\n",resp.Status)
    		// 请求头信息
    		fmt.Println("请求头:Header")
    		for k,v := range resp.Header {
    			fmt.Printf("%s = %v\n",k,v)
    		}
            // body 是一个io流,打印出body内容
            io.Copy(os.Stdout,resp.Body)
        }
    }
    
    
    """ 服务端信息
    客户端打印:
    proto:HTTP/1.1
    status:200 OK
    请求头:Header
    Date = [Mon, 27 Dec 2021 09:17:05 GMT]
    Content-Length = [6]
    Content-Type = [text/plain; charset=utf-8]
    Hi boy
    
    """
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    # 2.1.2 POST请求
    • http server
    package main
    
    import (
    	"fmt"
    	"io"
    	"net/http"
    	"os"
    )
    
    func RootHandler(w http.ResponseWriter,r *http.Request)  {
    	// 打印客户单信息
    	fmt.Printf("host %s\n",r.Host)
    	// 请求方法
    	fmt.Printf("method %s\n",r.Method)
    	// 请求头
    	fmt.Println("Header")
    	for k,v := range r.Header {
    		fmt.Printf("%s = %v\n",k,v)
    	}
        // 打印出 客户单请求内容
    	io.Copy(os.Stdout,r.Body)
        // 给客户端的响应信息
    	fmt.Fprint(w,"Hi boy")
    }
    
    func main() {
    	http.HandleFunc("/",RootHandler)
    	http.ListenAndServe(":18080",nil)
    }
    
    """服务端打印
    host 127.0.0.1:18080
    method POST
    Header
    User-Agent = [Go-http-client/1.1]
    Content-Length = [9]
    Content-Type = [text/plain]
    Accept-Encoding = [gzip]
    Hi Server
    
    """
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    • http client(POST请求)
    import (
    	"net/http"
        "io"
        "os"
    )
    
    func main()  {
        
    	reader := strings.NewReader("Hi Server")
    	// 需要三个参数,URL,Content-Type,提交内容(io.reader),在server端通过body取出
    	resp,err := http.Post("http://127.0.0.1:18080","text/plain",reader)
    	if err != nil {
    		fmt.Println(err)
    	}else {
    		defer resp.Body.Close()
    		fmt.Printf("proto:%s\n",resp.Proto)
    		// 请求状态码
    		fmt.Printf("status:%s\n",resp.Status)
    		// 请求头信息
    		fmt.Println("请求头:Header")
    		for k,v := range resp.Header {
    			fmt.Printf("%s = %v\n",k,v)
    		}
    		io.Copy(os.Stdout,resp.Body)
    	}
    }
    """客户端打印
    proto:HTTP/1.1
    status:200 OK
    请求头:Header
    Date = [Mon, 27 Dec 2021 09:59:08 GMT]
    Content-Length = [6]
    Content-Type = [text/plain; charset=utf-8]
    Hi boy
    """
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    # 2.2 Form表单提交数据
    • http client
    package main
    
    import (
    	"fmt"
    	"io"
    	"net/http"
    	"net/url"
    	"os"
    	"strings"
    )
    func postForm()  {
        // PostForm 通过表单提交,url.Values 传递提交参数
    	resp,err := http.PostForm("http://127.0.0.1:18080",url.Values{"name":[]string{"tchua"},"age":[]string{"22"}})
    	if err != nil {
    		fmt.Println(err)
    	}else {
    		defer resp.Body.Close()
    		fmt.Printf("proto:%s\n",resp.Proto)
    		// 请求状态码
    		fmt.Printf("status:%s\n",resp.Status)
    		// 请求头信息
    		fmt.Println("请求头:Header")
    		for k,v := range resp.Header {
    			fmt.Printf("%s = %v\n",k,v)
    		}
    		io.Copy(os.Stdout,resp.Body)
    	}
    }
    func main()  {
    	postForm()
    }
    
    """客户端打印
    服务端相应:
    proto:HTTP/1.1
    status:200 OK
    请求头:Header
    Date = [Tue, 28 Dec 2021 01:46:29 GMT]
    Content-Length = [6]
    Content-Type = [text/plain; charset=utf-8]
    Hi boy
    
    """
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    • http server
    package main
    
    import (
    	"fmt"
    	"io"
    	"net/http"
    	"os"
    )
    
    func RootHandler(w http.ResponseWriter,r *http.Request)  {
    	// 打印客户单信息
    	fmt.Printf("host %s\n",r.Host)
    	// 请求方法
    	fmt.Printf("method %s\n",r.Method)
    	// 请求头
    	fmt.Println("Header")
    	for k,v := range r.Header {
    		fmt.Printf("%s = %v\n",k,v)
    	}
        // 打印出 客户单请求内容
    	io.Copy(os.Stdout,r.Body)
        // 给客户端的响应信息
    	fmt.Fprint(w,"Hi boy")
    }
    
    func main() {
    	http.HandleFunc("/",RootHandler)
    	http.ListenAndServe(":18080",nil)
    }
    
    """服务端打印
    host 127.0.0.1:18080
    method POST
    Header
    User-Agent = [Go-http-client/1.1]
    Content-Length = [17]
    // x-www-form-urlencoded 表示客户端通过表单提交
    Content-Type = [application/x-www-form-urlencoded]
    Accept-Encoding = [gzip]
    // 客户端提交参数
    age=22&name=tchua
    
    """
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    # 2.3 自定义请求(do)
    • http client
    package main
    
    import (
    	"fmt"
    	"io"
    	"net/http"
    	"net/url"
    	"os"
    	"strings"
    	"time"
    )
    func complexRequest()  {
    	reader := strings.NewReader("Hi Server")
    	// 通过http.NewRequest 构造请求方法
    	req,err := http.NewRequest("POST","http://127.0.0.1:18080",reader)
    	if err != nil {
    		fmt.Println(err)
    	} else {
    		// 配置客户端请求头添加内容
    		req.Header.Add("User-Agent","tchua-1110")
    		req.Header.Add("channel","qq")
    		// 配置客户端Cookie
    		req.AddCookie(&http.Cookie{
    			Name:       "user",
    			Value:      "tchua",
    			Path:       "/",
    			Domain:     "localhost",
    		})
    		// 配置client
    		client := &http.Client{
    			Timeout:       100*time.Millisecond,
    		}
            // do 可以更加灵活的构造请求方法
    		if resp,err := client.Do(req);err != nil {
    			defer resp.Body.Close()
    			fmt.Printf("proto:%s\n",resp.Proto)
    			// 请求状态码
    			fmt.Printf("status:%s\n",resp.Status)
    			// 请求头信息
    			fmt.Println("请求头:Header")
    			for k,v := range resp.Header {
    				fmt.Printf("%s = %v\n",k,v)
    			}
    			io.Copy(os.Stdout,resp.Body)
    		}
    	}
    }
    func main()  {
    	complexRequest()
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    • http server
    package main
    
    import (
    	"fmt"
    	"io"
    	"net/http"
    	"os"
    )
    
    func RootHandler(w http.ResponseWriter,r *http.Request)  {
    	// 打印客户单信息
    	fmt.Printf("host %s\n",r.Host)
    	// 请求方法
    	fmt.Printf("method %s\n",r.Method)
    	// 请求头
    	fmt.Println("Header")
    	for k,v := range r.Header {
    		fmt.Printf("%s = %v\n",k,v)
    	}
    	// Cookie
    	for _,cookie := range r.Cookies(){
    		fmt.Printf("Name=%s\n",cookie.Name)
    		fmt.Printf("Value=%s\n",cookie.Value)
    		fmt.Printf("Path=%s\n",cookie.Path)
    		fmt.Printf("Domain=%s\n",cookie.Domain)
    	}
    	io.Copy(os.Stdout,r.Body)
    	fmt.Fprint(w,"Hi boy")
    }
    
    func main() {
    	http.HandleFunc("/",RootHandler)
    	http.ListenAndServe(":18080",nil)
    }
    
    """
    host 127.0.0.1:18080
    method POST
    Header
    User-Agent = [tchua-1110]
    Content-Length = [9]
    Channel = [qq]
    Cookie = [user=tchua]
    Accept-Encoding = [gzip]
    Name=user
    Value=tchua
    Path=
    Domain=
    Hi Server
    """
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51

    # 三、HTTP ROUTER

    # 3.1 介绍

    httprouter 是一个高性能、可扩展的HTTP路由

    1) 下载
    go get -u github.com/julienschmidt/httprouter
    2) Router 实现了http.Handler接口
    `net/http` 启动需要指定Handler,
    3) 为各种request method提供了更便捷的路由方式
    4) 支持restful请求方式
    支持参数类型URl(/user/:name),也支持通配符URL(/user/*addr)
    5) 支持ServerFiles访问静态文件
    6) 可以之定义捕获panic的方法
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 3.2 http server and client演示
    # 3.2.1 GET POST 请求
    • server
    package main
    
    import (
    	"fmt"
    	"io"
    	"net/http"
    	"os"
    
    	"github.com/julienschmidt/httprouter"
    )
    
    // 公共的方法 打印客户端请求方法,请求内容,响应内容
    func handle(method string, w http.ResponseWriter, r *http.Request, params httprouter.Params) {
    	fmt.Printf("method %s\n", r.Method)
    	io.Copy(os.Stdout, r.Body)
    	fmt.Println()
    	// 写入响应内容
    	w.Write([]byte("Hi boy,you request" + method))
    }
    // get请求 httprouter固定函数签名
    func get(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
    	handle("get", w, r, params)
    }
    // post请求 httprouter固定函数签名
    func post(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
    	handle("post", w, r, params)
    }
    
    func main() {
        // 初始化路由Router
    	router := httprouter.New()
    	router.GET("/", get)
    	router.POST("/", post)
    
    	// restful 方式请求 支持参数也通配符
        // 客户单请求地址: http://10.1.1.70:18080/user/tchua/vip/sh/xuhui
        // name=tchua,type=vip,addr=sh/xuhui
        // ByName 从请求参数值获取对应的值
    	router.POST("/user/:name/:type/*addr", func(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
    		fmt.Printf("name=%s,type=%s,addr=%s\n", p.ByName("name"), p.ByName("type"), p.ByName("addr"))
    	})
    
    	http.ListenAndServe("0.0.0.0:18080", router)
    }
    """
    method GET
    
    name=tchua,type=,addr=/sh/xuhui
    
    """
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    • client
    package main
    
    import (
    	"fmt"
    	"io"
    	"net/http"
    	"net/url"
    	"os"
    	"strings"
    	"time"
    )
    
    func simpleGet(){
    	if resp,err := http.Get("http://10.1.1.70:18080");err != nil {
    		fmt.Println(err)
    	}else {
    		defer resp.Body.Close()
    		// 打印出请求内容
    		// 请求协议
    		fmt.Printf("proto:%s\n",resp.Proto)
    		// 请求状态码
    		fmt.Printf("status:%s\n",resp.Status)
    		// 请求头信息
    		fmt.Println("请求头:Header")
    		for k,v := range resp.Header {
    			fmt.Printf("%s = %v\n",k,v)
    		}
    		io.Copy(os.Stdout,resp.Body)
    		fmt.Println()
    	}
    }
    func resuful()  {
        // 
    	if resp,err := http.Post("http://10.1.1.70:18080/user/tchua/vip/sh/xuhui","text/plain",nil);err != nil {
    		panic(err)
    	} else {
    		defer resp.Body.Close()
    		fmt.Printf("proto:%s\n",resp.Proto)
    		// 请求状态码
    		fmt.Printf("status:%s\n",resp.Status)
    		// 请求头信息
    		fmt.Println("请求头:Header")
    		for k,v := range resp.Header {
    			fmt.Printf("%s = %v\n",k,v)
    		}
    		io.Copy(os.Stdout,resp.Body)
    
    	}
    }
    
    // 函数调用
    func main()  {
    	simpleGet()
    	resuful()
    }
    
    """
    proto:HTTP/1.1
    status:200 OK
    请求头:Header
    Date = [Tue, 28 Dec 2021 03:45:38 GMT]
    Content-Length = [21]
    Content-Type = [text/plain; charset=utf-8]
    Hi boy,you requestget
    
    
    proto:HTTP/1.1
    status:200 OK
    请求头:Header
    Content-Length = [0]
    Date = [Tue, 28 Dec 2021 03:45:38 GMT]
    
    """
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    # 3.2.2 捕获panic
    package main
    
    import (
    	"fmt"
    	"io"
    	"net/http"
    	"os"
    
    	"github.com/julienschmidt/httprouter"
    )
    
    func handle(method string, w http.ResponseWriter, r *http.Request, params httprouter.Params) {
    	fmt.Printf("method %s\n", r.Method)
    	io.Copy(os.Stdout, r.Body)
    	fmt.Println()
    	// 写入响应内容
    	w.Write([]byte("Hi boy,you request" + method))
    }
    func get(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
    	handle("get", w, r, params)
    }
    
    func post(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
    	handle("post", w, r, params)
    }
    // 手动触发异常函数
    func pac(w http.ResponseWriter,r *http.Request,params httprouter.Params)  {
    	var arr []int
    	_ = arr[2]
    }
    func main() {
    	router := httprouter.New()
    	router.GET("/", get)
    
    	router.PanicHandler = func(w http.ResponseWriter, r *http.Request, err interface{}) {
    		w.WriteHeader(http.StatusInternalServerError)
    		// 把错误的详细信息写入到响应的header里面
    		fmt.Fprintf(w,"err:%v",err)
    	}
    	// 自定义错误路由,当有错误发生时,会自动路由到上面的PanicHandler
    	router.GET("/pac",pac)
    	http.ListenAndServe("0.0.0.0:18080", router)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43

    浏览器访问时,如果发生异常则会走PanicHandler,把错误信息写到响应header中。

    image-20211228144451807

    # 四、 请求参数校验(validator)

    go get github.com/go-playground/validator
    // 示例
    type RegistRequest struct {
        UserName string `validate:"gt=0"` // >0 长度大于0
        PassWord string `validate:"min=6,max=12"` //密码长度[6, 12]
        PassRepeat string `validate:"eqfield=PassWord"` //跨字段相等校验
        Email string `validate:"email"` //需要满足email的格式
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 4.1 常见约束
    • 范围约束
    1) 对于字符串、切片、数组和map,约束其长度。len=10,min=6,max=10,gt=10,oneof=yes no
    2) 对于数值,约束其取值,min,max,eq,ne,gt,gte,lt,lte,oneof=6 8
    
    1
    2
    • 跨字段约束
    1) 跨字段就在范围约束基础上加field后缀
    2) 如果还跨结构体(cross struct)就在跨字段的基础上在field前加cs
    
    1
    2
    • 字符串约束
    1) contains包含子串
    2) containsany包含任意unicode字符,containsany=abcd(字符串至少包含abcd中的一个字母)
    3) containsrun包含run字符
    4) excludes不包含子串
    5) excludesall 不包含任意的unicode字符,excludesall=abcd
    6) excludesrune不包含rune字符
    7) startswith以子串为前缀
    8) endswitch以子串为后缀
    
    1
    2
    3
    4
    5
    6
    7
    8
    • 唯一性约束
    1) 对于数组和切片,约束没有重复的元素
    2) 对于map,约束没有重复的value
    3) 对于元素类型为结构体的切片,unique约束结构体对象的某个字段不重复,通过unqiue=field指定这个字段名
    
    
    1
    2
    3
    4
    • 自定义约束
    // 固定函数签名
    func validateEmail(fl validator.FieldLevel) bool {
        input := fl.Field().String()
        if pass, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,})\.([a-z]{2,4})$`, input); pass {
    	  return true
     }
     return false
    }
    
    //注册一个自定义的validator
    val.RegisterValidation("my_email", validateEmail)
    
    Email string `validate:"my_email"`
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 4.2 检验示例
    • 普通字段校验
    package main
    
    import (
    	"fmt"
    	"github.com/go-playground/validator/v10"
    )
    // 定义全局对象
    var val = validator.New()
    // 带校验的结构体
    // 请求参数必须满足对应的参数校验才可通过
    type RegestRequest struct {
    	UserName string `validate:"gt=6"`
    	Passwd string `validate:"min=6,max=12"`
    	PassRepeat string `validate:"eqfield=Passwd"` // 跨字段
    	Email string `validate:"email"`
    }
    
    func main()  {
    	// 初始化 故意不按照参数约束初始化
    	req := RegestRequest{
    		UserName:   "tch",
    		Passwd:     "12345",
    		PassRepeat: "123",
    		Email:      "1",
    	}
    	// 校验
    	if err := val.Struct(req);err != nil {
    		fmt.Println(err)
    	}
    }
    """ 运行后打印
    Key: 'RegestRequest.UserName' Error:Field validation for 'UserName' failed on the 'gt' tag
    Key: 'RegestRequest.Passwd' Error:Field validation for 'Passwd' failed on the 'min' tag
    Key: 'RegestRequest.PassRepeat' Error:Field validation for 'PassRepeat' failed on the 'eqfield' tag
    Key: 'RegestRequest.Email' Error:Field validation for 'Email' failed on the 'email' tag
    
    """
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    • 跨结构体校验
    package main
    
    import (
    	"fmt"
    	"github.com/go-playground/validator/v10"
    )
    // 定义全局对象
    var val = validator.New()
    
    // 待校验的结构体
    type InnerRequest struct {
    	Passwd string `validate:"gte=6"`
    	Email string `validate:"email"`
    }
    
    // eqcsfield 跨结构体字段校验
    type OutterRequest struct {
    	UserName string `validate:"gte=6"`
    	PassRepeat string `validate:"eqcsfield=Nest.Passwd"`
    	// 跨结构体校验需要 先把结构体引用进来
    	Nest InnerRequest 
    }
    
    
    func main()  {
    	// 初始化
    	inreq := InnerRequest{
    		Passwd:   "1234567",
    		Email:      "1@qq.com",
    	}
    	outreq := OutterRequest{
    		UserName:   "tchuatchua",
    		PassRepeat: "1234567",
    		Nest:       inreq,
    	}
    	// 校验
    	if err := val.Struct(outreq);err != nil {
    		fmt.Println(err)
    	}
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    # 4.3 自定义校验
    package main
    
    import (
    	"fmt"
    	"github.com/go-playground/validator/v10"
    	"regexp"
    )
    // 定义全局对象
    var val = validator.New()
    
    // 自定义校验规则函数
    func validateEmail(fl validator.FieldLevel) bool {
    	input := fl.Field().String()
    	if pass, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,})\.([a-z]{2,4})$`, input); pass {
    		return true
    	}
    	return false
    }
    // 待校验结构体
    type InnerRequest struct {
    	Passwd string `validate:"gte=6"`
    	Email string `validate:"my_email"`
    }
    type OutterRequest struct {
    	UserName string `validate:"gte=6"`
    	PassRepeat string `validate:"eqcsfield=Nest.Passwd"`
    	// 跨结构体校验需要 先把结构体引用进来
    	Nest InnerRequest 
    }
    
    
    func main()  {
        // 把自定义规则函数注册
    	val.RegisterValidation("my_email",validateEmail)
    	// 初始化
    	inreq := InnerRequest{
    		Passwd:   "1234567",
    		Email:      "tchua@qq.com",
    	}
    	outreq := OutterRequest{
    		UserName:   "tchuatchua",
    		PassRepeat: "1234567",
    		Nest:       inreq,
    	}
    	// 校验
    	if err := val.Struct(outreq);err != nil {
    		fmt.Println(err)
    	}
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50

    # 五、 中间件

    # 5.1 中间件作用
    1) 将业务代码和非业务代码解耦
    2) 非业务代码:限流、超时控制、打日志等等
    
    1
    2
    # 5.2 中间件原理
    传入一个http.Handler,外面套上一些非业务功能代码,再返回一个http.Handler。支持中间件层层嵌套。通过HandlerFunc把一个func(rw http.ResponseWriter, r *http.Request)函数转为Handler
    
    func timeMiddleWare(next http.Handler) http.Handler {
        return http.HandlerFunc(func(rw http.ResponseWriter, r     *http.Request) {
            begin := time.Now()
            next.ServeHTTP(rw, r)
            timeElapsed := time.Since(begin)
            log.Printf("request %s use %d ms\n", r.URL.Path, timeElapsed.Milliseconds())
        })
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 5.3 中间件示例
    # 5.3.1 请求耗时
    package main
    
    import (
    	"log"
    	"net/http"
    	"time"
    )
    
    // 中间件 函数  
    // http.Handler 是一个接口 包含ServeHTTP方法
    // http.HandlerFunc 实现了ServeHTTP方法
    func timeMiddleWare(inner http.Handler)  http.Handler {
    	return http.HandlerFunc(func(rw http.ResponseWriter,r *http.Request) {
    		begin := time.Now()
    		inner.ServeHTTP(rw,r)
    		userTime := time.Since(begin)
    		log.Printf("use time %d ms\n",userTime.Milliseconds())
    	})
    }
    
    func getBoy(w http.ResponseWriter,r *http.Request)  {
    	w.Write([]byte("Hi Boy"))
    }
    
    func main()  {
    	// HandlerFunc 把业务函数转换为Handler
    	// https://www.jianshu.com/p/3b5c4fc0695c
    	http.Handle("/",timeMiddleWare(http.HandlerFunc(getBoy)))
    	http.ListenAndServe("0.0.0.0:18081",nil)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    # 5.3.2 限流
    • server
    package main
    
    import (
    	"log"
    	"net/http"
    	"time"
    )
    // 定义一个容量大小为100的管道
    // struct 空结构体不占用内存
    var limitChan = make(chan struct{},100)
    
    // 耗时中间件 函数
    // http.Handler 是一个接口 包含ServeHTTP方法
    // http.HandlerFunc 实现了ServeHTTP方法
    func timeMiddleWare(inner http.Handler)  http.Handler {
    	return http.HandlerFunc(func(rw http.ResponseWriter,r *http.Request) {
    		begin := time.Now()
    		inner.ServeHTTP(rw,r)
    		_ = time.Since(begin)
    		//log.Printf("use time %d ms\n",userTime.Milliseconds())
    	})
    }
    
    // 限流中间件
    func limitMiddleWare(inner http.Handler) http.Handler  {
    	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    		// 当管道容量达到100,代码在此阻塞
    		limitChan <- struct{}{}
    		log.Printf("当前qps %d\n",len(limitChan))
    		inner.ServeHTTP(w,r)
    		// 当请求结束时,释放取出管道数据
    		<- limitChan
    	})
    }
    func getBoy(w http.ResponseWriter,r *http.Request)  {
    	w.Write([]byte("Hi Boy"))
    }
    
    func main()  {
    	// HandlerFunc 把业务函数转换为Handler
    	// https://www.jianshu.com/p/3b5c4fc0695c
    	http.Handle("/",limitMiddleWare(timeMiddleWare(http.HandlerFunc(getBoy))))
    	http.ListenAndServe("0.0.0.0:18081",nil)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    • 并发模拟
    package main
    
    import (
    	"fmt"
    	"net/http"
    	"sync"
    )
    
    // 并发请求示例
    func qpsMax()  {
    	const P = 120
    	wg := sync.WaitGroup{}
    	wg.Add(P)
    	for i := 0;i < P; i++ {
    		go func() {
    			defer wg.Done()
    			if resp,err := http.Get("http://10.1.1.70:18081");err == nil {
    				resp.Body.Close()
    			}
    		}()
    	}
    	wg.Wait()
    }
    
    func main()  {
    	qpsMax()
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

    # 六、如何打造自己WEB框架

    # 6.1 前言
    框架作用: 节省封装时间,统一个团队的编码风格,节省沟通和排查问题的时间
    Web框架具备的功能:
    1) request参数获取
    2) 参数校验(validator)
    3) 路由(httprouter)
    4) response生成和渲染
    5) 中间件
    6) 会话管理
    
    1
    2
    3
    4
    5
    6
    7
    8
    • 参考
    Gorilla工具集:
    	mux: 一款强大的HTTP路由和URL匹配器
    	websocket: 一个快速且被广泛应用的WebSocket实现
    	sessions: 支持将会话跟踪信息保存到Cookie或者文件系统
    	handler: 为http服务提供很多有用的中间件
    	schema: 表单数据和go struct互相转换
    	csrf: 提供防止跨站点请求攻击的中间件
    
    1
    2
    3
    4
    5
    6
    7
    编辑 (opens new window)
    #Go
    上次更新: 2023/07/03, 15:10:18
    GO语言学习总结系列
    GO语言gin框架总结

    ← GO语言学习总结系列 GO语言gin框架总结→

    最近更新
    01
    cert-manager自动签发Lets Encrypt
    09-05
    02
    Docker构建多架构镜像
    08-02
    03
    Prometheus数据迁移至VMstorage
    08-01
    更多文章>
    Theme by Vdoing | Copyright © 2023-2024 |豫ICP备2021026650号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式