blob: f72bf985079e3ec9a001858d89e01f0610e411a2 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001package restful
2
3// Copyright 2013 Ernest Micklei. All rights reserved.
4// Use of this source code is governed by a license
5// that can be found in the LICENSE file.
6
7import (
8 "net/http"
9 "strings"
10)
11
12// RouteFunction declares the signature of a function that can be bound to a Route.
13type RouteFunction func(*Request, *Response)
14
15// RouteSelectionConditionFunction declares the signature of a function that
16// can be used to add extra conditional logic when selecting whether the route
17// matches the HTTP request.
18type RouteSelectionConditionFunction func(httpRequest *http.Request) bool
19
20// Route binds a HTTP Method,Path,Consumes combination to a RouteFunction.
21type Route struct {
22 Method string
23 Produces []string
24 Consumes []string
25 Path string // webservice root path + described path
26 Function RouteFunction
27 Filters []FilterFunction
28 If []RouteSelectionConditionFunction
29
30 // cached values for dispatching
31 relativePath string
32 pathParts []string
33 pathExpr *pathExpression // cached compilation of relativePath as RegExp
34
35 // documentation
36 Doc string
37 Notes string
38 Operation string
39 ParameterDocs []*Parameter
40 ResponseErrors map[int]ResponseError
41 ReadSample, WriteSample interface{} // structs that model an example request or response payload
42
43 // Extra information used to store custom information about the route.
44 Metadata map[string]interface{}
45
46 // marks a route as deprecated
47 Deprecated bool
48}
49
50// Initialize for Route
51func (r *Route) postBuild() {
52 r.pathParts = tokenizePath(r.Path)
53}
54
55// Create Request and Response from their http versions
56func (r *Route) wrapRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request, pathParams map[string]string) (*Request, *Response) {
57 wrappedRequest := NewRequest(httpRequest)
58 wrappedRequest.pathParameters = pathParams
59 wrappedRequest.selectedRoutePath = r.Path
60 wrappedResponse := NewResponse(httpWriter)
61 wrappedResponse.requestAccept = httpRequest.Header.Get(HEADER_Accept)
62 wrappedResponse.routeProduces = r.Produces
63 return wrappedRequest, wrappedResponse
64}
65
66// dispatchWithFilters call the function after passing through its own filters
67func (r *Route) dispatchWithFilters(wrappedRequest *Request, wrappedResponse *Response) {
68 if len(r.Filters) > 0 {
69 chain := FilterChain{Filters: r.Filters, Target: r.Function}
70 chain.ProcessFilter(wrappedRequest, wrappedResponse)
71 } else {
72 // unfiltered
73 r.Function(wrappedRequest, wrappedResponse)
74 }
75}
76
77// Return whether the mimeType matches to what this Route can produce.
78func (r Route) matchesAccept(mimeTypesWithQuality string) bool {
79 parts := strings.Split(mimeTypesWithQuality, ",")
80 for _, each := range parts {
81 var withoutQuality string
82 if strings.Contains(each, ";") {
83 withoutQuality = strings.Split(each, ";")[0]
84 } else {
85 withoutQuality = each
86 }
87 // trim before compare
88 withoutQuality = strings.Trim(withoutQuality, " ")
89 if withoutQuality == "*/*" {
90 return true
91 }
92 for _, producibleType := range r.Produces {
93 if producibleType == "*/*" || producibleType == withoutQuality {
94 return true
95 }
96 }
97 }
98 return false
99}
100
101// Return whether this Route can consume content with a type specified by mimeTypes (can be empty).
102func (r Route) matchesContentType(mimeTypes string) bool {
103
104 if len(r.Consumes) == 0 {
105 // did not specify what it can consume ; any media type (“*/*”) is assumed
106 return true
107 }
108
109 if len(mimeTypes) == 0 {
110 // idempotent methods with (most-likely or guaranteed) empty content match missing Content-Type
111 m := r.Method
112 if m == "GET" || m == "HEAD" || m == "OPTIONS" || m == "DELETE" || m == "TRACE" {
113 return true
114 }
115 // proceed with default
116 mimeTypes = MIME_OCTET
117 }
118
119 parts := strings.Split(mimeTypes, ",")
120 for _, each := range parts {
121 var contentType string
122 if strings.Contains(each, ";") {
123 contentType = strings.Split(each, ";")[0]
124 } else {
125 contentType = each
126 }
127 // trim before compare
128 contentType = strings.Trim(contentType, " ")
129 for _, consumeableType := range r.Consumes {
130 if consumeableType == "*/*" || consumeableType == contentType {
131 return true
132 }
133 }
134 }
135 return false
136}
137
138// Tokenize an URL path using the slash separator ; the result does not have empty tokens
139func tokenizePath(path string) []string {
140 if "/" == path {
141 return []string{}
142 }
143 return strings.Split(strings.Trim(path, "/"), "/")
144}
145
146// for debugging
147func (r Route) String() string {
148 return r.Method + " " + r.Path
149}