246 lines
4.8 KiB
Go
246 lines
4.8 KiB
Go
package tango
|
|
|
|
import (
|
|
"encoding/json"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
)
|
|
|
|
type Handler interface {
|
|
Handle(*Context)
|
|
}
|
|
|
|
type Context struct {
|
|
tan *Tango
|
|
Logger
|
|
|
|
idx int
|
|
req *http.Request
|
|
ResponseWriter
|
|
route *Route
|
|
params url.Values
|
|
callArgs []reflect.Value
|
|
matched bool
|
|
cookies *cookies
|
|
secCookies *secureCookies
|
|
|
|
action interface{}
|
|
Result interface{}
|
|
}
|
|
|
|
func NewContext(
|
|
tan *Tango,
|
|
req *http.Request,
|
|
resp ResponseWriter,
|
|
logger Logger) *Context {
|
|
return &Context{
|
|
tan: tan,
|
|
idx: 0,
|
|
req: req,
|
|
ResponseWriter: resp,
|
|
Logger: logger,
|
|
}
|
|
}
|
|
|
|
func (ctx *Context) HandleError() {
|
|
ctx.tan.ErrHandler.Handle(ctx)
|
|
}
|
|
|
|
func (ctx *Context) Req() *http.Request {
|
|
return ctx.req
|
|
}
|
|
|
|
func (ctx *Context) SecureCookies(secret string) Cookies {
|
|
if ctx.secCookies == nil {
|
|
ctx.secCookies = &secureCookies{
|
|
&cookies{
|
|
ctx.req,
|
|
ctx.ResponseWriter,
|
|
},
|
|
secret,
|
|
}
|
|
}
|
|
return ctx.secCookies
|
|
}
|
|
|
|
func (ctx *Context) Cookies() Cookies {
|
|
if ctx.cookies == nil {
|
|
ctx.cookies = &cookies{
|
|
ctx.req,
|
|
ctx.ResponseWriter,
|
|
}
|
|
}
|
|
return ctx.cookies
|
|
}
|
|
|
|
func (ctx *Context) Route() *Route {
|
|
ctx.newAction()
|
|
return ctx.route
|
|
}
|
|
|
|
func (ctx *Context) Params() url.Values {
|
|
ctx.newAction()
|
|
return ctx.params
|
|
}
|
|
|
|
func (ctx *Context) Action() interface{} {
|
|
ctx.newAction()
|
|
return ctx.action
|
|
}
|
|
|
|
func (ctx *Context) newAction() {
|
|
if !ctx.matched {
|
|
reqPath := removeStick(ctx.Req().URL.Path)
|
|
ctx.route, ctx.params = ctx.tan.Match(reqPath, ctx.Req().Method)
|
|
if ctx.route != nil {
|
|
vc := ctx.route.newAction()
|
|
ctx.action = vc.Interface()
|
|
switch ctx.route.routeType {
|
|
case StructPtrRoute:
|
|
ctx.callArgs = []reflect.Value{vc.Elem()}
|
|
case StructRoute:
|
|
ctx.callArgs = []reflect.Value{vc}
|
|
case FuncRoute:
|
|
ctx.callArgs = []reflect.Value{}
|
|
case FuncHttpRoute:
|
|
ctx.callArgs = []reflect.Value{reflect.ValueOf(ctx.ResponseWriter),
|
|
reflect.ValueOf(ctx.Req())}
|
|
case FuncReqRoute:
|
|
ctx.callArgs = []reflect.Value{reflect.ValueOf(ctx.Req())}
|
|
case FuncResponseRoute:
|
|
ctx.callArgs = []reflect.Value{reflect.ValueOf(ctx.ResponseWriter)}
|
|
case FuncCtxRoute:
|
|
ctx.callArgs = []reflect.Value{reflect.ValueOf(ctx)}
|
|
default:
|
|
panic("routeType error")
|
|
}
|
|
}
|
|
ctx.matched = true
|
|
}
|
|
}
|
|
|
|
func (ctx *Context) Next() {
|
|
ctx.idx += 1
|
|
ctx.Invoke()
|
|
}
|
|
|
|
func (ctx *Context) Invoke() {
|
|
if ctx.idx < len(ctx.tan.handlers) {
|
|
ctx.tan.handlers[ctx.idx].Handle(ctx)
|
|
} else {
|
|
ctx.newAction()
|
|
// route is matched
|
|
if ctx.action != nil {
|
|
ret := ctx.route.method.Call(ctx.callArgs)
|
|
if len(ret) > 0 {
|
|
ctx.Result = ret[0].Interface()
|
|
}
|
|
// not route matched
|
|
} else {
|
|
if !ctx.Written() {
|
|
ctx.NotFound()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ctx *Context) ServeFile(path string) error {
|
|
http.ServeFile(ctx, ctx.Req(), path)
|
|
return nil
|
|
}
|
|
|
|
func (ctx *Context) ServeXml(obj interface{}) error {
|
|
encoder := xml.NewEncoder(ctx)
|
|
err := encoder.Encode(obj)
|
|
if err == nil {
|
|
ctx.Header().Set("Content-Type", "application/xml")
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (ctx *Context) ServeJson(obj interface{}) error {
|
|
encoder := json.NewEncoder(ctx)
|
|
err := encoder.Encode(obj)
|
|
if err == nil {
|
|
ctx.Header().Set("Content-Type", "application/json")
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (ctx *Context) Download(fpath string) error {
|
|
f, err := os.Open(fpath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
fName := filepath.Base(fpath)
|
|
ctx.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%v\"", fName))
|
|
_, err = io.Copy(ctx, f)
|
|
return err
|
|
}
|
|
|
|
func (ctx *Context) Redirect(url string, status ...int) {
|
|
s := http.StatusFound
|
|
if len(status) > 0 {
|
|
s = status[0]
|
|
}
|
|
http.Redirect(ctx.ResponseWriter, ctx.Req(), url, s)
|
|
}
|
|
|
|
// Notmodified writes a 304 HTTP response
|
|
func (ctx *Context) NotModified() {
|
|
ctx.WriteHeader(http.StatusNotModified)
|
|
}
|
|
|
|
func (ctx *Context) Unauthorized() {
|
|
ctx.Abort(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
|
|
}
|
|
|
|
// NotFound writes a 404 HTTP response
|
|
func (ctx *Context) NotFound(message ...string) {
|
|
if len(message) == 0 {
|
|
ctx.Abort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
|
return
|
|
}
|
|
ctx.Abort(http.StatusNotFound, message[0])
|
|
}
|
|
|
|
// Abort is a helper method that sends an HTTP header and an optional
|
|
// body. It is useful for returning 4xx or 5xx errors.
|
|
// Once it has been called, any return value from the handler will
|
|
// not be written to the response.
|
|
func (ctx *Context) Abort(status int, body string) {
|
|
ctx.Result = Abort(status, body)
|
|
ctx.HandleError()
|
|
}
|
|
|
|
type Contexter interface {
|
|
SetContext(*Context)
|
|
}
|
|
|
|
type Ctx struct {
|
|
*Context
|
|
}
|
|
|
|
func (c *Ctx) SetContext(ctx *Context) {
|
|
c.Context = ctx
|
|
}
|
|
|
|
func Contexts() HandlerFunc {
|
|
return func(ctx *Context) {
|
|
if action := ctx.Action(); action != nil {
|
|
if a, ok := action.(Contexter); ok {
|
|
a.SetContext(ctx)
|
|
}
|
|
}
|
|
ctx.Next()
|
|
}
|
|
}
|