회원가입 | 고객센터 |
DESIGNONEX
dxcms.kr
로그인 회원가입
고객센터
3.4 라우팅 시스템

URL 처리 방식 • 라우팅 규칙 • 동적 라이팅

D DX
2026.04.21 00:51(수정됨) 134 0

1. 개요 — 라이팅 시스템의 구성 원리

DX 미니 프레임워크의 라이팅 시스템(Routing System)은 모든 HTTP 요청을 단일 진입점(index.php)으로 집결시킨 뒤, 내장 Router • Dispatcher • DxRouter 세 계층이 협력하여 요청을 처리하는 구조입니다. PHP 5.6부터 8.x까지, 그리고 Apache • Nginx • IIS • 저가형 웹호스팅까지 단일 코드베이스로 완전 호환됩니다.


핵심 설계 목표:

  • 단일 진입점: 루트의 index.php 만 직접 실행 가능, 나머지 PHP 파일 직접 접근 차단
  • 이중 라우터 체계: 파일 기반 Router(기존) + 클래스 기반 DxRouter(v6.2.0 신규) 병행 운용
  • 서버 독립성: URL Rewrite 모듈 없는 환경에서도 ?_url= 쿼리 파라미터로 자동 폴백
  • 멀티사이트 대응: site_domain 컬럼 기반으로 도메인별 게시판•페이지•테마 분리
 
요청 흐름 개요

