route.go 5.2 KB
Newer Older
J
jeff 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
package restful

// Copyright 2013 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.

import (
	"net/http"
	"strings"
)

// RouteFunction declares the signature of a function that can be bound to a Route.
type RouteFunction func(*Request, *Response)

// RouteSelectionConditionFunction declares the signature of a function that
// can be used to add extra conditional logic when selecting whether the route
// matches the HTTP request.
type RouteSelectionConditionFunction func(httpRequest *http.Request) bool

// Route binds a HTTP Method,Path,Consumes combination to a RouteFunction.
type Route struct {
	Method   string
	Produces []string
	Consumes []string
	Path     string // webservice root path + described path
	Function RouteFunction
	Filters  []FilterFunction
	If       []RouteSelectionConditionFunction

	// cached values for dispatching
	relativePath string
	pathParts    []string
	pathExpr     *pathExpression // cached compilation of relativePath as RegExp

	// documentation
	Doc                     string
	Notes                   string
	Operation               string
	ParameterDocs           []*Parameter
	ResponseErrors          map[int]ResponseError
J
Jeff 已提交
41
	DefaultResponse         *ResponseError
J
jeff 已提交
42 43 44 45 46 47 48
	ReadSample, WriteSample interface{} // structs that model an example request or response payload

	// Extra information used to store custom information about the route.
	Metadata map[string]interface{}

	// marks a route as deprecated
	Deprecated bool
H
hongming 已提交
49 50 51

	//Overrides the container.contentEncodingEnabled
	contentEncodingEnabled *bool
H
hongming 已提交
52 53 54 55 56 57 58 59

	// indicate route path has custom verb
	hasCustomVerb bool

	// if a request does not include a content-type header then
	// depending on the method, it may return a 415 Unsupported Media
	// Must have uppercase HTTP Method names such as GET,HEAD,OPTIONS,...
	allowedMethodsWithoutContentType []string
J
jeff 已提交
60 61 62 63 64
}

// Initialize for Route
func (r *Route) postBuild() {
	r.pathParts = tokenizePath(r.Path)
H
hongming 已提交
65
	r.hasCustomVerb = hasCustomVerb(r.Path)
J
jeff 已提交
66 67 68 69 70 71 72 73 74 75 76 77 78
}

// Create Request and Response from their http versions
func (r *Route) wrapRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request, pathParams map[string]string) (*Request, *Response) {
	wrappedRequest := NewRequest(httpRequest)
	wrappedRequest.pathParameters = pathParams
	wrappedRequest.selectedRoutePath = r.Path
	wrappedResponse := NewResponse(httpWriter)
	wrappedResponse.requestAccept = httpRequest.Header.Get(HEADER_Accept)
	wrappedResponse.routeProduces = r.Produces
	return wrappedRequest, wrappedResponse
}

J
Jeff 已提交
79 80 81 82
func stringTrimSpaceCutset(r rune) bool {
	return r == ' '
}

J
jeff 已提交
83 84
// Return whether the mimeType matches to what this Route can produce.
func (r Route) matchesAccept(mimeTypesWithQuality string) bool {
J
Jeff 已提交
85 86 87 88 89
	remaining := mimeTypesWithQuality
	for {
		var mimeType string
		if end := strings.Index(remaining, ","); end == -1 {
			mimeType, remaining = remaining, ""
J
jeff 已提交
90
		} else {
J
Jeff 已提交
91 92 93 94
			mimeType, remaining = remaining[:end], remaining[end+1:]
		}
		if quality := strings.Index(mimeType, ";"); quality != -1 {
			mimeType = mimeType[:quality]
J
jeff 已提交
95
		}
J
Jeff 已提交
96 97
		mimeType = strings.TrimFunc(mimeType, stringTrimSpaceCutset)
		if mimeType == "*/*" {
J
jeff 已提交
98 99 100
			return true
		}
		for _, producibleType := range r.Produces {
J
Jeff 已提交
101
			if producibleType == "*/*" || producibleType == mimeType {
J
jeff 已提交
102 103 104
				return true
			}
		}
J
Jeff 已提交
105 106 107
		if len(remaining) == 0 {
			return false
		}
J
jeff 已提交
108 109 110 111 112 113 114 115 116 117 118 119 120 121
	}
}

// Return whether this Route can consume content with a type specified by mimeTypes (can be empty).
func (r Route) matchesContentType(mimeTypes string) bool {

	if len(r.Consumes) == 0 {
		// did not specify what it can consume ;  any media type (“*/*”) is assumed
		return true
	}

	if len(mimeTypes) == 0 {
		// idempotent methods with (most-likely or guaranteed) empty content match missing Content-Type
		m := r.Method
H
hongming 已提交
122 123 124 125 126 127 128 129 130 131 132
		// if route specifies less or non-idempotent methods then use that
		if len(r.allowedMethodsWithoutContentType) > 0 {
			for _, each := range r.allowedMethodsWithoutContentType {
				if m == each {
					return true
				}
			}
		} else {
			if m == "GET" || m == "HEAD" || m == "OPTIONS" || m == "DELETE" || m == "TRACE" {
				return true
			}
J
jeff 已提交
133 134 135 136 137
		}
		// proceed with default
		mimeTypes = MIME_OCTET
	}

J
Jeff 已提交
138 139 140 141 142
	remaining := mimeTypes
	for {
		var mimeType string
		if end := strings.Index(remaining, ","); end == -1 {
			mimeType, remaining = remaining, ""
J
jeff 已提交
143
		} else {
J
Jeff 已提交
144 145 146 147
			mimeType, remaining = remaining[:end], remaining[end+1:]
		}
		if quality := strings.Index(mimeType, ";"); quality != -1 {
			mimeType = mimeType[:quality]
J
jeff 已提交
148
		}
J
Jeff 已提交
149
		mimeType = strings.TrimFunc(mimeType, stringTrimSpaceCutset)
J
jeff 已提交
150
		for _, consumeableType := range r.Consumes {
J
Jeff 已提交
151
			if consumeableType == "*/*" || consumeableType == mimeType {
J
jeff 已提交
152 153 154
				return true
			}
		}
J
Jeff 已提交
155 156 157
		if len(remaining) == 0 {
			return false
		}
J
jeff 已提交
158 159 160 161 162 163
	}
}

// Tokenize an URL path using the slash separator ; the result does not have empty tokens
func tokenizePath(path string) []string {
	if "/" == path {
J
Jeff 已提交
164
		return nil
J
jeff 已提交
165 166 167 168 169 170 171 172
	}
	return strings.Split(strings.Trim(path, "/"), "/")
}

// for debugging
func (r Route) String() string {
	return r.Method + " " + r.Path
}
H
hongming 已提交
173 174 175 176 177

// EnableContentEncoding (default=false) allows for GZIP or DEFLATE encoding of responses. Overrides the container.contentEncodingEnabled value.
func (r Route) EnableContentEncoding(enabled bool) {
	r.contentEncodingEnabled = &enabled
}