网上找的有坑,调查半天,原来是中间件实现的不对,特此分享一下,希望对其他人有帮助。
//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>
访问接口通过:
具体原理,可以参考这篇文章:
实测中,如果前端接口写错,仍然会报跨域错误。看了下 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: