一、使用cors解決跨域問題
CORS(Cross-origin resource sharing,跨域資源共享)是一個 W3C 標准,定義了在必須訪問跨域資源時,瀏覽器與服務器應該如何溝通。CORS 背后的基本思想,就是使用自定義的 HTTP 頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功,還是應該失敗。CORS 需要瀏覽器和服務器同時支持。
整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與。對於開發者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺。
瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。
1. 簡單請求
簡單請求是指滿足下面兩大條件的請求:
- 請求方法為 HEAD、GET、POST中的一種。
- HTTP頭信息不超過一下幾種:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type(只限於三個值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
)
對於簡單請求,瀏覽器回自動在請求的頭部添加一個 Origin 字段來說明,本子請求來自那個源(協議 + 域名 + 端口),服務端則通過這個值判斷是否接收本次請求。如果 Origin 在許可范圍內,則服務器返回的響應會多出幾個頭信息:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, Content-Length
Access-Control-Allow-Origin: *
Content-Type: text/html; charset=utf-8
我們就是通過配置這些參數來處理跨域請求的,下面會做詳細介紹。
2. 非簡單請求
非簡單請求是那種對服務器有特殊要求的請求,比如請求方法是 PUT 或 DELETE ,或者 Content-Type 字段的類型是 application/json。
非簡單請求的CORS請求,會在正式通信之前,增加一次HTTP查詢請求,稱為"預檢"請求(preflight)。
預檢請求其實就是我們常說的 OPTIONS 請求,表示這個請求是用來詢問的。頭信息里面,關鍵字段 Origin ,表示請求來自哪個源,除 Origin 字段,"預檢"請求的頭信息包括兩個特殊字段:
-
Access-Control-Request-Method
該字段是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法
-
Access-Control-Request-Headers
該字段是一個逗號分隔的字符串,指定瀏覽器CORS請求會額外發送的頭信息字段.
瀏覽器先詢問服務器,當前網頁所在的域名是否在服務器的許可名單之中,以及可以使用哪些HTTP動詞和頭信息字段。只有得到肯定答復,瀏覽器才會發出正式的 XMLHttpRequest 請求,否則就報錯。
3. 配置 CORS 解決跨域問題
上面我們了解了兩種跨域請求,其中出現了幾種特殊的 Header 字段,CORS 就是通過配置這些字段來解決跨域問題的,下面詳細介紹一下這些字段的含義:
(1)Access-Control-Allow-Origin
該字段是必須的。它的值要么是請求時Origin
字段的值,要么是一個*
,表示接受任意域名的請求。
(2)Access-Control-Allow-Methods
該字段必需,它的值是逗號分隔的一個字符串,表明服務器支持的所有跨域請求的方法。注意,返回的是所有支持的方法,而不單是瀏覽器請求的那個方法。這是為了避免多次"預檢"請求。
(3)Access-Control-Allow-Headers
如果瀏覽器請求包括Access-Control-Request-Headers
字段,則Access-Control-Allow-Headers
字段是必需的。它也是一個逗號分隔的字符串,表明服務器支持的所有頭信息字段,不限於瀏覽器在"預檢"中請求的字段。
(4)Access-Control-Expose-Headers
該字段可選。CORS請求時,XMLHttpRequest
對象的getResponseHeader()
方法只能拿到6個基本字段:Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
。如果想拿到其他字段,就必須在Access-Control-Expose-Headers
里面指定。
(5)Access-Control-Allow-Credentials
該字段可選。它的值是一個布爾值,表示是否允許發送Cookie。默認情況下,Cookie不包括在CORS請求之中。設為true
,即表示服務器明確許可,Cookie可以包含在請求中,一起發給服務器。這個值也只能設為 true,如果服務器不要瀏覽器發送Cookie,刪除該字段即可。
(6)Access-Control-Max-Age
該字段可選,用來指定本次預檢請求的有效期,單位為秒,在此期間,不用發出另一條預檢請求。
二、示例代碼
gin框架寫的http接口支持跨域請求的方法很簡單,實現一個支持跨域的中間件接口就行,關鍵代碼如下:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
origin := c.Request.Header.Get("origin") //請求頭部
if len(origin) == 0 {
origin = c.Request.Header.Get("Origin")
}
//接收客戶端發送的origin (重要!)
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
//允許客戶端傳遞校驗信息比如 cookie (重要)
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
//服務器支持的所有跨域請求的方法
c.Writer.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PUT, DELETE, UPDATE")
c.Writer.Header().Set("Content-Type", "application/json; charset=utf-8")
// 設置預驗請求有效期為 86400 秒
c.Writer.Header().Set("Access-Control-Max-Age", "86400")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
func statusOKHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "success"})
}
func main() {
r := gin.Default()
r.Use(Cors())
r.GET("/hello", statusOKHandler)
r.Run(":9000")
}
如果要調整一些跨域的規則,則修改cors方法里的一些相關參數即可。