0%

jsonp和cors处理跨域-Golang

记录一下实习期间的一个任务,原本使用的是 jsonp 来解决跨域的问题
但是前端拿不到 HTTP 的状态码,所以改用了 cors
如有错误,请大佬指正!!!

跨域这个东西之前一直不怎么清楚,这次顺便整理一下。
网页中进行测试请求,Server 是用的 GO 语言写的,戳我跳转 Golang-Server

跨域

跨域:浏览器对于 javascript 的同源策略的限制,例如 a.cn 下面的 js 不能调用 b.cn 中的 js,对象或数据(因为 a.cn 和 b.cn 是不同域),所以跨域就出现了.

同源策略

请求的 url 地址,必须与浏览器上的 url 地址处于同域上,也就是域名,端口,协议相同.
比如:我在本地上的域名是 localhost,请求另外一个域名一段数据。这个时候在浏览器上会报错。

1
2
3
4
5
6
7
8
9
10
11
<html>
<head>
<title>CrossDomain</title>
</head>
<body>
<script src="https://cdn.bootcss.com/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript">
$.get("http://localhost:8080/normalHttp", function (res) {});
</script>
</body>
</html>

浏览器路径: localhost/CrossDomain.html
CrossDomainError.png

这个就是同源策略的保护,如果浏览器对 javascript 没有同源策略的保护,那么一些重要的机密网站将会很危险~

假如自己的 url 是这个study.cn/json/jsonp/jsonp.html,访问其他 URL 的结果如下

请求地址 形式 结果
http://study.cn/test/a.html 同一域名,不同文件夹 成功
http://study.cn/json/jsonp/jsonp.html 同一域名,统一文件夹 成功
http://a.study.cn/json/jsonp/jsonp.html 不同域名,文件路径相同 失败
http://study.cn:8080/json/jsonp/jsonp.html 同一域名,不同端口 失败
https://study.cn/json/jsonp/jsonp.html 同一域名,不同协议 失败

jsonp

jsonp 全称是 JSON with Padding,是为了解决跨域请求资源而产生的解决方案,是一种依靠开发人员创造出的一种非官方跨域数据交互协议。
一个是描述信息的格式,一个是信息传递双方约定的方法。

jsonp 的产生

1.AJAX 直接请求普通文件存在跨域无权限访问的问题,不管是静态页面也好.
2.不过我们在调用 js 文件的时候又不受跨域影响,比如引入 jquery 框架的,或者是调用相片的时候
3.凡是拥有 scr 这个属性的标签都可以跨域例如<script><img><iframe>
4.如果想通过纯 web 端跨域访问数据只有一种可能,那就是把远程服务器上的数据装进 js 格式的文件里.
5.而 json 又是一个轻量级的数据格式,还被 js 原生支持
6.为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作 JSONP,该协议的一个要点就是允许用户传递一个 callback 参数给服务端

Example

放一个 jsonp 的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<html>
<head>
<title>Go JSONP Server</title>
</head>
<body>
<button id="btn">Click to get HTTP header via JSONP</button>
<pre id="result"></pre>
<script>
"use strict";

var btn = document.getElementById("btn");
var result = document.getElementById("result");

function myCallback(acptlang) {
result.innerHTML = JSON.stringify(acptlang, null, 2);
}

function jsonp() {
result.innerHTML = "Loading ...";
var tag = document.createElement("script");
tag.src = "http://localhost:8080/normalJsonp?callback=myCallback";
document.querySelector("head").appendChild(tag);
}

btn.addEventListener("click", jsonp);
</script>
</body>
</html>

打开localhost/JsonP.html,然后使用 click,既可以看到效果,但是前端好像拿不到表示错误的 HTTP 状态码,因为这个方式是使用的加载 src 的方式。
Jsonp.png

Cors

CORS 是一个 W3C 标准,全称是”跨域资源共享”(Cross-origin resource sharing)。下面有些内容没有整理,去看下阮老师写的吧–>阮一峰:跨域资源共享 CORS 详解

简介

CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE10。
整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。

rs/cors

Go net/http configurable handler to handle CORS requests.
一个 GitHub 的开源项目,我使用它作为中间件来完成跨域请求,抄一下 README 的 demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"net/http"

"github.com/rs/cors"
)

func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte("{\"hello\": \"world\"}"))
})

// cors.Default() setup the middleware with default options being
// all origins accepted with simple methods (GET, POST). See
// documentation below for more options.
handler := cors.Default().Handler(mux)
http.ListenAndServe(":8080", handler)
}

这个上面给出的替换整个 handler 的做法,我看了下rs/cors/doc,文档里面说明比较少(issue 里面给的内容很多,不过都是英文,逃),还是研究了一下如何对于单个的 api 进行处理的方法–>戳我跳转 Golang-Server

