<commit_before>package webgo

import (
	"context"
	"fmt"
	"net/http"
	"regexp"
	"strings"
)

// urlchars is regex to validate characters in a URI parameter
// const urlchars = `([a-zA-Z0-9\*\-+._~!$()=&',;:@%]+)`
// Regex prepared based on http://stackoverflow.com/a/4669750/1359163,
// https://tools.ietf.org/html/rfc3986
// Though the current one allows invalid characters in the URI parameter, it has better performance.
const (
	urlchars            = `([^/]+)`
	urlwildcard         = `(.*)`
	trailingSlash       = `[\/]?`
	errMultiHeaderWrite = `http: multiple response.WriteHeader calls`
	errMultiWrite       = `http: multiple response.Write calls`
	errDuplicateKey     = `Error: Duplicate URI keys found`
)

var validHTTPMethods = []string{
	http.MethodOptions,
	http.MethodHead,
	http.MethodGet,
	http.MethodPost,
	http.MethodPut,
	http.MethodPatch,
	http.MethodDelete,
}

// customResponseWriter is a custom HTTP response writer
type customResponseWriter struct {
	http.ResponseWriter
	statusCode int
	written    bool
}

// WriteHeader is the interface implementation to get HTTP response code and add
// it to the custom response writer
func (crw *customResponseWriter) WriteHeader(code int) {
	if crw.written {
		warnLogger.Println(errMultiHeaderWrite)
		return
	}

	crw.statusCode = code
	crw.ResponseWriter.WriteHeader(code)
}

// Write is the interface implementation to respond to the HTTP request,
// but check if a response was already sent.
func (crw *customResponseWriter) Write(body []byte) (int, error) {
	if crw.written {
		warnLogger.Println(errMultiWrite)
		return 0, nil
	}

	crw.written = true
	return crw.ResponseWriter.Write(body)
}

// Route defines a route for each API
type Route struct {
	// Name is unique identifier for the route
	Name string
	// Method is the HTTP request method/type
	Method string
	// Pattern is the URI pattern to match
	Pattern string
	// TrailingSlash if set to true, the URI will be matched with or without
	// a trailing slash. Note: It does not *do* a redirect.
	TrailingSlash bool

	// FallThroughPostResponse if enabled will execute all the handlers even if a response was already sent to the client
	FallThroughPostResponse bool

	// Handlers is a slice of http.HandlerFunc which can be middlewares or anything else. Though only 1 of them will be allowed to respond to client.
	// subsequent writes from the following handlers will be ignored
	Handlers []http.HandlerFunc

	// uriKeys is the list of URI parameter variables available for this route
	uriKeys []string
	// uriPatternString is the pattern string which is compiled to regex object
	uriPatternString string
	// uriPattern is the compiled regex to match the URI pattern
	uriPattern *regexp.Regexp
}

// computerPatternStr computes the pattern string required for the route's regex.
// It also adds the URI parameter key to the route's `keys` field
func (r *Route) computerPatternStr(patternString string, hasWildcard bool, key string) string {
	regexPattern := ""
	patternKey := ""
	if hasWildcard {
		patternKey = fmt.Sprintf(":%s*", key)
		regexPattern = urlwildcard
	} else {
		patternKey = fmt.Sprintf(":%s", key)
		regexPattern = urlchars
	}

	patternString = strings.Replace(patternString, patternKey, regexPattern, 1)

	for idx, k := range r.uriKeys {
		if key == k {
			errLogger.Fatalln(errDuplicateKey, "\nURI: ", r.Pattern, "\nKey:", k, ", Position:", idx+1)
		}
	}

	r.uriKeys = append(r.uriKeys, key)
	return patternString
}

// init prepares the URIKeys, compile regex for the provided pattern
func (r *Route) init() error {
	patternString := r.Pattern

	if strings.Contains(r.Pattern, ":") {
		// uriValues is a map of URI Key and it's respective value,
		// this is calculated per request
		key := ""
		hasKey := false
		hasWildcard := false

		for i := 0; i < len(r.Pattern); i++ {
			char := string(r.Pattern[i])

			if char == ":" {
				hasKey = true
			} else if char == "*" {
				hasWildcard = true
			} else if hasKey && char != "/" {
				key += char
			} else if hasKey && len(key) > 0 {
				patternString = r.computerPatternStr(patternString, hasWildcard, key)
				hasWildcard, hasKey = false, false
				key = ""
			}
		}

		if hasKey && len(key) > 0 {
			patternString = r.computerPatternStr(patternString, hasWildcard, key)
		}

	}

	if r.TrailingSlash {
		patternString = fmt.Sprintf("^%s%s$", patternString, trailingSlash)
	} else {
		patternString = fmt.Sprintf("^%s$", patternString)
	}

	// compile the regex for the pattern string calculated
	reg, err := regexp.Compile(patternString)
	if err != nil {
		return err
	}

	r.uriPattern = reg
	r.uriPatternString = patternString
	return nil
}

// matchAndGet returns if the request URI matches the pattern defined in a Route as well as
// all the URI parameters configured for the route.
func (r *Route) matchAndGet(requestURI string) (bool, map[string]string) {
	if r.Pattern == requestURI {
		return true, nil
	}

	if !r.uriPattern.Match([]byte(requestURI)) {
		return false, nil
	}

	// Getting URI parameters
	values := r.uriPattern.FindStringSubmatch(requestURI)
	if len(values) == 0 {
		return true, nil
	}

	uriValues := make(map[string]string, len(values)-1)
	for i := 1; i < len(values); i++ {
		uriValues[r.uriKeys[i-1]] = values[i]
	}
	return true, uriValues

}

// Router is the HTTP router
type Router struct {
	optHandlers    []*Route
	headHandlers   []*Route
	getHandlers    []*Route
	postHandlers   []*Route
	putHandlers    []*Route
	patchHandlers  []*Route
	deleteHandlers []*Route

	// NotFound is the generic handler for 404 resource not found response
	NotFound http.HandlerFunc
	// AppContext holds all the app specific context which is to be injected into all HTTP
	// request context
	AppContext map[string]interface{}

	// config has all the app config
	config       *Config
	serveHandler http.HandlerFunc
	// httpServer is the server handler for the active HTTP server
	httpServer *http.Server
	// httpsServer is the server handler for the active HTTPS server
	httpsServer *http.Server
}

func (rtr *Router) serve(rw http.ResponseWriter, req *http.Request) {
	var rr []*Route

	switch req.Method {
	case http.MethodOptions:
		rr = rtr.optHandlers
	case http.MethodHead:
		rr = rtr.headHandlers
	case http.MethodGet:
		rr = rtr.getHandlers
	case http.MethodPost:
		rr = rtr.postHandlers
	case http.MethodPut:
		rr = rtr.putHandlers
	case http.MethodPatch:
		rr = rtr.patchHandlers
	case http.MethodDelete:
		rr = rtr.deleteHandlers
	}

	var route *Route
	ok := false
	params := make(map[string]string, 0)
	path := req.URL.EscapedPath()
	for _, r := range rr {
		if ok, params = r.matchAndGet(path); ok {
			route = r
			break
		}
	}

	if !ok {
		// serve 404 when there are no matching routes
		rtr.NotFound(rw, req)
		return
	}

	crw := &customResponseWriter{
		ResponseWriter: rw,
	}
	// webgo context object created and is injected to the request context
	reqwc := req.WithContext(
		context.WithValue(
			req.Context(),
			wgoCtxKey,
			&WC{
				Params:     params,
				Route:      route,
				AppContext: rtr.AppContext,
			},
		),
	)

	for _, handler := range route.Handlers {
		if crw.written == false {
			// If there has been no write to response writer yet
			handler(crw, reqwc)
		} else if route.FallThroughPostResponse {
			// run a handler post response write, only if fall through is enabled
			handler(crw, reqwc)
		} else {
			// Do not run any more handlers if already responded and no fall through enabled
			break
		}
	}
}

// ServeHTTP is the required `ServeHTTP` implementation to listen to HTTP requests
func (rtr *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	rtr.serveHandler(rw, req)
}

// Use adds a middleware layer
func (rtr *Router) Use(f func(http.ResponseWriter, *http.Request, http.HandlerFunc)) {
	srv := rtr.serveHandler
	rtr.serveHandler = func(rw http.ResponseWriter, req *http.Request) {
		f(rw, req, srv)
	}
}

// NewRouter initializes returns a new router instance with all the configurations and routes set
func NewRouter(cfg *Config, routes []*Route) *Router {
	handlers := make(map[string][]*Route, len(validHTTPMethods))

	for _, validMethod := range validHTTPMethods {
		handlers[validMethod] = []*Route{}
	}

	for idx, route := range routes {
		found := false
		for _, validMethod := range validHTTPMethods {
			if route.Method == validMethod {
				found = true
			}
		}

		if !found {
			errLogger.Fatalln("Unsupported HTTP request method provided. Method:", route.Method)
		}

		if route.Handlers == nil || len(route.Handlers) == 0 {
			errLogger.Fatalln("No handlers provided for the route '", route.Pattern, "', method '", route.Method, "'")
		}

		err := route.init()
		if err != nil {
			errLogger.Fatalln("Unsupported URI pattern.", route.Pattern, err)
		}

		// checking if the URI pattern is duplicated
		for i := 0; i < idx; i++ {
			rt := routes[i]

			if rt.Name == route.Name {
				warnLogger.Println("Duplicate route name(\"" + rt.Name + "\") detected. Route name should be unique.")
			}

			if rt.Method == route.Method {
				// regex pattern match
				if ok, _ := rt.matchAndGet(route.Pattern); ok {
					warnLogger.Println("Duplicate URI pattern detected.\nPattern: '" + rt.Pattern + "'\nDuplicate pattern: '" + route.Pattern + "'")
					infoLogger.Println("Only the first route to match the URI pattern would handle the request")
				}
			}
		}

		handlers[route.Method] = append(handlers[route.Method], route)
	}

	r := &Router{
		optHandlers:    handlers[http.MethodOptions],
		headHandlers:   handlers[http.MethodHead],
		getHandlers:    handlers[http.MethodGet],
		postHandlers:   handlers[http.MethodPost],
		putHandlers:    handlers[http.MethodPut],
		patchHandlers:  handlers[http.MethodPatch],
		deleteHandlers: handlers[http.MethodDelete],

		NotFound:   http.NotFound,
		AppContext: make(map[string]interface{}, 0),
		config:     cfg,
	}
	// setting the default serve handler
	r.serveHandler = r.serve

	return r
}
<commit_msg>fixed typo in function name<commit_after>package webgo

import (
	"context"
	"fmt"
	"net/http"
	"regexp"
	"strings"
)

// urlchars is regex to validate characters in a URI parameter
// const urlchars = `([a-zA-Z0-9\*\-+._~!$()=&',;:@%]+)`
// Regex prepared based on http://stackoverflow.com/a/4669750/1359163,
// https://tools.ietf.org/html/rfc3986
// Though the current one allows invalid characters in the URI parameter, it has better performance.
const (
	urlchars            = `([^/]+)`
	urlwildcard         = `(.*)`
	trailingSlash       = `[\/]?`
	errMultiHeaderWrite = `http: multiple response.WriteHeader calls`
	errMultiWrite       = `http: multiple response.Write calls`
	errDuplicateKey     = `Error: Duplicate URI keys found`
)

var validHTTPMethods = []string{
	http.MethodOptions,
	http.MethodHead,
	http.MethodGet,
	http.MethodPost,
	http.MethodPut,
	http.MethodPatch,
	http.MethodDelete,
}

// customResponseWriter is a custom HTTP response writer
type customResponseWriter struct {
	http.ResponseWriter
	statusCode int
	written    bool
}

// WriteHeader is the interface implementation to get HTTP response code and add
// it to the custom response writer
func (crw *customResponseWriter) WriteHeader(code int) {
	if crw.written {
		warnLogger.Println(errMultiHeaderWrite)
		return
	}

	crw.statusCode = code
	crw.ResponseWriter.WriteHeader(code)
}

// Write is the interface implementation to respond to the HTTP request,
// but check if a response was already sent.
func (crw *customResponseWriter) Write(body []byte) (int, error) {
	if crw.written {
		warnLogger.Println(errMultiWrite)
		return 0, nil
	}

	crw.written = true
	return crw.ResponseWriter.Write(body)
}

// Route defines a route for each API
type Route struct {
	// Name is unique identifier for the route
	Name string
	// Method is the HTTP request method/type
	Method string
	// Pattern is the URI pattern to match
	Pattern string
	// TrailingSlash if set to true, the URI will be matched with or without
	// a trailing slash. Note: It does not *do* a redirect.
	TrailingSlash bool

	// FallThroughPostResponse if enabled will execute all the handlers even if a response was already sent to the client
	FallThroughPostResponse bool

	// Handlers is a slice of http.HandlerFunc which can be middlewares or anything else. Though only 1 of them will be allowed to respond to client.
	// subsequent writes from the following handlers will be ignored
	Handlers []http.HandlerFunc

	// uriKeys is the list of URI parameter variables available for this route
	uriKeys []string
	// uriPatternString is the pattern string which is compiled to regex object
	uriPatternString string
	// uriPattern is the compiled regex to match the URI pattern
	uriPattern *regexp.Regexp
}

