douwei8911 2016-12-20 09:05
浏览 305

在golang中,服务器端无法进行PDF下载

I have created pdf in serverside(golang) then I want to download that pdf thorugh the api call.I have used ajax post request. that request direct into following ExportReport handlder. but my downloaded pdf document is blank page. There is error happen because of the Content-Length setting on request header Error is :

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

This error broken down pdf download.please go though my code snippets.

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
}

Following are the how to invoke ajax request.

$.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[^;=
]*=((['"]).*?\2|[^;
]*)/;
            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
        }
    }
});
  • 写回答

2条回答 默认 最新

  • 普通网友 2016-12-20 09:35
    关注

    Look at these 2 lines:

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

    You want to set the same content type and length you get when getting the PDF, but the r request is the one associated with the request you serve! It should be:

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

    And also note that there is no guarantee that the URL you call will set the Content-Length, and so you should only set it in your response if it's non-zero. Also note that there is also no guarantee that the content length it sends is correct, so you should handle that with care. Also note that the content length header is automatically parsed by the net/http package and is stored in the response, you can use just that: Response.ContentLength.

    If you set the content length, the net/http package will not allow you to send more bytes than indicated. Attempting to write more gives you the error:

    http: wrote more than the declared Content-Length

    This little example proves / verifies it:

    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))
    }
    

    The handler h() writes 2 bytes, but indicates only 1 in content length. If you change that to 2, everything works.

    So what you should do is first check r.Header.Get("Content-Length") if it's not the empty string and is a number greater than 0; and only set it if so.

    If the received content length is missing and you still want to indicate it in your response, then you have no other choice but to first read the content into a buffer, whose length you can check and set prior to sending it.

    Also you omit checking if the HTTP GET request succeeded. Your comment indicates that it was an error page. Check that first:

    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
    }
    
    评论

报告相同问题?

悬赏问题

  • ¥15 微信会员卡接入微信支付商户号收款
  • ¥15 如何获取烟草零售终端数据
  • ¥15 数学建模招标中位数问题
  • ¥15 phython路径名过长报错 不知道什么问题
  • ¥15 深度学习中模型转换该怎么实现
  • ¥15 HLs设计手写数字识别程序编译通不过
  • ¥15 Stata外部命令安装问题求帮助!
  • ¥15 从键盘随机输入A-H中的一串字符串,用七段数码管方法进行绘制。提交代码及运行截图。
  • ¥15 TYPCE母转母,插入认方向
  • ¥15 如何用python向钉钉机器人发送可以放大的图片?