http - PDF 下载在 golang 的服务器端不起作用

标签 http go http-headers server-side

我在服务器端 (golang) 创建了 pdf,然后我想通过 api 调用下载该 pdf。我使用了 ajax post 请求。该请求直接进入以下 ExportReport 处理程序。但是我下载的pdf文档是空白页。 由于请求 header 上的 Content-Length 设置而发生错误 错误是:

 http: wrote more than the declared Content-Length
2016/12/20 14:37:39 http: multiple response.WriteHeader calls

此错误分解为 pdf 下载。请查看我的代码片段。

func ExportReport(w http.ResponseWriter, r *http.Request) *core_commons.AppError {

    url := "https://mydomainname/reporting/repository/dashboard.pdf"

    timeout := time.Duration(5) * time.Second
    cfg := &tls.Config{
        InsecureSkipVerify: true,
    }
    transport := &http.Transport{
        TLSClientConfig:       cfg,
        ResponseHeaderTimeout: timeout,
        Dial: func(network, addr string) (net.Conn, error) {
            return net.DialTimeout(network, addr, timeout)
        },
        DisableKeepAlives: true,
    }

    client := &http.Client{
        Transport: transport,
    }
    resp, err := client.Get(url)
    if err != nil {
        fmt.Println(err)
    }
    defer resp.Body.Close()

    w.Header().Set("Content-Disposition", "attachment; filename=dashboard.pdf")
    w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
    w.Header().Set("Content-Length", r.Header.Get("Content-Length"))

    _, err = io.Copy(w, resp.Body)
    if err != nil {
        fmt.Println(err)
    }
    return nil
}

以下是如何调用ajax请求。

$.ajax({
    type: "POST",
    url: '/reporting/api/report/export',
    data: JSON.stringify(payload),
    contentType: 'application/pdf',
    success: function(response, status, xhr) {
        // check for a filename
        var filename = "";
        var disposition = xhr.getResponseHeader('Content-Disposition');
        if (disposition && disposition.indexOf('attachment') !== -1) {
            var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
            var matches = filenameRegex.exec(disposition);
            if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
        }

        var type = xhr.getResponseHeader('Content-Type');
        var blob = new Blob([response], { type: type });

        if (typeof window.navigator.msSaveBlob !== 'undefined') {
            // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
            window.navigator.msSaveBlob(blob, filename);
        } else {
            var URL = window.URL || window.webkitURL;
            var downloadUrl = URL.createObjectURL(blob);

            if (filename) {
                // use HTML5 a[download] attribute to specify filename
                var a = document.createElement("a");
                // safari doesn't support this yet
                if (typeof a.download === 'undefined') {
                    window.location = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
});

最佳答案

看看这两行:

w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
w.Header().Set("Content-Length", r.Header.Get("Content-Length"))

您想要设置与获取 PDF 时相同的内容类型和长度,但是 r 请求是与您服务 的请求相关联的请求!应该是:

w.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
w.Header().Set("Content-Length", resp.Header.Get("Content-Length"))

另请注意,无法保证您调用的 URL 会设置 Content-Length,因此您应该只在响应中设置非零值。另请注意,也不能保证它发送的内容长度是正确的,因此您应该小心处理。另请注意,内容长度 header 由 net/http 包自动解析并存储在响应中,您可以只使用:Response.ContentLength .

如果您设置内容长度,net/http 包将不允许您发送比指示更多的字节。试图写更多会给你错误:

http: wrote more than the declared Content-Length

这个小例子证明/验证了它:

func h(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Length", "1")
    fmt.Println(w.Write([]byte("hi")))
}

func main() {
    http.HandleFunc("/", h)
    panic(http.ListenAndServe(":8080", nil))
}

处理程序 h() 写入 2 个字节,但内容长度仅指示 1。如果将其更改为 2,一切正常。

所以你应该做的是首先检查 r.Header.Get("Content-Length") 它是否不是空的 string 并且是一个大于 0;并且只有在这样的情况下才进行设置。

如果接收到的内容长度缺失,而您仍想在响应中指明它,那么您别无选择,只能先将内容读入缓冲区,您可以在发送之前检查和设置缓冲区的长度。

您还忽略了检查 HTTP GET 请求是否成功。您的评论表明这是一个错误页面。首先检查:

resp, err := client.Get(url)
if err != nil {
    fmt.Println(err)
    http.Error(w, "Can't serve PDF.", http.StatusInternalServerError)
    return
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
    http.Error(w, "Can't serve PDF.", http.StatusInternalServerError)
    return
}

关于http - PDF 下载在 golang 的服务器端不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41238407/

相关文章:

python - 想从 python 请求模块获取 http 请求

go - 如何在 Go 中的 if 语句中更新变量的值?

c - 如何知道 HTTP header 部分何时结束?

http - HTTP 部分 GET 是一种可靠的机制吗?

java - 在 Android 上请求发帖时出现问题

php - 将图像从 iOS 上传到 PHP

javascript - 模块级别的 Angular HTTP 拦截器

go - 为什么 goroutine 这么慢?

go build 不会将本地更改编译到 main

php - 设置 http header 时丢失 session 数据(在 CodeIgniter 中)