// computePatternStr computes the pattern string required for generating the route's regex.
// It also adds the URI parameter key to the route's `keys` field
func (r *Route) computePatternStr(patternString string, hasWildcard bool, key string) string {
	regexPattern := ""
	patternKey := ""
	if hasWildcard {
		patternKey = fmt.Sprintf(":%s*", key)
		regexPattern = urlwildcard
	} else {
		patternKey = fmt.Sprintf(":%s", key)
		regexPattern = urlchars
	}

	patternString = strings.Replace(patternString, patternKey, regexPattern, 1)

	for idx, k := range r.uriKeys {
		if key == k {
			errLogger.Fatalln(errDuplicateKey, "\nURI: ", r.Pattern, "\nKey:", k, ", Position:", idx+1)
		}
	}

	r.uriKeys = append(r.uriKeys, key)
	return patternString
}

// init prepares the URIKeys, compile regex for the provided pattern
func (r *Route) init() error {
	patternString := r.Pattern

	if strings.Contains(r.Pattern, ":") {
		// uriValues is a map of URI Key and it's respective value,
		// this is calculated per request
		key := ""
		hasKey := false
		hasWildcard := false

		for i := 0; i < len(r.Pattern); i++ {
			char := string(r.Pattern[i])

			if char == ":" {
				hasKey = true
			} else if char == "*" {
				hasWildcard = true
			} else if hasKey && char != "/" {
				key += char
			} else if hasKey && len(key) > 0 {
				patternString = r.computePatternStr(patternString, hasWildcard, key)
				hasWildcard, hasKey = false, false
				key = ""
			}
		}

		if hasKey && len(key) > 0 {
			patternString = r.computePatternStr(patternString, hasWildcard, key)
		}

	}

	if r.TrailingSlash {
		patternString = fmt.Sprintf("^%s%s$", patternString, trailingSlash)
	} else {
		patternString = fmt.Sprintf("^%s$", patternString)
	}

	// compile the regex for the pattern string calculated
	reg, err := regexp.Compile(patternString)
	if err != nil {
		return err
	}

	r.uriPattern = reg
	r.uriPatternString = patternString
	return nil
}

// matchAndGet returns if the request URI matches the pattern defined in a Route as well as
// all the URI parameters configured for the route.
func (r *Route) matchAndGet(requestURI string) (bool, map[string]string) {
	if r.Pattern == requestURI {
		return true, nil
	}

	if !r.uriPattern.Match([]byte(requestURI)) {
		return false, nil
	}

	// Getting URI parameters
	values := r.uriPattern.FindStringSubmatch(requestURI)
	if len(values) == 0 {
		return true, nil
	}

	uriValues := make(map[string]string, len(values)-1)
	for i := 1; i < len(values); i++ {
		uriValues[r.uriKeys[i-1]] = values[i]
	}
	return true, uriValues

}

// Router is the HTTP router
type Router struct {
	optHandlers    []*Route
	headHandlers   []*Route
	getHandlers    []*Route
	postHandlers   []*Route
	putHandlers    []*Route
	patchHandlers  []*Route
	deleteHandlers []*Route

	// NotFound is the generic handler for 404 resource not found response
	NotFound http.HandlerFunc
	// AppContext holds all the app specific context which is to be injected into all HTTP
	// request context
	AppContext map[string]interface{}

	// config has all the app config
	config       *Config
	serveHandler http.HandlerFunc
	// httpServer is the server handler for the active HTTP server
	httpServer *http.Server
	// httpsServer is the server handler for the active HTTPS server
	httpsServer *http.Server
}

func (rtr *Router) serve(rw http.ResponseWriter, req *http.Request) {
	var rr []*Route

	switch req.Method {
	case http.MethodOptions:
		rr = rtr.optHandlers
	case http.MethodHead:
		rr = rtr.headHandlers
	case http.MethodGet:
		rr = rtr.getHandlers
	case http.MethodPost:
		rr = rtr.postHandlers
	case http.MethodPut:
		rr = rtr.putHandlers
	case http.MethodPatch:
		rr = rtr.patchHandlers
	case http.MethodDelete:
		rr = rtr.deleteHandlers
	}

	var route *Route
	ok := false
	params := make(map[string]string, 0)
	path := req.URL.EscapedPath()
	for _, r := range rr {
		if ok, params = r.matchAndGet(path); ok {
			route = r
			break
		}
	}

	if !ok {
		// serve 404 when there are no matching routes
		rtr.NotFound(rw, req)
		return
	}

	crw := &customResponseWriter{
		ResponseWriter: rw,
	}
	// webgo context object created and is injected to the request context
	reqwc := req.WithContext(
		context.WithValue(
			req.Context(),
			wgoCtxKey,
			&WC{
				Params:     params,
				Route:      route,
				AppContext: rtr.AppContext,
			},
		),
	)

	for _, handler := range route.Handlers {
		if crw.written == false {
			// If there has been no write to response writer yet
			handler(crw, reqwc)
		} else if route.FallThroughPostResponse {
			// run a handler post response write, only if fall through is enabled
			handler(crw, reqwc)
		} else {
			// Do not run any more handlers if already responded and no fall through enabled
			break
		}
	}
}

// ServeHTTP is the required `ServeHTTP` implementation to listen to HTTP requests
func (rtr *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	rtr.serveHandler(rw, req)
}

// Use adds a middleware layer
func (rtr *Router) Use(f func(http.ResponseWriter, *http.Request, http.HandlerFunc)) {
	srv := rtr.serveHandler
	rtr.serveHandler = func(rw http.ResponseWriter, req *http.Request) {
		f(rw, req, srv)
	}
}

// NewRouter initializes returns a new router instance with all the configurations and routes set
func NewRouter(cfg *Config, routes []*Route) *Router {
	handlers := make(map[string][]*Route, len(validHTTPMethods))

	for _, validMethod := range validHTTPMethods {
		handlers[validMethod] = []*Route{}
	}

	for idx, route := range routes {
		found := false
		for _, validMethod := range validHTTPMethods {
			if route.Method == validMethod {
				found = true
			}
		}

		if !found {
			errLogger.Fatalln("Unsupported HTTP request method provided. Method:", route.Method)
		}

		if route.Handlers == nil || len(route.Handlers) == 0 {
			errLogger.Fatalln("No handlers provided for the route '", route.Pattern, "', method '", route.Method, "'")
		}

		err := route.init()
		if err != nil {
			errLogger.Fatalln("Unsupported URI pattern.", route.Pattern, err)
		}

		// checking if the URI pattern is duplicated
		for i := 0; i < idx; i++ {
			rt := routes[i]

			if rt.Name == route.Name {
				warnLogger.Println("Duplicate route name(\"" + rt.Name + "\") detected. Route name should be unique.")
			}

			if rt.Method == route.Method {
				// regex pattern match
				if ok, _ := rt.matchAndGet(route.Pattern); ok {
					warnLogger.Println("Duplicate URI pattern detected.\nPattern: '" + rt.Pattern + "'\nDuplicate pattern: '" + route.Pattern + "'")
					infoLogger.Println("Only the first route to match the URI pattern would handle the request")
				}
			}
		}

		handlers[route.Method] = append(handlers[route.Method], route)
	}

	r := &Router{
		optHandlers:    handlers[http.MethodOptions],
		headHandlers:   handlers[http.MethodHead],
		getHandlers:    handlers[http.MethodGet],
		postHandlers:   handlers[http.MethodPost],
		putHandlers:    handlers[http.MethodPut],
		patchHandlers:  handlers[http.MethodPatch],
		deleteHandlers: handlers[http.MethodDelete],

		NotFound:   http.NotFound,
		AppContext: make(map[string]interface{}, 0),
		config:     cfg,
	}
	// setting the default serve handler
	r.serveHandler = r.serve

	return r
}
<|endoftext|>


<commit_before>package air

import (
	"strings"
	"unsafe"
)

// router is a registry of all registered routes.
type router struct {
	tree      *node
	maxParams int
	routes    map[string]struct{}
}

// theRouter is the singleton of the `router`.
var theRouter = &router{
	tree: &node{
		handlers: map[string]Handler{},
	},
	routes: map[string]struct{}{},
}

// register registers a new route for the method and the path with the matching
// h in the r with the optional route-level gases..
func (r *router) register(method, path string, h Handler, gases ...Gas) {
	if path != "/" && hasLastSlash(path) {
		path = path[:len(path)-1]
	}

	msg := ""
	if path == "" {
		msg = "air: route path cannot be empty"
	} else if path[0] != '/' {
		msg = "air: route path must start with /"
	} else if strings.Contains(path, "//") {
		msg = "air: route path cannot have //"
	} else if strings.Count(path, ":") > 1 {
		ps := strings.Split(path, "/")
		for _, p := range ps {
			if strings.Count(p, ":") > 1 {
				msg = "air: adjacent params in route path " +
					"must be separated by /"
				break
			}
		}
	} else if strings.Contains(path, "*") {
		if strings.Count(path, "*") > 1 {
			msg = "air: only one * is allowed in route path"
		} else if path[len(path)-1] != '*' {
			msg = "air: * can only appear at end of route path"
		} else if strings.Contains(
			path[strings.LastIndex(path, "/"):],
			":",
		) {
			msg = "air: adjacent param and * in route path must " +
				"be separated by /"
		}
	} else if _, ok := r.routes[method+pathWithoutParamNames(path)]; ok {
		msg = "air: route already exists"
	} else {
		r.routes[method+pathWithoutParamNames(path)] = struct{}{}
	}

	if msg != "" {
		FATAL(msg, map[string]interface{}{
			"route": map[string]interface{}{
				"method": method,
				"path":   path,
			},
		})
	}

	nh := func(req *Request, res *Response) error {
		h := h
		for i := len(gases) - 1; i >= 0; i-- {
			h = gases[i](h)
		}

		return h(req, res)
	}

	paramNames := []string{}

	for i, l := 0, len(path); i < l; i++ {
		if path[i] == ':' {
			j := i + 1

			r.insert(method, path[:i], nil, static, nil)

			for ; i < l && path[i] != '/'; i++ {
			}

			paramName := path[j:i]

			for _, pn := range paramNames {
				if pn == paramName {
					e := map[string]interface{}{
						"route": map[string]interface{}{
							"method": method,
							"path":   path,
						},
					}
					FATAL(
						"air: route path cannot have "+
							"duplicate param names",
						e,
					)
				}
			}

			paramNames = append(paramNames, paramName)
			path = path[:j] + path[i:]

			if i, l = j, len(path); i == l {
				r.insert(method, path, nh, param, paramNames)
				return
			}

			r.insert(method, path[:i], nil, param, paramNames)
		} else if path[i] == '*' {
			r.insert(method, path[:i], nil, static, nil)
			paramNames = append(paramNames, "*")
			r.insert(method, path[:i+1], nh, any, paramNames)
			return
		}
	}

	r.insert(method, path, nh, static, paramNames)
}

// insert inserts a new route into the `tree` of the r.
func (r *router) insert(
	method,
	path string,
	h Handler,
	nk nodeKind,
	paramNames []string,
) {
	if l := len(paramNames); l > r.maxParams {
		r.maxParams = l
	}

	cn := r.tree // Current node as the root of the `tree` of the r

	var (
		s   = path // Search
		nn  *node  // Next node
		sl  int    // Search length
		pl  int    // Prefix length
		ll  int    // LCP length
		max int    // Max number of sl and pl
	)

	for {
		sl = len(s)
		pl = len(cn.prefix)
		ll = 0

		max = pl
		if sl < max {
			max = sl
		}

		for ; ll < max && s[ll] == cn.prefix[ll]; ll++ {
		}

		if ll == 0 {
			// At root node
			cn.label = s[0]
			cn.prefix = s
			if h != nil {
				cn.kind = nk
				cn.handlers[method] = h
				cn.paramNames = paramNames
			}
		} else if ll < pl {
			// Split node
			nn = &node{
				kind:       cn.kind,
				label:      cn.prefix[ll],
				prefix:     cn.prefix[ll:],
				handlers:   cn.handlers,
				parent:     cn,
				children:   cn.children,
				paramNames: cn.paramNames,
			}

			// Reset parent node
			cn.kind = static
			cn.label = cn.prefix[0]
			cn.prefix = cn.prefix[:ll]
			cn.children = nil
			cn.handlers = map[string]Handler{}
			cn.paramNames = nil
			cn.children = append(cn.children, nn)

			if ll == sl {
				// At parent node
				cn.kind = nk
				cn.handlers[method] = h
				cn.paramNames = paramNames
			} else {
				// Create child node
				nn = &node{
					kind:       nk,
					label:      s[ll],
					prefix:     s[ll:],
					handlers:   map[string]Handler{},
					parent:     cn,
					paramNames: paramNames,
				}
				nn.handlers[method] = h
				cn.children = append(cn.children, nn)
			}
		} else if ll < sl {
			s = s[ll:]

			if nn = cn.childByLabel(s[0]); nn != nil {
				// Go deeper
				cn = nn
				continue
			}

			// Create child node
			nn = &node{
				kind:       nk,
				label:      s[0],
				prefix:     s,
				handlers:   map[string]Handler{},
				parent:     cn,
				paramNames: paramNames,
			}
			nn.handlers[method] = h
			cn.children = append(cn.children, nn)
		} else if h != nil {
			// Node already exists
			cn.handlers[method] = h
			cn.paramNames = paramNames
		}

		return
	}
}

