route.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  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. Method string
  18. Produces []string
  19. Consumes []string
  20. Path string // webservice root path + described path
  21. Function RouteFunction
  22. Filters []FilterFunction
  23. If []RouteSelectionConditionFunction
  24. // cached values for dispatching
  25. relativePath string
  26. pathParts []string
  27. pathExpr *pathExpression // cached compilation of relativePath as RegExp
  28. // documentation
  29. Doc string
  30. Notes string
  31. Operation string
  32. ParameterDocs []*Parameter
  33. ResponseErrors map[int]ResponseError
  34. DefaultResponse *ResponseError
  35. ReadSample, WriteSample interface{} // structs that model an example request or response payload
  36. // Extra information used to store custom information about the route.
  37. Metadata map[string]interface{}
  38. // marks a route as deprecated
  39. Deprecated bool
  40. //Overrides the container.contentEncodingEnabled
  41. contentEncodingEnabled *bool
  42. }
  43. // Initialize for Route
  44. func (r *Route) postBuild() {
  45. r.pathParts = tokenizePath(r.Path)
  46. }
  47. // Create Request and Response from their http versions
  48. func (r *Route) wrapRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request, pathParams map[string]string) (*Request, *Response) {
  49. wrappedRequest := NewRequest(httpRequest)
  50. wrappedRequest.pathParameters = pathParams
  51. wrappedRequest.selectedRoutePath = r.Path
  52. wrappedResponse := NewResponse(httpWriter)
  53. wrappedResponse.requestAccept = httpRequest.Header.Get(HEADER_Accept)
  54. wrappedResponse.routeProduces = r.Produces
  55. return wrappedRequest, wrappedResponse
  56. }
  57. // dispatchWithFilters call the function after passing through its own filters
  58. func (r *Route) dispatchWithFilters(wrappedRequest *Request, wrappedResponse *Response) {
  59. if len(r.Filters) > 0 {
  60. chain := FilterChain{Filters: r.Filters, Target: r.Function}
  61. chain.ProcessFilter(wrappedRequest, wrappedResponse)
  62. } else {
  63. // unfiltered
  64. r.Function(wrappedRequest, wrappedResponse)
  65. }
  66. }
  67. func stringTrimSpaceCutset(r rune) bool {
  68. return r == ' '
  69. }
  70. // Return whether the mimeType matches to what this Route can produce.
  71. func (r Route) matchesAccept(mimeTypesWithQuality string) bool {
  72. remaining := mimeTypesWithQuality
  73. for {
  74. var mimeType string
  75. if end := strings.Index(remaining, ","); end == -1 {
  76. mimeType, remaining = remaining, ""
  77. } else {
  78. mimeType, remaining = remaining[:end], remaining[end+1:]
  79. }
  80. if quality := strings.Index(mimeType, ";"); quality != -1 {
  81. mimeType = mimeType[:quality]
  82. }
  83. mimeType = strings.TrimFunc(mimeType, stringTrimSpaceCutset)
  84. if mimeType == "*/*" {
  85. return true
  86. }
  87. for _, producibleType := range r.Produces {
  88. if producibleType == "*/*" || producibleType == mimeType {
  89. return true
  90. }
  91. }
  92. if len(remaining) == 0 {
  93. return false
  94. }
  95. }
  96. }
  97. // Return whether this Route can consume content with a type specified by mimeTypes (can be empty).
  98. func (r Route) matchesContentType(mimeTypes string) bool {
  99. if len(r.Consumes) == 0 {
  100. // did not specify what it can consume ; any media type (“*/*”) is assumed
  101. return true
  102. }
  103. if len(mimeTypes) == 0 {
  104. // idempotent methods with (most-likely or guaranteed) empty content match missing Content-Type
  105. m := r.Method
  106. if m == "GET" || m == "HEAD" || m == "OPTIONS" || m == "DELETE" || m == "TRACE" {
  107. return true
  108. }
  109. // proceed with default
  110. mimeTypes = MIME_OCTET
  111. }
  112. remaining := mimeTypes
  113. for {
  114. var mimeType string
  115. if end := strings.Index(remaining, ","); end == -1 {
  116. mimeType, remaining = remaining, ""
  117. } else {
  118. mimeType, remaining = remaining[:end], remaining[end+1:]
  119. }
  120. if quality := strings.Index(mimeType, ";"); quality != -1 {
  121. mimeType = mimeType[:quality]
  122. }
  123. mimeType = strings.TrimFunc(mimeType, stringTrimSpaceCutset)
  124. for _, consumeableType := range r.Consumes {
  125. if consumeableType == "*/*" || consumeableType == mimeType {
  126. return true
  127. }
  128. }
  129. if len(remaining) == 0 {
  130. return false
  131. }
  132. }
  133. }
  134. // Tokenize an URL path using the slash separator ; the result does not have empty tokens
  135. func tokenizePath(path string) []string {
  136. if "/" == path {
  137. return nil
  138. }
  139. return strings.Split(strings.Trim(path, "/"), "/")
  140. }
  141. // for debugging
  142. func (r Route) String() string {
  143. return r.Method + " " + r.Path
  144. }
  145. // EnableContentEncoding (default=false) allows for GZIP or DEFLATE encoding of responses. Overrides the container.contentEncodingEnabled value.
  146. func (r Route) EnableContentEncoding(enabled bool) {
  147. r.contentEncodingEnabled = &enabled
  148. }