如何在 Golang 中使用 http.FileServer 处理 GAE 上的 404 错误
How to handle 404 Error on GAE using http.FileServer in Golang
我正在使用 Google App Engine 来为我使用 Hugo 生成的(半)静态网站提供服务。我有一个目录 "public",其中存储了所有 HTML 文件并提供服务。例如,我还有一些用于联系表单处理的服务器端脚本。 app.yaml
文件如下所示。
// app.yaml
runtime: go
api_version: go1
handlers:
- url: /.*
script: _go_app
secure: always
简化后的 main.go
文件如下所示
// main.go
package main
import (
"net/http"
"encoding/json"
"appengine"
"appengine/urlfetch"
)
func init() {
fileHandler := http.FileServer(http.Dir("public"))
http.Handle("/", fileHandler)
http.HandleFunc("/contactus/", HandleContactus)
}
这工作得很好,可以为 html 文件提供服务。但是,我正在寻找一种解决方案来处理找不到页面且响应为 404 Not Found
(或任何其他服务器错误)的情况。
我的想法是创建一个自定义处理程序,它可以在 http.Handle("/", myCustomHandler)
中传递并处理服务器响应,并在必要时重定向到自定义 404.html
等。我是 Go 的新手,似乎无法弄清楚应该如何实施。我也看过 Gorilla Mux,但宁愿(如果可能的话)不使用外部库来保持简单。
基于this post,我尝试了以下
package main
import (
"net/http"
"encoding/json"
"appengine"
"appengine/urlfetch"
)
func StaticSiteHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
h.ServeHTTP(w, r)
})
}
func init() {
fileHandler := http.FileServer(http.Dir("public"))
http.Handle("/", StaticSiteHandler(fileHandler))
http.HandleFunc("/contactus/", HandleContactus)
}
这个解决方案的工作原理是它也为我的 HTML 页面提供服务,但我仍然无法弄清楚如何处理服务器响应代码。
如有任何帮助,我们将不胜感激。
谢谢!
您可以在提供文件之前统计文件以查看它是否存在。根据需要调整 404 处理程序(发出模板等)
package main
import (
"net/http"
"path"
"os"
)
func init() {
http.Handle("/", staticHandler)
}
func error404Handler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "404 not found", http.StatusNotFound)
}
func staticHandler(w http.ResponseWriter, r *http.Request) {
name := path.Clean(r.URL.Path)
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
error404Handler(w, r)
return
}
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
return http.ServeFile(w, r, name)
}
为了使中间件与 http.FileServer
分离,当您包装它时,您可以传递 http.ResponseWriter
的特定实现,它将:
- 积累 headers,以防他们需要被抛弃(如果
WriteHeader
被调用为 404)
- 如果使用 404 调用
WriteHeader
:
- 解雇累计headers
- 发送自定义 404
- 忽略来自包装处理程序
Write
的调用
- 如果
WriteHeader
没有被调用,或者用非 404 调用,那么:
- 排放累计headers到实际
ResponseWriter
- 将
WriteHeader
和 Write
调用路由到真正的 ResponseWriter
type notFoundInterceptorWriter struct {
rw http.ResponseWriter // set to nil to signal a 404 has been intercepted
h http.Header // set to nil to signal headers have been emitted
notFoundHandler http.Handler
r *http.Request
}
func (rw *notFoundInterceptorWriter) Header() http.Header {
if rw.h == nil && rw.rw != nil {
return rw.rw.Header()
}
return rw.h
}
func (rw *notFoundInterceptorWriter) WriteHeader(status int) {
if status == http.StatusNotFound {
rw.notFoundHandler.ServeHTTP(rw.rw, rw.r)
rw.rw = nil
} else {
for k, vs := range rw.h {
for _, v := range vs {
rw.rw.Header().Add(k, v)
}
}
rw.rw.WriteHeader(status)
}
rw.h = nil
}
func (rw *notFoundInterceptorWriter) Write(b []byte) (int, error) {
if rw.rw != nil {
return rw.rw.Write(b)
}
// ignore, so do as if everything was written OK
return len(b), nil
}
func StaticSiteHandler(h, notFoundHandler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w = ¬FoundInterceptorWriter{
rw: w,
h: make(http.Header),
notFoundHandler: notFoundHandler,
r: r,
}
h.ServeHTTP(w, r)
})
}
我正在使用 Google App Engine 来为我使用 Hugo 生成的(半)静态网站提供服务。我有一个目录 "public",其中存储了所有 HTML 文件并提供服务。例如,我还有一些用于联系表单处理的服务器端脚本。 app.yaml
文件如下所示。
// app.yaml
runtime: go
api_version: go1
handlers:
- url: /.*
script: _go_app
secure: always
简化后的 main.go
文件如下所示
// main.go
package main
import (
"net/http"
"encoding/json"
"appengine"
"appengine/urlfetch"
)
func init() {
fileHandler := http.FileServer(http.Dir("public"))
http.Handle("/", fileHandler)
http.HandleFunc("/contactus/", HandleContactus)
}
这工作得很好,可以为 html 文件提供服务。但是,我正在寻找一种解决方案来处理找不到页面且响应为 404 Not Found
(或任何其他服务器错误)的情况。
我的想法是创建一个自定义处理程序,它可以在 http.Handle("/", myCustomHandler)
中传递并处理服务器响应,并在必要时重定向到自定义 404.html
等。我是 Go 的新手,似乎无法弄清楚应该如何实施。我也看过 Gorilla Mux,但宁愿(如果可能的话)不使用外部库来保持简单。
基于this post,我尝试了以下
package main
import (
"net/http"
"encoding/json"
"appengine"
"appengine/urlfetch"
)
func StaticSiteHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
h.ServeHTTP(w, r)
})
}
func init() {
fileHandler := http.FileServer(http.Dir("public"))
http.Handle("/", StaticSiteHandler(fileHandler))
http.HandleFunc("/contactus/", HandleContactus)
}
这个解决方案的工作原理是它也为我的 HTML 页面提供服务,但我仍然无法弄清楚如何处理服务器响应代码。
如有任何帮助,我们将不胜感激。 谢谢!
您可以在提供文件之前统计文件以查看它是否存在。根据需要调整 404 处理程序(发出模板等)
package main
import (
"net/http"
"path"
"os"
)
func init() {
http.Handle("/", staticHandler)
}
func error404Handler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "404 not found", http.StatusNotFound)
}
func staticHandler(w http.ResponseWriter, r *http.Request) {
name := path.Clean(r.URL.Path)
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
error404Handler(w, r)
return
}
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
return http.ServeFile(w, r, name)
}
为了使中间件与 http.FileServer
分离,当您包装它时,您可以传递 http.ResponseWriter
的特定实现,它将:
- 积累 headers,以防他们需要被抛弃(如果
WriteHeader
被调用为 404) - 如果使用 404 调用
WriteHeader
:- 解雇累计headers
- 发送自定义 404
- 忽略来自包装处理程序
Write
的调用
- 如果
WriteHeader
没有被调用,或者用非 404 调用,那么:- 排放累计headers到实际
ResponseWriter
- 将
WriteHeader
和Write
调用路由到真正的ResponseWriter
- 排放累计headers到实际
type notFoundInterceptorWriter struct {
rw http.ResponseWriter // set to nil to signal a 404 has been intercepted
h http.Header // set to nil to signal headers have been emitted
notFoundHandler http.Handler
r *http.Request
}
func (rw *notFoundInterceptorWriter) Header() http.Header {
if rw.h == nil && rw.rw != nil {
return rw.rw.Header()
}
return rw.h
}
func (rw *notFoundInterceptorWriter) WriteHeader(status int) {
if status == http.StatusNotFound {
rw.notFoundHandler.ServeHTTP(rw.rw, rw.r)
rw.rw = nil
} else {
for k, vs := range rw.h {
for _, v := range vs {
rw.rw.Header().Add(k, v)
}
}
rw.rw.WriteHeader(status)
}
rw.h = nil
}
func (rw *notFoundInterceptorWriter) Write(b []byte) (int, error) {
if rw.rw != nil {
return rw.rw.Write(b)
}
// ignore, so do as if everything was written OK
return len(b), nil
}
func StaticSiteHandler(h, notFoundHandler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w = ¬FoundInterceptorWriter{
rw: w,
h: make(http.Header),
notFoundHandler: notFoundHandler,
r: r,
}
h.ServeHTTP(w, r)
})
}