// route returns a handler registered for the req.
func (r *router) route(req *Request) Handler {
	cn := r.tree // Current node as root of the `tree` of the r

	var (
		s   = pathClean(req.URL.Path)        // Search
		nn  *node                            // Next node
		nk  nodeKind                         // Next kind
		sn  *node                            // Saved node
		ss  string                           // Saved search
		sl  int                              // Search length
		pl  int                              // Prefix length
		ll  int                              // LCP length
		max int                              // Max number of sl and pl
		si  int                              // Start index
		pvs = make([]string, 0, r.maxParams) // Param values
	)

	// Search order: static > param > any
	for {
		if s == "" {
			break
		}

		pl = 0
		ll = 0

		if cn.label != ':' {
			sl = len(s)
			pl = len(cn.prefix)

			max = pl
			if sl < max {
				max = sl
			}

			for ; ll < max && s[ll] == cn.prefix[ll]; ll++ {
			}
		}

		if ll != pl {
			goto Struggle
		}

		if s = s[ll:]; s == "" {
			break
		}

		// Static node
		if nn = cn.child(s[0], static); nn != nil {
			// Save next
			if hasLastSlash(cn.prefix) {
				nk = param
				sn = cn
				ss = s
			}

			cn = nn

			continue
		}

		// Param node
	Param:
		if nn = cn.childByKind(param); nn != nil {
			// Save next
			if hasLastSlash(cn.prefix) {
				nk = any
				sn = cn
				ss = s
			}

			cn = nn

			for si = 0; si < len(s) && s[si] != '/'; si++ {
			}

			pvs = append(pvs, unescape(s[:si]))
			s = s[si:]

			continue
		}

		// Any node
	Any:
		if cn = cn.childByKind(any); cn != nil {
			if hasLastSlash(req.URL.Path) {
				si = len(req.URL.Path) - 1
				for ; si > 0 && req.URL.Path[si] == '/'; si-- {
				}

				s += req.URL.Path[si+1:]
			}

			if len(pvs) < len(cn.paramNames) {
				pvs = append(pvs, unescape(s))
			} else {
				pvs[len(cn.paramNames)-1] = unescape(s)
			}

			break
		}

		// Struggle for the former node
	Struggle:
		if sn != nil {
			cn = sn
			sn = nil
			s = ss

			switch nk {
			case param:
				goto Param
			case any:
				goto Any
			}
		}

		return NotFoundHandler
	}

	if handler := cn.handlers[req.Method]; handler != nil {
		for i := range pvs {
			req.Params[cn.paramNames[i]] = pvs[i]
		}

		return handler
	} else if len(cn.handlers) != 0 {
		return MethodNotAllowedHandler
	}

	return NotFoundHandler
}

// hasLastSlash reports whether the s has the last '/'.
func hasLastSlash(s string) bool {
	return len(s) > 0 && s[len(s)-1] == '/'
}

// pathWithoutParamNames returns a path from the p without the param names.
func pathWithoutParamNames(p string) string {
	for i, l := 0, len(p); i < l; i++ {
		if p[i] == ':' {
			j := i + 1

			for ; i < l && p[i] != '/'; i++ {
			}

			p = p[:j] + p[i:]
			i, l = j, len(p)

			if i == l {
				break
			}
		}
	}

	return p
}

// pathClean returns a clean path from the p.
func pathClean(p string) string {
	b := make([]byte, 0, len(p))
	for i, l := 0, len(p); i < l; {
		if p[i] == '/' {
			i++
		} else {
			b = append(b, '/')
			for ; i < l && p[i] != '/'; i++ {
				b = append(b, p[i])
			}
		}
	}

	if len(b) == 0 {
		return "/"
	}

	return *(*string)(unsafe.Pointer(&b))
}

// unescape return a normal string unescaped from the s.
func unescape(s string) string {
	// Count the %, check that they are well-formed.
	n := 0
	for i := 0; i < len(s); i++ {
		if s[i] == '%' {
			n++
			if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
				s = s[i:]

				if len(s) > 3 {
					s = s[:3]
				}

				return ""
			}
			i += 2
		}
	}

	if n == 0 {
		return s
	}

	t := make([]byte, len(s)-2*n)
	for i, j := 0, 0; i < len(s); i++ {
		switch s[i] {
		case '%':
			t[j] = unhex(s[i+1])<<4 | unhex(s[i+2])
			j++
			i += 2
		case '+':
			t[j] = ' '
			j++
		default:
			t[j] = s[i]
			j++
		}
	}

	return string(t)
}

// ishex reports whether the c is hex.
func ishex(c byte) bool {
	switch {
	case '0' <= c && c <= '9':
		return true
	case 'a' <= c && c <= 'f':
		return true
	case 'A' <= c && c <= 'F':
		return true
	}

	return false
}

// unhex returns the normal byte from the hex char c.
func unhex(c byte) byte {
	switch {
	case '0' <= c && c <= '9':
		return c - '0'
	case 'a' <= c && c <= 'f':
		return c - 'a' + 10
	case 'A' <= c && c <= 'F':
		return c - 'A' + 10
	}

	return 0
}

// node is the node of the radix tree.
type node struct {
	kind       nodeKind
	label      byte
	prefix     string
	handlers   map[string]Handler
	parent     *node
	children   []*node
	paramNames []string
}

// nodeKind is a kind of the `node`.
type nodeKind uint8

// the node kinds.
const (
	static nodeKind = iota
	param
	any
)

// child returns a child `node` of the n by the label and the kind.
func (n *node) child(label byte, kind nodeKind) *node {
	for _, c := range n.children {
		if c.label == label && c.kind == kind {
			return c
		}
	}

	return nil
}

// childByLabel returns a child `node` of the n by the l.
func (n *node) childByLabel(l byte) *node {
	for _, c := range n.children {
		if c.label == l {
			return c
		}
	}

	return nil
}

// childByKind returns a child `node` of the n by the k.
func (n *node) childByKind(k nodeKind) *node {
	for _, c := range n.children {
		if c.kind == k {
			return c
		}
	}

	return nil
}
<commit_msg>refactor: panic if registering an invalid route<commit_after>package air

import (
	"strings"
	"unsafe"
)

// router is a registry of all registered routes.
type router struct {
	tree      *node
	maxParams int
	routes    map[string]struct{}
}

// theRouter is the singleton of the `router`.
var theRouter = &router{
	tree: &node{
		handlers: map[string]Handler{},
	},
	routes: map[string]struct{}{},
}

// register registers a new route for the method and the path with the matching
// h in the r with the optional route-level gases..
func (r *router) register(method, path string, h Handler, gases ...Gas) {
	if path != "/" && hasLastSlash(path) {
		path = path[:len(path)-1]
	}

	msg := ""
	if path == "" {
		msg = "air: route path cannot be empty"
	} else if path[0] != '/' {
		msg = "air: route path must start with /"
	} else if strings.Contains(path, "//") {
		msg = "air: route path cannot have //"
	} else if strings.Count(path, ":") > 1 {
		ps := strings.Split(path, "/")
		for _, p := range ps {
			if strings.Count(p, ":") > 1 {
				msg = "air: adjacent params in route path " +
					"must be separated by /"
				break
			}
		}
	} else if strings.Contains(path, "*") {
		if strings.Count(path, "*") > 1 {
			msg = "air: only one * is allowed in route path"
		} else if path[len(path)-1] != '*' {
			msg = "air: * can only appear at end of route path"
		} else if strings.Contains(
			path[strings.LastIndex(path, "/"):],
			":",
		) {
			msg = "air: adjacent param and * in route path must " +
				"be separated by /"
		}
	} else if _, ok := r.routes[method+pathWithoutParamNames(path)]; ok {
		msg = "air: route already exists"
	} else {
		r.routes[method+pathWithoutParamNames(path)] = struct{}{}
	}

	if msg != "" {
		panic(msg)
	}

	paramNames := []string{}
	nh := func(req *Request, res *Response) error {
		h := h
		for i := len(gases) - 1; i >= 0; i-- {
			h = gases[i](h)
		}

		return h(req, res)
	}

	for i, l := 0, len(path); i < l; i++ {
		if path[i] == ':' {
			j := i + 1

			r.insert(method, path[:i], nil, static, nil)

			for ; i < l && path[i] != '/'; i++ {
			}

			paramName := path[j:i]

			for _, pn := range paramNames {
				if pn == paramName {
					panic("air: route path cannot have " +
						"duplicate param names")
				}
			}

			paramNames = append(paramNames, paramName)
			path = path[:j] + path[i:]

			if i, l = j, len(path); i == l {
				r.insert(method, path, nh, param, paramNames)
				return
			}

			r.insert(method, path[:i], nil, param, paramNames)
		} else if path[i] == '*' {
			r.insert(method, path[:i], nil, static, nil)
			paramNames = append(paramNames, "*")
			r.insert(method, path[:i+1], nh, any, paramNames)
			return
		}
	}

	r.insert(method, path, nh, static, paramNames)
}

// insert inserts a new route into the `tree` of the r.
func (r *router) insert(
	method,
	path string,
	h Handler,
	nk nodeKind,
	paramNames []string,
) {
	if l := len(paramNames); l > r.maxParams {
		r.maxParams = l
	}

	cn := r.tree // Current node as the root of the `tree` of the r

	var (
		s   = path // Search
		nn  *node  // Next node
		sl  int    // Search length
		pl  int    // Prefix length
		ll  int    // LCP length
		max int    // Max number of sl and pl
	)

	for {
		sl = len(s)
		pl = len(cn.prefix)
		ll = 0

		max = pl
		if sl < max {
			max = sl
		}

		for ; ll < max && s[ll] == cn.prefix[ll]; ll++ {
		}

		if ll == 0 {
			// At root node
			cn.label = s[0]
			cn.prefix = s
			if h != nil {
				cn.kind = nk
				cn.handlers[method] = h
				cn.paramNames = paramNames
			}
		} else if ll < pl {
			// Split node
			nn = &node{
				kind:       cn.kind,
				label:      cn.prefix[ll],
				prefix:     cn.prefix[ll:],
				handlers:   cn.handlers,
				parent:     cn,
				children:   cn.children,
				paramNames: cn.paramNames,
			}

			// Reset parent node
			cn.kind = static
			cn.label = cn.prefix[0]
			cn.prefix = cn.prefix[:ll]
			cn.children = nil
			cn.handlers = map[string]Handler{}
			cn.paramNames = nil
			cn.children = append(cn.children, nn)

			if ll == sl {
				// At parent node
				cn.kind = nk
				cn.handlers[method] = h
				cn.paramNames = paramNames
			} else {
				// Create child node
				nn = &node{
					kind:       nk,
					label:      s[ll],
					prefix:     s[ll:],
					handlers:   map[string]Handler{},
					parent:     cn,
					paramNames: paramNames,
				}
				nn.handlers[method] = h
				cn.children = append(cn.children, nn)
			}
		} else if ll < sl {
			s = s[ll:]

			if nn = cn.childByLabel(s[0]); nn != nil {
				// Go deeper
				cn = nn
				continue
			}

			// Create child node
			nn = &node{
				kind:       nk,
				label:      s[0],
				prefix:     s,
				handlers:   map[string]Handler{},
				parent:     cn,
				paramNames: paramNames,
			}
			nn.handlers[method] = h
			cn.children = append(cn.children, nn)
		} else if h != nil {
			// Node already exists
			cn.handlers[method] = h
			cn.paramNames = paramNames
		}

		return
	}
}

// route returns a handler registered for the req.
func (r *router) route(req *Request) Handler {
	cn := r.tree // Current node as root of the `tree` of the r

	var (
		s   = pathClean(req.URL.Path)        // Search
		nn  *node                            // Next node
		nk  nodeKind                         // Next kind
		sn  *node                            // Saved node
		ss  string                           // Saved search
		sl  int                              // Search length
		pl  int                              // Prefix length
		ll  int                              // LCP length
		max int                              // Max number of sl and pl
		si  int                              // Start index
		pvs = make([]string, 0, r.maxParams) // Param values
	)

	// Search order: static > param > any
	for {
		if s == "" {
			break
		}

		pl = 0
		ll = 0

		if cn.label != ':' {
			sl = len(s)
			pl = len(cn.prefix)

			max = pl
			if sl < max {
				max = sl
			}

			for ; ll < max && s[ll] == cn.prefix[ll]; ll++ {
			}
		}

		if ll != pl {
			goto Struggle
		}

		if s = s[ll:]; s == "" {
			break
		}

		// Static node
		if nn = cn.child(s[0], static); nn != nil {
			// Save next
			if hasLastSlash(cn.prefix) {
				nk = param
				sn = cn
				ss = s
			}

			cn = nn

			continue
		}

		// Param node
	Param:
		if nn = cn.childByKind(param); nn != nil {
			// Save next
			if hasLastSlash(cn.prefix) {
				nk = any
				sn = cn
				ss = s
			}

			cn = nn

			for si = 0; si < len(s) && s[si] != '/'; si++ {
			}

			pvs = append(pvs, unescape(s[:si]))
			s = s[si:]

			continue
		}

		// Any node
	Any:
		if cn = cn.childByKind(any); cn != nil {
			if hasLastSlash(req.URL.Path) {
				si = len(req.URL.Path) - 1
				for ; si > 0 && req.URL.Path[si] == '/'; si-- {
				}

				s += req.URL.Path[si+1:]
			}

			if len(pvs) < len(cn.paramNames) {
				pvs = append(pvs, unescape(s))
			} else {
				pvs[len(cn.paramNames)-1] = unescape(s)
			}

			break
		}

		// Struggle for the former node
	Struggle:
		if sn != nil {
			cn = sn
			sn = nil
			s = ss

			switch nk {
			case param:
				goto Param
			case any:
				goto Any
			}
		}

		return NotFoundHandler
	}

	if handler := cn.handlers[req.Method]; handler != nil {
		for i := range pvs {
			req.Params[cn.paramNames[i]] = pvs[i]
		}

		return handler
	} else if len(cn.handlers) != 0 {
		return MethodNotAllowedHandler
	}

	return NotFoundHandler
}

