본문으로 바로가기 본문 바로가기
WIKI 기술 지식 베이스

업로드 시 이벤트 알림으로 PDF 파일 요약하기 (Use event notification to summarize PDF files on upload)

원문 보기 위키 갱신

이 튜토리얼에서는 이벤트 알림을 사용해 PDF 파일이 R2 버킷에 업로드될 때 그 파일을 처리하는 방법을 배워요. Workers AI로 PDF를 요약하고, 요약본을 같은 버킷의 텍스트 파일로 저장할 거예요.

출처: 문서

본문

사전 요구사항

계속하려면 다음이 필요해요:

Node.js 버전 관리자

Volta ↗︎ 또는 nvm ↗︎ 같은 Node 버전 관리자를 사용하면 권한 문제를 피하고 Node.js 버전을 바꿀 수 있어요. 이 가이드에서 나중에 다룰 Wrangler는 Node 버전 16.17.0 이상이 필요해요.

1. 새 프로젝트 만들기

애플리케이션 프론트엔드를 제공하는 데 Static Assets을 사용하는 새 Worker 프로젝트를 만들 거예요. 사용자는 이 프론트엔드로 PDF 파일을 업로드할 수 있고, 그 파일은 Worker가 처리해요.

다음 명령을 실행해 새 Worker 프로젝트를 만들어요:

npm create cloudflare@latest -- pdf-summarizer
yarn create cloudflare pdf-summarizer
pnpm create cloudflare@latest pdf-summarizer

설정 시 다음 옵션을 선택해요:

  • What would you like to start with? → Hello World example 선택
  • Which template would you like to use? → Worker only 선택
  • Which language do you want to use? → TypeScript 선택
  • Do you want to use git for version control? → Yes 선택
  • Do you want to deploy your application? → No 선택(배포 전에 몇 가지를 바꿀 거예요)

pdf-summarizer 디렉터리로 이동해요:

cd pdf-summarizer

2. 프론트엔드 만들기

Static Assets를 사용하면 Worker에서 애플리케이션의 프론트엔드를 제공할 수 있어요. Static Assets를 사용하려면 Wrangler 파일에 필요한 바인딩을 추가해야 해요.

{
	"assets": {
		"directory": "public"
	}
}
[assets]
directory = "public"

다음으로 public 디렉터리를 만들고 index.html 파일을 추가해요. index.html 파일에는 다음 HTML 코드가 포함되어야 해요:

HTML 코드 보기

<!doctype html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>PDF Summarizer</title>
		<style>
			body {
				font-family: Arial, sans-serif;
				display: flex;
				flex-direction: column;
				min-height: 100vh;
				margin: 0;
				background-color: #fefefe;
			}
			.content {
				flex: 1;
				display: flex;
				justify-content: center;
				align-items: center;
			}
			.upload-container {
				background-color: #f0f0f0;
				padding: 20px;
				border-radius: 8px;
				box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
			}
			.upload-button {
				background-color: #4caf50;
				color: white;
				padding: 10px 15px;
				border: none;
				border-radius: 4px;
				cursor: pointer;
				font-size: 16px;
			}
			.upload-button:hover {
				background-color: #45a049;
			}
			footer {
				background-color: #f0f0f0;
				color: white;
				text-align: center;
				padding: 10px;
				width: 100%;
			}
			footer a {
				color: #333;
				text-decoration: none;
				margin: 0 10px;
			}
			footer a:hover {
				text-decoration: underline;
			}
		</style>
	</head>
	<body>
		<div class="content">
			<div class="upload-container">
				<h2>Upload PDF File</h2>
				<form id="uploadForm" onsubmit="return handleSubmit(event)">
					<input
						type="file"
						id="pdfFile"
						name="pdfFile"
						accept=".pdf"
						required
					/>
					<button type="submit" id="uploadButton" class="upload-button">
						Upload
					</button>
				</form>
			</div>
		</div>

		<footer>
			<a
				href="https://developers.cloudflare.com/r2/buckets/event-notifications/"
				target="_blank"
				>R2 Event Notification</a
			>
			<a
				href="https://developers.cloudflare.com/queues/get-started/#3-create-a-queue"
				target="_blank"
				>Cloudflare Queues</a
			>
			<a href="https://developers.cloudflare.com/workers-ai/" target="_blank"
				>Workers AI</a
			>
			<a
				href="https://github.com/harshil1712/pdf-summarizer-r2-event-notification"
				target="_blank"
				>GitHub Repo</a
			>
		</footer>

		<script>
			handleSubmit = async (event) => {
				event.preventDefault();

				// Disable the upload button and show a loading message
				const uploadButton = document.getElementById("uploadButton");
				uploadButton.disabled = true;
				uploadButton.textContent = "Uploading...";

				// get form data
				const formData = new FormData(event.target);
				const file = formData.get("pdfFile");

				if (file) {
					// call /api/upload endpoint and send the file
					await fetch("/api/upload", {
						method: "POST",
						body: formData,
					});

					event.target.reset();
				} else {
					console.log("No file selected");
				}
				uploadButton.disabled = false;
				uploadButton.textContent = "Upload";
			};
		</script>
	</body>
