在 Golang 中如何编写允许跨域访问的 Handlers
可以直接在原有的 Handler 里面加东西,也可以用 中间件 。 也可以再搞一个代理服务器。
1. 问题的背景
假如你有一个很简单的 Golang 服务器程序:
/* handler.go */
package main
import (
"log"
"net/http"
)
func serveHello(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello, world"))
}
func main() {
log.Fatal(http.ListenAndServe(":8080", http.HandlerFunc(serveHello)))
}
然后很自然地,你就可以运行它。
go run ./handler.go
# 开始监听 8080 端口,对所有的 http 请求,都会返回 hello, world
但是这个 服务器不支持 跨域资源共享 (Cross-Origin Resource Sharing, CORS)
。意思就是说,你打开浏览器,如果浏览器地址栏里面的域名不是 127.0.0.1:8080
,那么就没有办法用 XMLHttpRequest
对服务器发起请求。
如果硬要发起请求的话,就会出现 Cross-Origin Request Blocked
之类的
错误,如下图所示。
2. 解法:修改服务器的代码,使服务器允许 CORS
最简单的办法就是修改 serveHello
函数。在 serveHello
里面加上一行:
-/* handler.go */
+/* handler_allow_cors.go */
package main
import (
"log"
"net/http"
)
func serveHello(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Access-Control-Allow-Origin", "*")
w.Write([]byte("hello, world"))
}
func main() {
log.Fatal(http.ListenAndServe(":8080", http.HandlerFunc(serveHello)))
}
重新编译、重启服务器,再试一次,就发现 OK 了。
但是,有时候,只加 Access-Control-Allow-Origin
是不够的。
如果是 POST 或者其他类型的请求,还得设置其他的 Response Header,
还要处理 CORS Preflight:
-/* handler_allow_cors.go */
+/* handler_allow_cors_more.go */
package main
import (
"log"
"net/http"
)
func serveHello(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
+ w.Header().Set(
+ "Access-Control-Allow-Methods",
+ "POST, GET, OPTIONS, PUT, DELETE",
+ )
+ w.Header().Set(
+ "Access-Control-Allow-Headers",
+ "Accept, Content-Type, Content-Length",
+ )
+ if (*r).Method == "OPTIONS" {
+ return
+ }
w.Write([]byte("hello, world"))
}
func main() {
log.Fatal(http.ListenAndServe(":8080", http.HandlerFunc(serveHello)))
}
3. 中间件封装
但是假如有很多个 handlers 的话,我们 不想复制代码,然后在每个 handler 里面粘贴很多遍。
这个时候就可以把允许 CORS 的代码封装成中间件:
-/* handler_allow_cors_more.go */
+/* handler_allow_cors_using_middleware.go */
package main
import (
"log"
"net/http"
)
func serveHello(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Access-Control-Allow-Origin", "*")
- w.Header().Set(
- "Access-Control-Allow-Methods",
- "POST, GET, OPTIONS, PUT, DELETE",
- )
- w.Header().Set(
- "Access-Control-Allow-Headers",
- "Accept, Content-Type, Content-Length",
- )
- if (*r).Method == "OPTIONS" {
- return
- }
-
+ // 现在 serveHello 变回了最初的样子:
w.Write([]byte("hello, world"))
}
+func UNSAFE_enableCORS(h http.Handler) http.Handler {
+ return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Access-Control-Allow-Origin", "*")
+ w.Header().Set(
+ "Access-Control-Allow-Methods",
+ "POST, GET, OPTIONS, PUT, DELETE",
+ )
+ w.Header().Set(
+ "Access-Control-Allow-Headers",
+ "Accept, Content-Type, Content-Length",
+ )
+ if (*r).Method == "OPTIONS" {
+ return
+ }
+
+ h.ServeHTTP(w, r)
+ })
+}
func main() {
- log.Fatal(http.ListenAndServe(":8080", http.HandlerFunc(serveHello)))
+ log.Fatal(
+ http.ListenAndServe(
+ ":8080",
+ UNSAFE_enableCORS(http.HandlerFunc(serveHello)),
+ )
+ )
}
【旁注】什么叫“中间件”?
在这里,中间件(Middleware)指的就是接受一个 http.Handler,返回一个 http.Handler 的函数(见文章底部的“相关链接”)。比如上面代码中的
UNSAFE_enableCORS
就是一个中间件。
之后,如果想让某个 handler (比如说 serveContent
)
支持跨域访问的话,就用 UNSAFE_enableCORS(serveContent)
即可。
4. 能不能不改服务器的代码?
封装了中间件以后,其实只要在 main 函数里面改代码就可以了, handler 里面的代码不用改。但是假如服务器不在你手上,你根本改不了的话, 那该怎么办呢?
这个时候就可以设置代理服务器。比如,可以在本机上架个 nginx,然后在配置文件
里面设置 proxy_pass
,然后用 add_header
覆写原来的 Response Header。
假如你是前端程序员,想用别人的服务器做测试,也可以用 webpack 的 devServer 自己写一个代理服务器。
有点离题了。就到这里吧。
相关链接
- 【外链】MDN 对 CORS 的介绍
- 【外链】在 Golang 中允许跨域访问
- 【外链】什么是中间件
- 【外链】什么是中间件 - MDN