// hasLastSlash reports whether the s has the last '/'.
func hasLastSlash(s string) bool {
	return len(s) > 0 && s[len(s)-1] == '/'
}

// pathWithoutParamNames returns a path from the p without the param names.
func pathWithoutParamNames(p string) string {
	for i, l := 0, len(p); i < l; i++ {
		if p[i] == ':' {
			j := i + 1

			for ; i < l && p[i] != '/'; i++ {
			}

			p = p[:j] + p[i:]
			i, l = j, len(p)

			if i == l {
				break
			}
		}
	}

	return p
}

// pathClean returns a clean path from the p.
func pathClean(p string) string {
	b := make([]byte, 0, len(p))
	for i, l := 0, len(p); i < l; {
		if p[i] == '/' {
			i++
		} else {
			b = append(b, '/')
			for ; i < l && p[i] != '/'; i++ {
				b = append(b, p[i])
			}
		}
	}

	if len(b) == 0 {
		return "/"
	}

	return *(*string)(unsafe.Pointer(&b))
}

// unescape return a normal string unescaped from the s.
func unescape(s string) string {
	// Count the %, check that they are well-formed.
	n := 0
	for i := 0; i < len(s); i++ {
		if s[i] == '%' {
			n++
			if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
				s = s[i:]

				if len(s) > 3 {
					s = s[:3]
				}

				return ""
			}
			i += 2
		}
	}

	if n == 0 {
		return s
	}

	t := make([]byte, len(s)-2*n)
	for i, j := 0, 0; i < len(s); i++ {
		switch s[i] {
		case '%':
			t[j] = unhex(s[i+1])<<4 | unhex(s[i+2])
			j++
			i += 2
		case '+':
			t[j] = ' '
			j++
		default:
			t[j] = s[i]
			j++
		}
	}

	return string(t)
}

// ishex reports whether the c is hex.
func ishex(c byte) bool {
	switch {
	case '0' <= c && c <= '9':
		return true
	case 'a' <= c && c <= 'f':
		return true
	case 'A' <= c && c <= 'F':
		return true
	}

	return false
}

// unhex returns the normal byte from the hex char c.
func unhex(c byte) byte {
	switch {
	case '0' <= c && c <= '9':
		return c - '0'
	case 'a' <= c && c <= 'f':
		return c - 'a' + 10
	case 'A' <= c && c <= 'F':
		return c - 'A' + 10
	}

	return 0
}

// node is the node of the radix tree.
type node struct {
	kind       nodeKind
	label      byte
	prefix     string
	handlers   map[string]Handler
	parent     *node
	children   []*node
	paramNames []string
}

// nodeKind is a kind of the `node`.
type nodeKind uint8

// the node kinds.
const (
	static nodeKind = iota
	param
	any
)

// child returns a child `node` of the n by the label and the kind.
func (n *node) child(label byte, kind nodeKind) *node {
	for _, c := range n.children {
		if c.label == label && c.kind == kind {
			return c
		}
	}

	return nil
}

// childByLabel returns a child `node` of the n by the l.
func (n *node) childByLabel(l byte) *node {
	for _, c := range n.children {
		if c.label == l {
			return c
		}
	}

	return nil
}

