英文:
Accomplishing common App Engine handler tasks with Go
问题
这是一个最佳实践的问题,可能没有一个正确的答案。似乎大多数处理程序在开始处理特定于处理程序的工作之前需要执行一些常见的初始化工作。例如,用户认证、检测区域设置和加载翻译字符串、检查memcached值等。
在init
中处理其中一些任务似乎是合理的,但大多数任务需要Http.Request
或appengine.Context
。据我所见,这留下了三个选择:
-
实现
ServeHTTP
并在最后添加执行自定义初始化函数的能力。问题是,我将无法使用实现了自己的ServeHTTP
的Gorilla mux。 -
使用fork的mux版本(不太理想)。
-
在应用程序中的每个处理程序的开头放置一个
startHandler
函数。虽然这似乎很麻烦,但我想这样做可以明确地说明正在发生的事情,而不是在ServeHTTP
中“隐藏”公共代码。
如何处理所有处理程序共有的任务是首选的方式?我是否漏掉了其他方法?
这是一个完整的App Engine示例,展示了minikomi答案中概述的方法。还值得访问Jeff Wendling的教程。
package app
import (
"fmt"
"log"
"net/http"
"appengine"
"appengine/datastore"
"github.com/gorilla/context"
"github.com/gorilla/mux"
)
type Config struct {
DefaultLocale string
DefaultTimezone string
}
type ContextKey int
const (
SiteConfig ContextKey = iota
// ...
)
type InitHandler func(http.ResponseWriter, *http.Request, appengine.Context)
func (h InitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 所有处理程序初始化任务在这里进行
c := appengine.NewContext(r)
k := datastore.NewKey(c, "Config", "site:config", 0, nil)
config := new(Config)
if err := datastore.Get(c, k, config); err != nil {
log.Fatal("无法从数据存储中读取配置:%s\n", err.Error())
}
context.Set(r, SiteConfig, config)
// 最后,调用处理程序本身
h(w, r, c)
}
func init () {
r := mux.NewRouter()
r.Handle("/", InitHandler(home)) // 注意:不是r.HandleFunc!
http.Handle("/", r)
}
func home(w http.ResponseWriter, r *http.Request, c appengine.Context) {
site := context.Get(r, SiteConfig).(*Config)
fmt.Fprintf(w, "区域设置:%s,时区:%s。", site.DefaultLocale, site.DefaultTimezone)
}
有一段时间让我困惑的是需要使用router.Handle
而不是router.HandleFunc
。假设数据存储中有一个适当的实体,输出如下:
区域设置:en_US,时区:UTC。
英文:
This is a best-practice question that probably doesn't have one correct answer. It seems that most of my handlers need to perform a number of common initialisation jobs before starting on work specific to the handler. Examples would be user auth, detecting locale and loading translated strings, checking memcached values, and so on.
It would seem reasonable to take care of some of these tasks within init
, but most require the Http.Request
or the appengine.Context
. As far as I can see this leaves three choices:
-
Implement
ServeHTTP
and add the ability to execute a custom init function at the end. Problem being, I wouldn't be able to use Gorilla mux which implements its ownServeHTTP
. -
Use a forked version of mux (less than ideal).
-
Put a
startHandler
function at the beginning of each and every handler throughout the app. Seems cumbersome, although I suppose it makes it plain exactly what's happening as opposed to 'hiding' common code inServeHTTP
.
What's the preferred way to take care of jobs common to all handlers? Am I missing another approach?
Here's a complete App Engine example of the approach outlined in minikomi's answer. It's also well-worth visiting Jeff Wendling's tutorial.
package app
import (
"fmt"
"log"
"net/http"
"appengine"
"appengine/datastore"
"github.com/gorilla/context"
"github.com/gorilla/mux"
)
type Config struct {
DefaultLocale string
DefaultTimezone string
}
type ContextKey int
const (
SiteConfig ContextKey = iota
// ...
)
type InitHandler func(http.ResponseWriter, *http.Request, appengine.Context)
func (h InitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// All handler initialisation tasks go here
c := appengine.NewContext(r)
k := datastore.NewKey(c, "Config", "site:config", 0, nil)
config := new(Config)
if err := datastore.Get(c, k, config); err != nil {
log.Fatal("Couldn't read config from datastore: %s\n", err.Error())
}
context.Set(r, SiteConfig, config)
// Finally, call the handler itself
h(w, r, c)
}
func init () {
r := mux.NewRouter()
r.Handle("/", InitHandler(home)) // Note: NOT r.HandleFunc!
http.Handle("/", r)
}
func home(w http.ResponseWriter, r *http.Request, c appengine.Context) {
site := context.Get(r, SiteConfig).(*Config)
fmt.Fprintf(w, "Locale: %s, timezone: %s.", site.DefaultLocale, site.DefaultTimezone)
}
What threw me for a little while is the need to use router.Handle
and not router.HandleFunc
. Assuming an appropriate entity in the datastore, output looks like:
Locale: en_US, timezone: UTC.
答案1
得分: 8
你可以创建一个(func)类型,它具有一个ServeHTTP
函数,该函数可以完成你所需的所有操作,然后在内部调用原始函数,然后将你的处理程序转换为该类型:
package main
import (
"fmt"
"log"
"net/http"
)
type wrappedHandler func(w http.ResponseWriter, r *http.Request)
func (h wrappedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.Println("执行其他 GAE 操作")
h(w, r)
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi!")
}
func main() {
http.Handle("/", wrappedHandler(handler))
http.ListenAndServe(":8080", nil)
}
如果你想向handler()
函数传递一些内容,你可以将其添加到函数签名中,例如:
type wrappedHandler func(w http.ResponseWriter, r *http.Request, conn *db.Connection)
func (h wrappedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
conn := db.CreateConnection();
h(w, r, conn)
}
func handler(w http.ResponseWriter, r *http.Request, conn *db.Connection) {
data := conn.AllTheData()
fmt.Fprintf(w, data)
}
英文:
You can create a (func) type which has a ServeHTTP
that does everything you need, and then internally calls the original function, and then convert your handlers to that type:
package main
import (
"fmt"
"log"
"net/http"
)
type wrappedHandler func(w http.ResponseWriter, r *http.Request)
func (h wrappedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.Println("Do Other GAE Stuff")
h(w, r)
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi!")
}
func main() {
http.Handle("/", wrappedHandler(handler))
http.ListenAndServe(":8080", nil)
}
If you want to pass something in to the handler()
func, you can add it to the signature eg:
type wrappedHandler func(w http.ResponseWriter, r *http.Request, conn *db.Connection)
func (h wrappedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
conn := db.CreateConnection();
h(w, r, conn)
}
func handler(w http.ResponseWriter, r *http.Request, conn *db.Connection) {
data := conn.AllTheData()
fmt.Fprintf(w, data)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论