route.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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. // Extra information used to store custom information about the route.
  38. Metadata map[string]interface{}
  39. // marks a route as deprecated
  40. Deprecated bool
  41. //Overrides the container.contentEncodingEnabled
  42. contentEncodingEnabled *bool
  43. // indicate route path has custom verb
  44. hasCustomVerb bool
  45. // if a request does not include a content-type header then
  46. // depending on the method, it may return a 415 Unsupported Media
  47. // Must have uppercase HTTP Method names such as GET,HEAD,OPTIONS,...
  48. allowedMethodsWithoutContentType []string
  49. }
  50. // Initialize for Route
  51. func (r *Route) postBuild() {
  52. r.pathParts = tokenizePath(r.Path)
  53. r.hasCustomVerb = hasCustomVerb(r.Path)
  54. }
  55. // Create Request and Response from their http versions
  56. func (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.selectedRoute = r
  60. wrappedResponse := NewResponse(httpWriter)
  61. wrappedResponse.requestAccept = httpRequest.Header.Get(HEADER_Accept)
  62. wrappedResponse.routeProduces = r.Produces
  63. return wrappedRequest, wrappedResponse
  64. }
  65. func stringTrimSpaceCutset(r rune) bool {
  66. return r == ' '
  67. }
  68. // Return whether the mimeType matches to what this Route can produce.
  69. func (r Route) matchesAccept(mimeTypesWithQuality string) bool {
  70. remaining := mimeTypesWithQuality
  71. for {
  72. var mimeType string
  73. if end := strings.Index(remaining, ","); end == -1 {
  74. mimeType, remaining = remaining, ""
  75. } else {
  76. mimeType, remaining = remaining[:end], remaining[end+1:]
  77. }
  78. if quality := strings.Index(mimeType, ";"); quality != -1 {
  79. mimeType = mimeType[:quality]
  80. }
  81. mimeType = strings.TrimFunc(mimeType, stringTrimSpaceCutset)
  82. if mimeType == "*/*" {
  83. return true
  84. }
  85. for _, producibleType := range r.Produces {
  86. if producibleType == "*/*" || producibleType == mimeType {
  87. return true
  88. }
  89. }
  90. if len(remaining) == 0 {
  91. return false
  92. }
  93. }
  94. }
  95. // Return whether this Route can consume content with a type specified by mimeTypes (can be empty).
  96. func (r Route) matchesContentType(mimeTypes string) bool {
  97. if len(r.Consumes) == 0 {
  98. // did not specify what it can consume ; any media type (“*/*”) is assumed
  99. return true
  100. }
  101. if len(mimeTypes) == 0 {
  102. // idempotent methods with (most-likely or guaranteed) empty content match missing Content-Type
  103. m := r.Method
  104. // if route specifies less or non-idempotent methods then use that
  105. if len(r.allowedMethodsWithoutContentType) > 0 {
  106. for _, each := range r.allowedMethodsWithoutContentType {
  107. if m == each {
  108. return true
  109. }
  110. }
  111. } else {
  112. if m == "GET" || m == "HEAD" || m == "OPTIONS" || m == "DELETE" || m == "TRACE" {
  113. return true
  114. }
  115. }
  116. // proceed with default
  117. mimeTypes = MIME_OCTET
  118. }
  119. remaining := mimeTypes
  120. for {
  121. var mimeType string
  122. if end := strings.Index(remaining, ","); end == -1 {
  123. mimeType, remaining = remaining, ""
  124. } else {
  125. mimeType, remaining = remaining[:end], remaining[end+1:]
  126. }
  127. if quality := strings.Index(mimeType, ";"); quality != -1 {
  128. mimeType = mimeType[:quality]
  129. }
  130. mimeType = strings.TrimFunc(mimeType, stringTrimSpaceCutset)
  131. for _, consumeableType := range r.Consumes {
  132. if consumeableType == "*/*" || consumeableType == mimeType {
  133. return true
  134. }
  135. }
  136. if len(remaining) == 0 {
  137. return false
  138. }
  139. }
  140. }
  141. // Tokenize an URL path using the slash separator ; the result does not have empty tokens
  142. func tokenizePath(path string) []string {
  143. if "/" == path {
  144. return nil
  145. }
  146. return strings.Split(strings.Trim(path, "/"), "/")
  147. }
  148. // for debugging
  149. func (r *Route) String() string {
  150. return r.Method + " " + r.Path
  151. }
  152. // EnableContentEncoding (default=false) allows for GZIP or DEFLATE encoding of responses. Overrides the container.contentEncodingEnabled value.
  153. func (r *Route) EnableContentEncoding(enabled bool) {
  154. r.contentEncodingEnabled = &enabled
  155. }