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

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

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

  1. 累积标头,以防需要丢弃它们(如果使用404调用WriteHeader)。
  2. 如果使用404调用WriteHeader
    1. 忽略累积的标头
    2. 发送自定义的404响应
    3. 忽略来自包装处理程序的Write调用
  3. 如果未调用WriteHeader或调用非404状态码,则:
    1. 将累积的标头发送到真正的ResponseWriter
    2. WriteHeaderWrite调用路由到真正的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 = &notFoundInterceptorWriter{
            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:

  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 -->

    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 &amp;&amp; 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 = &amp;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 ( 
&quot;net/http&quot;
&quot;path&quot;
&quot;os&quot;
)
func init() {
http.Handle(&quot;/&quot;, staticHandler)
}
func error404Handler(w http.ResponseWriter, r *http.Request) {
http.Error(w, &quot;404 not found&quot;, 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, &quot;internal error&quot;, http.StatusInternalServerError)
return
}
return http.ServeFile(w, r, name)
}

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:

确定