코어 라우트로 안 되는 일이 생깁니다. 여러 리소스를 합쳐 한 번에 주고 싶거나, 우리 도메인의 동사(주문 취소 · 진단 시작)를 표현해야 할 때입니다. 그때 register_rest_route() 를 씁니다. 등록은 반드시 rest_api_init 안에서 합니다 — 그보다 이르면 REST 서버가 아직 없습니다.
네임스페이스는 버전을 포함합니다
네임스페이스는 벤더/버전 형태로 씁니다. wper/v1 처럼요. 버전이 붙어 있어야 응답 형태를 바꿔야 하는 날 v2 를 나란히 열고 기존 소비자를 깨뜨리지 않은 채 옮길 수 있습니다. 버전 없는 네임스페이스는 첫 변경에서 반드시 후회합니다.
벤더 부분은 프로젝트마다 고유해야 합니다. 두 플러그인이 같은 네임스페이스에 라우트를 등록하면 나중에 등록한 쪽이 조용히 무시됩니다.
add_action( 'rest_api_init', 'wper_register_routes' );
function wper_register_routes() {
register_rest_route(
'wper/v1',
'/orders/(?P<id>d+)',
[
'methods' => WP_REST_Server::READABLE,
'callback' => 'wper_get_order',
// 필수 인자다. 빠뜨리면 라우트는 공개되고 코어가 경고를 남긴다.
'permission_callback' => function () {
return current_user_can( 'edit_posts' );
},
'args' => [
'id' => [
'type' => 'integer',
'required' => true,
'sanitize_callback' => 'absint',
],
'detail' => [
'type' => 'string',
'enum' => [ 'summary', 'full' ],
'default' => 'summary',
],
],
]
);
}
permission_callback 은 선택 인자가 아닙니다
워드프레스 5.5 부터 permission_callback 이 없는 라우트는 _doing_it_wrong() 경고를 냅니다. 경고의 문구가 친절해서 오해를 낳는데, 경고가 났다고 라우트가 막히는 것은 아닙니다. 그 라우트는 그대로 공개 상태로 돕니다. 디버그가 꺼진 프로덕션에서는 경고조차 보이지 않습니다.
그래서 규약은 이렇습니다 — 공개 라우트를 만들 생각이면 '__return_true' 를 명시적으로 씁니다. 코드를 읽는 사람이 “빠뜨린 것” 과 “일부러 연 것” 을 구별할 수 있게 하기 위해서입니다. 웹훅 수신 라우트가 바로 그 경우이고, 5회차에서 따로 다룹니다.
콜백은 true · false · WP_Error 를 돌려줄 수 있습니다. false 를 돌려주면 코어가 상태 코드를 알아서 고릅니다 — 로그인하지 않았으면 401, 로그인했는데 권한이 모자라면 403 입니다. 직접 고르려면 WP_Error 를 돌려줍니다.
검증은 스키마가 하고, 권한 콜백보다 먼저 돕니다
args 에 타입 · 필수 여부 · enum · default · sanitize_callback 을 적으면 코어가 요청을 핸들러에 닿기 전에 검증하고 정제합니다. 핸들러 안에서 if ( ! isset( … ) ) 를 늘어놓지 않아도 되고, 실패 응답의 형태도 코어 라우트와 같아집니다.
순서를 알아 두면 쓸모가 있습니다. 인자 검증이 먼저이므로 권한 콜백은 이미 정제된 요청을 받습니다 — 콜백 안에서 $request['id'] 를 그대로 믿고 소유권을 조회해도 됩니다. 반대로 잘못된 인자를 보내면 권한을 보기 전에 400 이 나가므로, “권한이 없는데 왜 400 이지” 같은 혼동이 생길 수 있습니다.
핸들러는 WP_REST_Response 또는 WP_Error 를 돌려줍니다. 배열을 돌려줘도 되지만 rest_ensure_response() 를 거치면 나중에 헤더를 붙일 자리가 생깁니다.
function wper_get_order( WP_REST_Request $request ) {
$order = get_post( $request['id'] );
if ( ! $order || 'wper_order' !== $order->post_type ) {
return new WP_Error( 'wper_order_not_found', '주문을 찾을 수 없습니다.', [ 'status' => 404 ] );
}
return rest_ensure_response( [
'id' => $order->ID,
'status' => $order->post_status,
] );
}
이런 계층을 다른 사람의 사이트 위에 얹을 때 우리가 밟는 절차는 작업 과정에 그대로 적어 두었고, 설계 전반의 배경은 개발 워크플로우 아카이브에 있습니다.
다음 회차
라우트를 만들었으면 “누가 부르는가” 를 정해야 합니다. 다음 회차는 인증 — 쿠키와 nonce, 애플리케이션 비밀번호, 토큰이 각각 어떤 상황을 위한 것이고 위협 모델이 어떻게 다른지입니다.