英文:
How to download file in browser from Go server
问题
我的代码从远程URL获取文件并在浏览器中下载文件:
func Index(w http.ResponseWriter, r *http.Request) {
url := "http://upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png"
...
resp, err := client.Get(url)
if err != nil {
fmt.Println(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
}
fmt.Println(len(body))
// 在浏览器中下载文件
}
func main() {
http.HandleFunc("/", Index)
err := http.ListenAndServe(":8000", nil)
if err != nil {
fmt.Println(err)
}
}
代码:http://play.golang.org/p/x-EyR2zFjv
获取文件是可以的,但如何在浏览器中下载它呢?
英文:
My code get file from remote url and download file in browser:
func Index(w http.ResponseWriter, r *http.Request) {
url := "http://upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png"
...
resp, err := client.Get(url)
if err != nil {
fmt.Println(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
}
fmt.Println(len(body))
//download the file in browser
}
func main() {
http.HandleFunc("/", Index)
err := http.ListenAndServe(":8000", nil)
if err != nil {
fmt.Println(err)
}
}
code: http://play.golang.org/p/x-EyR2zFjv
Get file is ok, but how to downloaded it in browser?
答案1
得分: 89
为了使浏览器打开下载对话框,需要在响应中添加Content-Disposition
和Content-Type
头部:
w.Header().Set("Content-Disposition", "attachment; filename=WHATEVER_YOU_WANT")
w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
在将内容发送给客户端之前执行此操作。您可能还希望将响应的Content-Length
头部复制到客户端,以显示正确的进度。
要将响应主体流式传输到客户端而不完全加载到内存中(对于大文件很重要),只需将主体读取器复制到响应写入器中:
io.Copy(w, resp.Body)
io.Copy
是一个非常有用的小函数,它接受一个读取器接口和一个写入器接口,从一个接口读取数据并将其写入另一个接口。对于这种情况非常有用!
我已经修改了您的代码以实现这一点:http://play.golang.org/p/v9IAu2Xu3_
英文:
To make the browser open the download dialog, add a Content-Disposition
and Content-Type
headers to the response:
w.Header().Set("Content-Disposition", "attachment; filename=WHATEVER_YOU_WANT")
w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
Do this BEFORE sending the content to the client. You might also want to copy the Content-Length
header of the response to the client, to show proper progress.
To stream the response body to the client without fully loading it into memory (for big files this is important) - simply copy the body reader to the response writer:
io.Copy(w, resp.Body)
io.Copy
is a nice little function that take a reader interface and writer interface, reads data from one and writes it to the other. Very useful for this kind of stuff!
I've modified your code to do this: http://play.golang.org/p/v9IAu2Xu3_
答案2
得分: 12
如果你已经在磁盘上有该文件,只需使用http.ServeFile()。它会自动处理Content-Length
,以便浏览器可以显示下载进度。
w.Header().Set("Content-Disposition", "attachment; filename="+strconv.Quote(filename))
w.Header().Set("Content-Type", "application/octet-stream")
http.ServeFile(w, r, filePath)
英文:
In case you already have the file on disk, just use http.ServeFile(). It automatically handles Content-Length
so that the browser can display a download progress.
w.Header().Set("Content-Disposition", "attachment; filename="+strconv.Quote(filename))
w.Header().Set("Content-Type", "application/octet-stream")
http.ServeFile(w, r, filePath)
答案3
得分: 3
你需要使用Content-Disposition
头部,并将其值设置为attachment
。MDN Web文档对该头部的说明如下:
[...]
Content-Disposition
响应头部用于指示内容是内联显示(即作为网页的一部分在浏览器中显示),还是作为附件下载并保存到本地。
如果你想为客户端指定一个文件名,可以使用filename
指令。格式如下:
Content-Disposition: attachment; filename="filename.jpg"
这是一个可选参数,并且有一些限制。
[...] 文件名始终是可选的,应用程序不应盲目使用它:应该去除路径信息,并进行服务器文件系统规则的转换。[...]
为了正确格式化头部中的文件名,你可以使用mime.FormatMediaType
函数。示例:
cd := mime.FormatMediaType("attachment", map[string]string{"filename": d.Name()})
w.Header().Set("Content-Disposition", cd)
在这种情况下,你可以使用application/octet-stream
作为内容类型,因为浏览器不需要知道响应的MIME类型。
[...] 通用的二进制数据(或其真实类型未知的二进制数据)使用
application/octet-stream
。[...]
w.Header().Set("Content-Type", "application/octet-stream")
对于输出文件内容,我建议使用http.ServeContent
或http.ServeFile
,因为它们可以直接处理RFC 7233 - 范围请求。示例:
f, err := fs.Open(name)
// [...]
cd := mime.FormatMediaType("attachment", map[string]string{"filename": d.Name()})
w.Header().Set("Content-Disposition", cd)
w.Header().Set("Content-Type", "application/octet-stream")
http.ServeContent(w, r, d.Name(), d.ModTime(), f)
英文:
What you need to use is the Content-Disposition
header with attachment
value. MDN web docs says the following about the header:
> [...], the Content-Disposition
response header is a header indicating if the content is expected to be displayed inline in the browser, that is, as a Web page or as part of a Web page, or as an attachment, that is downloaded and saved locally.
If you want to specify a filename for the clients you can use the filename directive. In the following format:
Content-Disposition: attachment; filename="filename.jpg"
This is an optional parameter and it has some restrictions.
> [...] The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done. [...]
To format filename properly in the header you should use mime.FormatMediaType
. Example:
cd := mime.FormatMediaType("attachment", map[string]string{"filename": d.Name()})
w.Header().Set("Content-Disposition", cd)
In this case for content type you can use application/octet-stream
because the browser does not have to know the MIME type of the response.
> [...] Generic binary data (or binary data whose true type is unknown) is application/octet-stream. [...]
w.Header().Set("Content-Type", "application/octet-stream")
For outputing the content of a. file I would recommend to use http.ServeContent
or http.ServeFile
because they handle RFC 7233 - Range Requests out of the box. Example:
f, err := fs.Open(name)
// [...]
cd := mime.FormatMediaType("attachment", map[string]string{"filename": d.Name()})
w.Header().Set("Content-Disposition", cd)
w.Header().Set("Content-Type", "application/octet-stream")
http.ServeContent(w, r, d.Name(), d.ModTime(), f)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论