app

package
Version: v0.0.0-...-9b564c9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 27, 2020 License: MIT Imports: 20 Imported by: 2

README

go-serverapp/app

This repository contains the go-serverapp/app library.

To install:

go get github.com/ugorji/go-serverapp/app

Package Documentation

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

Exported Package API

const VarsKey = "router_vars" ...
const UseJsonOnErrHttpHeaderKey = "Z-App-Json-Response-On-Error"
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 struct{ ... }
type BaseApp struct{ ... }
    func NewApp(devServer bool, uuid string, viewsCfgPath string, lld LowLevelDriver) (gapp *BaseApp, err error)
type BaseDriver struct{ ... }
type BasicContext struct{ ... }
type BlobInfo struct{ ... }
type BlobReader interface{ ... }
type BlobWriter interface{ ... }
type Cache interface{ ... }
type Context interface{ ... }
type Driver interface{ ... }
    func AppDriver(appname string) (dr Driver)
type HTTPHandler struct{ ... }
type Handler interface{ ... }
type HandlerFunc func(Context, http.ResponseWriter, *http.Request) error
type Key interface{ ... }
type LowLevelDriver interface{ ... }
type PageNotFoundError string
type QueryFilter struct{ ... }
type QueryFilterOp int
    const EQ QueryFilterOp ...
    func ToQueryFilterOp(op string) QueryFilterOp
type QueryOpts struct{ ... }
type Route struct{ ... }
    func NewRoot(name string) (root *Route)
    func NewRoute(parent *Route, name string, handler Handler) *Route
    func NewRouteFunc(parent *Route, name string, handler HandlerFunc) *Route
type SafeStoreCache struct{ ... }
type Tier int32
    const DEVELOPMENT Tier = iota + 1 ...
type User struct{ ... }

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

View Source
const (
	VarsKey   = "router_vars"
	LogTarget = "router"
)
View Source
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 CtxCtx

func CtxCtx(c Context) context.Context

func Dispatch

func Dispatch(ctx Context, root *Route, w http.ResponseWriter, r *http.Request) error

Any wrapping function can call Dispatch, and overwrite TopLevelHandler

func DumpRequest

func DumpRequest(c Context, r *http.Request) (err error)

func NoMatchFoundHandler

func NoMatchFoundHandler(c Context, w http.ResponseWriter, r *http.Request) error

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

func RegisterAppDriver(appname string, driver Driver)

func TrueExpr

func TrueExpr(store safestore.I, req *http.Request) (bool, error)

A Matcher which always returns true.

func Vars

func Vars(sf safestore.I) (vars map[string]string)

Return the vars stored for this request. An application can request it and then update values in here.

Types

type AppInfo

type AppInfo struct {
	Tier   Tier     //= PRODUCTION
	ResVfs *vfs.Vfs // This must always be here (anyone may want resources)
	UUID   string   //this is "typically" a 4-digit string between 1001 and 9999 (identify the instance)
}

type BaseApp

type BaseApp struct {
	BaseDriver
	AppDriver Driver
	//SecureProvNames []string
	HostFn       func(Context) (string, error)
	HttpClientFn func(Context) (*http.Client, error)

	DumpRequestAtStartup bool
	DumpRequestOnError   bool
	// contains filtered or unexported fields
}

func NewApp

func NewApp(devServer bool, uuid string, viewsCfgPath string, lld LowLevelDriver) (gapp *BaseApp, err error)

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

type BasicContext struct {
	SeqNum     uint64
	TheAppUUID string
	SafeStore  *safestore.T
}

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 BlobInfo

type BlobInfo struct {
	Key          string
	ContentType  string
	CreationTime time.Time
	Filename     string
	Size         int64
}

type BlobReader

type BlobReader interface {
	io.Reader
	io.ReaderAt
	io.Seeker
	io.Closer
}

type BlobWriter

type BlobWriter interface {
	io.Writer
	Finish() (key string, err error)
}

type Cache

type Cache interface {
	CacheGet(ctx Context, items ...*safestore.Item) (err error)
	CachePut(ctx Context, items ...*safestore.Item) (err error)
	CacheIncr(ctx Context, key interface{}, delta int64, initVal uint64) (newVal uint64, err error)
	CacheDelete(ctx Context, keys ...interface{}) (err error)
}

type Context

type Context interface {
	Id() string
	Store() safestore.I
	AppUUID() string
}

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 Driver interface {
	Render(ctx Context, view string, data map[string]interface{}, wr io.Writer) error
	LandingPageURL(ctx Context, includeHost bool) (string, error)
	Info() *AppInfo
	LowLevelDriver
}

func AppDriver

func AppDriver(appname string) (dr 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

type Handler interface {
	HandleHttp(Context, http.ResponseWriter, *http.Request) error
}

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

type HandlerFunc func(Context, http.ResponseWriter, *http.Request) error

func (HandlerFunc) HandleHttp

func (hf HandlerFunc) HandleHttp(c Context, w http.ResponseWriter, r *http.Request) error

type Key

type Key interface {
	// Encode will encode into a string.
	// Encode() string
	// Incomplete denotes true if this key cannot be stored in datastore as is.
	Incomplete() bool
	// Id returns the value of the Id variable of the entity that this key represents.
	EntityId() int64
}

type LowLevelDriver

type LowLevelDriver interface {
	DriverName() string
	IndexesOnlyInProps() bool
	InstanceCache() Cache
	SharedCache(returnInstanceCacheIfNil bool) 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 QueryOpts

type QueryOpts struct {
	Shape       string
	Limit       int
	Offset      int
	StartCursor string
	EndCursor   string
	Order       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 NewRoot

func NewRoot(name string) (root *Route)

func NewRoute

func NewRoute(parent *Route, name string, handler Handler) *Route

Creates a new Route and adds it to the route tree.

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

func (rt *Route) FindByName(name string) *Route

Find a Route under the tree at this root.

func (*Route) Host

func (rt *Route) Host(hostRegexp string) *Route

This adds a Host matchExpr to this router.

func (*Route) Match

func (rt *Route) Match(store safestore.I, req *http.Request) *Route

check if the route matches. If so, check it's children. If any doesn't match, then return the current one.

func (*Route) Param

func (rt *Route) Param(param string) *Route

func (*Route) Path

func (rt *Route) Path(pathRegexp string) *Route

This adds a Path matchExpr to this router. It supports exact matches, as well as matches of regexp.

func (*Route) ToURL

func (rt *Route) ToURL(params ...string) (u *url.URL, err error)

func (*Route) ToURLX

func (rt *Route) ToURLX(params map[string]interface{}) (u *url.URL, err error)

Sister function to ToURL.

type SafeStoreCache

type SafeStoreCache struct {
	*safestore.T
}

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)

func (SafeStoreCache) CacheIncr

func (ch SafeStoreCache) CacheIncr(ctx Context, key interface{}, delta int64,
	initVal uint64) (newVal uint64, err error)

func (SafeStoreCache) CachePut

func (ch SafeStoreCache) CachePut(ctx Context, items ...*safestore.Item) (err error)

type Tier

type Tier int32
const (
	DEVELOPMENT Tier = iota + 1
	STAGING
	PRODUCTION
)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL