我在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 go my 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
}
以下是如何调用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
}
}
});
答案 0 :(得分: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"))
同时请注意,无法保证您调用的网址会设置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
的数字;并且只在这样设置它。
如果收到的内容缺失并且您仍想在回复中指明它,那么您别无选择,只能先将内容读入缓冲区,缓冲区的长度可以在发送之前检查和设置。< / p>
此外,您省略了检查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 :(得分: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)
一样使用