使用Go完成常见的App Engine处理程序任务

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

Accomplishing common App Engine handler tasks with Go

问题

这是一个最佳实践的问题,可能没有一个正确的答案。似乎大多数处理程序在开始处理特定于处理程序的工作之前需要执行一些常见的初始化工作。例如,用户认证、检测区域设置和加载翻译字符串、检查memcached值等。

init中处理其中一些任务似乎是合理的,但大多数任务需要Http.Requestappengine.Context。据我所见,这留下了三个选择:

  1. 实现ServeHTTP并在最后添加执行自定义初始化函数的能力。问题是,我将无法使用实现了自己的ServeHTTP的Gorilla mux。

  2. 使用fork的mux版本(不太理想)。

  3. 在应用程序中的每个处理程序的开头放置一个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:

  1. 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 own ServeHTTP.

  2. Use a forked version of mux (less than ideal).

  3. 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 in ServeHTTP.

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

huangapple
  • 本文由 发表于 2013年1月16日 08:34:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/14349551.html
匿名

发表评论

匿名网友

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

确定