HTTP 요청
  └─ .htaccess / nginx.conf / web.config
       └─ index.php (단일 진입점)
            ├─ [STEP 1] 클래스•함수 로드
            ├─ [STEP 2] 보안 초기화 (Secure.php)
            ├─ [STEP 3] DB 연결 + 설정 로드
            ├─ [STEP 4] 플러그인•테마•인증 초기화
            └─ [STEP 5] 라우팅 + 디스패치
                 ├─ DxRouter::dispatch()  ← routes/*.php 에 등록된 라우트 우선
                 └─ Dispatcher(Router)    ← 파일 기반 라우팅 폴백


2. URL 처리 방식


2.1 단일 진입점(index.php) 구조

루트 디렉토리에 위치한 index.php는 DX 프레임워크의 유일한 공개 PHP 파일입니다. .htaccess(Apache) • nginx.conf • web.config(IIS)에서 정적 파일과 install/ 폴더를 제외한 모든 요청을 이 파일로 전달합니다. index.php 자체도 .htaccess의 <Files> 지시문으로만 접근이 허용되며, 나머지 모든 .php 파일은 외부 직접 접근이 차단됩니다.

보안 설계: boards/, controllers/, core/ 등 하위 디렉토리의 PHP 파일은 각 폴더마다 .htaccess로 직접 접근이 차단됩니다.
오직 index.php를 통해서만 모든 기능이 실행됩니다.


2.2 URL Rewrite 동작 방식

각 웹서버별로 URL Rewrite 규칙이 다음과 같이 정의되어 있으며, 모두 동일한 결과(index.php로 전달)를 만들어냅니다.

Apache (.htaccess)
RewriteEngine On
RewriteBase /

# Authorization 헤더 PHP로 전달 (Apache+mod_rewrite 환경 버그 보완)
RewriteCond %{HTTP:Authorization} .
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

# 정적 파일•폴더는 그대로 서빙
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

# sitemap.xml, robots.txt 포함 나머지 전부 → index.php
RewriteRule ^ index.php [QSA,L]

Nginx (nginx.conf.example)
# try_files에서 $uri/(디렉토리 체크)를 제거하는 것이 핵심
# admin/, boards/ 등 실제 폴더가 있어도 index.php로 넘겨야 함
location / {
    try_files $uri /index.php?$query_string;
}

# 루트 index.php만 FastCGI 실행 허용
location = /index.php {
    fastcgi_param SCRIPT_FILENAME $document_root/index.php;
}

# 그 외 .php 파일 직접 실행 차단
location ~ \.php$ { deny all; }

IIS (web.config)
IIS는 URL Rewrite 2.x 모듈이 설치된 경우 web.config의 <rewrite> 블록이 활성화됩니다. 미설치 시에는 해당 블록을 제거하거나 주석 처리하고, ?_url= 폴백 방식을 사용합니다.


2.3 URL Rewrite 미지원 환경 폴백

저가형 웹호스팅 등 URL Rewrite 모듈이 없는 환경을 위해 쿼리 파라미터 방식의 폴백이 구현되어 있습니다. 시스템 설정(url_rewrite=0)이거나 $_GET['_url'] 파라미터가 존재하면 이 방식이 활성화됩니다.
 
구분 URL Rewrite 사용 시 URL Rewrite 미사용 시
게시판 목록 /notice /index.php?_url=/notice
게시물 보기 /notice/view/123 /index.php?_url=/notice/view/123
관리자 /admin/dashboard /index.php?_url=/admin/dashboard
인증 /auth/login /index.php?_url=/auth/login

폴백 URL 생성은 dx_base_url() 함수가 자동으로 처리합니다:
// dx_base_url() — URL Rewrite 여부에 따라 자동 분기
if (dx_config('url_rewrite', '1') === '0') {
    return $base . '/index.php?_url=/' . $path;
}
return $base . '/' . $path;


2.4 URI 정규화 및 세그먼트 파싱

Router::getUri()는 다양한 서버 환경에서 일관된 URI 문자열을 추출하고, Router::parseSegments()는 이를 배열로 분해합니다. 이 과정에서 XSS•경로 순회 공격을 방어합니다.

URI 추출 우선순위
  1. $_GET['_url'] — URL Rewrite 미지원 IIS 환경 폴백 파라미터
  2. $_SERVER['IIS_WasUrlRewritten'] + UNENCODED_URL — IIS URL Rewrite 2.x 환경
  3. dx_request_uri() (= REQUEST_URI) — Apache • Nginx 표준 환경
  4. HTTP_X_REWRITE_URL — ISAPI Rewrite 호환
  5. ORIG_PATH_INFO / PATH_INFO — 구형 호스팅 환경

세그먼트 보안 처리
첫 번째 세그먼트는 sitemap*.xml 또는 robots.txt 형태의 점(.)을 포함한 이름을 허용합니다. 나머지 세그먼트는 영문자•숫자•하이픈•언더스코어만 남기고 나머지 문자를 모두 제거하여 XSS와 경로 순회 공격을 방어합니다.
 
// 세그먼트 정규화 (XSS•경로 순회 방지)
$seg = preg_replace('/[^a-zA-Z0-9\-_]/', '', $seg);

// 이중 슬래시 정규화 (301 리다이렉트)
if (strpos($_dxRawPath, '//') !== false) {
    header('Location: ' . preg_replace('#/+#', '/', $_dxRawPath));
    exit;
}


3. 라우팅 규칙 — Router 클래스

Router 클래스는 core/router/Router.php에 위치하며, URI 세그먼트를 분석하여 요청의 타입과 관련 메타데이터를 결정합니다. 반환된 라우트 배열은 Dispatcher가 받아 실제 처리를 수행합니다.


3.1 라우팅 타입 상수

상수 설명
TYPE_HOME home 루트 경로 / — 홈 페이지
TYPE_PAGE page DB에 등록된 일반 페이지
TYPE_BOARD board 게시판 (목록•조회•작성 등)
TYPE_ADMIN admin 관리자 페이지 (/admin/...)
TYPE_AUTH auth 인증 관련 (/auth/login 등)
TYPE_API api API 엔드포인트, sitemap, robots
TYPE_SEARCH search 통합 검색 (/search)
TYPE_404 404 매칭 실패 — 404 처리


3.2 라우팅 우선순위 및 처리 흐름

Router::parse() 메서드는 다음 순서로 URI를 매칭합니다. 위에서 일치하는 항목이 발견되면 즉시 반환되어 후속 검사를 건너뜁니다.

라우팅 우선순위 (높은 순 → 낮은 순)
[1] 빈 세그먼트         → TYPE_HOME (홈)
[2] /search             → TYPE_SEARCH (통합 검색)
[3] /admin/...          → TYPE_ADMIN (관리자)
[4] /auth/...           → TYPE_AUTH (인증)
[5] /sitemap*.xml       → TYPE_API (사이트맵)
[6] /robots.txt         → TYPE_API (로봇)
[7] /api/...            → TYPE_API (API)
[8] /{board_key}/{action}/{id}
    → DB에서 board_key 조회 성공 시 TYPE_BOARD
[9] /{board_key}        → DB에서 board_key 조회 성공 시 TYPE_BOARD (list 액션)
[10] {전체 경로 슬러그}  → DB에서 slug 조회 성공 시 TYPE_PAGE
[11] {첫 번째 세그먼트}  → DB에서 slug 조회 성공 시 TYPE_PAGE
[12] 매칭 실패           → TYPE_404

게시판 지원 액션 목록:
 
액션 HTTP 메서드 설명
list GET 게시물 목록 조회
view GET 게시물 상세 조회 (/{key}/view/{id})
write GET/POST 게시물 작성 폼 및 저장
edit GET/POST 게시물 수정 폼 및 저장
delete POST 게시물 삭제
reply GET/POST 답글 작성
search GET 게시판 내 검색
bulk POST 일괄 처리 (다중 삭제 등)


3.3 URL 생성 헬퍼

Router 클래스는 URL을 직접 조합하지 않고 헬퍼 메서드를 통해 일관된 URL을 생성하도록 유도합니다.
 
// 페이지 URL 생성
Router::pageUrl('about');         // → /about

// 게시판 URL 생성
Router::boardUrl('notice');         // → /notice (list)
Router::boardUrl('notice', 'view', 123);  // → /notice/view/123

// 관리자 URL 생성
Router::adminUrl('boards');         // → /admin/boards
Router::adminUrl('members', 'edit'); // → /admin/members/edit


3.4 멀티사이트(site_domain) 지원

v6.x 이후부터 boards 테이블과 pages 테이블에 site_domain 컬럼이 추가되었습니다. Router는 information_schema를 통해 컬럼 존재 여부를 자동 감지(캐싱)하며, 컬럼이 있으면 현재 접속 도메인과 비교하여 해당 도메인의 게시판•페이지만 반환합니다. 구버전에서 마이그레이션 전 환경은 컬럼 없이도 정상 동작합니다.
 
// 멀티사이트 게시판 조회 (site_domain 컬럼 자동 감지)
SELECT * FROM `dx_boards`
  WHERE board_key = ?
    AND status = 1
    AND (site_domain = '' OR site_domain = ?)
  LIMIT 1


4. 동적 라이팅 — Dispatcher 클래스

Dispatcher 클래스(core/router/Dispatcher.php)는 Router가 반환한 라우트 배열을 받아 해당 타입에 맞는 핸들러를 실행합니다. 또한 DxExtend의 extend/middle/ 훅을 라우트 확정 직후 실행하여 커스터마이징 포인트를 제공합니다.


4.1 홈 디스패치 (dispatchHome)

루트 경로 / 요청 시 다음 4단계 우선순위로 홈 콘텐츠를 결정합니다.
  1. 현재 테마의 page/home.php — 테마 폴더에 파일이 있으면 최우선 사용
  2. DB에서 현재 도메인의 is_home=1 페이지 — 멀티사이트 도메인 매칭
  3. DB에서 공통(site_domain='') is_home=1 페이지 — 전체 공통 홈
  4. pages/home.php — 최종 폴백 파일

💡 tip: 테마 전용 홈 페이지를 만들려면
   themes/{테마명}/page/home.php 파일을 생성하면
   DB 설정 없이 즉시 적용됩니다.


4.2 페이지 디스패치 (dispatchPage)

DB의 pages 테이블에 등록된 페이지를 처리합니다. page_location 값에 따라 파일 탐색 경로가 달라집니다.
 
page_location 파일 탐색 경로 설명
global pages/{file_path} 사이트 공통 페이지. DX_PAGES/ 기준 경로
theme themes/{테마}/page/{file_path} 테마 전용 페이지. site_domain 테마 우선 사용

is_standalone=1 페이지는 renderStandalone() 으로 처리되어 레이아웃(헤더•푸터) 없이 파일만 직접 실행됩니다. SPA • iframe 콘텐츠 • 외부 서비스 연동 페이지에 활용합니다.

접근 레벨 체계 (access_level):
  • 0 — 전체 공개 (비로그인 포함)
  • 1 — 로그인 회원만 접근 가능
  • 9 — 관리자만 접근 가능


4.3 게시판 디스패치 (dispatchBoard)

게시판 요청은 항상 boards/handler.php 를 통해 처리됩니다. handler.php 내부의 _brd_render() 함수가 DB 쿼리와 변수 준비를 마친 후, DxTheme 폴백 체인으로 스킨 파일을 렌더링합니다.
 
// 게시판 디스패치 흐름
$GLOBALS['dx_board']      = $board;    // 게시판 정보 전역 주입
$GLOBALS['dx_action']     = $action;   // list / view / write ...
$GLOBALS['dx_board_skin'] = $skin;     // 스킨명 (기본: default)
require boards/handler.php;

// handler.php 내부에서 _brd_render($skinAction, $ctx) 호출
// DxTheme::resolveBoardSkin($skin, $action) 으로 파일 해석

접근 레벨 체계 (read_level / write_level):
  • 0 — 전체 공개
  • 1 — 로그인 필요
  • 9 — 관리자 전용

⚠  BIGINT ID 처리: PHP 32비트 환경에서 (int) 캐스팅 시 오버플로우가 발생할 수 있어
   게시물 ID는 ctype_digit() 검증 후 문자열로 유지합니다.


4.4 관리자 디스패치 (dispatchAdmin)

Auth::isAdmin() 검사를 통과하지 못하면 /auth/login 으로 즉시 리다이렉트합니다. 통과 시 admin/index.php 를 로드하며, dx_admin_action 전역 변수로 서브 액션이 전달됩니다.


4.5 인증 디스패치 (dispatchAuth)

core/auth/{action}.php 파일을 로드합니다 (예: login.php, register.php, logout.php). 소셜 로그인 콜백(*_callback 접미사)은 기존 출력 버퍼를 모두 비운 후 독립 HTML 페이지로 렌더링합니다.


4.6 API 디스패치 (dispatchApi)

core/api/{action}.php 파일을 로드합니다. sitemap • robots • captcha_image 는 Content-Type 헤더를 파일에서 직접 설정하며, 나머지는 Dispatcher가 application/json 헤더를 자동 발행합니다.


4.7 레이아웃 렌더링 체계 (renderWithLayout)

콘텐츠 파일과 레이아웃(헤더•푸터) 파일을 분리하여 결합하는 방식입니다. 출력 버퍼링을 활용해 콘텐츠를 $dx_content 변수에 담은 후 레이아웃 파일에 주입합니다.
 
// renderWithLayout 동작 원리
ob_start();
extract($context);       // route, page, type, slug 변수 주입
include $contentFile;    // 콘텐츠 파일 실행
$dx_content = ob_get_clean();

// 레이아웃 파일에서 $dx_content 를 echo 하여 콘텐츠 삽입
require $layoutFile;     // themes/{테마}/layout/main.php

renderPageWithLayout: 페이지 파일 실행 중 에러를 격리하는 강화 버전. PHP notice/warning은 로그에만 기록하고, Exception은 깔끔한 에러 박스로 대체하여 레이아웃을 유지합니다. 이 방식 덕분에 페이지 파일에서 에러가 발생해도 헤더와 푸터는 정상 출력됩니다.


5. DxRouter — 라라벨 스타일 라우터 (v6.2.0+)

v6.2.0에서 추가된 DxRouter 클래스는 routes/ 폴더의 PHP 파일에서 선언적으로 라우트를 등록하고, URI 패턴 매칭•미들웨어•컨트롤러 기반 처리를 지원합니다. 기존 파일 기반 라우팅보다 먼저 실행되며, 매칭되지 않으면 기존 방식으로 폴백됩니다.


5.1 라우트 등록 방식

// routes/web.php 예시

// HTTP 메서드별 등록
DxRouter::get('/mypage/dashboard', 'MemberController@dashboard')
        ->middleware('auth');

DxRouter::post('/api/update', 'MemberController@update')
        ->middleware(['auth', 'csrf']);

// 클로저 라우트
DxRouter::get('/health', function() {
    echo json_encode(['status' => 'ok', 'version' => DX_VERSION]);
    exit;
});

// 그룹 (공통 prefix + middleware)
DxRouter::group(['prefix' => '/shop', 'middleware' => 'auth'], function() {
    DxRouter::get('/cart', 'ShopController@cart');
    DxRouter::post('/order', 'ShopController@order')->middleware('csrf');
});

// REST 리소스 자동 등록
DxRouter::resource('/posts', 'PostController');
// → GET /posts (index), POST /posts (store)
//   GET /posts/{id} (show), PUT /posts/{id} (update)
//   DELETE /posts/{id} (destroy)


5.2 미들웨어 시스템

DxRouter는 내장 미들웨어와 커스텀 미들웨어를 모두 지원합니다.
 
미들웨어 동작
auth 비로그인 시 /auth/login 으로 리다이렉트
admin 비관리자 시 403 응답
guest 로그인 상태면 홈으로 리다이렉트
csrf CSRF 토큰 검증 실패 시 JSON 에러 반환
json Content-Type: application/json 헤더 자동 발행
throttle 요청 제한 (향후 확장용 슬롯)
커스텀 클로저 또는 Class@handle 형태로 등록 가능


5.3 라우트 우선순위와 폴백

index.php [STEP 5] 라우팅 실행 순서

1. routes/*.php 파일 알파벳순 자동 로드
   → DxRouter에 라우트 등록

2. DxRouter::dispatch() 실행
   → 현재 URI + HTTP 메서드 매칭
   → 매칭 성공: 미들웨어 실행 후 컨트롤러/클로저 호출 → 종료
   → 매칭 실패: false 반환

3. 폴백: new Dispatcher(new Router())->dispatch()
   → Router::resolve() 로 URI 분석
   → Dispatcher가 타입별 핸들러 실행


6. DxTheme 폴백 체인

DxTheme::resolve($relPath)는 현재 활성 테마에서 파일을 찾고, 없으면 default 테마에서 다시 탐색합니다. 이 폴백 구조 덕분에 커스텀 테마는 변경이 필요한 파일만 포함하면 됩니다.


파일 탐색 순서 (폴백 체인)

  1. themes/{현재테마}/{relPath}  ← 우선 탐색
  2. themes/default/{relPath}     ← 폴백


게시판 스킨 탐색 순서:

  1. themes/{현재테마}/board_{skin_key}/{action}.php
  2. themes/{현재테마}/board_default/{action}.php
  3. themes/default/board_{skin_key}/{action}.php
  4. themes/default/board_default/{action}.php

💡 site_domain 테마 오버라이드: Dispatcher::loadPageFile()은
   page의 site_domain이 있을 경우 해당 도메인 사이트의 테마를 우선
   사용합니다. 이를 통해 멀티사이트 환경에서 도메인별 완전히
   다른 디자인을 적용할 수 있습니다.


7. 세션 최적화와 보안

index.php는 모든 GET 요청에서 세션을 시작하지 않는 최적화를 적용합니다. 세션 파일 락 경합을 줄여 동시 접속자가 많은 환경에서 응답 속도를 향상시킵니다.

세션 시작 조건 (하나라도 해당되면 세션 시작):
  • POST 요청 또는 Ajax(XHR) 요청
  • 이미 세션 쿠키가 존재하는 경우
  • /admin, /auth 경로
  • /view/, /write, /edit, /reply, /api/ 경로

세션 미시작 조건: 위 조건에 해당하지 않는 순수 GET 요청 (대부분의 게시판 목록•홈 페이지 조회)
 
// DX_SECRET_KEY 기반 동적 키 이름
// 사이트마다 다른 64자리 랜덤 시크릿 키로
// 세션 키 이름 / CSRF 토큰 키 / Rate Limit 키를 동적 생성
// → 소스코드가 공개되어도 키 이름 예측 불가

// 보안 헤더 (Secure.php + .htaccess/web.config 이중 발행)
// X-Frame-Options: SAMEORIGIN
// X-Content-Type-Options: nosniff
// X-XSS-Protection: 1; mode=block
// Referrer-Policy: strict-origin-when-cross-origin


8. 서버 환경별 설정 가이드

환경 URL Rewrite 설정 폴백 방식 비고
Apache .htaccess (mod_rewrite) 자동 (설정 완비) 서브디렉토리 설치 지원
Nginx nginx.conf try_files 자동 (설정 완비) $uri/ 조건 제거 필수
IIS + URL Rewrite web.config <rewrite> 자동 URL Rewrite 2.x 설치 필요
IIS (Rewrite 없음) web.config <rewrite> 블록 삭제 ?_url= 파라미터 저가 호스팅 호환
저가형 호스팅 .htaccess (mod_rewrite 있으면) ?_url= 파라미터 PHP 5.6+ 지원

⚠  Nginx 주의사항: try_files 설정에서 $uri/(디렉토리 체크)를 반드시 제거해야 합니다.
   admin/, boards/ 등 실제 디렉토리가 존재하지만 이 요청들도 index.php로
   전달되어야 프레임워크가 정상 동작합니다.


9. 전체 흐름 요약

아래 다이어그램은 HTTP 요청이 실제 응답까지 처리되는 전체 경로를 보여줍니다.
 
HTTP 요청: GET /notice/view/42

1. Apache .htaccess
   RewriteRule ^ index.php [QSA,L]

2. index.php
   [STEP 1] require core/router/Router.php, Dispatcher.php, DxRouter.php
   [STEP 2] Secure::initSession() → 세션 시작(조건부) → 보안 헤더
   [STEP 3] require data/config.php → DB 연결
   [STEP 4] 플러그인 로드, DxSite, DxTheme, Auth 초기화
   [STEP 5] routes/*.php 자동 로드

3. DxRouter::dispatch()
   → GET /notice/view/42 에 매칭되는 DxRouter 라우트 없음 → false 반환

4. Dispatcher(new Router())->dispatch()
   → Router::resolve()
      getUri()        : /notice/view/42
      parseSegments() : [notice, view, 42]
      parse()         : $seg[0]=notice, $seg[1]=view (boardAction)
                        findBoard('notice') → DB 조회 성공
                        type=BOARD, slug=notice, action=view, id=42

5. DxExtend::runMiddle() — extend/middle/ 커스텀 코드 실행

6. Dispatcher::dispatchBoard()
   checkBoardAccess(board, 'view') → read_level=0 → 통과
   $GLOBALS['dx_board'] = $board
   $GLOBALS['dx_action'] = 'view'
   require boards/handler.php
     → _brd_action_view() → DB에서 게시물 42번 조회
     → _brd_render('view', $ctx)
        → DxTheme::resolveBoardSkin('default', 'view')
           → themes/{현재테마}/board_default/view.php 탐색
           → 없으면 themes/default/board_default/view.php 폴백

7. Dispatcher::renderWithLayout(skinFile, context)
   ob_start() → include skinFile → $dx_content = ob_get_clean()
   require themes/{테마}/layout/main.php  (= 최종 HTML 출력)

8. DxExtend::runBottom() — extend/bottom/ 커스텀 코드 실행
9. ob_end_flush() — 출력 버퍼 최종 전송

댓글0

로그인 후 댓글을 작성할 수 있습니다.
3.8 Extend 구조 코어 수정 없이 CMS를 확장하는 방법 2026.05.02 8. 플러그인 플러그인 DX마켓 등록 2026.05.01 8. 플러그인 플러그인 제작 2026.05.01 8. 플러그인 플러그인 구조 2026.05.01 7. 테마 테마 DX마켓 등록 2026.05.01 7. 테마 테마 제작 2026.05.01 7. 테마 테마 구조 2026.05.01 6. 게시판 스킨 DX마켓 등록 2026.05.01 6. 게시판 게시판 스킨 제작 2026.05.01 6. 게시판 댓글 및 답글 구조 2026.05.01 6. 게시판 게시판 구조 2026.05.01 5. 관리자 기능 사용법 DX 마켓 2026.04.21 5. 관리자 기능 사용법 사이트 설정 2026.04.21 5. 관리자 기능 사용법 소셜 로그인 2026.04.21 5. 관리자 기능 사용법 멀티사이트 2026.04.21 5. 관리자 기능 사용법 테마 2026.04.21 5. 관리자 기능 사용법 플러그인 2026.04.21 5. 관리자 기능 사용법 실시간 소켓 2026.04.21 5. 관리자 기능 사용법 다운로드 통계 2026.04.21 5. 관리자 기능 사용법 통계 2026.04.21
31
전체 회원
503
전체 게시글
775
전체 댓글
442
오늘 방문
33,174
전체 방문
3
현재 접속
인기글 7일 이내
최신글
최신댓글
목록