// childByKind returns a child `node` of the n by the k.
func (n *node) childByKind(k nodeKind) *node {
	for _, c := range n.children {
		if c.kind == k {
			return c
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package flash2

import (
	"io"
	"log"
	"net/http"
	"os"

	"github.com/gorilla/handlers"
)

type handFunc func(map[string]string) http.Handler

// NewRouter creates new Router
func NewRouter() *Router {
	return &Router{routes: make(routes), LogWriter: os.Stdout}
}

// Router stroring app routes structure
type Router struct {
	routes routes

	// SSL defines server type (default none SSL)
	SSL bool
	// PublicKey for SSL processing
	PublicKey string
	// PrivateKey for SSL processing
	PrivateKey string
	// LogWriter log writer interface
	LogWriter io.Writer
}

// NewRoute registers an empty route.
func (r *Router) NewRoute(prefix string) *Route {
	return &Route{router: r, prefix: prefix}
}

// HandleFunc registers a new route with a matcher for the URL path.
// See Route.HandlerFunc().
func (r *Router) HandleFunc(path string, f func(http.ResponseWriter, *http.Request)) {
	r.NewRoute("").HandleFunc(path, f)
}

// Route registers a new route with a matcher for URL path
// See Route.Route().
func (r *Router) Route(method, path string, f handlerFunc, funcs ...MWFunc) {
	r.NewRoute("").Route(method, path, f, funcs...)
}

// Get shorthand for Route("GET", ...)
func (r *Router) Get(path string, f handlerFunc, funcs ...MWFunc) {
	r.NewRoute("").Get(path, f, funcs...)
}

// Post shorthand for Route("POST", ...)
func (r *Router) Post(path string, f handlerFunc, funcs ...MWFunc) {
	r.NewRoute("").Post(path, f, funcs...)
}

// Put shorthand for Route("PUT", ...)
func (r *Router) Put(path string, f handlerFunc, funcs ...MWFunc) {
	r.NewRoute("").Put(path, f, funcs...)
}

// Delete shorthand for Route("DELETE", ...)
func (r *Router) Delete(path string, f handlerFunc, funcs ...MWFunc) {
	r.NewRoute("").Delete(path, f, funcs...)
}

// Controller registers a new Controller with a matcher for URL path
// See Route.Controller()
func (r *Router) Controller(path string, i interface{}, funcs ...MWFunc) {
	r.NewRoute("").Controller(path, i, funcs...)
}

// Handle registers a new handler to serve path
func (r *Router) Handle(path string, handler http.Handler) {
	r.NewRoute("").Handle(path, handler)
}

// PathPrefix create new prefixed group for routes
func (r *Router) PathPrefix(s string) *Route {
	return r.NewRoute(s)
}

// ServeHTTP dispatches the handler registered in the matched route.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	defer req.Body.Close()

	h := r.routes.match(req.Method, req.URL.Path)
	if h != nil {
		h.ServeHTTP(w, req)
	} else {
		http.NotFoundHandler().ServeHTTP(w, req)
	}
}

// Serve starting http server
func (r *Router) Serve(bind string) {
	var err error
	if r.SSL {
		log.Printf("Starting secure SSL Server on %s", bind)
		err = http.ListenAndServeTLS(bind, r.PublicKey, r.PrivateKey, r.logHandler())
	} else {
		log.Printf("Starting Server on %s", bind)
		err = http.ListenAndServe(bind, r.logHandler())
	}
	if err != nil {
		log.Fatalf("Server start error: ", err)
	}
}

func (r *Router) logHandler() http.Handler {
	if r.LogHTTP {
		return handlers.CombinedLoggingHandler(r.LogWriter, r)
	}
	return r
}
<commit_msg>ability to disable http logging<commit_after>package flash2

import (
	"io"
	"log"
	"net/http"
	"os"

	"github.com/gorilla/handlers"
)

type handFunc func(map[string]string) http.Handler

// NewRouter creates new Router
func NewRouter() *Router {
	return &Router{routes: make(routes), LogWriter: os.Stdout, LogHTTP: true}
}

// Router stroring app routes structure
type Router struct {
	routes routes

	// SSL defines server type (default none SSL)
	SSL bool
	// PublicKey for SSL processing
	PublicKey string
	// PrivateKey for SSL processing
	PrivateKey string
	// LogWriter log writer interface
	LogWriter io.Writer
	LogHTTP   bool
}

// NewRoute registers an empty route.
func (r *Router) NewRoute(prefix string) *Route {
	return &Route{router: r, prefix: prefix}
}

// HandleFunc registers a new route with a matcher for the URL path.
// See Route.HandlerFunc().
func (r *Router) HandleFunc(path string, f func(http.ResponseWriter, *http.Request)) {
	r.NewRoute("").HandleFunc(path, f)
}

// Route registers a new route with a matcher for URL path
// See Route.Route().
func (r *Router) Route(method, path string, f handlerFunc, funcs ...MWFunc) {
	r.NewRoute("").Route(method, path, f, funcs...)
}

// Get shorthand for Route("GET", ...)
func (r *Router) Get(path string, f handlerFunc, funcs ...MWFunc) {
	r.NewRoute("").Get(path, f, funcs...)
}

// Post shorthand for Route("POST", ...)
func (r *Router) Post(path string, f handlerFunc, funcs ...MWFunc) {
	r.NewRoute("").Post(path, f, funcs...)
}

// Put shorthand for Route("PUT", ...)
func (r *Router) Put(path string, f handlerFunc, funcs ...MWFunc) {
	r.NewRoute("").Put(path, f, funcs...)
}

// Delete shorthand for Route("DELETE", ...)
func (r *Router) Delete(path string, f handlerFunc, funcs ...MWFunc) {
	r.NewRoute("").Delete(path, f, funcs...)
}

// Controller registers a new Controller with a matcher for URL path
// See Route.Controller()
func (r *Router) Controller(path string, i interface{}, funcs ...MWFunc) {
	r.NewRoute("").Controller(path, i, funcs...)
}

// Handle registers a new handler to serve path
func (r *Router) Handle(path string, handler http.Handler) {
	r.NewRoute("").Handle(path, handler)
}

// PathPrefix create new prefixed group for routes
func (r *Router) PathPrefix(s string) *Route {
	return r.NewRoute(s)
}

// ServeHTTP dispatches the handler registered in the matched route.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	defer req.Body.Close()

	h := r.routes.match(req.Method, req.URL.Path)
	if h != nil {
		h.ServeHTTP(w, req)
	} else {
		http.NotFoundHandler().ServeHTTP(w, req)
	}
}

// Serve starting http server
func (r *Router) Serve(bind string) {
	var err error
	if r.SSL {
		log.Printf("Starting secure SSL Server on %s", bind)
		err = http.ListenAndServeTLS(bind, r.PublicKey, r.PrivateKey, r.logHandler())
	} else {
		log.Printf("Starting Server on %s", bind)
		err = http.ListenAndServe(bind, r.logHandler())
	}
	if err != nil {
		log.Fatalf("Server start error: ", err)
	}
}

func (r *Router) logHandler() http.Handler {
	if r.LogHTTP {
		return handlers.CombinedLoggingHandler(r.LogWriter, r)
	}
	return r
}
<|endoftext|>


<commit_before>// Package denco provides fast URL router.
package denco

import (
	"fmt"
	"sort"
	"strings"
)

const (
	// A special character for path parameter.
	ParamCharacter = ':'

	// A special character for wildcard path parameter.
	WildcardCharacter = '*'

	// TerminalCharacter is a special character for end of path.
	TerminationCharacter = '#'

	// Block size of array of BASE/CHECK of Double-Array.
	blockSize = 256
)

// Router represents a URL router.
type Router struct {
	static map[string]interface{}
	param  *doubleArray
}

// New returns a new Router.
func New() *Router {
	return &Router{
		static: make(map[string]interface{}),
		param:  newDoubleArray(blockSize),
	}
}

// Lookup returns data and path parameters that associated with path.
// params is a slice of the Param that arranged in the order in which parameters appeared.
// e.g. when built routing path is "/path/:id/:name" and given path is "/path/to/1/alice". params order is [{"id": "1"}, {"name": "alice"}], not [{"name": "alice"}, {"id": "1"}].
func (rt *Router) Lookup(path string) (data interface{}, params []Param, found bool) {
	if data, found := rt.static[path]; found {
		return data, nil, true
	}
	idx, values, found := rt.param.lookup(path, nil, 1)
	if !found {
		return nil, nil, false
	}
	nd := rt.param.node[-rt.param.bc[idx].base]
	if nd == nil {
		return nil, nil, false
	}
	if len(values) > 0 {
		params = make([]Param, len(values))
		for i, v := range values {
			params[i] = Param{Name: nd.paramNames[i], Value: v}
		}
	}
	return nd.data, params, true
}

// Build builds URL router from records.
func (rt *Router) Build(records []Record) error {
	statics, params := makeRecords(records)
	for _, r := range statics {
		rt.static[r.Key] = r.Value
	}
	if err := rt.param.build(params, 1, 0); err != nil {
		return err
	}
	return nil
}

// Param represents name and value of path parameter.
type Param struct {
	Name  string
	Value string
}

type doubleArray struct {
	bc   []baseCheck
	node []*node
}

func newDoubleArray(size int) *doubleArray {
	return &doubleArray{
		bc:   newBaseCheckArray(size + 1),
		node: []*node{nil}, // A start index is adjusting to 1 because 0 will be used as a mark of non-existent node.
	}
}

// newBaseCheckArray returns a new slice of baseCheck with given size.
func newBaseCheckArray(size int) []baseCheck {
	return make([]baseCheck, size)
}

// baseCheck represents a BASE/CHECK node.
type baseCheck struct {
	base      int
	check     byte
	paramType paramType
}

type paramType uint8

func (p paramType) IsParam() bool {
	return p&paramTypeSingle == paramTypeSingle
}

func (p paramType) IsWildcard() bool {
	return p&paramTypeWildcard == paramTypeWildcard
}

func (p paramType) IsAny() bool {
	return p > 0
}

func (p *paramType) SetSingle() {
	*p |= paramTypeSingle
}

func (p *paramType) SetWildcard() {
	*p |= paramTypeWildcard
}

const (
	paramTypeSingle = 1 << iota
	paramTypeWildcard
)

func (da *doubleArray) lookup(path string, params []string, idx int) (int, []string, bool) {
	indices := make([]uint64, 0, 1)
	for i := 0; i < len(path); i++ {
		if da.bc[idx].paramType.IsAny() {
			indices = append(indices, (uint64(i&0xffffffff)<<32)|uint64(idx&0xffffffff))
		}
		c := path[i]
		next := nextIndex(da.bc[idx].base, c)
		if da.bc[next].check != c {
			goto BACKTRACKING
		}
		idx = next
	}
	if next := nextIndex(da.bc[idx].base, TerminationCharacter); da.bc[next].check == TerminationCharacter {
		return next, params, true
	}
	return -1, nil, false
BACKTRACKING:
	for j := len(indices) - 1; j >= 0; j-- {
		i, idx := int((indices[j]>>32)&0xffffffff), int(indices[j]&0xffffffff)
		if da.bc[idx].paramType.IsParam() {
			next := NextSeparator(path, i)
			idx := nextIndex(da.bc[idx].base, ParamCharacter)
			params := append(params, path[i:next])
			path := path[next:]
			if idx, params, found := da.lookup(path, params, idx); found {
				return idx, params, true
			}
		}
		if da.bc[idx].paramType.IsWildcard() {
			idx := nextIndex(da.bc[idx].base, WildcardCharacter)
			params := append(params, path[i:])
			return idx, params, true
		}
	}
	return -1, nil, false
}

// build builds double-array from records.
func (da *doubleArray) build(srcs []*record, idx, depth int) error {
	sort.Sort(recordSlice(srcs))
	base, siblings, leaf, err := da.arrange(srcs, idx, depth)
	if err != nil {
		return err
	}
	if leaf != nil {
		nd, err := makeNode(leaf)
		if err != nil {
			return err
		}
		da.bc[idx].base = -len(da.node)
		da.node = append(da.node, nd)
	}
	for _, sib := range siblings {
		da.setCheck(nextIndex(base, sib.c), sib.c)
	}
	for _, sib := range siblings {
		records := srcs[sib.start:sib.end]
		switch sib.c {
		case ParamCharacter:
			for _, r := range records {
				next := NextSeparator(r.Key, depth+1)
				name := r.Key[depth+1 : next]
				r.paramNames = append(r.paramNames, name)
				r.Key = r.Key[next:]
			}
			da.bc[idx].paramType.SetSingle()
			if err := da.build(records, nextIndex(base, sib.c), 0); err != nil {
				return err
			}
		case WildcardCharacter:
			r := records[0]
			name := r.Key[depth+1 : len(r.Key)-1]
			r.paramNames = append(r.paramNames, name)
			r.Key = ""
			da.bc[idx].paramType.SetWildcard()
			if err := da.build(records, nextIndex(base, sib.c), 0); err != nil {
				return err
			}
		default:
			if err := da.build(records, nextIndex(base, sib.c), depth+1); err != nil {
				return err
			}
		}
	}
	return nil
}

// setBase sets BASE.
func (da *doubleArray) setBase(i, base int) {
	da.bc[i].base = base
}

// setCheck sets CHECK.
func (da *doubleArray) setCheck(i int, check byte) {
	da.bc[i].check = check
}

// extendBaseCheckArray extends array of BASE/CHECK.
func (da *doubleArray) extendBaseCheckArray() {
	da.bc = append(da.bc, newBaseCheckArray(blockSize)...)
}

// findEmptyIndex returns an index of unused BASE/CHECK node.
func (da *doubleArray) findEmptyIndex(start int) int {
	i := start
	for ; i < len(da.bc); i++ {
		if da.bc[i].base == 0 && da.bc[i].check == 0 {
			break
		}
	}
	return i
}

// findBase returns good BASE.
func (da *doubleArray) findBase(siblings []sibling, start int) (base int) {
	idx := start + 1
	firstChar := siblings[0].c
	for ; idx < len(da.bc); idx = da.findEmptyIndex(idx + 1) {
		base = nextIndex(idx, firstChar)
		i := 0
		for ; i < len(siblings); i++ {
			if next := nextIndex(base, siblings[i].c); len(da.bc) <= next || da.bc[next].base != 0 || da.bc[next].check != 0 {
				break
			}
		}
		if i == len(siblings) {
			return base
		}
	}
	da.extendBaseCheckArray()
	return nextIndex(idx, firstChar)
}

func (da *doubleArray) arrange(records []*record, idx, depth int) (base int, siblings []sibling, leaf *record, err error) {
	siblings, leaf, err = makeSiblings(records, depth)
	if err != nil {
		return -1, nil, nil, err
	}
	if len(siblings) < 1 {
		return -1, nil, leaf, nil
	}
	base = da.findBase(siblings, idx)
	da.setBase(idx, base)
	return base, siblings, leaf, err
}

// node represents a node of Double-Array.
type node struct {
	data interface{}

	// Names of path parameters.
	paramNames []string
}

// makeNode returns a new node from record.
func makeNode(r *record) (*node, error) {
	dups := make(map[string]bool)
	for _, name := range r.paramNames {
		if dups[name] {
			return nil, fmt.Errorf("denco: path parameter `%v' is duplicated in the key `%v'", name, r.Key)
		}
		dups[name] = true
	}
	return &node{data: r.Value, paramNames: r.paramNames}, nil
}

// sibling represents an intermediate data of build for Double-Array.
type sibling struct {
	// An index of start of duplicated characters.
	start int

	// An index of end of duplicated characters.
	end int

	// A character of sibling.
	c byte
}

// nextIndex returns a next index of array of BASE/CHECK.
func nextIndex(base int, c byte) int {
	return base ^ int(c)
}

// makeSiblings returns slice of sibling.
func makeSiblings(records []*record, depth int) (sib []sibling, leaf *record, err error) {
	var (
		pc byte
		n  int
	)
	for i, r := range records {
		if len(r.Key) <= depth {
			leaf = r
			continue
		}
		c := r.Key[depth]
		switch {
		case pc < c:
			sib = append(sib, sibling{start: i, c: c})
		case pc == c:
			continue
		default:
			return nil, nil, fmt.Errorf("denco: BUG: routing table hasn't been sorted")
		}
		if n > 0 {
			sib[n-1].end = i
		}
		pc = c
		n++
	}
	if n == 0 {
		return nil, leaf, nil
	}
	sib[n-1].end = len(records)
	return sib, leaf, nil
}

// Record represents a record data for router construction.
type Record struct {
	// Key for router construction.
	Key string

	// Result value for Key.
	Value interface{}
}

// NewRecord returns a new Record.
func NewRecord(key string, value interface{}) Record {
	return Record{
		Key:   key,
		Value: value,
	}
}

// record represents a record that use to build the Double-Array.
type record struct {
	Record
	paramNames []string
}

// makeRecords returns the records that use to build Double-Arrays.
func makeRecords(srcs []Record) (statics, params []*record) {
	spChars := string([]byte{ParamCharacter, WildcardCharacter})
	termChar := string(TerminationCharacter)
	for _, r := range srcs {
		if strings.ContainsAny(r.Key, spChars) {
			r.Key += termChar
			params = append(params, &record{Record: r})
		} else {
			statics = append(statics, &record{Record: r})
		}
	}
	return statics, params
}

// recordSlice represents a slice of Record for sort and implements the sort.Interface.
type recordSlice []*record

// Len implements the sort.Interface.Len.
func (rs recordSlice) Len() int {
	return len(rs)
}

// Less implements the sort.Interface.Less.
func (rs recordSlice) Less(i, j int) bool {
	return rs[i].Key < rs[j].Key
}

// Swap implements the sort.Interface.Swap.
func (rs recordSlice) Swap(i, j int) {
	rs[i], rs[j] = rs[j], rs[i]
}
<commit_msg>Fix an issue that incorrect value is returned caused by collision of BASE<commit_after>// Package denco provides fast URL router.
package denco

import (
	"fmt"
	"sort"
	"strings"
)

const (
	// A special character for path parameter.
	ParamCharacter = ':'

	// A special character for wildcard path parameter.
	WildcardCharacter = '*'

	// TerminalCharacter is a special character for end of path.
	TerminationCharacter = '#'

	// Block size of array of BASE/CHECK of Double-Array.
	blockSize = 256
)

// Router represents a URL router.
type Router struct {
	static map[string]interface{}
	param  *doubleArray
}

// New returns a new Router.
func New() *Router {
	return &Router{
		static: make(map[string]interface{}),
		param:  newDoubleArray(blockSize),
	}
}

// Lookup returns data and path parameters that associated with path.
// params is a slice of the Param that arranged in the order in which parameters appeared.
// e.g. when built routing path is "/path/:id/:name" and given path is "/path/to/1/alice". params order is [{"id": "1"}, {"name": "alice"}], not [{"name": "alice"}, {"id": "1"}].
func (rt *Router) Lookup(path string) (data interface{}, params []Param, found bool) {
	if data, found := rt.static[path]; found {
		return data, nil, true
	}
	idx, values, found := rt.param.lookup(path, nil, 1)
	if !found {
		return nil, nil, false
	}
	nd := rt.param.node[-rt.param.bc[idx].base]
	if nd == nil {
		return nil, nil, false
	}
	if len(values) > 0 {
		params = make([]Param, len(values))
		for i, v := range values {
			params[i] = Param{Name: nd.paramNames[i], Value: v}
		}
	}
	return nd.data, params, true
}

// Build builds URL router from records.
func (rt *Router) Build(records []Record) error {
	statics, params := makeRecords(records)
	for _, r := range statics {
		rt.static[r.Key] = r.Value
	}
	if err := rt.param.build(params, 1, 0, make(map[int]struct{})); err != nil {
		return err
	}
	return nil
}

// Param represents name and value of path parameter.
type Param struct {
	Name  string
	Value string
}

type doubleArray struct {
	bc   []baseCheck
	node []*node
}

func newDoubleArray(size int) *doubleArray {
	return &doubleArray{
		bc:   newBaseCheckArray(size + 1),
		node: []*node{nil}, // A start index is adjusting to 1 because 0 will be used as a mark of non-existent node.
	}
}

// newBaseCheckArray returns a new slice of baseCheck with given size.
func newBaseCheckArray(size int) []baseCheck {
	return make([]baseCheck, size)
}

// baseCheck represents a BASE/CHECK node.
type baseCheck struct {
	base      int
	check     byte
	paramType paramType
}

type paramType uint8

func (p paramType) IsParam() bool {
	return p&paramTypeSingle == paramTypeSingle
}

func (p paramType) IsWildcard() bool {
	return p&paramTypeWildcard == paramTypeWildcard
}

func (p paramType) IsAny() bool {
	return p > 0
}

func (p *paramType) SetSingle() {
	*p |= paramTypeSingle
}

func (p *paramType) SetWildcard() {
	*p |= paramTypeWildcard
}

const (
	paramTypeSingle = 1 << iota
	paramTypeWildcard
)

func (da *doubleArray) lookup(path string, params []string, idx int) (int, []string, bool) {
	indices := make([]uint64, 0, 1)
	for i := 0; i < len(path); i++ {
		if da.bc[idx].paramType.IsAny() {
			indices = append(indices, (uint64(i&0xffffffff)<<32)|uint64(idx&0xffffffff))
		}
		c := path[i]
		next := nextIndex(da.bc[idx].base, c)
		if da.bc[next].check != c {
			goto BACKTRACKING
		}
		idx = next
	}
	if next := nextIndex(da.bc[idx].base, TerminationCharacter); da.bc[next].check == TerminationCharacter {
		return next, params, true
	}
	return -1, nil, false
BACKTRACKING:
	for j := len(indices) - 1; j >= 0; j-- {
		i, idx := int((indices[j]>>32)&0xffffffff), int(indices[j]&0xffffffff)
		if da.bc[idx].paramType.IsParam() {
			next := NextSeparator(path, i)
			idx := nextIndex(da.bc[idx].base, ParamCharacter)
			params := append(params, path[i:next])
			path := path[next:]
			if idx, params, found := da.lookup(path, params, idx); found {
				return idx, params, true
			}
		}
		if da.bc[idx].paramType.IsWildcard() {
			idx := nextIndex(da.bc[idx].base, WildcardCharacter)
			params := append(params, path[i:])
			return idx, params, true
		}
	}
	return -1, nil, false
}

// build builds double-array from records.
func (da *doubleArray) build(srcs []*record, idx, depth int, usedBase map[int]struct{}) error {
	sort.Sort(recordSlice(srcs))
	base, siblings, leaf, err := da.arrange(srcs, idx, depth, usedBase)
	if err != nil {
		return err
	}
	if leaf != nil {
		nd, err := makeNode(leaf)
		if err != nil {
			return err
		}
		da.bc[idx].base = -len(da.node)
		da.node = append(da.node, nd)
	}
	for _, sib := range siblings {
		da.setCheck(nextIndex(base, sib.c), sib.c)
	}
	for _, sib := range siblings {
		records := srcs[sib.start:sib.end]
		switch sib.c {
		case ParamCharacter:
			for _, r := range records {
				next := NextSeparator(r.Key, depth+1)
				name := r.Key[depth+1 : next]
				r.paramNames = append(r.paramNames, name)
				r.Key = r.Key[next:]
			}
			da.bc[idx].paramType.SetSingle()
			if err := da.build(records, nextIndex(base, sib.c), 0, usedBase); err != nil {
				return err
			}
		case WildcardCharacter:
			r := records[0]
			name := r.Key[depth+1 : len(r.Key)-1]
			r.paramNames = append(r.paramNames, name)
			r.Key = ""
			da.bc[idx].paramType.SetWildcard()
			if err := da.build(records, nextIndex(base, sib.c), 0, usedBase); err != nil {
				return err
			}
		default:
			if err := da.build(records, nextIndex(base, sib.c), depth+1, usedBase); err != nil {
				return err
			}
		}
	}
	return nil
}

// setBase sets BASE.
func (da *doubleArray) setBase(i, base int) {
	da.bc[i].base = base
}

// setCheck sets CHECK.
func (da *doubleArray) setCheck(i int, check byte) {
	da.bc[i].check = check
}

// extendBaseCheckArray extends array of BASE/CHECK.
func (da *doubleArray) extendBaseCheckArray() {
	da.bc = append(da.bc, newBaseCheckArray(blockSize)...)
}

// findEmptyIndex returns an index of unused BASE/CHECK node.
func (da *doubleArray) findEmptyIndex(start int) int {
	i := start
	for ; i < len(da.bc); i++ {
		if da.bc[i].base == 0 && da.bc[i].check == 0 {
			break
		}
	}
	return i
}

// findBase returns good BASE.
func (da *doubleArray) findBase(siblings []sibling, start int, usedBase map[int]struct{}) (base int) {
	for idx, firstChar := start+1, siblings[0].c; ; idx = da.findEmptyIndex(idx + 1) {
		base = nextIndex(idx, firstChar)
		if _, used := usedBase[base]; used {
			continue
		}
		i := 0
		for ; i < len(siblings); i++ {
			next := nextIndex(base, siblings[i].c)
			if len(da.bc) <= next {
				da.extendBaseCheckArray()
			}
			if len(da.bc) <= next || da.bc[next].base != 0 || da.bc[next].check != 0 {
				break
			}
		}
		if i == len(siblings) {
			break
		}
	}
	usedBase[base] = struct{}{}
	return base
}

func (da *doubleArray) arrange(records []*record, idx, depth int, usedBase map[int]struct{}) (base int, siblings []sibling, leaf *record, err error) {
	siblings, leaf, err = makeSiblings(records, depth)
	if err != nil {
		return -1, nil, nil, err
	}
	if len(siblings) < 1 {
		return -1, nil, leaf, nil
	}
	base = da.findBase(siblings, idx, usedBase)
	da.setBase(idx, base)
	return base, siblings, leaf, err
}

// node represents a node of Double-Array.
type node struct {
	data interface{}

	// Names of path parameters.
	paramNames []string
}

// makeNode returns a new node from record.
func makeNode(r *record) (*node, error) {
	dups := make(map[string]bool)
	for _, name := range r.paramNames {
		if dups[name] {
			return nil, fmt.Errorf("denco: path parameter `%v' is duplicated in the key `%v'", name, r.Key)
		}
		dups[name] = true
	}
	return &node{data: r.Value, paramNames: r.paramNames}, nil
}

// sibling represents an intermediate data of build for Double-Array.
type sibling struct {
	// An index of start of duplicated characters.
	start int

	// An index of end of duplicated characters.
	end int

	// A character of sibling.
	c byte
}

// nextIndex returns a next index of array of BASE/CHECK.
func nextIndex(base int, c byte) int {
	return base ^ int(c)
}

// makeSiblings returns slice of sibling.
func makeSiblings(records []*record, depth int) (sib []sibling, leaf *record, err error) {
	var (
		pc byte
		n  int
	)
	for i, r := range records {
		if len(r.Key) <= depth {
			leaf = r
			continue
		}
		c := r.Key[depth]
		switch {
		case pc < c:
			sib = append(sib, sibling{start: i, c: c})
		case pc == c:
			continue
		default:
			return nil, nil, fmt.Errorf("denco: BUG: routing table hasn't been sorted")
		}
		if n > 0 {
			sib[n-1].end = i
		}
		pc = c
		n++
	}
	if n == 0 {
		return nil, leaf, nil
	}
	sib[n-1].end = len(records)
	return sib, leaf, nil
}

// Record represents a record data for router construction.
type Record struct {
	// Key for router construction.
	Key string

	// Result value for Key.
	Value interface{}
}

// NewRecord returns a new Record.
func NewRecord(key string, value interface{}) Record {
	return Record{
		Key:   key,
		Value: value,
	}
}

// record represents a record that use to build the Double-Array.
type record struct {
	Record
	paramNames []string
}

// makeRecords returns the records that use to build Double-Arrays.
func makeRecords(srcs []Record) (statics, params []*record) {
	spChars := string([]byte{ParamCharacter, WildcardCharacter})
	termChar := string(TerminationCharacter)
	for _, r := range srcs {
		if strings.ContainsAny(r.Key, spChars) {
			r.Key += termChar
			params = append(params, &record{Record: r})
		} else {
			statics = append(statics, &record{Record: r})
		}
	}
	return statics, params
}

// recordSlice represents a slice of Record for sort and implements the sort.Interface.
type recordSlice []*record

// Len implements the sort.Interface.Len.
func (rs recordSlice) Len() int {
	return len(rs)
}

// Less implements the sort.Interface.Less.
func (rs recordSlice) Less(i, j int) bool {
	return rs[i].Key < rs[j].Key
}

// Swap implements the sort.Interface.Swap.
func (rs recordSlice) Swap(i, j int) {
	rs[i], rs[j] = rs[j], rs[i]
}
<|endoftext|>


<commit_before>package fleet

import (
	"encoding/json"
	"io/ioutil"
	"net"
	"net/http"
)

type Client struct {
	http *http.Client
}

type Unit struct {
	CurrentState string `json:"currentState"`
	DesiredState string `json:"desiredState"`
	MachineID    string `json:"machineID"`
	Name         string `json:"name"`
}

type UnitsResponse struct {
	Units []Unit `json:"units"`
}

func NewClient(path string) Client {
	dialFunc := func(string, string) (net.Conn, error) {
		return net.Dial("unix", path)
	}

	httpClient := http.Client{
		Transport: &http.Transport{
			Dial: dialFunc,
		},
	}

	return Client{&httpClient}
}

func (self *Client) Units() ([]Unit, error) {
	response, err := self.http.Get("http://sock/fleet/v1/units")
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	var parsedResponse UnitsResponse
	err = json.Unmarshal(body, &parsedResponse)
	if err != nil {
		return nil, err
	}

	return parsedResponse.Units, nil
}
<commit_msg>Use json.Decoder instead of json.Unmarshal.<commit_after>package fleet

import (
	"encoding/json"
	"net"
	"net/http"
)

type Client struct {
	http *http.Client
}

type Unit struct {
	CurrentState string `json:"currentState"`
	DesiredState string `json:"desiredState"`
	MachineID    string `json:"machineID"`
	Name         string `json:"name"`
}

type UnitsResponse struct {
	Units []Unit `json:"units"`
}

func NewClient(path string) Client {
	dialFunc := func(string, string) (net.Conn, error) {
		return net.Dial("unix", path)
	}

	httpClient := http.Client{
		Transport: &http.Transport{
			Dial: dialFunc,
		},
	}

	return Client{&httpClient}
}

func (self *Client) Units() ([]Unit, error) {
	response, err := self.http.Get("http://sock/fleet/v1/units")
	if err != nil {
		return nil, err
	}

	decoder := json.NewDecoder(response.Body)
	var parsedResponse UnitsResponse
	err = decoder.Decode(&parsedResponse)
	if err != nil {
		return nil, err
	}

	return parsedResponse.Units, nil
}
<|endoftext|>


<commit_before>// Holds the general aspects of the library

package jcdecauxssb

import (
	"errors"
	"net/http"
	"net/url"
)

const (
	// BaseURL represents JcDecaux API base URL
	BaseURL = "https://api.jcdecaux.com/vls/v1/"

	// The default User-Agent used in the library
	// TODO: Add a library version
	UserAgent = "go-jcdecaux/"
)

// A Client manages communication with the JcDecaux API.
// It is the only way to "hack" the base URL
type Client struct {
	// HTTP client used to communicate with the API.
	client *http.Client

	// Base URL for API requests.
	BaseURL *url.URL

	// UserAgent agent used when communicating with JcDecaux API.
	UserAgent string

	// Application Key
	Key string
}

// New returns a new JcDecaux API client. If a nil httpClient is
// provided, http.DefaultClient will be used.
func New(key string, httpClient *http.Client) (*Client, error) {
	if key == "" {
		return nil, errors.New("Key field must be populated")
	}

	// As a key is 40 characters long, check for conformance
	if len(key) != 40 {
		return nil, errors.New("The key format is invalid, it must be 40 characters long")
	}

	if httpClient == nil {
		httpClient = http.DefaultClient
	}
	baseURL, err := url.Parse(BaseURL)
	if err != nil {
		return nil, err
	}

	c := &Client{
		client:    httpClient,
		BaseURL:   baseURL,
		UserAgent: UserAgent,
		Key:       key,
	}
	return c, err
}
<commit_msg>Remove the key checking from New() and make it an exported function<commit_after>// Holds the general aspects of the library

package jcdecauxssb

import (
	"errors"
	"net/http"
	"net/url"
)

const (
	// BaseURL represents JcDecaux API base URL
	BaseURL = "https://api.jcdecaux.com/vls/v1/"

	// The default User-Agent used in the library
	// TODO: Add a library version
	UserAgent = "go-jcdecaux/"
)

// A Client manages communication with the JcDecaux API.
// It is the only way to "hack" the base URL
type Client struct {
	// HTTP client used to communicate with the API.
	client *http.Client

	// Base URL for API requests.
	BaseURL *url.URL

	// UserAgent agent used when communicating with JcDecaux API.
	UserAgent string

	// Application Key
	Key string
}

// Checks the key
func CheckKey(key string) error {
	// If there is no key, return an error
	if key == "" {
		return errors.New("Key field must be populated")
	}

	// As a key is 40 characters long, check for conformance
	if len(key) != 40 {
		return errors.New("The key format is invalid, it must be 40 characters long")
	}
	return nil
}

// New returns a new JcDecaux API client. If a nil httpClient is provided, http.DefaultClient will be used.
func New(key string, httpClient *http.Client) (*Client, error) {

	err := CheckKey(key)
	if err != nil {
		return nil, err
	}

	if httpClient == nil {
		httpClient = http.DefaultClient
	}
	baseURL, err := url.Parse(BaseURL)
	if err != nil {
		return nil, err
	}

	c := &Client{
		client:    httpClient,
		BaseURL:   baseURL,
		UserAgent: UserAgent,
		Key:       key,
	}
	return c, err
}
<|endoftext|>


<commit_before>package main

import "github.com/tedsuo/rata"

const (
	Index = "Index"
	List  = "List"

	UI = "UI"
)

var Routes = rata.Routes([]rata.Route{
	{Path: "/list", Method: "GET", Name: List},

	//Deprecated URI
	{Path: "/ui", Method: "GET", Name: UI},

	{Path: "/", Method: "GET", Name: Index},
	{Path: "/js/:file", Method: "GET", Name: Index},
	{Path: "/css/:file", Method: "GET", Name: Index},
	{Path: "/font/:file", Method: "GET", Name: Index},
	{Path: "/images/:file", Method: "GET", Name: Index},
	{Path: "/:file", Method: "GET", Name: Index},
})
<commit_msg>Fix routing<commit_after>package main

import "github.com/tedsuo/rata"

const (
	Index = "Index"
	List  = "List"

	UI = "UI"
)

var Routes = rata.Routes([]rata.Route{
	{Path: "/list", Method: "GET", Name: List},

	//Deprecated URI
	{Path: "/ui/", Method: "GET", Name: UI},

	{Path: "/", Method: "GET", Name: Index},
	{Path: "/:file/", Method: "GET", Name: Index},
})
<|endoftext|>


<commit_before>package awsat

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"

	"github.com/aws/aws-sdk-go/service/cloudformation"
)

