40

造轮子 | golang | 简易http2拨测工具 - 简书

 4 years ago
source link: https://www.jianshu.com/p/b61aaeecb895?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

造轮子 | golang | 简易http2拨测工具

0.3542019.06.16 18:18:51字数 911阅读 1,133

最近需要进行http2相关的工作,但是开发环境和测试环境都的curl版本都太老了不支持http2,正好最近在学习golang,于是决定自己造个轮子:用go语言实现一个建议的http2客户端,以本文记录折腾过程。完整代码地址:https://github.com/yiekue/gh2c.

涉及内容:

  • flag包的使用
  • 标准库中http.Client的基本使用
  • golang中的http2

标准库的flag包

平时写程序中免不了根据输入的命令行参数来控制程序的行为,golang的标准库中提供了一个flag包,用于解析命令行输入的各种 - 开头的选项,使用比较方便,免去了自己挨个解析命令行参数的麻烦。

flag包支持boolstringint等多种类型的选项,使用过程比较简单:

  1. flag.Bool()flag.String()等函数定义一个flag,这些函数都有三个入参,依次是flag的名称默认值帮助信息,函数的返回值是一个对应类型的 指针指针指针
  2. 在使用变量之前调用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的步骤一般如下:

  1. 新建一个http.Client
  2. 设置client的各项参数,例如tls参数,http版本等。
  3. 使用http.NewRequest(),新建一个请求,并设置请求的请求头等各项参数。
  4. 使用client.Do(req),发送一个请求。
  5. 处理请求的响应信息。

使用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版本。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK