콘텐츠로 이동

Writer - 비동기 XML 스트림 라이터

Writer - 비동기 XML 스트림 라이터

섹션 제목: “Writer - 비동기 XML 스트림 라이터”

Writer는 논블로킹 WritableStream 워크플로우, 스트리밍 응답, 실시간 XML 생성을 위한 비동기 스트림 기반 XML 라이터입니다.

대용량 파일이나 대용량 문서를 최대 처리량으로 생성해야 한다면 WriterSyncSink를 권장합니다. 1GiB writer 벤치마크에서 sync sink 경로가 가장 높은 쓰기 처리량을 보였고, peak RSS는 async 쓰기와 같은 범위에 머물렀습니다.

  • 스트림 기반: 메모리 효율성을 위해 WritableStream에 직접 작성
  • 비동기: 논블로킹 작업을 위해 모든 메서드가 promise 반환
  • 대용량 문서 지원: 비동기 스트림 아키텍처가 필요한 경우 임의 크기의 XML 문서 처리
  • 메모리 효율적: 전체 XML을 메모리에 저장할 필요 없음
  • 실시간 생성: 스트리밍 API와 라이브 데이터에 완벽

참고: 작은 동기식 출력에는 문자열 builder인 WriterSync를 사용하고, 대용량 파일 출력에는 WriterSyncSink를 사용하세요.

import { Hono } from 'hono';
import { stream } from 'hono/streaming';
import { Writer } from 'stax-xml';
const app = new Hono();
app.get('/api/products', (c) => {
// 샘플 제품 데이터
const products = [
{ id: 'P001', name: 'Smartphone', price: 699.99, category: 'Electronics' },
{ id: 'P002', name: 'Headphones', price: 199.99, category: 'Electronics' },
{ id: 'P003', name: 'Coffee Maker', price: 149.99, category: 'Appliances' }
];
// 적절한 헤더 설정
c.header('Content-Type', 'application/xml; charset=utf-8');
c.header('Cache-Control', 'no-cache');
return stream(c, async (stream) => {
const writer = new Writer(stream, {
prettyPrint: true,
indentString: ' '
});
try {
// 스트림에 직접 XML 생성 - 모든 메서드에 await 필요
await writer.writeStartDocument('1.0', 'utf-8');
await writer.writeStartElement('products', {
attributes: {
count: products.length.toString(),
generated: new Date().toISOString()
}
});
for (const product of products) {
await writer.writeStartElement('product', {
attributes: {
id: product.id,
category: product.category
}
});
await writer.writeStartElement('name');
await writer.writeCharacters(product.name);
await writer.writeEndElement();
await writer.writeStartElement('price', { attributes: { currency: 'USD' } });
await writer.writeCharacters(product.price.toString());
await writer.writeEndElement();
await writer.writeEndElement(); // product
}
await writer.writeEndElement(); // products
await writer.writeEndDocument();
await writer.close(); // 중요: 스트림 닫기
} catch (error) {
console.error('XML 생성 오류:', error);
// 참고: 스트리밍 컨텍스트에서 오류 처리는 제한적입니다
}
});
});
export default app;
네임스페이스와 사용자 정의 엔터티를 포함한 고급 기능
섹션 제목: “네임스페이스와 사용자 정의 엔터티를 포함한 고급 기능”
import { Hono } from 'hono';
import { stream } from 'hono/streaming';
import { Writer } from 'stax-xml';
const app = new Hono();
app.get('/api/catalog', (c) => {
const items = [
{ id: '001', name: 'Premium Laptop', featured: true },
{ id: '002', name: 'Wireless Mouse', featured: false }
];
c.header('Content-Type', 'application/xml; charset=utf-8');
return stream(c, async (stream) => {
const writer = new Writer(stream, {
prettyPrint: true,
indentString: ' ',
addEntities: [
{ entity: 'company', value: 'Acme Corporation' },
{ entity: 'copyright', value: '© 2024' }
],
autoEncodeEntities: true
});
try {
// 네임스페이스와 사용자 정의 엔터티로 XML 작성
await writer.writeStartDocument('1.0', 'utf-8');
await writer.writeStartElement('catalog', {
prefix: 'cat',
uri: 'http://example.com/catalog',
attributes: { version: '2.0' }
});
await writer.writeNamespace('meta', 'http://example.com/metadata');
await writer.writeStartElement('header', { prefix: 'meta' });
await writer.writeStartElement('title');
await writer.writeCharacters('Product Catalog');
await writer.writeEndElement();
await writer.writeStartElement('company');
await writer.writeCharacters('&company;'); // 자동으로 인코딩됩니다
await writer.writeEndElement();
await writer.writeEndElement(); // header
await writer.writeStartElement('products');
for (const item of items) {
await writer.writeStartElement('product', {
attributes: { id: item.id, featured: item.featured.toString() }
});
await writer.writeStartElement('name');
await writer.writeCharacters(item.name);
await writer.writeEndElement();
// 자체 닫는 엘리먼트
await writer.writeStartElement('thumbnail', {
attributes: {
src: `${item.id}.jpg`,
alt: 'Product Image'
},
selfClosing: true
});
await writer.writeStartElement('description');
await writer.writeCData('<p>This is <b>HTML</b> content in CDATA</p>');
await writer.writeEndElement();
await writer.writeEndElement(); // product
}
await writer.writeEndElement(); // products
await writer.writeEndElement(); // catalog
await writer.writeEndDocument();
await writer.close();
} catch (error) {
console.error('XML 생성 오류:', error);
}
});
});
export default app;

