腾讯云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 种跨域请求:

  1. 简单请求
  2. 预检请求

简单请求

当 HTTP 请求出现以下两种情况时,浏览器认为是简单跨域请求:

  1. 请求方法是 GET、HEAD 或者 POST,并且当请求方法是 POST 时,Content-Type 必须是 application/x-www-form-urlencoded, multipart/form-data 或着 text/plain 中的一个值。
  2. 请求中没有自定义 HTTP 头部。

对于简单跨域请求,浏览器要做的就是在 HTTP 请求中添加 Origin Header,将 JavaScript 脚本所在域填充进去,向其他域的服务器请求资源。服务器端收到一个简单跨域请求后,根据资源权限配置,在响应头中添加 Access-Control-Allow-Origin Header。浏览器收到响应后,查看 Access-Control-Allow-Origin Header,如果当前域已经得到授权,则将结果返回给 JavaScript。否则浏览器忽略此次响应。相较于同源请求,CORS 简单请求会在头信息中额外增加一个 Origin 字段。

预检请求

当 HTTP 请求出现以下两种情况时,浏览器认为是带预检(Preflighted)的跨域请求:

  1. 除 GET、HEAD 和 POST(only with application/x-www-form-urlencoded, multipart/form-data, text/plain Content-Type)以外的其他 HTTP 方法。
  2. 请求中出现自定义 HTTP 头部。

非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为"预检"请求(preflight)。

预检(preflighted)请求会先用 HTTP 的 OPTIONS 方法请求另一个域名资源,确认后续实际(actual)请求能否可安全送出。由于跨域请求可能会携带使用者的信息,所以要先进行预检请求。

腾讯云SCF + 腾讯云API 网关实现跨域

当 SCF 绑定 API 网关触发器后,有 2 种方式实现跨域**(建议使用第 1 种方法)**:

  1. 借助 API 网关的跨域功能
  2. 云函数中实现跨域逻辑

本文就来介绍下,如果通过这 2 种方式,来实现跨域功能。建议选择第 1 种方式,来实现跨域功能,这样用户就不需要在函数中实现跨域相关的逻辑代码。

借助 API 网关的跨域功能

Step1. 绑定 API 网关触发器

绑定 API 网关触发器:

  1. 请求方法: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. 创建带跨域逻辑的云函数

创建函数:

  1. 运行环境: Python2.7
  2. 选择 空白模板
  3. 执行方法: 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 网关触发器:

  1. 请求方法: ANY
  2. 开启 启用集成响应

开启集成响应后,返回的参数需要满足集成响应的格式。

参考文档:集成响应与透传响应 – 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 网关后期会考虑支持。