V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
chenqh
V2EX  ›  Go 编程语言

golang http 请求也太恶心了把

  •  
  •   chenqh · 2021-05-08 21:06:42 +08:00 · 6919 次点击
    这是一个创建于 1055 天前的主题,其中的信息可能已经有所发展或是发生改变。

    使用 http 包碰到的 error

    • 创建 context deadline exceeded (Client.Timeout exceeded while awaiting headers)

    • 创建 EOF

    • 创建 read: connection reset by peer

    • 读 body (Client.Timeout or context cancellation while reading body)

    • 读 body EOF

    • 读 body read: connection reset by peer

    • 读 body (Client.Timeout or context cancellation while reading body)

    我是不是写的代码有问题呀, 为什么要处理这么多?

    第 1 条附言  ·  2021-05-08 23:41:24 +08:00
    尝试封装之后的结果

    ```

    func rawDownloadImage(resource string, client *http.Client, logPrefix string, saveNamePath string) error {

    maxRequest := 20

    var fgetResponse = func() (*http.Response, error) {
    // response, err := http.Get(imagePath)
    return client.Get(resource)

    }
    var flog = func(text string) {
    logging.FLog.Infof("%s, %s", logPrefix, text)
    }

    for j := 0; j < maxRequest; j++ {
    // 设置超时时间为 3 分钟
    err := func() error {
    resp, err := util.RepeatRequestWithLog(fgetResponse, 60, []int{502}, time.Second*3, flog)


    if err != nil {
    return err
    }

    if resp == nil {
    log.Fatalf("%s, login resp is null, err:%v", logPrefix, err)
    }
    logging.FLog.Infof("%s, login code:%d, err:%v", logPrefix, resp.StatusCode, err)
    // util.PanicIfNotNull(err)
    if resp.Body != nil {
    defer resp.Body.Close()
    }
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
    return err
    }

    out, err := os.Create(saveNamePath)
    if err != nil {
    return err
    }
    _, err = io.Copy(out, bytes.NewReader(body))
    if err != nil {
    return err
    }
    logging.FLog.Infof("%s, complete save path:%s", logPrefix, saveNamePath)
    // return nil

    // return resp
    return nil
    }()

    if err != nil {
    if strings.Contains(err.Error(), "(Client.Timeout or context cancellation while reading body)") {
    if j < maxRequest-1 {
    continue

    }
    return err
    }
    if util.StringContainAny(err.Error(), "EOF", "read: connection reset by peer", "http: server closed idle connection") {
    if j < maxRequest-1 {
    flog(fmt.Sprintf("err:%s", err.Error()))
    // 关闭 IdelConnections
    client.CloseIdleConnections()
    flog("close_idle_connections")
    continue
    }
    return err

    }
    return err
    }
    break
    } //

    return nil
    }

    ```
    第 2 条附言  ·  2021-05-08 23:41:32 +08:00
    RepeatRequestWithLog 代码

    ```
    func RepeatRequestWithLog(getResponse FGetResponse, maxCount int, retryCodeArr []int, duration time.Duration, flog func(text string)) (*http.Response, error) {
    for i := 0; i < maxCount; i++ {
    resp, err := getResponse()

    if err != nil {
    if strings.Contains(err.Error(), "(Client.Timeout or context cancellation while reading body)") || strings.Contains(err.Error(), "context deadline exceeded (Client.Timeout exceeded while awaiting headers)") ||
    strings.Contains(err.Error(), " i/o timeout (Client.Timeout exceeded while awaiting headers)") ||
    strings.Contains(err.Error(), " EOF") {
    if i < maxCount-1 {
    flog(fmt.Sprintf("sleep for index:%d, err:%s, max:%d", i, err.Error(), maxCount))
    time.Sleep(duration)
    continue
    }

    }
    return resp, err
    }
    code := resp.StatusCode
    isInRetryCode := false
    for _, iterCode := range retryCodeArr {
    if code == iterCode {
    isInRetryCode = true
    }
    }
    // should return
    if !isInRetryCode {
    return resp, nil

    } else {
    if resp != nil {
    resp.Body.Close()
    }
    // logging.FLog.Infof("time sleep")
    flog(fmt.Sprintf("sleep for index:%d", i))
    time.Sleep(duration)
    }

    }

    return nil, errors.New("should not reach here")
    }

    ```
    23 条回复    2021-05-10 11:37:53 +08:00
    ch2
        1
    ch2  
       2021-05-08 21:13:39 +08:00
    connection reset by peer 是 tcp 连接断开,这网络编程中是必须处理的
    Jirajine
        2
    Jirajine  
       2021-05-08 21:15:49 +08:00 via Android
    简单应用请求个 API 之类的你不用管那么多的,出错就抛就完事了。
    chenqh
        3
    chenqh  
    OP
       2021-05-08 21:19:57 +08:00
    @Jirajine 关键我在加重试功能,你懂的,就是一个请求,重复请求几次,知道成功

    @ch2 关键 golang ioutil.ReadAll(resp.Body) 也会触发 `connection reset by peer`, `(Client.Timeout or context cancellation while reading body)`, `EOF` 感觉要重复处理,关键封装 RepeatRequest 完全封装不了
    chenqh
        4
    chenqh  
    OP
       2021-05-08 21:21:24 +08:00
    还有

    * 创建 http: server closed idle connection

    * 读 body http: server closed idle connection
    rekulas
        5
    rekulas  
       2021-05-08 21:23:00 +08:00
    跟 go 没关 网络 /传输规范定的
    这也就算中层,已经算好用了,你要是去做更底层对接才吐血
    billlee
        6
    billlee  
       2021-05-08 21:26:15 +08:00
    需要方便使用的一个封装库吧,就像 Java 写 HTTP 也没有人会直接用标准库的 HttpUrlConnection
    gBurnX
        7
    gBurnX  
       2021-05-08 22:10:48 +08:00
    你的 C 语言老师,应该不太负责。

    一款程序,要稳定,需要讲究鲁棒性。鲁棒性的提高,需要通过处理很多很多问题来实现的,是很麻烦。

    举个大家都懂的例子:

    写文件。也就是程序向本地存储写文件。

    你能想到哪些会导致不稳定或失败的场景?

    我先不说答案,你自己可以先考虑一下。只有你考虑到的场景越多,你的程序都对这些场景做了处理,你的程序才会更健壮,更稳定。
    Jirajine
        8
    Jirajine  
       2021-05-08 22:40:45 +08:00 via Android
    @chenqh 重试也可以不用管具体错误是什么,数据小就直接 ReadAll 读完,数据大需要流式读只需要处理下 EOF,别的都不用管。
    chenqh
        9
    chenqh  
    OP
       2021-05-08 22:42:42 +08:00
    @Jirajine 我就是 readAll
    Jirajine
        10
    Jirajine  
       2021-05-08 22:48:07 +08:00 via Android
    @chenqh 那就什么都不用管,直接顺着调用,出错就抛。上层调用遇到错误计数加一然后重试,超过 n 次再抛。
    janxin
        11
    janxin  
       2021-05-08 22:50:38 +08:00
    这个跟封装没关系吧,本质上是各种类型错误处理的问题。提到需要区分每种错误然后处理的话这样子是没法避免的就是这么多,因为每个错误出现都是一种可能性;当然你想合并处理也并无不可,通过判断错误类型之类的就可以,但是创建和读 body 的错误合并没问题吗?如果这种前提下更多的错误可以合并处理吧?
    chenqh
        12
    chenqh  
    OP
       2021-05-08 22:53:49 +08:00
    @janxin 怎么合并呀,感觉合并了,如果是 exception 那种我知道合并, golang 这个 error 我不会合并
    Vedar
        13
    Vedar  
       2021-05-08 23:25:15 +08:00
    不用处理这么多 直接 panic recover 里面处理 就好了呀 和 exception 一样的呀
    wangsongyan
        14
    wangsongyan  
       2021-05-08 23:25:44 +08:00
    感觉你代码有问题,贴出来看看?
    lujjjh
        15
    lujjjh  
       2021-05-09 00:42:56 +08:00 via iPhone   ❤️ 1
    https://blog.golang.org/error-handling-and-go

    其实你要判断的是 err 是不是 Temporary 的,如果是,就是 retryable 的。
    neoblackcap
        16
    neoblackcap  
       2021-05-09 09:55:59 +08:00
    这些也是非常常见的网络错误,这换其他的语言也要处理的
    chenqh
        17
    chenqh  
    OP
       2021-05-09 11:15:08 +08:00
    @neoblackcap 关键是 golang, 读 body 也要重复处理一次,想 py,的 requests, res.text 直接读了,可能是我还不习惯 golang 的 error 处理把,如果是 python 那种 exceptions,我知道怎么到,golang 的 error 就感觉不会做了
    matrix67
        18
    matrix67  
       2021-05-09 11:36:08 +08:00
    sagaxu
        19
    sagaxu  
       2021-05-09 11:36:25 +08:00 via Android
    panic recover 就是 try catch 换个皮
    joesonw
        20
    joesonw  
       2021-05-09 14:15:20 +08:00   ❤️ 1
    别听人忽悠用 panic. panic 很重的, 会带上整个调用栈信息, 除非是 critical 需要中断业务处理才 panic, 用 error 虽然麻烦点, 但是更明确, 轻量.
    xsen
        21
    xsen  
       2021-05-09 18:52:08 +08:00
    net/http 提供的只是基本的接口与功能。你若要重试功能,一个是基于 net/http 自己完善
    另外一个,可以用第三方的库来,比如 resty
    https://github.com/go-resty/resty/

    还有别的,都是自带 retry 功能的
    zjyl1994
        22
    zjyl1994  
       2021-05-09 23:23:29 +08:00
    strings.Contains 查错误太怪了吧?有错打到日志就行了,能处理的处理不能处理的返给上一层呀,不需要分什么 eof 啥的。
    777777
        23
    777777  
       2021-05-10 11:37:53 +08:00
    用现成的库,resty
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5949 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 02:08 · PVG 10:08 · LAX 19:08 · JFK 22:08
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.