腾讯云SCF + 腾讯云API网关实现跨域
- 2019 年 11 月 1 日
- 笔记
跨域介绍
跨来源资源共享(Cross-Origin Resource Sharing(CORS))是一种使用额外 HTTP 标头来让目前浏览网站的 user agent 能获得访问不同来源(网域)服务器特定资源之权限的机制。当 user agent 请求一个不是目前文件来源——来自于不同网域(domain)、通信协定(protocol)或通信端口(port)的资源时,会建立一个跨来源HTTP请求(cross-origin HTTP request)。
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域:
|
当前页面 url |
被请求页面 url |
是否跨域 |
原因 |
|---|---|---|---|
|
http://www.example.com/ |
http://www.example.com/index.html |
否 |
同源(协议、域名、端口号相同) |
|
http://www.example.com/ |
https://www.example.com/index.html |
跨域 |
协议不同(http/https) |
|
http://www.example.com/ |
http://www.baidu.com/ |
跨域 |
主域名不同(example/baidu) |
|
http://www.example.com/ |
http://blog.example.com/ |
跨域 |
子域名不同(www/blog) |
|
http://www.example.com:8080/ |
http://www.example.com:7001/ |
跨域 |
端口号不同(8080/7001) |
跨域种类
一共有 2 种跨域请求:
- 简单请求
- 预检请求
简单请求
当 HTTP 请求出现以下两种情况时,浏览器认为是简单跨域请求:
- 请求方法是 GET、HEAD 或者 POST,并且当请求方法是 POST 时,Content-Type 必须是 application/x-www-form-urlencoded, multipart/form-data 或着 text/plain 中的一个值。
- 请求中没有自定义 HTTP 头部。
对于简单跨域请求,浏览器要做的就是在 HTTP 请求中添加 Origin Header,将 JavaScript 脚本所在域填充进去,向其他域的服务器请求资源。服务器端收到一个简单跨域请求后,根据资源权限配置,在响应头中添加 Access-Control-Allow-Origin Header。浏览器收到响应后,查看 Access-Control-Allow-Origin Header,如果当前域已经得到授权,则将结果返回给 JavaScript。否则浏览器忽略此次响应。相较于同源请求,CORS 简单请求会在头信息中额外增加一个 Origin 字段。
预检请求
当 HTTP 请求出现以下两种情况时,浏览器认为是带预检(Preflighted)的跨域请求:
- 除 GET、HEAD 和 POST(only with application/x-www-form-urlencoded, multipart/form-data, text/plain Content-Type)以外的其他 HTTP 方法。
- 请求中出现自定义 HTTP 头部。
非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为"预检"请求(preflight)。
预检(preflighted)请求会先用 HTTP 的 OPTIONS 方法请求另一个域名资源,确认后续实际(actual)请求能否可安全送出。由于跨域请求可能会携带使用者的信息,所以要先进行预检请求。
腾讯云SCF + 腾讯云API 网关实现跨域
当 SCF 绑定 API 网关触发器后,有 2 种方式实现跨域**(建议使用第 1 种方法)**:
- 借助 API 网关的跨域功能
- 云函数中实现跨域逻辑
本文就来介绍下,如果通过这 2 种方式,来实现跨域功能。建议选择第 1 种方式,来实现跨域功能,这样用户就不需要在函数中实现跨域相关的逻辑代码。
借助 API 网关的跨域功能
Step1. 绑定 API 网关触发器
绑定 API 网关触发器:
- 请求方法:GET/POST/HEAD/PUT/DELETE(根据需要进行选择)

目前 API 网关当请求方法为 ANY 时,无法开启跨域功能,所以这里请求方法不能选择 ANY。
Step2. 在 API 网关产品页面,开启 API 的跨域功能
1、在 API网关 产品页面,选择绑定的 API 服务和绑定的 API,编辑 API:

2、在编辑页面开启:支持CORS 选项

3、保存设置后,发布 API

