Uninote
Uninote

本文涵盖的内容来自于一本<<go web编程>>的书,作者:郑兆雄。

go编写http服务端

net/http 库

标准库可以分为客户端和服务器两部分:

  • 支持客户端的Client、Request、Header、Response、Cookie
  • 支持服务器的Server、ServeMux、Handler/HandleFunc、ResponseWriter、Header、Request和Cookie

构建web服务器的方法

  • 传入空的网络地址,和空的处理器。
package main

import (
	"net/http"
)

func main() {
	// 将会使用80当作web服务器的监听端口,使用http.DefaultServeMux为默认的处理器
	http.ListenAndServe("", nil)
}
  • 对服务器配置的结构体http.Server{}
package main

import (
	"crypto/tls"
	"log"
	"net/http"
)

func main() {
	// 创建一个web服务器对象,通过server结构体对服务器对象进行相关配置
	server := http.Server{
		Addr:              "",            // 服务的监听地址和端口信息
		Handler:           nil,           // 处理客户端请求的处理器
		TLSConfig:         &tls.Config{}, // https相关配置
		ReadTimeout:       0,             // 读取客户端请求的超时时间
		ReadHeaderTimeout: 0,             // 读取客户端请求头超时时间
		WriteTimeout:      0,             // 写入客户端响应超时时间
		IdleTimeout:       0,             // http连接超时时间
		MaxHeaderBytes:    0,             // 头部最大字节
		ErrorLog:          &log.Logger{}, // 日志对象配置
	}
	server.ListenAndServe()
}

  • 构建https服务器
package main

import (
	"net/http"
)

func main() {
	server := http.Server{
		Addr:    "",
		Handler: nil,
	}
	// cert.pem 证书, key.pem 私钥
	server.ListenAndServeTLS("cert.pem", "key.pem")
}
  • 创建证书和私钥(适合内网测试环境,少用可大概了解创建流程和x509标准,证书编码方式相关内容)
package main

import (
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"math/big"
	"net"
	"os"
	"time"

	"crypto/rsa"
)

func main() {
	max := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, _ := rand.Int(rand.Reader, max) // 生成的唯一序列号长串
	subject := pkix.Name{                         // 证书的标题信息
		Organization:       []string{"cert organization"},
		OrganizationalUnit: []string{"cert ou"},
		CommonName:         "testCert",
	}
	// x509是国际电信连门电信标准化部门为公钥制定的一个标准,包含了公钥证书的标准格式
	// x.509证书是一个经过编码的ASN.1格式的电子文档
	// 证书的编码有BER(Baic Encoding Rules)和DER方式编码(DER主要用于密码学,尤其是对x.509证书进行加密)
	template := &x509.Certificate{
		SerialNumber: serialNumber,
		Subject:      subject,
		NotBefore:    time.Now(),
		NotAfter:     time.Now().Add(time.Hour * 24 * 365), // 证书的有效期为1年
		KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, // 证书的用途,服务器身份验证
		IPAddresses:  []net.IP{net.ParseIP("127.0.0.1")},             // 证书只能工作在127.0.0.1IP上
	}
	// 生成密钥对
	pk, _ := rsa.GenerateKey(rand.Reader, 2048)
	// 创建证书
	derBytes, _ := x509.CreateCertificate(rand.Reader, template, template, pk.PublicKey, pk)

	certOut, _ := os.Create("cert.pem")
	// 对证书进行DER编码,并写入到"cert.pem"文件中
	pem.Encode(certOut, &pem.Block{
		Type:  "CERTIFICATE",
		Bytes: derBytes,
	})
	certOut.Close()

	keyOut, _ := os.Create("key.pem")
	pem.Encode(keyOut, &pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: x509.MarshalPKCS1PrivateKey(pk),
	})
	keyOut.Close()
}

处理http请求的处理器和处理器函数

正常的http请你去流程为client -> http请求 -> server -> serveMux -> handler/handleFunc -> response to client。

  • 处理器和处理器函数都处理客户端不同url的的请求,将处理的结果响应给客户端。
处理器
  • 在net/http库中,处理器是实现了handler接口的对象。
  • handler接口只有一个方法为ServeHttp(w http.ResponseWriter, r *http.Request)。
  • 定义处理器的示例:
package main

import (
	"fmt"
	"net/http"
)

// 定义一个对象
type Hello struct {
}

