1

I'm trying to setup a route for downlading videos for my Vue app backed by an Express server. For some reason, first request that is sent to backend is working as expected and it results in successful file download; however, the subsequent requests fail with Network Error, and I only get a brief error message that looks like this http://localhost:8080/download/videos/1667163624289.mp4 net::ERR_FAILED 200 (OK).

What could be the issue here?

I have an Express.js server (localhost:8000) setup with cors like below:

const express = require("express");
const app = express();
const port = 8000;
const cors = require("cors");

app.use(cors());

app.get("/download/:kind/:fileName", 
  async (req, res, next) => {
    const file = `${__dirname}/public/files/${req.params.kind}/${req.params.fileName}`;
    res.download(file);
});

app.listen(port, () => {
});

And my Vue (localhost:8080) component sends that request looks like this:

downloadVideo(fileName) {
      const fileName = fileDir.split('/').pop();
      const downloadUrl = `/download/videos/${fileName}`;
      axios({
        method: "get",
        url: downloadUrl,
        responseType: 'blob',
      })
      .then((response)=> {
        // create file link in browser's memory
        const href = URL.createObjectURL(response.data); // data is already a blob

        // create "a" element with href to file & click
        const link = document.createElement('a');
        link.href = href;
        link.setAttribute('download', 'my_video.mp4');
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(href);
      })
      .catch((err) => {
        // HANDLE ERROR HERE
      })
    },

I also have a vue config setup to proxy the requests to 8000:

// vue.config.js
module.exports = {
  devServer: {
    proxy: 'http://localhost:8000',
    disableHostCheck: true
  },
  outputDir: '../backend/public', // build will output to this folder
  assetsDir: ''     // relative to the output folder
}
ege
  • 812
  • 6
  • 16
  • Why not use `app.use(express.static("public/files"))` instead of creating a custom route? Also, on the client side, instead of downloading the response manually, let the browser handle that. Instead of making a get request, you can directly set `window.open("/download/videos/" + fileName)` – vighnesh153 Nov 02 '22 at 02:09
  • @vighnesh153 Thank you for the suggestion. I couldn't get this to auto-start the download process with Chrome. It opens up a new tab with the file, specifically the videos and pdfs. – ege Nov 03 '22 at 16:51
  • Yea. That is the problem with `window.open`. You can use your approach of creating a link to download. Added an answer. So, the only difference here is you don't create the data url and directly let express handle the download using `express.static` – vighnesh153 Nov 04 '22 at 02:54
  • What is `fileDir`? The `fileName` function param is replaced by part of this. Does that value ever change after the first download? – Mulhoon Nov 21 '22 at 13:20

2 Answers2

1

Instead of manually setting up the route for downloading files, you can directly set the static files directory as a controller and remove the app.get controller for downloading.

app.use(express.static("public/files"))

Then, on the client side, instead of downloading the file using JS, converting it into a data url, and then downloading it, you can do the following:

downloadVideo(fileName) {
    // whatever you want to do to the file name
    const parsedFileName = fileName

    const link = document.createElement('a');
    link.href = parsedFileName;
    link.setAttribute('download', 'my-video.mp4');  // or pdf
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
}

Here is a sample working example

vighnesh153
  • 4,354
  • 2
  • 13
  • 27
  • Thank you for your answer. This definitely simplifies the server and routes (although I'm not sure if setting up the route and sending the file with `res.download` would be more efficient, especially for the big files). However, this doesn't really answer my question above which is trying to understand why subsequent requests are failing... – ege Nov 05 '22 at 00:08
1

Im not really sure why the first request is going through. But the error looks like a CORS problem.

Basically, your frontend and backend run on different ports, which are treated like a different server altogether by the CORS checks.

I took the following config from the cors package docs

var express = require('express')
var cors = require('cors')
var app = express()
 
var corsOptions = {
  origin: 'http://localhost:8080',
  optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}

/*

Or use this, if you want to bypass cors checks - not a safe practice
var corsOptions = {
  origin: '*',
  optionsSuccessStatus: 200
}
*/
 
app.get('/products/:id', cors(corsOptions), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for only localhost:8080'})
})
 
app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

The 200 ERR is funny, because normally 200 means OK. But in this case 200 refers to the Preflight request and ERR to the fact that the Ports are different.

There is a good video about cors on youtube

Laurenz Honauer
  • 254
  • 1
  • 12
  • I'm still having the same issue... The first request goes through and I can download the video, but if I do it again I get the same error as above. – ege Nov 22 '22 at 03:05
  • 1
    I just recognized that you have a proxy set up anyway. That should basically render my answer irrelevant. Due to the proxy, there is only one origin from your servers POV which eliminates the possibility of it being a cors problem. – Laurenz Honauer Nov 22 '22 at 10:03