Post提交方式下载文件

提要

通常下载文件是直接GET访问下载链接,由浏览器默认行为进行下载。对于一些动态生成数据的场景,特别是需要用户提交部分参数,这时候GET请求就不合适了,需要POST提交进行下载。

Form Post下载文件

这是浏览器最原生的表单提交,可以设定form元素的methodPOST,这样请求格式为application/x-www-form-urlencoded,如果响应为文档流,浏览器会自动弹出下载对话框。这种方式的兼容性最好,但是页面会跳转是提交的action页面,或者设定formtarget_blank来新窗口/标签弹出下载对话框。

利用Blob新API

现代浏览器中已经新增了很多文件、流处理的API,这里我们需要利用Blob对象完成对文件流的接收。

function download() {  
    const defaultFilename = "unknownfile";
    return fetch("http://127.0.0.1:9000/download", {
        method: "POST",
        credentials: "include",
        headers: {
            "Content-Type": "application/json"
        }
    }).then(resp => {
        return resp.blob().then(blob => {
            // handle blob object
        });
    });
}

收到数据响应并完成文件接收之后,需要调出浏览器的下载行为,这里可以伪造一个a元素,绑定blob对象,并执行元素锚点点击,接下来则由浏览器默认行为接管下载。

function download() {  
    const defaultFilename = "unknownfile";
    return fetch("http://127.0.0.1:9000/download", {
        method: "POST",
        credentials: "include",
        headers: {
            "Content-Type": "application/json"
        }
    }).then(resp => {
        return resp.blob().then(blob => {
            const disposition = resp.headers.get("content-disposition");
            const match = disposition ? disposition.match(/attachment; filename="(.+)"/i) : null;
            const filename = match && match.length > 1 ? match[1] : defaultFilename;
            let a = document.createElement("a");
            document.body.appendChild(a);
            let url = window.URL.createObjectURL(blob);
            a.href = url;
            a.download = filename;
            a.click();
            window.URL.revokeObjectURL(url);
        });
    });
}

衍生-前台伪下载

有了第二种方法,我们还可以衍生它的应用。例如用户前台填写了一些数据,想不通过后台交互,完成提供用户下载数据的功能。下面举个例子用户填写了数据,下载为csv文件。

function fakeDownload() {  
    const records = [
        {
            key: "key1",
            value: "value1"
        },
        {
            key: "key2",
            value: "value2"
        }
    ]
    let data =
        "col1,col2";
    records.forEach(record => {
        data += `\n${record.key},${record.value}`;
    });
    const blob = new Blob([data], { type: "text/csv;charset=utf-8" });
    const link = document.createElement("a");
    let url = window.URL.createObjectURL(blob);
    link.setAttribute("href", url);
    link.setAttribute("download", "export.csv");
    document.body.appendChild(link);
    link.click();
    window.URL.revokeObjectURL(url);
}