Writer는 엘리먼트 생성을 단순화하는 통합 API를 지원합니다:

// 속성이 있는 자체 닫는 엘리먼트
await writer.writeStartElement('img', {
attributes: {
src: 'image.jpg',
alt: 'Image'
},
selfClosing: true // writeEndElement() 호출 불필요
});
// 네임스페이스가 있는 엘리먼트
await writer.writeStartElement('title', {
prefix: 'html',
uri: 'http://www.w3.org/1999/xhtml',
attributes: { lang: 'ko' }
});
class Writer {
// 생성자 - WritableStream에 직접 XML 스트리밍용
constructor(stream: WritableStream<Uint8Array>, options?: WriterOptions)
// 문서 레벨 메서드
writeStartDocument(version?: string, encoding?: string): Promise<this>
writeEndDocument(): Promise<void>
// 엘리먼트 작성 메서드
writeStartElement(localName: string, options?: WriteElementOptions): Promise<this>
writeEndElement(): Promise<this>
// 콘텐츠 작성 메서드
writeCharacters(text: string): Promise<this>
writeCData(cdata: string): Promise<this>
writeComment(comment: string): Promise<this>
// 스트림 관리
close(): Promise<void> // 기본 스트림 닫기
flush(): Promise<void> // 수동 플러시
getMetrics(): object // 성능 메트릭
}
interface WriterOptions {
encoding?: string; // 기본값: 'utf-8'
prettyPrint?: boolean; // 기본값: false
indentString?: string; // 기본값: ' '
addEntities?: { entity: string, value: string }[];
autoEncodeEntities?: boolean; // 기본값: true
namespaces?: NamespaceDeclaration[];
bufferSize?: number; // 기본값: 16384
highWaterMark?: number; // 기본값: 65536
flushThreshold?: number; // 기본값: 0.8
enableAutoFlush?: boolean; // 기본값: true
}
interface WriteElementOptions {
prefix?: string;
uri?: string;
attributes?: Record<string, string | AttributeInfo>;
selfClosing?: boolean;
}
interface AttributeInfo {
value: string;
localName: string;
prefix?: string;
uri?: string;
}
interface NamespaceDeclaration {
prefix?: string;
uri: string;
}

🚀 Writer를 언제 사용해야 할까요?

섹션 제목: “🚀 Writer를 언제 사용해야 할까요?”

Writer를 사용하는 경우:

  • 클라이언트에 실시간 XML 스트리밍
  • 메모리 효율성이 중요한 경우
  • 비동기/스트리밍 아키텍처 작업
  • 메모리에 맞지 않는 데이터 처리
  • 응답을 스트리밍해야 하는 API 구축
  • 라이브 데이터 소스에서 실시간 XML 생성

대용량 파일 생성에는 WriterSyncSink를 권장합니다. 소규모 문서나 동기식 작업에는 XML을 메모리에서 문자열로 구성하는 WriterSync를 사용하세요.