尽管已将源设置为“*”,但尝试从托管在 AWS 上的环回服务器获取数据时为什么会出现 CORS 错误?
我正在尝试使用以下代码从我的远程托管环回服务器获取数据到一个简单的 html 页面:
const headers = {
'Content-Type': 'application/json',
'api-target': 'rest'
};
fetch("https://nightli-staging-server.link/public-guest-lists/1", {
headers: headers
})
.then(response => response.json())
.then(data => {
const widget = document.getElementById("widget");
widget.innerHTML = JSON.stringify(data);
});
但是,我收到以下 CORS 错误:
Access to fetch at 'https://nightli-staging-server.link/public-guest-lists/1' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
我在我的index.ts文件中设置origin为“*”,用于启动环回服务器。这是 index.ts 文件的相关部分:
const corsOptions = {
origin: '*',
credentials: true,
exposedHeaders: ['Access-Control-Allow-Origin', 'Access-Control-Allow-Headers'],
};
expressApp.use(cors(corsOptions));
为什么我仍然遇到 CORS 错误?还有什么可能导致此问题,我该如何解决?
从远程托管的环回服务器发布相关文件(应该有帮助):
index.ts:
import aws from 'aws-sdk';
import cors from 'cors';
import express, {NextFunction} from 'express';
import {graphqlHTTP} from 'express-graphql';
import {createServer as createServerHttp} from 'http';
import {createGraphQLSchema, Oas3} from 'openapi-to-graphql';
import qs from 'qs';
import {ApplicationConfig, NightliBackendApplication} from './application';
export * from './application';
export async function main(options: ApplicationConfig = {}) {
const expressApp = express();
let url = `${process.env.HOST}:${process.env.PORT ?? 3000}`;
const app = new NightliBackendApplication(options);
await app.boot();
await app.start();
url = app.restServer.url!;
console.log(`Server is running at ${url}`);
console.log(`Connected to database ${process.env.DB_DATABASE}`);
const oas = await app.restServer.getApiSpec();
const graphResult = await createGraphQLSchema(oas as Oas3, {
strict: false,
viewer: false,
singularNames: true,
fillEmptyResponses: true,
baseUrl: url,
headers: {
'X-Origin': 'GraphQL',
},
tokenJSONpath: '$.jwt',
});
const {schema} = graphResult;
const corsOptions = {
origin: '*',
credentials: true,
exposedHeaders: ['Access-Control-Allow-Origin', 'Access-Control-Allow-Headers'],
headers: ['Content-Type', 'api-target']
};
expressApp.use(cors(corsOptions));
expressApp.use(express.json({limit: '50mb'}));
expressApp.use(express.urlencoded({limit: '50mb'}));
// For AWS ALB health checks
expressApp.get('/', (req, res) => {
res.sendStatus(200);
});
expressApp.use(
'/graphql',
// eslint-disable-next-line @typescript-eslint/no-misused-promises
graphqlHTTP(request => ({
schema,
graphiql: true,
context: {
jwt: request.headers.authorization?.replace(/^Bearer /, ''),
},
})),
);
aws.config.update({
accessKeyId: process.env.AWS_ACCESS_KEY,
secretAccessKey: process.env.AWS_SECRET_KEY,
region: process.env.AWS_REGION,
});
const server = createServerHttp(expressApp);
const graphqlPort = process.env.GRAPHQL_PORT ?? 3001;
server.listen(graphqlPort, () => {
console.log(
`GraphQL is running at http://${process.env.HOST}:${graphqlPort}/graphql`,
);
});
return app;
}
if (require.main === module) {
// Run the application
const config: ApplicationConfig = {
rest: {
port: +(process.env.PORT ?? 3000),
host: process.env.HOST,
// The `gracePeriodForClose` provides a graceful close for http/https
// servers with keep-alive clients. The default value is `Infinity`
// (don't force-close). If you want to immediately destroy all sockets
// upon stop, set its value to `0`.
// See https://www.npmjs.com/package/stoppable
gracePeriodForClose: 5000, // 5 seconds
cors: {
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
preflightContinue: true,
credentials: true,
allowedHeaders: 'Content-Type, api-target',
exposedHeaders: ['Access-Control-Allow-Origin', 'Access-Control-Allow-Headers'],
// Add a function to log when an OPTIONS request is received
optionsSuccess: function(req:Request, res:Response, next:NextFunction) {
const origin = req.headers.get('origin');
console.log("in optionsSucces!")
console.log("req", req)
if (origin) {
console.log('Received OPTIONS request from:', origin);
} else {
console.log('Received OPTIONS request without Origin header.');
}
next();
}
},
openApiSpec: {
// useful when used with OpenAPI-to-GraphQL to locate your application
setServersFromRequest: true,
},
expressSettings: {
'query parser': function (value: string, option: any) {
return qs.parse(value, {
arrayLimit: 800,
});
},
},
},
};
console.log("CORS config:", config.rest.cors);
main(config).catch(err => {
console.error('Cannot start the application.', err);
process.exit(1);
});
}
调用端点的控制器:
import {inject} from '@loopback/core';
import {
Request,
RestBindings,
SchemaObject,
get,
param,
post,
requestBody,
response
} from '@loopback/rest';
import {ServerResponse} from 'http';
import {EventItemServiceBindings, GuestListServiceBindings} from '../keys';
import {CreatePublicGuestlist, GetPublicGuestListSchema, messageSchemaResponse} from '../schemas';
import {IEventItemService, IGuestListService} from '../services';
import {PublicGuestListPayload, PublicGuestListType} from '../types';
export class PublicGuestListController {
constructor(
@inject(GuestListServiceBindings.GUEST_LIST_SERVICE)
public guestListService: IGuestListService,
@inject(EventItemServiceBindings.EVENT_ITEM_SERVICE)
public eventItemService: IEventItemService,
) { }
@post('/public-guest-lists/{clubId}')
@response(200, {
description: 'Create public guest and add to public guest list',
content: {'application/json': {schema: messageSchemaResponse}},
})
async createPublicGuestList(
@param.path.number('clubId') clubId: number,
@requestBody({
content: {
'application/json': {
schema: CreatePublicGuestlist as SchemaObject,
},
},
})
payload: PublicGuestListPayload,
@inject(RestBindings.Http.REQUEST)
request: Request,
@inject(RestBindings.Http.RESPONSE) response: ServerResponse,
): Promise<{message: string}> {
await this.guestListService.addToPublicGuestList(clubId, payload);
// Set Access-Control-Allow-Origin header to allow all connections
response.setHeader('Access-Control-Allow-Origin', '*');
return {message: 'Request sent'};
}
@get('/public-guest-lists/{clubId}')
@response(200, {
description: 'Get public guest lists (event_items) for club',
content: {'application/json': {schema: GetPublicGuestListSchema}},
})
async getPublicGuestLists(
@param.path.number('clubId') clubId: number,
@inject(RestBindings.Http.RESPONSE) response: ServerResponse,
): Promise<PublicGuestListType[]> {
const publicGuestLists = await this.eventItemService.getPublicEventItemsByClub(clubId);
// Set Access-Control-Allow-Origin header to allow all connections
response.setHeader('Access-Control-Allow-Origin', '*');
return publicGuestLists;
}
}
sequence.ts 文件:
import {MiddlewareSequence} from '@loopback/rest';
export class MySequence extends MiddlewareSequence {}
application.ts 文件:
import {AuthenticationComponent} from '@loopback/authentication';
import {
AuthorizationBindings,
AuthorizationComponent,
AuthorizationDecision,
} from '@loopback/authorization';
import {BootMixin} from '@loopback/boot';
import {ApplicationConfig} from '@loopback/core';
import {CronComponent} from '@loopback/cron';
import {RepositoryMixin} from '@loopback/repository';
import {RestApplication} from '@loopback/rest';
import {
RestExplorerBindings,
RestExplorerComponent,
} from '@loopback/rest-explorer';
import {ServiceMixin} from '@loopback/service-proxy';
import path from 'path';
import {BindingComponent} from './bindings';
import {PostgresqlDataSource} from './datasources';
import {UserServiceBindings} from './keys';
import {MySequence} from './sequence';
export {ApplicationConfig};
export class NightliBackendApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
constructor(options: ApplicationConfig = {}) {
super(options);
// Set up the custom sequence
this.sequence(MySequence);
// Set up default home page
this.static('/', path.join(__dirname, '../public'));
// Customize @loopback/rest-explorer configuration here
this.configure(RestExplorerBindings.COMPONENT).to({
path: '/explorer',
});
this.component(RestExplorerComponent);
// Mount authentication system
this.component(AuthenticationComponent);
// Mount authorization system
this.configure(AuthorizationBindings.COMPONENT).to({
precedence: AuthorizationDecision.DENY,
defaultDecision: AuthorizationDecision.DENY,
});
this.component(AuthorizationComponent);
// Mount jwt component
this.component(BindingComponent);
// Cron job component
this.component(CronComponent);
// Bind datasource
this.dataSource(PostgresqlDataSource, UserServiceBindings.DATASOURCE_NAME);
this.projectRoot = __dirname;
// logger
// const fileTransport: DailyRotateFile = new DailyRotateFile({
// level: 'error',
// filename: 'logs/application-%DATE%.log',
// datePattern: 'YYYY-MM-DD',
// format: combine(label({label: 'Error log!'}), timestamp(), errorFormat),
// zippedArchive: true,
// maxSize: '20m',
// maxFiles: '14d',
// });
// this.bind('logging.winston.transports.file')
// .to(fileTransport)
// .apply(extensionFor(WINSTON_TRANSPORT));
// this.configure(LoggingBindings.COMPONENT).to({
// enableFluent: false,
// enableHttpAccessLog: true,
// });
// this.component(LoggingComponent);
// Customize @loopback/boot Booter Conventions here
this.bootOptions = {
controllers: {
// Customize ControllerBooter Conventions here
dirs: ['controllers'],
extensions: ['.controller.js'],
nested: true,
},
};
}
}
我通过向浏览器中的获取请求添加“api-target:rest”标头来确认端点正常运行。这会覆盖预检请求标头并绕过 CORS 限制,从而成功接收预期的响应正文。
更新:
在 AWS S3 存储桶上托管客户端时,我得到了这个 CORS 错误:
“可以从来源‘https://nightli-widget.s3.eu-north-1.amazonaws.com’获取‘https://nightli-staging-server.link/public-guest-lists/1’已被 CORS 策略阻止:对预检请求的响应未通过访问控制检查:它没有 HTTP 正常状态。”
我现在还在 index.ts 文件中包含了 console.logs
optionsSuccess: function(req:Request, res:Response, next:NextFunction) {
const origin = req.headers.get('origin');
console.log("in optionsSucces!")
console.log("req", req)
if (origin) {
console.log('Received OPTIONS request from:', origin);
} else {
console.log('Received OPTIONS request without Origin header.');
}
next();
}
}
控制台中没有显示任何日志。