Processor
Processor는 HTML 라이브러리의 핵심 처리 엔진입니다. 패키지 함수에 비해 Processor 는 내부 리소스(캐시, 인코딩 감지기)를 재사용하여 고빈도 호출 시나리오에 적합합니다.
생성
New
Processor 인스턴스를 생성하며, 선택적으로 설정을 전달할 수 있습니다.
func New(cfg ...Config) (*Processor, error)매개변수: 최대 1 개의 Config, 미제공 시 DefaultConfig()를 사용합니다.
p, err := html.New(html.DefaultConfig())
if err != nil {
log.Fatal(err)
}
defer p.Close()내부 초기화:
New는 단순한 대입이 아니라, 다음 단계를 수행하여 반환된 Processor 가 즉시 사용 가능하도록 보장합니다:
- 설정 검증:
Config.Validate()를 호출하며, 유효하지 않은 설정은*ConfigError를 반환합니다 (errors.Is(err, ErrInvalidConfig)가 참). 검증 범위에는 수치 경계(MaxInputSize,MaxCacheEntries,WorkerPoolSize,MaxDepth가 음수이거나 한도 초과일 수 없음)와 포맷 문자열(InlineImageFormat/InlineLinkFormat/TableFormat값이 합법적)이 포함됩니다. - Scorer 설정: 커스텀
Scorer가 설정된 경우scorerAdapter로 내부 인터페이스에 적용하고; 그렇지 않으면SharedDefaultScorer(읽기 전용, 동시성 안전)를 사용합니다. - 포맷 문자열 사전 계산:
InlineImageFormat/InlineLinkFormat을 정규화하여(소문자+공백 제거, 빈 문자열은"none"으로 매핑)imageFormat/linkFormat필드에 캐시하여, 핫 경로에서 반복적인strings.ToLower를 피합니다. - 캐시 정리 시작:
CacheTTL>0이고CacheCleanup>0일 때만 백그라운드 정리 goroutine 을 시작합니다; 둘 중 하나라도 0이면 시작하지 않습니다.
동시성 안전성
동시성 사용
Processor는 여러 goroutine 간에 안전하게 공유할 수 있으며, 추가 락이 필요 없습니다. 동시성 보장은 다음에서 비롯됩니다:
- 불변 설정:
config는New()이후 불변입니다(*Config포인터는 재할당되거나 수정되지 않음). 따라서ExtractToMarkdown등의 포맷 메서드는 락 없이도 안전하게 값 복사를 수행하여 임시 Processor 를 생성할 수 있습니다 — 포맷 덮어쓰기가 공유 설정에 다시 기록되지 않습니다. - 통계 카운터:
TotalProcessed/CacheHits/CacheMisses/ErrorCount/totalProcessTime은 모두atomic연산을 사용합니다. - 캐시: 내부
Cache는 자체 락을 가지며 읽기/쓰기가 안전합니다. - Scorer: 내장
DefaultScorer는 읽기 전용입니다. 커스텀Scorer는 스스로 동시성 안전을 보장해야 합니다 (예: 내부적으로 락 유지), 단일 Processor 가 동시성Extract시 여러 goroutine 에서 해당Score/ShouldRemove를 호출하기 때문입니다.
콘텐츠 추출
오류 반환
Extract 메서드 계열은 처리의 각 단계에서 명확한 센티넬 오류를 반환하며, errors.Is로 정확하게 판별할 수 있습니다:
| 오류 | 트리거 조건 | 비고 |
|---|---|---|
ErrProcessorClosed | p가 nil이거나 이미 Close됨 | 모든 메서드 공용 |
ErrInputTooLarge | 입력 바이트 수가 MaxInputSize 초과 | *InputError로 래핑, 실제/제한 크기 포함 |
| 인코딩 감지 오류 | 인코딩 감지 또는 UTF-8 변환 실패 | 원본 오류가 래핑됨 |
ErrInvalidHTML | 바이트를 HTML 로 파싱할 수 없음 | 하위 파싱 오류도 함께 래핑 |
ErrMaxDepthExceeded | 요소 중첩 깊이가 MaxDepth 초과 | 반복식 검증, 스택 오버플로 방지 |
ErrProcessingTimeout | 처리 소요가 ProcessingTimeout 초과 | ProcessingTimeout=0은 무제한 의미 |
ErrInternalPanic | 내부 예기치 않은 panic 이 복구됨 | 안전망 보호, 정상 사용에서는 나타나지 않아야 함 |
context가 포함된 버전은 추가로 context.Canceled(사용자 취소) 또는 context.DeadlineExceeded(컨텍스트 타임아웃, ErrProcessingTimeout으로 정규화됨)를 반환합니다.
Extract
func (p *Processor) Extract(htmlBytes []byte) (*Result, error)HTML 바이트에서 콘텐츠를 추출하고 인코딩을 자동으로 감지합니다.
ExtractFromFile
func (p *Processor) ExtractFromFile(filePath string) (*Result, error)파일에서 콘텐츠를 추출합니다.
ExtractText
func (p *Processor) ExtractText(htmlBytes []byte) (string, error)순수 텍스트만 반환합니다.
ExtractTextFromFile
func (p *Processor) ExtractTextFromFile(filePath string) (string, error)파일에서 순수 텍스트를 추출합니다.
컨텍스트 버전
모든 추출 메서드에는 ExtractWithContext가 포함된 버전이 있습니다:
func (p *Processor) ExtractWithContext(ctx context.Context, htmlBytes []byte) (*Result, error)
func (p *Processor) ExtractFromFileWithContext(ctx context.Context, filePath string) (*Result, error)
func (p *Processor) ExtractTextWithContext(ctx context.Context, htmlBytes []byte) (string, error)
func (p *Processor) ExtractTextFromFileWithContext(ctx context.Context, filePath string) (string, error)출력 형식
func (p *Processor) ExtractToMarkdown(htmlBytes []byte) (string, error)
func (p *Processor) ExtractToMarkdownFromFile(filePath string) (string, error)
func (p *Processor) ExtractToJSON(htmlBytes []byte) ([]byte, error)
func (p *Processor) ExtractToJSONFromFile(filePath string) ([]byte, error)컨텍스트 버전:
func (p *Processor) ExtractToMarkdownWithContext(ctx context.Context, htmlBytes []byte) (string, error)
func (p *Processor) ExtractToMarkdownFromFileWithContext(ctx context.Context, filePath string) (string, error)
func (p *Processor) ExtractToJSONWithContext(ctx context.Context, htmlBytes []byte) ([]byte, error)
func (p *Processor) ExtractToJSONFromFileWithContext(ctx context.Context, filePath string) ([]byte, error)캐시 동작 차이
두 메서드는 캐시 처리에서 완전히 다릅니다:
ExtractToMarkdown은 임시 Processor 를 구성합니다 (불변의config를 복사하지만,MaxCacheEntries는 0, 감사 비활성화), 주 캐시를 읽거나 쓰지 않으므로 주 Processor 의 캐시를 오염시키거나 적중하지 않습니다. Markdown 포맷 결과도 캐시되지 않습니다.ExtractToJSON은p.Extract를 직접 호출하며, 정상 캐시 경로를 거칩니다 — 주 캐시에 적중/기록하며, 통계 카운터도 그에 따라 갱신됩니다.
Markdown 출력도 캐시의 혜택을 받으려면, MarkdownConfig()로 전용 Processor 를 생성하고 Extract를 호출하거나, 출력을 직접 캐시하세요.
링크 추출
func (p *Processor) ExtractAllLinks(htmlBytes []byte) ([]LinkResource, error)
func (p *Processor) ExtractAllLinksFromFile(filePath string) ([]LinkResource, error)
func (p *Processor) ExtractAllLinksWithContext(ctx context.Context, htmlBytes []byte) ([]LinkResource, error)
func (p *Processor) ExtractAllLinksFromFileWithContext(ctx context.Context, filePath string) ([]LinkResource, error)배치 처리
func (p *Processor) ExtractBatch(htmlContents [][]byte) *BatchResult
func (p *Processor) ExtractBatchWithContext(ctx context.Context, htmlContents [][]byte) *BatchResult
func (p *Processor) ExtractBatchFiles(filePaths []string) *BatchResult
func (p *Processor) ExtractBatchFilesWithContext(ctx context.Context, filePaths []string) *BatchResult통계와 캐시
캐시 동작 상세
MaxCacheEntries > 0일 때, Extract는 캐시를 활성화합니다:
- 적중 경로: 캐시 항목 감지 후,
CacheHits와TotalProcessed를 각각 1 씩 증가시키며, 반환되는 것은cloneResult입니다 —Images/Links/Videos/Audios등 슬라이스에 대해copy를 수행한 딥카피입니다. 호출자가 반환값을 수정해도 캐시의 항목에 영향을 주지 않으며, 동시성 적중 읽기 시의 데이터 경쟁도 방지합니다. - 미적중 경로: 처리 완료 후 결과를 캐시에 기록하고,
cloneResult를 반환합니다 (마찬가지로 딥카피). 따라서 캐시 항목과 반환값은 서로 별칭하지 않습니다. - 캐시 비활성화:
MaxCacheEntries = 0일 때,Extract는 캐시 키 생성과Get/Set을 건너뛰며 (단락), 캐시 오버헤드가 전혀 없습니다.
GetStatistics
현재 처리 통계 정보를 반환합니다.
func (p *Processor) GetStatistics() StatisticsStatistics 각 필드의 의미:
| 필드 | 설명 |
|---|---|
TotalProcessed | 오류 없이 완료된 추출 횟수, 캐시 적중 포함 |
CacheHits | 캐시에서 직접 적중한 횟수 |
CacheMisses | 미적중하여 전체 처리가 필요했던 횟수 |
ErrorCount | 오류를 반환한 추출 횟수 |
AverageProcessTime | 추출당 평균 경과 시간(TotalProcessed가 0 이면 0) |
stats := p.GetStatistics()
fmt.Printf("처리됨: %d, 캐시 적중: %d\n",
stats.TotalProcessed, stats.CacheHits)ClearCache
캐시를 비우고 누적 통계는 유지합니다.
func (p *Processor) ClearCache()ResetStatistics
모든 통계 카운터를 재설정합니다.
func (p *Processor) ResetStatistics()감사
GetAuditLog
감사 로그 항목을 가져옵니다.
func (p *Processor) GetAuditLog() []AuditEntryClearAuditLog
감사 로그를 비웁니다.
func (p *Processor) ClearAuditLog()라이프사이클
Close
Processor 가 보유한 리소스를 해제합니다. 사용 완료 후 반드시 호출해야 합니다.
func (p *Processor) Close() errorp, _ := html.New(cfg)
defer p.Close()
// ... p 를 사용하여 추출라이프사이클 모범 사례
- 싱글톤 재사용: 장기 실행 서비스(HTTP handler, worker)에서 Processor 를 하나 생성하고 동시성 요청 간에 공유하며, 캐시와 함께 수익을 극대화합니다. Processor 자체는 동시성 안전하므로 요청마다 새로 만들 필요가 없습니다.
defer Close(): 생성 직후defer p.Close()를 호출하여, 예외 경로에서도 백그라운드 정리 goroutine 과 감사 리소스가 해제되도록 합니다.Close는 캐시 정리 goroutine 을 중지하고, 캐시를 비우며, 감사 sink 를 닫습니다.- Close 후 사용 금지:
Close이후 어떤 메서드를 호출해도ErrProcessorClosed를 반환합니다.Close는CompareAndSwap으로 멱등성을 보장하며, 중복 호출은 안전하지만 의미는 없습니다.