最近需要进行http2相关的工作,但是开发环境和测试环境都的curl版本都太老了不支持http2,正好最近在学习golang,于是决定自己造个轮子:用go语言实现一个建议的http2客户端,以本文记录折腾过程。完整代码地址:https://github.com/yiekue/gh2c.
涉及内容:
-
flag
包的使用 - 标准库中
http.Client
的基本使用 - golang中的
http2
标准库的flag包
平时写程序中免不了根据输入的命令行参数来控制程序的行为,golang的标准库中提供了一个flag包,用于解析命令行输入的各种 -
开头的选项,使用比较方便,免去了自己挨个解析命令行参数的麻烦。
flag包支持bool
、string
、int
等多种类型的选项,使用过程比较简单:
- 用
flag.Bool()
、flag.String()
等函数定义一个flag,这些函数都有三个入参,依次是flag的名称、默认值、帮助信息,函数的返回值是一个对应类型的 指针 !指针 !指针 ! - 在使用变量之前调用
flag.Parse()
进行解析。如果解析失败,程序会退出,并且打印各个变量的帮助信息,包含名称、默认值、和之前定义的帮助信息。解析会在第一个非-
开头的参数停止,在非flag参数后面的flag会被忽略。
package main
import (
"flag"
"fmt"
"os"
)
// 首先定义需要flag的名称、默认值、帮助信息。需要注意的是这里的函数的返回值都是指针
var help = flag.Bool("help", false, "print help info")
var version = flag.Int("v", 2, "http version 1/2")
var method = flag.String("method", "GET", "http method, GET/POST...")
func main() {
// 首先调用Parse()函数进行解析。解析后,前面定义的各种变量就可以直接用了。
flag.Parse()
if *help {
// 打印选项的默认值和帮助信息
flag.PrintDefaults()
os.Exit(0)
}
switch *version {
case 1:
fmt.Println("HTTP/1.1")
case 2:
fmt.Println("HTTP/2.0")
default:
flag.PrintDefaults()
os.Exit(1)
}
fmt.Println("method:", *method)
}
上面这段代码的运行结果:
[~/code/test]$ go run test.go
HTTP/2.0
method: GET
由于设置了输出帮助信息的flag默认是false
,因此默认不会打印帮助信息,而是打印了另外两个flag的默认值。指定输出帮助信息:
[~/code/test]$ go run test.go -help
-help
print help info
-method string
http method, GET/POST... (default "GET")
-v int
http version 1/2 (default 2)
设置bool
型的flag,只需要在命令行中添加这个flag即可,不需要指定它的值。而对于string
之类的flag,就需要指定flag的值:
[~/code/test]$ go run test.go -v 1 -method "POST"
HTTP/1.1
method: POST
如果命令行中的flag在代码中没有定义或者flag的输入格式错误,程序会打印错误信息和已定义的flag信息并退出:
[~/code/test]$ go run test.go -v 1 -metho
flag provided but not defined: -metho
Usage of /tmp/go-build852894954/b001/exe/test:
-help
print help info
-method string
http method, GET/POST... (default "GET")
-v int
http version 1/2 (default 2)
exit status 2
http.Client && http2
golang的标准库net/http
中提供了一个http的客户端,可以进行简单的http操作。但是如果要使用http2就需要额外的golang.org/x/net/http2
,由于国内特殊的网络环境,golang.org无法直接访问到,可以到github的镜像仓库中下载使用。同时需要下载http2依赖的text.
使用Client的步骤一般如下:
- 新建一个
http.Client
- 设置
client
的各项参数,例如tls参数,http版本等。 - 使用
http.NewRequest()
,新建一个请求,并设置请求的请求头等各项参数。 - 使用
client.Do(req)
,发送一个请求。 - 处理请求的响应信息。
使用http.Client
发起http请求的流程:
package main
import (
"crypto/tls"
"flag"
"fmt"
"golang.org/x/net/http2"
"io/ioutil"
"net/http"
"os"
)
var help = flag.Bool("help", false, "print help info")
var version = flag.Int("v", 2, "http version 1/2")
var method = flag.String("method", "GET", "http method, GET/POST...")
func main() {
flag.Parse()
if *help {
flag.PrintDefaults()
os.Exit(0)
}
// 从命令行读取URL,URL需要在各种flag之后
url := flag.Arg(0)
if "" == url {
fmt.Println("error: please input URL")
flag.PrintDefaults()
}
tlsConfig := &tls.Config{
InsecureSkipVerify: false,
}
// 新建一个client
client := &http.Client{}
// 设置http版本,默认使用http2
switch *version {
case 1:
client.Transport = &http.Transport{
TLSClientConfig: tlsConfig,
}
case 2:
client.Transport = &http2.Transport{
TLSClientConfig: tlsConfig,
}
default:
fmt.Println("error: unkown http version:", *version)
flag.PrintDefaults()
os.Exit(1)
}
// 使用参数输入的请求方法和url新建一个请求
req, err := http.NewRequest(*method, url, nil)
if err != nil {
fmt.Println("error: failed to create request,", err)
flag.PrintDefaults()
os.Exit(1)
}
// 设置User-Agent
req.Header.Set("User-Agent", "GH2C")
// 发送请求
resp, err := client.Do(req)
if nil != err {
fmt.Println("error: failed to do request,", err)
flag.PrintDefaults()
os.Exit(1)
}
defer resp.Body.Close()
// 读取响应体信息
body, err := ioutil.ReadAll(resp.Body)
if nil != err {
fmt.Println("error: failed to read body.")
flag.PrintDefaults()
os.Exit(1)
}
// 答应响应头和响应体长度
fmt.Println(">", resp.Proto, resp.Status)
for k, vs := range resp.Header {
for _, v := range vs {
fmt.Printf("> %s: %s\n", k, v)
}
}
fmt.Println("body.length:", len(string(body)))
}
在网上百度一个支持http2的网站,测试一把,效果如下(域名侵删):
[~/code/test]$ go run test.go -v 2 https://www.chinacache.com/
> HTTP/2.0 200 OK
> Content-Type: text/html
> Expires: Mon, 17 Jun 2019 04:39:54 GMT
> Accept-Ranges: bytes
> Age: 19405
> Etag: W/"5cdbdbc8-2dc0"
> Last-Modified: Wed, 15 May 2019 09:28:40 GMT
> Date: Sun, 16 Jun 2019 04:39:54 GMT
> Server: nginx
> Powered-By-Chinacache: HIT from CMN-CD-b-3g3
> Cc_cache: TCP_HIT
body.length: 11712
gh2c
将在上一节的基础上增加更多的flag来增加更多的功能就成了支持http2的简易拨测工具gh2c:
- 支持自定义头域
- 自定义是否忽略证书
- 更友好的输出信息
*[master][~/code/gh2c]$ ./gh2c -help
Usage: ./gh2c -[flags] url
-H string
custom headers
-HKVsep string
used for split a custom header key and value (default ":")
-Hsep string
used for split custom headers (default ";")
-body
output response body
-debug
print debug info
-help
print help info
-host string
custom Host to override default (default "defaltHost")
-method string
http method, GET/POST... (default "GET")
-v int
http version 1/2 (default 2)
-verifyCert
enable verification of the server certificate
效果如下,默认不输出body:
*[master][~/code/gh2c]$ go run gh2c.go -v 2 -H "test:testheadker|test2:testheader2" -Hsep "|" https://example.com/
< GET HTTP/2.0 /
< Host: www.chinacache.com
< Test: testheadker
< Test2: testheader2
< User-Agent: GH2C
<
> HTTP/2.0 200 OK
> Etag: W/"5cdbdbc8-2dc0"
> Last-Modified: Wed, 15 May 2019 09:28:40 GMT
> Date: Sun, 16 Jun 2019 04:39:54 GMT
> Server: nginx
> Cc_cache: TCP_HIT
> Accept-Ranges: bytes
> Age: 11967
> Expires: Mon, 17 Jun 2019 04:39:54 GMT
> Powered-By-Chinacache: HIT from CMN-CD-b-3g3
> Content-Type: text/html
<
FIXME
在把http2.Transport
赋值给client.Transport
之后,使用req.Proto
获取到的仍然是HTTP/1.1
,不知道怎么获取实际使用http版本。