golang 对访问某域名的 https 请求绑定指定的 ip (类似于 curl –resolve)。
动机
我们需要自己实现一个拨测的工具,具体功能是从我们的管理后台获取域名列表,然后根据域名去做该域名的所有 ip 地址解析(有些域名不止一个 A 记录),拿到该域名的所有 ip 后,对该域名下特定的 ip 进行拨测。
看了一些开源的 request 库,好像都没有我们想要的这个功能,于是我们打算用 golang 的标准库 “net/http” 包来实现,golang “net/http” 包很强大,默认的行为是对该域名进行解析,得到该域名的所有 ip ,然后依次对这些 ip 进行探测,首次成功或者失败,即退出程序,所以,我们还需要对它进行二次封装才能符合我们的要求。
方法一
这个方法比较简单,直接重构 http 客户端的 DefaultTransport 下的 DialContext 方法。
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
|
package main
import (
"context"
"fmt"
"log"
"net"
"net/http"
"strings"
"time"
)
func HttpsHostBindIp(ip, domain string) {
//异常处理
defer func() {
//捕获异常
err := recover()
if err != nil {
//存在异常,抛出异常
fmt.Printf("http 请求 serviceUrl 一次出错:%v", err)
}
}()
dialer := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
// DualStack: true, // this is deprecated as of go 1.16
}
// create your own transport, there's an example on godoc.
http.DefaultTransport.(*http.Transport).DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
// 3. 获取 addr 的端口,有可能我们第一次访问它会重定向,端口变了
// 所以这里的逻辑是防止它重定向的时候端口变了,这里 ip 还是绑定的
// 之前端口,会导致错误 400
portSlice := strings.Split(addr, ":")
port := portSlice[1]
addr = ip + ":" + port
return dialer.DialContext(ctx, network, addr)
}
resp, err := http.Get(domain)
if err != nil {
fmt.Println(err)
}
log.Println(resp.StatusCode, resp.Header, err)
}
func main() {
HttpsHostBindIp("14.17.92.71", "http://www.bilibili.com")
}
|
方法二
这种写法就比较灵活,可以定制 http 客户端的其他参数,实现效果也是一样的。
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
74
75
76
77
78
79
80
81
82
83
|
package main
import (
"bytes"
"context"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"strings"
"time"
)
func HttpsHostBindIp(ipAddr, serviceUrl string) (*http.Response, error) {
//异常处理
defer func() {
//捕获异常
err := recover()
if err != nil {
//存在异常,抛出异常
fmt.Printf("http 请求 serviceUrl 一次出错:%v", err)
}
}()
urlObj, err := url.Parse(serviceUrl)
if err != nil {
fmt.Printf("url.Parse(serviceUrl) Error: %v", err)
}
request, _ := http.NewRequest("GET", urlObj.String(), bytes.NewBuffer([]byte{}))
defer request.Body.Close() //
var response *http.Response
// 一、 绑定固定的IP
// 1. 先准备一个Dialer对象
dialer := &net.Dialer{
Timeout: 7 * time.Second,
KeepAlive: 30 * time.Second,
}
transport := &http.Transport{
// 2. 插入特别的改写条件,然后继续利用原先的DialContext逻辑
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
// 3. 获取 addr 的端口,有可能我们第一次访问它会重定向,端口变了
// 所以这里的逻辑是防止它重定向的时候端口变了,这里 ip 还是绑定的
// 之前端口,会导致错误 400
portSlice := strings.Split(addr, ":")
port := portSlice[1]
addr = ipAddr + ":" + port
return dialer.DialContext(ctx, network, addr)
},
}
// 客户端重定向
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// 重定向超过10次就跳出
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
return nil
},
Transport: transport,
}
response, err = client.Do(request)
if err != nil {
fmt.Printf("client.Do(request) Error:%v\n", err)
}
return response, err
}
func main() {
r, e := HttpTestOnceHostBindIp("14.17.92.71", "http://www.bilibili.com")
if e != nil {
fmt.Println(e)
return
}
log.Println(r.StatusCode, r.Header, err)
}
|
总结
我们想通过代码来实现类似 curl http://www.example.com --resolve www.example.com:80:127.0.0.1
的效果,一开始也没什么头绪,不过后面参考网上其他网友的帖子,加上自己的一些理解,于是也是实现了自己工具的功能。
参考:
golang 强制对特定 ip 的 http 请求(类似于 curl –resolve)
使用GO 程序指定IP地址访问 http/https 地址 类似curl –resolve XXXIP:PortYYY