├── README.md ├── controller.go ├── controller_test.go └── helpers_test.go /README.md: -------------------------------------------------------------------------------- 1 | # controller 2 | Package controller is a lightweight and composable controller implementation 3 | for net/http 4 | 5 | Sometimes plain net/http handlers are not enough, and you want to have logic 6 | that is resource/concept specific, and data that is request specific. 7 | 8 | This is where controllers come into play. Controllers are structs that 9 | implement a specific interface related to lifecycle management. A Controller 10 | can also contain an arbitrary amount of methods that can be used as handlers to 11 | incoming requests. This package makes it easy to automatically construct a new 12 | Controller instance and invoke a specified method on that controller for every 13 | request. 14 | 15 | ## Example 16 | 17 | ``` go 18 | package main 19 | 20 | import ( 21 | "net/http" 22 | 23 | "github.com/codegangsta/controller" 24 | ) 25 | 26 | type MyController struct { 27 | controller.Base 28 | } 29 | 30 | func (c *MyController) Index() error { 31 | c.ResponseWriter.Write([]byte("Hello World")) 32 | return nil 33 | } 34 | 35 | func main() { 36 | http.Handle("/", controller.Action((*MyController).Index)) 37 | http.ListenAndServe(":3000", nil) 38 | } 39 | 40 | ``` 41 | -------------------------------------------------------------------------------- /controller.go: -------------------------------------------------------------------------------- 1 | // Package controller is a lightweight and composable controller implementation 2 | // for net/http 3 | // 4 | // Sometimes plain net/http handlers are not enough, and you want to have logic 5 | // that is resource/concept specific, and data that is request specific. 6 | // 7 | // This is where controllers come into play. Controllers are structs that 8 | // implement a specific interface related to lifecycle management. A Controller 9 | // can also contain an arbitrary amount of methods that can be used as handlers 10 | // to incoming requests. This package makes it easy to automatically construct 11 | // a new Controller instance and invoke a specified method on that controller 12 | // for every request. 13 | // 14 | // This is an example controller: 15 | // 16 | // type MyController struct { 17 | // controller.Base 18 | // } 19 | // 20 | // func (c *MyController) Index() error { 21 | // c.ResponseWriter.Write([]byte("Hello World")) 22 | // return nil 23 | // } 24 | // 25 | // To handle HTTP requests with this controller, use the controller.Action 26 | // function: 27 | // 28 | // http.Handle("/", controller.Action((*MyController).Index)) 29 | // 30 | package controller 31 | 32 | import ( 33 | "errors" 34 | "net/http" 35 | "reflect" 36 | ) 37 | 38 | // Controller is an interface for defining a web controller that can be 39 | // automatically constructed via the controller.Action function. This interface 40 | // contains lifecycle methods that are vital during the controllers lifetime. 41 | // A controller instance is constructed every time the http.Handler result from 42 | // controller.Action is invoked (this is usually every http request) 43 | type Controller interface { 44 | // Init initializes the controller. If it returns an error, then the Error 45 | // method on the controller will be invoked. 46 | Init(http.ResponseWriter, *http.Request) error 47 | // Destroy is called after the Controllers action has been called or after an 48 | // error has occured. This is a useful method for cleaning up anything that 49 | // was initialized. 50 | Destroy() 51 | // Error is the error handling mechanism for the controller. It is called if 52 | // Init or controller action return an error. It can also be invoked manually 53 | // for consistent error handling across a controller. 54 | Error(code int, error string) 55 | } 56 | 57 | // Base is a base implementation for a Controller. It contains the Request and 58 | // ResponseWriter objects for controller actions to easily consume. Base is 59 | // meant to be embedded in your own controller struct. 60 | type Base struct { 61 | Request *http.Request 62 | ResponseWriter http.ResponseWriter 63 | } 64 | 65 | // Init initializes the base controller with a ResponseWriter and Request. 66 | // Embedders of this struct should remember to call Init if the embedder is 67 | // implementing the Init function themselves. 68 | func (b *Base) Init(rw http.ResponseWriter, r *http.Request) error { 69 | b.Request, b.ResponseWriter = r, rw 70 | return nil 71 | } 72 | 73 | // Destroy performs cleanup for the base controller 74 | func (b *Base) Destroy() { 75 | } 76 | 77 | // Error will send an HTTP error to the given ResponseWriter from Init 78 | func (b *Base) Error(code int, error string) { 79 | http.Error(b.ResponseWriter, error, code) 80 | } 81 | 82 | // Action takes a method expression and translates it into a callable 83 | // http.Handler which, when called: 84 | // 85 | // 1. Constructs a controller instance 86 | // 2. Initializes the controller via the Init function 87 | // 3. Invokes the Action method referenced by the method expression 88 | // 4. Calls destroy on the controller 89 | // 90 | // This flow allows for similar logic to be cleanly reused while data is no 91 | // longer shared between requests. This is because a new Controller instance 92 | // will be constructed every time the returned http.Handler's ServeHTTP method 93 | // is invoked. 94 | // 95 | // An example of a valid method expression is: 96 | // 97 | // controller.Action((*MyController).Index) 98 | // 99 | // Where MyController is an implementor of the Controller interface and Index 100 | // is a method on MyController that takes no arguments and returns an err 101 | func Action(action interface{}) http.Handler { 102 | val := reflect.ValueOf(action) 103 | t, err := controllerType(val) 104 | if err != nil { 105 | panic(err) 106 | } 107 | 108 | return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 109 | v := reflect.New(t) 110 | c := v.Interface().(Controller) 111 | err = c.Init(rw, r) 112 | defer c.Destroy() 113 | if err != nil { 114 | c.Error(http.StatusInternalServerError, err.Error()) 115 | return 116 | } 117 | ret := val.Call([]reflect.Value{v})[0].Interface() 118 | if ret != nil { 119 | c.Error(http.StatusInternalServerError, ret.(error).Error()) 120 | return 121 | } 122 | }) 123 | } 124 | 125 | func controllerType(action reflect.Value) (reflect.Type, error) { 126 | t := action.Type() 127 | 128 | if t.Kind() != reflect.Func { 129 | return t, errors.New("Action is not a function") 130 | } 131 | 132 | if t.NumIn() != 1 { 133 | return t, errors.New("Wrong Number of Arguments in action") 134 | } 135 | 136 | if t.NumOut() != 1 { 137 | return t, errors.New("Wrong Number of return values in action") 138 | } 139 | 140 | out := t.Out(0) 141 | if !out.Implements(interfaceOf((*error)(nil))) { 142 | return t, errors.New("Action return type invalid") 143 | } 144 | 145 | t = t.In(0) 146 | for t.Kind() == reflect.Ptr { 147 | t = t.Elem() 148 | } 149 | 150 | if !reflect.PtrTo(t).Implements(interfaceOf((*Controller)(nil))) { 151 | return t, errors.New("Controller does not implement ctrl.Controller interface") 152 | } 153 | 154 | return t, nil 155 | } 156 | 157 | func interfaceOf(value interface{}) reflect.Type { 158 | t := reflect.TypeOf(value) 159 | 160 | for t.Kind() == reflect.Ptr { 161 | t = t.Elem() 162 | } 163 | 164 | return t 165 | } 166 | -------------------------------------------------------------------------------- /controller_test.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | type TestController struct { 9 | Base 10 | } 11 | 12 | func (t *TestController) Index() error { 13 | return nil 14 | } 15 | 16 | func (t *TestController) BadAction() { 17 | } 18 | 19 | func (t *TestController) BadAction2() string { 20 | return "" 21 | } 22 | 23 | type NoController struct { 24 | } 25 | 26 | func (n *NoController) Foo() { 27 | } 28 | 29 | func TestValidateAction(t *testing.T) { 30 | var validateTests = []struct { 31 | action interface{} 32 | valid bool 33 | }{ 34 | {(*TestController).Index, true}, 35 | {(*TestController).BadAction, false}, 36 | {(*TestController).BadAction2, false}, 37 | {(*NoController).Foo, false}, 38 | {"bad", false}, 39 | } 40 | 41 | for _, test := range validateTests { 42 | _, err := controllerType(reflect.ValueOf(test.action)) 43 | if (err == nil) != test.valid { 44 | t.Errorf("Action: %v should be valid=%v but returned error %v", reflect.ValueOf(test.action), test.valid, err) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /helpers_test.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "reflect" 7 | "runtime" 8 | "testing" 9 | ) 10 | 11 | // assert fails the test if the condition is false. 12 | func assert(tb testing.TB, condition bool, msg string, v ...interface{}) { 13 | if !condition { 14 | _, file, line, _ := runtime.Caller(1) 15 | fmt.Printf("%s:%d: "+msg, append([]interface{}{filepath.Base(file), line}, v...)...) 16 | tb.FailNow() 17 | } 18 | } 19 | 20 | // ok fails the test if an err is not nil. 21 | func ok(tb testing.TB, err error) { 22 | if err != nil { 23 | _, file, line, _ := runtime.Caller(1) 24 | fmt.Printf("%s:%d: unexpected error: %s", filepath.Base(file), line, err.Error()) 25 | tb.FailNow() 26 | } 27 | } 28 | 29 | // equals fails the test if exp is not equal to act. 30 | func equals(tb testing.TB, exp, act interface{}) { 31 | if !reflect.DeepEqual(exp, act) { 32 | _, file, line, _ := runtime.Caller(1) 33 | fmt.Printf("%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\n\n", filepath.Base(file), line, exp, act) 34 | tb.FailNow() 35 | } 36 | } 37 | --------------------------------------------------------------------------------