<style>
body, p, li, table {
font-family: "Arial", "MingLiU";
}
h1, h2, h3, h4, h5, h6 {
font-family: "Arial", "Microsoft YaHei";
}
h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
font-family: "Consolas";
}
pre, code {
font-family: "MingLiU";
white-space: pre-wrap !important;
word-break: break-all !important;
}
.markdown-body pre {
margin-bottom: 3px;
padding: 10px 12px 6px 12px;
}
.markdown-body h6 code {
font-size: 9pt;
font-family: 'Arial';
font-weight: normal;;
}
.div-font {
font-family: "Arial", "MingLiU";
}
pre.fit {
margin-top: -1em;
margin-bottom: 0.5em;
}
pre.inline {
display: inline;
padding: 0.2em 0;
margin: 0;
line-height: 1.5;
}
pre.inline::before, pre.inline::after {
content: "";
display: inline-block;
width: 0.3em;
}
pre.code-block {
margin: -1em 0em 0.25em;
font-size: 0.85em;
}
.markdown-body p+pre {
margin-top: -12px;
padding-top: 6px;
margin-bottom: 0px;
}
code.inpara {
display: inline-block;
margin: 0.5em 0;
}
span.spnote {
font-size: 0.85em;
color: #f43;
}
span.spnote.add {
color: #43f;
}
span.spnote a {
color: #912;
}
span.refsrc {
display: block;
margin-top: -0.5em;
margin-left: 1.5em;
font-size: 0.75em;
font-family: "Arial", "MingLiU";
}
.sp {
color: #FF4000 !important;
}
a.private-note {
color: indianred;
}
a.private-note:hover {
color: firebrick;
}
.date-notes {
font-size: 9pt;
font-style: italic;
color: gray;
margin-bottom: 1em;
}
.lfis {
margin-left: 1.5rem;
}
.lfid {
margin-left: 2rem;
}
.smn {
font-size: 0.8em;
color: mediumslateblue;
margin-right: 0.33em;
}
.ano {
font-size: 0.9em;
color: #f43;
}
i.emph {
color: #d66;
}
i.empho {
color: #93c;
}
.left-indent {
margin-left: 2em;
}
strong, .emphasis {
font-family: "Arial", "Microsoft YaHei";
font-weight: bold;
}
strong code {
font-family: "Consolas", "Microsoft YaHei";
}
.alert {
margin-bottom: 0;
}
.alert-success a {
color: #40b142;
}
.alert-warning a {
color: #c19953;
}
.alert-danger a {
color: #dc625f;
}
h6 div.timestamp {
margin-top: 1rem;
}
</style>
# 前端 CORS (使用 Fetch API) 與後端 (PHP) 設定
###### Tags: `JavaScript` `FetchAPI` `CORS` `PHP`
## 前端程式碼示例
example.js
```javascript
fetch('http://example.url/example/route', {
method: 'POST',
credentials: 'include',
headers: new Headers({
'Custom-Header': encodeURIComponent('A custom message'), // 可傳輸非 ISO-8859-1 字元
'Custom-Cookie': document.cookie
})
})
.then(response => {
if (response.ok) return response.json();
throw new Error(`${response.url} (${response.statusText})`);
})
.then(async data => {
console.log(data);
// 將後端回應的 Base64 資料解碼再轉回 UTF-8
let message = decodeURIComponent(window.atob(data.message));
console.log(message);
})
.catch(error => {
console.warn(error);
});
```
## 後端程式碼示例
http://example.url/example/route
```php
<?php
# 白名單
$allowedHost = [
'61.62.63.64',
'101.102.103.104',
'185.186.187.188'
];
# 取得請求發送者的 IP
$clientIP = (function()
{
$ip = null;
if (isset($_SERVER['HTTP_CLIENT_IP']) && $_SERVER['HTTP_CLIENT_IP'] !== '')
{
$ip = $_SERVER['HTTP_CLIENT_IP'];
}
else if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] !== '')
{
$ip = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0];
}
else if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] !== '')
{
$ip = $_SERVER['REMOTE_ADDR'];
}
return trim($ip);
})();
# 帶微秒的請求時間
$requestData = (function()
{
$time = explode('.', $_SERVER['REQUEST_TIME_FLOAT']);
$date = date('Y-m-d H:i:s', $time[0]);
return "{$date}.{$time[1]}";
})();
# 限定接受的 HTTP 方法為 OPTIONS(預檢請求)及 POST(自定義),其他方法回 404
switch ($_SERVER['REQUEST_METHOD'])
{
case 'OPTIONS':
case 'POST':
{
# 宣告 CORS 預檢請求(Preflight Request)所需要的 header
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS')
{
header("Access-Control-Request-Method: GET, POST, PUT, PATCH, DELETE, OPTIONS");
header('Access-Control-Allow-Headers: Content-Type, Custom-Header, Custom-Cookie');
}
# 允許 CORS 認證
header('Access-Control-Allow-Credentials: true');
# 連入 IP 不在白名單內,返回 401
if (!in_array($clientIP, $allowedHost))
{
header("{$_SERVER['SERVER_PROTOCOL']} 401 Unauthorized");
exit;
}
# 請求來自瀏覽器時,在回應 header 中加入允許來源
if (isset($_SERVER['HTTP_ORIGIN']))
{
header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
}
# 預檢 OPTIONS 動作完畢,跳出 switch case
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') break;
# 回應資料陣列
$data = [];
# 請求 header 中有 Custom-Header 時,將其值 base64 編碼處理後加入為回應資料的 message 欄位
if (isset($_SERVER['HTTP_CUSTOM_HEADER']))
{
$data['message'] = base64_encode($_SERVER['HTTP_CUSTOM_HEADER']);
}
# 取得請求 header 中帶的 Custom-Cookie
$customCookie = [];
if (isset($_SERVER['HTTP_CUSTOM_COOKIE']))
{
$customCookiePair = explode(';', $_SERVER['HTTP_CUSTOM_COOKIE']);
foreach ($customCookiePair as $cookiePair)
{
$cookieSlice = explode('=', $cookiePair);
if (trim($cookieSlice[0]) !== '')
{
$customCookie[trim($cookieSlice[0])] = trim($cookieSlice[1]);
}
}
}
$data['customCookie'] = $customCookie;
# 通知瀏覽器建立 cookie(須指定 SameSite=None; Secure 選項)
setcookie('lastAccessDate', $requestDate, [
'samesite' => 'None',
'secure' => true
]);
# 將 SERVER 資訊加入回應資料以便檢視
$data = array_merge($data, ['server' => $_SERVER]);
# 將 COOKIE 資訊加入回應資料以便檢視
$data = array_merge($data, ['cookie' => $_COOKIE]);
header('Content-Type: application/json');
echo json_encode($data, 320);
break;
}
default:
header("{$_SERVER['SERVER_PROTOCOL']} 404 Not Found");
break;
}
exit;
```