</html>

애플리케이션의 프론트엔드를 보려면 다음 명령을 실행하고 터미널에 표시된 URL로 이동해요:

npm run dev
 ⛅️ wrangler 3.80.2
-------------------

⎔ Starting local server...
[wrangler:inf] Ready on http://localhost:8787
╭───────────────────────────╮
│  [b] open a browser       │
│  [d] open devtools        │
│  [l] turn off local mode  │
│  [c] clear console        │
│  [x] to exit              │
╰───────────────────────────╯

브라우저에서 URL을 열면 파일 업로드 폼이 있는 걸 볼 수 있어요. 파일을 업로드해 보면 서버에 업로드되지 않는 걸 알 수 있어요. 프론트엔드가 백엔드에 연결되어 있지 않기 때문이에요. 다음 단계에서 파일 업로드를 처리할 Worker를 업데이트할 거예요.

3. 파일 업로드 처리

파일 업로드를 처리하려면 먼저 R2 바인딩을 추가해야 해요. Wrangler 파일에 다음 코드를 추가해요:

{
	"r2_buckets": [
		{
			"binding": "MY_BUCKET",
			"bucket_name": "<R2_BUCKET_NAME>"
		}
	]
}
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "<R2_BUCKET_NAME>"

<R2_BUCKET_NAME>을 R2 버킷의 이름으로 바꿔요.

다음으로 src/index.ts 파일을 업데이트해요. src/index.ts 파일에는 다음 코드가 포함되어야 해요:

src/index.ts

export default {
	async fetch(request, env, ctx): Promise<Response> {
		// Get the pathname from the request
		const pathname = new URL(request.url).pathname;

		if (pathname === "/api/upload" && request.method === "POST") {
			// Get the file from the request
			const formData = await request.formData();
			const file = formData.get("pdfFile") as File;

			// Upload the file to Cloudflare R2
			const upload = await env.MY_BUCKET.put(file.name, file);
			return new Response("File uploaded successfully", { status: 200 });
		}

		return new Response("incorrect route", { status: 404 });
	},
} satisfies ExportedHandler<Env>;

위 코드가 하는 일은 다음과 같아요:

  • 요청이 /api/upload 엔드포인트에 대한 POST 요청인지 확인해요. 그렇다면 요청에서 파일을 가져와 Workers API로 Cloudflare R2에 업로드해요.
  • 요청이 /api/upload 엔드포인트에 대한 POST 요청이 아니면 404 응답을 반환해요.

Worker 코드가 TypeScript로 작성되었으므로 필요한 타입 정의를 추가하기 위해 다음 명령을 실행해야 해요. 필수는 아니지만 오류를 피하는 데 도움이 돼요.

request.body에 접근할 때 생길 수 있는 오류 방지하기

Request ↗︎의 본문은 한 번만 접근할 수 있어요. 같은 요청에서 이전에 request.formData()를 사용했다면 request.body에 접근할 때 TypeError가 발생할 수 있어요.