func TestStack(t *testing.T) {
	_, tplFilePath, tplClean := generateTmpFile("tpl body content")
	defer tplClean()

	_, polFilePath, polClean := generateTmpFile("policy content")
	defer polClean()

	t.Run("create", func(t *testing.T) {

		Template("create stack name=new-stack template-file="+tplFilePath+" capabilities=one,two disable-rollback=true notifications=none,ntwo on-failure=done parameters=1:pone,2:ptwo resource-types=rone,rtwo role=donjuan policy-file="+polFilePath+" timeout=180").Mock(&cloudformationMock{
			CreateStackFunc: func(input *cloudformation.CreateStackInput) (*cloudformation.CreateStackOutput, error) {
				return &cloudformation.CreateStackOutput{StackId: String("new-stack-id")}, nil
			}}).ExpectInput("CreateStack", &cloudformation.CreateStackInput{
			StackName:        String("new-stack"),
			TemplateBody:     String("tpl body content"),
			Capabilities:     []*string{String("one"), String("two")},
			DisableRollback:  Bool(true),
			NotificationARNs: []*string{String("none"), String("ntwo")},
			OnFailure:        String("done"),
			Parameters:       []*cloudformation.Parameter{{ParameterKey: String("1"), ParameterValue: String("pone")}, {ParameterKey: String("2"), ParameterValue: String("ptwo")}},
			ResourceTypes:    []*string{String("rone"), String("rtwo")},
			RoleARN:          String("donjuan"),
			StackPolicyBody:  String("policy content"),
			TimeoutInMinutes: Int64(180),
		}).ExpectCommandResult("new-stack-id").ExpectCalls("CreateStack").Run(t)
	})

	t.Run("update", func(t *testing.T) {
		_, polUpdateFilePath, clean := generateTmpFile("update policy content")
		defer clean()

		Template("update stack name=other-name template-file="+tplFilePath+" use-previous-template=true capabilities=one,two notifications=none,ntwo parameters=1:pone,2:ptwo resource-types=rone,rtwo role=donjuan policy-file="+polFilePath+" policy-update-file="+polUpdateFilePath).Mock(&cloudformationMock{
			UpdateStackFunc: func(input *cloudformation.UpdateStackInput) (*cloudformation.UpdateStackOutput, error) {
				return &cloudformation.UpdateStackOutput{StackId: String("any-stack-id")}, nil
			}}).ExpectInput("UpdateStack", &cloudformation.UpdateStackInput{
			StackName:                   String("other-name"),
			TemplateBody:                String("tpl body content"),
			Capabilities:                []*string{String("one"), String("two")},
			NotificationARNs:            []*string{String("none"), String("ntwo")},
			Parameters:                  []*cloudformation.Parameter{{ParameterKey: String("1"), ParameterValue: String("pone")}, {ParameterKey: String("2"), ParameterValue: String("ptwo")}},
			ResourceTypes:               []*string{String("rone"), String("rtwo")},
			RoleARN:                     String("donjuan"),
			StackPolicyBody:             String("policy content"),
			StackPolicyDuringUpdateBody: String("update policy content"),
			UsePreviousTemplate:         Bool(true),
		}).ExpectCommandResult("any-stack-id").ExpectCalls("UpdateStack").Run(t)
	})

	t.Run("delete", func(t *testing.T) {
		Template("delete stack name=any-stack-name retain-resources=1,2").Mock(&cloudformationMock{
			DeleteStackFunc: func(input *cloudformation.DeleteStackInput) (*cloudformation.DeleteStackOutput, error) {
				return nil, nil
			}}).ExpectInput("DeleteStack", &cloudformation.DeleteStackInput{
			StackName:       String("any-stack-name"),
			RetainResources: []*string{String("1"), String("2")},
		}).ExpectCalls("DeleteStack").Run(t)
	})

	_, stackFileYMLPath, stackFileYMLClean := generateTmpFileWithName(`
Parameters:
  Test1: 1
  Test2: 2
  Test3: 3
Tags:
  Tag1: 1
  Tag2: 2
  Tag3: 3
StackPolicy:
  Statement:
  - Effect: Allow
    Resource: "*"
`, "stackfile.yml")

	defer stackFileYMLClean()

	t.Run("update", func(t *testing.T) {
		Template("update stack name=some-stack template-file="+tplFilePath+" stack-file="+stackFileYMLPath+" parameters=Test1:a,Test2:b tags=Tag1:a,Tag2:b policy-file="+polFilePath).Mock(&cloudformationMock{
			UpdateStackFunc: func(input *cloudformation.UpdateStackInput) (*cloudformation.UpdateStackOutput, error) {
				return &cloudformation.UpdateStackOutput{StackId: String("any-stack-id")}, nil
			}}).ExpectInput("UpdateStack", &cloudformation.UpdateStackInput{
			StackName:       String("some-stack"),
			TemplateBody:    String("tpl body content"),
			Parameters:      []*cloudformation.Parameter{{ParameterKey: String("Test1"), ParameterValue: String("a")}, {ParameterKey: String("Test2"), ParameterValue: String("b")}, {ParameterKey: String("Test3"), ParameterValue: String("3")}},
			Tags:            []*cloudformation.Tag{{Key: String("Tag1"), Value: String("a")}, {Key: String("Tag2"), Value: String("b")}, {Key: String("Tag3"), Value: String("3")}},
			StackPolicyBody: String("policy content"),
		}).ExpectCalls("UpdateStack").Run(t)
	})

	_, stackFileJSONPath, stackFileJSONClean := generateTmpFileWithName(`{"Parameters":{"Test1":"1","Test2":"2","Test3":"3"},"Tags":{"Tag1":"1","Tag2":"2","Tag3":"3"},"StackPolicy":{"Statement":[{"Effect":"Allow","Resource":"*"}]}}`, "stackfile.json")

	defer stackFileJSONClean()

	t.Run("update", func(t *testing.T) {
		Template("update stack name=some-stack template-file="+tplFilePath+" stack-file="+stackFileJSONPath+" parameters=Test1:a,Test2:b tags=Tag1:a,Tag2:b").Mock(&cloudformationMock{
			UpdateStackFunc: func(input *cloudformation.UpdateStackInput) (*cloudformation.UpdateStackOutput, error) {
				return &cloudformation.UpdateStackOutput{StackId: String("any-stack-id")}, nil
			}}).ExpectInput("UpdateStack", &cloudformation.UpdateStackInput{
			StackName:       String("some-stack"),
			TemplateBody:    String("tpl body content"),
			Parameters:      []*cloudformation.Parameter{{ParameterKey: String("Test1"), ParameterValue: String("a")}, {ParameterKey: String("Test2"), ParameterValue: String("b")}, {ParameterKey: String("Test3"), ParameterValue: String("3")}},
			Tags:            []*cloudformation.Tag{{Key: String("Tag1"), Value: String("a")}, {Key: String("Tag2"), Value: String("b")}, {Key: String("Tag3"), Value: String("3")}},
			StackPolicyBody: String(`{"Statement":[{"Effect":"Allow","Resource":"*"}]}`),
		}).ExpectCalls("UpdateStack").Run(t)
	})

}

