Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@
## 2025-01-24 - 단일 readAttributes 호출로 파일 속성 조회 최적화
**학습:** `isDirectory`, `!it.isDirectory()`, `isSymbolicLink` 3개의 개별적인 파일 시스템 I/O 호출을 수행하면 성능 저하가 큽니다. 이를 단일 `Files.readAttributes` 호출로 변경하여 메타데이터를 한 번에 조회함으로써 I/O 오버헤드를 대폭 줄일 수 있음을 확인했습니다.
**조치:** 디렉토리 순회 시 파일의 여러 속성을 확인할 때는 개별적인 stat 호출보다 `Files.readAttributes`를 사용하여 필요한 모든 속성을 한 번에 가져오는 방식을 우선적으로 고려해야 합니다.
## 2024-07-14 - 정적 리소스 해싱 최적화
**학습:** `process_dir`과 같이 재귀적으로 호출되는 함수 내부에서 정적 리소스 연산을 분리하는 것은 매우 중요합니다. 기존 코드는 크롤링 시 방문하는 모든 디렉토리마다 정적 CSS 내용에 대해 SHA-256 해시를 매번 다시 계산하여 불필요한 O(N) 암호화 연산 부하를 유발했습니다. Kotlin에서 파일 최상위 상수로 선언하면 JaCoCo가 암묵적 getter 메서드를 생성하여 커버리지가 떨어질 수 있는데, 이를 `private object` 내부의 `const val` 및 `@JvmField`로 캡슐화하면 성능 향상과 더불어 100% 테스트 커버리지를 함께 유지할 수 있음을 확인했습니다.
**조치:** 무거운 암호화 해시 연산이나 정적 CSS 내용과 같은 불변 데이터는 항상 함수 외부(예: private object)로 분리하여 한 번만 계산되도록 캐싱해야 하며, 리팩토링 후에는 반드시 JaCoCo 커버리지 손실이 없는지 확인합니다.
21 changes: 13 additions & 8 deletions src/main/kotlin/html4tree/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -240,11 +240,10 @@ fun write_index_file(curr_dir: File, content: String) {
}
}

fun process_dir(curr_dir: File, excludeSet: Set<String>? = null, dirFiles: Array<File>? = null){

val exclude: Set<String> = excludeSet ?: process_ignore_file(curr_dir)

val cssContent = """
// ⚡ Bolt Performance Optimization: Extract static CSS and hash calculation to file level to avoid recalculation per directory
private object StaticCss {
const val cssContent = """
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
line-height: 1.5;
Expand Down Expand Up @@ -310,12 +309,18 @@ fun process_dir(curr_dir: File, excludeSet: Set<String>? = null, dirFiles: Array
}
"""

val styleHash = "sha256-" + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-256").digest(cssContent.toByteArray(Charsets.UTF_8)))
@JvmField val styleHash = "sha256-" + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-256").digest(cssContent.toByteArray(Charsets.UTF_8)))

val css = """
const val css = """
<style>
${cssContent} </style>
"""
}

fun process_dir(curr_dir: File, excludeSet: Set<String>? = null, dirFiles: Array<File>? = null){

val exclude: Set<String> = excludeSet ?: process_ignore_file(curr_dir)


val index_top = """<!doctype html>
<html lang="ko">
Expand All @@ -324,11 +329,11 @@ ${cssContent} </style>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<!-- 보안 향상: 인라인 스크립트 실행 방지 -->
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src '${styleHash}'; base-uri 'none'; form-action 'none';">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src '${StaticCss.styleHash}'; base-uri 'none'; form-action 'none';">
<!-- 보안 향상: 리퍼러를 통한 디렉토리 경로 노출 방지 -->
<meta name="referrer" content="no-referrer">
<title>${curr_dir.getName().escapeHtml()}</title>
${css}
${StaticCss.css}
</head>
<body>
<main>
Expand Down
Loading