2014-02-11 185 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代碼的清晰度。 –