[WordPress] HTTP API 教學 – 外掛串接外部資源的正確方式

本篇文章更新時間:2026/03/03
如有資訊過時或語誤之處,歡迎使用 Contact 功能通知或向一介資男的 LINE 社群反應。
如果本站內容對你有幫助,歡迎贊助支持


本系列文參考自 WordPress.org 官方外掛開發文件 - HTTP API 的繁體中文版本,並加入作者實務開發經驗補充。

WordPress HTTP API 是外掛開發中與外部服務溝通的標準介面。無論你要串接第三方金流、取得社群平台資料、或是與自家後端 API 互動,都應該透過 WordPress 內建的 HTTP API 來發送請求。這套 API 封裝了 PHP 底層各種 HTTP 傳輸方式(cURL、fopen、fsockopen 等),自動選擇當前伺服器環境中最適合的方法執行請求,讓開發者只需要關注業務邏輯,而不用擔心底層相容性問題。本文將從 WordPress 為何規定必須使用 HTTP API 開始,逐步介紹 GET、POST 請求的使用方式、回應處理、進階設定,以及實務上的快取與錯誤處理策略。

為什麼要使用 HTTP API - 不能直接用 cURL 的原因

很多 PHP 開發者的第一直覺是直接使用 curl_init()file_get_contents() 或其他原生 PHP 函式來發送 HTTP 請求。但在 WordPress 外掛開發中,這是被明確禁止的做法——如果你的外掛要上架到 WordPress.org,審核團隊會因為直接使用 cURL 而退回你的外掛。

WordPress.org 外掛審核指南明確要求:所有 HTTP 請求必須使用 WordPress HTTP API(即 wp_remote_get()wp_remote_post() 等函式)。理由如下:

  • 伺服器相容性:並非所有主機都安裝了 cURL 擴充。WordPress HTTP API 會自動偵測可用的傳輸方式,確保在任何環境都能正常運作。
  • 安全性一致:WordPress HTTP API 內建 SSL 驗證、重導向限制、逾時控制等安全機制,使用統一的 API 才能確保這些防護一致生效。
  • 可過濾性:透過 HTTP API 發出的請求,其他外掛可以透過 pre_http_requesthttp_response 等 Filter 進行攔截或修改。直接使用 cURL 會完全繞過 WordPress 的 Hook 系統。
  • 測試友善:在單元測試中,可以透過 filter 模擬 HTTP 回應,而不需要實際發送網路請求。如果直接使用 cURL,測試時就必須依賴真實的外部服務。
// ❌ 錯誤做法 - 直接使用 cURL(外掛審核不會通過)
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, 'https://api.example.com/data' );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
$result = curl_exec( $ch );
curl_close( $ch );

// ✅ 正確做法 - 使用 WordPress HTTP API
$response = wp_remote_get( 'https://api.example.com/data' );
$body     = wp_remote_retrieve_body( $response );

簡單來說,只要你的程式碼是在 WordPress 環境中執行,就應該使用 HTTP API。這不只是規範問題,更是品質保證。

GET 請求 - wp_remote_get()

wp_remote_get() 是最常使用的 HTTP API 函式,用於從外部 URL 取得資料。它接受兩個參數:

/**
 * 發送 GET 請求
 *
 * @param string $url  目標 URL
 * @param array  $args 選用參數(timeout, headers, cookies 等)
 * @return array|WP_Error 回應陣列或 WP_Error 物件
 */
$response = wp_remote_get( $url, $args );

預設參數值如下:

  • method - GET
  • timeout - 5(秒)
  • redirection - 5(最多跟隨幾次重導向)
  • httpversion - 1.0
  • blocking - true(是否等待回應完成才繼續執行)
  • headers - array()
  • body - null
  • cookies - array()

以下是一個完整的實務範例,從外部 API 取得 JSON 資料並解析:

/**
 * 從 GitHub API 取得使用者公開資訊
 *
 * @param string $username GitHub 使用者名稱
 * @return array|WP_Error 解析後的使用者資料或錯誤
 */