ajax 请求

这里放一下 ajax 的请求,对应是 Golang-Server 的请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<html>
<head>
<title>Go JSONP Server</title>
</head>
<body>
<script src="https://cdn.bootcss.com/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript">
var url = "http://localhost:8080/hello";
$.ajax({
type: "get",
url: url,
headers: {
contentType: "application/json;charset=utf-8",
},
dataType: "json",
success: function (data) {
if ("true" == data.status) {
// alert("成功!"+XMLHttpRequest.status);
//这里也可以循环data里你要显示的数据
return true;
} else {
// alert("失败!错误信息如下:" + data.errorMsg+"status:"+XMLHttpRequest.status);
return false;
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
// alert("请求失败!"+XMLHttpRequest.status);
},
});
</script>
</body>
</html>

请求的结果,首先发起 OPTIONS 的请求
OPTIONS.png
然后发起 GET 请求
GET.png

Preflighted Request

这个就是先发送一个方法为 OPTIONS 的预请求(preflight request),用于试探服务端是否能接受真正的请求,如果 OPTIONS 获得的回应是拒绝性质的,比如 404/403/500 等 http 状态,就会停止 post、put 等请求的发出。
那么, 什么情况下请求会变成 preflighted request 呢?

  • 请求方法不是 GET/HEAD/POST
  • POST 请求的 Content-Type 并非 application/x-www-form-urlencoded, multipart/form-data, 或 text/plain
  • 请求设置了自定义的 header 字段

Golang-Server

这里是样例使用 Server 的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
"net/http"

"strconv"
"github.com/rs/cors"
"fmt"
)

func normalHttp(w http.ResponseWriter, req *http.Request) {
data := "{\"hello\": \"world\"}"
w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
w.Write([]byte(data))
return
}
func norlJsonp(w http.ResponseWriter, req *http.Request) {
query := req.URL.Query()
callback := query.Get("callback")
data := "{\"hello\": \"world\"}"
re := []byte(fmt.Sprintf("%s(%s)", callback, data))
w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
w.Header().Set("Content-Length", strconv.Itoa(len(re)))
w.Write([]byte(re))
return
}

type myClass struct {
ID int `json:"id"`
}

func main() {
mux := http.NewServeMux()
c := cors.New(cors.Options{
AllowedHeaders: []string{"*"},
})
mux.Handle("/normalHttp", http.HandlerFunc(normalHttp))
mux.Handle("/normalJsonp", http.HandlerFunc(norlJsonp))
mux.Handle("/hello", c.Handler(http.HandlerFunc(normalHttp)))

http.ListenAndServe(":8080", mux)
}

gin 配置

对于 gin 框架来说,如果想要限制某几个 origin 可以访问,可以使用中间件进行如下设置,创建一个名为 mycors 的包,下面创建 mycors.go,可以通过 AllowedOrigins,AllowedHeaders 的设置来控制跨域请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package mycors

import (
"github.com/gin-gonic/gin"
cors "github.com/rs/cors/wrapper/gin"
)

func Cors() gin.HandlerFunc {
return cors.New(cors.Options{
AllowedOrigins: []string{"http://example.com", "http://example1.com"},
AllowedHeaders: []string{"*"},
AllowCredentials: true,
// Enable Debugging for testing, consider disabling in production
//Debug: true,
})
}

然后在 router 中对 gin 使用中间件,这样就可以对 r 进行跨域处理,需要注意的是 group 需要重新设置

1
2
3
4
5
6
7
8
9
10
r := gin.New()
r.Use(mycors.Cors())
test := r.Group("/test", mycors.Cors())
{
test.GET("/testapi", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "message",
})
})
}

NOT PASS ACCESS

如果遇到了这个报错,是首部的问题,如果按照默认的配置是不会有这个问题的,在上面的 gin 框架的讲解中也提到了对应的允许方式,AllowedHeaders 可以是多个字符串只允许某些首部的使用
Acess-Origin.png
Access-Control_Allow_Origin.png

如果你使用其他的封装,首先请仔细查看文档 Parameters对不同框架的支持,在默认情况下应该是可以直接使用的,还是解决不了的话可以试试这个.

1
2
3
c := cors.New(cors.Options{
AllowedHeaders: []string{"*"},
})

后记

对于现在的跨域问题,jsonp 和 cors 是两种比较常见的解决方法,当然很多地方也使用 NGINX 来进行处理 OPTIONS 的请求,对于 iframe 等等因为作者不是前端就不赘述了。

参考资料

听说好看的人都关注了我的公众号《泫言》