---
title: "خطای CORS چیست؟ معنای Blocked by CORS Policy و راه رفع آن"
description: "خطای CORS یعنی مرورگر نگذاشت صفحه شما پاسخ یک مبدأ دیگر را بخواند. معنای پیام blocked by CORS policy و راه رفع آن را گام‌به‌گام توضیح می‌دهیم."
url: https://proxynet.io/fa/blog/cors-error
date: 2026-10-06
author: "Acar Diveroli"
category: "آموزش‌ها, وب اسکرپینگ"
lang: fa
---

# خطای CORS چیست؟ معنای Blocked by CORS Policy و راه رفع آن

فرانت‌اند شما روی `http://localhost:5173` اجرا می‌شود و API روی `http://localhost:3001`. API در curl و Postman پاسخ JSON برمی‌گرداند، اما در مرورگر `fetch()` خطای `TypeError: Failed to fetch` می‌دهد و کنسول این پیام را چاپ می‌کند: `Access to fetch at 'http://localhost:3001/api/products' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.` با این حال لاگ API نشان می‌دهد درخواست رسیده و با `200` پاسخ گرفته است.

در ادامه می‌بینید مبدأ (origin) چیست، چرا مرورگر پاسخ را مسدود می‌کند، درخواست ساده با درخواست پیش‌پرواز (preflight) چه فرقی دارد و هر پیام Chrome چه می‌گوید. سپس به راه‌حل‌های آزموده‌شده برای Express، Flask و Vite می‌رسیم، به یک مسیر سمت سرور برای APIهایی که در اختیار شما نیستند و به این پرسش که چرا کد وب اسکرپینگ هرگز با CORS روبه‌رو نمی‌شود.

> **نکته: پاسخ کوتاه**
>
> خطای CORS یعنی مرورگر پاسخ را از مبدأ دیگری دریافت کرده اما آن را به JavaScript شما نداده است، چون سرور هدر `Access-Control-Allow-Origin` را با نام مبدأ صفحه شما نفرستاده است. مبدأ ترکیب پروتکل، میزبان و پورت است؛ پس `localhost:5173` و `localhost:3001` دو مبدأ متفاوت‌اند. راه‌حل در سرور است: به مبدأ دقیق خود اجازه دهید، به درخواست‌های پیش‌پرواز `OPTIONS` پاسخ دهید و متدها و هدرهایی را که به کار می‌برید فهرست کنید. در محیط توسعه، تنظیم پروکسی سرور توسعه API را هم‌مبدأ می‌کند؛ API متعلق به دیگران را از بک‌اند خودتان فراخوانی کنید. `mode: "no-cors"`، افزونه‌های مرورگر، پروکسی‌های CORS عمومی، VPN و پروکسی این خطا را رفع نمی‌کنند.

## خطای CORS چیست؟

