2014-02-11 119 views
3

我试图从Go web服务器下载压缩文件。我已经成功将文件压缩并可以从我的服务器目录中解压缩。我遇到的问题是提供文件并使用Javascript下载。从golang服务器下载浏览器中的压缩文件

这里是我的代码的概述:

1)请对服务器的请求,从另一个端点

2检索数据)结构基于文件类型用户希望返回的数据(CSV( setupCSVRows功能)或JSON)

3)而写缓冲区的字节到文件,并返回该文件的地址

4)当用户点击一个链接,使一个HTTP GET与文件地址的请求,并在新打开的内容窗户向下负载

每次我尝试解压缩我得到了错误的文件:归档文件不完整(与该取档节目)和Mac上的默认归档实用程序然后显示一个简短的加载屏幕关闭。

Go代码:

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代码:

$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); 
    }) 
} 

正如你所看到的,我已经尝试了许多不同的URI方案用于下载该文件,但没有奏效。

我需要在服务器端设置MIME类型吗?

任何帮助将不胜感激。请让我知道,如果我需要包括任何更多的细节。

+0

是否使用JS中的'+'操作符强制转换为字符串? –

回答

2

我最终走了一条略有不同的路线。现在我在响应头上设置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) 
//delete file from server once it has been served 
defer os.Remove(fullFileName) 
} 

角UI代码:

<a ng-show="complete" href="/api/batch/export/files?fullFileName={{fileAddress}}">Download {{currentCollection.name}}</a> 

这会自动触发下载的zip文件不再损坏。

+0

感谢这个例子!我的压缩文件以“下载”的形式下载。有没有办法保留文件名?谢谢。 – bxiong

5

我不能评论(新用户) - 但在关于命名文件,只需在服务之前设置的标头(用于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)) 

} 
+0

我给+1,以便下次发表评论。 – topskip

+0

很好的答案。 +1代码的清晰度。 –