function myplugin_get_github_user( $username ) {
    $url = sprintf( 'https://api.github.com/users/%s', sanitize_text_field( $username ) );

    $response = wp_remote_get( $url, array(
        'timeout' => 10,
        'headers' => array(
            'Accept' => 'application/vnd.github.v3+json',
        ),
    ) );

    // 檢查是否為 WP_Error(網路錯誤、DNS 解析失敗等)
    if ( is_wp_error( $response ) ) {
        return $response;
    }

    // 檢查 HTTP 狀態碼
    $status_code = wp_remote_retrieve_response_code( $response );
    if ( 200 !== $status_code ) {
        return new WP_Error(
            'github_api_error',
            sprintf( 'GitHub API 回傳非預期的狀態碼:%d', $status_code )
        );
    }

    // 取得並解析回應內容
    $body = wp_remote_retrieve_body( $response );
    $data = json_decode( $body, true );

    if ( json_last_error() !== JSON_ERROR_NONE ) {
        return new WP_Error( 'json_parse_error', 'JSON 解析失敗' );
    }

    return $data;
}

// 使用範例
$user_data = myplugin_get_github_user( 'developer-name' );
if ( is_wp_error( $user_data ) ) {
    error_log( 'GitHub API 錯誤:' . $user_data->get_error_message() );
} else {
    echo '使用者名稱:' . esc_html( $user_data['name'] );
    echo '公開 Repo 數:' . intval( $user_data['public_repos'] );
}

這個範例展示了標準的 API 呼叫流程:發送請求 → 檢查錯誤 → 驗證狀態碼 → 解析回應。接下來的章節會詳細說明每個步驟的回應處理方式。

POST 請求 - wp_remote_post()

wp_remote_post() 用於向外部 API 發送資料,函式簽名與 wp_remote_get() 完全相同,差別在於 HTTP 方法為 POST,且通常需要透過 body 參數傳送資料:

/**
 * 發送 POST 請求到外部 API
 *
 * @param string $url  目標 URL
 * @param array  $args 請求參數(body, headers 等)
 * @return array|WP_Error 回應陣列或 WP_Error 物件
 */
$response = wp_remote_post( $url, $args );

以下是一個向外部 API 送出表單資料的範例:

/**
 * 送出聯絡表單資料到外部 CRM API
 *
 * @param array $form_data 表單資料
 * @return bool|WP_Error 成功回傳 true,失敗回傳 WP_Error
 */
function myplugin_submit_to_crm( $form_data ) {
    $response = wp_remote_post( 'https://api.example-crm.com/v1/contacts', array(
        'timeout' => 15,
        'headers' => array(
            'Content-Type'  => 'application/json',
            'Authorization' => 'Bearer ' . get_option( 'myplugin_crm_api_key' ),
        ),
        'body' => wp_json_encode( array(
            'name'    => sanitize_text_field( $form_data['name'] ),
            'email'   => sanitize_email( $form_data['email'] ),
            'subject' => sanitize_text_field( $form_data['subject'] ),
            'message' => sanitize_textarea_field( $form_data['message'] ),
        ) ),
    ) );

    if ( is_wp_error( $response ) ) {
        return $response;
    }

    $status_code = wp_remote_retrieve_response_code( $response );

    if ( $status_code >= 200 && $status_code < 300 ) {
        return true;
    }

    $body = wp_remote_retrieve_body( $response );
    return new WP_Error(
        'crm_api_error',
        sprintf( 'CRM API 回傳錯誤(%d):%s', $status_code, $body )
    );
}

幾個重要注意事項:

  • body 傳入陣列時,WordPress 會自動以 application/x-www-form-urlencoded 格式編碼(類似標準 HTML 表單送出)。伺服器端可透過 $_POST 讀取。
  • 如果 API 要求 JSON 格式,需要手動設定 Content-Typeapplication/json,並用 wp_json_encode() 將 body 編碼為 JSON 字串。
  • API 金鑰等敏感資訊應該儲存在 wp_options 中,透過 get_option() 讀取,切勿寫死在程式碼裡。

