| package restful |
| |
| // Copyright 2013 Ernest Micklei. All rights reserved. |
| // Use of this source code is governed by a license |
| // that can be found in the LICENSE file. |
| |
| import ( |
| "net/http" |
| "strings" |
| ) |
| |
| // RouteFunction declares the signature of a function that can be bound to a Route. |
| type RouteFunction func(*Request, *Response) |
| |
| // RouteSelectionConditionFunction declares the signature of a function that |
| // can be used to add extra conditional logic when selecting whether the route |
| // matches the HTTP request. |
| type RouteSelectionConditionFunction func(httpRequest *http.Request) bool |
| |
| // Route binds a HTTP Method,Path,Consumes combination to a RouteFunction. |
| type Route struct { |
| Method string |
| Produces []string |
| Consumes []string |
| Path string // webservice root path + described path |
| Function RouteFunction |
| Filters []FilterFunction |
| If []RouteSelectionConditionFunction |
| |
| // cached values for dispatching |
| relativePath string |
| pathParts []string |
| pathExpr *pathExpression // cached compilation of relativePath as RegExp |
| |
| // documentation |
| Doc string |
| Notes string |
| Operation string |
| ParameterDocs []*Parameter |
| ResponseErrors map[int]ResponseError |
| ReadSample, WriteSample interface{} // structs that model an example request or response payload |
| |
| // Extra information used to store custom information about the route. |
| Metadata map[string]interface{} |
| |
| // marks a route as deprecated |
| Deprecated bool |
| } |
| |
| // Initialize for Route |
| func (r *Route) postBuild() { |
| r.pathParts = tokenizePath(r.Path) |
| } |
| |
| // Create Request and Response from their http versions |
| func (r *Route) wrapRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request, pathParams map[string]string) (*Request, *Response) { |
| wrappedRequest := NewRequest(httpRequest) |
| wrappedRequest.pathParameters = pathParams |
| wrappedRequest.selectedRoutePath = r.Path |
| wrappedResponse := NewResponse(httpWriter) |
| wrappedResponse.requestAccept = httpRequest.Header.Get(HEADER_Accept) |
| wrappedResponse.routeProduces = r.Produces |
| return wrappedRequest, wrappedResponse |
| } |
| |
| // dispatchWithFilters call the function after passing through its own filters |
| func (r *Route) dispatchWithFilters(wrappedRequest *Request, wrappedResponse *Response) { |
| if len(r.Filters) > 0 { |
| chain := FilterChain{Filters: r.Filters, Target: r.Function} |
| chain.ProcessFilter(wrappedRequest, wrappedResponse) |
| } else { |
| // unfiltered |
| r.Function(wrappedRequest, wrappedResponse) |
| } |
| } |
| |
| // Return whether the mimeType matches to what this Route can produce. |
| func (r Route) matchesAccept(mimeTypesWithQuality string) bool { |
| parts := strings.Split(mimeTypesWithQuality, ",") |
| for _, each := range parts { |
| var withoutQuality string |
| if strings.Contains(each, ";") { |
| withoutQuality = strings.Split(each, ";")[0] |
| } else { |
| withoutQuality = each |
| } |
| // trim before compare |
| withoutQuality = strings.Trim(withoutQuality, " ") |
| if withoutQuality == "*/*" { |
| return true |
| } |
| for _, producibleType := range r.Produces { |
| if producibleType == "*/*" || producibleType == withoutQuality { |
| return true |
| } |
| } |
| } |
| return false |
| } |
| |
| // Return whether this Route can consume content with a type specified by mimeTypes (can be empty). |
| func (r Route) matchesContentType(mimeTypes string) bool { |
| |
| if len(r.Consumes) == 0 { |
| // did not specify what it can consume ; any media type (“*/*”) is assumed |
| return true |
| } |
| |
| if len(mimeTypes) == 0 { |
| // idempotent methods with (most-likely or guaranteed) empty content match missing Content-Type |
| m := r.Method |
| if m == "GET" || m == "HEAD" || m == "OPTIONS" || m == "DELETE" || m == "TRACE" { |
| return true |
| } |
| // proceed with default |
| mimeTypes = MIME_OCTET |
| } |
| |
| parts := strings.Split(mimeTypes, ",") |
| for _, each := range parts { |
| var contentType string |
| if strings.Contains(each, ";") { |
| contentType = strings.Split(each, ";")[0] |
| } else { |
| contentType = each |
| } |
| // trim before compare |
| contentType = strings.Trim(contentType, " ") |
| for _, consumeableType := range r.Consumes { |
| if consumeableType == "*/*" || consumeableType == contentType { |
| return true |
| } |
| } |
| } |
| return false |
| } |
| |
| // Tokenize an URL path using the slash separator ; the result does not have empty tokens |
| func tokenizePath(path string) []string { |
| if "/" == path { |
| return []string{} |
| } |
| return strings.Split(strings.Trim(path, "/"), "/") |
| } |
| |
| // for debugging |
| func (r Route) String() string { |
| return r.Method + " " + r.Path |
| } |