[태그:] 워드프레스

  • WP REST API, fetch(), 그리고 CORS 허용하기

    WP API를 불러다 사용할 때, 도메인이 다르면 CORS 제한에 걸리게 된다. 물론 서버에서 직접 부르면 이 제한은 없지만, 브라우저에서 직접 호출할 때는 성가신 문제가 생긴다.

    그래서 보통은 서버에서 직접 Access-Control-Allow-Origin 헤더를 추가하도록 하는 팁을 발견할 수 있다. 하지만 워드프레스 WP REST API 사용할 때 굳이 이렇게까지 할 필요는 없다.

    간단하다. 요청할 때 Origin 헤더를 추가하고, 거기에 브라우저에서 접속한 URL을 입력하면 된다.

    원리는 이렇다. 아래는 WP REST API 응답 메시지 출력 중, CORS 헤더를 만드는 함수 구현이다.

    function rest_send_cors_headers( $value ) {
    	$origin = get_http_origin();
    
    	if ( $origin ) {
    		// Requests from file:// and data: URLs send "Origin: null".
    		if ( 'null' !== $origin ) {
    			$origin = sanitize_url( $origin );
    		}
    		header( 'Access-Control-Allow-Origin: ' . $origin );
    		header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE' );
    		header( 'Access-Control-Allow-Credentials: true' );
    		header( 'Vary: Origin', false );
    	} elseif ( ! headers_sent() && 'GET' === $_SERVER['REQUEST_METHOD'] && ! is_user_logged_in() ) {
    		header( 'Vary: Origin', false );
    	}
    
    	return $value;
    }Code language: PHP (php)

    코드에 get_http_origin() 함수에서 $origin 을 발견하면 알아서 Access-Control-Allow-Origin 헤더를 추가해 주는 흐름을 볼 수 있다.

    그리고 get_http_origin() 함수 구현은 아래와 같은데,

    function get_http_origin() {
    	$origin = '';
    	if ( ! empty( $_SERVER['HTTP_ORIGIN'] ) ) {
    		$origin = $_SERVER['HTTP_ORIGIN'];
    	}
    
    	/**
    	 * Change the origin of an HTTP request.
    	 *
    	 * @since 3.4.0
    	 *
    	 * @param string $origin The original origin for the request.
    	 */
    	return apply_filters( 'http_origin', $origin );
    }Code language: PHP (php)

    요청 헤더에서 Origin 헤더 값을 발견하면 그 값을 리턴하도록 되어 있다. 그러면 js/ts 코드에서 fetch()를 호출할 때 아래처럼 하면 된다.

    const url = 'https://...' // 요청할 URL.
    
    fetch(url, {
      mode: 'cors',
      headers: {
        'Content-Type': '...',
        'Origin': 'https://....', // 브라우저의 URL.
      }
    })Code language: JavaScript (javascript)

    이렇게 하면 워드프레스가 알아서 CORS 허용 헤더를 추가해준다. 끝!

  • 메타 테이블 체계 확장하기

    그러니까, 워드프레스에서 메타 테이블 체계는 상당히 매력적이다. 물론 단점도 많긴 하다. 그러나 간단하게 확장 가능한 유연한 저장 장소가 필요할 때 메타 키 – 메타 값 식으로 테이블을 구성해서 쓰고 싶은 생각이 들 때가 한두번이 아니다.

    다행히 워드프레스 코어 또한 메타데이터 코드는 한 벌만 만들고 포스트, 텀, 유저 등에서 확장하여 재활용하고 있다. 플러그인에서도 동일하게 확장하여 메타데이터 스타일의 테이블을 사용할 수 있다. 한 번 해 보자.

    (더 보기…)
  • Good Job! PHPUnit-Polyfills

    코어의 유닛 테스트 코드들은 아직 완전히 최신 PHPUnit을 지원하는 건 아닌 것 같다. 대신 Yoast의 PHPUnit-Polyfills 라이브러리를 사용하여 호환성을 보장하는 것 같다. 덕분에 PHPUnit 9 최신 버전을 사용할 수도 있고, 그 덕분에 PHP 8.1 최신 버전이 사용 가능하다.

    최신 버전의 PHP와 PHPUnit을 사용하여 유닛 테스트! 이제는 정상적으로 실행된다!

    이게 된다면 앞으로 플러그인 개발시 7.4보다 더 높은 버전을 도입해서 사용할 수 있겠다! 그동안 8.0의 기능을 사용하지 못해 정체되었는데, 이제는 가능할지도.

  • Naran Terms Merger v1.0.0

    Naran Terms Merger v1.0.0

    워드프레스 사이트 내에 정의된 택소노미 텀들을 정리하는 두구입니다. 블로그를 오래 운영하면서 그래도 자잘한 글들을 쓰다 보니 태그가 많이 쌓입니다.

    그런데 태그를 기분대로 만들다 보니, 태그 분류의 응집력이 너무 떨어집니다. 태그가 글들을 상세하게 분류하는 것도 나름 중요하지만, 불필요하게 태그가 중복되기도 하고, 태그 하나에 포스트가 겨우 하나씩만 대응되기도 합니다. 그런 것들은 재고의 여지가 있습니다.

    파편화된 태그를 모아 좀 더 응집력 있는 태그로 만든다… 컨셉은 좋습니다. 의외로 WordPress.org 에 올라온 플러그인도 많지 않습니다. 이게 해 보면 단순한 작업일 것 같지만 파편화된 텀을 일관성있게 묶게 시각화하는 UI 작업이 간단하지가 않은 것 같습니다.

    그래도 일단 초기의 컨셉을 다듬고 수정한 1.0.0 버전을 만들었습니다. 실제 텀들을 삭제하는 작업을 포함하므로 아직은 좀 더 테스트가 필요하겠습니다. 실제로 운영된 블로그에 설치해서 내부 테스트를 하니, 아직은 좀 더 필요한 부분들이 많은 것 같습니다.

    병합 방법

    관리자 > 도구 > Terms Merger 에서 찾을 수 있습니다. 텀을 헤집는 녀석이라 관리자 권한이어야만 접근이 가능하도록 설정햇습니다.

    화면에 두 개의 탭이 나옵니다. 하나는 Terms, 하나는 Slots 입니다.

    Terms 에서는 택소노미별 텀을 늘어놓습니다. Slots 에서는 병합할 텀들을 모으고 점검합니다.

    Terms 에서 아래 그림처럼 텀을 선택하고 슬롯을 지정합니다. 그러면 해당 텀은 그 슬롯에 배당됩니다.

    텀들을 슬롯에 모으면, Slots 탭으로 이동하여 슬롯에 담긴 텀을 확인해 볼 수 있습니다.

    1 Add new slot 버튼을 누르면 슬롯을 더 만들 수 있습니다. 최대 10개까지 생성이 가능합니다. 즉, 텀을 1번부터 10번 슬롯 중 하나에 담아서 분류를 할 수 있게 되는 것입니다.

    이 때 Assigned term(s) 에 내가 쌓아둔 텀들의 목록이 나타납니다. x를 누르면 슬롯에서 삭제할 수 있습니다.

    2 Header term 은 실제 병합을 할 때 어떤 텀을 대상으로 병합할지 기준을 정하는 것입니다. 파란색으로 하이라이트된 텀이 헤더 텀입니다. 위 그림에서는 ‘admin-ajax.php’라는 텀을 기준으로 나머지 텀들이 병합됩니다.

    3 Merge Terms 를 누르면 병합이 진행됩니다. 진행하기 전 경고창이 떠서 재차 확인을 요구합니다.

    4 Remove this slot 은 슬롯을 지우는 명령입니다. 슬롯만 삭제되고 텀에는 아무런 영향이 없습니다.

    많은 개선점이 …

    UI가 그다지 간단하지는 않았습니다. 많은 디테일한 점이 필요한 것으로 보입니다. 개발된 플러그인을 제 블로그에 설치하여, 나름 실사 블로그 사이트에 쌓인 태그 데이터를 대상으로 테스트해 보았습니다. 그래 보니 실제 사용에 있어 사용자가 정말 편리하게 텀을 분석하고 관리하려면 아직 부족한 점이 많다는 것을 느낍니다. 그래도 이 정도 개발을 진행한 것에 보람을 느끼며 포스팅을 올립니다.

    좀 더 완성도를 올리면 wordpress.org 에 올려 볼까 고민하고 있습니다. 혹시 원하는 분이 계시다면 github repository에서 다운로드 받아 테스트해 보세요.

  • 같은 옵션에 경쟁을 붙이면 어떻게 될까?

    워드프레스에서 옵션 테이블은 매우 자주 이용하는 테이블입니다. 여기에 사이트 전체에 빈번하게 사용되는 값들이 저장되어 있습니다. 코어, 테마, 플러그인 모두 상당히 이 옵션 테이블에 의존하고 있습니다. 때로는 너무 옵션 테이블에 의존해서 상당히 퍼포먼스가 떨어질 때도 있긴 하지요. 이 포스트에서는 그 퍼포먼스의 문제는 논외로 하고, 이 옵션 테이블의 업데이트를 할 때 알아두면 좋을 내용을 적어 보도록 하겠습니다.

    (더 보기…)

    페이지: 1 2 3 4 5

  • 워드프레스 코어 #3

    2020년 08월 04일 네번째 모임 발표 자료

    워드프레스 데이터베이스와 회원 로그인에 대한 자료입니다.

    슬라이드 링크

  • 워드프레스 코어 #2

    지난 시간에 이어 코어 두 번째 이야기를 준비했습니다. 이번에는 wp() 함수에서 일어나는 라우팅에 대해 알아봅니다.

    URL을 입력하면 각 주소에 따라 서로 다른 콘텐츠가 나옵니다. 어떤 주소로는 단일 포스트 세부 내용이 나오고, 또다른 주소로는 아카이브 페이지가, RSS 피드가 나옵니다. 이게 어떻게 가능할까요? 이 원리를 간단히 알아봤습니다.

    슬라이드 보기

  • 워드프레스 코어 #1

    2020년 7월 2일 두번째 모임 발표

    워드프레스가 사용자로부터 요청을 받으면, 응답을 보내기까지 어떤 일들이 일어나는지 간략하게 알아본 자료입니다.

    슬라이드 링크

  • 워드프레스 코어 한 벌로 여러 싱글 사이트 세팅하기

    워드프레스는 멀티사이트로 운용할 수 있다. 한 벌의 코어로 여러 사이트를 제작할 수 있다. 이 때 site1.example.com 같은 서브도메인이나 아니면 example.com/site1 처럼 패스로 각 사이트를 생성할 수 있으며, 약간의 수정만 거치면 각 사이트마다 독자적인 도메인 설정도 가능하다.

    그러나 이 방법은 한 코어에 wp-config.php와 플러그인, 테마, 그리고 일부 DB 내용을 공유하게 된다. 즉, 완전히 분리된 사이트를 운용하게 되는 것은 아닌 것이다.

    이 포스트에서는 워드프레스 싱글 사이트를 기반으로 하되 완전히 각자의 영역을 독자적으로 가지는 셋팅법을 적고자 한다. 사실 독자적인 셋팅을 가져가므로 설정에 의해 각 사이트는 싱글 사이트일 수도 있고 멀티사이트가 될 수도 있다. 단, 코어와 코어에 위치한 .htaccess 때문에 싱글사이트 전용, 혹은 멀티사이트 전용으로만 운용 가능하다. 도메인별로 .htaccess 처리가 가능하면 좋겠지만 쉽지 않아 보인다.

    이 방법은 코어를 하나로 절약할 수 있다는 장점이 있다. 워드프레스 코어 API는 워낙 성숙해 있고 변화가 뚜렷하지 않기 때문에 버전간의 차이로 인한 문제가 드문 편이기 때문에 꽤 안심할만하다.

    하지만 내 개인적인으로는 코어 이외에 다른 셋업들은 그냥 하나하나 설치하는 스탠드얼론 방법에 비해 이 방법이 아주 이득이 있는지는 모르겠다. 여러 사이트가 산만하게 늘어지는 것보다는 조금 정리할 수 있다는 여지가 있다는 정도? 그냥 이런 방법이 있고, 이런 응용이 가능하니 기록하는 의미로 포스팅한다.

    도메인 설정

    각 도메인마다 로컬 서버인 127.0.0.1로 매핑해야 한다. 가장 간단한 방법은 /etc/hosts 파일을 편집하는 법이다. 이 포스트를 위한 예시로 a.shared.com, b.shared.com 이라는 가상의 도메인을 사용하기로 하자.

    # /etc/hosts
    127.0.0.1 shared.com
    127.0.0.1 a.shared.com
    127.0.0.1 b.shared.com

    경로 설정

    여기서는 shared 라는 디렉토리를 설명을 위한 루트 디렉토리로 한다. shared 아래에는 다음처럼 디렉토리를 설정하였다.

    • configs: 도메인별 설정 저장
    • contents: wp-content 디렉토리를 도메인별로 저장
    • core: 워드프레스 코어 파일

    코어 파일 다운로드

    한 벌의 코어를 사용하기 때문에 코어를 별도의 장소에 격리한다. shared/core가 그 장소이며, 저기에 워드프레스 코어 파일을 다운로드 받는다. 아래 간단한 한 줄 다운로드 예제 코드를 첨부한다.

    # 워드프레스 설치는 이 한줄로!
    wget https://wordpress.org/latest.tar.gz -O - | tar xzf - --strip=1 -C shared/core
    echo 'Completed!'

    아파치 서버 조정

    복잡한 설정이 필요한 nginx보다는 간단하게 쓸 수 있는 아파치를 선호한다. 첫번째 아파치 설정은 디렉토리 접근 허용이다.

    <Directory "shared">
    	AllowOverride All
    	Require all granted
    </Directory>

    두번째는 가상호스트 설정. /etc/hosts를 위한 도메인 설정은 와일드카드가 먹히지 않지만, 다행히 아파치 가상호스트에서는 ServerAlias로 와일드카드를 설정할 수 있다. 요즘 https를 많이 사용하므로 간단하게 자가서명한 인증서를 이용해 https 방식으로도 동작하게 해 뒀다.

    <VirtualHost *:80>
    	ServerName   shared.com
    	ServerAlias  *.shared.com
    	DocumentRoot shared/core
    	Alias        /contents shared/contents
    </VirtualHost>
    <VirtualHost *:443>
    	ServerName   shared.com
    	ServerAlias  *.shared.com
    	DocumentRoot shared/core
    	Alias        /contents shared/contents
    
    	SSLEngine On
    	SSLCertificateFile    apache2/ssl/apache2.crt
    	SSLCertificateKeyFile apache2/ssl/apache2.key
    </VirtualHost>

    wp-config.php 파일 설정 조정

    wp-config.php는 워드프레스 코어에 두어도 되고, 코어 파일이 있는 디렉토리의 하나 위에 두어도 된다. 여기서는 코어 디렉토리보다는 하나 위쪽인 shared/wp-config.php 파일을 두도록 하자. 그리고 여기서는 각 사이트를 위한 설정 파일을 인클루드한다.

    <?php
    $host   = $_SERVER['HTTP_HOST'] ?? '';
    $config = __DIR__ . "/configs/{$host}.php";
    
    if ( $host && file_exists( $config ) && is_readable( $config ) ) {
        require_once __DIR__ . '/configs/common.php';
        require_once $config;
    } else {
        die( 'Config file is missing!' );
    }
    
    /* That's all, stop editing! Happy publishing. */
    
    /** Absolute path to the WordPress directory. */
    if ( ! defined( 'ABSPATH' ) ) {
    	define( 'ABSPATH', __DIR__ . '/' );
    }
    
    /** Sets up WordPress vars and included files. */
    require_once ABSPATH . 'wp-settings.php';
    

    wp-config.php는 각 도메인마다 읽어야 할 설정 파일을 구분해서 읽도록 지시한다. 그리고 shared/config/common.php 파일에는 모든 사이트의 범용 설정을 작성할 것이다. 나는 데이터베이스를 하나로 잡고 마치 멀티사이트처럼 접두만 변경할 것이므로 접속 정보는 common.php에 작성할 것이다. 그리고 디버그 모드 같은 설정도 이곳에 공통 요소로 잡을 것이다.

    <?php
    define( 'DB_NAME', 'database_name_here' );
    define( 'DB_USER', 'username_here' );
    define( 'DB_PASSWORD', 'password_here' );
    define( 'DB_HOST', 'localhost' );
    define( 'DB_CHARSET', 'utf8' );
    define( 'DB_COLLATE', '' );
    
    define( 'WP_DEBUG', true );
    define( 'WP_DEBUG_DISPLAY', true );
    define( 'WP_DEBUG_LOG', false );
    define( 'WP_DISABLE_FATAL_ERROR_HANDLER', true );
    define( 'SCRIPT_DEBUG', true );
    

    그리고 shared/configs/a.shared.com.php, shared/configs/b.shared.com.php 두 파일을 적절히 생성한다.

    define('AUTH_KEY', '...');
    define('SECURE_AUTH_KEY', '...');
    define('LOGGED_IN_KEY', '...');
    define('NONCE_KEY', '...');
    define('AUTH_SALT', '...');
    define('SECURE_AUTH_SALT', '...');
    define('LOGGED_IN_SALT',  '...');
    define('NONCE_SALT', '...');
    
    $table_prefix = 'a_'

    각 KEY, SALT는 https://api.wordpress.org/secret-key/1.1/salt/ 에서 적절히 생성한 것을 사용한다. 개발 설정에서는 이 키를 common.php에 둘 수 있겠지만, 실서버 설정이라면 분리하는 것이 맞다. 그리고 $table_prefix 는 중요하다. 사이트마다 다른 문자열을 적용하여 다른 테이블을 사용하도록 설정하자. 물론, 의도적으로 같은 접두사를 쓰는 것도 가능하다.

    HTTPS로 리다이렉트

    자가인증서이긴 하지만 HTTPS 환경을 꾸미는데 부족함이 없다. http로 접속시 https로 리다이렉트하는 코드를 삽입하자.

    <IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301,QSA]
    </IfModule>

    WP-CONENT 디렉토리 변경

    여기까지 하면 도메인마다 다른 설정 파일과 다른 테이블을 이용하되 워드프레스 코어를 하나로 쓰는 것이 가능해진다. 그러나 아직 작업이 하나 더 남아있다. 여기까지 설정하면 모든 테마와 플러그인, 그리고 업로드 디렉토리 같은 wp-content 디렉토리를 공유하기 때문이다. 이것을 도메인별로 분리하는 작업을 더해야 한다.

    우선 shared/core/contents가 비어 있다. 여기에 a.shared.com, b.shared.com 두 도메인을 그대로 딴 디렉토리를 만들고, 생성된 디렉토리마다 wp-content의 index.php 파일과 plugins, themes, uploads 디렉토리를 복사해 넣는다.

    그리고 shared/configs/common.php 에 아래 설정을 덧붙인다.

    define( 'WP_CONTENT_DIR', "shared/contents/{$_SERVER['HTTP_HOST']}" );
    define( 'WP_CONTENT_URL', "https://{$_SERVER['HTTP_HOST']}/contents/{$_SERVER['HTTP_HOST']}" );

    이제 도메인마다 각자 다른 콘텐트 디렉토리를 가지게 된다.

    정리

    길게 늘여쓰기는 했지만 다음이 핵심이다.

    • shared 디렉토리에 configs, contents, core 세 디렉토리 생성후 각각 설정, wp-content, 코어 부분을 담당한다. 설정과 wp-content는 도메인마다 달라지므로 각각의 도메인으로 구분된 디렉토리별 관리를 한다.
    • configs 디렉토리에 common.php로 공통 설정을 두고, 각 도메인.php 파일을 읽도록 wp-config.php 파일 설정을 한다.
    • WP_CONTENT_DIR, WP_CONTENT_URL 도 shared/contents/{$HTTP_HOST}를 읽을 수 있도록 설정한다. 그리고 아파치 서버도 적절히 alias 설정을 해 두어 각 도메인별로 변경된 wp-content 접근이 가능하게 한다.
    • 각 도메인별 접근시 코어가 도메인별로 다른 설정 파일을 읽어 각자 다른 데이터베이스, wp-content (shared/contents/{$HTTP_HOST})를 이용하도록 한다.

    이렇게 하면 한 코어만 고정적으로 두고 새 워드프레스를 만들고 가볍게 삭제하는 것이 가능하다. 코어 업데이트도 단 한번이면 족하고, 사용 환경에 따라 장점이 많을 수 있다.

    그러나 이런 세팅을 사용해 개발을 몇 번 해 봤는데 단점도 있었다. 아무리 코어가 안정적이어도 코어의 환경이 변경되면 전에 없던 에러가 발생할 우려가 있기 마련이다. 때로는 스탠드얼론으로 모든 환경을 한 벌에 두고 안정적으로 두는 것이 더 나을 수도 있다.

    그리고 개발시 WP CLI를 자주 사용하게 되는데, 이 방법을 쓰는 경우 코어와 설정 등이 분리되어 있어 WP CLI를 사용하기 위해 추가적으로 파라미터를 더 지정해 줘야 하기 때문에 조금, 아니 꽤 불편하다는 단점이 있다. 이런 추가적인 설정을 계속해서 요구하기 때문에 오히려 더 번잡스러운 것이 아닌가 하는 생각도 좀 들었다. 워드프레스 코어 소스 파일이 많기는 하지만, 그렇다고 용량이 감당 못할 정도로 엄청난 것도 아니고…

    추가: 새 도메인에 새 워드프레스 추가하기

    이제 앞으로 새로운 도메인으로 워드프레스를 설치하려면 다음 순서대로 하면 된다.

    1. 새로운 도매인과 IP 매핑. e.g. /etc/hosts
    2. shared/contents 디렉토리에 도메인 이름으로 디렉토리 생성. 여기가 새 워드프레스의 wp-content 가 됨. 코어에 있는 wp-content/ 안의 내용을 복사하면 편하다.
    3. shared/configs 디렉토리에 도메인 이름.php 파일 생성. 여기에 적절한 설정을 붙인다. $table_prefix 변수는 반드시 여기서 설정해 두어야 한다.
    4. 새로운 도메인으로 접속하여 워드프레스 설치를 마친다.

  • flush_rewrite_rules() 함수에 대해

    플러그인/테마 개발시 flush_rewrite_rules()라는 함수를 심심찮게 쓴다. 안 하면 사이트에서 404 에러가 날 수 있어 항상 코드에 집어 넣는 나름 중요한 역할을 하는 녀석이다. 그런데 이 함수가 하는 역할이 정확히 무엇인지 알고 있는가? 이 포스트에서 이 함수에 대해 좀 더 정확히 알아보고 기록해본다.

    (더 보기…)
  • 워드프레스 5.2 에러 복구 기능 스터디

    들어가며

    워드프레스 5.2에 들어오면서 여러 개선점이 있었습니다. 릴리즈 노트에 나열된 사항 중 단연 사이트 상태 점검(Site Health Check), 치명적인 에러에서 코어 동작을 보호하는 기능 (PHP Error Protection) 이 눈에 띕니다.

    사이트 동작 체크는 도구 > Site Health 에서 확인할 수 있으며, 기능 자체는 Health Check & Troubleshooting에서 많은 것을 가져온 것으로 보입니다. 코어 버전과 플러그인 버전의 차이는 문제 진단 기능(troubleshooting)의 유무로 보입니다. 이 기능에 대해서는 별도로 정리하기로 하지요. 지금까지 여러 플러그인들이 시스템 정보 수집 페이지를 각자 개발했었습니다. 이제 앞으로 이런 시스템 정보는 코어에서 잘 통합될 것으로 보입니다.

    Site Health Check 페이지. 기능의 Look & Feel이 Health Check & Troubleshooting에서 많이 가져온 것이 보입니다.
    코어에 새로 추가된 사이트 정보 화면

    업데이트 이후 갑자기 서버가 죽는 일. 워드프레스를 장기적으로 운영할 때 가장 골치아픈 문제일 겁니다. 코어가 아무리 안정적으로 발전한다 하더라도, 세상에 완벽은 없습니다. 언제 어디서든 잘못된 코드를 작성할 위험은 도사리고 있습니다. 어느 순간 업데이트를 끝마치고 페이지를 재접속했더니 텅 빈 화면만 나온다고 생각해 보세요. 영어권에서는 이것을 WSOD라고 부르더군요. White Screen of Death.

    그렇다고 그게 무서워 업데이트를 마냥 미뤄 놓을 수는 없습니다. 아주 가끔 실수가 있다고는 하지만, 전반적으로는 업데이트란 모든 플러그인과 테마가 지금보다 더욱 편하고 좋은 환경을 만들기 위해 노력한 결과물입니다. 보안상 중요한 문제가 일어날 수도 있어 외면해서는 절대 안 되겠죠.

    각설하고, 이번 워드프레스 코어 개발진이 이에 적극적으로 대응하기로 결정했나 봅니다. 5.2부터는 PHP 코드에 의도치 않은 치명적인 에러가 발생하도 무조건 WSOD 상태로 빠지지 않도록 동작합니다. 사용자에게도, 개발자에게도 매우 환영할 만한 기능입니다. 이 포스트를 통해 이 기능이 어떻게 동작하는지, 또 어떻게 에러로부터 사용자를 보호하는지 기록해 보려 합니다.

    에러 보호 기능

    PHP 에러 보호 기능의 핵심 중의 핵심은 아래 두 PHP 함수라고 볼 수 있습니다.

    register_shutdown_function() 은 PHP가 완전히 종료하기 전에 특정 기능을 실행할 수 있도록 하는 함수입니다. 그리고 error_get_last() 함수는 PHP 실행 중 현재 시점에서 가장 마지막에 발생한 에러를 조회하는 함수입니다.

    코어는 이 두 함수를 이용해 다음 과정을 실행할 것입니다.

    1. register_shutdown_function() 함수를 이용해 스크립트 종료 직전 에러 핸들러를 등록합니다.
    2. 스크립트 종료 직전 에러 핸들러가 error_get_last() 함수를 이용해 스크립트 실행 중 심각한 에러가 발생했는지 아닌지 점검합니다. 만약 별다른 에러가 없다면 임무는 여기까지입니다.
    3. 그러나 어떤 에러를 발견하였다면 에러 대응을 시도합니다.

    아주 간략하게 3단계로 축약한 기본 개념입니다. 이 부분을 구현한 코드는 wp-includes/error-protection.php 파일의 wp_register_fatal_error_handler() 함수를 참고하시면 됩니다.

    코어의 구체적인 에러 복구

    에러를 만나게 되면 이 에러가 난 위치를 파악하여 테마면 테마 에러로, 플러그인 에러면 플러그인 에러로 DB 옵션 테이블에 기록합니다. 관리자에겐 복구 모드를 접근할 수 있는 링크를 이메일로 전달합니다.

    복구 모드는 쿠키로 구워진 토큰값을 통해 인식됩니다. 복구 모드를 통해 관리자 패널에 접속하면 해당 사용자는 에러가 난 테마나 플러그인을 중단시킨 상태에서 접속하게 됩니다. 복구 모드에서는 그림처럼 어드민 바 상단 우측에 어드민 바임을 알리는 띠가 붙습니다. 브라우저 제목도 ‘Recovery Mode’라고 나오지요.

    복구 모드에 대한 설명 'Exit Recovery Mode' 띠가 둘러져 있습니다.
    복구 모드 중인 관리자 화면

    이 모드에서 관리자는 테마 혹은 플러그인의 에러를 확인할 수 있습니다.

    테마 에러 진단. Themes 페이지 (wp-admin/themes.php)의 밑바닥에서 출력됩니다.
    플러그인의 에러 진단. Plugins 페이지 (wp-admin/plugins.php)의 플러그인 목록에서 발견됩니다.

    테마는 에러 메시지가 조금 불친절하군요. 반면 플러그인은 파일 몇 번째 줄에서 어떤 에러가 나오는지까지 나오네요. 테마 메시지는 좀 더 개선되어야 할 필요가 있어 보입니다.

    여기서 관리자는 해당 오류를 수정하여 ‘resume’을 선택하든지, 아니면 ‘deactivate’를 선택하여 해당 플러그인 기능을 비활성화하더라도 전체 사이트가 마비되는 것을 막든지 선택할 수 있습니다.

    어떻게 복구 모드로 진입하는가?

    관리자 페이지에서도 심각한 에러를 만나게 되면, 코어는 관리자에게 이메일을 보내어 사이트의 에러 상태, 그리고 복구를 위한 링크를 전달합니다. 이메일은 아래 그림과 유사합니다.

    에러 탐지가 되면 이런 메일이 날아옵니다

    메일 중간에 있는 임시 URL로 접근하면 복구 모드로 동작할 수 있는 로그인 페이지를 만나게 됩니다. 이 페이지로 로그인을 하면 로그인 세션 토큰 외 특별한 토큰을 쿠키로 전달받게 됩니다.

    워드프레스 로그인 세션 쿠키

    wordpress_rec_로 시작하는 것이 바로 복구 모드를 위해 발급된 쿠키입니다. 이 쿠키값이 올바르면 사용자는 복구 모드로 동작하는 것입니다.

    또한 쿠키 값은 특별한 규칙이 있고, 이 규칙대로 잘 디코딩하면 세션 ID라는 것이 있습니다. 에러가 나면 에러 내용을 DB 옵션 테이블에 기록하는데, 옵션 테이블 이름은 이 세션 ID에 의해 조금씩 달라집니다. 세션 ID는 보통 난수적으로 선택됩니다. 실험을 위해 일부러 에러를 내고 복구모드에 들어왔을 때 옵션 테이블에는 아래와 같은 에러 내용이 기록된 것을 확인했습니다.

    option name: 3a3f3bd482a7a6f2fc2a4480a7c1d8da3a6002e3_paused_extensions
    option value: array(2) {
       'theme' =>
       array(1) {
         'twentyseventeen' =>
         array(4) {
           'type' =>
           int(256)
           'message' =>
           string(17) "Error on purpose."
           'file' =>
           string(90) "/home/changwoo/develop/wordpress/wp-contents/wp.local/themes/twentyseventeen/functions.php"
           'line' =>
           int(11)
         }
       }
       'plugin' =>
       array(1) {
         'wsod-test' =>
         array(4) {
           'type' =>
           int(256)
           'message' =>
           string(17) "Error on purpose."
           'file' =>
           string(85) "/home/changwoo/develop/wordpress/wp-contents/wp.local/plugins/wsod-test/wsod-test.php"
           'line' =>
           int(6)
         }
       }
     }
    (옵션값은 unserialize 시켰습니다.)

    에러 탐지 및 복구 모드와 관련된 설정값

    에러 탐지와 복구 모드에 대한 설명이 어느 정도 된 것 같네요. 이제 이와 관련된 define 상수나 설정값을 메모하면서 포스팅을 마치기로 합니다.

    • WP_DISABLE_FATAL_ERROR_HANDLER: 참일 때는 아예 에러 보호 기능이 동작하지 않습니다. 개발 중에는 유용하게 쓸 수 있겠네요.
    • RECOVERY_MODE_EMAIL: 에러가 생길 때 복구 모드 안내를 위한 메일 주소를 설정하는데 쓰입니다. 선언되지 않으면 워드프레스 관리자 이메일입니다.
    • WP_RECOVERY_MODE_SESSION_ID: 난수로 발생되는 세션 ID를 고정할 수 있습니다.
    • wp-content/php-error.php 파일: 기본 에러 화면을 변경할 수 있는 템플릿 파일로 사용할 수 있습니다. 원한다면 WP_Fatal_Error_Handler::display_default_error_template() 메소드를 참고하여 사이트 고유의 에러 화면으로 만들 수 있습니다.
    • wp-content/fatal-error-handler.php 파일: WP_Fatal_Error_Handler 가 아닌 다른 에러 핸들러 클래스를 정의할 수 있습니다. 반드시 ‘handle()’이라는 퍼블릭 메소드가 정의되어 있어야 합니다.
  • 디버그 로그는 가려놓읍시다!

    디버그 로그는 가려놓읍시다!

    워드프레스는 흔히 카페24 같은 호스팅을 사용한다든지, 이런 오픈된 개발 서버에서 작업되는 경우도 흔합니다. 그리고 워드프레스의 동작을 보다 정밀하게 파악하기 위해, 아래 같은 설정을 사용하기도 합니다.

    (더 보기…)
  • 새 워드프레스 운영자에게 보내는 잔소리

    클라이언트에게 답신 메일을 보내다, 장문이 되어버렸다. 그런데 나중에 발췌해서 공유하고자 함이니 고객에 대한 것은 편집해서 공유해 본다. 잘 쓴 글은 아니지만 그래도 나누고 싶다.

    워드프레스를 사용하여 운영을 시작하시는 분들께는 다음과 같은 조언을 드리고 싶습니다.

    (더 보기…)
  • 커스텀 포스트가 관리자 페이지에서 일반 페이지처럼 템플릿을 선택할 수 있도록 하기

    커스텀 포스트가 관리자 페이지에서 일반 페이지처럼 템플릿을 선택할 수 있도록 하기

    register_post_type() 함수 인자에서 ‘support’ 항목에 ‘page-attributes’를 추가한다. 그리고 아래의 필터를 추가한다.

    <?php
    add_filter( 'theme_' . 'custom_post_type' . '_templates', 'my_templates', 10, 4);
    
    function my_templates( $post_templates, $wp_theme, $post, $post_type ) {
      if ( empty( $post_templates ) ) {
        $pt = $wp_theme->get_post_templates();
        $post_templates = $pt['page'] ?? array();
      }
      return $post_templates;
    }

     

  • 중국 메디힐

    • 개발 기간: 2017년 8월 ~ 10월
    • 사이트 주소: http://www.medihealcos.cn/

    (더 보기…)

  • Uncode 테마 + KBoard 게시판은 성능 저하를 일으킬 수 있습니다.

    불타는 금요일을 보내고 토요일 새벽까지 디버깅을 한 결과입니다.
     Uncode 테마와 KBoard 게시판을 사용할 경우 워드프레스의 성능 저하를 유발시킬 수 있습니다. 둘을 따로따로 쓰면 문제가 없지만 같이 사용하는 경우 사이트에 로딩이 생기며, 특히 비주얼 컴포저 사용시 심각한 로딩 시간이 생기게 됩니다.

    원인은 KBoard의 세션과 Uncode 테마의 ‘list_images‘ AJAX 동작 때문에 발생합니다. KBoard의 메인 파일인 kboard/index.php 파일의 초반에는 session_start()로 세션을 시작하는 구문이 있습니다. 그리고 이 세션은 KBoard 곳곳에서 활용됩니다.

    한편 Uncode는 list_images라는 ajax 액션을 통해 uncode_list_images()라는 함수를 동작시킵니다(uncode/core/inc/admin.php: 1086). 이 함수는 무려 모든 이미지 파일을 불러와 그 이미지 파일의 용량을 계산합니다. 그리고 아래와 같은 메시지를 응답으로 넘깁니다.

    The Adaptive Images system is using 87.6M of the 6.29G space left.

    JSON 포맷이 아닌, 그냥 단순 텍스트 응답으로 날아옵니다. 이 응답이 자바스크립트 같은 곳에서 프로그래밍적으로 유의미한지는 확인하지 않았으나, 그렇게 보이지는 않습니다.

    문제는 이미지의 양이 많아질 때 터집니다. 제가 작업 중인 사이트는 이미지 파일만 1만개에 가깝게 유지하고 있습니다. 사이트의 용량만도 기가바이트급입니다. 언코드는 무식하게도 이 1만개에 육박하는 이미지를 매번 관리자 화면 로딩시 별도의 AJAX 요청을 통해 모두 쿼리로 불러와, 용량을 계산하고, 위 메시지를 출력하고 있던 것입니다.  이 계산을 위해 대략 수 초, 심하면 저의 경우처럼 30~40초까지 걸리기도 합니다.

    이제 이 상태에 KBoard가 끼어들면 문제가 정말 심각해집니다.  KBoard는 로딩될 때마다 세션을 실행합니다. 세션에는 한 번에 하나의 연결만 접근할 수 있으므로 둘 이상의 연결을 동시 처리할 수 없습니다. 그런데 언코드는 파일 용량을 계산하는 AJAX를 페이지를 부를 때마다 자동으로 실행합니다. 병목구간이 발생하는 것입니다.

    비주얼 컴포저로 각 위젯을 편집 버튼을 누르면 각 UI 정보는 AJAX를 통해 얻어 옵니다. 그러나 이미 이미지 용량 계산 때문에 다른 AJAX 요청에는 응답할 수 없는 상태입니다. 그래서 꽤 오랫동안 로딩이 걸리게 되는 것입니다. 아마 다른 페이지에 접근할 때도 마찬가지로 로딩이 심하게 발생할 수 있을 것입니다.

    문제를 해결하려면

    1. Uncode와 KBoard를 같이 쓰지 않는다.
    2. 같이 써야 한다면 Uncode의 이미지 계산하는 부분, uncode/core/inc/admin.php 파일의 1086번째 줄 부근의
      /* AJAX call to load all images */
      add_action( 'wp_ajax_list_images', 'uncode_list_images' );

       부분을 주석 처리.

    3. 코어를 건드리는 것은 부담스러우므로 차일드 테마나, 별도의 플러그인을 생성.
      add_action( 'plugins_loaded', 'my_uncode_kboard_fix' );
      
      function my_uncode_kboard_fix() {
        if( has_action( 'wp_ajax_list_images', 'uncode_list_images' ) {
          remove_action( 'wp_ajax_list_images', 'uncode_list_images' ) ;
        }
      }

    매번 이런 용량 계산을 하는 언코드도 문제지만, KBoard도 굳이 세션을 써야 했나 생각이 듭니다.

  • 기본 권한과 메타 권한

    기본 권한과 메타 권한

    이전 포스트에서 예고한 대로 이번에는 기본 권한과 메타 권한에 대해 포스팅해 보고자 합니다.

    구 워드프레스에는 회원들의 권한을 구분하기 위해 ‘레벨’이라는 개념을 사용했습니다. 예전 제로보드4 에서도 볼 수 있었던 개념입니다. 레벨 1부터 숫자가 높을 수록 권한이 낮아지는 방식의 권한 테이블 방식으로, 방법은 간단하나 썩 훌륭한 방법은 아니었습니다. 왜냐면 한 회원은 반드시 어떤 레벨에 종속되어 버리는 문제가 있고, 또 실제 운영에 있어서 권한의 높낮이보다 각기 회원의 세밀한 권한 설정도 중요할 때가 많은데, 이를 확장할 방법이 부족한 것이었죠.

    워드프레스 2.0버전부터 새로운 개념인 역할과 권한(roles and capabilities)이 생겼습니다. 이 달라진 권한 개념은 레벨이라는 개념을 완전히 버리고 새롭게 디자인된 권한 설정입니다. 그럼 역할과 권한이 어떤 특성을 가졌는지부터 간단하게 설명해 보도록 할께요.

    (더 보기…)

  • 워드프레스 권한 실습 – 포스트 권한 집중 탐구

    한때, 워드프레스는 사람들이 무료이고 쓰기 쉬워서 찾는 거지, 구식 개발이고 소스 코드도 알아보기 어렵고… 아무튼 이건 진짜 별로다. 이렇게 생각한 적이 있었습니다. 그러다가 생각이 바뀌게 된 계기가 있는데, 바로 역할과 권한(Role and Capability, 줄여서 RC) 체계를 접하게 된 것이었죠. 이후 워드프레스에 대한 평가가 긍정적으로 변했죠. 특히나 RC의 풍부한 표현은 정말 마음에 듭니다. 조금 어렵기는 해도, 사용자 레벨 같은 거와는 비교할 바가 아닙니다.

    워드프레스는 CMS입니다. 단연코 콘텐츠의 관리와 유통에 있어 전문가입니다. 잘 뜯어보면 세심하게 배려한 부분이 정말 많습니다. 개발자 입장에서 볼 때 어떤 콘텐츠를 잘 관리하고 전파할 목적이라면, 어설프게 만들지 말고 워드프레스 써라고 충고하고 싶습니다. 10여년간 그들이 쌓아 올린 콘텐츠 관리에 대한 노하우는 만만한 것이 아닙니다.

    사실 그동안 역할과 권한은 잘 쓰면 굉장히 멋진 것이 될 거라 생각은 해 왔습니다. 그러나 그 잘 쓰는 법을 여전히 잘 터득하지 못한 것 같았습니다. 물론 코덱스를 잘 읽으면 어지간한 정보는 습득할 수는 있지만 이것저것 만져보면서 직접 경험해보는 해킹(hacking)이 필요했습니다. 직접 좌충우돌하는 경험을 하고 싶었습니다. 그리고 이 포스트는 그 경험을 기록하기 위해 작성하는 것입니다. 

    (더 보기…)