Własne endpointy REST w WordPressie: walidacja, wyświetlanie i wydajność

Management Summary
Własny interfejs danych pozwala stronie lub aplikacji WordPress pobierać informacje potrzebne do konkretnej funkcji. Interfejs oznacza tu ustalony adres wymiany danych zamiast całej strony internetowej. Może uprościć odpowiedzi i ograniczyć zbędną pracę, gdy standardowe rozwiązania nie pasują. Określa również, kto może odczytywać lub zmieniać informacje. Korzyść wynika z jasnego celu, kontrolowanego dostępu i niezawodnego działania, a nie wyłącznie z mniejszej odpowiedzi.
Spis treści
- Niewielki endpoint z jasno określonymi polami odpowiedzi
- 1. Sprawdzenie istniejącego API przed dodaniem trasy
- 2. Rejestracja, walidacja i ograniczenie żądania
- 3. Adres endpointu generowany przez WordPressa
- 4. Wyświetlanie danych jako tekstu
- 5. Osobne decyzje o cache i uwierzytelnianiu
- 6. Weryfikacja poprawności i rzeczywistego kosztu
- Pytania i odpowiedzi
- Źródła
Niewielki endpoint z jasno określonymi polami odpowiedzi
Endpoint REST to adres, pod którym program pobiera dane. Własny endpoint WordPressa może zwracać dokładnie pola potrzebne elementowi strony, np. liście najnowszych wpisów. Dzięki temu format odpowiedzi jest ustalony, a zakres pracy przy każdym żądaniu ograniczony. Sama zamiana admin-ajax.php na REST nie gwarantuje określonego czasu odpowiedzi.
Obie ścieżki uruchamiają WordPressa oraz odpowiedni kod wtyczek i motywu. admin-ajax.php dodatkowo ładuje kod administracyjny i wykonuje admin_init. Koszt zależy od callbacków, bazy, cache i obciążenia serwera. „400–600 ms wobec poniżej 50 ms” oraz „60–80% szybciej” wymagają pomiarów; takie liczby nie są właściwością REST.

