如何在使用Golang的http.FileServer时处理GAE上的404错误

huangapple go评论111阅读模式
英文:

How to handle 404 Error on GAE using http.FileServer in Golang

问题

我正在使用Google App Engine来提供使用Hugo生成的(半)静态网站。我有一个名为"public"的目录,其中存储了所有的HTML文件,并且需要提供这些文件。我还有一些用于处理联系表单等服务器端脚本。app.yaml文件如下所示:

  1. runtime: go
  2. api_version: go1
  3. handlers:
  4. - url: /.*
  5. script: _go_app
  6. secure: always

简化的main.go文件如下所示:

  1. package main
  2. import (
  3. "net/http"
  4. "encoding/json"
  5. "appengine"
  6. "appengine/urlfetch"
  7. )
  8. func init() {
  9. fileHandler := http.FileServer(http.Dir("public"))
  10. http.Handle("/", fileHandler)
  11. http.HandleFunc("/contactus/", HandleContactus)
  12. }

这个方案完全正常工作,并提供了HTML文件。然而,我正在寻找一种处理页面未找到的情况的解决方案,例如返回404 Not Found或其他服务器错误的响应。

我的想法是创建一个自定义处理程序,可以在http.Handle("/", myCustomHandler)中传递,并处理服务器响应,如果需要的话,重定向到自定义的404.html页面或类似页面。我对Go还不熟悉,似乎无法弄清楚应该如何实现这一点。我也看过Gorilla Mux,但如果可能的话,我更愿意不使用外部库,以保持简单。

根据这篇文章,我尝试了以下代码:

  1. package main
  2. import (
  3. "net/http"
  4. "encoding/json"
  5. "appengine"
  6. "appengine/urlfetch"
  7. )
  8. func StaticSiteHandler(h http.Handler) http.Handler {
  9. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
  10. h.ServeHTTP(w, r)
  11. })
  12. }
  13. func init() {
  14. fileHandler := http.FileServer(http.Dir("public"))
  15. http.Handle("/", StaticSiteHandler(fileHandler))
  16. http.HandleFunc("/contactus/", HandleContactus)
  17. }

这个解决方案在某种程度上也可以工作,它也提供了我的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.

  1. // app.yaml
  2. runtime: go
  3. api_version: go1
  4. handlers:
  5. - url: /.*
  6. script: _go_app
  7. secure: always

And the simplified main.go file looks like this

  1. // main.go
  2. package main
  3. import (
  4. "net/http"
  5. "encoding/json"
  6. "appengine"
  7. "appengine/urlfetch"
  8. )
  9. func init() {
  10. fileHandler := http.FileServer(http.Dir("public"))
  11. http.Handle("/", fileHandler)
  12. http.HandleFunc("/contactus/", HandleContactus)
  13. }

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

  1. package main
  2. import (
  3. "net/http"
  4. "encoding/json"
  5. "appengine"
  6. "appengine/urlfetch"
  7. )
  8. func StaticSiteHandler(h http.Handler) http.Handler {
  9. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
  10. h.ServeHTTP(w, r)
  11. })
  12. }
  13. func init() {
  14. fileHandler := http.FileServer(http.Dir("public"))
  15. http.Handle("/", StaticSiteHandler(fileHandler))
  16. http.HandleFunc("/contactus/", HandleContactus)
  17. }

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实现来包装它,该实现将:

  1. 累积标头,以防需要丢弃它们(如果使用404调用WriteHeader)。
  2. 如果使用404调用WriteHeader
    1. 忽略累积的标头
    2. 发送自定义的404响应
    3. 忽略来自包装处理程序的Write调用
  3. 如果未调用WriteHeader或调用非404状态码,则:
    1. 将累积的标头发送到真正的ResponseWriter
    2. WriteHeaderWrite调用路由到真正的ResponseWriter
  1. type notFoundInterceptorWriter struct {
  2. rw http.ResponseWriter // 设置为nil以表示已拦截到404
  3. h http.Header // 设置为nil以表示已发出标头
  4. notFoundHandler http.Handler
  5. r *http.Request
  6. }
  7. func (rw *notFoundInterceptorWriter) Header() http.Header {
  8. if rw.h == nil && rw.rw != nil {
  9. return rw.rw.Header()
  10. }
  11. return rw.h
  12. }
  13. func (rw *notFoundInterceptorWriter) WriteHeader(status int) {
  14. if status == http.StatusNotFound {
  15. rw.notFoundHandler.ServeHTTP(rw.rw, rw.r)
  16. rw.rw = nil
  17. } else {
  18. for k, vs := range rw.h {
  19. for _, v := range vs {
  20. rw.rw.Header().Add(k, v)
  21. }
  22. }
  23. rw.rw.WriteHeader(status)
  24. }
  25. rw.h = nil
  26. }
  27. func (rw *notFoundInterceptorWriter) Write(b []byte) (int, error) {
  28. if rw.rw != nil {
  29. return rw.rw.Write(b)
  30. }
  31. // 忽略,所以假设一切都写入成功
  32. return len(b), nil
  33. }
  34. func StaticSiteHandler(h, notFoundHandler http.Handler) http.Handler {
  35. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  36. w = &notFoundInterceptorWriter{
  37. rw: w,
  38. h: make(http.Header),
  39. notFoundHandler: notFoundHandler,
  40. r: r,
  41. }
  42. h.ServeHTTP(w, r)
  43. })
  44. }
