커스텀 포스트 말단지점(endpoint)과 다시 쓰기(rewrite) 옵션 집중 분석

말단지점(endpoint) 및 다시 쓰기(rewrite) 기능은 URL 축약과 고유주소 생성과 관련 깊은 옵션이다.

여기에 해당하는 옵션은 단 2개로 수가 적지만, 커스텀 포스트 옵션 중 서버의 기능과 밀접한 부분이다. 내가 포스트를 쓰면서 가장 염두에 둔 포인트이기도 하고.

  • permalink_epmask
  • rewrite

나머지 옵션에 대해서는 이 포스트를 참고하자.

워드프레스의 rewrite에 대해

커스텀 포스트의 옵션 값과는 약간 거리가 있지만, 우선 워드프레스가 어떻게 다시 쓰기를 하는지에 대해 이야기를 먼저 하고자 한다. 옵션이 워낙 적어 분량 채우기기도 하지만… 워드프레스의 다시 쓰기 원리를 이해하고 이 옵션을 보는 것이 더 낫지 않을까?

웹사이트를 돌아다니다 보면 URL이 엄청나게 긴 것들을 볼 수 있다. 그에 비하면 엄청 양반이지만, 워드프레스에서도 이렇게 긴 URL로 쿼리를 할 수 있다.

/index.php?post_type=post&paged=1

위 URL예는 블로그 포스트 1페이지를 불러 오는 예이다. 파라미터가 단 두개 뿐이라 부담은 없지만, 이런 URL은 그다지 기억하기 쉽지도 않고, 예쁘지도 않다. 또 SEO (Search Engine Optimization) 관점에서도 이렇게 장황한 URL은 권장할 만한 사안은 아니다.

깔끔한 URL을 사용하게 되면 URL 자체로도 보다 의미 있어진다. 하지만 어떻게 하면 깔끔하고 좋은 URL을 만드는지 그 요령에 대해서는 논외로 하고, 어떻게 이렇게 URL을 쓸 수 있는지에 대해서만 쓰겠다.

웹서버가 서비스를 하는 가장 기본적인 방법은 파일 기반이다. 그림 파일, 자바스크립트 등등, 또 정적인 HTML 파일들이 그렇다. 그냥 있는 그대로 클라이언트들에게 전달하면 되는 구조다.

그러나 서버 사이트 스크립트 언어인 PHP로 짜여신 파일의 경우는 조금 다르다. PHP 파일 스크립트를 (소스) 그대로 클라이언트에게 전달하는 일은 없다. PHP 스크립트 또한 파일 기반이라는 점은 변함이 없지만, 그 파일에 접근할 때 어떠한 파라미터를 주는지에 따라, 또는 GET인지 POST인지 전달 방식에 따라 결과는 달라질 수 있다.

워드프레스에서도 마찬가지다. 단지 워드프레스 코어가 index.php 이외의 주소로 접근하는 것에 대해 잘 대비했을 뿐이다. 워드프레스의 index.php 외 여러 php 파일을 대상으로 웹브라우저에서 접근해 보라. 그저 빈 스크린만 나올 뿐이다. 물론 로그인이나 관리자 같은 몇몇 특별한 접근 포인트들은 예외지만.

그러면 워드프레스에서는 어떻게 깔끔한 URL을 처리하도록 만드는 것일까? 우선 웹서버에서 rewrite를 지원해야 한다. Apache나 nginx 둘 다 rewrite를 지원할 수 있으므로 이 기능에 의지해 URL을 찾아내는 것이다. 단, rewrite 기능에 모든 것을 의지하지는 않는다. 개략적인 룰만 지정하고, 나머지 복잡한 규칙은 내부의 DB에 저장한 다음 동적으로 처리한다.

