route.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. package restful
  2. // Copyright 2013 Ernest Micklei. All rights reserved.
  3. // Use of this source code is governed by a license
  4. // that can be found in the LICENSE file.
  5. import (
  6. "net/http"
  7. "strings"
  8. )
  9. // RouteFunction declares the signature of a function that can be bound to a Route.
  10. type RouteFunction func(*Request, *Response)
  11. // RouteSelectionConditionFunction declares the signature of a function that
  12. // can be used to add extra conditional logic when selecting whether the route
  13. // matches the HTTP request.
  14. type RouteSelectionConditionFunction func(httpRequest *http.Request) bool
  15. // Route binds a HTTP Method,Path,Consumes combination to a RouteFunction.
  16. type Route struct {
  17. ExtensionProperties
  18. Method string
  19. Produces []string
  20. Consumes []string
  21. Path string // webservice root path + described path
  22. Function RouteFunction
  23. Filters []FilterFunction
  24. If []RouteSelectionConditionFunction
  25. // cached values for dispatching
  26. relativePath string
  27. pathParts []string
  28. pathExpr *pathExpression // cached compilation of relativePath as RegExp
  29. // documentation
  30. Doc string
  31. Notes string
  32. Operation string
  33. ParameterDocs []*Parameter
  34. ResponseErrors map[int]ResponseError
  35. DefaultResponse *ResponseError
  36. ReadSample, WriteSample interface{} // structs that model an example request or response payload
  37. WriteSamples []interface{} // if more than one return types is possible (oneof) then this will contain multiple values
  38. // Extra information used to store custom information about the route.
  39. Metadata map[string]interface{}
  40. // marks a route as deprecated
  41. Deprecated bool
  42. //Overrides the container.contentEncodingEnabled
  43. contentEncodingEnabled *bool
  44. // indicate route path has custom verb
  45. hasCustomVerb bool
  46. // if a request does not include a content-type header then
  47. // depending on the method, it may return a 415 Unsupported Media
  48. // Must have uppercase HTTP Method names such as GET,HEAD,OPTIONS,...
  49. allowedMethodsWithoutContentType []string
  50. }
  51. // Initialize for Route
  52. func (r *Route) postBuild() {
  53. r.pathParts = tokenizePath(r.Path)
  54. r.hasCustomVerb = hasCustomVerb(r.Path)
  55. }
  56. // Create Request and Response from their http versions
  57. func (r *Route) wrapRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request, pathParams map[string]string) (*Request, *Response) {
  58. wrappedRequest := NewRequest(httpRequest)
  59. wrappedRequest.pathParameters = pathParams
  60. wrappedRequest.selectedRoute = r
  61. wrappedResponse := NewResponse(httpWriter)
  62. wrappedResponse.requestAccept = httpRequest.Header.Get(HEADER_Accept)
  63. wrappedResponse.routeProduces = r.Produces
  64. return wrappedRequest, wrappedResponse
  65. }
  66. func stringTrimSpaceCutset(r rune) bool {
  67. return r == ' '
  68. }
  69. // Return whether the mimeType matches to what this Route can produce.
  70. func (r Route) matchesAccept(mimeTypesWithQuality string) bool {
  71. remaining := mimeTypesWithQuality
  72. for {
  73. var mimeType string
  74. if end := strings.Index(remaining, ","); end == -1 {
  75. mimeType, remaining = remaining, ""
  76. } else {
  77. mimeType, remaining = remaining[:end], remaining[end+1:]
  78. }
  79. if quality := strings.Index(mimeType, ";"); quality != -1 {
  80. mimeType = mimeType[:quality]
  81. }
  82. mimeType = strings.TrimFunc(mimeType, stringTrimSpaceCutset)
  83. if mimeType == "*/*" {
  84. return true
  85. }
  86. for _, producibleType := range r.Produces {
  87. if producibleType == "*/*" || producibleType == mimeType {
  88. return true
  89. }
  90. }
  91. if len(remaining) == 0 {
  92. return false
  93. }
  94. }
  95. }
  96. // Return whether this Route can consume content with a type specified by mimeTypes (can be empty).
  97. // If the route does not specify Consumes then return true (*/*).
  98. // If no content type is set then return true for GET,HEAD,OPTIONS,DELETE and TRACE.
  99. func (r Route) matchesContentType(mimeTypes string) bool {
  100. if len(r.Consumes) == 0 {
  101. // did not specify what it can consume ; any media type (“*/*”) is assumed
  102. return true
  103. }
  104. if len(mimeTypes) == 0 {
  105. // idempotent methods with (most-likely or guaranteed) empty content match missing Content-Type
  106. m := r.Method
  107. // if route specifies less or non-idempotent methods then use that
  108. if len(r.allowedMethodsWithoutContentType) > 0 {
  109. for _, each := range r.allowedMethodsWithoutContentType {
  110. if m == each {
  111. return true
  112. }
  113. }
  114. } else {
  115. if m == "GET" || m == "HEAD" || m == "OPTIONS" || m == "DELETE" || m == "TRACE" {
  116. return true
  117. }
  118. }
  119. // proceed with default
  120. mimeTypes = MIME_OCTET
  121. }
  122. remaining := mimeTypes
  123. for {
  124. var mimeType string
  125. if end := strings.Index(remaining, ","); end == -1 {
  126. mimeType, remaining = remaining, ""
  127. } else {
  128. mimeType, remaining = remaining[:end], remaining[end+1:]
  129. }
  130. if quality := strings.Index(mimeType, ";"); quality != -1 {
  131. mimeType = mimeType[:quality]
  132. }
  133. mimeType = strings.TrimFunc(mimeType, stringTrimSpaceCutset)
  134. for _, consumeableType := range r.Consumes {
  135. if consumeableType == "*/*" || consumeableType == mimeType {
  136. return true
  137. }
  138. }
  139. if len(remaining) == 0 {
  140. return false
  141. }
  142. }
  143. }
  144. // Tokenize an URL path using the slash separator ; the result does not have empty tokens
  145. func tokenizePath(path string) []string {
  146. if "/" == path {
  147. return nil
  148. }
  149. if TrimRightSlashEnabled {
  150. // 3.9.0
  151. return strings.Split(strings.Trim(path, "/"), "/")
  152. } else {
  153. // 3.10.2
  154. return strings.Split(strings.TrimLeft(path, "/"), "/")
  155. }
  156. }
  157. // for debugging
  158. func (r *Route) String() string {
  159. return r.Method + " " + r.Path
  160. }
  161. // EnableContentEncoding (default=false) allows for GZIP or DEFLATE encoding of responses. Overrides the container.contentEncodingEnabled value.
  162. func (r *Route) EnableContentEncoding(enabled bool) {
  163. r.contentEncodingEnabled = &enabled
  164. }
  165. // TrimRightSlashEnabled controls whether
  166. // - path on route building is using path.Join
  167. // - the path of the incoming request is trimmed of its slash suffux.
  168. // Value of true matches the behavior of <= 3.9.0
  169. var TrimRightSlashEnabled = true