package nettools import ( "crypto/tls" "fmt" "golang.org/x/net/context" "io/ioutil" "net" "net/http" "net/url" "strings" "time" ) type HttpPingResult struct { Time int Proto string Status int Length int Err error IP net.IP } func (httpR *HttpPingResult) Result() int { return httpR.Time } func (httpR *HttpPingResult) Error() error { return httpR.Err } func (httpR *HttpPingResult) String() string { if httpR.Err != nil { return fmt.Sprintf("%s", httpR.Err) } else { return fmt.Sprintf("%s: protocol=%s, status=%d, length=%d, time=%d ms", httpR.IP.String(), httpR.Proto, httpR.Status, httpR.Length, httpR.Time) } } type HttpPing struct { Method string URL string Timeout time.Duration // 以下参数全部为可选 DisableHttp2 bool DisableCompression bool Insecure bool Referrer string UserAgent string IP net.IP } func (httpC *HttpPing) Ping() IPingResult { return httpC.PingContext(context.Background()) } func (httpC *HttpPing) PingContext(ctx context.Context) IPingResult { u, err := url.Parse(httpC.URL) if err != nil { return httpC.errorResult(err) } host := u.Hostname() ip := cloneIP(httpC.IP) if ip == nil { var err error ip, err = LookupFunc(host) if err != nil { return httpC.errorResult(err) } } dialer := &net.Dialer{ Timeout: httpC.Timeout, KeepAlive: -1, } dialfunc := func(ctx context.Context, network, address string) (net.Conn, error) { h, p, err := net.SplitHostPort(address) if err != nil { return nil, err } if ip == nil || !strings.EqualFold(h, host) { var err error ip, err = LookupFunc(h) if err != nil { return nil, err } } addr := net.JoinHostPort(ip.String(), p) return dialer.DialContext(ctx, network, addr) } trans := http.DefaultTransport.(*http.Transport).Clone() trans.DialContext = dialfunc trans.DisableKeepAlives = true trans.MaxIdleConnsPerHost = -1 trans.DisableCompression = httpC.DisableCompression trans.ForceAttemptHTTP2 = !httpC.DisableHttp2 trans.TLSClientConfig = &tls.Config{ InsecureSkipVerify: httpC.Insecure, } req, err := http.NewRequestWithContext(ctx, httpC.Method, httpC.URL, nil) if err != nil { return httpC.errorResult(err) } if httpC.UserAgent == "" { httpC.UserAgent = "httping" } req.Header.Set("User-Agent", httpC.UserAgent) if httpC.Referrer != "" { req.Header.Set("Referer", httpC.Referrer) } client := &http.Client{} client.Transport = trans client.Timeout = httpC.Timeout client.CheckRedirect = func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse } t0 := time.Now() resp, err := client.Do(req) if err != nil { return httpC.errorResult(err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return httpC.errorResult(err) } return &HttpPingResult{int(time.Now().Sub(t0).Milliseconds()), resp.Proto, resp.StatusCode, len(body), nil, ip} } func (httpC *HttpPing) errorResult(err error) *HttpPingResult { r := &HttpPingResult{} r.Err = err return r } func NewHttpPing(method, url string, timeout time.Duration) *HttpPing { return &HttpPing{ Method: method, URL: url, Timeout: timeout, } } var ( _ IPing = (*HttpPing)(nil) _ IPingResult = (*HttpPingResult)(nil) )