기본적인 컨셉은 이렇다.

  1. .htaccess 파일에 rewrite 룰을 다음과 같이 설정해 둔다: URL의 경로를 모두 무시하고 무조건 /index.php 파일에서 요청을 처리하도록 서버 흐름을 조작한다. 어차피 클라이언트가 어떤 경로를 요청했는지는 PHP의 $_SERVER 변수에 다 기록되어 있다.
  2. $_SERVER[‘REQUEST_URI’]에서 읽어온 URL과 데이터베이스에 저장된 rewrite 규칙을 대조한다. 여기서 규칙에 맞는 것을 찾는다면, 안 예쁜 URL로 변경한다. 즉, ‘index.php?param1=val1&param2=val2…’ 식으로 URL이 변경된다.
  3. 파라미터에 맞게 데이터베이스 쿼리를 한다.
  4. 템플릿을 불러와 적절히 응답을 한다.

워드프레스는 아파치를 서버로 사용한 경우에 내부에 .htaccess 파일을 생성한다. 생성하지 못하면 .htaccess 파일을 따로 업로드하라고 안내를 한다. 코덱스에서 기술하는 기본적인 .htaccess 파일 구조는 이렇다.

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
  • ‘<IfModule> … </IfMoudle>’은 아파치 서버의 rewrite 모듈이 동작하는 경우에만 그 안의 내용을 읽으라는 뜻이다.
  • ‘RewriteEngine On’은 다시 쓰기를 작동한다는 뜻이다.
  • ‘RewriteBase /’은 다시 쓰기를 ‘/’를 대상으로 이뤄 진다는 것이다.
  • 첫번째 RewriteRule은 URL이 그냥 index.php로 시작하고, 또 그대로 끝난다면, 이 주소는 더 건드리지 않고 놔 두라는 뜻이다.
  • 두 번째 RewriteRule 위에 두 개의 RewriteCond가 있다. 두번째 RewriteRule의 선결조건을 의미한다.
    • %{REQUEST_FILENAME}는 서버 내에서 가져와야 할 파일의 이름을 의미하는 변수이다.
    • ‘!-f’, ‘!-d’는 각각 ‘파일이 아닌 경우’와 ‘디렉토리가 아닌 경우’를 뜻한다.
    • 그래서 %{REQUEST_FILENAME}이 파일도 아니고, 디렉토리도 아니라면 URL은 무시하고 index.php로 들어와 처리하라는 뜻으로 해석된다.

예를 들어 ‘<home_url>/category/foods’라는 깔끔한 URL을 웹브라우저에서 요청했다. 사실 이런 경로는 서버에 존재하지 않는다. 그러므로 아파치 웹서버는 그냥 index.php 파일로 접속하도록 처리를 한다.

그리고 wp() 함수를 호출하고, WP::main()에서 WP::parse_request() 함수를 처리하는 동안 데이터베이스에 있는 다시 쓰기 규칙을 읽어와 ‘category/foods’라는 경로는 원래 어떤 index.php?쿼리 문자열이었는지를 조사한다.

아마 워드레스에서는 기본적으로 ‘category/food’를 ‘index.php?category_name=food’로 치환할 것이다. 그러면 예로 제시한 URL은 사실 ‘index.php?category_name=food’와 같으며 웹브라우저에서 이 URL로 요청을 해도 같은 응답이 오게 될 것이다.

이 다시 쓰기 규칙은 워드프레스 options 테이블의 ‘rewrite_rules’라는 이름으로 저장되어 있다. 규칙은 배열을 serialize 된 상태로 존재한다.

custom_post_rewrite_001
rewrite_rule들이 데이터베이스의 레코드로 저장되어 있다. 그 위 고유주소(permalinks) 메뉴의 옵션값도 저장되어 있는 것이 보인다.

또다른 예로, 가끔 워드프레스를 이사하거나 정비하는 과정에서 전면 페이지(frontend) 홈은 보이지만 네비게이션 메뉴에 있는 다른 페이지로 옮기면 404 에러가 발생하는 경우가 있다. 이 문제는 .htaccess에서 존재하지 않는 서버상의 경로를 무조건 index.php로 처리하라는 명령이 누락되어 있기 때문이다.

Nginx도 크게 다른 것이 없다. 아파치처럼 .htaccess를 활용하지는 않지만, 어떤 URL로 요청이 들어오든지간에 처리를 index.php에 맡긴다는 점은 동일하다.

