跨源资源共享

实例

http://localhost:8082expresshttp://localhost:8080golang net/httpfetch()http://localhost:8080/api/students

后端代码:

/server/main.go
import (
	"encoding/json"
	"fmt"
	"net/http"

	_ "github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
)

var db *sqlx.DB

type Student struct {
	ID   int64
	Name string
	Sex  string
	Age  int64
}

// 连接数据库
func init(){
    dns := "user:pwd@tcp(localhost:3306)/db_name?charset=utf8&parseTime=True&loc=Local"
	db, err = sqlx.Open("mysql", dns)
	if err != nil {
        fmt.Println(err)
        return
	}
	db.SetMaxOpenConns(2000)
	db.SetMaxIdleConns(1000)
}

// 获取所有学生信息(数据自己事先插入)
func GetAllStudent() []Student {
	sqlStr := "SELECT * FROM student"
	students := make([]Student, 0)
	rows, _ := db.Query(sqlStr)
	student := Student{}
	for rows.Next() {
		rows.Scan(&student.ID, &student.Name, &student.Sex, &student.Age)
		students = append(students, student)
	}
	defer rows.Close()
	return students
}

func GetAllStudentInfo(w http.ResponseWriter, r *http.Request) {
	students := GetAllStudent()
	resp := make(map[string]interface{})
	resp["msg"] = "成功"
	resp["code"] = "200"
	resp["data"] = students
	jsonResp, err := json.Marshal(resp)
	if err != nil {
		fmt.Println(err)
		return
	}
	w.Write(jsonResp)
}

func main() {
	http.HandleFunc("/api/students", GetAllStudentInfo)
	http.ListenAndServe(":8080", nil)
}

前端代码:

/clientnpm install express --save-dev
/client/main.js
import express from 'express'

// 返回了一个服务器对象
const app = express()
// express.static(): 指定静态资源所在目录
app.use(express.static('./'))
app.listen(8082)
node main.js
/client/students.html
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>学生信息</title>
</head>
<body>
    <script>
        // 以同步方式获取响应
        async function getStudents() {
            const promiseResp = await fetch("http://localhost:8080/api/students")
            const resp = await promiseResp.json()
            console.log(resp)
        }
        getStudents()
    </script>
</body>
</html>
http://localhost:8082/students.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J8Wzk6Cp-1663642784944)(images/image-20220919222211766.png)]

可以看到控制台里打印的并不是我们预期的后端给的数据,这是为什么呢?

首先,我们要知道照成这个错误的原因是什么,我们先看整个请求相应的流程是什么样的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IsstQQj6-1663642784945)(images/image-20220919224956766.png)]

问题清楚了,那么如何解决呢?

解决方法1:

交给后端来做

Originstudents.htmllocalhost:8082Origin:http://localhost:8082Access-Control-Allow-OriginAccess-Control-Allow-Origin:http://localhost:8082http://localhost:8082Access-Control-Allow-Origin:*

我们打开F12,查看网络:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mb5HEs6p-1663642784946)(images/image-20220920100058884.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ww68r5Wg-1663642784946)(images/image-20220920100141932.png)]

OriginOrigin
Access-Control-Allow-Origin

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yytlUITi-1663642784947)(images/image-20220920100354945.png)]

http://localhost:8082
w.Header().Set("Access-Control-Allow-Origin", "http://localhost:8082")
Access-Control-Allow-Origin

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zMNOjtaC-1663642784948)(images/image-20220920100901698.png)]

解决方法2:

交给前端来做

除了上面说的解决方法1,还可以通过代理解决:

在这里插入图片描述

​ 这次我们在前端服务器里加入了一个代理的插件,此时前端服务器就和浏览器有一个约定,原本浏览器有一部分请求发送给8082,有一部分发送给8080,这个新的约定就是说:

http://localhost:8082/api/studentsstudents.html

这时候我们来看,对于浏览器来说,有没有发生跨域问题?

Origin

至于代理发的请求,它是通过JavaScript的API发请求,接响应的,是没有什么同源策略、跨域问题。

跨域和同源都是浏览器的特殊行为。

如何区分我这个请求到底是走8082还是走8080呢?

/api//api//api/

看下面代码就明白了:

/clientnpm install http-proxy-middleware --save-dev
/client/students.html
// 修改请求地址,由8080改为8082
const promiseResp = await fetch("http://localhost:8082/api/students")
/client/main.js
import express from 'express'
import { createProxyMiddleware } from 'http-proxy-middleware'

// 返回了一个服务器对象
const app = express()
// express.static(): 指定静态资源所在目录
app.use(express.static('./'))
// 添加代理,凡是以/api为前缀的,都代理到 http://localhost:8080
app.use('/api', createProxyMiddleware({
    target: "http://localhost:8080",
    changeOrigin: true
}
));

app.listen(8082)
node main.js
http://localhost:8082/student.html

可以看到响应被获取到了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ab20C6zF-1663642784950)(images/image-20220920105601280.png)]

Origin

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FEjIzFYs-1663642784951)(images/image-20220920105729208.png)]

总结:

http://localhost:8080/ahttps://localhost:8080/bfetchXMLHttpRequestgolang net/http clientpostman

补充:

Golang解决跨域的完整代码:

上面给的解决方法1,只是针对例子的简陋的版本,真正go通过CORS解决跨域问题的完整代码:

gin中间件:

func Cors(context *gin.Context) {
	method := context.Request.Method
	 // 1. [必须]接受指定域的请求,可以使用*不加以限制,但不安全
	//context.Header("Access-Control-Allow-Origin", "*")
	context.Header("Access-Control-Allow-Origin", context.GetHeader("Origin"))
	fmt.Println(context.GetHeader("Origin"))
	// 2. [必须]设置服务器支持的所有跨域请求的方法
	context.Header("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")
	 // 3. [可选]服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段
	context.Header("Access-Control-Allow-Headers", "Content-Type, Content-Length, Token")
	 // 4. [可选]设置XMLHttpRequest的响应对象能拿到的额外字段
	context.Header("Access-Control-Expose-Headers", "Access-Control-Allow-Headers, Token")
	// 5. [可选]是否允许后续请求携带认证信息Cookir,该值只能是true,不需要则不设置
	context.Header("Access-Control-Allow-Credentials", "true")
	// 6. 放行所有OPTIONS方法
	if method == "OPTIONS" {
		context.AbortWithStatus(http.StatusNoContent)
		return
	}
	context.Next()
}

原生HTTP中间件:

func corsMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// 1. [必须]接受指定域的请求,可以使用*不加以限制,但不安全
		// w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
		// 2. [必须]设置服务器支持的所有跨域请求的方法
		w.Header().Set("Access-Control-Allow-Methods", "POST,GET,PUT,DELETE,OPTIONS")
		// 3. [可选]服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Content-Length,Token")
		// 4. [可选]设置XMLHttpRequest的响应对象能拿到的额外字段
		w.Header().Set("Access-Control-Expose-Headers", "Access-Control-Allow-Headers,Token")
		// 5. [可选]是否允许后续请求携带认证信息Cookir,该值只能是true,不需要则不设置
		w.Header().Set("Access-Control-Allow-Credentials", "true")
		next.ServeHTTP(w, r)
	})
}

func main() {
	http.Handle("/api/test", corsMiddleware(http.HandlerFunc(test)))
	http.ListenAndServe(":8080", nil)
}