从Golang服务器在浏览器中下载压缩文件。

huangapple go评论82阅读模式
英文:

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:

  1. Make a request to server which retrieves data from another endpoint

  2. Structure the returned data based on the file type the user wants(CSV(setupCSVRows function) or JSON)

  3. Write bytes from buffer to file and return the file's address

  4. 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[&quot;fullFileName&quot;][0]

writer.Header().Set(&quot;Content-type&quot;, &quot;application/zip&quot;)
http.ServeFile(writer, req, fullFileName)
//delete file from server once it has been served
defer os.Remove(fullFileName)
}

Angular UI code:

&lt;a ng-show=&quot;complete&quot; href=&quot;/api/batch/export/files?fullFileName={{fileAddress}}&quot;&gt;Download {{currentCollection.name}}&lt;/a&gt;

This automatically triggers the download and the zip file is no longer corrupted.

huangapple
  • 本文由 发表于 2014年2月11日 11:36:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/21692580.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定