// 该对象实现了handler接口中的ServHTTP方法
func (h *Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hello, %s", r.Host)
}

func main() {
	// 实例化一个对象
	handler := &Hello{}
	server := http.Server{
		Addr:    "127.0.0.1:8080",
		Handler: handler, // 使用实例化的对象代替http.DefaultServeMux默认的多路选择器
	}
	server.ListenAndServe()
}
// 这里所有的url请求都会走handler这个处理器,肯定不能满足业务需求的。
// 但是我们可以多定义几个对象,然后使用http.Handle("url string",handler)方法将它加入到我们的服务器中。
处理器函数
  • 处理器函数则是func(w http.ResponseWriter, r *http.Request)类型的函数即可。
  • http.handlerFunc类型同http.DefaultServeMux都实现了handler接口,都可以当作一个handler的实例。
串联多个处理器
  • 处理器传入处理器函数中,返回一个http.Handler,处理器执行的时机由处理器函数中决定。意在为多个处理器加上相同的处理器函数(比如日志记录,安全检查等我们需要的函数)。
  • 处理器串联示例代码
package main

import (
	"fmt"
	"net/http"
)

type Hello struct {
}

func (h *Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hello, %s", r.Host)
}

func log(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// log 函数执行代码
		fmt.Println("收到客户端的请求", "客户端的IP地址为", r.Host)
		// handler处理器的执行时机
		h.ServeHTTP(w, r)
	})
}

func main() {
	handler := &Hello{}
	server := http.Server{
		Addr:    "127.0.0.1:8080",
		Handler: log(handler), // 使用串联的处理器
	}
	server.ListenAndServe()
}


http.ServeMux和http.DefaultServeMux

  • 都是net/http包中提供的多路选择器
  • DefaultServeMux是ServeMux的一个实例
  • ServeMux可以将多个路由和handler绑定起来(一个路由和处理器的映射表),当客户端相应的路由时后,再将请求转发到相应的处理器上。

对于路由/1/2的请求,多路复用器只绑定了/1的路由为/HANDLER1处理器和/路由为/HANDLER,/1/2的请求路由将会被导向/HANDLER来处理,而如果/1/绑定为/HANDLER1那么相同的请求会被HANDLER1来处理。这点是在使用ServeMux需要特别注意的一点

http请求和响应

  • 报文格式
    • 请求/响应行
    • 请求/响应头部
    • 一个或多个空行
    • 请求/响应主体

html表单

  • 一般post请求由html表单发送,html中get方法也是可以提交表单的。
  • 提交表单可以用的applicatioon/x-www-form-urlencoded这种编码方式,来提交少量数据,而multipart/form-data则多用于大资源的提交。其次还有h5常用的text/plain编码方式。

