본문으로 건너뛰기

wwwroot

wwwroot는 HandStack 화면과 정적 리소스를 제공하는 모듈입니다. 계약 기반 화면 리소스는 ContractRequestPath 아래에 노출하고, 실제 정적 자산은 WWWRootBasePath에서 서비스하며, 공통 파일 카탈로그에 등록한 호스트 파일도 지정 URL로 제공합니다.

책임 범위

  • contracts/wwwroot의 애플리케이션별 화면 리소스를 기본 /view 경로로 노출합니다.
  • modules/wwwroot/wwwroot 아래의 실제 정적 파일을 서비스합니다.
  • shared_files.json 카탈로그의 요청 경로를 기존 정적 파일 루트 밖의 호스트 파일에 연결합니다.
  • 정적 파일 요청에 대해 대소문자 무시 탐색과 캐시 정책을 적용합니다.
  • Basic token 기반 파일 동기화 API를 제공합니다.
  • 계약 파일 업로드 후 대상 모듈의 refresh API를 호출해 런타임 계약 캐시를 갱신합니다.

주요 API

메서드경로용도
POST/wwwroot/api/sync/upload계약 또는 정적 파일을 업로드 동기화합니다.
GET/wwwroot/api/sync/refresh대상 모듈 refresh API를 호출합니다.
GET/wwwroot/api/htmx/*HTMX 샘플/보조 API입니다.
GET/wwwroot/api/index/*화면 보조 API와 거래 직접 호출 래퍼입니다.
GET/shared-files/manifest공통 파일의 공개 요청 경로 목록을 반환합니다.

핵심 구현

  • ModuleInitializer.cs: 정적 파일 provider, 계약 리소스 경로, CORS/필터 처리를 구성합니다.
  • Extensions/SharedFileServingMiddleware.cs: 공통 파일 요청 경로를 호스트 파일에 연결합니다.
  • Entity/SharedFileCatalog.cs: items[].requestPath/hostFilePath 카탈로그 스키마입니다.
  • wwwroot/js/syn.loader.js: manifest의 공통 CSS/JS와 기타 텍스트 자산을 브라우저 로딩 과정에 추가합니다.
  • Areas/wwwroot/Controllers/SyncController.cs: 파일 업로드와 refresh 동기화 API입니다.
  • Areas/wwwroot/Controllers/IndexController.cs: 화면 보조 API와 transact 직접 호출을 제공합니다.
  • Extensions/ModuleApiClient.cs: 화면에서 업무 거래를 직접 호출하는 래퍼입니다.
  • Contracts/wwwroot: 계약 기반 화면/자산 매핑입니다.

주요 설정

설정설명
ContractRequestPath계약 기반 정적 리소스 요청 경로입니다. 기본값은 view입니다.
ContractBasePath계약 기반 화면 리소스 루트입니다.
WWWRootBasePath실제 정적 파일 루트입니다.
SharedFileConfigPath공통 파일 카탈로그 JSON 경로입니다. 빈 값이면 사용하지 않습니다.
FileSyncTokens/wwwroot/api/sync/* 호출을 허용할 Basic token 목록입니다.
BusinessServerUrl화면 보조 API가 거래를 실행할 transact URL입니다.
ModuleLogFilePathwwwroot 모듈 로그 파일 경로입니다.

공통 파일 카탈로그

module.json에서 카탈로그 경로를 지정합니다.

"SharedFileConfigPath": "../config/shared_files.json"

shared_files.json은 다음 형식입니다.

{
"items": [
{
"requestPath": "/assets/common.css",
"hostFilePath": "../shared-files/common.css"
},
{
"requestPath": "/assets/common.js",
"hostFilePath": "../shared-files/common.js"
},
{
"requestPath": "/assets/settings.json",
"hostFilePath": "../shared-files/settings.json"
},
{
"requestPath": "/assets/fragment.html",
"hostFilePath": "../shared-files/fragment.html"
}
]
}

예제 파일은 shared_files.json, common.css, common.js, settings.json/sample/wwwroot/shared-files/fragment.html에서 확인할 수 있습니다. 다음처럼 배치하면 위 상대경로가 일치합니다.

HANDSTACK_HOME/
├─ app/
│ └─ ack
├─ config/
│ └─ shared_files.json
└─ shared-files/
├─ common.css
├─ common.js
├─ settings.json
└─ fragment.html
  • SharedFileConfigPathhostFilePath의 상대경로는 모두 카탈로그 디렉터리가 아니라 ack 실행 기준 경로인 GlobalConfiguration.EntryBasePath에서 해석됩니다. 환경 변수와 절대경로도 사용할 수 있습니다.
  • requestPathHttpContext.Request.Path와 대소문자를 무시하고 정확히 비교하므로 /로 시작해야 합니다. 쿼리 문자열은 비교 대상이 아닙니다.
  • requestPath 또는 hostFilePath가 비어 있는 항목은 적재하지 않습니다.
  • 중복 요청 경로는 첫 번째 항목이 우선하며, 대상 파일이 없으면 다음 미들웨어로 넘어갑니다.
  • 파일 확장자로 MIME 형식을 판단하고 알 수 없는 형식은 application/octet-stream으로 응답합니다.
  • 카탈로그는 모듈 초기화 때 한 번 적재되므로 파일을 수정한 뒤 ack를 다시 시작해야 합니다.

GET /shared-files/manifesthostFilePath를 제외한 다음 형식의 요청 경로 목록을 반환합니다.

[
{ "requestPath": "/assets/common.css" },
{ "requestPath": "/assets/common.js" },
{ "requestPath": "/assets/settings.json" },
{ "requestPath": "/assets/fragment.html" }
]

syn.loader.js는 화면 시작 시 manifest를 no-cache로 조회합니다.

확장자브라우저 처리
.css기존 스타일 로드 목록에 추가합니다.
.js기존 스크립트 로드 목록에 추가합니다.
.html, .htmtype="text/html" 텍스트 자산으로 <head>에 추가합니다.
.jsontype="application/json" 텍스트 자산으로 <head>에 추가합니다.
.xmltype="application/xml" 텍스트 자산으로 <head>에 추가합니다.
.mdtype="text/markdown" 텍스트 자산으로 <head>에 추가합니다.
.txttype="text/plain" 텍스트 자산으로 <head>에 추가합니다.
.csvtype="text/csv" 텍스트 자산으로 <head>에 추가합니다.
그 외type="text/plain" 텍스트 자산으로 <head>에 추가합니다.

텍스트 자산 ID는 마지막 확장자를 제거한 파일명에서 만들고 영문·숫자·밑줄 이외의 문자는 _로 바꿉니다. 다음과 같이 JSON이나 HTML 내용을 읽을 수 있습니다.

const settings = JSON.parse(document.getElementById('shared_settings').textContent);
const fragment = document.getElementById('shared_fragment').innerHTML;

CSS/JS 분기 비교는 대소문자를 구분하므로 파일 확장자는 소문자로 작성합니다. 서로 다른 경로라도 확장자를 제외한 파일명이 같으면 DOM ID가 중복될 수 있으므로 고유한 파일명을 사용합니다. manifest 또는 텍스트 파일 로딩 예외는 로더 로그에 남고 나머지 화면 초기화는 계속됩니다.

공통 파일 미들웨어와 manifest는 세션·인증 미들웨어보다 먼저 실행하고 자체 인증·권한 검사를 하지 않습니다. manifest가 요청 경로 목록도 공개하므로 카탈로그와 대상 파일은 공개 가능한 자산만 등록하고 카탈로그 쓰기 권한을 제한해야 합니다.

동기화 대상

SyncController는 계약 동기화 시 허용된 모듈만 처리합니다. 현재 소스 기준으로 dbclient, graphclient, transact, function, wwwroot 계약을 대상으로 합니다.

파일 업로드 후 refresh가 필요한 경우 대상 모듈의 refresh API가 호출됩니다. 예를 들어 DB 계약을 업로드하면 dbclient의 query 계약 캐시를 갱신해야 합니다.

실행 흐름

  1. 모듈 초기화 시 SharedFileConfigPath의 카탈로그를 읽어 공통 파일 목록을 메모리에 적재합니다.
  2. 공통 파일 미들웨어가 manifest를 제공하고 정확히 일치하는 요청 경로를 호스트 파일에서 먼저 찾습니다.
  3. 브라우저의 syn.loader.js가 manifest의 CSS/JS와 기타 텍스트 자산을 화면 로딩 과정에 추가합니다.
  4. ContractBasePath/{ApplicationID}ContractRequestPath 아래 정적 파일 provider로 등록합니다.
  5. WWWRootBasePath는 실제 정적 파일 루트로 등록됩니다.
  6. 화면 요청은 공통 파일, 계약 경로 또는 실제 정적 경로에서 파일을 찾습니다.
  7. 동기화 API는 Basic token을 검증한 뒤 파일을 저장하고 대상 모듈 refresh를 수행합니다.

운영 주의사항

  • FileSyncTokens가 비어 있으면 동기화 요청은 거부됩니다.
  • SharedFileConfigPath가 비어 있으면 기능을 사용하지 않습니다. 파일 누락 또는 JSON 오류는 경고 로그를 남기고 빈 목록으로 처리합니다.
  • manifest는 대상 파일의 존재 여부와 관계없이 적재된 요청 경로를 반환합니다. 대상 파일이 없으면 실제 요청은 다음 미들웨어로 넘어갑니다.
  • 카탈로그 항목 변경은 자동으로 다시 읽지 않으므로 ack 재시작이 필요합니다. 이미 등록된 대상 파일의 내용 변경은 다음 요청부터 반영됩니다.
  • ContractRequestPathWWWRootBasePath는 요청 경로가 충돌하지 않게 분리합니다.
  • 정적 파일 동기화는 운영 화면에 직접 영향을 주므로 배포 권한과 감사 로그를 함께 관리합니다.
  • 화면에서 직접 거래를 호출하는 경우 BusinessServerUrl, 인증 토큰, 공개 거래 범위를 transact 설정과 함께 점검합니다.