写作 resp.本地文件的正文有时不完整

Writing resp.Body to a local file sometimes is incomplete

提问人:Yann Bizeul 提问时间:7/5/2023 最后编辑:Yann Bizeul 更新时间:7/5/2023 访问量:80

问:

我正在编写一个程序,该程序对某个端点进行大规模的REST调用,然后将结果按原样缓存在本地文件中,并返回一个文件指针供调用者使用。

在99%的情况下,我的代码工作正常,但我随机得到一个不包含请求整个正文的缓存文件。

代码如下所示,调用方仅调用如下:readFromCache()

    file, err := CacheProxy{}.readFromCache(url, path.Join("CACHE", a.Hostname, a.ID, s))
func (c CacheProxy) readFromCache(url string, p string) (io.ReadCloser, error) {
    c.makeDirectory(p)
    file, err := os.Open(p)
    if err != nil {
        // Can't open the file if it doesn't exists, readFromURL then
        return c.readFromURL(url, p)
    }
    return file, nil
}

func (c CacheProxy) readFromURL(url string, p string) (io.ReadCloser, error) {
    client := http.Client{}
    resp, err := client.Get(url)
    if err != nil {
        log.Errorf("Cant read from URL : %s", err)
        return nil, err
    }

    defer resp.Body.Close()

    out, err := os.Create(p)
    if err != nil {
        log.Errorf("Unable to open file : %s", err)
        return nil, err
    }

    writer := bufio.NewWriter(out)
    _, err = io.Copy(writer, resp.Body)
    if err != nil {
        log.Errorf("Unable to copy file : %s", err)
        return nil, err
    }

    writer.Flush()

    _, err = out.Seek(0, 0)
    if err != nil {
        log.Errorf("Unable seek to beginning : %s", err)
        return nil, err
    }
    return out, nil
}

我也试图添加但没有成功out.Sync()

如果我绕过缓存并直接返回,它似乎不会引起任何问题。resp.BodyreadFromURL()

我使用的原因是为了提高内存效率。io.Copy()

请注意,我返回一个,调用它是调用它的责任。io.ReadCloserClose()

我还应该补充一点,这个函数是从不同的线程调用的,但没有调用会指向同一个文件,所以我认为我不会在同一位置写两次并在写文件时覆盖文件。

将结果写入文件的部分似乎相对简单,我希望了解为什么在任何时候输出都会被截断。

我想我捕捉并记录了每一个重要的东西。如果远程服务器意外关闭了流,我会假设返回错误?errorio.Copy()

但在这种情况下,即使直接返回(绕过缓存),我也会有异常,但事实并非如此resp.Body

[更新 1]

以下代码可以正常工作:

func (c CacheProxy) readFromURL(url string, p string) (io.ReadCloser, error) {
    resp, err := http.Get(url)
    if err != nil {
        log.Errorf("Cant read from URL : %s", err)
        return nil, err
    }

    defer resp.Body.Close()

    if resp.StatusCode != 200 {
        return nil, fmt.Errorf("ResultCode != 200 (%d), aborting", resp.StatusCode)
    }

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        log.Errorf("Error while reading body : %s", err)
        return nil, err
    }

    os.WriteFile(p, body, 0600)

    out, err := os.Open(p)
    if err != nil {
        log.Errorf("Cannot reopen file : %s", err)
        return nil, err
    }
    return out, nil
}

这让我想知道我做错了什么,而且,我不喜欢目前我必须在内存中加载所有内容才能编写它。io.Copy()bufio.Writer

[更新2]

我试图使用而不是,同样的问题发生了:file.ReadFrom()io.Copy()

func (c CacheProxy) readFromURL(url string, p string) (io.ReadCloser, error) {
    resp, err := http.Get(url)
    if err != nil {
        log.Errorf("Cant read from URL : %s", err)
        return nil, err
    }

    defer resp.Body.Close()

    if resp.StatusCode != 200 {
        return nil, fmt.Errorf("ResultCode != 200 (%d), aborting", resp.StatusCode)
    }

    out, err := os.Create(p)
    if err != nil {
        log.Errorf("Unable to open file : %s", err)
        return nil, err
    }
    out.ReadFrom(resp.Body)
    if err != nil {
        log.Errorf("Unable to read stream : %s", err)
        return nil, err
    }
    err = out.Close()
    if err != nil {
        log.Errorf("Error while closing file : %s", err)
        return nil, err
    }

    out, err = os.Open(p)
    if err != nil {
        log.Errorf("Cannot reopen file : %s", err)
        return nil, err
    }
    return out, nil
}

[更新3]

有趣的是,有时文件没有被截断,但缺少开头,结尾很好,这似乎非常奇怪。我想我的线程做错了什么,但我被告知 http。客户端是完全线程安全的,我应该能够始终如一地从不同线程中的不同请求中读取不同的正文。

文件 go http io

评论

1赞 JimB 7/5/2023
你没有关闭这里,你确定它被完全刷新到磁盘了吗?out
0赞 Volker 7/5/2023
正如 JimB 已经指出的那样:如果没有 ing,就不可能写入任何内容(无论 Sync 如何)。并且(不相关)重用你的 http。客户。Close()
0赞 Yann Bizeul 7/5/2023
即使没有关闭或刷新到磁盘,这真的重要吗?我在操作后将文件指针返回给客户端,因此它应该是 ˋresp 的完整副本。Body' 在内存中可用,我不在乎它是否在以后调用 ˋClose()' 时完全写入磁盘(确实如此)outCopy()
1赞 LeGEC 7/5/2023
如果出现错误,则返回代码(而不是文件)并且不会调用。您是否确认在运行代码时从未看到错误消息?io.Copynilwriter.Flush()"Unable to copy to file :"
0赞 Yann Bizeul 7/5/2023
在不使用最新测试的情况下进行编辑。io.Copy()

答: 暂无答案