오류를 피하려면 Request의 본문에 접근하려는 각 후속 시도마다 request.clone()으로 Request 객체의 복사본을 만들어요. Workers에는 Worker당 128MB 메모리 제한이 있고, 특히 큰 파일을 Worker 메모리에 여러 번 로드하면 이 제한에 도달할 수 있다는 점을 명심해요. 메모리 사용량이 이 제한에 도달하지 않도록 Streams 사용을 고려해 보세요.

npm run cf-typegen

변경 사항을 테스트하려면 개발 서버를 다시 시작할 수 있어요:

npm run dev

4. 큐(Queue) 만들기

이벤트 알림은 R2 버킷의 데이터 변경을 포착해요. 알림을 받기 위해 새 큐 pdf-summarize를 만들어야 해요:

npx wrangler queues create pdf-summarizer

Wrangler 파일에 바인딩을 추가해요:

{
	"queues": {
		"consumers": [
			{
				"queue": "pdf-summarizer"
			}
		]
	}
}
[[queues.consumers]]
queue = "pdf-summarizer"

5. 이벤트 알림 처리

이제 이벤트 알림을 받을 큐가 있으니, Worker를 업데이트해 이벤트 알림을 처리해야 해요. PDF에서 텍스트 콘텐츠를 추출하고, Workers AI로 내용을 요약하고, 그 결과를 R2 버킷에 저장하는 Queue 핸들러를 추가해야 해요.

src/index.ts 파일을 업데이트해 Queue 핸들러를 추가해요:

src/index.ts

export default {
	async fetch(request, env, ctx): Promise<Response> {
		// No changes in the fetch handler
	},
	async queue(batch, env) {
		for (let message of batch.messages) {
			console.log(`Processing the file: ${message.body.object.key}`);
		}
	},
} satisfies ExportedHandler<Env>;

위 코드가 하는 일은 다음과 같아요:

  • queue 핸들러는 큐에 새 메시지가 추가될 때 호출돼요. 배치의 메시지를 순회하며 파일의 이름을 기록해요.

지금은 queue 핸들러가 아무것도 하지 않아요. 다음 단계에서 PDF에서 텍스트 콘텐츠를 추출하고, Workers AI로 내용을 요약하고, 버킷에 추가하도록 queue 핸들러를 업데이트할 거예요.

6. PDF에서 텍스트 콘텐츠 추출

PDF에서 텍스트 콘텐츠를 추출하기 위해 Worker는 unpdf ↗︎ 라이브러리를 사용해요. unpdf 라이브러리는 PDF 파일 작업을 위한 유틸리티를 제공해요.

다음 명령을 실행해 unpdf 라이브러리를 설치해요:

npm i unpdf
yarn add unpdf
pnpm add unpdf
bun add unpdf

src/index.ts 파일을 업데이트해 unpdf 라이브러리에서 필요한 모듈을 임포트해요:

src/index.ts

import { extractText, getDocumentProxy } from "unpdf";

다음으로 queue 핸들러를 업데이트해 PDF에서 텍스트 콘텐츠를 추출해요:

src/index.ts

async queue(batch, env) {
  for(let message of batch.messages) {
    console.log(`Processing file: ${message.body.object.key}`);
    // Get the file from the R2 bucket
    const file = await env.MY_BUCKET.get(message.body.object.key);
    if (!file) {
				console.error(`File not found: ${message.body.object.key}`);
				continue;
			}
    // Extract the textual content from the PDF
    const buffer = await file.arrayBuffer();
    const document = await getDocumentProxy(new Uint8Array(buffer));

    const {text} = await extractText(document, {mergePages: true});
    console.log(`Extracted text: ${text.substring(0, 100)}...`);
    }
}

위 코드가 하는 일은 다음과 같아요:

  • queue 핸들러는 R2 버킷에서 파일을 가져와요.
  • queue 핸들러는 unpdf 라이브러리로 PDF에서 텍스트 콘텐츠를 추출해요.
  • queue 핸들러는 텍스트 콘텐츠를 기록해요.

7. Workers AI로 내용 요약하기

Workers AI를 사용하려면 Wrangler 파일에 Workers AI 바인딩을 추가해야 해요. Wrangler 파일에는 다음 코드가 있어야 해요:

{
	"ai": {
		"binding": "AI"
	}
}
[ai]
binding = "AI"