permalink_epmask

사실 이 옵션을 별로 건드려야 할 이유는 거의 없다. register_post_type 함수 내부를 들여다 봐도 이 옵션은 단지 EP_PERMALINK라는 기본값을 채워 넣는 것 이외에 언급되는 일도 없고.

사실 epmask를 코어에 유효하도록 설정하려면 add_rewrite_endpoint() 함수를 써야만 한다. 또 이 옵션에 값을 집어넣는다고 해서 register_post_type이 별도로 이 함수를 쓰는 것도 아니다.

말단지점에 대한 부가 설명

말단지점(endpoint)란 http://example.com/service/path 처럼 웹서버의 어떤 경로라고 생각할 수 있는데 이것이 왜 생겼고, 어떻게 쓰여지는지는 여기에 간략히 설명되어 있다. 간단히 말해 서버의 어떤 단일 개체를 위한 엔드포인트에 별도의 패스를 덧붙일 수 있도록 한 것이다.

예를 들어 wphack 커스텀 포스트에 개체에 접근하기 위해서 ‘/wphack/<post-slug>’ 이라는 말단지점을 사용할 수 있다. 그런데 어떤 목적 때문에 포스트의 제목만, 정말 제목만 출력하는 URL이 필요하다고 가정해보자. 물론 URL을 별도로 꾸밀 수도 있지만, 이러한 URL이 포스트 타입이나 페이지 타입 전반에 걸쳐 필요로 하다면? 이러한 경우를 위해 도입된 것이 바로 endpoint api이다.

마스킹을 위해 쓰이는 상수는 다음과 같다.

  • EP_NONE
  • EP_PERMALINK
  • EP_ATTACHMENT
  • EP_DATE
  • EP_YEAR
  • EP_MONTH
  • EP_DAY
  • EP_ROOT
  • EP_COMMENTS
  • EP_SEARCH
  • EP_CATEGORIES
  • EP_TAGS
  • EP_AUTHORS
  • EP_PAGES
  • EP_ALL_ARCHIVES
  • EP_ALL
add_action( 'init', 'wphack_ep_title' );

function wphack_ep_title() {
  add_rewrite_endpoint( 'title', EP_PERMALINK | EP_PAGES );
}

말단지점에 대해 선언한다. 이 말단지점을 인식하고 동작시키는 코드는 아래와 같다.

add_action( 'template_redirect', 'wphack_ep_title_redirect' );

function wphack_ep_title_redirect() {
  global $wp_query;

  if( !isset( $wp_query->query_vars['title'] ) || !is_singular() ) {
    return;
  }

  global $post;

  header( 'Content-TYpe: text/plain' );
  echo $post->post_title;

  exit;
}

이렇게 하면 wphack, 또는 어떤 포스트, 페이지 타입에 대해 /title/ 이라는 경로를를 추가로 입력하면 개체의 제목만 출력한다. add_rewrite_endpoint 함수에 EP_PERMALINK | EP_PAGES 마스크를 씌웠으므로 각각 포스트, 페이지에 적용되었다. 나머지 상수도 마찬가지이다. 예륻 들어 EP_DAY라는 마스크는 엔드포인트에 날짜가 사용된다면 그 엔드포인트에 추가 경로를 붙일 수 있도록 한다.

이렇게 코드를 짜면, 데이터베이스를 갱신해 주어 서버에서 인식되도록 처리해야 한다. 간단한 방법으로는 설정 (settings) > 고유주소 (permalinks)로 가서 저장 버튼을 한 번 눌러 주는 것이다. 내용을 변화시킬 필요는 없다. 이렇게 하면 options 테이블의 rewrite_rules 내용이 갱신된다.

그리고 이렇게 갱신된 내용은 ‘Debug Bar’와 ‘Debug Bar Rewrite rules’ 플러그인을 사용해 진단해 볼 수 있다.

