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[^;=\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
}
}
});
Look at these 2 lines:
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: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 thenet/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:This little example proves / verifies it:
The handler
h()
writes 2 bytes, but indicates only 1 in content length. If you change that to2
, everything works.So what you should do is first check
r.Header.Get("Content-Length")
if it's not the emptystring
and is a number greater than0
; 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:Use like http://127.0.0.1:2333/?url=
base64encoded(url)