英文:

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:

  1. accumulate headers, in case they'd need to be ditched away (if WriteHeader is called with a 404)
  2. if WriteHeader is called with a 404:
  3. dismiss accumulated headers
  4. send a custom 404
  5. ignore calls with Write from the wrapped handler
  6. if WriteHeader is not called, or called with a non-404, then:
  7. emit accumulated headers to the real ResponseWriter
  8. route the WriteHeader and Write calls to the real ResponseWriter

<!-- language: go -->

  1. type notFoundInterceptorWriter struct {
  2. rw http.ResponseWriter // set to nil to signal a 404 has been intercepted
  3. h http.Header // set to nil to signal headers have been emitted
  4. notFoundHandler http.Handler
  5. r *http.Request
  6. }
  7. func (rw *notFoundInterceptorWriter) Header() http.Header {
  8. if rw.h == nil &amp;&amp; rw.rw != nil {
  9. return rw.rw.Header()
  10. }
  11. return rw.h
  12. }
  13. func (rw *notFoundInterceptorWriter) WriteHeader(status int) {
  14. if status == http.StatusNotFound {
  15. rw.notFoundHandler.ServeHTTP(rw.rw, rw.r)
  16. rw.rw = nil
  17. } else {
  18. for k, vs := range rw.h {
  19. for _, v := range vs {
  20. rw.rw.Header().Add(k, v)
  21. }
  22. }
  23. rw.rw.WriteHeader(status)
  24. }
  25. rw.h = nil
  26. }
  27. func (rw *notFoundInterceptorWriter) Write(b []byte) (int, error) {
  28. if rw.rw != nil {
  29. return rw.rw.Write(b)
  30. }
  31. // ignore, so do as if everything was written OK
  32. return len(b), nil
  33. }
  34. func StaticSiteHandler(h, notFoundHandler http.Handler) http.Handler {
  35. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  36. w = &amp;notFoundInterceptorWriter{
  37. rw: w,
  38. h: make(http.Header),
  39. notFoundHandler: notFoundHandler,
  40. r: r,
  41. }
  42. h.ServeHTTP(w, r)
  43. })
  44. }

答案2

得分: 1

你可以在提供文件之前对其进行状态检查,以查看文件是否存在。根据需要调整404处理程序(发出模板等)。

  1. package main
  2. import (
  3. "net/http"
  4. "path"
  5. "os"
  6. )
  7. func init() {
  8. http.Handle("/", staticHandler)
  9. }
  10. func error404Handler(w http.ResponseWriter, r *http.Request) {
  11. http.Error(w, "404 not found", http.StatusNotFound)
  12. }
  13. func staticHandler(w http.ResponseWriter, r *http.Request) {
  14. name := path.Clean(r.URL.Path)
  15. if _, err := os.Stat(name); err != nil {
  16. if os.IsNotExist(err) {
  17. error404Handler(w, r)
  18. return
  19. }
  20. http.Error(w, "internal error", http.StatusInternalServerError)
  21. return
  22. }
  23. http.ServeFile(w, r, name)
  24. }
英文:

You can stat the file before serving it to see if it exists. Adapt the 404 handler as needed (emit a template, etc.)

  1. package main
  2. import (
  3. &quot;net/http&quot;
  4. &quot;path&quot;
  5. &quot;os&quot;
  6. )
  7. func init() {
  8. http.Handle(&quot;/&quot;, staticHandler)
  9. }
  10. func error404Handler(w http.ResponseWriter, r *http.Request) {
  11. http.Error(w, &quot;404 not found&quot;, http.StatusNotFound)
  12. }
  13. func staticHandler(w http.ResponseWriter, r *http.Request) {
  14. name := path.Clean(r.URL.Path)
  15. if _, err := os.Stat(name); err != nil {
  16. if os.IsNotExist(err) {
  17. error404Handler(w, r)
  18. return
  19. }
  20. http.Error(w, &quot;internal error&quot;, http.StatusInternalServerError)
  21. return
  22. }
  23. return http.ServeFile(w, r, name)
  24. }

huangapple
  • 本文由 发表于 2016年9月4日 23:10:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/39318367.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定