使用 Gorilla/Mux 和 Go 的 CORS 不再有麻烦
简介
如今,大型后端解决方案通常看起来像**一组微服务相互交互并与一个或多个前端(通常是浏览器)应用程序**交互。
这些服务可以分布在**多个虚拟机或物理机** 上,因此每个应用程序(即托管应用程序的服务器)都会获得一个**不同的域名/IP 地址**。
前端应用程序**通常使用 Javascript 或 Typescript 与后端交互,但是如果请求发送到不同的(与前端服务器)服务器,浏览器可能会认为它对安全构成潜在风险并阻止它 **。为了解决这个问题,后端 API 应该提供带有 valid origin 的标头(前端应用程序所在的域名或 ip)。今天我们将学习如何正确设置 Gorilla/mux Web API 以永远忘记 CORS。
我们应该从后端提供什么
考虑以下 REST 资源,即用户:
-
GET /api/user/- 用于获取所有用户 -
GET /api/user/{id}/- 用于通过 id 获取单个用户 -
POST /api/user/- 用于创建新用户 -
PUT /api/user/{id}/- 用于更新现有用户 -
DELETE /api/user/{id}/- 用于删除现有用户
基本上,我们必须实现以下几点:
- 向端点添加对 OPTIONS 的响应:
OPTIONS /api/user/和OPTIONS /api/user/{id}/,具有以下标头:
*Access-Control-Allow-Origin- 域名/ip 或 * 用于任何来源;
*Access-Control-Allow-Headers- 在这里我们可以简单地设置 *
*Access-Control-Allow-Methods- 我们可以简单地列出所有方法,但我认为最好将OPTIONS, GET, POST作为/api/user/的值和OPTIONS, GET, PUT, DELETE作为/api/user/{id}/的值发送;
- 在服务器端添加可选的来源检查(如有必要)。
今天的文章是关于第一个的,你可以找到第二个github.com\gorilla\handlers。它有一个中间件,限制只能访问特定的来源。也许您注意到您的后端并不总是需要响应 OPTIONS 请求,因为浏览器没有发送 OPTIONS 请求简单请求。但在上面的例子中,我们必须这样做。当您的 api 非常大时,您必须不断添加预检处理程序(请参见下面的示例),并且每隔一段时间 您可能会忘记这样做,尤其是当您刚刚完成一些复杂端点的设计时。使用我们的 (Wissance LLC) 解决方案(开源 github 包),您可以忘记添加预检处理程序,因为我们的包会自动执行此操作。 如果您觉得我们的套餐对您有用(这对我们非常重要),请给我们一颗星。
如何轻松完成所有这些工作
我们实现了自己的HandlerFunc,其签名与mux.Router.HandlerFunc类似,但有一些细微差别:
-
我们将一个指向
mux.Router的指针作为第一个参数传递。我们这样做是因为我们也需要使用子路由器,看看这个单元测试你可以使用参考。 -
在分配路由处理程序时,我们将处理程序方法参数作为最后一个可变参数传递,而不是调用
.Methods(),我们认为这也使事情变得更简单。
基本上对于自动预检处理,我们需要创建WebApiHandler实例并将所需的原始值作为其第二个参数传递给它(Access-Control-Allow-Origin标头**仅支持单个值**)并使用我们的HandlerFunc而不是 gorilla/mux,请参见示例:
handler := NewWebApiHandler(true, AnyOrigin)
// Get only method
baseUrl := "http://127.0.0.1:8998"
configResource := baseUrl + "/api/config/"
handler.HandleFunc(handler.Router, configResource,
webApi.GetConfigHandler,"GET")
// full crud
functionResourceRoot := baseUrl + "/api/function/"
handler.HandleFunc(handler.Router, functionResourceRoot,
webApi.GetAllFunctions, "GET")
handler.HandleFunc(handler.Router, functionResourceRoot,
webApi.CreateFunction, "POST")
functionResourceById := baseUrl + "/api/function/{id:[0-9]+}/"
handler.HandleFunc(handler.Router, functionResourceById,
webApi.GetFunctionById, "GET")
handler.HandleFunc(handler.Router, functionResourceById,
webApi.UpdateFunction, "PUT")
handler.HandleFunc(handler.Router, functionResourceById,
webApi.DeleteFunction, "DELETE")
进入全屏模式 退出全屏模式
对于上面的代码片段,我们必须添加 3 个额外的预检处理程序:
-
http://127.0.0.1:8998/api/config/; -
http://127.0.0.1:8998/api/function/; -
http://127.0.0.1:8998/api/function/{id};
在我们创建我们的包之前,我们总是这样编写处理程序:
router.HandleFunc(functionResourceRoot,
webApi.PreflightRoot).Methods("OPTIONS")
router.HandleFunc(functionResourceById,
webApi.PreflightByID).Methods("OPTIONS")
func (webApi *WebApiContext) PreflightRoot(respWriter http.ResponseWriter, request *http.Request) {
rest.EnableCors(&respWriter)
respWriter.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
}
func (webApi *WebApiContext) PreflightByID(respWriter http.ResponseWriter, request *http.Request) {
rest.EnableCors(&respWriter)
respWriter.Header().Set("Access-Control-Allow-Methods", "GET, PUT, DELETE, OPTIONS")
}
进入全屏模式 退出全屏模式
我们还应该展示webApi对象是什么以及它的一些请求处理函数的外观:
type WebApiContext struct {
Db *gorm.DB // passing gorm to Context
Config *config.AppConfig
// other fields
}
func (webApi *WebApiContext) GetAllFunctions(respWriter http.ResponseWriter, request *http.Request) {
// return here array of functions, body omitted
}
func (webApi *WebApiContext) GetFunctionById(respWriter http.ResponseWriter, request *http.Request) {
// return function by id, body omitted
}
进入全屏模式 退出全屏模式
让我们看看添加库后我们的代码有何变化:
webApi := rest.WebApiContext{Db: appContext.ModelContext.Context, Config: appContext.Config, WebApiHandler: gr.NewWebApiHandler(true, gr.AnyOrigin)}
webApi.WebApiHandler.Router.Use( keycloakAuthService.KeycloakAuthMiddleware)
webApi.WebApiHandler.Router.Use(r.InspectorMiddleware)
router := webApiContext.WebApiHandler.Router
router.StrictSlash(true)
// function resource
webApi.WebApiHandler.HandleFunc(router, baseUri+"/function/", webApi.GetAllFunctions, "GET")
webApi.WebApiHandler.HandleFunc(router, baseUri+"/function/find/", webApi.FindFunctions, "GET").Queries("query", "{query}")
webApi.WebApiHandler.HandleFunc(router, baseUri+"/function/name/", webApi.GetFunctionsNames, "GET")
webApi.WebApiHandler.HandleFunc(router, baseUri+"/function/{id:[0-9]+}/", webApi.GetFunctionById, "GET")
webApi.WebApiHandler.HandleFunc(router, baseUri+"/function/{id:[0-9]+}/body/", webApi.GetFunctionWithBodyById, "GET")
webApi.WebApiHandler.HandleFunc(router, baseUri+"/function/", webApi.CreateFunction, "POST")
webApi.WebApiHandler.HandleFunc(router, baseUri+"/function/{id:[0-9]+}/", webApi.UpdateFunction, "PUT")
webApi.WebApiHandler.HandleFunc(router, baseUri+"/function/{id:[0-9]+}/", webApi.DeleteFunction, "DELETE")
webApi.WebApiHandler.HandleFunc(router, baseUri+"/function/filter/", webApi.FilterFunctions, "GET")
进入全屏模式 退出全屏模式
结论
我们制作这个软件包并不是因为我们手头有很多空闲时间,而是因为每次我们处理大事时都会遇到大量与 CORS 相关的错误,我们被迫这样做。现在我们有了这个解决方案,CORS 不再是问题。
更多推荐


所有评论(0)