Download the zipped file in the browser from the golang server

I am trying to download an archived file from the Go web server. I successfully pinned the file and can unzip it from the directory of my server. The problem I am facing is serving the file and downloading it using Javascript.

Here is an overview of my code:

1) Make a request to a server that retrieves data from another endpoint

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

3) Write bytes from the buffer to the file and return the file address

4) When the user clicks on the link, make an http get request with the file address and open the contents in a new window to download

Every time I try to unzip a file, I get an error: the archive file is incomplete (with Unarchiver), and the default archive utility on the Mac shows a short download 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 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 tried many different URI schemes to load the file, but nothing 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 add more details.

+3
source share
2 answers

In the end, I went a little different way. Now I set the MIME type in the response header and create a link pointing 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>

, zip .

+2

( ), ( 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))

}
+5

All Articles