英文:
How to handle 404 Error on GAE using http.FileServer in Golang
问题
我正在使用Google App Engine来提供使用Hugo生成的(半)静态网站。我有一个名为"public"的目录,其中存储了所有的HTML文件,并且需要提供这些文件。我还有一些用于处理联系表单等服务器端脚本。app.yaml
文件如下所示:
runtime: go
api_version: go1
handlers:
- url: /.*
script: _go_app
secure: always
简化的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,但如果可能的话,我更愿意不使用外部库,以保持简单。
根据这篇文章,我尝试了以下代码:
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页面,但我仍然无法弄清楚如何处理服务器响应代码。
如果有任何帮助,将不胜感激。
谢谢!
英文:
I am using Google App Engine to serve my (semi-)static website generated with Hugo. I have a directory "public" where all the HTML files are stored and are to be served. I also have some server-side scripts for the contact form handling for example. The app.yaml
file looks like this.
// app.yaml
runtime: go
api_version: go1
handlers:
- url: /.*
script: _go_app
secure: always
And the simplified main.go
file looks like this
// 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)
}
This works perfectly well and serves the html files. However, I am looking at a solution to handle the cases where the pages are not found and the response is 404 Not Found
for example (or any other server error).
My thought was to create a custom handler which can be passed in the http.Handle("/", myCustomHandler)
and would handle the server response and would redirect to a custom 404.html
or the like if necessary. I am new to Go and can't seem to figure out how this should be implemented. I have also looked at the Gorilla Mux, but would prefer (if possible) not to use external libraries to keep it simple.
Based on this post, I have tried the following
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)
}
This solution works in the sense that it also does serve my HTML pages, however I still can't figure out how to handle the server response codes.
Any help would be highly appreciated.
Thanks!
答案1
得分: 3
为了使中间件与http.FileServer
解耦,您可以通过传递一个特定的http.ResponseWriter
实现来包装它,该实现将:
- 累积标头,以防需要丢弃它们(如果使用404调用
WriteHeader
)。 - 如果使用404调用
WriteHeader
:- 忽略累积的标头
- 发送自定义的404响应
- 忽略来自包装处理程序的
Write
调用
- 如果未调用
WriteHeader
或调用非404状态码,则:- 将累积的标头发送到真正的
ResponseWriter
- 将
WriteHeader
和Write
调用路由到真正的ResponseWriter
- 将累积的标头发送到真正的
type notFoundInterceptorWriter struct {
rw http.ResponseWriter // 设置为nil以表示已拦截到404
h http.Header // 设置为nil以表示已发出标头
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)
}
// 忽略,所以假设一切都写入成功
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)
})
}
英文:
To keep the middleware decoupled from the http.FileServer
, as you're wrapping it, you can pass a specific implementation of http.ResponseWriter
that will:
- accumulate headers, in case they'd need to be ditched away (if
WriteHeader
is called with a 404) - if
WriteHeader
is called with a 404: - dismiss accumulated headers
- send a custom 404
- ignore calls with
Write
from the wrapped handler - if
WriteHeader
is not called, or called with a non-404, then: - emit accumulated headers to the real
ResponseWriter
- route the
WriteHeader
andWrite
calls to the realResponseWriter
<!-- language: go -->
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 = &notFoundInterceptorWriter{
rw: w,
h: make(http.Header),
notFoundHandler: notFoundHandler,
r: r,
}
h.ServeHTTP(w, r)
})
}
答案2
得分: 1
你可以在提供文件之前对其进行状态检查,以查看文件是否存在。根据需要调整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
}
http.ServeFile(w, r, name)
}
英文:
You can stat the file before serving it to see if it exists. Adapt the 404 handler as needed (emit a template, etc.)
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)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论