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

最近需要进行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版本。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270