2014-02-11 3 views
3

Я пытаюсь загрузить заархивированный файл с веб-сервера Go. Я успешно закрепил файл и могу разархивировать его из каталога моего сервера. Проблема, с которой я столкнулся, заключается в обслуживании файла и его загрузке с помощью Javascript.Загрузите zipped-файл в браузере с сервера golang

Вот краткий обзор моего кода:

1) Сделайте запрос на сервер, который извлекает данные из другой конечной

2) Структура возвращаемых данных на основе типа файла пользователь хочет (CSV (setupCSVRows функция) или JSON)

3) Написать байт из буфера в файл и возвратить адрес файла

4) Когда пользователь нажимает на ссылку, сделать HTTP GET запрос с адресом файла и открыть содержимое в новом окно вниз load

Каждый раз, когда я пытаюсь разархивировать файл, я получаю сообщение об ошибке: файл архива неполный (с программой Unarchiver), а утилита архива по умолчанию на 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> 

Это автоматически запускает загрузку и почтовый файл больше не повреждено.

+0

спасибо за пример! мой zipped-файл загружен как «загрузка». есть способ сохранить имя файла? Благодарю. – 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 для ясности кода. –

Смежные вопросы