Search code examples
expresshttpgoogle-cloud-functionsmiddlewarehtml5-audio

HTTP request handler that sends an audio file from another URL as a response


I want to allow users request audio files. The files are hosted on a separate file server. I don't want users to get these files unless they've gone through my server first.

How do I make a function that basically acts as a middle man between the user and the file server. I currently have something like this:

async (req, res) => {
    const mediaLink = `https://www.example.com/audio.mp3`;
    const mediaResponse = await fetch(mediaLink, {
        headers: {
            Range: req.headers.range,
        }
    });
    const blob = await mediaResponse.blob(); // I'm guessing here. Idk.
    res.send(blob);
}

I tested this in an <audio> tag but the audio never loaded:

<audio controls src="http://localhost:5001/file-server-middleware" />

Solution

  • The correct way to handle such a request would be to pipe the response body back to the client making sure to copy across any relevant headers that may be in the response from the file server. Read up on the HTTP Functions documentation to see what use cases you should be looking out for (e.g. CORS).

    async (req, res) => {
        const mediaLink = `https://www.example.com/audio.mp3`;
    
        // You may wish to pass through Cache-related headers, such as
        // If-None-Match, If-Match, If-Modified-Since, If-Range, If-Unmodified-Since
    
        const mediaResponse = await fetch(mediaLink, {
            headers: {
                Range: req.headers.range,
            }
        });
    
        // note: this currently passes the body & status from the file server
        // as-is, you may want to send your own body on failed status codes to
        // hide the existence of the external server (i.e. custom 404 pages, no
        // Nginx error pages, etc)
    
        // mediaResponse.status may be:
        //  - 200 (sending full file)
        //  - 206 (sending portion of file)
        //  - 304 (not modified, if using cache headers)
        //  - 404 (file not found)
        //  - 412 (precondition failed, if using cache headers)
        //  - 416 (range not satisfiable)
        //  - 5xx (internal server errors from the file server)
    
        res
          .status(mediaResponse.status)
          .set({
            /* ... other headers (e.g. CORS, Cache-Control) ... */
            'Content-Type': mediaResponse.headers.get('content-type'),
            'Content-Length': mediaResponse.headers.get('content-length'),
            'Content-Encoding': mediaResponse.headers.get('content-encoding'),
            'Etag': mediaResponse.headers.get('Etag'), // for caching
            'Last-Modified': mediaResponse.headers.get('last-modified') // for caching
          });
        
        mediaResponse.body.pipe(res);
    }
    

    You may also want to look into the various express-compatible proxy modules that can handle the bodies and headers for you. Note that some of these may not function properly if used in a Firebase Cloud Function as the request bodies are automatically consumed for you.