胤的博客
CC BY-NC-SA 4.0

©️ 2023 胤 版权所有

banner

所念皆星河

avatar

Next.js 服务端的一个妙用

2023.09.19

目前防止盗链的一种方法是判断请求的 referer 值,只对符合条件的请求响应资源。为了方便用户能够直接在浏览器地址栏访问资源,一些图片会允许空值 referer,这时候浏览器可以通过设置 referer policy 置空 referer 达到请求资源的目的。我的博客使用的是 Bilibili 图床,B 站图片就允许 referer 为空。

最近我在研究怎么用浏览器前端下载 B 站视频,通过查阅 API 文档,我发现 B 站视频请求的 referer 必须是 bilibili 域下的,并且不能为空。然而,出于安全策略的考虑,浏览器前端无法直接修改 referer 为指定值。首先我想到的一个方法是反向代理,反代可以在服务端设置 referer 请求头,但是 B 站视频解析出的下载地址可能是不同的域名,因此反代要设置 Nginx 规则,对请求进行不同转发。

我在测试的时候用的是 Next.js 框架,这是一个自带服务端的框架,因此,我们可以直接在 Next.js 的服务端修改 referer。在官方文档中,route.ts 文件可以处理客户端的请求,我们要做的就是使用这个文件转发请求从而获取资源。

// api/router.ts

import axios from "axios";
import type { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
  // 视频地址
  const { url } = await req.body.json();

  try {
    const res = await axios({
      url,
      method: "GET",
      headers: {
        Referer: "https://www.bilibili.com",
        Cookie: "XXX",
      },
      // 这里需要请求资源的二进制文件流
      responseType: "arraybuffer",
    });

    // 直接返回二进制文件流
    return res.data;
  } catch (e) {
    // 出错再次抛出
    throw e;
  }
}

Next.js 服务端返回的是二进制文件流,在客户端需要重新处理:

"use client";

import axios from "axios";
import { useState } from "react";

export default function Index() {
  const [downloadUrl, setDownloadUrl] = useState("");
  const fetchData = async (url: string) => {
    try {
      const res = await axios({
        url: "/api/router.ts",
        method: "POST",
        responseType: "arraybuffer",
        data: {
          url,
        },
      });

      const data = res.data;
      const blob = new Blob([data], {
        type: "video/mp4",
      });
      setDownloadUrl(URL.createObjectURL(blob));
    } catch (e) {
      console.log(e);
    }
  };

  return (
    <div>
      {downloadUrl && (
        <a href={downloadUrl} download={"test.mp4"}>
          {downloadUrl}
        </a>
      )}
      <button onClick={() => fetchData("XXXXX")}>fetch</button>
    </div>
  );
}

上述方法的本质是在服务端转发请求的时候重新设置请求头,从而获取资源的二进制数据,再把二进制数据传给前端,前端通过 blob 将二进制数据保存在内存中,并生成下载链接。

归功于 Next.js 自带的服务端,我们不用去配置额外的 Nginx 规则。但是以上的代码还是存在一些问题,在测试中,客户端要等服务端取得了视频所有的二进制数据后才会得到响应,假如一个视频的大小是上百兆,等待时间会非常久。我看了一些开源的 B 站视频下载工具,他们的做法是请求分段视频,在分段全部拿到后通过 FFmpeg 合并成一个视频,但是 FFmpeg 的包真的太大了,我看看还有没有其他的替代品吧。

评论