go http Request结构组成

  • URL 字段
    • Scheme、Opaque、User、Host、Path、RawQuery(查询参数)、Fragment(分段,#后面的字符串,浏览器输入url的分段会被浏览器去除)
  • Header字段
    • 一个或者多个键值对,key为string的切片
    • header有提供添加(追加key)、删除、设置(将key切片设置为空,然后再向key第一个索引位置设置key)、获取这四个方法;
    • 获取headerr.Header["Accept-Encoding"]或者使用get方法h := r.Header.Get("Accept-Encoding");第一个是字符串切片,第二个是字符串
  • Body字段
    • 请求和响应都包含这个字段。
    • 这个字段是一个io.ReadCloser接口,所以body字段包含了read和close方法,read接受一个字节切片,执行read方法会返回一个读取的字节数和一个可选的错误。
  • Form字段、PostForm、MultipartForm字段
    • ParseForm和ParseMultipartForm可以对表单和url中url.RawQuery进行语法分析(一个对表单数据解码的过程,如url提交的查询参数或表单提交的查询参数中包含%20将解析为空格)
    • MultipartForm用来获取form-data编码的表单,而Form(url+body)和PostForm(body)用来获取url编码表单数据。
    • FormValue或者PostFormValue可以自动调用parseForm或者ParseMultipartForm方法。
  • 文件上传
    • 服务器获取文件上传的两种方式如下代码所示:
package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func upload(w http.ResponseWriter, r *http.Request) {
	// file, _, err := r.FormFile("upload") // 和下面三行功能类似,但r.FormFile()只能获取第一个文件
	r.ParseMultipartForm(1024)
	fileHeader := r.MultipartForm.File["upload"][0]
	file, err := fileHeader.Open()
	if err != nil {
		fmt.Println(err)
		return
	}
	data, _ := ioutil.ReadAll(file)

	fmt.Fprintln(w, string(data))
}

func main() {
	http.HandleFunc("/", upload)
	if err := http.ListenAndServe(":8080", nil); err != nil {
		fmt.Println("lintenAndServe error:", err)
	}
}

# curl 模拟上传文本文件
echo upload text > file.txt
curl -F 'upload=@./file.txt' 127.0.0.1:8080/
upload text
  • 处理json格式的请求:
    • 客户端json使用的编码格式只能使application/x-www-form-urlencoded,否则无法使用parseForm方法获取数据。

ResponseWriter接口

- 如果响应内容没有设置响应主体的类型,将使用主体的前512字节推断响应主体类型并返回给客户端。
- 实现ResponseWriter接口的对象为reponse非导出结构体的指针。
- ReponseWriter有三个方法:Write(向响应主体写入内容)、WriteHeader(写入响应码)、Header(添加或修改响应头)。
- 编写响应头实现重定向示例代码:
package main

import (
	"fmt"
	"net/http"
)

func redirect(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Location", "http://www.baidu.com")
	w.WriteHeader(http.StatusFound)
	fmt.Fprintln(w, "redirect to baidu")
}

func main() {
	http.HandleFunc("/redirect", redirect)
	if err := http.ListenAndServe(":8080", nil); err != nil {
		fmt.Println("lintenAndServe error:", err)
	}
}

# curl -i 127.0.0.1:8080/redirect
HTTP/1.1 302 Found
Location: http://www.baidu.com
Date: Sat, 14 Aug 2021 02:26:02 GMT
Content-Length: 18
Content-Type: text/plain; charset=utf-8

cookie

cookie 大致可以分为会话cookie和持久化cookie。

  • 会话
    • 不设置过期时间。浏览器关闭自动移除。
  • 持久化
    • 设置过期时间。expires字段和MaxAge字段都可以指定过期时间,MaxAge设置的是多少秒后过期。
  • 设置cookie的方法:
    • http.SetCookie()
    • w.Header.Add("Set-Cookie",http.Cookie{})
  • 服务端获取客户端发送的Cookie
    • r.Header["Cookie"] // 获取cookie字典
    • r.Cookie("CookieName") // 通过cookie名获取单独的某一个cookie
  • 实例:用cookie实现闪现消息,当有某一cookie时,显示cookie的内容,并将cookie设置成过期cookie:
package main

import (
	"encoding/base64"
	"fmt"
	"net/http"
	"time"
)

func setCookie(w http.ResponseWriter, r *http.Request) {
	c := http.Cookie{
		Name:     "flash",
		Value:    base64.URLEncoding.EncodeToString([]byte("hello cookie")),
		HttpOnly: true,
	}
	w.Header().Set("Set-Cookie", c.String())
	// http.SetCookie(w, &c)
}
func showCookie(w http.ResponseWriter, r *http.Request) {
	c, err := r.Cookie("flash")
	if err != nil {
		fmt.Fprintln(w, "no found cookie")
	} else {
		rc := http.Cookie{
			Name:     "flash",
			Value:    c.Value,
			Expires:  time.Unix(1, 0),
			MaxAge:   -1,
			HttpOnly: false,
		}
		http.SetCookie(w, &rc)
		// c.Vaule 是经过编码后的,要获取字符串需要使用UrlEncoding.DecodeString来解码
		val, _ := base64.URLEncoding.DecodeString(c.Value)
		fmt.Fprint(w, string(val))
	}

}

func main() {
	http.HandleFunc("/set_cookie", setCookie)
	http.HandleFunc("/show_cookie", showCookie)
	if err := http.ListenAndServe("0.0.0.0:8080", nil); err != nil {
		fmt.Println("lintenAndServe error:", err)
	}
}

模版引擎语法

  • 执行过程
    • 处理器调用模版引擎,传入一个或多个模版文件,对模版文件进行语法分析后生成语法分析后的模版;
    • 然后再传入模版需要的动态数据,模版在接收到参数后生成相应的html,并将这些html写入到ResponseWriter返回给客户端。
    • tips: 向parseFiles传入多个文件,会创建跟模板文件名同名的模板;传入的是多个模板文件,则会返回一个模板集合。
/* 创建一个或多个模板 */
// 方式1:创建模版引擎,并解析模版文件,隐式声明模板名称
t := template.ParseFiles("tmpl.html")
// 方式2:创建模版引擎,显式声明模板名称
t := template.New("tmpl.html)
// 方式2:解析模版文件
t ,_ := t.ParseFiles("tmpl.html")
// 方式3:匹配模版文件(如下匹配以.html结尾的模版文件,返回一个模板集合)
t ,_ := template.ParseGlob("*.html")
/* 处理模版报错 */
// Must捕获解析模版的报错直接抛出panic错误
t := template.Must(template.ParseFiles("tmpl.html))
/* 执行模板 */
# 1.调用模板的Execute方法,传入ResponseWriter和模板的数据
# 2.调用模板ExecuteTemplate方法,对模版集合中指定模板执行
  • 模版组成:
    • 动作 :{{}}包裹,或者自定义的定界符。
    • 参数、变量、管道、函数
    • 文本
/* 动作 */
// 1. 条件动作
{{ if arg }}
	some Content
{{ end }}
// arg:任意单个值
// ------
{{ if arg }}
	some Content
{{else}}
	other Content
{{ end }}
// 例子
package main

import (
	"html/template"
	"math/rand"
	"net/http"
)

var html = `
<html>
	<head>
		<meta http-equiv="Content-type" content="text/html; charset=utf-8">
		<title>web programming</title>
	</head>
	<body>
		{{ if . }}
			Num is greater than 5!
		{{ else }}
			Num is 5 or less!
		{{ end }}
	</body>
</html>
`

func handlerFunc(w http.ResponseWriter, r *http.Request) {
	t := template.New("tmpl.html")
	t, _ = t.Parse(html)
	t.Execute(w, rand.Intn(10) > 5)
}

func main() {
	http.HandleFunc("/", handlerFunc)
	http.ListenAndServe(":8080", nil)
}

// 2. 迭代动作
{{ range array }}
	Loop body {{ . }}
{{ end }}
// -----如果array为空,执行else代码块
{{ range array }}
	Loop body {{ . }}
{{ else }}
	nothing to show
{{ end }}

// 3. 设置动作:在指定范围内为.设置新值(设置动作块外的值还是处理器传入的值)
{{ arg1 }}
{{ with arg2 }}
	arg1 set arg2
{{ end }}
// -----如果arg2为空,则执行else块代码
{{ arg1 }}
{{ with arg2 }}
	arg1 set arg2
{{ else }}
	dot set
{{ end }}

// 4. 包含动作:一个模板嵌套另一个模板
{{ template "name" }}
// ------向嵌套模板传递数据
{{ template "name" arg }}

// 5. 显式定义模板
特点1:同一个模板文件内可以定义多个模板,调用ExecuteTemplate()执行指定的模板,不同的页面有了公共的布局
特点2:不同的文件可以定义同名的模板,template.ParseFiles()时区分传入即可调用不同的模板
{{ define "Name" }} // 定义一个名字为Name的模板
{{ end }}

// 6. 动作块
应用场景:当模板中引用了其他模板,但处理器在解析模板时没有传入定义的模板文件,动作块可充当默认的模板(防止执行模板报错)
{{ block arg }}
	arg
{{ end }}

// 1. 参数
模板中的一个值,可以是布尔、整数、字符串、结构体、数组、变量、方法(返回值最多两个:1个值+1个错误)、函数等

// 2. 变量
// ------动作中设置变量
$var := Value
// ------变量+迭代
{{ range $key,$value := . }}
  key is $key, value is $value.
{{ end }}

// 3. 管道
和unix中的管道类似,如下:
{{ 1.11143 | printf "%.2f" }}

// 4. 函数(入参可以任意个,出参最多2个:一个值,一个错误)
// ------模板内部函数
fmt.Sprint ...
// ------自定义函数
步骤1:创建映射关系:template.FuncMap{}映射关系,key为函数名(模板文件中调用它,大写开头),value为最多两个返回值、任意参数的函数
步骤2:创建模板调用.Funcs方法把函数和模板绑定
示例代码:
package main

import (
	"fmt"
	"html/template"
	"net/http"
	"time"
)

var html = `
<html>
	<head>
		<meta http-equiv="Content-type" content="text/html; charset=utf-8">
		<title>web programming</title>
	</head>
	<body>
		now Time: {{ Pdate }}
	</body>
</html>
`

func printDate() string {
	return time.Now().Format("2006-01-02 15:04:05")
}

func handlerFunc(w http.ResponseWriter, r *http.Request) {
	var err error
	funcMap := template.FuncMap{"Pdate": printDate}
	t := template.New("html").Funcs(funcMap)
	t, err = t.Parse(html)
	if err != nil {
		fmt.Print("parse error:", err)
		return
	}
	t.Execute(w, "")
}

func main() {
	http.HandleFunc("/", handlerFunc)
	http.ListenAndServe(":8080", nil)
}

  • 模版引擎库
    • text/template:通用的模版引擎。
    • html/template:专门为html格式设定的模版引擎库。
  • 模板特性
    • 上下感知:模板对传入的数据,根据模板定义显示的类型进行转义
    • 上下文感知可以防御一定的xss夸站点攻击,但不是信条,也可以调用template.HTML()方法不对传入数据转义
// 跨站点攻击模拟
package main

import (
	"fmt"
	"html/template"
	"net/http"
)

var html = `
<html>
	<head>
		<meta http-equiv="Content-type" content="text/html; charset=utf-8">
		<title>web programming</title>
	</head>
	<body>
		{{ if . }}
			{{ . }}
		{{ else }}
null
		{{ end }}
	</body>
</html>
`

var form = `
<html>
	<head>
		<meta http-equiv="Content-type" content="text/html; charset=utf-8">
		<title>web programming</title>
	</head>
	<body>
		<form action="/form" method="post">
		Comment: <input name="comment" type="text">
		<hr/>
		<button id="submit">Submit</button>
	</body>
</html>
`
var formData string

func fromHandler(w http.ResponseWriter, r *http.Request) {
	var err error
	t := template.New("from")
	t, err = t.Parse(form)
	if err != nil {
		fmt.Print("parse error:", err)
		return
	}
	formData = r.FormValue("comment")
	t.Execute(w, nil)
}

func printHandler(w http.ResponseWriter, r *http.Request) {
	t := template.New("html")
	t.Parse(html)
	// 设置头,关闭浏览器夸站点防御
	w.Header().Set("X-XSS-Protection", "0")
	// 不对显示内容进行转义
	t.Execute(w, template.HTML(formData))
}

func main() {
	// 访问/form用户输入模拟攻击代码:<script>alert('Pwnd!');</script>
	http.HandleFunc("/form", fromHandler)
	// 显示攻击效果
	http.HandleFunc("/print", printHandler)
	http.ListenAndServe(":8080", nil)
}

数据存储

存放于内存

将数据存放在:数组、切片、map、栈、队列、树等数据结构
优势:性能好、速度快
缺点:非持久化、程序重启数据丢失

文件存储

  1. 原样存储
io/iotuil包
os包
  1. CSV格式存储(comma-separated value,逗号分隔值文本格式): 处理表格数据
相关的包:encoding/csv
应用场景:用户提供大量数据、无法让用户把数据填入提供的表单
用法:用户使用表格填入所有数据导出为CSV格式文件,并上传CSV文件,程序根据需要解析CSV文件获取数据;也可以是程序将数据打包成CSV文件发送给用户

csv 部分方法示例:
	// 创建csv文件
	csvFile,_ := os.Create("test.csv")
	// 创建一个writer
	writer := csv.NewWriter(csvFile)
	// 写入csv内容
	writer.Write([]string{})
	// 缓冲内容写入到磁盘
	writer.Flush()

	// 打开csv文件
	file ,_ := os.Open("test.csv")
	// 创建一个reader
	reader := csv.NewReader(file)
	// 设置字段数量,-1表示无字段不报错
	reader.FieldsPerRecord = -1
	// 读取所有到d
	d,_ := reader.ReadAll()
  1. gob包
encoding/gob包
介绍:将内容存放成二进制文件
特点:快速将内存数据写入到一个或多个文件中
示例代码:
	file := "test"

	// 写入二进制内容到文件
	buf := new(bytes.Buffer)
	encoder := gob.NewEncoder(buf)
	encoder.Encode("test gob")
	ioutil.WriteFile(file, buf.Bytes(), 0644)

	// 从二进制文件中读取
	result := ""
	d, _ := ioutil.ReadFile(file)
	buf = bytes.NewBuffer(d)
	decoder := gob.NewDecoder(buf)
	decoder.Decode(&result)

关系型数据库

将二维数据存放在:mysql、sqlserver、postgresql、oracle等关系性数据库服务中。
  1. go sql包
  • 提供了对数据库的CRUD(Create、Retrieve、Update、Delete)操作。
  • 对数据库操作之前需要先对数据库进行连接。
import _ "ithub.com/go-sql-driver/mysql" // 只使用mysql包的初始化方法(sql.Register("mysql", &MySQLDriver{})方法,注册mysql CRUD驱动),不使用包内提供的方法
var Db *sql.Db // 数据库handle,代表一个包含0个或任意多个数据库连接的连接池

func init(){ // 初始化Db数据库句柄
	var err error
	Db,err = sql.Open("mysql","user:password@/dbname") // 惰性连接,在要执行CRUD操作时才建立连接
	if err !=nil{
		panic("sql.Open error")
	}
}
  • CRUD操作示例
Db.QueryRow() // 查询一行
row.Scan() // 获取查询结果,并映射到程序中的结构体字段中

Db.Query() // 查询多行
row.Next() // 遍历多行结果

Db.Exec() // 执行更新或删除语句

  • 关系映射器ORM(三方库提供的支持:将关系型数据库中的表映射为程序中的对象)
// 1. sqlx:github.com/jmoiron/sqlx
// 2. GROM: github.com/jinzhu/gorm   // 最常用

go web服务

基于SOAP(Simple Object Access Protocol)的web服务

  • 使用xml格式数据;
  • 出现较早、w3c标准化、文档资料丰富、扩展丰富(WS-*开头),企业级系统常用;
  • 使用WSDL对客户端和服务端提供坚实的契约(如:服务的操作、输入、输出都需要定义契约);
  • RPC风格,一般用在内部应用集成。
  • go提供的内置相关库
encoding/xml : 分析xml
	xml.Unmarshal() // 反序列化为go中的结构体
	xml.NewDecoder() // 根据给定的xml数据,生成相应的解码器
	xml.Marshal() // 根据go中结构体生成xml数据
	...

基于REST(Representational State Transfer:具象状态传输)的web服务

  • 灵活,使用如json格式数据;
  • 用少许动作(GET、POST、PUT、DELETE等),操作资源;
  • 实现简单,一般用来提供三方接口。
  • go提供的内置相关库
encoding/json : 分析json数据内置库
	json.Marshal()
	json.Unmarshal()
	json.NewDecoder()
	...

应用测试

内置测试库: testing

  • 文件名*_test.go结尾
  • 测试方法以Test开头,签名为func Test(t *testing.T){...}
  • 常用方法:
// 日志相关
t.Log()
t.Logf()
t.Error()
t.Errorf()
// 指令
go test
	-v
	-count=1
	-short TestFuncName // 只执行单个测试方法
	-parallel 3 // 并行执行多个测试方法
  • 基准测试
    • 测试方法以Benchmark头,签名为Benchmark(*testing.B){...}
    • 常用方法:
    go test 选项
    	-test.count 定义基准测试次数
    	-run 运行单个基准测试的方法
    

内置测试库:net/http/httptest(基于内置testing包实现)

// ----简易http测试流程----
// 1. 创建多路复用器
mux := http.NewServeMux()
// 2. 将被测试的handler绑定到多路复用器
mux.HandleFunc("url",handle)
// 3. 创建记录器
httptest.NewRecorder()
// 4. 创建请求
httptest.NewRequest(writer,request)
// 5. 向被测试的handler发送请求
mux.ServeHTTP()
// 6. 分析记录器中的响应
if writer.Code... {}

替身测试

  • 一种依赖注入的设计模式,需要在被测试代码中添加和修改;
  • 不希望用到实际的对象、结构体、函数,使用替身模拟它们;
  • 替身一般是一个接口,实现了替换对象相关的所有方法;
  • 再创建新的替身(对象、结构体、函数等)实现接口即可。

第三方库

  • gocheck
  • ginkgo

并发

  • gorouting
  • channel
  • 并发相关的包sync

部署go应用

  • 二进制
  • docker

puppet

go并发(极客课堂)

点赞(0) 阅读(861) 举报
目录
标题