除了 GET 和 POST 之外,WordPress 也提供了 wp_remote_head() 用於只取得回應標頭而不下載內容(適合檢查資源是否存在或是否有更新),以及 wp_remote_request() 可以指定任意 HTTP 方法(PUT、DELETE、PATCH 等),適用於 RESTful API 的完整操作:

// 使用 HEAD 請求檢查資源是否存在
$response = wp_remote_head( 'https://api.example.com/resource/123' );
$status   = wp_remote_retrieve_response_code( $response );

// 使用 DELETE 方法刪除遠端資源
$response = wp_remote_request( 'https://api.example.com/resource/123', array(
    'method'  => 'DELETE',
    'headers' => array(
        'Authorization' => 'Bearer ' . $api_token,
    ),
) );

// 使用 PUT 方法更新遠端資源
$response = wp_remote_request( 'https://api.example.com/resource/123', array(
    'method'  => 'PUT',
    'headers' => array(
        'Content-Type'  => 'application/json',
        'Authorization' => 'Bearer ' . $api_token,
    ),
    'body' => wp_json_encode( array( 'status' => 'published' ) ),
) );

回應處理 - retrieve 函式與錯誤檢查

WordPress HTTP API 的所有請求函式(wp_remote_get()wp_remote_post() 等)都回傳相同的資料結構:一個包含完整回應資訊的陣列,或者在連線失敗時回傳 WP_Error 物件。WordPress 提供了一組 retrieve 輔助函式來安全地從回應中取得所需資訊:

is_wp_error() - 檢查是否發生錯誤

在處理回應之前,必須先檢查請求是否成功。is_wp_error() 用於判斷回傳值是否為 WP_Error 物件。會觸發 WP_Error 的情況包括:DNS 解析失敗、連線逾時、SSL 憑證問題等網路層面的錯誤。

$response = wp_remote_get( 'https://api.example.com/data' );

if ( is_wp_error( $response ) ) {
    $error_message = $response->get_error_message();
    $error_code    = $response->get_error_code();
    error_log( "HTTP 請求失敗 [{$error_code}]:{$error_message}" );
    return false;
}

wp_remote_retrieve_response_code() - 取得 HTTP 狀態碼

即使請求本身成功了(沒有回傳 WP_Error),伺服器也可能回傳 4xx 或 5xx 的錯誤狀態碼:

$status_code = wp_remote_retrieve_response_code( $response );

if ( 200 !== $status_code ) {
    // 依據不同狀態碼做不同處理
    switch ( $status_code ) {
        case 401:
        case 403:
            error_log( 'API 認證失敗,請檢查 API 金鑰' );
            break;
        case 404:
            error_log( '請求的資源不存在' );
            break;
        case 429:
            error_log( 'API 請求頻率超過限制' );
            break;
        case 500:
        case 503:
            error_log( '外部 API 伺服器錯誤' );
            break;
        default:
            error_log( "未預期的狀態碼:{$status_code}" );
    }
    return false;
}

wp_remote_retrieve_body() - 取得回應內容

取得回應的 body 內容。大多數 API 回傳的是 JSON 格式,需要進一步解析:

$body = wp_remote_retrieve_body( $response );
$data = json_decode( $body, true );

// 驗證 JSON 解析結果
if ( null === $data && json_last_error() !== JSON_ERROR_NONE ) {
    error_log( 'JSON 解析失敗:' . json_last_error_msg() );
    return false;
}

wp_remote_retrieve_headers() - 取得回應標頭

取得全部或特定的回應標頭:

// 取得所有回應標頭
$headers = wp_remote_retrieve_headers( $response );

// 取得特定標頭
$content_type = wp_remote_retrieve_header( $response, 'content-type' );
$rate_limit   = wp_remote_retrieve_header( $response, 'x-ratelimit-remaining' );

// 實務應用:根據 Rate Limit 標頭控制請求頻率
if ( $rate_limit !== '' && intval( $rate_limit ) < 10 ) {
    error_log( '警告:API Rate Limit 即將耗盡,剩餘 ' . $rate_limit . ' 次' );
}

完整的回應處理範本

以下是一個可以在實務中直接套用的標準回應處理流程:

