英文:
Download zipped file in browser from golang server
问题
我正在尝试从一个Go web服务器下载一个压缩文件。我已经成功地压缩了文件,并且可以从服务器的目录中解压缩它。我遇到的问题是使用JavaScript提供文件并下载它。
以下是我的代码概述:
1)向服务器发出请求,从另一个端点检索数据
2)根据用户所需的文件类型(CSV或JSON),对返回的数据进行结构化处理
3)将缓冲区中的字节写入文件并返回文件的地址
4)当用户点击链接时,使用文件地址进行http get请求,并在新窗口中打开内容以进行下载
每次我尝试解压缩文件时,都会出现错误:存档文件不完整(使用The Unarchiver程序),而Mac上的默认存档实用程序显示一个简短的加载屏幕,然后关闭。
Go代码:
func ExportData(writer http.ResponseWriter, req *http.Request, session sessions.Session) (int, string) {
// 代码省略...
}
func ReturnFile(writer http.ResponseWriter, req *http.Request) {
// 代码省略...
}
func setupCSVRows(contents []byte) ([]byte, error) {
// 代码省略...
}
JavaScript代码:
$scope.exportCollection = function(fileType) {
// 代码省略...
};
$scope.downloadFile = function() {
// 代码省略...
};
如你所见,我尝试了许多不同的URI方案来下载文件,但没有任何效果。
我需要在服务器端设置MIME类型吗?
非常感谢您的帮助。如果需要更多详细信息,请告诉我。
英文:
I am trying to download a zipped file from a Go webserver. I have successfully zipped the file and can unzip it from my server's directory. The problem I have run into is serving the file and downloading it with Javascript.
Here is an overview of my code:
-
Make a request to server which retrieves data from another endpoint
-
Structure the returned data based on the file type the user wants(CSV(setupCSVRows function) or JSON)
-
Write bytes from buffer to file and return the file's address
-
When user clicks a link, make an http get request with file address and open the contents in a new window for download
Every time I try to unzip the file I get the error: archive file is incomplete (with The Unarchiver program) and the default Archive Utility on Mac shows a brief loading screen then closes.
Go Code:
func ExportData(writer http.ResponseWriter, req *http.Request, session sessions.Session) (int, string) {
headers := HeaderCreation{
OriginalRequest: req,
Session: session,
}
qs := req.URL.Query()
if len(qs["collectionID"]) != 1 {
return 400, "ERROR: Must submit one collectionID in query string"
}
if len(qs["fileType"]) != 1 {
return 400, "ERROR: Must submit one fileType in query string"
}
collID := qs["collectionID"][0]
fileType := qs["fileType"][0]
url := "http://" + config.Data.Address + "/api/" + collID
response, err := httpClient.DoSystemRequest("GET", url, nil, headers)
if err != nil {
return 500, "ERROR: Could not resolve DataURL/api" + err.Error()
} else {
contents, err := ioutil.ReadAll(response.Body)
response.Body.Close()
if err != nil {
return 400, "ERROR: Response from Platform unreadable"
}
buf := new(bytes.Buffer)
w := zip.NewWriter(buf)
file, err := w.Create(collID + "." + fileType)
if err != nil {
return 400, "ERROR: Unable to create zip file with name of: " + collID + " and type of: " + fileType + "; " + err.Error()
}
switch fileType {
case "csv":
rows, err := setupCSVRows(contents)
if err != nil {
return 400, err.Error()
}
_, err = file.Write(rows)
if err != nil {
return 400, "Unable to write CSV to zip file; " + err.Error()
}
case "json":
_, err := file.Write(contents)
if err != nil {
return 400, err.Error()
}
} // end switch
err = w.Close()
if err != nil {
return 400, "ERROR: Unable to close zip file writer; " + err.Error()
}
//create fileName based on collectionID and current time
fileAddress := collID + strconv.FormatInt(time.Now().Unix(), 10)
//write the zipped file to the disk
ioutil.WriteFile(fileAddress + ".zip", buf.Bytes(), 0777)
return 200, fileAddress
} //end else
}
func ReturnFile(writer http.ResponseWriter, req *http.Request) {
queries := req.URL.Query()
fullFileName := queries["fullFileName"][0]
http.ServeFile(writer, req, fullFileName)
//delete file from server once it has been served
//defer os.Remove(fullFileName)
}
func setupCSVRows(contents []byte) ([]byte, error) {
//unmarshal into interface because we don't know json structure in advance
var collArr interface{}
jsonErr := json.Unmarshal(contents, &collArr)
if jsonErr != nil {
return nil, errors.New("ERROR: Unable to parse JSON")
}
//had to do some weird stuff here, not sure if it's the best method
s := reflect.ValueOf(collArr)
var rows bytes.Buffer
var headers []string
for i := 0; i < s.Len(); i++ {
var row []string
m := s.Index(i).Interface()
m2 := m.(map[string]interface{})
for k, v := range m2 {
if i == 0 {
if k != "item_id" {
headers = append(headers, k)
}
}
if k != "item_id" {
row = append(row, v.(string))
}
}
if i == 0 {
headersString := strings.Join(headers, ",")
rows.WriteString(headersString + "\n")
}
rowsString := strings.Join(row, ",")
rows.WriteString(rowsString + "\n")
}
return rows.Bytes(), nil
}
Javascript Code:
$scope.exportCollection = function(fileType) {
$scope.exporting = true;
$scope.complete = false;
$http.get('/api/batch/export?collectionID=' + $scope.currentCollection.collectionID + '&fileType=' + fileType.toLowerCase()).success(function(data){
$scope.fileAddress = data;
}).error(function(err) {
console.log(err);
});
};
$scope.downloadFile = function() {
$http.get('/api/batch/export/files?fullFileName=' + $scope.fileAddress + ".zip")
.success(function(data) {
console.log(data);
//window.open("data:application/zip;base64," + content);
//var content = "data:text/plain;charset=x-user-defined," + data;
var content = "data:application/zip;charset=utf-8," + data;
//var content = "data:application/octet-stream;charset=utf-8" + data;
//var content = "data:application/x-zip-compressed;base64," + data;
//var content = "data:application/x-zip;charset=utf-8," + data;
// var content = "data:application/x-zip-compressed;base64," + data;
window.open(content);
})
.error(function(err) {
console.log(err);
})
}
As you can see, I have tried many different URI schemes for downloading the file but nothing has worked.
Do I need to set the MIME type on the server side?
Any help would be greatly appreciated. Please let me know if I need to include any more details.
答案1
得分: 10
我可以帮你翻译这段代码。以下是翻译的结果:
// 无法评论(新用户)- 但是关于给文件命名,只需在提供服务之前设置头部(在这里使用ServeContent,但对于你的用法应该是可互换的):
func serveFile(w http.ResponseWriter, r *http.Request) {
data, err := ioutil.ReadFile("path/to/file/and/file+ext")
if err != nil {
log.Fatal(err)
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", "attachment; filename=" + "fileName.here")
w.Header().Set("Content-Transfer-Encoding", "binary")
w.Header().Set("Expires", "0")
http.ServeContent(w, r, "path/to/file/and/file+ext", time.Now(), bytes.NewReader(data))
}
希望对你有帮助!
英文:
I can't comment (new user) - but in regards to naming the file, simply set the headers before serving (ServeContent used, but should be interchangable for your usage here):
func serveFile(w http.ResponseWriter, r *http.Request){
data, err := ioutil.ReadFile("path/to/file/and/file+ext")
if(err != nil){
log.Fatal(err)
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", "attachment; filename=" + "fileName.here")
w.Header().Set("Content-Transfer-Encoding", "binary")
w.Header().Set("Expires", "0")
http.ServeContent(w, r, "path/to/file/and/file+ext", time.Now(), bytes.NewReader(data))
}
答案2
得分: 3
我最终选择了稍微不同的方法。现在我在响应头中设置了MIME类型,并创建了一个指向文件的链接。
Go代码:
func ReturnFile(writer http.ResponseWriter, req *http.Request) {
queries := req.URL.Query()
fullFileName := queries["fullFileName"][0]
writer.Header().Set("Content-type", "application/zip")
http.ServeFile(writer, req, fullFileName)
//一旦文件被传送,就从服务器上删除文件
defer os.Remove(fullFileName)
}
Angular UI代码:
<a ng-show="complete" href="/api/batch/export/files?fullFileName={{fileAddress}}">下载 {{currentCollection.name}}</a>
这将自动触发下载,而且zip文件不再损坏。
英文:
I ended up going down a slightly different route. Now I set the MIME type on the response header and create a link that points to the file.
Go code:
func ReturnFile(writer http.ResponseWriter, req *http.Request) {
queries := req.URL.Query()
fullFileName := queries["fullFileName"][0]
writer.Header().Set("Content-type", "application/zip")
http.ServeFile(writer, req, fullFileName)
//delete file from server once it has been served
defer os.Remove(fullFileName)
}
Angular UI code:
<a ng-show="complete" href="/api/batch/export/files?fullFileName={{fileAddress}}">Download {{currentCollection.name}}</a>
This automatically triggers the download and the zip file is no longer corrupted.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论