func generateTmpFileWithName(content, filename string) (*os.File, string, func()) {
	dir, err := ioutil.TempDir("", "awless-at-tmpdir")
	if err != nil {
		panic(err)
	}

	tmpfn := filepath.Join(dir, filename)
	if err := ioutil.WriteFile(tmpfn, []byte(content), 0666); err != nil {
		panic(err)
	}

	file, err := os.Open(tmpfn)
	if err != nil {
		panic(err)
	}

	cleanup := func() {
		file.Close()
		if err := os.RemoveAll(dir); err != nil {
			panic(err)
		}
	}

	return file, file.Name(), cleanup
}
<commit_msg>Create Stack: Rollback triggers: add acceptance test for rollback config when creating a stack<commit_after>package awsat

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudformation"
)

func TestStack(t *testing.T) {
	_, tplFilePath, tplClean := generateTmpFile("tpl body content")
	defer tplClean()

	_, polFilePath, polClean := generateTmpFile("policy content")
	defer polClean()

	t.Run("create", func(t *testing.T) {

		Template("create stack name=new-stack template-file="+tplFilePath+" capabilities=one,two disable-rollback=true notifications=none,ntwo on-failure=done parameters=1:pone,2:ptwo resource-types=rone,rtwo role=donjuan policy-file="+polFilePath+" rollback-triggers=[arn1,arn2] rollback-monitoring-min=2 timeout=180").Mock(&cloudformationMock{
			CreateStackFunc: func(input *cloudformation.CreateStackInput) (*cloudformation.CreateStackOutput, error) {
				return &cloudformation.CreateStackOutput{StackId: String("new-stack-id")}, nil
			}}).ExpectInput("CreateStack", &cloudformation.CreateStackInput{
			StackName:        String("new-stack"),
			TemplateBody:     String("tpl body content"),
			Capabilities:     []*string{String("one"), String("two")},
			DisableRollback:  Bool(true),
			NotificationARNs: []*string{String("none"), String("ntwo")},
			OnFailure:        String("done"),
			Parameters:       []*cloudformation.Parameter{{ParameterKey: String("1"), ParameterValue: String("pone")}, {ParameterKey: String("2"), ParameterValue: String("ptwo")}},
			ResourceTypes:    []*string{String("rone"), String("rtwo")},
			RoleARN:          String("donjuan"),
			StackPolicyBody:  String("policy content"),
			TimeoutInMinutes: Int64(180),
			RollbackConfiguration: &cloudformation.RollbackConfiguration{
				MonitoringTimeInMinutes: Int64(2),
				RollbackTriggers: []*cloudformation.RollbackTrigger{
					{Arn: String("arn1"), Type: aws.String("AWS::CloudWatch::Alarm")},
					{Arn: String("arn2"), Type: aws.String("AWS::CloudWatch::Alarm")},
				},
			},
		}).ExpectCommandResult("new-stack-id").ExpectCalls("CreateStack").Run(t)
	})

	t.Run("update", func(t *testing.T) {
		_, polUpdateFilePath, clean := generateTmpFile("update policy content")
		defer clean()

		Template("update stack name=other-name template-file="+tplFilePath+" use-previous-template=true capabilities=one,two notifications=none,ntwo parameters=1:pone,2:ptwo resource-types=rone,rtwo role=donjuan policy-file="+polFilePath+" policy-update-file="+polUpdateFilePath).Mock(&cloudformationMock{
			UpdateStackFunc: func(input *cloudformation.UpdateStackInput) (*cloudformation.UpdateStackOutput, error) {
				return &cloudformation.UpdateStackOutput{StackId: String("any-stack-id")}, nil
			}}).ExpectInput("UpdateStack", &cloudformation.UpdateStackInput{
			StackName:                   String("other-name"),
			TemplateBody:                String("tpl body content"),
			Capabilities:                []*string{String("one"), String("two")},
			NotificationARNs:            []*string{String("none"), String("ntwo")},
			Parameters:                  []*cloudformation.Parameter{{ParameterKey: String("1"), ParameterValue: String("pone")}, {ParameterKey: String("2"), ParameterValue: String("ptwo")}},
			ResourceTypes:               []*string{String("rone"), String("rtwo")},
			RoleARN:                     String("donjuan"),
			StackPolicyBody:             String("policy content"),
			StackPolicyDuringUpdateBody: String("update policy content"),
			UsePreviousTemplate:         Bool(true),
		}).ExpectCommandResult("any-stack-id").ExpectCalls("UpdateStack").Run(t)
	})

	t.Run("delete", func(t *testing.T) {
		Template("delete stack name=any-stack-name retain-resources=1,2").Mock(&cloudformationMock{
			DeleteStackFunc: func(input *cloudformation.DeleteStackInput) (*cloudformation.DeleteStackOutput, error) {
				return nil, nil
			}}).ExpectInput("DeleteStack", &cloudformation.DeleteStackInput{
			StackName:       String("any-stack-name"),
			RetainResources: []*string{String("1"), String("2")},
		}).ExpectCalls("DeleteStack").Run(t)
	})

	_, stackFileYMLPath, stackFileYMLClean := generateTmpFileWithName(`
Parameters:
  Test1: 1
  Test2: 2
  Test3: 3
Tags:
  Tag1: 1
  Tag2: 2
  Tag3: 3
StackPolicy:
  Statement:
  - Effect: Allow
    Resource: "*"
`, "stackfile.yml")

	defer stackFileYMLClean()

	t.Run("update", func(t *testing.T) {
		Template("update stack name=some-stack template-file="+tplFilePath+" stack-file="+stackFileYMLPath+" parameters=Test1:a,Test2:b tags=Tag1:a,Tag2:b policy-file="+polFilePath).Mock(&cloudformationMock{
			UpdateStackFunc: func(input *cloudformation.UpdateStackInput) (*cloudformation.UpdateStackOutput, error) {
				return &cloudformation.UpdateStackOutput{StackId: String("any-stack-id")}, nil
			}}).ExpectInput("UpdateStack", &cloudformation.UpdateStackInput{
			StackName:       String("some-stack"),
			TemplateBody:    String("tpl body content"),
			Parameters:      []*cloudformation.Parameter{{ParameterKey: String("Test1"), ParameterValue: String("a")}, {ParameterKey: String("Test2"), ParameterValue: String("b")}, {ParameterKey: String("Test3"), ParameterValue: String("3")}},
			Tags:            []*cloudformation.Tag{{Key: String("Tag1"), Value: String("a")}, {Key: String("Tag2"), Value: String("b")}, {Key: String("Tag3"), Value: String("3")}},
			StackPolicyBody: String("policy content"),
		}).ExpectCalls("UpdateStack").Run(t)
	})

	_, stackFileJSONPath, stackFileJSONClean := generateTmpFileWithName(`{"Parameters":{"Test1":"1","Test2":"2","Test3":"3"},"Tags":{"Tag1":"1","Tag2":"2","Tag3":"3"},"StackPolicy":{"Statement":[{"Effect":"Allow","Resource":"*"}]}}`, "stackfile.json")

	defer stackFileJSONClean()

	t.Run("update", func(t *testing.T) {
		Template("update stack name=some-stack template-file="+tplFilePath+" stack-file="+stackFileJSONPath+" parameters=Test1:a,Test2:b tags=Tag1:a,Tag2:b").Mock(&cloudformationMock{
			UpdateStackFunc: func(input *cloudformation.UpdateStackInput) (*cloudformation.UpdateStackOutput, error) {
				return &cloudformation.UpdateStackOutput{StackId: String("any-stack-id")}, nil
			}}).ExpectInput("UpdateStack", &cloudformation.UpdateStackInput{
			StackName:       String("some-stack"),
			TemplateBody:    String("tpl body content"),
			Parameters:      []*cloudformation.Parameter{{ParameterKey: String("Test1"), ParameterValue: String("a")}, {ParameterKey: String("Test2"), ParameterValue: String("b")}, {ParameterKey: String("Test3"), ParameterValue: String("3")}},
			Tags:            []*cloudformation.Tag{{Key: String("Tag1"), Value: String("a")}, {Key: String("Tag2"), Value: String("b")}, {Key: String("Tag3"), Value: String("3")}},
			StackPolicyBody: String(`{"Statement":[{"Effect":"Allow","Resource":"*"}]}`),
		}).ExpectCalls("UpdateStack").Run(t)
	})

}