/**
 * 標準 API 請求處理範本
 *
 * @param string $endpoint API 端點
 * @return array|WP_Error 解析後的資料或錯誤
 */
function myplugin_api_request( $endpoint ) {
    $response = wp_remote_get( $endpoint, array(
        'timeout' => 10,
        'headers' => array(
            'Accept' => 'application/json',
        ),
    ) );

    // 第一層:檢查網路層面錯誤
    if ( is_wp_error( $response ) ) {
        return $response;
    }

    // 第二層:檢查 HTTP 狀態碼
    $status_code = wp_remote_retrieve_response_code( $response );
    if ( $status_code < 200 || $status_code >= 300 ) {
        return new WP_Error(
            'api_http_error',
            sprintf( 'API 回傳 HTTP %d', $status_code ),
            array( 'status' => $status_code )
        );
    }

    // 第三層:解析回應內容
    $body = wp_remote_retrieve_body( $response );
    $data = json_decode( $body, true );

    if ( null === $data && json_last_error() !== JSON_ERROR_NONE ) {
        return new WP_Error( 'api_json_error', 'JSON 解析失敗:' . json_last_error_msg() );
    }

    return $data;
}

進階設定 - timeout、headers、sslverify 與認證

WordPress HTTP API 的第二個參數 $args 允許你精細控制請求的各種行為。以下是最常用的進階設定:

timeout - 連線逾時設定

預設的 timeout 是 5 秒。對於大多數 API 呼叫來說這是合理的,但某些情況下你可能需要調整:

// 較慢的 API 或大量資料下載,延長 timeout
$response = wp_remote_get( $url, array(
    'timeout' => 30, // 最多等待 30 秒
) );

// 非關鍵的背景請求,縮短 timeout 避免阻塞
$response = wp_remote_get( $url, array(
    'timeout' => 3,
) );

注意:在 WordPress 的 AJAX 或 Cron 請求中,PHP 本身可能有更短的執行時間限制。設定過長的 timeout 不一定有效,需要同時考慮 max_execution_time 的限制。

headers - 自訂請求標頭

許多 API 要求傳送特定的 Header,常見的用途包括認證、指定回應格式、API 版本控制等:

$response = wp_remote_get( $url, array(
    'headers' => array(
        'Accept'        => 'application/json',      // 期望的回應格式
        'Authorization' => 'Bearer ' . $api_token,  // Bearer Token 認證
        'X-API-Version' => '2.0',                   // API 版本
        'User-Agent'    => 'MyPlugin/1.0',           // 自訂 User-Agent
    ),
) );

sslverify - SSL 驗證

sslverify 預設為 true,這是正確且安全的預設值。在正式環境中絕對不應該關閉 SSL 驗證。但在本地開發環境中,自簽憑證可能會導致請求失敗,此時可以暫時關閉:

// ⚠️ 僅在本地開發環境使用,正式環境絕不關閉
$response = wp_remote_get( $url, array(
    'sslverify' => defined( 'WP_DEBUG' ) && WP_DEBUG ? false : true,
) );

重要:如果你在正式環境遇到 SSL 驗證錯誤,正確的處理方式是修正伺服器的 CA 憑證設定,而不是關閉驗證。WordPress 會使用自帶的 CA bundle 檔案(位於 wp-includes/certificates/ca-bundle.crt)進行驗證。

常見的認證方式

不同的 API 使用不同的認證機制,以下是最常見的三種:

// 方式一:Basic Authentication(帳號密碼)
$response = wp_remote_get( $url, array(
    'headers' => array(
        'Authorization' => 'Basic ' . base64_encode( $username . ':' . $password ),
    ),
) );

// 方式二:Bearer Token(OAuth 2.0 常用)
$response = wp_remote_get( $url, array(
    'headers' => array(
        'Authorization' => 'Bearer ' . $access_token,
    ),
) );

// 方式三:API Key 放在 Header
$response = wp_remote_get( $url, array(
    'headers' => array(
        'X-API-Key' => $api_key,
    ),
) );

// 方式四:API Key 放在 Query String
$url_with_key = add_query_arg( 'api_key', $api_key, $url );
$response     = wp_remote_get( $url_with_key );