custom_post_rewrite_002
두 플러그인을 받고 활성화 한다.
custom_post_rewrite_003
로그인하면 어드민 바에 Debug 항목이 추가된 것이 보인다. 이 버튼을 누르면 그림과 같은 항목이 나온다. 좌측 메뉴에서 ‘Rewrite Rules’항목을 선택한다.
custom_post_rewrite_004
title 부분만 골라 보았다. 이렇게 정규식 매칭 표현과 매칭된 규칙이 어떻게 변환될지 정의되어 있다.

rewrite

rewrite 옵션은 하나의 배열을 값으로 받으며, 이 배열은 아래의 키를 가진다. 이 값을 변경하면 반드시 새로 고침 레코드를 갱신해 주어야 한다.

slug

query_var 옵션이 질의 문자열에 쓰이는 반면, slug 옵션은 rewrite에 사용된다. 만약 slug의 값으로 ‘ask’라는 문자열을 사용했다면 커스텀 포스트 타입 wphack은 전단부에 다음과 같은 URL로 접근 가능해진다.

<site_url>/ask/                      # 목록 (아카이브)
<site_url>/ask/page/PAGE_NUMBER      # 페이지 번호로
<site_url>/ask/slug/                 # 특정 개체만

with_front

true/false를 가질 수 있는다. Front라는 것은 고유주소 설정에서 태그 항목 이전에 고정으로 생성한 문자열을 말한다. 아래의 그림을 보면 이해가 빠를 것이다.

custom_post_rewrite_005
/with-front 부분이 front이다.

고유주소에 /with-front 문자열을 앞에 덧붙였는데, 이렇게 저장을 하면 워드프레스의 대부분의 기본적인 고유주소는 /with-front로 시작한다. 그러나 이 값을 false로 설정하면 현재 동록하는 커스텀 포스트만큼은 이 문자열을 허용하지 않는다.

slug가 ‘ask’로 설정되었고 front가 ‘/with-front’라면

  • with_front가 false 일 때: <site_url>/ask로 개채의 목록 접근
  • with_front가 true 일 때: <site_url>/with-front/ask로 개체의 목록 접근

feeds

포스트 타입을 만들면 이에 대한 피드도 간단하게 만들어진다. 피드는 atom, rss, rss2 , rdf 다 지원을 한다. 이 값을 true로 하면 주소 끝에 /feed, /rss, /rss2 만 붙이면 바로 해당 형식으로 콘텐츠를 지원한다.

custom_post_rewrite_006
/feed, /rss, /atom, /feed/rss, 같은 주소만 붙여주면 아주 쉽게 피드를 작성할 수 있다.

pages

목록에서 페이징을 가능하게 한다. 이 항목이 true로 되어 있어야 <site_url>/ask/page/PAGE_NUMBER 처럼 된 URL로 접근 시 제대로 페이징된 목록을 보여 주게 된다.

페이지 기능 자체를 이 옵션으로 켜고 끄는 것이 아니다. 단지 URL 규칙을 제대로 설정하는 것이다.

ep_mask

워드프레스 3.4 이후부터 permalink_epmask 옵션을 대체하는 옵션이다.

마치며

3.4에 오면서 permalink_epmask 옵션이 rewrite 옵션과 통합되면서, 사실상 이 포스트에서 다룬 옵션은 고작 하나였다. 그렇지만 rewrite는 워드프레스 코어 기능과 매우 밀접한 중요한 기능이라 생각해서 별도의 포스트로 따로 작성한 것이다.

쿼리 문자열은 코어에 있어 엄청나게 중요한 부분이다. 어떤 콘텐츠를 어떻게 보여줄지는 모두 쿼리 파라미터에 달려 있기 때문에 사실 이 쿼리 파라미터가 워드프레스를 움직이는 중추라고 할 수 있다. 쿼리 문자열을 직관적인 URL로 변환시켜 주는 옵션이 rewrite이며 이것은 서버의 다시쓰기 기능을 필요로 하지만, 복잡한 다시쓰기 규칙은 워드프레스 코어 자체에서 별도로 수행하고 있다.

댓글 남기기