Datastream을 PDF 뷰어에 직접 제공하는 방법

최근에 스트림 데이터와 사용 중인 PDF 뷰어에 직접 데이터를 공급하는 방법에 대해 배웠습니다. 해결책은 상당히 쉬웠지만 해결책을 찾고 무슨 일이 일어나고 있는지 이해하는 데 약간의 시간이 걸렸습니다.

약간의 맥락



이 프로젝트에서는 NextJS, MinIO(내 s3 버킷에 액세스하기 위해) 및 React PDF 뷰어를 사용하고 있습니다. API의 기본 설정에서 NextJS를 사용하고 있으므로 페이지에서 고유한 api 디렉토리를 사용하고 있습니다. 나는 그들이 백그라운드에서 진행하는 자동 매직 라우팅 시스템을 매우 좋아합니다. 이제 API에서 직접 데이터를 공급해야 하는 이유가 궁금할 것입니다. 이 프로젝트의 S3 버킷은 K8s(Kubernetes) 클러스터 내에서만 사용할 수 있도록 구성되었습니다. 즉, 브라우저가 외부에 있거나 클러스터에 있기 때문에 사전 서명된 URL을 사용하여 브라우저에서 문서를 가져올 수 없었습니다. 따라서 클러스터 내에서 데이터를 검색하는 방법이 필요했습니다. 이로 인해 API에서 직접 데이터를 제공하게 됩니다.
  • MinIO
  • React PDF Viewer
  • NextJS

  • 샘플 NextJS 구조

    ├─ app
        ├── components
        ├── lib <-- custom
        │   ├── **/*.ts
        │   ├── minio.ts
        └── pages
            ├── api
            │   ├──[documentId].ts
            └── index.tsx
    
    


    미니IO



    MinIO JavaScript Client SDK으로 작업 중이므로 API에서 MinIO 연결을 만들 수 있습니다.

    먼저 AWS S3 버킷에 연결하기 위해 환경 변수를 제공하는 클라이언트 정의와 내 앱에 필요한 메서드가 있는 MinIo 구성 파일을 설정했습니다.

    import * as Minio from 'minio';
    import config from 'lib/config';
    import { Readable } from 'stream';
    import { NextApiResponse } from 'next';
    
    const { BUCKET_NAME, BUCKET_HOST, BUCKET_PORT, AWS_ACCESSKEY_ID, AWS_SECRET_ACCESS_KEY } = config;
    
    export default function minio(): Minio.Client {
        global.minio =
            global.minio ||
            new Minio.Client({
                endPoint: BUCKET_HOST,
                port: Number(BUCKET_PORT),
                useSSL: false, 
                accessKey: AWS_ACCESSKEY_ID,
                secretKey: AWS_SECRET_ACCESS_KEY,
            });
        return global.minio;
    }
    
    // Checks if Bucket exists, create bucket if it does not
    export const upsertMinioBucket = async (minioClient: Minio.Client) => {
        const bucketExists = await minioClient.bucketExists(BUCKET_NAME);
        if (bucketExists) {
            return true;
        } else {
            await minioClient.makeBucket(BUCKET_NAME, 'us-east-1');
            return await minioClient.bucketExists(BUCKET_NAME);
        }
    };
    
    // Upload file to bucket and returns with object => (err| objInfo)
    // Uploads contents from a file to objectName.
    // fPutObject(bucketName, objectName, filePath, metaData[, callback])
    export const upsertMinioFile = async (minioClient: Minio.Client, filePath: string, fileName: string) => {
        if (filePath) {
            const objectMade = await minioClient.fPutObject(BUCKET_NAME, fileName, filePath, {});
            return objectMade;
        } 
    };
    
    export const loadMinioStream = async (minioClient: Minio.Client, fileName: string | string[]) => {
        const response = await minioClient.getObject(BUCKET_NAME, fileName).then((err, stream) => (!err ? stream : err));
    
        return response;
    };
    
    export const streamToResponse = async (stream: Readable, res: NextApiResponse): Promise<any> => {
        return new Promise<any>(() => {
            stream.on('data', function (chunk) {
                res.write(chunk);
            });
            stream.on('end', () => {
                res.end();
            });
            stream.on('error', (err) => res.end(err));
        });
    };
    
    


    getObject + streamToResponse = ❤️



    API 경로[documentId].ts에서 MinIO 개체 작업getObject과 스트림 데이터를 통해 실행하기 위한 사용자 지정 메서드를 사용하여 스트림을 응답에 직접 씁니다.

    import { NextApiRequest, NextApiResponse } from 'next';
    import { runMiddleware } from 'lib/middleware';
    import CORS from 'cors';
    import minio, { loadMinioStream, streamToResponse, upsertMinioBucket } from 'lib/minio';
    const client = minio();
    // Initializing the cors middleware
    const cors = runMiddleware(
        CORS({
            methods: ['GET', 'POST', 'OPTIONS'],
        })
    );
    
    export default async function handler(_req: NextApiRequest, res: NextApiResponse) {
        await cors(_req, res);
        const { documentId = '1' } = _req.query;
        const resp = await getDocumentStream(documentId, res);
        res.json(resp);
    }
    
    export const getDocumentStream = async (documentId: string | string[], res: NextApiResponse) => {
        try {
            const exists = process.env.NODE_ENV !== 'test' ? await upsertMinioBucket(client) : false;
            let resolve: any = null;
            if (exists && process.env.NODE_ENV !== 'test') {
                const stream = await loadMinioStream(client, documentId);
                resolve = await streamToResponse(stream, res);
            }
            return {
                statusCode: 200,
                data: resolve,
            };
        } catch (e) {
            return {
                statusCode: 500,
                data: {
                    success: false,
                    error: `${e}`,
                },
            };
        }
    };
    


    React PDF 뷰어에 입력



    그런 다음 내 파일을 PDF 뷰어에 표시하기 위해 내 API 경로를 fileURL로 제공하고 Blam이 있습니다.

    export default function PDFViewer({ document}){
        ...
        const [filePath, setFilePath] = useState('');
        const workerUrl = 'https://unpkg.com/[email protected]/legacy/build/pdf.worker.js';
    
    
        useEffect(() => {
                if (document) {
                    let path = `//${window.location.hostname}${port}/api/documents/pdf/${document?.id}`;
                    const port = window.location.port == '80' ? '' : ':' + window.location.port;
                    setFilePath(path);
                }
        }, [document]);
    
    }
    
        return(
            <Worker workerUrl={workerUrl}>
                <Viewer fileUrl={filePath}  />
            </Worker>
        )
    
    
    
    


    참고: 이 특정 솔루션은 PDF에만 국한되지 않으며 이미지, 바이너리 등의 경우에 사용할 수 있습니다.

    여기까지 왔다면 축하한다고 말하고 싶습니다! 당신은 끝까지 해냈습니다! 보상으로 이 gif를 선물합니다!

    좋은 웹페이지 즐겨찾기