2016-09-04 298 views
2

我正在使用Google App Engine来服务我使用Hugo生成的(半)静态网站。我有一个目录“公共”,其中所有的HTML文件都存储并将被提供。例如,我还有一些用于联系人表单处理的服务器端脚本。 app.yaml文件看起来像这样。如何在Golang中使用http.FileServer处理404错误使用http.FileServer

// 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页面,但我仍然无法弄清楚如何处理服务器响应代码。

任何帮助将不胜感激。 谢谢!

回答

3

为了保持从http.FileServer解耦中间件,为你包装,你可以通过一个具体实施http.ResponseWriter将:

  1. 积累头,万一他们会需要被抛弃掉(如果WriteHeader是带一个404)
  2. 如果WriteHeader是带一个404:
    1. DIS小姐积累头
    2. 发送自定义404
    3. 忽略来自包裹处理程序与Write电话
  3. 如果WriteHeader不叫,或者叫与非404,则:
    1. 发出积累头到真ResponseWriter
    2. 路由WriteHeaderWrite调用真实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 = &notFoundInterceptorWriter{ 
      rw:    w, 
      h:    make(http.Header), 
      notFoundHandler: notFoundHandler, 
      r:    r, 
     } 
     h.ServeHTTP(w, r) 
    }) 
} 
+0

对于未来的读者,如果您需要捕获错误到渲染模板中途,或者你想更改处理程序的签名(从处理程序返回一个错误,例如)我将完全推荐这种包装响应写入器的方法来为其添加自定义逻辑。 – ernestoalejo

+0

谢谢Thomas Broyer,这个解决方案效果很好。我已经实现了一个notFoundHandler,它只是为我的静态404.html文件提供服务。 – pascal

0

您可以在提供文件之前对文件进行统计,看它是否存在。根据需要调整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) 
}