func generateTmpFileWithName(content, filename string) (*os.File, string, func()) {
	dir, err := ioutil.TempDir("", "awless-at-tmpdir")
	if err != nil {
		panic(err)
	}

	tmpfn := filepath.Join(dir, filename)
	if err := ioutil.WriteFile(tmpfn, []byte(content), 0666); err != nil {
		panic(err)
	}

	file, err := os.Open(tmpfn)
	if err != nil {
		panic(err)
	}

	cleanup := func() {
		file.Close()
		if err := os.RemoveAll(dir); err != nil {
			panic(err)
		}
	}

	return file, file.Name(), cleanup
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// godoc: Go Documentation Server

// Web server tree:
//
//	http://godoc/		main landing page
//	http://godoc/doc/	serve from $GOROOT/doc - spec, mem, tutorial, etc.
//	http://godoc/src/	serve files from $GOROOT/src; .go gets pretty-printed
//	http://godoc/cmd/	serve documentation about commands (TODO)
//	http://godoc/pkg/	serve documentation about packages
//				(idea is if you say import "compress/zlib", you go to
//				http://godoc/pkg/compress/zlib)
//
// Command-line interface:
//
//	godoc packagepath [name ...]
//
//	godoc compress/zlib
//		- prints doc for package compress/zlib
//	godoc crypto/block Cipher NewCMAC
//		- prints doc for Cipher and NewCMAC in package crypto/block

package main

import (
	"bytes";
	"flag";
	"fmt";
	"http";
	"io";
	"log";
	"os";
	"time";
)

var (
	// periodic sync
	syncCmd				= flag.String("sync", "", "sync command; disabled if empty");
	syncMin				= flag.Int("sync_minutes", 0, "sync interval in minutes; disabled if <= 0");
	syncDelay	delayTime;	// actual sync delay in minutes; usually syncDelay == syncMin, but delay may back off exponentially

	// server control
	httpaddr	= flag.String("http", "", "HTTP service address (e.g., ':6060')");

	// layout control
	html		= flag.Bool("html", false, "print HTML in command-line mode");
)

func exec(c *http.Conn, args []string) bool {
	r, w, err := os.Pipe();
	if err != nil {
		log.Stderrf("os.Pipe(): %v\n", err);
		return false;
	}

	bin := args[0];
	fds := []*os.File{nil, w, w};
	if *verbose {
		log.Stderrf("executing %v", args);
	}
	pid, err := os.ForkExec(bin, args, os.Environ(), goroot, fds);
	defer r.Close();
	w.Close();
	if err != nil {
		log.Stderrf("os.ForkExec(%q): %v\n", bin, err);
		return false;
	}

	var buf bytes.Buffer;
	io.Copy(r, &buf);
	wait, err := os.Wait(pid, 0);
	if err != nil {
		os.Stderr.Write(buf.Bytes());
		log.Stderrf("os.Wait(%d, 0): %v\n", pid, err);
		return false;
	}
	if !wait.Exited() || wait.ExitStatus() != 0 {
		os.Stderr.Write(buf.Bytes());
		log.Stderrf("executing %v failed (exit status = %d)", args, wait.ExitStatus());
		return false;
	}

	if *verbose {
		os.Stderr.Write(buf.Bytes());
	}
	if c != nil {
		c.SetHeader("content-type", "text/plain; charset=utf-8");
		c.Write(buf.Bytes());
	}

	return true;
}

func dosync(c *http.Conn, r *http.Request) {
	args := []string{"/bin/sh", "-c", *syncCmd};
	if exec(c, args) {
		// sync succeeded
		syncTime.set(nil);
		syncDelay.set(*syncMin);	//  revert to regular sync schedule
	} else {
		// sync failed - back off exponentially, but try at least once a day
		syncDelay.backoff(24*60);
	}
}

func usage() {
	fmt.Fprintf(os.Stderr,
		"usage: godoc package [name ...]\n"
		"	godoc -http=:6060\n");
	flag.PrintDefaults();
	os.Exit(2);
}

func loggingHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(c *http.Conn, req *http.Request) {
		log.Stderrf("%s\t%s", c.RemoteAddr, req.Url);
		h.ServeHTTP(c, req);
	});
}

func main() {
	flag.Usage = usage;
	flag.Parse();

	// Check usage first; get usage message out early.
	switch {
	case *httpaddr != "":
		if flag.NArg() != 0 {
			usage();
		}
	default:
		if flag.NArg() == 0 {
			usage();
		}
	}

	if err := os.Chdir(goroot); err != nil {
		log.Exitf("chdir %s: %v", goroot, err);
	}

	readTemplates();

	if *httpaddr != "" {
		var handler http.Handler = http.DefaultServeMux;
		if *verbose {
			log.Stderrf("Go Documentation Server\n");
			log.Stderrf("address = %s\n", *httpaddr);
			log.Stderrf("goroot = %s\n", goroot);
			log.Stderrf("pkgroot = %s\n", *pkgroot);
			log.Stderrf("tmplroot = %s\n", *tmplroot);
			handler = loggingHandler(handler);
		}

		registerPublicHandlers(http.DefaultServeMux);
		if *syncCmd != "" {
			http.Handle("/debug/sync", http.HandlerFunc(dosync));
		}

		// The server may have been restarted; always wait 1sec to
		// give the forking server a chance to shut down and release
		// the http port.
		time.Sleep(1e9);

		// Start sync goroutine, if enabled.
		if *syncCmd != "" && *syncMin > 0 {
			syncDelay.set(*syncMin);	// initial sync delay
			go func() {
				for {
					dosync(nil, nil);
					delay, _ := syncDelay.get();
					if *verbose {
						log.Stderrf("next sync in %dmin", delay.(int));
					}
					time.Sleep(int64(delay.(int))*60e9);
				}
			}();
		}

		// Start indexing goroutine.
		go indexer();

		// Start http server.
		if err := http.ListenAndServe(*httpaddr, handler); err != nil {
			log.Exitf("ListenAndServe %s: %v", *httpaddr, err);
		}
		return;
	}

	if *html {
		packageText = packageHtml;
		parseerrorText = parseerrorHtml;
	}

	info := getPageInfo(flag.Arg(0));

	if info.PDoc != nil && flag.NArg() > 1 {
		args := flag.Args();
		info.PDoc.Filter(args[1:len(args)]);
	}

	if err := packageText.Execute(info, os.Stdout); err != nil {
		log.Stderrf("packageText.Execute: %s", err);
	}
}
<commit_msg>don't update sync time if no files have changed (and thus avoid re-indexing after every sync attempt)<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// godoc: Go Documentation Server

// Web server tree:
//
//	http://godoc/		main landing page
//	http://godoc/doc/	serve from $GOROOT/doc - spec, mem, tutorial, etc.
//	http://godoc/src/	serve files from $GOROOT/src; .go gets pretty-printed
//	http://godoc/cmd/	serve documentation about commands (TODO)
//	http://godoc/pkg/	serve documentation about packages
//				(idea is if you say import "compress/zlib", you go to
//				http://godoc/pkg/compress/zlib)
//
// Command-line interface:
//
//	godoc packagepath [name ...]
//
//	godoc compress/zlib
//		- prints doc for package compress/zlib
//	godoc crypto/block Cipher NewCMAC
//		- prints doc for Cipher and NewCMAC in package crypto/block

package main

import (
	"bytes";
	"flag";
	"fmt";
	"http";
	"io";
	"log";
	"os";
	"time";
)

var (
	// periodic sync
	syncCmd				= flag.String("sync", "", "sync command; disabled if empty");
	syncMin				= flag.Int("sync_minutes", 0, "sync interval in minutes; disabled if <= 0");
	syncDelay	delayTime;	// actual sync delay in minutes; usually syncDelay == syncMin, but delay may back off exponentially

	// server control
	httpaddr	= flag.String("http", "", "HTTP service address (e.g., ':6060')");

	// layout control
	html		= flag.Bool("html", false, "print HTML in command-line mode");
)

func exec(c *http.Conn, args []string) (status int) {
	r, w, err := os.Pipe();
	if err != nil {
		log.Stderrf("os.Pipe(): %v\n", err);
		return 2;
	}

	bin := args[0];
	fds := []*os.File{nil, w, w};
	if *verbose {
		log.Stderrf("executing %v", args);
	}
	pid, err := os.ForkExec(bin, args, os.Environ(), goroot, fds);
	defer r.Close();
	w.Close();
	if err != nil {
		log.Stderrf("os.ForkExec(%q): %v\n", bin, err);
		return 2;
	}

	var buf bytes.Buffer;
	io.Copy(r, &buf);
	wait, err := os.Wait(pid, 0);
	if err != nil {
		os.Stderr.Write(buf.Bytes());
		log.Stderrf("os.Wait(%d, 0): %v\n", pid, err);
		return 2;
	}
	status = wait.ExitStatus();
	if !wait.Exited() || status > 1  {
		os.Stderr.Write(buf.Bytes());
		log.Stderrf("executing %v failed (exit status = %d)", args, status);
		return;
	}

	if *verbose {
		os.Stderr.Write(buf.Bytes());
	}
	if c != nil {
		c.SetHeader("content-type", "text/plain; charset=utf-8");
		c.Write(buf.Bytes());
	}

	return;
}

func dosync(c *http.Conn, r *http.Request) {
	args := []string{"/bin/sh", "-c", *syncCmd};
	switch exec(c, args) {
	case 0:
		// sync succeeded and some files have changed
		syncTime.set(nil);
		fallthrough;
	case 1:
		// sync failed because no files changed
		// don't change the sync time
		syncDelay.set(*syncMin);	//  revert to regular sync schedule
	default:
		// sync failed because of an error - back off exponentially, but try at least once a day
		syncDelay.backoff(24*60);
	}
}

func usage() {
	fmt.Fprintf(os.Stderr,
		"usage: godoc package [name ...]\n"
		"	godoc -http=:6060\n");
	flag.PrintDefaults();
	os.Exit(2);
}

func loggingHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(c *http.Conn, req *http.Request) {
		log.Stderrf("%s\t%s", c.RemoteAddr, req.Url);
		h.ServeHTTP(c, req);
	});
}

func main() {
	flag.Usage = usage;
	flag.Parse();

	// Check usage first; get usage message out early.
	switch {
	case *httpaddr != "":
		if flag.NArg() != 0 {
			usage();
		}
	default:
		if flag.NArg() == 0 {
			usage();
		}
	}

	if err := os.Chdir(goroot); err != nil {
		log.Exitf("chdir %s: %v", goroot, err);
	}

	readTemplates();

	if *httpaddr != "" {
		var handler http.Handler = http.DefaultServeMux;
		if *verbose {
			log.Stderrf("Go Documentation Server\n");
			log.Stderrf("address = %s\n", *httpaddr);
			log.Stderrf("goroot = %s\n", goroot);
			log.Stderrf("pkgroot = %s\n", *pkgroot);
			log.Stderrf("tmplroot = %s\n", *tmplroot);
			handler = loggingHandler(handler);
		}

		registerPublicHandlers(http.DefaultServeMux);
		if *syncCmd != "" {
			http.Handle("/debug/sync", http.HandlerFunc(dosync));
		}

		// The server may have been restarted; always wait 1sec to
		// give the forking server a chance to shut down and release
		// the http port.
		time.Sleep(1e9);

		// Start sync goroutine, if enabled.
		if *syncCmd != "" && *syncMin > 0 {
			syncDelay.set(*syncMin);	// initial sync delay
			go func() {
				for {
					dosync(nil, nil);
					delay, _ := syncDelay.get();
					if *verbose {
						log.Stderrf("next sync in %dmin", delay.(int));
					}
					time.Sleep(int64(delay.(int))*60e9);
				}
			}();
		}

		// Start indexing goroutine.
		go indexer();

		// Start http server.
		if err := http.ListenAndServe(*httpaddr, handler); err != nil {
			log.Exitf("ListenAndServe %s: %v", *httpaddr, err);
		}
		return;
	}

	if *html {
		packageText = packageHtml;
		parseerrorText = parseerrorHtml;
	}

	info := getPageInfo(flag.Arg(0));

	if info.PDoc != nil && flag.NArg() > 1 {
		args := flag.Args();
		info.PDoc.Filter(args[1:len(args)]);
	}

	if err := packageText.Execute(info, os.Stdout); err != nil {
		log.Stderrf("packageText.Execute: %s", err);
	}
}
<|endoftext|>