来自浏览器的 GET 请求可以将文件下载到本地,但是 XMLHttpRequest JavaScript 脚本无法下载文件。

3

我认为我遇到了XMLHttpRequest的问题,当我导航到localhost/dashboard/downloadfile?file-name=hw3.txt时,该文件会在本地下载,但是如果我使用checkDownload()函数启动XMLHttpRequest,则文件不会被下载。

以下是我的客户端代码:

function checkDownload() {
  const filename = "hw3.txt";
  const xhr = new XMLHttpRequest();
  xhr.responseType = "blob";
  xhr.open('GET', `/dashboard/downloadfile?file-name=${ filename }`);
  xhr.onreadystatechange = () => {
    if(xhr.readyState === 4) {
      if(xhr.status === 200) {

      }
    }
  }
  xhr.send();
}

然后这是我的服务器代码:

app.get('/dashboard/downloadfile', requiresLogin, (req, res) => {
  const userid = req.user.id;
  const filename = req.query['file-name'];

  db.getFileKey([userid, filename], (keyres) => {
    const params = {
      Bucket: S3_BUCKET,
      Key: keyres.rows[0].filekey,
    };


    res.setHeader('Content-disposition', `attachment; filename=${ filename }`);
    res.setHeader('Content-type', `${ mime.getType(keyres.rows[0].filetype) }`);
    s3.getObject(params, (awserr, awsres) => {
      if(awserr) console.log(awserr);
      else console.log(awsres);
    }).createReadStream().pipe(res);
  });
});

什么是错误? - Emeeus
它不会抛出错误。客户端收到一个200的响应,但没有下载任何文件。 - jschelling
好的,那么您已经在xhr对象中拥有了页面,请尝试在.status的if语句内使用console.log(xhr.responseText)。 - Emeeus
XHR 不会下载文件,你应该放一个链接或其他的东西 - Emeeus
文本文件在客户端控制台上正确打印,但下载仍然无法正常工作。我认为链接不起作用,因为我在服务器上使用数据库来获取S3存储桶中用户文件的文件密钥,并且需要进行用户验证。如果有办法绕过这个问题,我肯定会尝试,但我不明白它是如何工作的。 - jschelling
据我所了解,当你使用blob时,你会将文件以二进制形式读取,可以参考这个例子http://jsfiddle.net/woLkd0bz/并更改URL。要下载文件,一个简单的链接应该就可以了,比如`<a href="yourfile.txt" download="newFile.txt">下载</a>`如果文件在浏览器中可见,你应该以这种方式下载它。 - Emeeus
3个回答

1
我搞定了。我不再尝试从s3.getObject()创建读取流,而是在服务器上生成了一个签名的URL,将其返回给客户端,然后使用带有element.href = signedRequest的'a' HTML元素,并使用JavaScript单击该元素。我遇到的新问题是,我无法找到一种在最初上传时设置s3对象的元数据的方法,我需要通过aws控制台手动更改单个s3对象的元数据,以便它具有标题Content-Disposition: attachment; filename=${ filename }

更改后的客户端代码:

function initDownload(filename) {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', `/sign-s3-get-request?file-name=${ filename }`);
  xhr.onreadystatechange = () => {
    if(xhr.readyState === 4) {
      if(xhr.status === 200) {
        const response = JSON.parse(xhr.responseText);
        startDownload(response.signedRequest, response.url);
      }
    }
  }
  xhr.send();
}

function startDownload(signedRequest, url) {
  var link = document.createElement('a');
  link.href = signedRequest;
  link.setAttribute('download', 'download');
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
}

修改了服务器代码:

app.get('/sign-s3-get-request', requiresLogin, (req, res) => {
  const userid = req.user.id;
  const filename = req.query['file-name'];

  db.getFileKey([userid, filename], (keyres) => {
    const s3Params = {
      Bucket: S3_BUCKET,
      Key: keyres.rows[0].filekey,
      Expires: 60,
    };

    s3.getSignedUrl('getObject', s3Params, (err, data) => {
      if (err) {
        // eslint-disable-next-line
        console.log(err);
        res.end();
      }
      const returnData = {
        signedRequest: data,
        url: `https://${S3_BUCKET}.s3.amazonaws.com/${ keyres.rows[0].filekey }`,
      };
      res.write(JSON.stringify(returnData));
      res.end();
    });
  });
});

0

从服务器获取到一个 blob,所以为了下载,当 xhr.status === 200 时需要执行相应操作。

类似于这样:

...
if(xhr.status === 200) {
   var fileUrl = URL.createObjectURL(xhr.responseText)
   window.location.replace(fileUrl)
}
...  

我遇到了一个错误:未捕获的类型错误:在“URL”上执行“createObjectURL”失败:找不到与提供的签名匹配的函数。 在XMLHttpRequest.xhr.onreadystatechange处。 - jschelling
我是个白痴,显然你必须在某个地方声明URL,但是我无法弄清楚构造函数,因为我得到了一个流返回 - jschelling
尝试使用window.URL。应该是一样的。这个API随浏览器一起提供。这里是文档链接:https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURL - Nichlas Brødegaard Larsson
尝试过了,但还是不起作用。我在xhr.status为200之后加了"console.log(xhr.repsonse)",txt文件正确打印出来了,但仍然没有下载到本地存储。这与服务器上的createReadStream有关吗?我需要将其转换为Blob可以读取的格式吗? - jschelling
只是为了确认一下。你想要下载到本地磁盘,而不是本地存储吗?顺便说一句,你在评论中的console.log有一个拼写错误。你的服务器应该没问题,因为你可以直接在URL上下载。 - Nichlas Brødegaard Larsson
我想将文件下载到本地磁盘。直接的URL可以正常工作,但是我无论如何都搞不明白这个XMLHttpRequest。这个拼写错误只是在这里,当我运行它时它完全正常。 - jschelling


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接