Web Server 연동
StAX-XML은 EventReader를 통해 web standard ReadableStream<Uint8Array> 입력을
받습니다. Parser가 pure JavaScript이므로 server deploy에서 native addon 설치,
Lambda layer, Docker image rebuild, edge runtime용 binary fallback이 필요하지
않습니다.
서버 체크리스트
섹션 제목: “서버 체크리스트”- 큰 XML에서는
request.text()나request.arrayBuffer()보다 request stream을 사용하세요. - Body size limit과 timeout은 web framework에서 먼저 설정하세요. Parser가 첫 번째 resource limit이 되면 안 됩니다.
- 일반적인 async request stream은
EventReader를 사용하세요. - Event object 할당을 줄이는 것이 중요하면
StreamReader를 사용하세요. 애플리케이션이 bounded synchronous byte chunk를 이미 가지고 있고 CPU work 위치를 통제할 수 있을 때만StreamReaderSync를 사용하세요. - File stream chunk size는 64 KiB에서 1 MiB 근처가 적당합니다. 너무 작은 chunk는 framework overhead를 늘리고, 너무 큰 chunk는 parser가 yield하기 전 latency를 키웁니다.
- Request 사이에 reader를 공유하지 마세요. Request body마다 reader를 하나씩 새로 만드세요.
Express
섹션 제목: “Express”Express request object는 Node stream입니다. EventReader에 넘기기 전에 web stream으로
변환하세요.
import express from 'express';import { Readable } from 'node:stream';import { EventReader, XmlEventType } from 'stax-xml';
const app = express();
app.post('/xml', async (req, res, next) => { try { const stream = Readable.toWeb(req) as ReadableStream<Uint8Array>; const reader = new EventReader(stream); let items = 0;
for await (const event of reader) { if (event.type === XmlEventType.START_ELEMENT && event.name === 'item') { items++; } }
res.json({ items }); } catch (error) { next(error); }});큰 XML을 받을 수 있는 endpoint에서는 express.text()를 피하세요. Handler가 실행되기
전에 전체 body를 buffering합니다.
Fastify
섹션 제목: “Fastify”Streaming XML endpoint에서는 raw request stream을 사용하세요. Route handler 전에 body를 eager buffering하지 않도록 content type parser를 등록하세요.
import Fastify from 'fastify';import { Readable } from 'node:stream';import { EventReader, XmlEventType } from 'stax-xml';
const fastify = Fastify();
fastify.addContentTypeParser( ['application/xml', 'text/xml'], (_request, payload, done) => done(null, payload),);
fastify.post('/xml', async (request, reply) => { const stream = Readable.toWeb(request.body as Readable) as ReadableStream<Uint8Array>; const reader = new EventReader(stream); let elements = 0;
for await (const event of reader) { if (event.type === XmlEventType.START_ELEMENT) { elements++; } }
return reply.send({ elements });});Hono와 Cloudflare Workers
섹션 제목: “Hono와 Cloudflare Workers”Fetch 기반 runtime은 request body를 이미 web stream으로 노출합니다.
import { Hono } from 'hono';import { EventReader, XmlEventType } from 'stax-xml';
const app = new Hono();
app.post('/xml', async (c) => { const body = c.req.raw.body; if (!body) { return c.json({ error: 'Missing request body' }, 400); }
let elements = 0; for await (const event of new EventReader(body)) { if (event.type === XmlEventType.START_ELEMENT) { elements++; } }
return c.json({ elements });});
export default app;Next.js Route Handler
섹션 제목: “Next.js Route Handler”Route handler는 standard Request를 받습니다. Node runtime과 Edge runtime에서
request.body를 그대로 사용하세요.
import { EventReader, XmlEventType } from 'stax-xml';
export async function POST(request: Request) { if (!request.body) { return Response.json({ error: 'Missing request body' }, { status: 400 }); }
let elements = 0; for await (const event of new EventReader(request.body)) { if (event.type === XmlEventType.START_ELEMENT) { elements++; } }
return Response.json({ elements });}Bun
섹션 제목: “Bun”Bun.serve()도 Fetch Request 모델을 사용합니다.
import { EventReader, XmlEventType } from 'stax-xml';
Bun.serve({ async fetch(request) { if (!request.body) { return Response.json({ error: 'Missing request body' }, { status: 400 }); }
let elements = 0; for await (const event of new EventReader(request.body)) { if (event.type === XmlEventType.START_ELEMENT) { elements++; } }
return Response.json({ elements }); },});Deno
섹션 제목: “Deno”Deno server API도 fetch-compatible이므로 같은 stream 경로를 사용합니다.
import { EventReader, XmlEventType } from 'npm:stax-xml';
Deno.serve(async (request) => { if (!request.body) { return Response.json({ error: 'Missing request body' }, { status: 400 }); }
let elements = 0; for await (const event of new EventReader(request.body)) { if (event.type === XmlEventType.START_ELEMENT) { elements++; } }
return Response.json({ elements });});Backpressure와 CPU work
섹션 제목: “Backpressure와 CPU work”EventReader는 I/O boundary에서 request stream을 await한 뒤, 도착한 byte batch를
동기적으로 drain합니다. 이 방식은 parsing을 예측 가능하고 portable하게 유지하지만,
매우 큰 batch는 parsing되는 동안 JavaScript thread를 점유합니다.
Latency에 민감한 server에서는 incoming chunk를 bounded하게 유지하고 parser loop 안에 무거운 business logic을 넣지 마세요. 필요한 field만 최소한으로 추출한 뒤, request latency보다 inline completion이 덜 중요하면 queue나 worker로 domain work를 넘기는 편이 낫습니다.