Ideas for Go-Json-Rest v3.0.0 (RFC)
V3 is the opportunity for API changes and improvements.
In the past two years Go-Json-Rest has changed a lot. It got the notion of middleware, bringing basic features like Auth, CORS, JSONP that were missing at the beginning. Over time the list of options and settings has doubled. This is reflected in the main object ResourceHandler that has become a long list of options. It works, but it could be more convenient to use. The goal of the v3 is to provide a replacement for ResourceHandler
that is simpler to use and more flexible.
The ResourceHandler does not do much, given the settings, it instantiates the middlewares and the router, and wraps all that in order to produce a net/http Handler. The middlewares and the router are in fact the low-level api of go-json-rest. The idea of v3 is to open it, make it public.
Proposed new API
1) Make public the existing private Middlewares
So in addition the following, already public, middlewares:
- AuthBasicMiddleware
- CorsMiddleware
- JsonpMiddleware
We will get the following ones:
- AccessLogApacheMiddleware
- AccessLogJsonMiddleware
- TimerMiddleware
- RecorderMiddleware
- GzipMiddleware
- RecoverMiddleware
- ContentTypeCheckerMiddleware
- JsonIndentMiddleware
- PoweredByMiddleware
- StatusMiddleware
2) Propose some predefined stacks of Middlewares
Precise lists and options to be determined, but for instance:
var DefaultDevStack = []Middleware{
&AccessLogApacheMiddleware{},
&TimerMiddleware{},
&RecorderMiddleware{},
&JsonIndentMiddleware{},
&PoweredByMiddleware{},
&RecoverMiddleware{
EnableResponseStackTrace: true,
},
}
var DefaultProdStack = []Middleware{
&AccessLogApacheMiddleware{},
&TimerMiddleware{},
&RecorderMiddleware{},
&GzipMiddleware{},
&PoweredByMiddleware{},
&RecoverMiddleware{},
&CheckContentTypeMiddleware{},
}
Most of the options and settings of the current ResourceHandler
end up being options of the middlewares, or just including of not including a middleware.
Example:
MyStack := []Middleware{
&AccessLogApacheMiddleware{
Logger: &myLogger,
Format: rest.CombinedLogFormat,
},
&TimerMiddleware{},
&RecorderMiddleware{},
&GzipMiddleware{},
&RecoverMiddleware{
Logger: &myLogger,
},
&CheckContentTypeMiddleware{},
}
3) A new App interface
Go-Json-Rest already defines this interface:
type HandlerFunc func(ResponseWriter, *Request)
type Middleware interface {
MiddlewareFunc(handler HandlerFunc) HandlerFunc
}
In v3, it will be completed by this new:
type App interface {
AppFunc() HandlerFunc
}
v3 will also offer the two following adapter types. Convenient to write simple Apps or Middlewares without defining types. (Unit tests are written this way, for instance)
type MiddlewareSimple func(handler HandlerFunc) HandlerFunc
func (ms MiddlewareSimple) MiddlewareFunc(handler HandlerFunc) HandlerFunc {
}
type AppSimple HandlerFunc
func (as AppSimple) AppFunc() HandlerFunc {
}
It allows to write
api.SetApp(AppSimple(func(w ResponseWriter, r *Request) {
...
}))
api.Use(MiddlewareSimple(func(handler HandlerFunc) HandlerFunc {
return func(w ResponseWriter, r *Request) {
...
}
}))
4) Explicitly build the router as an App
The router already implements the App
interface by providing the AppFunc
method. The following function will be added to explicitly build it:
func MakeRouter(routes ...Routes) (App, error) {
}
Building a Go-Json-Rest api now consists in assembling a stack of Middlewares and putting an App on top of it. It also open the door to interchangeable routers by allowing third party routers to be wrapped inside an App
.
5) Finally, this new Api object that provides the syntactic sugar
type Api struct {
}
func NewApi() *Api {
}
func SetApp(app App) {
}
func (api *Api) Use(middlewares ...Middleware) {
}
func (api *Api) MakeHandler() http.Handler {
}
The new “Hello World!” example
“Hello World!” with JSONP support:
func main() {
api := rest.NewApi()
// the Middleware stack
api.Use(rest.DefaultDevStack...)
api.Use(&rest.JsonpMiddleware{
CallbackNameKey: "cb",
})
// build the App, here the rest Router
router, err := rest.MakeRouter(
&Route{"GET", "/message", func(w rest.ResponseWriter, req *rest.Request) {
w.WriteJson(map[string]string{"Body": "Hello World!"})
}},
)
if err != nil {
log.Fatal(err)
}
api.SetApp(router)
// build and run the handler
log.Fatal(http.ListenAndServe(
":8080",
api.MakeHandler(),
))
}
Compared to the previous version:
func main() {
handler := rest.ResourceHandler{
PreRoutingMiddlewares: []rest.Middleware{
&rest.JsonpMiddleware{
CallbackNameKey: "cb",
},
},
}
err := handler.SetRoutes(
&rest.Route{"GET", "/message", func(w rest.ResponseWriter, req *rest.Request) {
w.WriteJson(map[string]string{"Body": "Hello World!"})
}},
)
if err != nil {
log.Fatal(err)
}
log.Fatal(http.ListenAndServe(":8080", &handler))
}
Semver and backward compatibility
Go-Json-Rest follows Semver, breaking API changes are introduced only with major version changes. In this particular case, even if the API changes are important, it is possible to maintain the backward compatibility and just mark the old API as deprecated.
The objects will be marked as deprecated, and after a few months, can be removed from the package.