php asp網(wǎng)站開發(fā)營銷推廣有哪些形式
Gin CORS 跨域請求資源共享與中間件
文章目錄
- Gin CORS 跨域請求資源共享與中間件
- 一、同源策略
- 1.1 什么是瀏覽器的同源策略?
- 1.2 同源策略判依據(jù)
- 1.3 跨域問題三種解決方案
- 二、CORS:跨域資源共享簡介(后端技術(shù))
- 三 CORS基本流程
- 1.CORS請求分類
- 2.基本流程
- 四、CORS兩種請求詳解
- 1.兩種請求詳解
- 2.解決跨域問題:瀏覽器對于這兩種請求的處理
- 五、Gin 中間件
- 5.1 中間件介紹
- 5.2 初識中間件
- 5.3 c.Next()
- 5.4 多個中間件執(zhí)行順序
- 5.5 c.Abort()
- 5.6 全局中間件與局部中間件
- 5.7 中間件和視圖函數(shù)之間共享數(shù)據(jù)
- 5.8 在路由分組中配置中間件
- 5.9 中間件解決跨域
- 5.10 中間件注意事項
- 5.10.1 Gin 默認中間件
- 5.10.2 gin中間件中使用 **goroutine**
- 六、Gin 框架跨域問題解決
- 5.1 安裝
- 5.2 導(dǎo)入
- 5.3 直接設(shè)置跨域參數(shù)(一般用這個)
- 5.4 使用DefaultConfig作為起點
- 5.5 Default()允許所有來源
一、同源策略
1.1 什么是瀏覽器的同源策略?
- 同源策略
(Same origin policy)
是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響??梢哉fWeb是構(gòu)建在同源策略基礎(chǔ)之上的,瀏覽器只是針對同源策略的一種實現(xiàn) - 瀏覽器最基本的安全策略
- 瀏覽器只能接收相同域
(IP地址+端口)
返回的數(shù)據(jù)
1.2 同源策略判依據(jù)
請求的url
地址,必須與瀏覽器上的url
地址處于同域上,也就是域名,端口,協(xié)議相同,只要協(xié)議、域名和端口任意一個不同,都是跨域請求。
- 比如: 我在本地上的域名是
127.0.0.1:8000
,請求另外一個域名:127.0.0.1:8001
一段數(shù)據(jù)- 瀏覽器上就會報錯,這個就是同源策略的保護,如果瀏覽器對
javascript
沒有同源策略的保護,那么一些重要的機密網(wǎng)站將會很危險- 已攔截跨源請求:同源策略禁止讀取位于
http://127.0.0.1:8001/SendAjax/
的遠程資源。(原因:CORS 頭缺少 'Access-Control-Allow-Origin')
- 但是注意,項目2中的訪問已經(jīng)發(fā)生了,說明是瀏覽器對非同源請求返回的結(jié)果做了攔截
所以就導(dǎo)致了向不同域發(fā)請求,就會出現(xiàn)跨域問題(被瀏覽器阻止了),正常來說,如果我們不做額外處理,是沒辦法這么發(fā)請求的。
1.3 跨域問題三種解決方案
CORS
(跨域資源共享:后端技術(shù)),主流采用的方案,使用第三方插件- 前端代理(只能在測試階段使用):node起了一個服務(wù),正向代理
jsonp
:只能解決get請求跨域,本質(zhì)原理使用了某些標(biāo)簽不限制跨域(img,script)
二、CORS:跨域資源共享簡介(后端技術(shù))
CORS需要瀏覽器和服務(wù)器同時支持。目前,所有瀏覽器都支持該功能,IE瀏覽器不能低于IE10。
整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與。對于開發(fā)者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。瀏覽器一旦發(fā)現(xiàn)AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺。
因此,實現(xiàn)CORS通信的關(guān)鍵是服務(wù)器。只要服務(wù)器實現(xiàn)了CORS接口,就可以跨源通信。
三 CORS基本流程
1.CORS請求分類
-
簡單請求 (simple request):簡單請求只發(fā)一次
-
非簡單請求 (not-so-simple request):發(fā)送兩次,第一次是options請求,第二次是真正的請求
2.基本流程
- 瀏覽器發(fā)出CORS簡單請求,只需要在頭信息之中增加一個Origin字段。
- 瀏覽器發(fā)出CORS非簡單請求,會在正式通信之前,增加一次HTTP查詢請求,稱為"預(yù)檢"請求(preflight)。瀏覽器先詢問服務(wù)器,當(dāng)前網(wǎng)頁所在的域名是否在服務(wù)器的許可名單之中,以及可以使用哪些HTTP動詞和頭信息字段。只有得到肯定答復(fù),瀏覽器才會發(fā)出正式的XMLHttpRequest請求,否則就報錯。
四、CORS兩種請求詳解
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
凡是不同時滿足上面兩個條件,就屬于非簡單請求
瀏覽器對這兩種請求的處理,是不一樣的。
簡單請求和非簡單請求的區(qū)別
- 簡單請求: 一次請求
- 非簡單請求:兩次請求,在發(fā)送數(shù)據(jù)之前會先發(fā)一次請求用于做“預(yù)檢”,只有“預(yù)檢”通過后才再發(fā)送一次請求用于數(shù)據(jù)傳輸。
關(guān)于“預(yù)檢”
- 請求方式:OPTIONS
- “預(yù)檢”其實做檢查,檢查如果通過則允許傳輸數(shù)據(jù),檢查不通過則不再發(fā)送真正想要發(fā)送的消息
如何“預(yù)檢” ?
如果復(fù)雜請求是PUT等請求,則服務(wù)端需要設(shè)置允許某請求,否則“預(yù)檢”不通過
Access-Control-Allow-Methods
如果復(fù)雜請求設(shè)置了請求頭,則服務(wù)端需要設(shè)置允許某請求頭,否則“預(yù)檢”不通過
Access-Control-Allow-Headers
2.解決跨域問題:瀏覽器對于這兩種請求的處理
支持跨域,簡單請求
- 服務(wù)器設(shè)置響應(yīng)頭:
Access-Control-Allow-Origin = ‘域名’ 或 ‘*’
支持跨域,復(fù)雜請求
非簡單請求需要判斷是否是options請求
由于復(fù)雜請求時,首先會發(fā)送“預(yù)檢”請求,如果“預(yù)檢”成功,則發(fā)送真實數(shù)據(jù)。
- “預(yù)檢”請求時,允許請求方式則需服務(wù)器設(shè)置響應(yīng)頭:
Access-Control-Allow-Methods
- “預(yù)檢”請求時,允許請求頭則需服務(wù)器設(shè)置響應(yīng)頭:
Access-Control-Allow-Headers
五、Gin 中間件
在Gin框架中,中間件(Middleware)是一種允許在請求到達處理程序之前或之后執(zhí)行一些邏輯的機制。中間件允許你在處理請求的過程中插入一些代碼,例如驗證請求、記錄日志、處理跨域等。
5.1 中間件介紹
中間件是Gin框架的一個關(guān)鍵概念。它是一個函數(shù),接受gin.Context
作為參數(shù),可以在請求到達處理程序之前或之后執(zhí)行一些邏輯。中間件允許你在請求處理過程中執(zhí)行預(yù)處理或后處理的操作。
5.2 初識中間件
在Gin框架中,使用Use
方法可以注冊一個全局中間件,它將應(yīng)用于所有路由。例如:
package mainimport ("fmt""github.com/gin-gonic/gin""net/http"
)func LoggerMiddleware(c *gin.Context) {// 在請求處理之前執(zhí)行的邏輯fmt.Println("Start Logging")// 將請求傳遞給下一個處理程序c.Next()// 在請求處理之后執(zhí)行的邏輯fmt.Println("End Logging")
}func main() {r := gin.Default()// 注冊全局中間件r.Use(LoggerMiddleware)// 定義路由r.GET("/", func(c *gin.Context) {c.String(http.StatusOK, "Hello, Gin!")})r.Run(":8080")
}
在上述例子中,LoggerMiddleware
是一個簡單的中間件,用于記錄請求日志。通過使用Use
方法,我們將這個中間件注冊為全局中間件,它將應(yīng)用于所有的路由。
查看Use
方法源碼如下:
綜上,所以中間件必須是一個 gin.HandlerFunc
類型,配置路由的時候可以傳遞多個 func
回調(diào)函數(shù)。
5.3 c.Next()
在中間件中,通過調(diào)用c.Next()
可以將請求傳遞給下一個處理程序。這是一個重要的步驟,如果你忘記調(diào)用c.Next()
,那么請求將不會繼續(xù)傳遞給后續(xù)的中間件或路由處理程序。
package mainimport ("fmt""github.com/gin-gonic/gin""time"
)// 寫一個中間件,統(tǒng)計視圖函數(shù)運行時間
func totalTime(c *gin.Context) {start := time.Now()c.Next() //繼續(xù)往后執(zhí)行end := time.Now()fmt.Println("視圖函數(shù)運行時間為:", end.Sub(start))
}
func main() {r := gin.Default()// 把中間件函數(shù)加在視圖函數(shù)之前r.GET("/", totalTime, func(c *gin.Context) {time.Sleep(time.Second * 2)c.String(200, "hello,Gin!")})r.Run()
}
5.4 多個中間件執(zhí)行順序
如果你有多個中間件,它們將按照注冊的順序執(zhí)行。在上述例子中,如果我們有多個全局中間件,它們將按照注冊的順序依次執(zhí)行。
func Middleware1(c *gin.Context) {fmt.Println("Middleware 1 - Start")c.Next()fmt.Println("Middleware 1 - End")
}func Middleware2(c *gin.Context) {fmt.Println("Middleware 2 - Start")c.Next()fmt.Println("Middleware 2 - End")
}func main() {r := gin.Default()// 注冊全局中間件,按照注冊順序執(zhí)行r.Use(Middleware1)r.Use(Middleware2)r.GET("/hello", func(c *gin.Context) {c.String(http.StatusOK, "Hello, Gin!")})r.Run(":8080")
}
輸出結(jié)果如下:
Middleware 1 - Start
Middleware 2 - Start
Middleware 2 - End
Middleware 1 - End
5.5 c.Abort()
在中間件中,通過調(diào)用c.Abort()
可以終止請求鏈,不再執(zhí)行后續(xù)的中間件或路由處理程序。這通常是在中間件中檢測到錯誤或條件不滿足時使用的。
package mainimport ("github.com/gin-gonic/gin""net/http"
)func isValidAuth(authorizationHeader string) bool {// 在這里實現(xiàn)你的身份驗證邏輯// 例如,你可能驗證一個令牌或檢查憑證// 如果身份驗證成功,返回 true;否則返回 falsereturn true
}func AuthMiddleware(c *gin.Context) {// 檢查是否有有效的 Authorization 頭if authorizationHeader := c.GetHeader("Authorization"); authorizationHeader == "" {// 如果 Authorization 頭缺失,返回未授權(quán)狀態(tài)c.AbortWithStatus(http.StatusUnauthorized)return}// 檢查使用你的自定義邏輯提供的身份驗證是否有效if !isValidAuth(c.GetHeader("Authorization")) {// 如果身份驗證失敗,返回未授權(quán)狀態(tài)c.AbortWithStatus(http.StatusUnauthorized)return}// 如果身份驗證成功,繼續(xù)執(zhí)行下一個中間件或路由處理程序c.Next()
}func main() {r := gin.Default()// 應(yīng)用 AuthMiddleware 以保護 "/protected" 路由r.GET("/protected", AuthMiddleware, func(c *gin.Context) {// 只有在 AuthMiddleware 允許請求繼續(xù)時,才會執(zhí)行此處理程序c.String(http.StatusOK, "你有權(quán)訪問受保護的路由!")})r.Run(":8080")
}
5.6 全局中間件與局部中間件
全局中間件是通過Use
方法注冊的,它將應(yīng)用于所有路由。局部中間件是在定義單個路由時附加的。
package mainimport ("fmt""github.com/gin-gonic/gin""net/http"
)// GlobalMiddleware1 全局中間件1
func GlobalMiddleware1(c *gin.Context) {c.Header("X-Global-Middleware-1", "Applied")fmt.Printf("GlobalMiddleware1\n")c.Next()
}// GlobalMiddleware2 全局中間件2
func GlobalMiddleware2(c *gin.Context) {c.Header("X-Global-Middleware-2", "Applied")fmt.Printf("GlobalMiddleware2\n")c.Next()
}// LocalMiddleware3 局部中間件3
func LocalMiddleware3(c *gin.Context) {c.Header("X-Local-Middleware-3", "Applied")fmt.Printf("LocalMiddleware3\n")c.Next()
}func main() {// 創(chuàng)建一個新的 Gin 引擎r := gin.New()// 使用全局中間件1r.Use(GlobalMiddleware1)// 使用全局中間件2r.Use(GlobalMiddleware2)r.GET("/", func(c *gin.Context) {c.String(http.StatusOK, "Hello,Gin!")})// 定義一個路由組,并在路由組中使用局部中間件3apiGroup := r.Group("/api")apiGroup.Use(LocalMiddleware3)// 路由1,將同時應(yīng)用全局中間件1、全局中間件2以及局部中間件3apiGroup.GET("/endpoint1", func(c *gin.Context) {c.String(http.StatusOK, "Endpoint 1")})// 路由2,將同時應(yīng)用全局中間件1、全局中間件2r.GET("/endpoint2", func(c *gin.Context) {c.String(http.StatusOK, "Endpoint 2")})// 啟動服務(wù)器r.Run(":8080")
}
5.7 中間件和視圖函數(shù)之間共享數(shù)據(jù)
在中間件和視圖函數(shù)之間共享數(shù)據(jù)可以使用c.Set
和c.Get
方法。
package mainimport ("fmt""github.com/gin-gonic/gin""net/http"
)func CustomMiddleware(c *gin.Context) {// 在中間件中設(shè)置數(shù)據(jù)c.Set("userID", 123)// 繼續(xù)執(zhí)行下一個中間件或路由處理程序c.Next()
}func ProtectedRouteHandler(c *gin.Context) {// 從上一個中間件中獲取數(shù)據(jù)userID, exists := c.Get("userID")if !exists {// 如果數(shù)據(jù)不存在,返回錯誤響應(yīng)c.String(http.StatusInternalServerError, "無法獲取用戶信息")return}// 數(shù)據(jù)存在,繼續(xù)處理c.String(http.StatusOK, fmt.Sprintf("用戶ID:%v,你有權(quán)訪問受保護的路由!", userID))
}func main() {r := gin.Default()// 應(yīng)用 CustomMiddleware 中間件以設(shè)置數(shù)據(jù)r.Use(CustomMiddleware)// 設(shè)置保護路由r.GET("/protected", ProtectedRouteHandler)r.Run(":8080")
}
5.8 在路由分組中配置中間件
在Gin框架中,你可以使用路由分組將中間件應(yīng)用于一組相關(guān)的路由。這樣,你可以更清晰地組織你的中間件和路由。
func LoggerMiddleware(c *gin.Context) {fmt.Println("Request Log")c.Next()
}func AuthMiddleware(c *gin.Context) {// 檢查是否有有效的身份驗證信息if !isValidAuth(c.GetHeader("Authorization")) {c.AbortWithStatus(http.StatusUnauthorized)return}c.Next()
}func main() {r := gin.Default()// 創(chuàng)建一個路由分組,并將中間件應(yīng)用于該分組中的所有路由apiGroup := r.Group("/api", LoggerMiddleware, AuthMiddleware)apiGroup.GET("/users", func(c *gin.Context) {c.String(http.StatusOK, "List of Users")})apiGroup.GET("/products", func(c *gin.Context) {c.String(http.StatusOK, "List of Products")})r.Run(":8080")
}
5.9 中間件解決跨域
下面是一個簡單的例子:
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()// 使用中間件處理跨域問題r.Use(CORSMiddleware())// 其他路由注冊r.GET("/hello", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "Hello, CORS is enabled!"})})// 啟動 Gin 服務(wù)器r.Run(":8080")
}// CORSMiddleware 中間件處理跨域問題
func CORSMiddleware() gin.HandlerFunc {return func(c *gin.Context) {c.Writer.Header().Set("Access-Control-Allow-Origin", "*")c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")if c.Request.Method == "OPTIONS" {c.AbortWithStatus(204)return}c.Next()}
}
在上述例子中,CORSMiddleware
函數(shù)返回一個用于處理跨域的中間件。通過將該中間件注冊到Gin框架中,可以輕松地解決跨域問題。
5.10 中間件注意事項
5.10.1 Gin 默認中間件
gin.Default()
默認使用了 Logger
和 Recovery
中間件,其中:
-
Logger
中間件將日志寫入gin.DefaultWriter
,即使配置了GIN_MODE=release
。 -
Recovery
中間件會recover
任何panic
。如果有panic
的話,會寫入 500 響應(yīng)碼。如果不想使用上面兩個默認的中間件,可以使用
gin.New()
新建一個沒有任何默認中間件的 路由。
5.10.2 gin中間件中使用 goroutine
當(dāng)在中間件或 handler 中啟動新的 goroutine
時,不能使用原始的上下文(c *gin.Context)
, 必須使用其只讀副本(c.Copy())
r.GET("/", func(c *gin.Context) {cCp := c.Copy()go func() {// simulate a long task with time.Sleep(). 5 seconds time.Sleep(5 * time.Second)// 這里使用你創(chuàng)建的副本fmt.Println("Done! in path " + cCp.Request.URL.Path)}()c.String(200, "首頁")})
六、Gin 框架跨域問題解決
目前大多數(shù)的 Web 框架,都提供了 CORS 的解決方案。Gin
也不例外,Gin
里面也提供了一個 middleware 實現(xiàn)來解決跨域問題,打開如下Gin 中間件插件庫合集:
https://github.com/gin-gonic/contrib
找到cors 文件夾:
進入后會提示:此軟件包已于2016-12-07放棄。請改用gin-contrib/cors。點擊進入最新的即可。
- GitHub 地址:https://github.com/gin-contrib/cors
5.1 安裝
go get github.com/gin-contrib/cors
5.2 導(dǎo)入
import "github.com/gin-contrib/cors"
5.3 直接設(shè)置跨域參數(shù)(一般用這個)
package mainimport ("github.com/gin-contrib/cors""github.com/gin-gonic/gin""strings""time"
)func main() {// 創(chuàng)建一個默認的 Gin 實例server := gin.Default()// 使用 CORS 中間件處理跨域問題,配置 CORS 參數(shù)server.Use(cors.New(cors.Config{// 允許的源地址(CORS中的Access-Control-Allow-Origin)// AllowOrigins: []string{"https://foo.com"},// 允許的 HTTP 方法(CORS中的Access-Control-Allow-Methods)AllowMethods: []string{"PUT", "PATCH"},// 允許的 HTTP 頭部(CORS中的Access-Control-Allow-Headers)AllowHeaders: []string{"Origin"},// 暴露的 HTTP 頭部(CORS中的Access-Control-Expose-Headers)ExposeHeaders: []string{"Content-Length"},// 是否允許攜帶身份憑證(CORS中的Access-Control-Allow-Credentials)AllowCredentials: true,// 允許源的自定義判斷函數(shù),返回true表示允許,false表示不允許AllowOriginFunc: func(origin string) bool {if strings.HasPrefix(origin, "http://localhost") {// 允許你的開發(fā)環(huán)境return true}// 允許包含 "yourcompany.com" 的源return strings.Contains(origin, "yourcompany.com")},// 用于緩存預(yù)檢請求結(jié)果的最大時間(CORS中的Access-Control-Max-Age)MaxAge: 12 * time.Hour,}))// 啟動 Gin 服務(wù)器,監(jiān)聽在 0.0.0.0:8080 上server.Run(":8080")
}
5.4 使用DefaultConfig作為起點
func main() {router := gin.Default()// - No origin allowed by default// - GET,POST, PUT, HEAD methods// - Credentials share disabled// - Preflight requests cached for 12 hoursconfig := cors.DefaultConfig()config.AllowOrigins = []string{"http://google.com"}// config.AllowOrigins = []string{"http://google.com", "http://facebook.com"}// config.AllowAllOrigins = truerouter.Use(cors.New(config))router.Run()
}
**注意:**雖然 Default()
允許所有來源,但 DefaultConfig()
不允許,您仍然必須使用 AllowAllOriins
。
5.5 Default()允許所有來源
func main() {router := gin.Default()// same as// config := cors.DefaultConfig()// config.AllowAllOrigins = true// router.Use(cors.New(config))router.Use(cors.Default())router.Run()
}
使用所有來源會禁用 Gin
為客戶端設(shè)置 cookie 的能力。處理憑據(jù)時,不要允許所有來源。