简介

如今,大型后端解决方案通常看起来像**一组微服务相互交互并与一个或多个前端(通常是浏览器)应用程序**交互。

这些服务可以分布在**多个虚拟机或物理机** 上,因此每个应用程序(即托管应用程序的服务器)都会获得一个**不同的域名/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}/- 用于删除现有用户

基本上,我们必须实现以下几点:

  1. 向端点添加对 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}/的值发送;

  1. 在服务器端添加可选的来源检查(如有必要)。

今天的文章是关于第一个的,你可以找到第二个github.com\gorilla\handlers。它有一个中间件,限制只能访问特定的来源。也许您注意到您的后端并不总是需要响应 OPTIONS 请求,因为浏览器没有发送 OPTIONS 请求简单请求。但在上面的例子中,我们必须这样做。当您的 api 非常大时,您必须不断添加预检处理程序(请参见下面的示例),并且每隔一段时间 您可能会忘记这样做,尤其是当您刚刚完成一些复杂端点的设计时。使用我们的 (Wissance LLC) 解决方案(开源 github 包),您可以忘记添加预检处理程序,因为我们的包会自动执行此操作。 如果您觉得我们的套餐对您有用(这对我们非常重要),请给我们一颗星。

如何轻松完成所有这些工作

我们实现了自己的HandlerFunc,其签名与mux.Router.HandlerFunc类似,但有一些细微差别:

  1. 我们将一个指向mux.Router的指针作为第一个参数传递。我们这样做是因为我们也需要使用子路由器,看看这个单元测试你可以使用参考。

  2. 在分配路由处理程序时,我们将处理程序方法参数作为最后一个可变参数传递,而不是调用.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 不再是问题。

Logo

ModelScope旨在打造下一代开源的模型即服务共享平台,为泛AI开发者提供灵活、易用、低成本的一站式模型服务产品,让模型应用更简单!

更多推荐