go kratos框架跨域中间件实现(v2)

背景

网上找的有坑,调查半天,原来是中间件实现的不对,特此分享一下,希望对其他人有帮助。

实现

中间件

//MiddlewareCors 设置跨域请求头
func MiddlewareCors() middleware.Middleware {
   return func(handler middleware.Handler) middleware.Handler {
      return func(ctx context.Context, req interface{}) (interface{}, error) {
         if ts, ok := transport.FromServerContext(ctx); ok {
            if ht, ok := ts.(http.Transporter); ok {
               ht.ReplyHeader().Set("Access-Control-Allow-Origin", "*")
               ht.ReplyHeader().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,PATCH,DELETE")
               ht.ReplyHeader().Set("Access-Control-Allow-Credentials", "true")
               ht.ReplyHeader().Set("Access-Control-Allow-Headers", "Content-Type,"+
                  "X-Requested-With,Access-Control-Allow-Credentials,User-Agent,Content-Length,Authorization")
            }
         }
         return handler(ctx, req)
      }
   }
}

使用

// NewHTTPServer new a HTTP server.
func NewHTTPServer(c *conf.Server, timeline *service.TimelineService, logger log.Logger) *http.Server {
   var opts = []http.ServerOption{
      http.Middleware(
         recovery.Recovery(),
         logging.Server(logger),
         MiddlewareCors(),
      ),
   }
   if c.Http.Network != "" {
      opts = append(opts, http.Network(c.Http.Network))
   }
   if c.Http.Addr != "" {
      opts = append(opts, http.Address(c.Http.Addr))
   }
   if c.Http.Timeout != nil {
      opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration()))
   }
   srv := http.NewServer(opts...)
   v1.RegisterTimelineHTTPServer(srv, timeline)
   return srv
}

前端

前端啥也不用设置,正常使用即可。

下面的代码是前后端分离的,用go启动了一个静态html服务(端口是3000),本地服务的端口是8000,所以是跨域的。

    <script type="text/javascript">
      let lastRead = 0;
      axios.defaults.baseURL = "http://localhost:8000";

      function syncMsg() {
        let uri = "member=" + getUserId() + "&last_read=" + lastRead + "&count=10";
        axios.get("/timeline/sync?" + uri).then(function (response) {
          console.log(response);
        });
      }

      function getUserId() {
        return document.getElementById("from_user_id").value;
      }
    </script>

访问接口通过:

具体原理,可以参考这篇文章:

  • 4种方法解决js跨域的实现方式

404跨域

实测中,如果前端接口写错,仍然会报跨域错误。看了下 kratos 的 http.NewServer()的源码:

// NewServer creates an HTTP server by options.
func NewServer(opts ...ServerOption) *Server {
   srv := &Server{
      network:     "tcp",
      address:     ":0",
      timeout:     1 * time.Second,
      middleware:  matcher.New(),
      dec:         DefaultRequestDecoder,
      enc:         DefaultResponseEncoder,
      ene:         DefaultErrorEncoder,
      strictSlash: true,
   }
   for _, o := range opts {
      o(srv)
   }
   srv.router = mux.NewRouter().StrictSlash(srv.strictSlash)
   srv.router.NotFoundHandler = http.DefaultServeMux
   srv.router.MethodNotAllowedHandler = http.DefaultServeMux
   srv.router.Use(srv.filter())
   srv.Server = &http.Server{
      Handler:   FilterChain(srv.filters...)(srv.router),
      TLSConfig: srv.tlsConf,
   }
   return srv
}

我们发现针对404的默认处理没有支持跨域,仍然是用 runtime net/http 包的默认处理函数。

新增 NotFoundHandler :

func NotFoundHandler(res nethttp.ResponseWriter, req *nethttp.Request) {
   res.Header().Set("Access-Control-Allow-Origin", "*")
   res.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,PATCH,DELETE")
   res.Header().Set("Access-Control-Allow-Credentials", "true")
   res.Header().Set("Access-Control-Allow-Headers", "Content-Type,"+
      "X-Requested-With,Access-Control-Allow-Credentials,User-Agent,Content-Length,Authorization")

   log.Info("NotFoundHandler")

   errCode := errors.NotFound("page not found", "page not found")
   buffer, _ := json.Marshal(errCode)
   //res.WriteHeader(400)
   _, _ = res.Write(buffer)
}

使用:

// NewHTTPServer new a HTTP server.
func NewHTTPServer(c *conf.Server, timeline *service.TimelineService, logger log.Logger) *http.Server {
   // 这里,注册not found路由
   nethttp.HandleFunc("/", NotFoundHandler)

   var opts = []http.ServerOption{
      http.Middleware(
         recovery.Recovery(),
         logging.Server(logger),
         MiddlewareCors(),
      ),
   }
   // ...
}

nethttp包会使用默认的 Mux:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
     DefaultServeMux.HandleFunc(pattern, handler)
}

也就是kratos中 srv.router.NotFoundHandler = http.DefaultServeMux 指定的Mux。

Reference:

  • implementing_a_custom_404_error_message
  • an-introduction-to-handlers-and-servemuxes-in-go
  • golang-dissecting-listen-and-serve

你可能感兴趣的:(Golang学习和进阶,golang,中间件,json)