خطای CORS یعنی مرورگر از سپردن یک پاسخ به اسکریپت شما خودداری می‌کند. CORS، کوتاه‌شده Cross-Origin Resource Sharing (اشتراک منابع میان مبدأها)، مجموعه‌ای از هدرهای پاسخ HTTP است که سرور با آن‌ها به مرورگر می‌گوید کدام مبدأهای دیگر اجازه خواندن پاسخ‌هایش را دارند. [استاندارد Fetch](https://fetch.spec.whatwg.org/#http-cors-protocol) این سازوکار را طوری تعریف می‌کند که سرور باید صریحاً اجازه بدهد، تا داده‌هایی که پشت فایروال یا صفحه ورود هستند به‌طور پیش‌فرض به سایت‌های دیگر نشت نکنند. اگر هدرهای منطبق در کار نباشد، مرورگر قاعده پیش‌فرض خود، یعنی سیاست هم‌مبدأ (same-origin policy)، را اجرا می‌کند و جلوی خواندن را می‌گیرد.

از این تعریف سه نتیجه به دست می‌آید. لاگ‌های سرور عادی به نظر می‌رسند، چون خطا را مرورگر ساخته است. کد شما فقط یک خطای کلی دریافت می‌کند، `TypeError: Failed to fetch` از `fetch()` یا `AxiosError: Network Error` (با کد `ERR_NETWORK`) از Axios، و علت فقط در کنسول دیده می‌شود. و راه‌حل در سروری است که پاسخ می‌دهد، نه در کدی که درخواست می‌فرستد.

## مبدأ چیست و سیاست هم‌مبدأ جلوی چه چیزی را می‌گیرد؟

مبدأ ترکیب پروتکل (scheme)، میزبان و پورت یک URL است. دو URL فقط وقتی هم‌مبدأ هستند که هر سه یکی باشند:

- `http://localhost:5173` و `http://localhost:3001`: پورت‌ها متفاوت‌اند، پس مبدأها هم متفاوت‌اند.
- `http://example.com` و `https://example.com`: پروتکل‌ها متفاوت‌اند.
- `https://example.com` و `https://api.example.com`: میزبان‌ها متفاوت‌اند؛ هر زیردامنه مبدأ جداگانه‌ای است.
- `https://example.com/shop` و `https://example.com/api`: هم‌مبدأ؛ مسیر (path) به حساب نمی‌آید.

سیاست هم‌مبدأ همچنان اجازه می‌دهد صفحه تصویر، اسکریپت و فایل استایل سایت‌های دیگر را در خود جای دهد، به آن‌ها لینک بدهد و فرم به آن‌ها بفرستد. آنچه جلویش را می‌گیرد خواندن است: اسکریپت نمی‌تواند پاسخی را که از مبدأ دیگری آمده بخواند، مگر آنکه آن مبدأ اجازه دهد ([راهنمای MDN درباره سیاست هم‌مبدأ](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)). CORS راهی است که سرور با آن این اجازه را می‌دهد.

## درخواست میان‌مبدأ گام‌به‌گام چگونه کار می‌کند؟

1. صفحه شما در مبدأ A تابع `fetch()` را برای یک URL در مبدأ B فراخوانی می‌کند.
2. مرورگر بررسی می‌کند که درخواست «ساده» است یا نه. اگر نباشد، نخست یک درخواست پیش‌پرواز می‌فرستد (بخش بعد).
3. مرورگر هدر `Origin` را اضافه می‌کند، مثلاً `Origin: http://localhost:5173`. اسکریپت‌ها نمی‌توانند آن را تنظیم یا حذف کنند.
4. سرور کد خود را اجرا می‌کند و پاسخ می‌دهد. هر کاری که کد قرار بود انجام دهد، در این لحظه انجام شده است.
5. مرورگر مقدار `Access-Control-Allow-Origin` در پاسخ را با مبدأ صفحه مقایسه می‌کند. تطابق دقیق را می‌پذیرد، یا `*` را، به شرط آنکه اطلاعات ورود (credentials) مانند کوکی همراه درخواست نرفته باشد.
6. اگر تطابق باشد، پاسخ به کد شما می‌رسد؛ وگرنه مرورگر آن را دور می‌اندازد، promise رد (reject) می‌شود و کنسول علت را می‌نویسد.

گامی که اغلب نادیده گرفته می‌شود گام 4 است: CORS جلوی رسیدن درخواست به سرور را نمی‌گیرد و curl، اسکریپت‌ها و سرورهای دیگر گام 5 را اصلاً اجرا نمی‌کنند. CORS از بازدیدکنندگان محافظت می‌کند تا صفحه‌ای که باز می‌کنند نتواند با کوکی‌های خودشان داده‌هایشان را در سایت‌های دیگر بخواند؛ از API شما محافظت نمی‌کند.

## درخواست ساده و درخواست پیش‌پرواز: چرا GET کار می‌کند اما POST نه؟

درخواستی «ساده» است که از متد `GET`، `HEAD` یا `POST` استفاده کند و فقط هدرهای مجاز را داشته باشد: `Accept`، `Accept-Language`، `Content-Language`، `Range` و `Content-Type` با یکی از مقدارهای `application/x-www-form-urlencoded`، `multipart/form-data` یا `text/plain`. هر درخواست دیگری پیش‌پرواز می‌گیرد: یک درخواست `OPTIONS` با هدرهای `Access-Control-Request-Method` و `Access-Control-Request-Headers` که مرورگر فقط در صورتی پس از آن درخواست اصلی را می‌فرستد که پاسخش اجازه دهد ([راهنمای CORS در MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS)). بدنه JSON، هدر `Authorization`، متدهای `PUT`، `PATCH` و `DELETE` یا هدری سفارشی مانند `X-Request-ID` همگی پیش‌پرواز را به راه می‌اندازند.

از صفحه‌ای روی پورت 5173 یک `GET` ساده و یک `POST` با بدنه JSON به یک API روی پورت 3001 فرستادیم که هیچ هدر CORS ارسال نمی‌کند. لاگ API این بود:

```text
[no-cors-api] GET /api/products origin=http://localhost:5173
[no-cors-api] OPTIONS /api/products origin=http://localhost:5173
```

`GET` با `200` و خود داده پاسخ گرفت و مرورگر باز هم آن را مسدود کرد. از `POST` فقط پیش‌پرواز به سرور رسید؛ پیش‌پرواز شکست خورد و `POST` هرگز فرستاده نشد. اما یک `POST` میان‌مبدأ ساده، مانند ارسال فرمی با کدگذاری `application/x-www-form-urlencoded`، به سرور می‌رسد و اجرا می‌شود، هرچند صفحه خطا می‌بیند. پس از چنین عملیاتی با احراز هویت و توکن‌های CSRF محافظت کنید، نه با CORS.

هدر `Access-Control-Max-Age` به مرورگر اجازه می‌دهد پاسخ پیش‌پرواز را کش کند. مقدار پیش‌فرض در استاندارد Fetch پنج ثانیه است؛ Chromium این مقدار را حداکثر تا 2 ساعت و Firefox تا 24 ساعت می‌پذیرد.

## پیام‌های blocked by CORS policy هر کدام چه معنایی دارند؟

هر پیام Chrome با `Access to fetch at '<URL>' from origin '<origin>' has been blocked by CORS policy:` شروع می‌شود، یا برای Axios و `XMLHttpRequest` با `Access to XMLHttpRequest at`. Chromium این متن‌ها را در کد منبع خود به انگلیسی ثابت کرده است و در رابط فارسی هم به انگلیسی دیده می‌شوند. متن پس از دونقطه علت را می‌گوید. پنج ردیف نخست جدول زیر را در آزمون خود با یک مرورگر ⁦Chromium 152⁩ عیناً دیدیم؛ بقیه از کد منبع Chromium آمده‌اند.

| پیام پس از `blocked by CORS policy:` | معنا | راه‌حل |
|---|---|---|
| `No 'Access-Control-Allow-Origin' header is present on the requested resource.` | هدر CORS وجود ندارد: تنظیم نشده یا یک صفحه خطا پاسخ داده است | به مبدأ خود اجازه دهید؛ کد وضعیت واقعی را بررسی کنید |
| `Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present…` | پاسخ `OPTIONS` هدر CORS نداشت؛ درخواست اصلی فرستاده نشد | به `OPTIONS` با هدرهای CORS پاسخ دهید |
| `The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.` | کوکی فرستاده شد و سرور با `*` پاسخ داد | مبدأ دقیق به‌همراه `Access-Control-Allow-Credentials: true` |
| `Request header field x-debug is not allowed by Access-Control-Allow-Headers in preflight response.` | هدری که می‌فرستید مجاز نیست | آن را مجاز کنید یا دیگر نفرستید |
| `Method PATCH is not allowed by Access-Control-Allow-Methods in preflight response.` | متد مجاز نیست | متد را اضافه کنید |
| `The 'Access-Control-Allow-Origin' header has a value '…' that is not equal to the supplied origin.` | مبدأ دیگری مجاز شده است: پورت نادرست، `http`، اسلش پایانی | فهرست مجاز را اصلاح کنید |
| `The 'Access-Control-Allow-Origin' header contains multiple values '…', but only one is allowed.` | دو لایه هدر را اضافه می‌کنند، مثلاً برنامه و nginx | آن را فقط در یک جا تنظیم کنید |
| `Response to preflight request doesn't pass access control check: It does not have HTTP ok status.` | `OPTIONS` پاسخ `401`، `404`، `405` یا `500` گرفت | بگذارید `OPTIONS` پیش از احراز هویت عبور کند |
| `Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.` | `OPTIONS` تغییر مسیر داده شد، مثلاً به `https` یا صفحه ورود | URL نهایی را فراخوانی کنید |

نسخه‌های قدیمی‌تر Chrome جمله‌ای اضافه می‌کردند که `mode: 'no-cors'` را پیشنهاد می‌داد؛ Chromium در مارس 2025 آن را حذف کرد، چون افراد را گمراه می‌کرد (دلیلش را در پرسش‌های متداول آورده‌ایم). Firefox همین وضعیت را چنین می‌نویسد: `Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at … (Reason: CORS header 'Access-Control-Allow-Origin' missing)`.

## علت واقعی را چگونه پیدا کنید؟

ابزارهای توسعه‌دهنده مرورگر را با کلید F12 باز کنید و سطر زبانه **Console** (در رابط فارسی: **کنسول**) را بخوانید: URL، مبدأ و علت. در زبانه **Network** (**شبکه**)، ستون **Status** (**وضعیت**) برای درخواست مسدودشده عبارت `CORS error` را نشان می‌دهد (در رابط فارسی: «خطای CORS»). فراخوانی‌ای که پیش‌پرواز داشته یک ردیف جداگانه `OPTIONS` هم دارد که در ستون **Initiator** (**آغازگر**) آن `Preflight` نوشته شده است (در رابط فارسی: «پیش‌پرواز»). برای دیدن هدرها روی آن کلیک کنید.

کد وضعیت واقعی به‌آسانی از چشم می‌افتد. در آزمون ما یک دروازه (gateway) بدون هدرهای CORS پاسخ `502` داد: کنسول فقط سطر `No 'Access-Control-Allow-Origin' header` را نشان داد، در حالی که `curl -i` روی همان URL عبارت `HTTP/1.1 502 Bad Gateway` را چاپ کرد. صفحه‌های خطای nginx، متعادل‌کننده‌های بار یا برنامه‌ای که از کار افتاده به‌ندرت هدرهای CORS دارند، برای همین قطعی‌ها شبیه مشکل CORS به نظر می‌رسند. دستور `add_header` در nginx فقط روی پاسخ‌های 200، 201، 204، 206، 301، 302، 303، 304، 307 و 308 اعمال می‌شود، مگر آنکه پارامتر `always` را اضافه کنید ([مستندات nginx](https://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header)).

## اگر API مال خودتان است، خطای CORS را چگونه رفع کنید؟

هدرها را از خود API و برای مبدأهای دقیق فرانت‌اند خود بفرستید. هر قطعه کد را در 6 اکتبر 2026 با ⁦Node.js 24.11⁩، ⁦Express 5.2.1⁩، ⁦cors 2.8.6⁩، ⁦Vite 8.3.3⁩، ⁦Python 3.13⁩، ⁦Flask 3.1.3⁩ و ⁦Flask-CORS 6.0.5⁩ آزمودیم.

### Express

با `npm install express cors` نصب کنید و در `package.json` مقدار `"type": "module"` را قرار دهید.

```js
// server.js: an API that allows one front end (Express 5, cors 2.8)
import express from "express";
import cors from "cors";

const app = express();

app.use(cors({
  origin: ["https://app.example.com", "http://localhost:5173"],
  methods: ["GET", "POST", "PUT", "DELETE"],
  allowedHeaders: ["Content-Type", "Authorization"],
  credentials: true,
  maxAge: 600,
}));
app.use(express.json());

app.get("/api/products", (req, res) => {
  res.json([{ id: 1, name: "Desk lamp" }]);
});

app.post("/api/products", (req, res) => {
  res.status(201).json({ created: req.body });
});

app.listen(3000, () => console.log("API on http://localhost:3000"));
```

اگر این میان‌افزار (middleware) با `app.use()` پیش از مسیرها ثبت شود، به همه درخواست‌های پیش‌پرواز `OPTIONS` هم پاسخ می‌دهد ([مستندات میان‌افزار cors در Express](https://expressjs.com/en/resources/middleware/cors.html)). مبدأها را دقیقاً همان‌طور بنویسید که مرورگر می‌فرستد، بدون اسلش پایانی. مبدأهای دیگر هیچ هدر `Access-Control-Allow-Origin` دریافت نمی‌کنند و هدف هم همین است. `credentials: true` را فقط وقتی نگه دارید که فرانت‌اند با `credentials: "include"` کوکی بفرستد.

پیش‌پرواز را از ترمینال بررسی کنید:

```bash
curl -i -X OPTIONS http://localhost:3000/api/products \
  -H "Origin: http://localhost:5173" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: content-type"
```

سطرهای مربوط از اجرای ما:

```text
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:5173
Vary: Origin
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET,POST,PUT,DELETE
Access-Control-Allow-Headers: Content-Type,Authorization
Access-Control-Max-Age: 600
```

`Vary: Origin` به کش‌ها و CDNها می‌گوید پاسخ به هدر `Origin` بستگی دارد. با `Origin: https://evil.example`، همان دستور باز هم `204` برمی‌گرداند، اما بدون سطر `Access-Control-Allow-Origin`.

### Flask

با `pip install flask flask-cors` نصب کنید.

```python
# app.py: the same API in Flask 3 with Flask-CORS 6
from flask import Flask, jsonify, request
from flask_cors import CORS

app = Flask(__name__)
CORS(
    app,
    resources={r"/api/*": {"origins": ["https://app.example.com", "http://localhost:5173"]}},
    allow_headers=["Content-Type", "Authorization"],
    supports_credentials=True,
    max_age=600,
)

@app.get("/api/products")
def list_products():
    return jsonify([{"id": 1, "name": "Desk lamp"}])

@app.post("/api/products")
def create_product():
    return jsonify({"created": request.get_json()}), 201

if __name__ == "__main__":
    app.run(port=5000)
```

در اجرای ما پاسخ پیش‌پرواز `200` بود و مبدأ، متدها و `Vary: Origin` را داشت و `POST` صفحه پاسخ `201` گرفت. همراه `supports_credentials=True` همیشه فهرستی از مبدأها بدهید: بدون آن، ⁦Flask-CORS 6.0.5⁩ یک مبدأ دلخواه، یعنی `https://evil.example`، را همراه `Access-Control-Allow-Credentials: true` به ما برگرداند.

### nginx، دروازه‌ها و CDNها

اگر به‌جای برنامه، یک ریورس پروکسی هدرها را تنظیم می‌کند، به هر `add_header` پارامتر `always` را بدهید تا پاسخ‌های خطا هم آن‌ها را داشته باشند، و بگذارید فقط یک لایه آن‌ها را تنظیم کند. وقتی اطلاعات ورود را مجاز کرده‌اید، هرگز هر `Origin` ورودی را عیناً در پاسخ کپی نکنید: در آن صورت هر وب‌سایتی می‌تواند با کوکی‌های کاربران شما داده‌هایشان را بخواند. مبدأها را با یک فهرست ثابت مقایسه کنید.

## محیط توسعه: درخواست‌های API را از سرور توسعه عبور دهید

در محیط توسعه، ساده‌ترین راه‌حل این است که API را هم‌مبدأ کنید. سرور توسعه Vite می‌تواند هر مسیری را که با `/api` شروع می‌شود به API برساند:

```js
// vite.config.js: in development, /api goes through Vite to the API
import { defineConfig } from "vite";

export default defineConfig({
  server: {
    port: 5173,
    proxy: {
      "/api": {
        target: "http://localhost:3000",
        changeOrigin: true,
      },
    },
  },
});
```

فرانت‌اند `fetch("/api/products")` را بدون نام میزبان فراخوانی می‌کند. مرورگر مبدأ خودش را می‌بیند، پس بررسی CORS اجرا نمی‌شود و Vite درخواست را به پورت 3000 می‌رساند؛ در آزمون ما `GET` پاسخ `200` و `POST` پاسخ `201` گرفت. گزینه `changeOrigin` هدر `Host` را برابر مقصد قرار می‌دهد. webpack-dev-server همین کار را با `devServer.proxy` انجام می‌دهد. در محیط عملیاتی، فرانت‌اند و API را از طریق وب‌سرور خود زیر یک مبدأ ارائه کنید یا همان تنظیم CORS بالا را نگه دارید.

## وقتی API مال شما نیست: آن را از بک‌اند خود فراخوانی کنید

API شخص ثالثی که هدر CORS ندارد معمولاً برای سرورها ساخته شده است، اغلب به این دلیل که کلید محرمانه آن هرگز نباید به مرورگر برسد. یک مسیر به بک‌اند خود اضافه کنید: مرورگر مبدأ شما را فراخوانی می‌کند و سرور شما API را.

```js
// relay.js: your backend calls the third-party API; the browser only talks to you
import express from "express";

const app = express();
const PARTNER_URL = "https://api.partner.example/v1/products";

app.get("/api/partner-products", async (req, res) => {
  try {
    const r = await fetch(PARTNER_URL, {
      headers: { Authorization: `Bearer ${process.env.PARTNER_API_KEY}` },
      signal: AbortSignal.timeout(10_000),
    });
    res.status(r.status).type(r.headers.get("content-type") ?? "application/json");
    res.send(await r.text());
  } catch (err) {
    res.status(502).json({ error: "partner API unreachable", detail: err.cause?.code ?? err.name });
  }
});

app.listen(8080, () => console.log("relay on http://localhost:8080"));
```

این مسیر را با یک سرور محلی آزمودیم که جای API شریک را گرفته بود و هدر CORS نمی‌فرستاد: JSON با `200` برگشت. مسیر را از مبدأ صفحه ارائه کنید؛ کلید در یک متغیر محیطی روی سرور می‌ماند. کار با هدرها، بدنه درخواست و احراز هویت با `fetch` سمت سرور و Axios در [معادل cURL در JavaScript](/fa/blog/curl-in-javascript) توضیح داده شده است.

مقصد را ثابت نگه دارید. مسیری که هر آدرسی را که در `?url=` می‌رسد فراخوانی کند، سرور شما را به یک پروکسی باز تبدیل می‌کند که هر کسی می‌تواند از آن استفاده کند، حتی علیه آدرس‌های داخلی‌ای که فقط سرور شما به آن‌ها دسترسی دارد. شرایط استفاده و محدودیت نرخ API همچنان برقرار است: جای فراخوانی عوض شده، قواعد نه.

## چرا پروکسی‌های CORS عمومی خطرناک‌اند؟

یک پروکسی CORS عمومی URL را به‌جای شما فراخوانی می‌کند و `Access-Control-Allow-Origin: *` را به پاسخ اضافه می‌کند. خطا ناپدید می‌شود، اما:

- گرداننده آن URL کامل، همه هدرها از جمله کلیدهای API و توکن‌ها، و خود پاسخ را می‌بیند.
- می‌تواند پاسخ را تغییر دهد و صفحه شما به آن اعتماد می‌کند.
- درخواست‌های کاربران شما از شرکتی می‌گذرد که هیچ قراردادی با آن ندارید.
- محدودیت‌های نرخ و قطعی‌های آن به مشکل شما تبدیل می‌شود.

## CORS و وب اسکرپینگ: چرا اسکریپت Python یا Node.js شما هرگز با آن روبه‌رو نمی‌شود؟

CORS فقط در مرورگرها وجود دارد. همان API را که مرورگر مسدود کرده بود، این بار از ⁦Node.js 24⁩ فراخوانی کردیم:

```js
// The same request from Node.js: no browser, no CORS check
const r = await fetch("http://localhost:3001/api/products");
console.log(r.status, r.headers.get("access-control-allow-origin"), await r.text());
```

```text
200 null [{"id":1,"name":"Desk lamp"}]
```

هدر `Access-Control-Allow-Origin` وجود ندارد و Node.js باز هم پاسخ را می‌خواند؛ کتابخانه Requests در Python و curl هم همین رفتار را دارند. اگر می‌کوشید با `fetch()` در کنسول مرورگر یا یک برنامه فرانت‌اند داده جمع کنید، خطای CORS به شما می‌گوید جای این کار در کد سمت سرور است. اینکه کدام زبان برای آن مناسب‌تر است در [اسکرپینگ وب: JavaScript یا Python؟](/fa/blog/web-scraping-javascript-vs-python) مقایسه شده است. قواعد سایت آنجا هم برقرار است: از `robots.txt` پیروی کنید، سرعت درخواست‌ها را پایین نگه دارید و اگر API رسمی وجود دارد از آن استفاده کنید.

پروکسی خطای CORS را رفع نمی‌کند. مرورگر مبدأ صفحه را با هدر `Access-Control-Allow-Origin` مقایسه می‌کند و آدرس IP بخشی از این بررسی نیست؛ پس پروکسی مسکونی (رزیدنتال)، پروکسی دیتاسنتر یا VPN چیزی را تغییر نمی‌دهد. جای پروکسی در کلاینت HTTP کد سمت سرور شماست، همان‌طور که در [استفاده از پروکسی در Node.js](/fa/blog/nodejs-proxy) نشان داده‌ایم.

برای جمع‌آوری مجاز داده‌ای که به نتایج محلی در کشورها و شهرهای مختلف نیاز دارد، [پروکسی مسکونی](https://proxynet.io/fa/residential-proxy) درخواست‌ها را با هدف‌گیری کشور و شهر از طریق اتصال‌های خانگی می‌فرستد.

برای درخواست‌های پرحجم به APIهای عمومی و صفحه‌هایی با حفاظت سبک، [پروکسی دیتاسنتر](https://proxynet.io/fa/datacenter-proxy) آدرس‌های ⁦IPv4⁩ اختصاصی با ترافیک بدون سهمیه ارائه می‌دهد.

## چه کسانی با خطای CORS روبه‌رو می‌شوند؟

- توسعه‌دهندگان فرانت‌اند که برنامه و API آن‌ها روی پورت‌های متفاوت اجرا می‌شود.
- برنامه‌های تک‌صفحه‌ای که یک API شخص ثالث را مستقیم از مرورگر فراخوانی می‌کنند.
- تیم‌هایی که API را به زیردامنه‌ای مانند `api.example.com` یا به `https` منتقل می‌کنند.
- فرانت‌اندهایی که با ابزارهای هوش مصنوعی تولید شده‌اند و API را مستقیم فراخوانی می‌کنند، گاهی با کلیدی که درون صفحه گذاشته شده است.

## اشتباه‌های رایج

- **افزودن `Access-Control-Allow-Origin` به درخواست.** این یک هدر پاسخ است؛ در `fetch()` به هدری سفارشی تبدیل می‌شود که خودش پیش‌پرواز را به راه می‌اندازد.
- **استفاده از `mode: "no-cors"`.** پاسخ مات (opaque) برمی‌گردد: کد وضعیت `0` و بدنه‌ای که نمی‌توان آن را خواند.
- **افزونه‌های allow CORS یا خاموش کردن امنیت وب مرورگر.** فقط مرورگر خود شما را تغییر می‌دهد، و چنین افزونه‌ای می‌تواند صفحه‌هایی را که روی آن‌ها اجرا می‌شود بخواند.
- **پاسخ `*` در حالی که فرانت‌اند کوکی می‌فرستد.** مرورگر این ترکیب را رد می‌کند.
- **`app.options("*", cors())` در ⁦Express 5⁩.** برنامه هنگام راه‌اندازی با `PathError [TypeError]: Missing parameter name at index 1: *` متوقف می‌شود؛ `app.use(cors())` خودش به پیش‌پروازها پاسخ می‌دهد.
- **میان‌افزار احراز هویتی که `OPTIONS` را رد می‌کند.** درخواست‌های پیش‌پرواز توکن ندارند، پس با خطای `It does not have HTTP ok status` شکست می‌خورند.

## راهنمای انتخاب

| وضعیت | چه کنید |
|---|---|
| فرانت‌اند و API در محیط توسعه روی پورت‌های متفاوت‌اند | پروکسی سرور توسعه (`server.proxy` در Vite) |
| API خودتان که از فرانت‌اند خودتان فراخوانی می‌شود | مبدأهای دقیق را مجاز کنید؛ به `OPTIONS` پاسخ دهید |
| فرانت‌اند کوکی می‌فرستد | مبدأ دقیق، `Access-Control-Allow-Credentials: true`، `Vary: Origin` |
| پیام `is not allowed` برای یک هدر یا متد | آن را به `Access-Control-Allow-Headers` یا `-Methods` اضافه کنید |
| خطا پس از استقرار (deploy) یا زیر بار ظاهر می‌شود | کد وضعیت واقعی را با curl بررسی کنید؛ `always` در nginx |
| API شخص ثالث بدون هدر CORS | از بک‌اند خود فراخوانی کنید و کلید را روی سرور نگه دارید |
| جمع‌آوری داده از سایت‌های دیگر | کد سمت سرور؛ CORS آنجا اعمال نمی‌شود |

## پرسش‌های متداول

### چرا Postman یا curl کار می‌کند اما مرورگر خطای CORS می‌دهد؟

فقط مرورگرها CORS را اجرا می‌کنند. سرور به همه کلاینت‌ها همان پاسخ را می‌فرستد و فقط مرورگر پیش از سپردن آن به اسکریپت، `Access-Control-Allow-Origin` را بررسی می‌کند. فراخوانی موفق curl ثابت می‌کند API کار می‌کند، نه اینکه مرورگر اجازه خواندن پاسخ آن را دارد.

### چرا روی localhost خطای CORS می‌گیرم؟

پورت متفاوت یعنی مبدأ متفاوت: `localhost:5173` و `localhost:3000` دو مبدأ روی یک دستگاه‌اند. در API به مبدأ فرانت‌اند اجازه دهید یا از پروکسی سرور توسعه استفاده کنید. جدا از این، از ⁦Chrome 142⁩ به بعد، وب‌سایتی عمومی که `localhost` یا دستگاهی در شبکه خانگی شما را فراخوانی کند به اجازه شما نیاز دارد؛ اگر اجازه ندهید، کنسول از رد شدن دسترسی به فضای آدرس `loopback` یا `local` خبر می‌دهد.

### آیا ⁦mode: "no-cors"⁩ خطای CORS را رفع می‌کند؟

نه. درخواست فرستاده می‌شود، اما مرورگر یک پاسخ مات برمی‌گرداند؛ در آزمون ما کد وضعیت `0` بود، نوع پاسخ `opaque` و بدنه خواندنی نبود. این حالت فقط برای درخواست‌هایی مناسب است که هرگز پاسخشان را نمی‌خوانید.

### آیا CORS یک سازوکار امنیتی برای API من است؟

نه. CORS جلوی این را می‌گیرد که یک سایت با کوکی‌های بازدیدکننده داده‌های او را در سایت دیگری بخواند، اما هر کسی همچنان می‌تواند API شما را با یک اسکریپت فراخوانی کند. از API با احراز هویت، کنترل دسترسی (authorization) و محدودیت نرخ محافظت کنید.

### آیا پروکسی یا VPN خطای CORS را رفع می‌کند؟

نه. مرورگر مبدأ صفحه را با هدر `Access-Control-Allow-Origin` در پاسخ مقایسه می‌کند؛ آدرس IP بخشی از این بررسی نیست.

### چرا فقط درخواست POST من شکست می‌خورد اما GET کار می‌کند؟

`POST` احتمالاً به خاطر بدنه JSON، هدر `Authorization` یا یک هدر سفارشی به پیش‌پرواز نیاز دارد. اگر API به درخواست `OPTIONS` درست پاسخ ندهد، مرورگر هرگز `POST` را نمی‌فرستد. در زبانه Network دنبال ردیف `OPTIONS` بگردید یا پیش‌پرواز را با curl بفرستید.

## خلاصه

خطای CORS یعنی مرورگر حاضر نیست پاسخ مبدأ دیگری را به اسکریپت شما بدهد؛ درخواست معمولاً به سرور رسیده است. علت را در کنسول بخوانید، کد وضعیت واقعی را با curl بررسی کنید و مشکل را در سرور رفع کنید: مبدأهای دقیق، پاسخ به `OPTIONS`، متدها و هدرهای درست و `Vary: Origin`. در زمان توسعه از پروکسی سرور توسعه و برای APIهایی که در اختیارتان نیست از بک‌اند خودتان استفاده کنید. جمع‌آوری داده جایش در کد سمت سرور است، جایی که CORS اعمال نمی‌شود؛ انواع پروکسی برای این کار در صفحه [خدمات پروکسی ما](/fa/proxy) آمده‌اند.