Step3. 测试跨域功能
1、简单跨域请求
$ curl -v -X GET -H "Origin: http://example.com" http://service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com/test/corstest * About to connect() to service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com port 80 (#0) * Trying 111.231.97.251... * Connected to service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com (111.231.97.251) port 80 (#0) > GET /test/corstest HTTP/1.1 > User-Agent: curl/7.29.0 > Host: service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com > Accept: */* > Origin: http://example.com > < HTTP/1.1 200 OK < Content-Type: application/json < Transfer-Encoding: chunked < Connection: keep-alive < X-Api-ID: api-bzh0ss12 < X-Service-RateLimit: 5000/5000 < X-Api-RateLimit: unlimited < Date: Thu, 31 Oct 2019 23:25:46 GMT < Access-Control-Allow-Origin: http://example.com < Access-Control-Allow-Credentials: true < Access-Control-Expose-Headers: X-Api-ID,X-Service-RateLimit,X-UsagePlan-RateLimit,X-UsagePlan-Quota,Cache-Control,Connection,Content-Disposition,Date,Keep-Alive,Pragma,Via,Accept,Accept-Charset,Accept-Encoding,Accept-Language,Authorization,Cookie,Expect,From,Host,If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,Range,Origin,Referer,User-Agent,X-Forwarded-For,X-Forwarded-Host,X-Forwarded-Proto,Accept-Range,Age,Content-Range,Content-Security-Policy,ETag,Expires,Last-Modified,Location,Server,Set-Cookie,Trailer,Transfer-Encoding,Vary,Allow,Content-Encoding,Content-Language,Content-Length,Content-Location,Content-Type < * Connection #0 to host service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com left intact cors template function run successfully
2、预检跨域请求
$ curl -v -X OPTIONS -H "Access-Control-Request-Method: GET" http://service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com/test/corstest * About to connect() to service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com port 80 (#0) * Trying 111.231.97.251... * Connected to service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com (111.231.97.251) port 80 (#0) > OPTIONS /test/corstest HTTP/1.1 > User-Agent: curl/7.29.0 > Host: service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com > Accept: */* > Access-Control-Request-Method: GET > < HTTP/1.1 204 No Content < Date: Thu, 31 Oct 2019 23:24:53 GMT < Connection: keep-alive < X-Api-ID: api-bzh0ss12 < Access-Control-Allow-Origin: * < Access-Control-Allow-Headers: X-Api-ID,X-Service-RateLimit,X-UsagePlan-RateLimit,X-UsagePlan-Quota,Cache-Control,Connection,Content-Disposition,Date,Keep-Alive,Pragma,Via,Accept,Accept-Charset,Accept-Encoding,Accept-Language,Authorization,Cookie,Expect,From,Host,If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,Range,Origin,Referer,User-Agent,X-Forwarded-For,X-Forwarded-Host,X-Forwarded-Proto,Accept-Range,Age,Content-Range,Content-Security-Policy,ETag,Expires,Last-Modified,Location,Server,Set-Cookie,Trailer,Transfer-Encoding,Vary,Allow,Content-Encoding,Content-Language,Content-Length,Content-Location,Content-Type < Access-Control-Allow-Methods: GET,POST,PUT,DELETE,HEAD,OPTIONS,PATCH < Access-Control-Max-Age: 86400 < Server: apigw/1.0.15 < * Connection #0 to host service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com left intact
可以看到,网关均正确返回跨域需要的 Headers。
开启跨域后,OPTIONS 请求不走鉴权逻辑
云函数中实现跨域逻辑
Step1. 创建带跨域逻辑的云函数
创建函数:
- 运行环境: Python2.7
- 选择 空白模板
- 执行方法: index.main_handle

函数代码为:
# -*- coding: utf-8 -*- import logging logger = logging.getLogger() response = {"isBase64": False, "statusCode": 200, "headers": {}} def set_simple_cors(): response["headers"]["Access-Control-Allow-Origin"] = "*" response["headers"]["Access-Control-Allow-Credentials"] = "true" response["headers"]["Access-Control-Expose-Headers"] = "X-Api-ID,X-Service-RateLimit,X-UsagePlan-RateLimit,X-UsagePlan-Quota,Cache-Control,Connection,Content-Disposition,Date,Keep-Alive,Pragma,Via,Accept,Accept-Charset,Accept-Encoding,Accept-Language,Authorization,Cookie,Expect,From,Host,If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,Range,Origin,Referer,User-Agent,X-Forwarded-For,X-Forwarded-Host,X-Forwarded-Proto,Accept-Range,Age,Content-Range,Content-Security-Policy,ETag,Expires,Last-Modified,Location,Server,Set-Cookie,Trailer,Transfer-Encoding,Vary,Allow,Content-Encoding,Content-Language,Content-Length,Content-Location,Content-Type" return response def set_preflight_cors(): response["headers"]["Access-Control-Allow-Origin"] = "*" response["headers"]["Access-Control-Allow-Credentials"] = "true" response["headers"]["Access-Control-Allow-Methods"] = "GET,POST,PUT,DELETE,HEAD,OPTIONS,PATCH" response["headers"]["Access-Control-Allow-Headers"] = "X-Api-ID,X-Service-RateLimit,X-UsagePlan-RateLimit,X-UsagePlan-Quota,Cache-Control,Connection,Content-Disposition,Date,Keep-Alive,Pragma,Via,Accept,Accept-Charset,Accept-Encoding,Accept-Language,Authorization,Cookie,Expect,From,Host,If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,Range,Origin,Referer,User-Agent,X-Forwarded-For,X-Forwarded-Host,X-Forwarded-Proto,Accept-Range,Age,Content-Range,Content-Security-Policy,ETag,Expires,Last-Modified,Location,Server,Set-Cookie,Trailer,Transfer-Encoding,Vary,Allow,Content-Encoding,Content-Language,Content-Length,Content-Location,Content-Type" response["headers"]["Access-Control-Max-Age"] = 86400 return response def is_simple_cors(httpMethod, headers): simple_methods = ['HEAD', 'GET', 'POST'] simple_headers = ['accept', 'accept-language', 'content-language', 'last-event-id', 'content-type', 'origin'] allow_content_types = ['application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain'] if httpMethod not in simple_methods: return False if httpMethod == "POST" and headers['content-type'] not in allow_content_types: return False for header in headers: if header not in simple_headers: return False return True def is_preflight_cors(httpMethod, headers): httpPreflightMethod = headers.get('access-control-request-method', '') if httpMethod == "OPTIONS" and httpPreflightMethod != "": return True return False def main_handler(event, context): logger.info('start cors template function') logger.info("event: %s", event) httpMethod = event['httpMethod'] headers = event['headers'] # 跨域:预检请求 if is_preflight_cors(httpMethod, headers): logger.info("receive preflight http cors request") return set_preflight_cors() # 跨域:简单请求 if is_simple_cors(httpMethod, headers): logger.info("receive simple http cors request") set_simple_cors() # 同源请求,正常完成业务逻辑 logger.info("receive normal http request.") body = "cors template function run successfully" response["body"] = body return response
Step2. 绑定 API 网关触发器
绑定 API 网关触发器:
- 请求方法: ANY
- 开启 启用集成响应