blocking - 非阻塞請求

如果你只需要「發出去」但不關心回應結果(例如觸發遠端 webhook 或記錄事件),可以將 blocking 設為 false

// 非阻塞請求:發送後立即繼續執行,不等待回應
wp_remote_post( 'https://webhook.example.com/event', array(
    'blocking' => false,
    'body'     => array(
        'event' => 'order_completed',
        'order_id' => $order_id,
    ),
) );

使用非阻塞請求時,回應內容會是空的,你無法得知請求是否成功。只有在確定不需要處理回應時才使用此設定。

實務建議

作者實務經驗分享:

1. 善用 Transients API 快取 API 回應:外部 API 呼叫是效能瓶頸的主要來源之一。每次頁面載入都發送 HTTP 請求不僅拖慢網站速度,也容易觸發 API 的 Rate Limit。使用 WordPress 的 Transients API 將 API 回應快取一段時間,是最基本也最有效的優化策略:

function myplugin_get_external_data() {
    // 先檢查快取
    $cached = get_transient( 'myplugin_api_data' );
    if ( false !== $cached ) {
        return $cached;
    }

    // 快取不存在或已過期,重新請求
    $response = wp_remote_get( 'https://api.example.com/data', array(
        'timeout' => 10,
    ) );

    if ( is_wp_error( $response ) ) {
        return $response;
    }

    $data = json_decode( wp_remote_retrieve_body( $response ), true );
    if ( null === $data ) {
        return new WP_Error( 'parse_error', '資料解析失敗' );
    }

    // 寫入快取,有效期 1 小時
    set_transient( 'myplugin_api_data', $data, HOUR_IN_SECONDS );

    return $data;
}

2. 建立統一的錯誤處理模式:在外掛中統一封裝 API 呼叫,建立一致的錯誤處理與日誌記錄機制。不要在每個呼叫點各自處理錯誤,而是建立一個共用的 API Client 類別或函式。這樣不僅減少重複程式碼,也讓除錯更容易。上面的「標準 API 請求處理範本」就是一個好的起點。

3. API 金鑰的安全管理:將 API 金鑰儲存在 wp_options 中,透過後台設定頁面讓管理員輸入,並在顯示時遮蔽部分字元。絕對不要將 API 金鑰寫死在原始碼中,也不要將包含金鑰的設定檔提交到版本控制系統。更多安全性的實務建議,請參考「WordPress 安全性完整教學」。

4. 處理外部服務不可用的情況:外部 API 隨時可能故障。你的外掛應該優雅地處理這種情況——顯示快取的舊資料、顯示友善的錯誤訊息,或者提供降級功能。絕對不要因為外部 API 無回應就讓整個頁面輸出 PHP 錯誤訊息。

5. 注意 timeout 對使用者體驗的影響:如果 API 呼叫發生在前台頁面的渲染流程中,5 秒的 timeout 就意味著使用者可能要多等 5 秒才能看到頁面。盡量將 API 呼叫放在背景處理(如 WP-Cron),前台只讀取快取資料。

6. 請求前先做資料驗證與清理:所有傳送到外部 API 的資料都應該先經過 WordPress 的 sanitize 函式處理,所有從外部 API 接收的資料在輸出前都應該先經過 esc 函式跳脫。外部 API 的回傳資料等同於不可信的使用者輸入,千萬不要直接輸出。


本文是「WordPress 外掛開發完整指南」系列的第 11 篇。

上一篇:[WordPress] 使用者管理與角色權限開發教學

下一篇:[WordPress] WP-Cron 排程任務開發教學


Share:

作者: Chun

WordPress 社群貢獻者、開源社群推廣者。專注於 WordPress 外掛開發、網站效能最佳化、伺服器管理,以及 iDempiere 開源 ERP 導入與客製開發。曾參與 WordCamp Taipei 等社群活動,GitHub Arctic Code Vault Contributor。提供資訊顧問、WordPress 開發教學、主機最佳化與企業 ERP 整合服務。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *


文章
Filter
Apply Filters
Mastodon