Documentation ¶
Overview ¶
Package app provides shared foundation for server based applications.
CONTEXT ¶
We do not depend on information stored per request. Instead, we pass all the information that is needed for each function via arguments.
All interaction in the application may require a Context. The Context includes (for example):
- app-engine.Context
- tx (is this in a transaction?)
- util.SafeStore (contains all information that normally goes into request attributes)
Handlers should take an app.Context. The top-level handler should create it and pass it down the function chain. WebRouter supports this natively with a Context object.
DRIVER ¶
This is the interface between the services and the backend system. It is exposed by an app.Context.
Some typical things the Driver will support:
- SendMail(...)
- Give me an appropriate *http.Client
- Store some entities in the backend
- Load an entity from backend given its id, or some of its properties
- ...
MISC ¶
Some misc info:
- To allow for rpc, testing, and other uses, we provide a header called "Z-App-Json-Response-On-Error" (app.UseJsonOnErrHttpHeaderKey). If set, then we return errors as a json string, as opposed to showing the user friendly, and browser friendly, error view page. RPC, Testing, etc will set this on their requests.
Base App ¶
This is the base of an actual application. It sets up everything and handles requests. By design, it fully implements app.AppDriver.
It sets up the following specifically:
- logging (calling logging.RunAsync if desired)
- app Driver (setup app.Svc)
- Initialize template sets for all the different views. Including creating a function map for the templates and defining an appropriate Render method
- Setup Routing logic ie how to route requests
- Setup OauthHandles for all our supported oauth providers
- map all requests to its builtin dispatcher (which wraps router.Dispatch and does pre and post things)
Why we did things a certain way:
- Wrapping ResponseWriter: So we can know if the headers have been written (ie response committed)
We need to differentiate code for dev environment from prod environment:
- Tests should not be shipped on prod
- LoadInit, other dev things should not even run on prod
This package expects the following:
- Define a route called "landing" (which is typically mapped to Path: /) so we can route to the landing page or show the link to the landing page
- Define views called "error", "notfound" so we can show something when either is encountered from code
- Also define view called "apperror", and we just show its content when there's an error without inheriting or depending on anyone else.
WEB ROUTER ¶
Intelligently dispatches requests to handlers.
It figures out what handler to dispatch a request to, by analysing any/all attributes of the request (headers and url). It also supports the ability to recreate a request (URL + Headers) based on the characteristics of a Route.
A Route is Matched if all its Routes or Route Expressions match.
It works as follows:
- A Route is a node in a tree. It can have children, and also have matchExpr to determine whether to proceed walking down the tree or not.
- At runtime, the package looks for the deepest Route which can handle a Request, in sequence. This means that a branch is checked, and if it matches, then its children are checked. All this recursively. If none of it's children can handle the request, then the branch handles it.
- You can also reverse-create a URL for a route, from the parameters of the route. For example, if a route has Host: {hostname}.mydomain.com, and Path: /show/{id}, you should be able to reconstruct the URL for that host, passing appropriate parameters for hostname and id.
An application will define functions with the signature:
(w http.ResponseWriter, r *http.Request) (int, os.Error)
These functions will handle requests, and return the appropriate http status code, and an error The application wrapper can then decide whether to do anything further based on these e.g. show a custom error view, etc
In addition, during a Dispatch, when Paths are matched, it will look for named variables and store them in a request-scoped store. This way, they are not parsed again, and can be used for:
- during request handling, to get variables
- during URL generation of a named route
An application using the router will have pseudo-code like:
----------------------------------------------------- func main() { go logging.Run() root = web.NewRoot("Root") web.NewRoute(root, "p1", p1).Path("/p1/${key}") ... http.HandleFunc("/", MyTopLevelHandler) } func MyTopLevelHandler(w http.ResponseWriter, r *http.Request) { ... some filtering work statusCode, err = web.Dispatch(ctx, root, w, r) ... some more filtering and error handling } -----------------------------------------------------
Index ¶
- Constants
- func CtxCtx(c Context) context.Context
- func Dispatch(ctx Context, root *Route, w http.ResponseWriter, r *http.Request) error
- func DumpRequest(c Context, r *http.Request) (err error)
- func NoMatchFoundHandler(c Context, w http.ResponseWriter, r *http.Request) error
- func RegisterAppDriver(appname string, driver Driver)
- func TrueExpr(store safestore.I, req *http.Request) (bool, error)
- func Vars(sf safestore.I) (vars map[string]string)
- type AppInfo
- type BaseApp
- type BaseDriver
- type BasicContext
- type BlobInfo
- type BlobReader
- type BlobWriter
- type Cache
- type Context
- type Driver
- type HTTPHandler
- type Handler
- type HandlerFunc
- type Key
- type LowLevelDriver
- type PageNotFoundError
- type QueryFilter
- type QueryFilterOp
- type QueryOpts
- type Route
- func (rt *Route) FindByName(name string) *Route
- func (rt *Route) Host(hostRegexp string) *Route
- func (rt *Route) Match(store safestore.I, req *http.Request) *Route
- func (rt *Route) Param(param string) *Route
- func (rt *Route) Path(pathRegexp string) *Route
- func (rt *Route) ToURL(params ...string) (u *url.URL, err error)
- func (rt *Route) ToURLX(params map[string]interface{}) (u *url.URL, err error)
- type SafeStoreCache
- func (ch SafeStoreCache) CacheDelete(ctx Context, keys ...interface{}) (err error)
- func (ch SafeStoreCache) CacheGet(ctx Context, items ...*safestore.Item) (err error)
- func (ch SafeStoreCache) CacheIncr(ctx Context, key interface{}, delta int64, initVal uint64) (newVal uint64, err error)
- func (ch SafeStoreCache) CachePut(ctx Context, items ...*safestore.Item) (err error)
- type Tier
- type User
Constants ¶
const ( VarsKey = "router_vars" LogTarget = "router" )
const ( //if this header is set, and there's an error, we will return response in json format. //this way, testing, rpc, etc do not have to deal with the error view for browsers. UseJsonOnErrHttpHeaderKey = "Z-App-Json-Response-On-Error" )
Variables ¶
This section is empty.
Functions ¶
func NoMatchFoundHandler ¶
This is the method that the root handler runs by default. The root handler is called if nothing matches. It just returns a "404" error.
func RegisterAppDriver ¶
Types ¶
type BaseApp ¶
type BaseDriver ¶
type BaseDriver struct { AppInfo Views *web.Views // = web.NewViews() PreRenderFn func(ctx Context, view string, data map[string]interface{}) error Root *Route }
func (*BaseDriver) Info ¶
func (gapp *BaseDriver) Info() *AppInfo
func (*BaseDriver) LandingPageURL ¶
func (gapp *BaseDriver) LandingPageURL(ctx Context, includeHost bool) (s string, err error)
func (*BaseDriver) Render ¶
func (gapp *BaseDriver) Render(ctx Context, view string, data map[string]interface{}, wr io.Writer) (err error)
App Render method, which adds some variables to the data for the templates. Note: All added keys start with Z. (so application code should not add keys which start with Z).
type BasicContext ¶
This is safe for copying, and embedding as a value.
func (*BasicContext) AppUUID ¶
func (c *BasicContext) AppUUID() string
func (*BasicContext) HostId ¶
func (c *BasicContext) HostId() string
func (*BasicContext) Id ¶
func (c *BasicContext) Id() string
func (*BasicContext) RequestId ¶
func (c *BasicContext) RequestId() string
func (*BasicContext) Store ¶
func (c *BasicContext) Store() safestore.I
type Context ¶
Context is the context passed by an app into a request. Note: It is in the util package so it can be shared by both app and web packages.
type Driver ¶
type HTTPHandler ¶
type HTTPHandler struct { App *BaseApp InitFn func(c Context, w http.ResponseWriter, r *http.Request) (err error) StartFn func(c Context, w http.ResponseWriter, r *http.Request) (haltOnErr bool, err error) OnErrorFn func(err interface{}, c Context, w http.ResponseWriter, r *http.Request) }
func (HTTPHandler) ServeHTTP ¶
func (h HTTPHandler) ServeHTTP(w0 http.ResponseWriter, r *http.Request)
type Handler ¶
This interface will serve http request, and return a status code and an error This allows the wrapping function to do filtering and error handling
type HandlerFunc ¶
func (HandlerFunc) HandleHttp ¶
func (hf HandlerFunc) HandleHttp(c Context, w http.ResponseWriter, r *http.Request) error
type LowLevelDriver ¶
type LowLevelDriver interface { DriverName() string IndexesOnlyInProps() bool InstanceCache() Cache NewContext(r *http.Request, appUUID string, seqnum uint64) (Context, error) //Check to see whether to use the cache or not for the current operation UseCache(ctx Context, preferred bool) bool // Return a http client which is applicable for the runtime environment // For example, some environments will want you to pass through a proxy, etc HttpClient(ctx Context) (*http.Client, error) // Return the Host for this app (e.g. mydomain.net:8080, mydomain.net, etc) Host(ctx Context) (string, error) ParentKey(ctx Context, key Key) Key EncodeKey(ctx Context, key Key) string GetInfoFromKey(ctx Context, key Key) (kind string, shape string, intId int64, err error) DecodeKey(ctx Context, s string) (Key, error) // NewKey returns a New Key representing the kind, shape, intId and parent key passed. // If intId < 0, allocate a new intId from the backend datastore. NewKey(ctx Context, kind string, shape string, intId int64, pkey Key) (key Key, err error) DatastoreGet(ctx Context, keys []Key, dst []interface{}) (err error) DatastorePut(ctx Context, keys []Key, dst []interface{}, dprops []interface{}) (keys2 []Key, err error) DatastoreDelete(ctx Context, keys []Key) (err error) // Query for some entities, given pairs of filters which are AND'ed together. // The kind and shape limit the returns. Query(ctx Context, parent Key, kind string, opts *QueryOpts, filters ...*QueryFilter) ( res []Key, endCursor string, err error) BlobWriter(ctx Context, contentType string) (BlobWriter, error) BlobReader(ctx Context, key string) (BlobReader, error) BlobInfo(ctx Context, key string) (*BlobInfo, error) BlobServe(ctx Context, key string, response http.ResponseWriter) error }
type PageNotFoundError ¶
type PageNotFoundError string
func (PageNotFoundError) Error ¶
func (e PageNotFoundError) Error() string
type QueryFilter ¶
type QueryFilter struct { Name string Op QueryFilterOp Value interface{} }
Filter encapsulates filters passed to a query.
type QueryFilterOp ¶
type QueryFilterOp int
const ( EQ QueryFilterOp GTE GT LTE LT )
func ToQueryFilterOp ¶
func ToQueryFilterOp(op string) QueryFilterOp
func (QueryFilterOp) String ¶
func (q QueryFilterOp) String() string
type Route ¶
type Route struct { // either the children or the handler is nil Name string Parent *Route Children []*Route Matchers []matchExpr Handler Handler // contains filtered or unexported fields }
func NewRouteFunc ¶
func NewRouteFunc(parent *Route, name string, handler HandlerFunc) *Route
Creates a new Route off a route function and adds it to the route tree.
func (*Route) FindByName ¶
Find a Route under the tree at this root.
func (*Route) Match ¶
check if the route matches. If so, check it's children. If any doesn't match, then return the current one.
type SafeStoreCache ¶
func (SafeStoreCache) CacheDelete ¶
func (ch SafeStoreCache) CacheDelete(ctx Context, keys ...interface{}) (err error)
func (SafeStoreCache) CacheGet ¶
func (ch SafeStoreCache) CacheGet(ctx Context, items ...*safestore.Item) (err error)
type User ¶
type User struct { Id int64 Name string `db:"dbname=n"` Email string `db:"dbname=em"` Atn map[string]string `db:"dbname=a,ftype=expando"` // contains filtered or unexported fields }
The most fundamental entity is the User. It is basic, and only supports basic things, like email, login providers/credentials, and tags.
We don't support passwords in here since authentication is handled by external providers.