...

Source file src/github.com/emicklei/go-restful/v3/route.go

Documentation: github.com/emicklei/go-restful/v3

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

View as plain text