【问题标题】:Downloading a file with content type Content-Type:multipart/mixed下载内容类型为 Content-Type:multipart/mixed 的文件
【发布时间】:2018-03-02 12:24:24
【问题描述】:

我正在做一个角度应用程序,我必须到达一个休息端点并下载作为响应发送的文件,但我不明白如何去做。我有如下响应标头

内容配置:附件;文件名="配置.zip" 内容类型:多部分/混合;边界=边界_25_1816124633_1519993185650 MIME-Version:1.0 Transfer-Encoding:chunked

响应看起来像

--Boundary_25_1816124633_1519993185650 Content-Type: application/json

{"配置":[{},{},{}]} --Boundary_25_1816124633_1519993185650 Content-Type: application/octet-stream

PKMÛJAä;%RecurrenceEvent_CreateContract_1.jsoníYKoãF¾ÈxÝ0è÷Ã7Mb L&íÝK0ͦCD¬1ðß(J¤HÙ²¼yV'»ÙU¬®úªú«â· ö«åºv~\Í~ùöýw³Ù,È«ù

编辑

这是我对后端的 http 调用

return this.http.get(url).map(response => {
// TODO
});

如何下​​载附加的 zip 文件?请帮忙。我被卡住了。

【问题讨论】:

  • 请把你已经试过的。还有你想在你得到 angular 后用 zip 做什么?
  • 我试过的!好吧,我得到了回复,但我不知道如何继续,所以我想我没有任何东西可以告诉你......一旦我得到 zip,我只想使用 fileSaver 下载文件......
  • 在我看来您有代码,请在问题中发布您的代码。我对您在 ajax 调用中传递的选项很感兴趣。
  • 好吧,它是一个接听电话,所以还没有通过任何电话,如果我必须这样做,我不...我真的不知道文件下载是如何工作的!

标签: javascript angular


【解决方案1】:
const executeSaveAs = (content) => {
    let blob = new Blob([content], {'type': "application/octet-stream"});
    saveAs(blob, "downloaded_zip.zip"); // This is from https://github.com/eligrey/FileSaver.js
};

return this.http.get(url, {responseType: 'arraybuffer'}).pipe(executeSaveAs);

我们需要设置预期的响应类型,我们希望它是“arraybuffer”。然后,我们为 FileSaver 执行常规操作,即创建一个 blob 并将其传递给包的 saveAs 函数。

编辑

根据评论,要求澄清解析多部分响应的各个部分。

关于多部分内容类型的信息

标题中定义的边界表示响应的不同部分。每个部分前面都有一行

--boundaryThatIsInTheHeader

方法

我们可以根据边界拆分响应。为此,我们必须首先从头部解析边界。正则表达式在这里拯救我们:

let boundaryRegex = new RegExp(/boundary=(\S+)/g);
const header = `Content-Disposition:attachment; filename="Config.zip" Content-Type:multipart/mixed;boundary=Boundary_25_1816124633_1519993185650 MIME-Version:1.0 Transfer-Encoding:chunked`; // As in the question

const boundary = '--' + boundaryRegex.exec(header)[1]; // We obtain the boundary here

现在,我们需要以边界为分隔符分割响应。

response.split(boundary);

服务器的这个特定响应返回的值是

[ "", " Content-Type: application/json\n\n {\"config\":[{},{},{}]} ", " Content-Type: application/octet-stream\n\n PKMÛJAä;%RecurrenceEvent_CreateContract_1.jsoníYKoãF¾ÈxÝ0è÷Ã7Mb L&íÝK0ͦCD¬1ðß(J¤HÙ²¼yV'»ÙU¬®úªú«â· ö«åºv~Í~ùöýw³Ù,È«ù"]

请注意数组的第二个元素,即 JSON。这就是我们想要的!我们现在可以使用简单的 RegEx 删除额外的数据,即内容类型。如果响应的格式是固定的,我们也可以直接按照索引去掉。 zip 文件的内容也是如此。

【讨论】:

  • 厉害,这个下载附件很完美。但正如我所说,它是一个多部分响应,我将如何解析 json 正文和附件?
  • @VikhyathMaiya 现在在编辑的答案中提到了这个概念。这对你有用吗?
  • @VikhyathMaiya 对不起,如果我让答案模棱两可。在编辑中,我所做的所有解析(response.split 中的 response)都是您最初的 HTTP 调用的响应 - 您在问题本身中提到的调用,没有指定任何 responseType。现在,一旦您获得多部分响应的不同“部分”,您就可以解析各个元素,如答案中所述。
【解决方案2】:

我相信multipart/mixedmultipart/form-data 有一些共同的结构。
(不完全确定有什么区别。)

form-data 主要用于向服务器发送表单,但也可以也可以反过来使用。

fetch api 有一个名为.formData()的方法

这主要与服务人员有关。如果用户提交表单并且服务工作者拦截了请求,您可以例如在其上调用 formData() 以获取键值映射,修改某些字段,然后将表单继续发送到服务器(或在本地使用) .

因此,如果我们能够获得响应并将 content-type 标头更改为 multipart/form-data,我们就可以利用 fetch api 来读取内容,而无需对其进行解析。

getExampleResponse(async res => {
  // replace the content-type to multipart/form-data so fetch api can parse it
  const type = res.headers.get('content-type')
  res.headers.set('content-type', type.replace('mixed', 'form-data'))

  // return the response as a formData
  const fd = await res.formData()

  // console.log(...fd)
  console.log(JSON.parse(fd.get('json')))
  console.log(fd.get('image'))

  const file = fd.get('image')
  const link = document.createElement('a')
  const image = new Image
  link.href = image.src = URL.createObjectURL(file)
  link.innerText = 'download ' + (link.download = file.name)
  
  // saveAs(file); // This is from https://github.com/eligrey/FileSaver.js

  document.body.appendChild(image)
  document.body.appendChild(link)  
})




/* 
Don't mind this, it's just an example response you are getting...
What you actually want is something like 

function getExampleResponse(cb) {
  fetch(url).then(cb)
}
*/
function getExampleResponse(e){var a=document.createElement("canvas"),d=a.getContext("2d");d.fillStyle="blue";d.fillRect(0,0,a.width,a.height);a.toBlob(function(b){var a={a:123};var c='--Boundary_25_1816124633_1519993185650\r\nContent-Disposition: form-data; name="json"\r\nContent-Type: application/json\r\n'+("Content-Length: "+JSON.stringify(a).length+"\r\n\r\n");c+=JSON.stringify(a)+"\r\n";c=c+'--Boundary_25_1816124633_1519993185650\r\nContent-Disposition: form-data; name="image"; filename="image.png"\r\nContent-Transfer-Encoding: binary\r\n'+
("Content-Type: "+b.type+"\r\n");c+="Content-Length: "+b.size+"\r\n\r\n";b=new Blob([c,b,"\r\n--Boundary_25_1816124633_1519993185650--\r\n"]);e(new Response(b,{headers:{"Content-Type":"multipart/mixed; boundary=Boundary_25_1816124633_1519993185650"}}))})};

【讨论】:

    【解决方案3】:

    是的!找这个很久了,应该早点弄明白。我想我会把它添加到线程中。

    var boundIndex = res.headers['content-type'].indexOf('boundary=') + 'boundary='.length;
    var bound = '--' + res.headers['content-type'].slice(boundIndex, res.headers['content-type'].length);
    
    var emanData = res.data.split(bound);
    

    我的回复包括一个 JSON 部分和 Zip 部分,然后可以将其保存到磁盘。

    谢谢!

    【讨论】:

      【解决方案4】:

      您可以使用file-saver 包使其在代码中更清晰。然后是从后端获取数据的服务方法:

      getFile(fileName: string): Observable<Blob> {
          return this.http.get(`${environment.apiServerUrl}/` + fileName, {responseType: 'blob'})
          .catch(error => this.handleErrorResponse() // catch http error);
        }
      

      然后在浏览器事件上调用函数

       this.service.getFile('filenameInServer.zip')
           .subscribe(fileData => FileSaver.saveAs(fileData, 'sugestedFileNameInDownloadDialog.zip'));
      

      【讨论】:

      • 保存文件不是大问题,它是从multipart/mixed 内容中提取文件
      猜你喜欢
      • 2020-08-06
      • 2016-02-06
      • 1970-01-01
      • 1970-01-01
      • 2011-01-15
      • 1970-01-01
      • 1970-01-01
      • 2018-12-25
      • 2012-09-05
      相关资源
      最近更新 更多