19.2 HTTP客户端-定制HTTP请求、调试HTTP、响应超时

1. 定制HTTP请求

如果需要对向服务器发送的HTTP请求做更多超越于默认设置的定制化。

  • client := http.Client{}
    • 使用net/http包提供的导出类型Client,创建一个表示客户端的变量。
  • request, err := http.NewRequest("GET", "https://ifconfig.io/ip", nil)
    • 调用net/http包提供的导出函数NewRequest,构建一个HTTP请求。
    • 参数解析:请求类型,目标url;返回1个request变量。
  • response, err := client.Do(request)
    • 用所创建的客户端发送所构建的HTTP请求,并获取响应。

用这种方法可以单独设置请求头、基本身份验证和cookies等请求参数。

一般而言,除非要完成的任务非常简单,否则推荐使用这种定制化方法。

Go 复制代码
// 定制HTTP请求
// 如果快捷方法产生的简单GET请求不足以满足对请求报文
// 做进一步控制的需要,则可以使用自定义的HTTP客户端
package main
import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

func main() {
    client := http.Client{}
    request, err := http.NewRequest(
        "GET", "https://ifconfig.io/ip", nil)
    if err != nil {
        log.Fatal(err)
    }
	response, err := client.Do(request)
    if err != nil {
        log.Fatal(err)
    }

    defer response.Body.Close()

    resBody, err := ioutil.ReadAll(response.Body)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%s", resBody)
}
// 打印输出:
xxx.xxx.xxx.xxx 显示客户端ip,已隐去

2. 调试HTTP

Go语言标准库的net/http/httputil包提供了一些方法,可用于调试往返于客户端和服务器之间的HTTP请求及响应

  • 打印请求包,下面2个函数均返回关于"请求"或"响应"的字节切片,转为为字符串格式即可打印显示。
    • debugRequest, err := httputil.DumpRequestOut(request, true)
    • fmt.Printf("%s", debugRequest)
  • 打印响应包
    • debugResponse, err := httputil.DumpResponse(response, true)
    • fmt.Printf("%s", debugResponse)

如果我们希望仅在调试环节打印这些信息,那么可以将DEBUG设置为1个环节变量或配置变量,通过os包的Getenv函数用于获取环境变量的值,可据此判断是否打印调试信息。

  • debug := os.Getenv("DEBUG")
Go 复制代码
// 调试HTTP请求
// net/http/httputil包的DumpRequestOut和DumpResponse函
// 数,可用于在调试过程中查看HTTP请求和响应,帮助查找BUG
package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/http/httputil"
    "os"
)

func main() {
    debug := os.Getenv("DEBUG")
    client := http.Client{}
	request, err := http.NewRequest(
        "GET", "https://ifconfig.io/ip", nil)
    if err != nil {
        log.Fatal(err)
    }

    request.Header.Add( // 通过设置请求头,设置了可接受的响应内容类型为json
        "Accept", "application/json")

    if debug == "1" {	// 打印请求
        debugRequest, err :=
            httputil.DumpRequestOut(request, true)
        if err != nil {
            log.Fatal(err)
        }

        fmt.Printf("%s", debugRequest)
    }

	response, err := client.Do(request)
    if err != nil {
        log.Fatal(err)
    }

    defer response.Body.Close()

    if debug == "1" {	// 打印响应
        debugResponse, err :=
            httputil.DumpResponse(
                response, true)
        if err != nil {
            log.Fatal(err)
        }

        fmt.Printf("%s", debugResponse)
    }
	resBody, err := ioutil.ReadAll(response.Body)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%s", resBody)
}
// 打印输出:
GET /ip HTTP/1.1
Host: ifconfig.io
User-Agent: Go-http-client/1.1
Accept: application/json
Accept-Encoding: gzip

HTTP/2.0 200 OK
Content-Length: 13
Alt-Svc: h3-24=":443"; ma=86400, h3-23=":443"; ma=86400
Cf-Cache-Status: DYNAMIC
Cf-Ray: 562461317d37d342-LAX
Content-Type: text/plain; charset=utf-8
Date: Sun, 09 Feb 2020 08:12:40 GMT
Expect-Ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
Server: cloudflare
Set-Cookie: __cfduid=d9ad8e7d64d78d19d075953bebfb13afa1581235960; expires=Tue, 10-Mar-20 08:12:40 GMT; path=/; domain=.ifconfig.io; HttpOnly; SameSite=Lax

xxx.xxx.xxx.xxx	客户端ip,已隐去
xxx.xxx.xxx.xxx

3. 响应超时

客户端向服务器发送请求后,完全无法知道服务器会在多长时间内返回响应。

在系统的底层,有太多因素会对响应时间构成影响。

  • 在客户端一侧:
    • DNS查找速度
    • 创建TCP套接字的速度
    • 与服务器建立TCP连接的速度
    • TLS握手的速度(如果使用HTTPS)
    • 向服务器发送数据的速度
  • 在服务器一侧:
    • 重定向的速度
    • 业务处理的速度
    • 向客户端发送数据的速度

以默认方式创建的客户端,没有对响应设置超时,这意味着:

  • 如果服务器很久甚至永远没有向客户端返回响应,客户端将一直等待
  • 维持这条连接的内存和表示这个套接字的文件描述符,也将一直存在
  • 如果发出的多个请求都是这种情况,那么客户端的资源将会很快耗尽

建议为客户端设置响应超时,一旦超过时间还没有收到响应,即宣告错误

  • client := http.Client{Timeout: 1 * time.Second}

