Как обрабатывать ошибку 404 в GAE с помощью http.FileServer в Golang

Я использую Google App Engine для обслуживания своего (полу)статического веб-сайта, созданного с помощью Hugo. У меня есть общедоступный каталог, в котором хранятся и обслуживаются все файлы 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, но предпочел бы (если возможно) не использовать внешние библиотеки, чтобы упростить его.

Основываясь на этом сообщении, я попытался следующее

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-страницы, однако я до сих пор не могу понять, как обрабатывать коды ответа сервера.

Любая помощь будет высоко оценен. Спасибо!


person pascal    schedule 04.09.2016    source источник


Ответы (2)


Чтобы ПО промежуточного слоя не было связано с http.FileServer, когда вы его создаете, вы можете передать конкретную реализацию http.ResponseWriter что:

  1. накапливать заголовки на случай, если их нужно будет удалить (если WriteHeader вызывается с ошибкой 404)
  2. if WriteHeader is called with a 404:
    1. dismiss accumulated headers
    2. отправить пользовательский 404
    3. игнорировать вызовы с Write из обернутого обработчика
  3. if WriteHeader is not called, or called with a non-404, then:
    1. emit accumulated headers to the real ResponseWriter
    2. направить вызовы 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 = &notFoundInterceptorWriter{
            rw:              w,
            h:               make(http.Header),
            notFoundHandler: notFoundHandler,
            r:               r,
        }
        h.ServeHTTP(w, r)
    })
}
person Thomas Broyer    schedule 20.09.2016
comment
Для будущих читателей, если вам нужно отловить ошибки на полпути к рендерингу шаблона или вы хотите изменить сигнатуру обработчика (например, вернуть ошибку из обработчика), я бы полностью рекомендовал этот подход обертывания модуля записи ответа, чтобы добавить пользовательская логика к нему. - person ernestoalejo; 20.09.2016
comment
Спасибо, Томас Бройер, это решение работает хорошо. Я реализовал notFoundHandler, который просто обслуживает мой статический файл 404.html. - person pascal; 24.09.2016

Вы можете проверить файл перед его подачей, чтобы увидеть, существует ли он. При необходимости адаптируйте обработчик 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)
}
person ernestoalejo    schedule 20.09.2016