다음 명령을 실행해 AI 타입 정의를 추가해요:

npm run cf-typegen

src/index.ts 파일을 업데이트해 Workers AI로 내용을 요약해요:

src/index.ts

async queue(batch, env) {
  for(let message of batch.messages) {
    // Extract the textual content from the PDF
    const {text} = await extractText(document, {mergePages: true});
    console.log(`Extracted text: ${text.substring(0, 100)}...`);

    // Use Workers AI to summarize the content
    const result: AiSummarizationOutput = await env.AI.run(
    "@cf/facebook/bart-large-cnn",
      {
        input_text: text,
      }
    );
    const summary = result.summary;
    console.log(`Summary: ${summary.substring(0, 100)}...`);
  }
}

queue 핸들러는 이제 Workers AI로 내용을 요약해요.

8. 요약본을 R2 버킷에 추가

이제 요약본이 있으니 R2 버킷에 추가해야 해요. src/index.ts 파일을 업데이트해 요약본을 R2 버킷에 추가해요:

src/index.ts

async queue(batch, env) {
  for(let message of batch.messages) {
    // Extract the textual content from the PDF
    // ...
    // Use Workers AI to summarize the content
    // ...

    // Add the summary to the R2 bucket
    const upload = await env.MY_BUCKET.put(`${message.body.object.key}-summary.txt`, summary, {
				httpMetadata: {
					contentType: 'text/plain',
				},
	});
	console.log(`Summary added to the R2 bucket: ${upload.key}`);
  }
}

queue 핸들러는 이제 요약본을 텍스트 파일로 R2 버킷에 추가해요.

9. 이벤트 알림 활성화

queue 핸들러가 들어오는 이벤트 알림 메시지를 처리할 준비가 됐어요. 버킷에 대해 wrangler r2 bucket notification create 명령으로 이벤트 알림을 활성화해야 해요. 다음 명령은 pdf 접미사에 대해 object-create 이벤트 유형의 이벤트 알림을 만들어요:

npx wrangler r2 bucket notification create <R2_BUCKET_NAME> --event-type object-create --queue pdf-summarizer --suffix "pdf"

<R2_BUCKET_NAME>을 R2 버킷의 이름으로 바꿔요.

pdf 접미사에 대한 이벤트 알림이 생성돼요. pdf 접미사를 가진 새 파일이 R2 버킷에 업로드되면 pdf-summarizer 큐가 트리거돼요.

10. Worker 배포

Worker를 배포하려면 wrangler deploy 명령을 실행해요:

npx wrangler deploy

wrangler deploy 명령의 출력에서 URL을 복사해요. 그 URL이 배포된 애플리케이션의 URL이에요.

11. 테스트

애플리케이션을 테스트하려면 배포된 애플리케이션의 URL로 이동해 PDF 파일을 업로드해요. 또는 Cloudflare 대시보드 ↗︎를 사용해 PDF 파일을 업로드할 수 있어요.

로그를 보려면 wrangler tail 명령을 사용할 수 있어요.

npx wrangler tail

터미널에서 로그를 볼 수 있어요. Cloudflare 대시보드로 이동해 Workers Logs 섹션에서 로그를 볼 수도 있어요.

R2 버킷을 확인하면 요약 파일이 보일 거예요.

결론

이 튜토리얼에서는 R2 이벤트 알림을 사용해 업로드 시 객체를 처리하는 방법을 배웠어요. PDF 파일을 업로드하는 애플리케이션을 만들고, PDF 파일의 요약본을 만드는 consumer Worker를 만들었어요. 또한 Workers AI로 PDF 파일의 내용을 요약하고 그 요약본을 R2 버킷에 업로드하는 방법도 배웠어요.

이미지, 비디오, 오디오 파일 같은 다른 유형의 파일을 처리할 때도 같은 접근 방식을 사용할 수 있어요. 객체 삭제, 객체 업데이트 같은 다른 유형의 이벤트를 처리할 때도 같은 접근 방식을 사용할 수 있어요.

이 튜토리얼의 코드를 보려면 GitHub ↗︎에서 찾을 수 있어요.

더 알아보기 (Learn more)