golang中的PDF下载无法从服务器端运行

问题描述 投票:0回答:2

我在serverside(golang)创建了pdf然后我想通过api调用下载pdf。我使用了ajax post请求。请求直接进入以下ExportReport handlder。但我下载的pdf文件是空白页。由于请求标头上的Content-Length设置而发生错误错误是:

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

这个错误分解了pdf download.please通过我的代码片段。

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
        }
    }
});
http go http-headers server-side
2个回答
2
投票

看看这两行:

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,因此您只应在响应中设置它,如果它不为零。另请注意,也不能保证它发送的内容长度是正确的,因此您应该小心处理。另请注意,内容长度标头由net/http包自动解析并存储在响应中,您可以使用:Response.ContentLength

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

http:写了超过声明的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
}

1
投票
package main

import (
    "encoding/base64"
    "fmt"
    "io"
    "net/http"
    "net/url"
    "path"
)

func main() {
    fmt.Println("Starting transform download sever at http://127.0.0.1:2333")
    http.HandleFunc("/", HandleClient)
    err := http.ListenAndServe(":2333", nil)
    if err != nil {
        fmt.Println(err)
    }
}

func HandleClient(writer http.ResponseWriter, request *http.Request) {
    //First of check if Get is set in the URL
    encoded := request.URL.Query().Get("url")
    if encoded == "" {
        //Get not set, send a 400 bad request
        http.Error(writer, "Get 'url' not specified in url.", 500)
        return
    }
    decoded, err  := base64.StdEncoding.DecodeString(encoded)
    if err != nil {
        http.Error(writer, "base64 decode error", 501)
        return
    }
    fileUrl := string(decoded)
    filename, err := GetFilename(fileUrl)
    if err != nil {
        http.Error(writer, "error url", 502)
        return
    }
    resp, err := http.Get(fileUrl)
    if err != nil {
        http.Error(writer, "error url", 502)
        return
    }
    defer resp.Body.Close()
    writer.Header().Set("Content-Disposition", "attachment; filename="+filename)
    writer.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
    writer.Header().Set("Content-Length", resp.Header.Get("Content-Length"))
    _, err = io.Copy(writer, resp.Body)
    if err != nil {
        http.Error(writer, "Remote server error", 503)
        return
    }
    return
}

func GetFilename(inputUrl string) (string, error) {
    u, err := url.Parse(inputUrl)
    if err != nil {
        return "", err
    }
    u.RawQuery = ""
    return path.Base(u.String()), nil
}



http://127.0.0.1:2333/?url=base64encoded(url)一样使用

© www.soinside.com 2019 - 2024. All rights reserved.