route.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  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. "bytes"
  7. "net/http"
  8. "strings"
  9. )
  10. // RouteFunction declares the signature of a function that can be bound to a Route.
  11. type RouteFunction func(*Request, *Response)
  12. // Route binds a HTTP Method,Path,Consumes combination to a RouteFunction.
  13. type Route struct {
  14. Method string
  15. Produces []string
  16. Consumes []string
  17. Path string // webservice root path + described path
  18. Function RouteFunction
  19. Filters []FilterFunction
  20. // cached values for dispatching
  21. relativePath string
  22. pathParts []string
  23. pathExpr *pathExpression // cached compilation of relativePath as RegExp
  24. // documentation
  25. Doc string
  26. Notes string
  27. Operation string
  28. ParameterDocs []*Parameter
  29. ResponseErrors map[int]ResponseError
  30. ReadSample, WriteSample interface{} // structs that model an example request or response payload
  31. // Extra information used to store custom information about the route.
  32. Metadata map[string]interface{}
  33. }
  34. // Initialize for Route
  35. func (r *Route) postBuild() {
  36. r.pathParts = tokenizePath(r.Path)
  37. }
  38. // Create Request and Response from their http versions
  39. func (r *Route) wrapRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request) (*Request, *Response) {
  40. params := r.extractParameters(httpRequest.URL.Path)
  41. wrappedRequest := NewRequest(httpRequest)
  42. wrappedRequest.pathParameters = params
  43. wrappedRequest.selectedRoutePath = r.Path
  44. wrappedResponse := NewResponse(httpWriter)
  45. wrappedResponse.requestAccept = httpRequest.Header.Get(HEADER_Accept)
  46. wrappedResponse.routeProduces = r.Produces
  47. return wrappedRequest, wrappedResponse
  48. }
  49. // dispatchWithFilters call the function after passing through its own filters
  50. func (r *Route) dispatchWithFilters(wrappedRequest *Request, wrappedResponse *Response) {
  51. if len(r.Filters) > 0 {
  52. chain := FilterChain{Filters: r.Filters, Target: r.Function}
  53. chain.ProcessFilter(wrappedRequest, wrappedResponse)
  54. } else {
  55. // unfiltered
  56. r.Function(wrappedRequest, wrappedResponse)
  57. }
  58. }
  59. // Return whether the mimeType matches to what this Route can produce.
  60. func (r Route) matchesAccept(mimeTypesWithQuality string) bool {
  61. parts := strings.Split(mimeTypesWithQuality, ",")
  62. for _, each := range parts {
  63. var withoutQuality string
  64. if strings.Contains(each, ";") {
  65. withoutQuality = strings.Split(each, ";")[0]
  66. } else {
  67. withoutQuality = each
  68. }
  69. // trim before compare
  70. withoutQuality = strings.Trim(withoutQuality, " ")
  71. if withoutQuality == "*/*" {
  72. return true
  73. }
  74. for _, producibleType := range r.Produces {
  75. if producibleType == "*/*" || producibleType == withoutQuality {
  76. return true
  77. }
  78. }
  79. }
  80. return false
  81. }
  82. // Return whether this Route can consume content with a type specified by mimeTypes (can be empty).
  83. func (r Route) matchesContentType(mimeTypes string) bool {
  84. if len(r.Consumes) == 0 {
  85. // did not specify what it can consume ; any media type (“*/*”) is assumed
  86. return true
  87. }
  88. if len(mimeTypes) == 0 {
  89. // idempotent methods with (most-likely or garanteed) empty content match missing Content-Type
  90. m := r.Method
  91. if m == "GET" || m == "HEAD" || m == "OPTIONS" || m == "DELETE" || m == "TRACE" {
  92. return true
  93. }
  94. // proceed with default
  95. mimeTypes = MIME_OCTET
  96. }
  97. parts := strings.Split(mimeTypes, ",")
  98. for _, each := range parts {
  99. var contentType string
  100. if strings.Contains(each, ";") {
  101. contentType = strings.Split(each, ";")[0]
  102. } else {
  103. contentType = each
  104. }
  105. // trim before compare
  106. contentType = strings.Trim(contentType, " ")
  107. for _, consumeableType := range r.Consumes {
  108. if consumeableType == "*/*" || consumeableType == contentType {
  109. return true
  110. }
  111. }
  112. }
  113. return false
  114. }
  115. // Extract the parameters from the request url path
  116. func (r Route) extractParameters(urlPath string) map[string]string {
  117. urlParts := tokenizePath(urlPath)
  118. pathParameters := map[string]string{}
  119. for i, key := range r.pathParts {
  120. var value string
  121. if i >= len(urlParts) {
  122. value = ""
  123. } else {
  124. value = urlParts[i]
  125. }
  126. if strings.HasPrefix(key, "{") { // path-parameter
  127. if colon := strings.Index(key, ":"); colon != -1 {
  128. // extract by regex
  129. regPart := key[colon+1 : len(key)-1]
  130. keyPart := key[1:colon]
  131. if regPart == "*" {
  132. pathParameters[keyPart] = untokenizePath(i, urlParts)
  133. break
  134. } else {
  135. pathParameters[keyPart] = value
  136. }
  137. } else {
  138. // without enclosing {}
  139. pathParameters[key[1:len(key)-1]] = value
  140. }
  141. }
  142. }
  143. return pathParameters
  144. }
  145. // Untokenize back into an URL path using the slash separator
  146. func untokenizePath(offset int, parts []string) string {
  147. var buffer bytes.Buffer
  148. for p := offset; p < len(parts); p++ {
  149. buffer.WriteString(parts[p])
  150. // do not end
  151. if p < len(parts)-1 {
  152. buffer.WriteString("/")
  153. }
  154. }
  155. return buffer.String()
  156. }
  157. // Tokenize an URL path using the slash separator ; the result does not have empty tokens
  158. func tokenizePath(path string) []string {
  159. if "/" == path {
  160. return []string{}
  161. }
  162. return strings.Split(strings.Trim(path, "/"), "/")
  163. }
  164. // for debugging
  165. func (r Route) String() string {
  166. return r.Method + " " + r.Path
  167. }