在声明http.Client变量对象时,设置其Timeout字段值,例如设置响应超 时1秒钟 。

继续使用client.Do发送请求,如果超过一秒钟还没收到来自服务器的响应,则返回错误。

Go 复制代码
// 处理响应超时
// HTTP客户端在向服务器发送请求后,完全无法知道何时能收到对方的响应。
// 建议设置一个超时时间,如果在指定的时间内没有收到响应,则返回错误
package main
import (
    "fmt" 
    "io/ioutil" 
    "log" 
    "net/http" 
    "net/http/httputil" 
    "os" 
    "time" 
)
func main() {
    debug := os.Getenv("DEBUG")
    client := http.Client{
        Timeout: 1 * time.Second}
	request, err := http.NewRequest(
        "GET", "https://ifconfig.io/ip", nil)
    if err != nil {
        log.Fatal(err)
    }

    if debug == "1" {
        debugRequest, err :=
            httputil.DumpRequestOut(request, true)
        if err != nil {
            log.Fatal(err)
        }

        fmt.Printf("%s", debugRequest)
    }
	response, err := client.Do(request)
    if err != nil {
        log.Fatal(err)
    }

    defer response.Body.Close()

    if debug == "1" {
        debugResponse, err :=
            httputil.DumpResponse(
                response, true)
        if err != nil {
            log.Fatal(err)
        }

        fmt.Printf("%s", debugResponse)
    }
	resBody, err := ioutil.ReadAll(response.Body)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%s", resBody)
}
// 打印输出:
2020/02/09 14:21:08 Get https://ifconfig.io/ip: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)

使用Transport可以更精细化地控制超时,甚至为传输的每个阶段设置超时。此时,我们需要填充client结构体中的Transport字段,该字段值是由一个http导出类型(公有类型)Transport所创建的结构体变量,该结构体可包含多个字段,通过每个字段为传输的每个阶段设置单独的超时时间。

  • dl := net.Dialer{
  • Timeout: 30 * time.Second,
  • KeepAlive: 30 * time.Second,
  • }
  • tr := http. Transport {
  • DialContext: dl.DialContext,
  • TLSHandshakeTimeout: 10 * time.Second,
  • IdleConnTimeout: 90 * time.Second,
  • ResponseHeaderTimeout: 10 * time.Second,
  • ExpectContinueTimeout: 1 * time.Second,
  • }
  • client := http.Client{ Transport: &tr }
Go 复制代码
// 精细化控制超时
// 使用Transport可以更精细化地控制超时,甚 
// 至为HTTP传输的每个阶段设置独立的超时
package main

import (
   "fmt"
   "io/ioutil"
   "log"
   "net"
   "net/http"
   "net/http/httputil"
   "os"
   "testing"
   "time"
)

func TestFineResponseTimeout(t *testing.T) {
   debug := os.Getenv("DEBUG")
   dl := net.Dialer{
      Timeout:   30 * time.Second,
      KeepAlive: 30 * time.Second,
   }
   tr := http.Transport{
      DialContext:           dl.DialContext,
      TLSHandshakeTimeout:   10 * time.Second,
      IdleConnTimeout:       90 * time.Second,
      ResponseHeaderTimeout: 10 * time.Second,
      ExpectContinueTimeout: 1 * time.Second,
   }
   client := http.Client{Transport: &tr}
   request, err := http.NewRequest(
      "GET", "https://ifconfig.io/ip",
      nil)
   if err != nil {
      log.Fatal(err)
   }

   if debug == "1" {
      debugRequest, err :=
         httputil.DumpRequestOut(
            request, true)
      if err != nil {
         log.Fatal(err)
      }

      fmt.Printf("%s", debugRequest)
   }
   response, err := client.Do(request)
   if err != nil {
      log.Fatal(err)
   }

   defer response.Body.Close()

   if debug == "1" {
      debugResponse, err :=
         httputil.DumpResponse(
            response, true)
      if err != nil {
         log.Fatal(err)
      }

      fmt.Printf("%s", debugResponse)
   }

   resBody, err := ioutil.ReadAll(response.Body)
   if err != nil{
      log.Fatal(err)
   }

   fmt.Printf("%s",resBody)
}
// 打印输出:
2020/02/09 16:39:39 Get https://ifconfig.io/ip: dial tcp: lookup ifconfig.io: no such host //手动断网导致访问失败
相关推荐
小梁不秃捏4 分钟前
计算机网络之体系结构
网络·计算机网络·osi
fareast_mzh14 分钟前
发UDP包查询时间服务器NTP
服务器·网络协议·udp
-風过无痕33 分钟前
node.js项目-创建websocket模块
websocket·网络协议·node.js
_sky123_36 分钟前
XML 外部实体注入漏洞
xml·服务器·网络
念晚91737 分钟前
Linux——echo命令,管道符,vi/vim 文本编辑器
linux·运维·服务器·网络·数据库
香蕉皮蛋44 分钟前
基于Cisco模拟器的组网实验
网络·计算机网络·智能路由器·运维开发
ADOP_2 小时前
掌握PoE交换机的潜力:全面的以太网供电连接手册
网络
天草二十六_简村人9 小时前
Charles抓包工具系列文章(三)-- 接口映射工具(Map Remote和Map Local)
网络·tcp/ip·http·postman
CLONS9 小时前
华为数通——SVI(vlanif虚拟接口)
运维·网络·华为
栗克11 小时前
C# SocketUDP服务器,组播
服务器·开发语言·windows·http·c#·.net