title: Download zipped file in browser from golang server
body: <p>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. </p>
  
  <p>Here is an overview of my code:</p>
  
  <p>1) Make a request to server which retrieves data from another endpoint </p>
  
  <p>2) Structure the returned data based on the file type the user wants(CSV(setupCSVRows function) or JSON)</p>
  
  <p>3) Write bytes from buffer to file and return the file's address </p>
  
  <p>4) When user clicks a link, make an http get request with file address and open the contents in a new window for download</p>
  
  <p>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.</p>
  
  <p>Go Code:</p>
  
  <pre><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, &amp;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 &lt; 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
  }
  </code></pre>
  
  <p>Javascript Code:</p>
  
  <pre><code>$scope.exportCollection = function(fileType) {
      $scope.exporting = true;
      $scope.complete = false;
  
      $http.get('/api/batch/export?collectionID=' + $scope.currentCollection.collectionID + '&amp;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);
    })
  }
  </code></pre>
  
  <p>As you can see, I have tried many different URI schemes for downloading the file but nothing has worked. </p>
  
  <p>Do I need to set the MIME type on the server side?</p>
  
  <p>Any help would be greatly appreciated. Please let me know if I need to include any more details.</p>