开启集成响应后,返回的参数需要满足集成响应的格式。
参考文档:集成响应与透传响应 – https://cloud.tencent.com/document/product/583/12513#.E9.9B.86.E6.88.90.E5.93.8D.E5.BA.94.E4.B8.8E.E9.80.8F.E4.BC.A0.E5.93.8D.E5.BA.94
Step3. 测试跨域功能
1、简单跨域请求
$ curl -v -X GET -H "Origin: http://example.com" http://service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com/test/corstest * About to connect() to service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com port 80 (#0) * Trying 111.231.97.251... * Connected to service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com (111.231.97.251) port 80 (#0) > GET /test/corstest HTTP/1.1 > User-Agent: curl/7.29.0 > Host: service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com > Accept: */* > Origin: http://example.com > < HTTP/1.1 200 OK < Content-Type: application/json < Transfer-Encoding: chunked < Connection: keep-alive < X-Api-ID: api-51u298ne < X-Service-RateLimit: 5000/5000 < X-Api-RateLimit: unlimited < Access-Control-Allow-Credentials: true < Access-Control-Allow-Headers: X-Api-ID,X-Service-RateLimit,X-UsagePlan-RateLimit,X-UsagePlan-Quota,Cache-Control,Connection,Content-Disposition,Date,Keep-Alive,Pragma,Via,Accept,Accept-Charset,Accept-Encoding,Accept-Language,Authorization,Cookie,Expect,From,Host,If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,Range,Origin,Referer,User-Agent,X-Forwarded-For,X-Forwarded-Host,X-Forwarded-Proto,Accept-Range,Age,Content-Range,Content-Security-Policy,ETag,Expires,Last-Modified,Location,Server,Set-Cookie,Trailer,Transfer-Encoding,Vary,Allow,Content-Encoding,Content-Language,Content-Length,Content-Location,Content-Type < Access-Control-Allow-Methods: GET,POST,PUT,DELETE,HEAD,OPTIONS,PATCH < Access-Control-Allow-Origin: * < Date: Fri, 01 Nov 2019 00:08:50 GMT < * Connection #0 to host service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com left intact cors template function run successfully
2、 预检跨域请求
$ curl -v -X OPTIONS -H "Access-Control-Request-Method: GET" http://service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com/test/corstest * About to connect() to service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com port 80 (#0) * Trying 111.231.97.251... * Connected to service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com (111.231.97.251) port 80 (#0) > OPTIONS /test/corstest HTTP/1.1 > User-Agent: curl/7.29.0 > Host: service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com > Accept: */* > Access-Control-Request-Method: GET > < HTTP/1.1 200 OK < Content-Type: application/json < Transfer-Encoding: chunked < Connection: keep-alive < X-Api-ID: api-51u298ne < X-Service-RateLimit: 5000/5000 < X-Api-RateLimit: unlimited < Access-Control-Allow-Credentials: true < Access-Control-Allow-Headers: X-Api-ID,X-Service-RateLimit,X-UsagePlan-RateLimit,X-UsagePlan-Quota,Cache-Control,Connection,Content-Disposition,Date,Keep-Alive,Pragma,Via,Accept,Accept-Charset,Accept-Encoding,Accept-Language,Authorization,Cookie,Expect,From,Host,If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,Range,Origin,Referer,User-Agent,X-Forwarded-For,X-Forwarded-Host,X-Forwarded-Proto,Accept-Range,Age,Content-Range,Content-Security-Policy,ETag,Expires,Last-Modified,Location,Server,Set-Cookie,Trailer,Transfer-Encoding,Vary,Allow,Content-Encoding,Content-Language,Content-Length,Content-Location,Content-Type < Access-Control-Allow-Methods: GET,POST,PUT,DELETE,HEAD,OPTIONS,PATCH < Access-Control-Allow-Origin: * < Date: Fri, 01 Nov 2019 00:07:40 GMT < * Connection #0 to host service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myqcloud.com left intact
可以看到,函数均正确返回跨域需要的 Headers。
API 网关后期产品优化
目前 ANY 方法还不支持跨域设置,这个 API 网关后期会考虑支持。