1. Sprawdzenie istniejącego API przed dodaniem trasy
Dla zwykłej listy wpisów dostępny jest utrzymywany endpoint wp/v2/posts. Ograniczone per_page oraz wybrane _fields mogą zmniejszyć odpowiedź. Własna trasa ma sens przy odmiennych wymaganiach dotyczących danych. Dodatkowe trasy wymagają też reguł dostępu, testów i utrzymania.
Poniższy przykład zwraca najnowsze metadane wpisów, a nie logi. Przykład dydaktyczny zakłada, że tytuły i daty wybranych opublikowanych wpisów bez hasła mają być jawnie publiczne. Ograniczenia członkostwa, wybór języka i inne zasady widoczności wymagają kontroli konkretnej integracji przed publicznym udostępnieniem. Ani WP_Query, ani pomyślny callback uprawnień nie wdraża automatycznie wszystkich polityk innych wtyczek.
2. Rejestracja, walidacja i ograniczenie żądania
Rejestracja powinna następować w rest_api_init. Przestrzeń nazw, np. lw-toolbox/v1, oddziela trasę od innych wtyczek. Rejestracja w innym miejscu jest niezgodna z przewidzianym użyciem i może wywołać ostrzeżenie; odpowiedź 404 w każdym takim przypadku nie jest jednak gwarantowana.
Reguła limit akceptuje wyłącznie zwykłe dziesiętne liczby całkowite od 1 do 100. Ułamki, zapis wykładniczy, tablice, zero i wartości ujemne są odrzucane. Walidacja określa dopuszczalne wejście, a następnie konwersja tworzy liczbę całkowitą. Walidacja nie jest autoryzacją ani zamiennikiem parametryzowanych zapytań SQL.
add_action( 'rest_api_init', function () {
register_rest_route( 'lw-toolbox/v1', '/latest-posts', array(
'methods' => WP_REST_Server::READABLE,
// Public data only: no private logs or membership content.
'permission_callback' => '__return_true',
'args' => array(
'limit' => array(
'default' => 10,
'validate_callback' => function ( $value ) {
return ( is_string( $value ) || is_int( $value ) )
&& preg_match( '/\A(?:[1-9][0-9]?|100)\z/', (string) $value ) === 1;
},
'sanitize_callback' => function ( $value ) {
return (int) $value;
},
),
),
'callback' => function ( WP_REST_Request $request ) {
$query = new WP_Query( array(
'post_type' => 'post',
'post_status' => 'publish',
'has_password' => false,
'posts_per_page' => $request->get_param( 'limit' ),
'orderby' => array( 'date' => 'DESC', 'ID' => 'DESC' ),
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
) );
$items = array();
foreach ( $query->posts as $post ) {
$items[] = array(
'id' => (int) $post->ID,
'title' => (string) $post->post_title,
'date_gmt' => (string) $post->post_date_gmt,
);
}
$response = new WP_REST_Response( $items, 200 );
$response->header( 'Cache-Control', 'no-store' );
return $response;
},
) );
} );Kod należy do niewielkiej wtyczki przeznaczonej dla witryny lub odpowiedniego pliku motywu potomnego; nowy plik PHP wymaga otwierającego znacznika PHP. Publiczny callback uprawnień jest świadomą decyzją o dostępie. Prywatne logi, dane konta i treści ograniczone wymagają odpowiedniego sprawdzenia uprawnienia lub dostępu do konkretnego obiektu.
no_found_rows pomija zbędne zapytanie o całkowitą liczbę wyników. Wstępne zapełnianie cache metadanych i taksonomii jest wyłączone, ponieważ odpowiedź nie używa tych pól. Zwykły cache zapytań pozostaje dostępny. WP_Query nie jest z definicji powolny, a bezpośredni SQL nie jest z definicji szybszy. SQL może omijać filtry zapytań i reguły widoczności wtyczek. Gdy własna tabela rzeczywiście wymaga SQL, wartości powinny trafiać do placeholderów $wpdb->prepare(), a identyfikatory pochodzić z zaufanej konfiguracji.
Przykład zwraca niewielką odpowiedź sukcesu, również pustą listę przy braku wyników. Nie wdraża pełnego monitoringu: błędy bazy wymagają ograniczonego dostępu do monitoringu serwera oraz ustalonej polityki błędów. Odpowiedzi błędów nie mogą ujawniać zapytań SQL ani danych logowania. Odrzucony parametr REST zapobiega zapytaniu tego callbacku, a nie całej pracy bazy podczas uruchamiania WordPressa.
3. Adres endpointu generowany przez WordPressa
Stały host i ścieżka /wp-json/ w katalogu głównym mogą zawieść przy instalacji w podkatalogu lub innych ustawieniach odnośników. Poniższy kontener należy do szablonu PHP, nie zwykłego bloku HTML w edytorze. rest_url() wyznacza adres, a esc_url() przygotowuje wartość atrybutu HTML. Trzy komunikaty stanu odpowiadają językowi strony.
<div id="lw-fast-data-box" role="status"
data-endpoint="<?php echo esc_url( rest_url( 'lw-toolbox/v1/latest-posts' ) ); ?>"
data-loading="Ładowanie..." data-empty="Brak wpisów."
data-error="Nie udało się wczytać wpisów."></div>4. Wyświetlanie danych jako tekstu
JavaScript może zostać załadowany raz jako zewnętrzny skrypt interfejsu. Obsługuje ładowanie, pustą listę, błędy HTTP, niepoprawne dane oraz dziesięciosekundowy limit czasu po stronie klienta. Publiczne żądanie celowo pomija dane uwierzytelniające. Brak treści żądania oznacza brak potrzeby wysyłania nagłówka JSON Content-Type.
async function loadLatestPosts() {
const box = document.getElementById('lw-fast-data-box');
if (!box) return;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
box.textContent = box.dataset.loading;
try {
const url = new URL(box.dataset.endpoint, document.baseURI);
url.searchParams.set('limit', '5');
const response = await fetch(url, {
method: 'GET', credentials: 'omit', cache: 'no-store',
headers: { Accept: 'application/json' },
signal: controller.signal
});
if (!response.ok) throw new Error('HTTP ' + response.status);
const items = await response.json();
if (!Array.isArray(items) || items.length > 5 || items.some(item =>
!item || !Number.isInteger(item.id) ||
typeof item.title !== 'string' || typeof item.date_gmt !== 'string'
)) throw new Error('Invalid response');
if (!items.length) {
box.textContent = box.dataset.empty;
return;
}
const list = document.createElement('ul');
for (const item of items) {
const li = document.createElement('li');
const title = document.createElement('strong');
title.textContent = item.title;
li.append(title, document.createTextNode(' (' + item.date_gmt + ' UTC)'));
list.append(li);
}
box.replaceChildren(list);
} catch (error) {
box.textContent = box.dataset.error;
} finally {
clearTimeout(timeout);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', loadLatestPosts, { once: true });
} else {
loadLatestPosts();
}textContent traktuje tytuł jako tekst, także przy nawiasach ostrych. Wstawianie tytułu z bazy do innerHTML umożliwiałoby interpretację HTML. Walidacja limit nie zabezpiecza takiego miejsca wyświetlania. Surowy format tytułu jest tutaj świadomy: encje HTML zapisane w tytule mogą pojawić się dosłownie. Ewentualne dekodowanie wymaga osobnej, przetestowanej reguły prezentacji.
5. Osobne decyzje o cache i uwierzytelnianiu
Przykład stosuje no-store konsekwentnie do pustych i niepustych odpowiedzi sukcesu. To ostrożna polityka początkowa, nie optymalizacja wydajności. Współdzielony cache wymaga dodatkowego projektu: public, max-age=60 pasuje tylko do świadomie wspólnej reprezentacji, z właściwymi kluczami i unieważnianiem. Ciasteczka, autoryzacja, język, parametry i konfiguracja proxy wpływają na tę decyzję. private zabrania zapisu we współdzielonym cache, lecz dopuszcza zapis w przeglądarce. no-store nakazuje brak zapisu odpowiedzi. Żaden nagłówek nie zapewnia kontroli dostępu.
Przy uwierzytelnianiu WordPressa przez ciasteczko wymagany jest również poprawny nonce wp_rest, zwykle w nagłówku X-WP-Nonce. Chroniony endpoint nadal musi sprawdzać odpowiednie uprawnienie. Nonce nie jest hasłem API ani zamiennikiem autoryzacji. Klienty zewnętrzne mogą korzystać z obsługiwanych metod, np. haseł aplikacji przez HTTPS; dane logowania nie powinny trafiać do kodu przeglądarki.
6. Weryfikacja poprawności i rzeczywistego kosztu
- Test domyślnego limitu, 1, 100, 0, 101, ułamków, zapisu wykładniczego i tablic. Niepoprawne wartości powinny otrzymać 400 przed uruchomieniem callbacku endpointu.
- Kontrola pustej listy, tytułów ze znacznikami, błędnego JSON, błędów HTTP i limitu czasu klienta. Test rzeczywistego podkatalogu i ustawień odnośników.
- Kontrola treści nieopublikowanych, zabezpieczonych hasłem i ograniczonych członkostwem oraz wszystkich języków i dostępu anonimowego lub uwierzytelnionego.
- Sprawdzenie nagłówków na serwerze źródłowym i za rzeczywistym proxy. Wielokrotne pomiary zimnych i ciepłych żądań, czasu serwera, liczby zapytań, pamięci, TTFB i typowej równoległości. Porównanie przy identycznym zakresie danych i regułach dostępu.
Użyteczny raport wydajności podaje środowisko testowe oraz rozrzut czasów odpowiedzi. Mediana to środkowy wynik pomiaru; 95% zmierzonych czasów odpowiedzi nie przekracza 95. percentyla. Pojedyncza szybka odpowiedź lub mniejszy JSON nie dowodzi ogólnej gwarancji opóźnienia.
Pytania i odpowiedzi
Dlaczego zalogowana przeglądarka może nie przejść kontroli uprawnień?
Uwierzytelnianie REST przez ciasteczko wymaga dodatkowo nonce REST. Bez tego nonce WordPress traktuje odpowiednie żądanie z ciasteczkiem jako anonimowe. Błędny nonce i brak uprawnienia są różnymi błędami; końcowy status zależy także od metody uwierzytelniania i callbacku uprawnień.
Czy REST pomija całą pracę wtyczek?
Nie. Kosztowne działania podczas zwykłego uruchamiania WordPressa mogą obciążać także REST. Profilowanie identyfikuje istotne callbacki przed decyzją o zmianie architektury.
Czy callback uprawnień automatycznie zapewnia właściwą publikację danych?
Nie. Zwrócenie true jawnie pozwala na dostęp. Wybrane pola i rekordy nadal wymagają świadomej polityki publikacji, obejmującej reguły innych wtyczek.