Skip to content

Commit

Permalink
implemented webservice and route level filter chains
Browse files Browse the repository at this point in the history
see restful-filters.go for an example
  • Loading branch information
emicklei committed May 22, 2013
1 parent 17b3a8f commit 77ce39d
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 47 deletions.
5 changes: 5 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ Example main:
log.Fatal(http.ListenAndServe(":8080", nil))
}
Filter
A filter dynamically intercepts requests and responses to transform or use the information contained in the requests or responses.
[project]: https://github.com/emicklei/go-restful
[example]: http://ernestmicklei.com/2012/11/24/go-restful-first-working-example/
Expand Down
93 changes: 48 additions & 45 deletions examples/restful-filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,84 +14,87 @@ type UserList struct {
Users []User
}

func main() {
// install a global filter (processed before any webservice)
restful.Dispatch = globalLogging

restful.Add(NewUserService())
log.Printf("start listening on localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}

func NewUserService() *restful.WebService {
ws := new(restful.WebService)
ws.
Path("/users").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML)

logging := LoggingFilter{findUser}.handleFilter
// install a webservice filter (processed before any route)
ws.Filter(webserviceLogging)

counter := CountFilter{0, logging}
counting := (&counter).handleFilter
ws.Route(ws.GET("/{user-id}").To(counting))
// install a counter filter
ws.Route(ws.GET("").Filter(NewCountFilter().routeCounter).To(getAllUsers))

//ws.Filter("/users/", handleLogging)
// install 2 chained route filters (processed before calling findUser)
ws.Route(ws.GET("/{user-id}").Filter(routeLogging).Filter(NewCountFilter().routeCounter).To(findUser))
return ws
}

// GlobalFilter
func globalHandleLogging(w http.ResponseWriter, r *http.Request) {
log.Printf("[global-filter] %s,%s\n", r.Method, r.URL)
// Global Filter
func globalLogging(w http.ResponseWriter, r *http.Request) {
log.Printf("[global-filter (logger)] %s,%s\n", r.Method, r.URL)
restful.DefaultDispatch(w, r)
}

// WebServiceFilter
func webserviceHandleLogging(httpWriter http.ResponseWriter, httpRequest *http.Request) {
log.Printf("[webservice-filter] %s,%s\n", httpRequest.Method, httpRequest.URL)
// WebService Filter
func webserviceLogging(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
log.Printf("[webservice-filter (logger)] %s,%s\n", req.Request.Method, req.Request.URL)
chain.ProcessFilter(req, resp)
}

// RouteFunctionFilter
type LoggingFilter struct {
WrappedFunction restful.RouteFunction
}

func (l LoggingFilter) handleFilter(request *restful.Request, response *restful.Response) {
log.Printf("[function-filter (logging)] req:%v resp:%v", request, response)
l.WrappedFunction(request, response)
// Route Filter (defines FilterFunction)
func routeLogging(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
log.Printf("[route-filter (logger)] %s,%s\n", req.Request.Method, req.Request.URL)
chain.ProcessFilter(req, resp)
}

// Route Filter (as a struct that defines a FilterFunction)
// CountFilter implements a FilterFunction for counting requests.
type CountFilter struct {
Count int
WrappedFunction restful.RouteFunction
count int
counter chan int // for go-routine safe count increments
}

func (c *CountFilter) handleFilter(request *restful.Request, response *restful.Response) {
c.Count++
log.Printf("[function-filter (count)] count:%d, req:%v resp:%v", c.Count, request, response)
c.WrappedFunction(request, response)
// NewCountFilter creates and initializes a new CountFilter.
func NewCountFilter() *CountFilter {
c := new(CountFilter)
c.counter = make(chan int)
go func() {
for {
c.count += <-c.counter
}
}()
return c
}

// Global Filter > replace Dispatch function , type HandlerFunc func(ResponseWriter, *Request
// WebService Filter > filter on pattern?
// Route Filter > RouteFunction func(*Request, *Response)

// A filter dynamically intercepts requests and responses to transform or use the information contained in the requests or responses.
// http://www.oracle.com/technetwork/java/filters-137243.html
//func handleLogging(request *restful.Request, response *restful.Response, chain *FilterChain) {
// log.Printf("req:%v resp:%v", request, response)

// chain.handleNextFilter(request, response)
//}
// routeCounter increments the count of the filter (through a channel)
func (c *CountFilter) routeCounter(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
c.counter <- 1
log.Printf("[route-filter (counter)] count:%d", c.count)
chain.ProcessFilter(req, resp)
}

// GET http://localhost:8080/users
//
func getAllUsers(request *restful.Request, response *restful.Response) {
log.Printf("getAllUsers")
response.WriteEntity(UserList{[]User{User{"42", "Gandalf"}, User{"3.14", "Pi"}}})
}

// GET http://localhost:8080/users/42
//
func findUser(request *restful.Request, response *restful.Response) {
log.Printf("findUser")
response.WriteEntity(User{"42", "Gandalf"})
}

func main() {
// Install global filter (directly using replacement of Dispatch)
restful.Dispatch = globalHandleLogging

restful.Add(NewUserService())
log.Printf("start listening on localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
22 changes: 22 additions & 0 deletions filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package restful

// FilterChain is a request scoped object to process one or more filters before calling the target RouteFunction.
type FilterChain struct {
Filters []FilterFunction // ordered list of FilterFunction
Index int // index into filters that is currently in progress
Target RouteFunction // function to call after passing all filters
}

// ProcessFilter passes the request,response pair through the next of Filters.
// Each filter can decide to proceed to the next Filter or handle the Response itself.
func (f *FilterChain) ProcessFilter(request *Request, response *Response) {
if f.Index < len(f.Filters) {
f.Index++
f.Filters[f.Index-1](request, response, f)
} else {
f.Target(request, response)
}
}

// FilterFunction definitions must call ProcessFilter on the FilterChain to pass on the control and eventually call the RouteFunction
type FilterFunction func(*Request, *Response, *FilterChain)
11 changes: 10 additions & 1 deletion route.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Route struct {
Consumes []string
Path string
Function RouteFunction
Filters []FilterFunction

// cached values for dispatching
relativePath string
Expand All @@ -39,7 +40,15 @@ func (self *Route) postBuild() {
func (self *Route) dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
params := self.extractParameters(httpRequest.URL.Path)
accept := httpRequest.Header.Get(HEADER_Accept)
self.Function(&Request{httpRequest, params}, &Response{httpWriter, accept, self.Produces})
wrappedRequest := &Request{httpRequest, params}
wrappedResponse := &Response{httpWriter, accept, self.Produces}
if len(self.Filters) > 0 {
chain := FilterChain{Filters: self.Filters, Target: self.Function}
chain.ProcessFilter(wrappedRequest, wrappedResponse)
} else {
// unfiltered
self.Function(wrappedRequest, wrappedResponse)
}
}

// Return whether the mimeType matches to what this Route can produce.
Expand Down
8 changes: 8 additions & 0 deletions route_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type RouteBuilder struct {
consumes []string
httpMethod string // required
function RouteFunction // required
filters []FilterFunction
// documentation
doc string
readSample, writeSample interface{}
Expand Down Expand Up @@ -81,6 +82,12 @@ func (self *RouteBuilder) servicePath(path string) *RouteBuilder {
return self
}

// Filter appends a FilterFunction to the end of filters for this Route to build.
func (self *RouteBuilder) Filter(filter FilterFunction) *RouteBuilder {
self.filters = append(self.filters, filter)
return self
}

// If no specific Route path then set to rootPath
// If no specific Produces then set to rootProduces
// If no specific Consumes then set to rootConsumes
Expand All @@ -101,6 +108,7 @@ func (self *RouteBuilder) Build() Route {
Produces: self.produces,
Consumes: self.consumes,
Function: self.function,
Filters: self.filters,
relativePath: self.currentPath,
Doc: self.doc,
ParameterDocs: self.parameters,
Expand Down
13 changes: 13 additions & 0 deletions web_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type WebService struct {
produces []string
consumes []string
pathParameters []*Parameter
filters []FilterFunction
}

// Path specifies the root URL template path of the WebService.
Expand Down Expand Up @@ -80,10 +81,22 @@ func (self WebService) RootPath() string {
return self.rootPath
}

// PathParameters return the path parameter names for (shared amoung its Routes)
func (self WebService) PathParameters() []*Parameter {
return self.pathParameters
}

// Filters returns the list of FilterFunction
func (self *WebService) Filters() []FilterFunction {
return self.filters
}

// Filter adds a filter function to the chain of filters applicable to all its Routes
func (self *WebService) Filter(filter FilterFunction) *WebService {
self.filters = append(self.filters, filter)
return self
}

/*
Convenience methods
*/
Expand Down
18 changes: 17 additions & 1 deletion web_service_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Dispatcher interface {
Routes() []Route
RootPath() string
PathParameters() []*Parameter
Filters() []FilterFunction
// rootRegEx
}

Expand Down Expand Up @@ -94,7 +95,22 @@ func DefaultDispatch(httpWriter http.ResponseWriter, httpRequest *http.Request)
// step 3. Identify the method (Route) that will handle the request
route, detected := detectRoute(routes, httpWriter, httpRequest)
if detected {
route.dispatch(httpWriter, httpRequest)
// pass through filters (if any)
filters := dispatcher.Filters()
if len(filters) > 0 {
accept := httpRequest.Header.Get(HEADER_Accept)
wrappedRequest := &Request{httpRequest, map[string]string{}} // empty parameters
wrappedResponse := &Response{httpWriter, accept, []string{}} // empty content-types

chain := FilterChain{Filters: filters, Target: func(req *Request, resp *Response) {
// handle request by route
route.dispatch(resp, req.Request)
}}
chain.ProcessFilter(wrappedRequest, wrappedResponse)
} else {
// handle request by route
route.dispatch(httpWriter, httpRequest)
}
}
// else a non-200 response has already been written
}
Expand Down

0 comments on commit 77ce39d

Please sign in to comment.