react-pdf分片获取服务器pdf文件,并进行身份验证

react-pdf分片获取服务器pdf文件,并进行身份验证

笔者最近想要在next项目中渲染pdf文件,了解到了react-pdf库,通过访问url来获取文件。但是当pdf文件较大时,会受制网速,使得体验不佳,于是想要优化。

实现逻辑:因为是用url去获取文件,所以会用延时的问题,如果文件过大,那么就会造成界面的长时间空白,这样肯定是不行的,那么就要进行切片获取——即每次请求只拿到部分数据,这样就会快很多。
使用分片操作,即后端允许分片返回数据,当前端访问该接口时候,知道可以分片返回,react-pdf就会自动调用分片获取,从而极大的优化获取速度,提升使用体验。

react-pdf基于pdfjs开发的,因为pdfjs是默认支持分片获取的,所以只有后端返回的接口符合分片结果,react-pdf就会自动进行分片请求,从而提升体验。

后端实现

后端使用fastapi

代码

from fastapi import APIRouter, Depends, HTTPException, Query, Request
from middlewares.auth import AuthBearer
from fastapi.responses import StreamingResponse
import os
import re

pdf_router = APIRouter()

# get specific pdf
@pdf_router.get("/pdf", tags=["PDF"])
async def get_pdf_handler(
    request: Request,
):
    file_path = os.path.join(
        os.path.dirname(os.path.dirname(__file__)),
        "pdfs",
        "example.pdf",
    )
    if not os.path.exists(file_path):
        raise HTTPException(status_code=404, detail=f"{file_path} not found")

    file_size = os.path.getsize(file_path)
    range_header = request.headers.get("Range", None)
    byte_range = 1024 * 1024
    start = 0
    end = file_size - 1

    if range_header:
        try:
            match = re.search(r"bytes=(\d+)-(\d+)?", range_header)
            if match:
                start = int(match.group(1))
                if match.group(2):
                    end = int(match.group(2))
                else:
                    end = min(start + byte_range - 1, file_size - 1)
        except ValueError:
            raise HTTPException(status_code=416, detail="Invalid range header")

    def iterfile(file_path, start, end):
        try:
            with open(file_path, "rb") as f:
                f.seek(start)
                while start <= end:
                    bytes_to_read = min(byte_range, end - start + 1)
                    data = f.read(bytes_to_read)
                    if not data:
                        break
                    start += len(data)
                    yield data
        except IOError:
            raise HTTPException(status_code=500, detail="Error reading file")

    headers = {
        "Content-Range": f"bytes {start}-{end}/{file_size}",
        "Accept-Ranges": "bytes",
        "Content-Length": str(end - start + 1),
        "Content-Type": "application/pdf",
        "Access-Control-Expose-Headers": "Accept-Ranges,Content-Range"
    }

    return StreamingResponse(
        iterfile(file_path, start, end), headers=headers, status_code=206
    )

由后端代码可以看出,返回的headers是包含Range头的,这就表明了可以分片获取,然后返回流式数据,即实现了笔者需求。

身份验证

笔者使用的后端服务是supabase,提供了auth验证

因为react-pdf接受的输入是url,而为了传输身份信息需要在请求头添加Bearer信息,这样的话在身份验证之间和pdf渲染之间就有矛盾。

笔者了解到可以先获取数据然后将数据转化为数据流传给react-pdf,但是笔者还是想要最直观的方式来解决该问题。

于是考虑从输入到react-pdf中的url入手,既然单纯的url无法实现身份信息传输,那么就借由next的api,进行一个请求转发,在转发的过程中添加身份信息,并且保证了在react-pdf中的输入依然是一个url。

代码

import { NextRequest, NextResponse } from "next/server";
import { ApiPath } from "@/app/constant";

async function handle(
  req: NextRequest,
  { params }: { params: { query: string[] } },
) {
  const [pdfFile, access_token] = params.query;
  req.headers.set("Authorization", `Bearer ${access_token}`);

  const fetchOptions: RequestInit = {
    method: req.method,
    headers: req.headers,
  };

  const baseUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
  const path = ApiPath.Pdf;
  const fetchUrl = `${baseUrl}${path}?query=${pdfFile}`;

  try {
    const res = await fetch(fetchUrl, fetchOptions);

    const responseHeaders = new Headers(res.headers);
    responseHeaders.set("Content-Type", "application/pdf");

    return new NextResponse(res.body, {
      status: res.status,
      statusText: res.statusText,
      headers: responseHeaders,
    });
  } catch (e) {
    console.error("[pdf api]", e);
  }
}

export const GET = handle;

该代码写在app/api/pdf/[...query]/route.ts中,之后通过访问本地的/api/pdf端口就可以实现pdf获取,并且实现了身份信息的添加。

但是可以看出,access_token是传输进来,因为ts文件无法获取supabase的token,为了达到身份信息的要求,无奈摈弃了实现的方式的优雅,在访问/api/pdf端口的时候传入了pdfFile和accessi_token。(如果有读者有更好的实现反方式麻烦教授一下)

参考文章

github:react-pdf-p2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值