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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
$.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
        }
    }
});
  • 无法保证您调用的URL会设置Content-Length,因此,如果响应的URL非零,则只应在响应中进行设置。

查看以下两行:

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

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

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

还要注意,不能保证您调用的URL会设置Content-Length,因此,如果响应的URL非零,则只应在响应中进行设置。还要注意,也不能保证它发送的内容长度正确,因此您应谨慎处理。还要注意,内容长度标头是由net/http包自动解析的,并存储在响应中,您可以使用它:Response.ContentLength

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

http: wrote more than the declared Content-Length

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

1
2
3
4
5
6
7
8
9
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请求是否成功的检查。您的评论表明这是一个错误页面。首先检查:

1
2
3
4
5
6
7
8
9
10
11
12
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
}
  • r.Header.Get(" Content-Length")不为空,在我的情况下为434
  • @SandunPriyanka然后,您的查询似乎返回一个错误页面,该页面不是PDF。 您应该先检查一下。
  • 在我的情况下,resp.StatusCode == http.StatusOK,但是在运行io.copy时,发生了以上错误" http:写的内容多于声明的Content-Length 2016/12/20 15:29:02 http:多个response.WriteHeader 电话"
  • @SandunPriyanka参见编辑后的答案。 您使用了r.Header.Get("Content-Type"),但您应该使用resp.Header.Get("Content-Type")
  • 该错误已修复,但生成的pdf为空白页。
  • @SandunPriyanka林不惊讶PDF是空白的。 一个PDF有一个标题,如果完整的PDF只有434个字节,那么内容的空间就不大了。 您的PDF源不生成内容。
  • 感谢你的帮助。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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)

  • 欢迎来到SO! 您是否也想通过添加一些解释来改善答案?
  • @FailedScientist您可以在终端上构建并运行它以启动服务器