<commit_before>package dbr

import (
	// "fmt"
	"bytes"
	"database/sql/driver"
	"reflect"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"
)

// Need to turn \x00, \n, \r, \, ', " and \x1a
// Returns an escaped, quoted string. eg, "hello 'world'" -> "'hello \'world\''"
func escapeAndQuoteString(val string) string {
	buf := bytes.Buffer{}

	buf.WriteRune('\'')

	for _, char := range val {
		if char == '\'' { // single quote: ' -> \'
			buf.WriteString("\\'")
		} else if char == '"' { // double quote: " -> \"
			buf.WriteString("\\\"")
		} else if char == '\\' { // slash: \ -> "\\"
			buf.WriteString("\\\\")
		} else if char == '\n' { // control: newline: \n -> "\n"
			buf.WriteString("\\n")
		} else if char == '\r' { // control: return: \r -> "\r"
			buf.WriteString("\\r")
		} else if char == 0 { // control: NUL: 0 -> "\x00"
			buf.WriteString("\\x00")
		} else if char == 0x1a { // control: \x1a -> "\x1a"
			buf.WriteString("\\x1a")
		} else {
			buf.WriteRune(char)
		}
	}

	buf.WriteRune('\'')

	return buf.String()
}

func isUint(k reflect.Kind) bool {
	return (k == reflect.Uint) ||
		(k == reflect.Uint8) ||
		(k == reflect.Uint16) ||
		(k == reflect.Uint32) ||
		(k == reflect.Uint64)
}

func isInt(k reflect.Kind) bool {
	return (k == reflect.Int) ||
		(k == reflect.Int8) ||
		(k == reflect.Int16) ||
		(k == reflect.Int32) ||
		(k == reflect.Int64)
}

func isFloat(k reflect.Kind) bool {
	return (k == reflect.Float32) ||
		(k == reflect.Float64)
}

// sql is like "id = ? OR username = ?"
// vals is like []interface{}{4, "bob"}
// NOTE that vals can only have values of certain types:
//   - Integers (signed and unsigned)
//   - floats
//   - strings (that are valid utf-8)
//   - booleans
//   - times
var typeOfTime = reflect.TypeOf(time.Time{})

func Interpolate(sql string, vals []interface{}) (string, error) {
	if sql == "" && len(vals) == 0 {
		return "", nil
	}

	curVal := 0
	maxVals := len(vals)

	buf := bytes.Buffer{}

	for _, r := range sql {
		if r != '?' {
			buf.WriteRune(r)
		} else if r == '?' && curVal < maxVals {
			v := vals[curVal]

			valuer, ok := v.(driver.Valuer)
			if ok {
				val, err := valuer.Value()
				if err != nil {
					return "", err
				} else {
					v = val
				}
			}

			valueOfV := reflect.ValueOf(v)
			kindOfV := valueOfV.Kind()

			if v == nil {
				buf.WriteString("NULL")
			} else if isInt(kindOfV) {
				var ival int64 = valueOfV.Int()

				buf.WriteString(strconv.FormatInt(ival, 10))
			} else if isUint(kindOfV) {
				var uival uint64 = valueOfV.Uint()

				buf.WriteString(strconv.FormatUint(uival, 10))
			} else if kindOfV == reflect.String {
				var str string = valueOfV.String()

				if !utf8.ValidString(str) {
					return "", ErrNotUTF8
				}

				buf.WriteString(escapeAndQuoteString(str))
			} else if isFloat(kindOfV) {
				var fval float64 = valueOfV.Float()

				buf.WriteString(strconv.FormatFloat(fval, 'f', -1, 64))
			} else if kindOfV == reflect.Bool {
				var bval bool = valueOfV.Bool()

				if bval {
					buf.WriteRune('1')
				} else {
					buf.WriteRune('0')
				}
			} else if kindOfV == reflect.Struct {
				if typeOfV := valueOfV.Type(); typeOfV == typeOfTime {
					t := valueOfV.Interface().(time.Time)
					buf.WriteString(escapeAndQuoteString(t.UTC().Format(timeFormat)))
				} else {
					return "", ErrInvalidValue
				}
			} else if kindOfV == reflect.Slice {
				typeOfV := reflect.TypeOf(v)
				subtype := typeOfV.Elem()
				kindOfSubtype := subtype.Kind()

				sliceLen := valueOfV.Len()
				stringSlice := make([]string, 0, sliceLen)

				if sliceLen == 0 {
					return "", ErrInvalidSliceLength
				} else if isInt(kindOfSubtype) {
					for i := 0; i < sliceLen; i += 1 {
						var ival int64 = valueOfV.Index(i).Int()
						stringSlice = append(stringSlice, strconv.FormatInt(ival, 10))
					}
				} else if isUint(kindOfSubtype) {
					for i := 0; i < sliceLen; i += 1 {
						var uival uint64 = valueOfV.Index(i).Uint()
						stringSlice = append(stringSlice, strconv.FormatUint(uival, 10))
					}
				} else if kindOfSubtype == reflect.String {
					for i := 0; i < sliceLen; i += 1 {
						var str string = valueOfV.Index(i).String()
						if !utf8.ValidString(str) {
							return "", ErrNotUTF8
						}
						stringSlice = append(stringSlice, escapeAndQuoteString(str))
					}
				} else {
					return "", ErrInvalidSliceValue
				}
				buf.WriteRune('(')
				buf.WriteString(strings.Join(stringSlice, ","))
				buf.WriteRune(')')
			} else {
				return "", ErrInvalidValue
			}

			curVal += 1
		} else {
			return "", ErrArgumentMismatch
		}
	}

	if curVal != maxVals {
		return "", ErrArgumentMismatch
	}

	return buf.String(), nil
}
<commit_msg>TWEAK: Return from the interpolator early in some cases to prevent unnecessary work/allocations.<commit_after>package dbr

import (
	// "fmt"
	"bytes"
	"database/sql/driver"
	"reflect"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"
)

// Need to turn \x00, \n, \r, \, ', " and \x1a
// Returns an escaped, quoted string. eg, "hello 'world'" -> "'hello \'world\''"
func escapeAndQuoteString(val string) string {
	buf := bytes.Buffer{}

	buf.WriteRune('\'')

	for _, char := range val {
		if char == '\'' { // single quote: ' -> \'
			buf.WriteString("\\'")
		} else if char == '"' { // double quote: " -> \"
			buf.WriteString("\\\"")
		} else if char == '\\' { // slash: \ -> "\\"
			buf.WriteString("\\\\")
		} else if char == '\n' { // control: newline: \n -> "\n"
			buf.WriteString("\\n")
		} else if char == '\r' { // control: return: \r -> "\r"
			buf.WriteString("\\r")
		} else if char == 0 { // control: NUL: 0 -> "\x00"
			buf.WriteString("\\x00")
		} else if char == 0x1a { // control: \x1a -> "\x1a"
			buf.WriteString("\\x1a")
		} else {
			buf.WriteRune(char)
		}
	}

	buf.WriteRune('\'')

	return buf.String()
}

func isUint(k reflect.Kind) bool {
	return (k == reflect.Uint) ||
		(k == reflect.Uint8) ||
		(k == reflect.Uint16) ||
		(k == reflect.Uint32) ||
		(k == reflect.Uint64)
}

func isInt(k reflect.Kind) bool {
	return (k == reflect.Int) ||
		(k == reflect.Int8) ||
		(k == reflect.Int16) ||
		(k == reflect.Int32) ||
		(k == reflect.Int64)
}

func isFloat(k reflect.Kind) bool {
	return (k == reflect.Float32) ||
		(k == reflect.Float64)
}

// sql is like "id = ? OR username = ?"
// vals is like []interface{}{4, "bob"}
// NOTE that vals can only have values of certain types:
//   - Integers (signed and unsigned)
//   - floats
//   - strings (that are valid utf-8)
//   - booleans
//   - times
var typeOfTime = reflect.TypeOf(time.Time{})

func Interpolate(sql string, vals []interface{}) (string, error) {
	// Get the number of arguments to add to this query
	maxVals := len(vals)

	// If our query is blank and has no args return early
	// Args with a blank query is an error
	if sql == "" {
		if maxVals != 0 {
			return "", ErrArgumentMismatch
		}
		return "", nil
	}

	// If we have no args and the query has no place holders return early
	// No args for a query with place holders is an error
	if len(vals) == 0 {
		for _, c := range sql {
			if c == '?' {
				return "", ErrArgumentMismatch
			}
		}
		return sql, nil
	}

	// Iterate over each rune in the sql string and replace with the next arg if it's a place holder
	curVal := 0
	buf := bytes.Buffer{}

	for _, r := range sql {
		if r != '?' {
			buf.WriteRune(r)
		} else if r == '?' && curVal < maxVals {
			v := vals[curVal]

			valuer, ok := v.(driver.Valuer)
			if ok {
				val, err := valuer.Value()
				if err != nil {
					return "", err
				} else {
					v = val
				}
			}

			valueOfV := reflect.ValueOf(v)
			kindOfV := valueOfV.Kind()

			if v == nil {
				buf.WriteString("NULL")
			} else if isInt(kindOfV) {
				var ival int64 = valueOfV.Int()

				buf.WriteString(strconv.FormatInt(ival, 10))
			} else if isUint(kindOfV) {
				var uival uint64 = valueOfV.Uint()

				buf.WriteString(strconv.FormatUint(uival, 10))
			} else if kindOfV == reflect.String {
				var str string = valueOfV.String()

				if !utf8.ValidString(str) {
					return "", ErrNotUTF8
				}

				buf.WriteString(escapeAndQuoteString(str))
			} else if isFloat(kindOfV) {
				var fval float64 = valueOfV.Float()

				buf.WriteString(strconv.FormatFloat(fval, 'f', -1, 64))
			} else if kindOfV == reflect.Bool {
				var bval bool = valueOfV.Bool()

				if bval {
					buf.WriteRune('1')
				} else {
					buf.WriteRune('0')
				}
			} else if kindOfV == reflect.Struct {
				if typeOfV := valueOfV.Type(); typeOfV == typeOfTime {
					t := valueOfV.Interface().(time.Time)
					buf.WriteString(escapeAndQuoteString(t.UTC().Format(timeFormat)))
				} else {
					return "", ErrInvalidValue
				}
			} else if kindOfV == reflect.Slice {
				typeOfV := reflect.TypeOf(v)
				subtype := typeOfV.Elem()
				kindOfSubtype := subtype.Kind()

				sliceLen := valueOfV.Len()
				stringSlice := make([]string, 0, sliceLen)

				if sliceLen == 0 {
					return "", ErrInvalidSliceLength
				} else if isInt(kindOfSubtype) {
					for i := 0; i < sliceLen; i += 1 {
						var ival int64 = valueOfV.Index(i).Int()
						stringSlice = append(stringSlice, strconv.FormatInt(ival, 10))
					}
				} else if isUint(kindOfSubtype) {
					for i := 0; i < sliceLen; i += 1 {
						var uival uint64 = valueOfV.Index(i).Uint()
						stringSlice = append(stringSlice, strconv.FormatUint(uival, 10))
					}
				} else if kindOfSubtype == reflect.String {
					for i := 0; i < sliceLen; i += 1 {
						var str string = valueOfV.Index(i).String()
						if !utf8.ValidString(str) {
							return "", ErrNotUTF8
						}
						stringSlice = append(stringSlice, escapeAndQuoteString(str))
					}
				} else {
					return "", ErrInvalidSliceValue
				}
				buf.WriteRune('(')
				buf.WriteString(strings.Join(stringSlice, ","))
				buf.WriteRune(')')
			} else {
				return "", ErrInvalidValue
			}

			curVal += 1
		} else {
			return "", ErrArgumentMismatch
		}
	}

	if curVal != maxVals {
		return "", ErrArgumentMismatch
	}

	return buf.String(), nil
}
<|endoftext|>


<commit_before>package ar

import (
	"io"
	"time"
	"strconv"
)

const HEADER_BYTE_SIZE = 68

type slicer []byte

func (sp *slicer) next(n int) (b []byte) {
	s := *sp
	b, *sp = s[0:n], s[n:]
	return
}

type Writer struct {
	w io.Writer
	globalHeader bool
}

type Header struct {
	Name string
	ModTime time.Time
	Uid int
	Gid int
	Mode int64
	Size int64
}

func NewWriter(w io.Writer) *Writer { return &Writer{w: w} }

func (aw *Writer) numeric(b []byte, x int64) {
	s := strconv.FormatInt(x, 10)
	for len(s) < len(b) {
		s = s + " "
	}
	copy(b, []byte(s))
}

func (aw *Writer) octal(b []byte, x int64) {
	s := "100" + strconv.FormatInt(x, 8)
	for len(s) < len(b) {
		s = s + " "
	}
	copy(b, []byte(s))
}

func (aw *Writer) string(b []byte, str string) {
	s := str
	for len(s) < len(b) {
		s = s + " "
	}
	copy(b, []byte(s))
}

func (aw *Writer) Write(b []byte) (n int, err error) {
	n, err = aw.w.Write(b)
	if len(b)%2 == 1 {
		n2, _ := aw.w.Write([]byte{'\n'})
		return n+n2, err
	}

	return
}

func (aw *Writer) WriteHeader(hdr *Header) error {
	header := make([]byte, HEADER_BYTE_SIZE)
	s := slicer(header)

	if !aw.globalHeader {
		aw.string(s.next(8), "!<arch>\n")
		aw.globalHeader = true
	}

	aw.string(s.next(16), hdr.Name)
	aw.numeric(s.next(12), hdr.ModTime.Unix())
	aw.numeric(s.next(6), int64(hdr.Uid))
	aw.numeric(s.next(6), int64(hdr.Gid))
	aw.octal(s.next(8), hdr.Mode)
	aw.numeric(s.next(10), hdr.Size)
	aw.string(s.next(2), "`\n")

	_, err := aw.w.Write(header)

	return err
}<commit_msg>Add comments<commit_after>package ar

import (
	"io"
	"time"
	"strconv"
)

const HEADER_BYTE_SIZE = 68

type slicer []byte

func (sp *slicer) next(n int) (b []byte) {
	s := *sp
	b, *sp = s[0:n], s[n:]
	return
}

// Writer provides sequential writing of an ar archive.
// An ar archive is sequence of header file pairs
// Call WriteHeader to begin writing a new file, then call Write to supply the file's data
//
// Example:
// archive := ar.NewWriter(writer)
// header := new(ar.Header)
// header.Size = 15 // bytes
// if err := archive.WriteHeader(header); err != nil {
// 	return err
// }
// io.Copy(archive, data)
type Writer struct {
	w io.Writer
	globalHeader bool
}

type Header struct {
	Name string
	ModTime time.Time
	Uid int
	Gid int
	Mode int64
	Size int64
}

// Create a new ar writer that writes to w
func NewWriter(w io.Writer) *Writer { return &Writer{w: w} }

func (aw *Writer) numeric(b []byte, x int64) {
	s := strconv.FormatInt(x, 10)
	for len(s) < len(b) {
		s = s + " "
	}
	copy(b, []byte(s))
}

func (aw *Writer) octal(b []byte, x int64) {
	s := "100" + strconv.FormatInt(x, 8)
	for len(s) < len(b) {
		s = s + " "
	}
	copy(b, []byte(s))
}

func (aw *Writer) string(b []byte, str string) {
	s := str
	for len(s) < len(b) {
		s = s + " "
	}
	copy(b, []byte(s))
}

// Writes to the current entry in the ar archive
// Returns ErrWriteTooLong if more than header.Size
// bytes are written after a call to WriteHeader
func (aw *Writer) Write(b []byte) (n int, err error) {
	n, err = aw.w.Write(b)
	if len(b)%2 == 1 {
		n2, _ := aw.w.Write([]byte{'\n'})
		return n+n2, err
	}

	return
}

// Writes the header to the underlying writer and prepares
// to receive the file payload
func (aw *Writer) WriteHeader(hdr *Header) error {
	header := make([]byte, HEADER_BYTE_SIZE)
	s := slicer(header)

	if !aw.globalHeader {
		aw.string(s.next(8), "!<arch>\n")
		aw.globalHeader = true
	}

	aw.string(s.next(16), hdr.Name)
	aw.numeric(s.next(12), hdr.ModTime.Unix())
	aw.numeric(s.next(6), int64(hdr.Uid))
	aw.numeric(s.next(6), int64(hdr.Gid))
	aw.octal(s.next(8), hdr.Mode)
	aw.numeric(s.next(10), hdr.Size)
	aw.string(s.next(2), "`\n")

	_, err := aw.w.Write(header)

	return err
}<|endoftext|>


<commit_before>package x

import (
	"math"
	"math/rand"
	"time"
)

// NewPRN creates a new rand.Rand object with its own seed
func NewPRN() *rand.Rand {
	return rand.New(rand.NewSource(time.Now().UnixNano()))
}

//ArgMax returns the argmax (index) for an array of floats
func ArgMax(A []float64) int {
	max := math.Inf(-1)
	idx := -1
	for i, v := range A {
		if v > max {
			max = v
			idx = i
		}
	}
	return idx
}

//ToInt helper for Observation objects
func ToInt(o ObservationBits) Observation {
	n := 0
	for _, b := range o {
		n <<= 1
		if b {
			n++
		}
	}
	return Observation(n)
}

// Equals checks equality of percepts
func Equals(e, p *Percept) bool {
	return p.R == e.R && p.O == e.O
}

// RLUtility is the utility function for normal reward-based reinforcement learners
func RLUtility(o Observation, r Reward, dfr int) float64 {
	return float64(r)
}

// Log2 for integers to hackishly improve performance in MCTS
func Log2(v uint) int {
	n := -1
	for ; v > 0; n++ {
		v >>= 1
	}
	return n
}
<commit_msg>add entropy util fn<commit_after>package x

import (
	"math"
	"math/rand"
	"time"
)

// NewPRN creates a new rand.Rand object with its own seed
func NewPRN() *rand.Rand {
	return rand.New(rand.NewSource(time.Now().UnixNano()))
}

//ArgMax returns the argmax (index) for an array of floats
func ArgMax(A []float64) int {
	max := math.Inf(-1)
	idx := -1
	for i, v := range A {
		if v > max {
			max = v
			idx = i
		}
	}
	return idx
}

//ToInt helper for Observation objects
func ToInt(o ObservationBits) Observation {
	n := 0
	for _, b := range o {
		n <<= 1
		if b {
			n++
		}
	}
	return Observation(n)
}

// Equals checks equality of percepts
func Equals(e, p *Percept) bool {
	return p.R == e.R && p.O == e.O
}

// RLUtility is the utility function for normal reward-based reinforcement learners
func RLUtility(o Observation, r Reward, dfr int) float64 {
	return float64(r)
}

// Log2 for integers to hackishly improve performance in MCTS
func Log2(v uint) int {
	n := -1
	for ; v > 0; n++ {
		v >>= 1
	}
	return n
}

// Entropy (Shannon)
func Entropy(p []float64) float64 {
	ent := 0.0
	for _, v := range p {
		if v == 0 {
			continue
		}
		ent -= v * math.Log2(v)
	}
	return ent
}
<|endoftext|>


<commit_before>// +build linux

// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
//	http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.

package stats

import (
	"context"
	"fmt"
	"time"

	apitaskstatus "github.com/aws/amazon-ecs-agent/agent/api/task/status"
	"github.com/aws/amazon-ecs-agent/agent/config"
	"github.com/aws/amazon-ecs-agent/agent/ecscni"
	"github.com/aws/amazon-ecs-agent/agent/eni/netlinkwrapper"
	"github.com/aws/amazon-ecs-agent/agent/stats/resolver"
	"github.com/aws/amazon-ecs-agent/agent/utils/nswrapper"
	"github.com/aws/amazon-ecs-agent/agent/utils/retry"
	"github.com/containernetworking/plugins/pkg/ns"

	"github.com/cihub/seelog"
	"github.com/docker/docker/api/types"
	dockerstats "github.com/docker/docker/api/types"
	netlinklib "github.com/vishvananda/netlink"
)

const (
	// linkTypeDevice defines the string that's expected to be the output of
	// netlink.Link.Type() method for netlink.Device type.
	linkTypeDevice = "device"
	linkTypeVlan   = "vlan"
	// encapTypeLoopback defines the string that's set for the link.Attrs.EncapType
	// field for localhost devices. The EncapType field defines the link
	// encapsulation method. For localhost, it's set to "loopback".
	encapTypeLoopback = "loopback"
)

// StatsTask abstracts methods to gather and aggregate network data for a task. Used only for AWSVPC mode.
type StatsTask struct {
	StatsQueue            *Queue
	TaskMetadata          *TaskMetadata
	Ctx                   context.Context
	Cancel                context.CancelFunc
	Resolver              resolver.ContainerMetadataResolver
	nswrapperinterface    nswrapper.NS
	netlinkinterface      netlinkwrapper.NetLink
	metricPublishInterval time.Duration
}

func newStatsTaskContainer(taskARN string, containerPID string, numberOfContainers int,
	resolver resolver.ContainerMetadataResolver, publishInterval time.Duration) (*StatsTask, error) {
	nsAgent := nswrapper.NewNS()
	netlinkclient := netlinkwrapper.New()

	ctx, cancel := context.WithCancel(context.Background())
	return &StatsTask{
		TaskMetadata: &TaskMetadata{
			TaskArn:          taskARN,
			ContainerPID:     containerPID,
			NumberContainers: numberOfContainers,
		},
		Ctx:                   ctx,
		Cancel:                cancel,
		Resolver:              resolver,
		netlinkinterface:      netlinkclient,
		nswrapperinterface:    nsAgent,
		metricPublishInterval: publishInterval,
	}, nil
}

func (task *StatsTask) StartStatsCollection() {
	queueSize := int(config.DefaultContainerMetricsPublishInterval.Seconds() * 4)
	task.StatsQueue = NewQueue(queueSize)
	task.StatsQueue.Reset()
	go task.collect()
}

func (task *StatsTask) StopStatsCollection() {
	task.Cancel()
}

func (taskStat *StatsTask) collect() {
	taskArn := taskStat.TaskMetadata.TaskArn
	backoff := retry.NewExponentialBackoff(time.Second*1, time.Second*10, 0.5, 2)

	for {
		err := taskStat.processStatsStream()
		select {
		case <-taskStat.Ctx.Done():
			seelog.Debugf("Stopping stats collection for taskStat %s", taskArn)
			return
		default:
			if err != nil {
				d := backoff.Duration()
				time.Sleep(d)
				seelog.Debugf("Error querying stats for task %s: %v", taskArn, err)
			}
			// We were disconnected from the stats stream.
			// Check if the task is terminal. If it is, stop collecting metrics.
			terminal, err := taskStat.terminal()
			if err != nil {
				// Error determining if the task is terminal. clean-up anyway.
				seelog.Warnf("Error determining if the task %s is terminal, stopping stats collection: %v",
					taskArn, err)
				taskStat.StopStatsCollection()
			} else if terminal {
				seelog.Infof("Task %s is terminal, stopping stats collection", taskArn)
				taskStat.StopStatsCollection()
			}
		}
	}
}

func (taskStat *StatsTask) processStatsStream() error {
	taskArn := taskStat.TaskMetadata.TaskArn
	awsvpcNetworkStats, errC := taskStat.getAWSVPCNetworkStats(taskStat.TaskMetadata.DeviceName,
		taskStat.TaskMetadata.ContainerPID, taskStat.TaskMetadata.NumberContainers)

	returnError := false
	for {
		select {
		case <-taskStat.Ctx.Done():
			seelog.Info("task context is done")
			return nil
		case err := <-errC:
			seelog.Warnf("Error encountered processing metrics stream from host, this may affect "+
				"cloudwatch metric accuracy: %s", err)
			returnError = true
		case rawStat, ok := <-awsvpcNetworkStats:
			if !ok {
				if returnError {
					return fmt.Errorf("error encountered processing metrics stream from host")
				}
				return nil
			}
			if err := taskStat.StatsQueue.Add(rawStat); err != nil {
				seelog.Warnf("Task [%s]: error converting stats: %v", taskArn, err)
			}
		}

	}
}

func (taskStat *StatsTask) terminal() (bool, error) {
	resolvedTask, err := taskStat.Resolver.ResolveTaskByARN(taskStat.TaskMetadata.TaskArn)
	if err != nil {
		return false, err
	}
	return resolvedTask.GetKnownStatus() == apitaskstatus.TaskStopped, nil
}

func getDevicesList(linkList []netlinklib.Link) []string {
	var deviceNames []string
	for _, link := range linkList {
		if link.Type() != linkTypeDevice && link.Type() != linkTypeVlan {
			// We only care about netlink.Device/netlink.Vlan types. Ignore other link types.
			continue
		}
		if link.Attrs().EncapType == encapTypeLoopback {
			// Ignore localhost
			continue
		}
		deviceNames = append(deviceNames, link.Attrs().Name)
	}
	return deviceNames
}

func (taskStat *StatsTask) populateNIDeviceList(containerPID string) ([]string, error) {
	var err error
	var deviceList []string
	netNSPath := fmt.Sprintf(ecscni.NetnsFormat, containerPID)
	err = taskStat.nswrapperinterface.WithNetNSPath(netNSPath, func(ns.NetNS) error {
		linksInTaskNetNS, linkErr := taskStat.netlinkinterface.LinkList()
		deviceNames := getDevicesList(linksInTaskNetNS)
		deviceList = append(deviceList, deviceNames...)
		return linkErr
	})
	return deviceList, err
}

func linkStatsToDockerStats(netLinkStats *netlinklib.LinkStatistics, numberOfContainers uint64) dockerstats.NetworkStats {
	networkStats := dockerstats.NetworkStats{
		RxBytes:   netLinkStats.RxBytes / numberOfContainers,
		RxPackets: netLinkStats.RxPackets / numberOfContainers,
		RxErrors:  netLinkStats.RxErrors / numberOfContainers,
		RxDropped: netLinkStats.RxDropped / numberOfContainers,
		TxBytes:   netLinkStats.TxBytes / numberOfContainers,
		TxPackets: netLinkStats.TxPackets / numberOfContainers,
		TxErrors:  netLinkStats.TxErrors / numberOfContainers,
		TxDropped: netLinkStats.TxDropped / numberOfContainers,
	}
	return networkStats
}

func (taskStat *StatsTask) getAWSVPCNetworkStats(deviceList []string, containerPID string,
	numberOfContainers int) (<-chan *types.StatsJSON, <-chan error) {

	errC := make(chan error)
	statsC := make(chan *dockerstats.StatsJSON)
	if numberOfContainers > 0 {
		go func() {
			defer close(statsC)
			statPollTicker := time.NewTicker(taskStat.metricPublishInterval)
			defer statPollTicker.Stop()
			for range statPollTicker.C {
				networkStats := make(map[string]dockerstats.NetworkStats, len(deviceList))
				if len(taskStat.TaskMetadata.DeviceName) == 0 {
					var err error
					taskStat.TaskMetadata.DeviceName, err = taskStat.populateNIDeviceList(taskStat.TaskMetadata.ContainerPID)
					if err != nil {
						errC <- err
						return
					}
				}
				for _, device := range deviceList {
					var link netlinklib.Link
					err := taskStat.nswrapperinterface.WithNetNSPath(fmt.Sprintf(ecscni.NetnsFormat, containerPID),
						func(ns.NetNS) error {
							var linkErr error
							if link, linkErr = taskStat.netlinkinterface.LinkByName(device); linkErr != nil {
								return linkErr
							}
							return nil
						})

					if err != nil {
						errC <- err
						return
					}
					netLinkStats := link.Attrs().Statistics
					networkStats[link.Attrs().Name] = linkStatsToDockerStats(netLinkStats, uint64(numberOfContainers))
				}

				dockerStats := &types.StatsJSON{
					Networks: networkStats,
					Stats: types.Stats{
						Read: time.Now(),
					},
				}
				statsC <- dockerStats
			}
		}()
	}

	return statsC, errC
}
<commit_msg>replace device list argument with task stats device<commit_after>// +build linux

// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
//	http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.

package stats

import (
	"context"
	"fmt"
	"time"

	apitaskstatus "github.com/aws/amazon-ecs-agent/agent/api/task/status"
	"github.com/aws/amazon-ecs-agent/agent/config"
	"github.com/aws/amazon-ecs-agent/agent/ecscni"
	"github.com/aws/amazon-ecs-agent/agent/eni/netlinkwrapper"
	"github.com/aws/amazon-ecs-agent/agent/stats/resolver"
	"github.com/aws/amazon-ecs-agent/agent/utils/nswrapper"
	"github.com/aws/amazon-ecs-agent/agent/utils/retry"
	"github.com/containernetworking/plugins/pkg/ns"

	"github.com/cihub/seelog"
	"github.com/docker/docker/api/types"
	dockerstats "github.com/docker/docker/api/types"
	netlinklib "github.com/vishvananda/netlink"
)

const (
	// linkTypeDevice defines the string that's expected to be the output of
	// netlink.Link.Type() method for netlink.Device type.
	linkTypeDevice = "device"
	linkTypeVlan   = "vlan"
	// encapTypeLoopback defines the string that's set for the link.Attrs.EncapType
	// field for localhost devices. The EncapType field defines the link
	// encapsulation method. For localhost, it's set to "loopback".
	encapTypeLoopback = "loopback"
)

// StatsTask abstracts methods to gather and aggregate network data for a task. Used only for AWSVPC mode.
type StatsTask struct {
	StatsQueue            *Queue
	TaskMetadata          *TaskMetadata
	Ctx                   context.Context
	Cancel                context.CancelFunc
	Resolver              resolver.ContainerMetadataResolver
	nswrapperinterface    nswrapper.NS
	netlinkinterface      netlinkwrapper.NetLink
	metricPublishInterval time.Duration
}

func newStatsTaskContainer(taskARN string, containerPID string, numberOfContainers int,
	resolver resolver.ContainerMetadataResolver, publishInterval time.Duration) (*StatsTask, error) {
	nsAgent := nswrapper.NewNS()
	netlinkclient := netlinkwrapper.New()

	ctx, cancel := context.WithCancel(context.Background())
	return &StatsTask{
		TaskMetadata: &TaskMetadata{
			TaskArn:          taskARN,
			ContainerPID:     containerPID,
			NumberContainers: numberOfContainers,
		},
		Ctx:                   ctx,
		Cancel:                cancel,
		Resolver:              resolver,
		netlinkinterface:      netlinkclient,
		nswrapperinterface:    nsAgent,
		metricPublishInterval: publishInterval,
	}, nil
}

func (task *StatsTask) StartStatsCollection() {
	queueSize := int(config.DefaultContainerMetricsPublishInterval.Seconds() * 4)
	task.StatsQueue = NewQueue(queueSize)
	task.StatsQueue.Reset()
	go task.collect()
}

func (task *StatsTask) StopStatsCollection() {
	task.Cancel()
}

func (taskStat *StatsTask) collect() {
	taskArn := taskStat.TaskMetadata.TaskArn
	backoff := retry.NewExponentialBackoff(time.Second*1, time.Second*10, 0.5, 2)

	for {
		err := taskStat.processStatsStream()
		select {
		case <-taskStat.Ctx.Done():
			seelog.Debugf("Stopping stats collection for taskStat %s", taskArn)
			return
		default:
			if err != nil {
				d := backoff.Duration()
				time.Sleep(d)
				seelog.Debugf("Error querying stats for task %s: %v", taskArn, err)
			}
			// We were disconnected from the stats stream.
			// Check if the task is terminal. If it is, stop collecting metrics.
			terminal, err := taskStat.terminal()
			if err != nil {
				// Error determining if the task is terminal. clean-up anyway.
				seelog.Warnf("Error determining if the task %s is terminal, stopping stats collection: %v",
					taskArn, err)
				taskStat.StopStatsCollection()
			} else if terminal {
				seelog.Infof("Task %s is terminal, stopping stats collection", taskArn)
				taskStat.StopStatsCollection()
			}
		}
	}
}

func (taskStat *StatsTask) processStatsStream() error {
	taskArn := taskStat.TaskMetadata.TaskArn
	awsvpcNetworkStats, errC := taskStat.getAWSVPCNetworkStats()

	returnError := false
	for {
		select {
		case <-taskStat.Ctx.Done():
			seelog.Info("task context is done")
			return nil
		case err := <-errC:
			seelog.Warnf("Error encountered processing metrics stream from host, this may affect "+
				"cloudwatch metric accuracy: %s", err)
			returnError = true
		case rawStat, ok := <-awsvpcNetworkStats:
			if !ok {
				if returnError {
					return fmt.Errorf("error encountered processing metrics stream from host")
				}
				return nil
			}
			if err := taskStat.StatsQueue.Add(rawStat); err != nil {
				seelog.Warnf("Task [%s]: error converting stats: %v", taskArn, err)
			}
		}

	}
}

func (taskStat *StatsTask) terminal() (bool, error) {
	resolvedTask, err := taskStat.Resolver.ResolveTaskByARN(taskStat.TaskMetadata.TaskArn)
	if err != nil {
		return false, err
	}
	return resolvedTask.GetKnownStatus() == apitaskstatus.TaskStopped, nil
}

func getDevicesList(linkList []netlinklib.Link) []string {
	var deviceNames []string
	for _, link := range linkList {
		if link.Type() != linkTypeDevice && link.Type() != linkTypeVlan {
			// We only care about netlink.Device/netlink.Vlan types. Ignore other link types.
			continue
		}
		if link.Attrs().EncapType == encapTypeLoopback {
			// Ignore localhost
			continue
		}
		deviceNames = append(deviceNames, link.Attrs().Name)
	}
	return deviceNames
}

func (taskStat *StatsTask) populateNIDeviceList(containerPID string) ([]string, error) {
	var err error
	var deviceList []string
	netNSPath := fmt.Sprintf(ecscni.NetnsFormat, containerPID)
	err = taskStat.nswrapperinterface.WithNetNSPath(netNSPath, func(ns.NetNS) error {
		linksInTaskNetNS, linkErr := taskStat.netlinkinterface.LinkList()
		deviceNames := getDevicesList(linksInTaskNetNS)
		deviceList = append(deviceList, deviceNames...)
		return linkErr
	})
	return deviceList, err
}

func linkStatsToDockerStats(netLinkStats *netlinklib.LinkStatistics, numberOfContainers uint64) dockerstats.NetworkStats {
	networkStats := dockerstats.NetworkStats{
		RxBytes:   netLinkStats.RxBytes / numberOfContainers,
		RxPackets: netLinkStats.RxPackets / numberOfContainers,
		RxErrors:  netLinkStats.RxErrors / numberOfContainers,
		RxDropped: netLinkStats.RxDropped / numberOfContainers,
		TxBytes:   netLinkStats.TxBytes / numberOfContainers,
		TxPackets: netLinkStats.TxPackets / numberOfContainers,
		TxErrors:  netLinkStats.TxErrors / numberOfContainers,
		TxDropped: netLinkStats.TxDropped / numberOfContainers,
	}
	return networkStats
}

func (taskStat *StatsTask) getAWSVPCNetworkStats() (<-chan *types.StatsJSON, <-chan error) {

	errC := make(chan error)
	statsC := make(chan *dockerstats.StatsJSON)
	if taskStat.TaskMetadata.NumberContainers > 0 {
		go func() {
			defer close(statsC)
			statPollTicker := time.NewTicker(taskStat.metricPublishInterval)
			defer statPollTicker.Stop()
			for range statPollTicker.C {
				if len(taskStat.TaskMetadata.DeviceName) == 0 {
					var err error
					taskStat.TaskMetadata.DeviceName, err = taskStat.populateNIDeviceList(taskStat.TaskMetadata.ContainerPID)
					if err != nil {
						errC <- err
						return
					}
				}
				networkStats := make(map[string]dockerstats.NetworkStats, len(taskStat.TaskMetadata.DeviceName))
				for _, device := range taskStat.TaskMetadata.DeviceName {
					var link netlinklib.Link
					err := taskStat.nswrapperinterface.WithNetNSPath(fmt.Sprintf(ecscni.NetnsFormat,
						taskStat.TaskMetadata.ContainerPID),
						func(ns.NetNS) error {
							var linkErr error
							if link, linkErr = taskStat.netlinkinterface.LinkByName(device); linkErr != nil {
								return linkErr
							}
							return nil
						})

					if err != nil {
						errC <- err
						return
					}
					netLinkStats := link.Attrs().Statistics
					networkStats[link.Attrs().Name] = linkStatsToDockerStats(netLinkStats,
						uint64(taskStat.TaskMetadata.NumberContainers))
				}

				dockerStats := &types.StatsJSON{
					Networks: networkStats,
					Stats: types.Stats{
						Read: time.Now(),
					},
				}
				statsC <- dockerStats
			}
		}()
	}

	return statsC, errC
}
<|endoftext|>


<commit_before>package jantar

import (
	"fmt"
	"github.com/tsurai/jantar/context"
	"net/http"
	"reflect"
	"regexp"
	"runtime"
	"strings"
)

type route struct {
	cName   string
	cAction string
	pattern string
	method  string
	handler http.HandlerFunc
}

type rootNode struct {
	get    *pathNode
	post   *pathNode
	delete *pathNode
	put    *pathNode
}

type pathLeaf struct {
	paramNames []string
	route      *route
}

type pathNode struct {
	edges    map[string]*pathNode
	wildcard *pathNode
	leaf     *pathLeaf
}

type router struct {
	namedRoutes map[string]*route
	pathRoot    rootNode
}

// Router functions ----------------------------------------------
func newRouter() *router {
	return &router{namedRoutes: make(map[string]*route), pathRoot: rootNode{newPathNode(), newPathNode(), newPathNode(), newPathNode()}}
}

func (r *router) getMethodPathNode(method string) *pathNode {
	switch strings.ToUpper(method) {
	case "GET":
		return r.pathRoot.get
	case "POST":
		return r.pathRoot.post
	case "PUT":
		return r.pathRoot.put
	case "DELETE":
		return r.pathRoot.delete
	default:
		return r.pathRoot.get
	}
}

func (r *router) findPathLeaf(method string, path string) (*pathLeaf, map[string]string) {
	var variables []string
	node := r.getMethodPathNode(method)

	for _, segment := range splitPath(path) {
		if edge, ok := node.edges[segment]; ok {
			node = edge
		} else {
			if node.wildcard == nil {
				return nil, nil
			}
			variables = append(variables, segment)
			node = node.wildcard
		}
	}

	if node.leaf == nil {
		return nil, nil
	}

	param := make(map[string]string)
	for i, v := range variables {
		param[node.leaf.paramNames[i]] = v
	}

	return node.leaf, param
}

func (r *router) insertPathLeaf(method string, path string) *pathLeaf {
	var paramNames []string
	node := r.getMethodPathNode(method)

	for _, segment := range splitPath(path) {
		if strings.HasPrefix(segment, ":") {
			if node.wildcard == nil {
				node.wildcard = newPathNode()
			}

			paramNames = append(paramNames, segment[1:])
			node = node.wildcard
			continue
		}

		if edge, ok := node.edges[segment]; ok {
			node = edge
		} else {
			node.edges[segment] = newPathNode()
			node = node.edges[segment]
		}
	}

	node.leaf = &pathLeaf{paramNames, nil}
	return node.leaf
}

func (r *router) addRoute(method string, path string, handler interface{}) *route {
	route := newRoute(strings.ToUpper(method), path, handler)

	node := r.insertPathLeaf(method, path)
	node.route = route

	// is route a controller route
	if route.cName != "" {
		// add to named routes with name as controller#action
		r.namedRoutes[strings.ToLower(route.cName+"#"+route.cAction)] = route
	}

	return route
}

func (r *router) searchRoute(req *http.Request) *route {
	if node, params := r.findPathLeaf(req.Method, req.URL.Path); node != nil {
		if len(params) != 0 {
			context.Set(req, "UrlParam", params, true)
		}

		return node.route
	}

	return nil
}

func (r *router) getReverseURL(name string, param []interface{}) string {
	route := r.getNamedRoute(name)
	nParam := len(param)

	if route != nil {
		i := -1
		regex := regexp.MustCompile(":[^/]+")
		url := regex.ReplaceAllStringFunc(route.pattern, func(str string) string {
			i = i + 1
			if i < nParam {
				return fmt.Sprintf("%v", param[i])
			}
			return ""
		})

		return url
	}

	return ""
}

func (r *router) getNamedRoute(name string) *route {
	if route, ok := r.namedRoutes[strings.ToLower(name)]; ok {
		return route
	}

	return nil
}

// Route functions ---------------------------------------------
func newRoute(method string, pattern string, handler interface{}) *route {
	var finalFunc http.HandlerFunc
	cName := ""
	cAction := ""

	if reflect.TypeOf(handler).ConvertibleTo(reflect.TypeOf(http.NotFound)) {
		finalFunc = handler.(func(http.ResponseWriter, *http.Request))
	} else if cType := getControllerType(handler); cType != nil {
		fn := runtime.FuncForPC(reflect.ValueOf(handler).Pointer())
		if fn == nil {
			Log.Warning("failed to add route. Can't fetch controller function")
			return nil
		}

		regex := regexp.MustCompile(".*\\.\\(\\*(.*)\\)\\.(.*)")
		matches := regex.FindStringSubmatch(fn.Name())

		if len(matches) == 3 && matches[0] == fn.Name() {
			cName = matches[1]
			cAction = matches[2]
		}

		finalFunc = func(rw http.ResponseWriter, r *http.Request) {
			c := newController(cType, rw, r, cName, cAction)

			var in []reflect.Value
			in = append(in, reflect.ValueOf(c))

			reflect.ValueOf(handler).Call(in)
		}
	} else {
		Log.Warningd(JLData{"type": reflect.TypeOf(handler)}, "failed to add route. Invalid handler type")
	}

	return &route{cName, cAction, pattern, method, finalFunc}
}

func (r *route) Name(name string) {
	router := context.GetGlobal("Router").(*router)
	router.namedRoutes[strings.ToLower(name)] = r
}

// Helper functions ---------------------------------------------
func newPathNode() *pathNode {
	return &pathNode{edges: make(map[string]*pathNode)}
}

func splitPath(path string) []string {
	segments := strings.Split(path, "/")

	if segments[0] == "" {
		segments = segments[1:]
	}
	return segments
}
<commit_msg>avoid panic in newRoute func<commit_after>package jantar

import (
	"fmt"
	"github.com/tsurai/jantar/context"
	"net/http"
	"reflect"
	"regexp"
	"runtime"
	"strings"
)

type route struct {
	cName   string
	cAction string
	pattern string
	method  string
	handler http.HandlerFunc
}

type rootNode struct {
	get    *pathNode
	post   *pathNode
	delete *pathNode
	put    *pathNode
}

type pathLeaf struct {
	paramNames []string
	route      *route
}

type pathNode struct {
	edges    map[string]*pathNode
	wildcard *pathNode
	leaf     *pathLeaf
}

type router struct {
	namedRoutes map[string]*route
	pathRoot    rootNode
}

// Router functions ----------------------------------------------
func newRouter() *router {
	return &router{namedRoutes: make(map[string]*route), pathRoot: rootNode{newPathNode(), newPathNode(), newPathNode(), newPathNode()}}
}

func (r *router) getMethodPathNode(method string) *pathNode {
	switch strings.ToUpper(method) {
	case "GET":
		return r.pathRoot.get
	case "POST":
		return r.pathRoot.post
	case "PUT":
		return r.pathRoot.put
	case "DELETE":
		return r.pathRoot.delete
	default:
		return r.pathRoot.get
	}
}

func (r *router) findPathLeaf(method string, path string) (*pathLeaf, map[string]string) {
	var variables []string
	node := r.getMethodPathNode(method)

	for _, segment := range splitPath(path) {
		if edge, ok := node.edges[segment]; ok {
			node = edge
		} else {
			if node.wildcard == nil {
				return nil, nil
			}
			variables = append(variables, segment)
			node = node.wildcard
		}
	}

	if node.leaf == nil {
		return nil, nil
	}

	param := make(map[string]string)
	for i, v := range variables {
		param[node.leaf.paramNames[i]] = v
	}

	return node.leaf, param
}

func (r *router) insertPathLeaf(method string, path string) *pathLeaf {
	var paramNames []string
	node := r.getMethodPathNode(method)

	for _, segment := range splitPath(path) {
		if strings.HasPrefix(segment, ":") {
			if node.wildcard == nil {
				node.wildcard = newPathNode()
			}

			paramNames = append(paramNames, segment[1:])
			node = node.wildcard
			continue
		}

		if edge, ok := node.edges[segment]; ok {
			node = edge
		} else {
			node.edges[segment] = newPathNode()
			node = node.edges[segment]
		}
	}

	node.leaf = &pathLeaf{paramNames, nil}
	return node.leaf
}

func (r *router) addRoute(method string, path string, handler interface{}) *route {
	route := newRoute(strings.ToUpper(method), path, handler)

	node := r.insertPathLeaf(method, path)
	node.route = route

	// is route a controller route
	if route.cName != "" {
		// add to named routes with name as controller#action
		r.namedRoutes[strings.ToLower(route.cName+"#"+route.cAction)] = route
	}

	return route
}

func (r *router) searchRoute(req *http.Request) *route {
	if node, params := r.findPathLeaf(req.Method, req.URL.Path); node != nil {
		if len(params) != 0 {
			context.Set(req, "UrlParam", params, true)
		}

		return node.route
	}

	return nil
}

func (r *router) getReverseURL(name string, param []interface{}) string {
	route := r.getNamedRoute(name)
	nParam := len(param)

	if route != nil {
		i := -1
		regex := regexp.MustCompile(":[^/]+")
		url := regex.ReplaceAllStringFunc(route.pattern, func(str string) string {
			i = i + 1
			if i < nParam {
				return fmt.Sprintf("%v", param[i])
			}
			return ""
		})

		return url
	}

	return ""
}

func (r *router) getNamedRoute(name string) *route {
	if route, ok := r.namedRoutes[strings.ToLower(name)]; ok {
		return route
	}

	return nil
}

// Route functions ---------------------------------------------
func newRoute(method string, pattern string, handler interface{}) *route {
	var finalFunc http.HandlerFunc
	cName := ""
	cAction := ""

	if reflect.TypeOf(handler) == reflect.TypeOf(http.NotFound) {
		finalFunc = handler.(func(http.ResponseWriter, *http.Request))
	} else if cType := getControllerType(handler); cType != nil {
		fn := runtime.FuncForPC(reflect.ValueOf(handler).Pointer())
		if fn == nil {
			Log.Warning("failed to add route. Can't fetch controller function")
			return nil
		}

		regex := regexp.MustCompile(".*\\.\\(\\*(.*)\\)\\.(.*)")
		matches := regex.FindStringSubmatch(fn.Name())

		if len(matches) == 3 && matches[0] == fn.Name() {
			cName = matches[1]
			cAction = matches[2]
		}

		finalFunc = func(rw http.ResponseWriter, r *http.Request) {
			c := newController(cType, rw, r, cName, cAction)

			var in []reflect.Value
			in = append(in, reflect.ValueOf(c))

			reflect.ValueOf(handler).Call(in)
		}
	} else {
		Log.Warningd(JLData{"type": reflect.TypeOf(handler), "wanted": reflect.TypeOf(http.NotFound)}, "failed to add route. Invalid handler type")
	}

	return &route{cName, cAction, pattern, method, finalFunc}
}

func (r *route) Name(name string) {
	router := context.GetGlobal("Router").(*router)
	router.namedRoutes[strings.ToLower(name)] = r
}

// Helper functions ---------------------------------------------
func newPathNode() *pathNode {
	return &pathNode{edges: make(map[string]*pathNode)}
}

func splitPath(path string) []string {
	segments := strings.Split(path, "/")

	if segments[0] == "" {
		segments = segments[1:]
	}
	return segments
}
<|endoftext|>


<commit_before>package turbo

import (
	"encoding/json"
	"errors"
	"github.com/gorilla/mux"
	"log"
	"net/http"
	"reflect"
	"strconv"
	"strings"
)

type switcher func(methodName string, resp http.ResponseWriter, req *http.Request) (interface{}, error)

var switcherFunc switcher

func router(s switcher) *mux.Router {
	switcherFunc = s
	r := mux.NewRouter()
	for _, v := range urlServiceMaps {
		httpMethods := strings.Split(v[0], ",")
		path := v[1]
		methodName := v[2]
		r.HandleFunc(path, handler(methodName)).Methods(httpMethods...)
	}
	return r
}

var handler = func(methodName string) func(http.ResponseWriter, *http.Request) {
	return func(resp http.ResponseWriter, req *http.Request) {
		ParseRequestForm(req)
		interceptors := getInterceptors(req)
		// TODO !!! if N doBefore() run, then N doAfter should run too
		req, err := doBefore(interceptors, resp, req)
		if err != nil {
			log.Println(err.Error())
			return
		}
		skipSwitch := doHijackerPreprocessor(resp, req)
		if !skipSwitch {
			serviceResp, err := switcherFunc(methodName, resp, req)
			if err == nil {
				doPostprocessor(resp, req, serviceResp)
			} else {
				log.Println(err.Error())
				// do not 'return' here, this is not a bug
			}
		}
		err = doAfter(interceptors, resp, req)
		if err != nil {
			log.Println(err.Error())
			return
		}
	}
}

func getInterceptors(req *http.Request) []Interceptor {
	interceptors := Interceptors(req)
	if len(interceptors) == 0 {
		interceptors = CommonInterceptors()
	}
	return interceptors
}

func doBefore(interceptors []Interceptor, resp http.ResponseWriter, req *http.Request) (request *http.Request, err error) {
	for _, i := range interceptors {
		req, err = i.Before(resp, req)
		if err != nil {
			log.Println("error in interceptor!" + err.Error())
			return nil, err
		}
	}
	return req, nil
}

func doHijackerPreprocessor(resp http.ResponseWriter, req *http.Request) bool {
	pre := Preprocessor(req)
	if hijack := Hijacker(req); hijack != nil {
		if pre != nil {
			log.Printf("Warning: PreProcessor ignored, URL: %s", req.URL.String())
		}
		hijack(resp, req)
		return true
	} else if pre != nil {
		if err := pre(resp, req); err != nil {
			log.Println(err.Error())
			return true
		}
	}
	return false
}

func doPostprocessor(resp http.ResponseWriter, req *http.Request, serviceResponse interface{}) {
	// 1, run postprocessor, if any
	post := Postprocessor(req)
	if post != nil {
		post(resp, req, serviceResponse)
		return
	}

	// 2, parse serviceResponse with registered struct
	//if user defined struct registered {
	// TODO user can define a struct, which defines how data is mapped
	// from response to this struct, and how this struct is parsed into xml/json
	// return
	//}

	//3, return as json
	jsonBytes, err := json.Marshal(serviceResponse)
	if err != nil {
		log.Println(err.Error())
	}
	resp.Write(jsonBytes)
}

func doAfter(interceptors []Interceptor, resp http.ResponseWriter, req *http.Request) (err error) {
	l := len(interceptors)
	for i := l - 1; i >= 0; i-- {
		req, err = interceptors[i].After(resp, req)
		if err != nil {
			log.Println("error in interceptor!")
			return err
		}
	}
	return nil
}

// SetValue sets v to fieldValue according to fieldValue's Kind
func SetValue(fieldValue reflect.Value, v string) error {
	switch k := fieldValue.Kind(); k {
	case reflect.Int,
		reflect.Int8,
		reflect.Int16,
		reflect.Int32,
		reflect.Int64:
		i, err := strconv.ParseInt(v, 10, 64)
		if err != nil {
			return errors.New("error int")
		}
		fieldValue.SetInt(i)
	case reflect.String:
		fieldValue.SetString(v)
	case reflect.Bool:
		b, err := strconv.ParseBool(v)
		if err != nil {
			return errors.New("error bool")
		}
		fieldValue.SetBool(b)
	case reflect.Float32, reflect.Float64:
		f, err := strconv.ParseFloat(v, 64)
		if err != nil {
			return errors.New("error float")
		}
		fieldValue.SetFloat(f)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		u, err := strconv.ParseUint(v, 10, 64)
		if err != nil {
			return errors.New("error uint")
		}
		fieldValue.SetUint(u)
	default:
		return errors.New("not supported kind[" + k.String() + "]")
	}
	return nil
}

// ReflectValue returns a reflect.Value with v according to fieldValue's Kind
func ReflectValue(fieldValue reflect.Value, v string) (value reflect.Value, err error) {
	switch k := fieldValue.Kind(); k {
	case reflect.Int16:
		var i int64
		if v == "" {
			i = 0
		} else {
			i, err = strconv.ParseInt(v, 10, 16)
			if err != nil {
				return reflect.ValueOf(i), errors.New("error int")
			}
		}
		return reflect.ValueOf(int16(i)), nil
	case reflect.Int32:
		var i int64
		if v == "" {
			i = 0
		} else {
			i, err = strconv.ParseInt(v, 10, 32)
			if err != nil {
				return reflect.ValueOf(i), errors.New("error int")
			}
		}
		return reflect.ValueOf(int32(i)), nil
	case reflect.Int64:
		var i int64
		if v == "" {
			i = 0
		} else {
			i, err = strconv.ParseInt(v, 10, 64)
			if err != nil {
				return reflect.ValueOf(i), errors.New("error int")
			}
		}
		return reflect.ValueOf(int64(i)), nil
	case reflect.String:
		return reflect.ValueOf(v), nil
	case reflect.Bool:
		var b bool
		if v == "" {
			b = false
		} else {
			b, err = strconv.ParseBool(v)
			if err != nil {
				return reflect.ValueOf(b), errors.New("error bool")
			}
		}
		return reflect.ValueOf(bool(b)), nil
	case reflect.Float32:
		var f float64
		if v == "" {
			f = 0
		} else {
			f, err = strconv.ParseFloat(v, 64)
			if err != nil {
				return reflect.ValueOf(f), errors.New("error float")
			}
		}
		return reflect.ValueOf(float32(f)), nil
	case reflect.Float64:
		var f float64
		if v == "" {
			f = 0
		} else {
			f, err = strconv.ParseFloat(v, 64)
			if err != nil {
				return reflect.ValueOf(f), errors.New("error float")
			}
		}
		return reflect.ValueOf(float64(f)), nil
		//case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		//	var u uint64
		//	if v == "" {
		//		u = 0
		//	} else {
		//		u, err := strconv.ParseUint(v, 10, 64)
		//		if err != nil {
		//			return reflect.ValueOf(u), errors.New("error uint")
		//		}
		//	}
		//	return reflect.ValueOf(u), nil
	default:
		return reflect.ValueOf(0), errors.New("not supported kind[" + k.String() + "]")
	}
}

//BuildStruct finds values from request, and set them to struct fields recursively
func BuildStruct(theType reflect.Type, theValue reflect.Value, req *http.Request) error {
	fieldNum := theType.NumField()
	for i := 0; i < fieldNum; i++ {
		fieldName := theType.Field(i).Name
		fieldValue := theValue.FieldByName(fieldName)
		if fieldValue.Kind() == reflect.Ptr && fieldValue.Type().Elem().Kind() == reflect.Struct {
			convertor := MessageFieldConvertor(fieldValue.Type().Elem())
			if convertor != nil {
				fieldValue.Set(convertor(req))
				continue
			}
			err := BuildStruct(fieldValue.Type().Elem(), fieldValue.Elem(), req)
			if err != nil {
				return err
			}
			continue
		}
		v, ok := findValue(fieldName, req)
		if !ok {
			continue
		}
		err := SetValue(fieldValue, v)
		if err != nil {
			return err
		}
	}
	return nil
}

func findValue(fieldName string, req *http.Request) (string, bool) {
	snakeCaseName := ToSnakeCase(fieldName)
	v, ok := req.Form[snakeCaseName]
	if ok && len(v) > 0 {
		return v[0], true
	}
	ctxValue := req.Context().Value(fieldName)
	if ctxValue != nil {
		return ctxValue.(string), true
	}
	ctxValue = req.Context().Value(snakeCaseName)
	if ctxValue != nil {
		return ctxValue.(string), true
	}
	return "", false
}

// MakeParams returns a reflect.Value slice for grpc request
func MakeParams(req *http.Request, requestValue reflect.Value) []reflect.Value {
	params := make([]reflect.Value, 2)
	params[0] = reflect.ValueOf(req.Context())
	params[1] = requestValue
	return params
}

// ParseResult checks if error is nil, will returns an error if any
func ParseResult(result []reflect.Value) (serviceResponse interface{}, err error) {
	if result[1].Interface() == nil {
		return result[0].Interface(), nil
	}
	return nil, result[1].Interface().(error)
}

// BuildArgs returns a list of reflect.Value for thrift request
func BuildArgs(argsType reflect.Type, argsValue reflect.Value, req *http.Request, buildStructArg func(typeName string, req *http.Request) (v reflect.Value, err error)) ([]reflect.Value, error) {
	fieldNum := argsType.NumField()
	params := make([]reflect.Value, fieldNum)
	for i := 0; i < fieldNum; i++ {
		field := argsType.Field(i)
		fieldName := field.Name
		valueType := argsValue.FieldByName(fieldName).Type()
		if field.Type.Kind() == reflect.Ptr && valueType.Elem().Kind() == reflect.Struct {
			convertor := MessageFieldConvertor(valueType.Elem())
			if convertor != nil {
				params[i] = convertor(req)
				continue
			}
			structName := valueType.Elem().Name()
			v, err := buildStructArg(structName, req)
			if err != nil {
				return nil, err
			}
			params[i] = v
			continue
		}
		v, ok := findValue(fieldName, req)
		if !ok {
			continue
		}
		value, err := ReflectValue(argsValue.FieldByName(fieldName), v)
		if err != nil {
			return nil, err
		}
		params[i] = value
	}
	return params, nil
}
<commit_msg>fix: use default value if key not shown<commit_after>package turbo

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gorilla/mux"
	"log"
	"net/http"
	"reflect"
	"strconv"
	"strings"
)

type switcher func(methodName string, resp http.ResponseWriter, req *http.Request) (interface{}, error)

var switcherFunc switcher

func router(s switcher) *mux.Router {
	switcherFunc = s
	r := mux.NewRouter()
	for _, v := range urlServiceMaps {
		httpMethods := strings.Split(v[0], ",")
		path := v[1]
		methodName := v[2]
		r.HandleFunc(path, handler(methodName)).Methods(httpMethods...)
	}
	return r
}

var handler = func(methodName string) func(http.ResponseWriter, *http.Request) {
	return func(resp http.ResponseWriter, req *http.Request) {
		ParseRequestForm(req)
		interceptors := getInterceptors(req)
		// TODO !!! if N doBefore() run, then N doAfter should run too
		req, err := doBefore(interceptors, resp, req)
		if err != nil {
			log.Println(err.Error())
			return
		}
		skipSwitch := doHijackerPreprocessor(resp, req)
		if !skipSwitch {
			serviceResp, err := switcherFunc(methodName, resp, req)
			if err == nil {
				doPostprocessor(resp, req, serviceResp)
			} else {
				log.Println(err.Error())
				// do not 'return' here, this is not a bug
			}
		}
		err = doAfter(interceptors, resp, req)
		if err != nil {
			log.Println(err.Error())
			return
		}
	}
}

func getInterceptors(req *http.Request) []Interceptor {
	interceptors := Interceptors(req)
	if len(interceptors) == 0 {
		interceptors = CommonInterceptors()
	}
	return interceptors
}

func doBefore(interceptors []Interceptor, resp http.ResponseWriter, req *http.Request) (request *http.Request, err error) {
	for _, i := range interceptors {
		req, err = i.Before(resp, req)
		if err != nil {
			log.Println("error in interceptor!" + err.Error())
			return nil, err
		}
	}
	return req, nil
}

func doHijackerPreprocessor(resp http.ResponseWriter, req *http.Request) bool {
	pre := Preprocessor(req)
	if hijack := Hijacker(req); hijack != nil {
		if pre != nil {
			log.Printf("Warning: PreProcessor ignored, URL: %s", req.URL.String())
		}
		hijack(resp, req)
		return true
	} else if pre != nil {
		if err := pre(resp, req); err != nil {
			log.Println(err.Error())
			return true
		}
	}
	return false
}

func doPostprocessor(resp http.ResponseWriter, req *http.Request, serviceResponse interface{}) {
	// 1, run postprocessor, if any
	post := Postprocessor(req)
	if post != nil {
		post(resp, req, serviceResponse)
		return
	}

	// 2, parse serviceResponse with registered struct
	//if user defined struct registered {
	// TODO user can define a struct, which defines how data is mapped
	// from response to this struct, and how this struct is parsed into xml/json
	// return
	//}

	//3, return as json
	jsonBytes, err := json.Marshal(serviceResponse)
	if err != nil {
		log.Println(err.Error())
	}
	resp.Write(jsonBytes)
}

func doAfter(interceptors []Interceptor, resp http.ResponseWriter, req *http.Request) (err error) {
	l := len(interceptors)
	for i := l - 1; i >= 0; i-- {
		req, err = interceptors[i].After(resp, req)
		if err != nil {
			log.Println("error in interceptor!")
			return err
		}
	}
	return nil
}

// SetValue sets v to fieldValue according to fieldValue's Kind
func SetValue(fieldValue reflect.Value, v string) error {
	switch k := fieldValue.Kind(); k {
	case reflect.Int,
		reflect.Int8,
		reflect.Int16,
		reflect.Int32,
		reflect.Int64:
		i, err := strconv.ParseInt(v, 10, 64)
		if err != nil {
			return errors.New("error int")
		}
		fieldValue.SetInt(i)
	case reflect.String:
		fieldValue.SetString(v)
	case reflect.Bool:
		b, err := strconv.ParseBool(v)
		if err != nil {
			return errors.New("error bool")
		}
		fieldValue.SetBool(b)
	case reflect.Float32, reflect.Float64:
		f, err := strconv.ParseFloat(v, 64)
		if err != nil {
			return errors.New("error float")
		}
		fieldValue.SetFloat(f)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		u, err := strconv.ParseUint(v, 10, 64)
		if err != nil {
			return errors.New("error uint")
		}
		fieldValue.SetUint(u)
	default:
		return errors.New("not supported kind[" + k.String() + "]")
	}
	return nil
}

// ReflectValue returns a reflect.Value with v according to fieldValue's Kind
func ReflectValue(fieldValue reflect.Value, v string) (reflect.Value, error) {
	switch k := fieldValue.Kind(); k {
	case reflect.Int16:
		i, err := strconv.ParseInt(v, 10, 16)
		if err != nil {
			return reflect.ValueOf(int16(0)), err
		}
		return reflect.ValueOf(int16(i)), nil
	case reflect.Int32:
		i, err := strconv.ParseInt(v, 10, 32)
		if err != nil {
			return reflect.ValueOf(int32(0)), err
		}
		return reflect.ValueOf(int32(i)), nil
	case reflect.Int64:
		i, err := strconv.ParseInt(v, 10, 64)
		if err != nil {
			return reflect.ValueOf(int64(0)), err
		}
		return reflect.ValueOf(int64(i)), nil
	case reflect.String:
		return reflect.ValueOf(v), nil
	case reflect.Bool:
		b, err := strconv.ParseBool(v)
		if err != nil {
			return reflect.ValueOf(false), err
		}
		return reflect.ValueOf(bool(b)), nil
	case reflect.Float32:
		f, err := strconv.ParseFloat(v, 32)
		if err != nil {
			return reflect.ValueOf(0), errors.New("error float")
		}
		return reflect.ValueOf(float32(f)), nil
	case reflect.Float64:
		f, err := strconv.ParseFloat(v, 64)
		if err != nil {
			return reflect.ValueOf(0), errors.New("error float")
		}
		return reflect.ValueOf(float64(f)), nil
		//case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		//	var u uint64
		//	if v == "" {
		//		u = 0
		//	} else {
		//		u, err := strconv.ParseUint(v, 10, 64)
		//		if err != nil {
		//			return reflect.ValueOf(u), errors.New("error uint")
		//		}
		//	}
		//	return reflect.ValueOf(u), nil
	default:
		return reflect.ValueOf(0), errors.New("not supported kind[" + k.String() + "]")
	}
}

//BuildStruct finds values from request, and set them to struct fields recursively
func BuildStruct(theType reflect.Type, theValue reflect.Value, req *http.Request) error {
	fieldNum := theType.NumField()
	for i := 0; i < fieldNum; i++ {
		fieldName := theType.Field(i).Name
		fieldValue := theValue.FieldByName(fieldName)
		if fieldValue.Kind() == reflect.Ptr && fieldValue.Type().Elem().Kind() == reflect.Struct {
			convertor := MessageFieldConvertor(fieldValue.Type().Elem())
			if convertor != nil {
				fieldValue.Set(convertor(req))
				continue
			}
			err := BuildStruct(fieldValue.Type().Elem(), fieldValue.Elem(), req)
			if err != nil {
				return err
			}
			continue
		}
		v, ok := findValue(fieldName, req)
		if !ok {
			continue
		}
		err := SetValue(fieldValue, v)
		if err != nil {
			return err
		}
	}
	return nil
}

func findValue(fieldName string, req *http.Request) (string, bool) {
	snakeCaseName := ToSnakeCase(fieldName)
	v, ok := req.Form[snakeCaseName]
	if ok && len(v) > 0 {
		return v[0], true
	}
	ctxValue := req.Context().Value(fieldName)
	if ctxValue != nil {
		return ctxValue.(string), true
	}
	ctxValue = req.Context().Value(snakeCaseName)
	if ctxValue != nil {
		return ctxValue.(string), true
	}
	return "", false
}

// MakeParams returns a reflect.Value slice for grpc request
func MakeParams(req *http.Request, requestValue reflect.Value) []reflect.Value {
	params := make([]reflect.Value, 2)
	params[0] = reflect.ValueOf(req.Context())
	params[1] = requestValue
	return params
}

// ParseResult checks if error is nil, will returns an error if any
func ParseResult(result []reflect.Value) (serviceResponse interface{}, err error) {
	if result[1].Interface() == nil {
		return result[0].Interface(), nil
	}
	return nil, result[1].Interface().(error)
}

// BuildArgs returns a list of reflect.Value for thrift request
func BuildArgs(argsType reflect.Type, argsValue reflect.Value, req *http.Request, buildStructArg func(typeName string, req *http.Request) (v reflect.Value, err error)) ([]reflect.Value, error) {
	fieldNum := argsType.NumField()
	params := make([]reflect.Value, fieldNum)
	for i := 0; i < fieldNum; i++ {
		field := argsType.Field(i)
		fieldName := field.Name
		valueType := argsValue.FieldByName(fieldName).Type()
		if field.Type.Kind() == reflect.Ptr && valueType.Elem().Kind() == reflect.Struct {
			convertor := MessageFieldConvertor(valueType.Elem())
			if convertor != nil {
				params[i] = convertor(req)
				continue
			}
			structName := valueType.Elem().Name()
			v, err := buildStructArg(structName, req)
			if err != nil {
				return nil, err
			}
			params[i] = v
			continue
		}
		v, ok := findValue(fieldName, req)
		if !ok {
			fmt.Println("[info]value not found! key[" + fieldName + "], use default value[" + v + "]")
		}
		value, err := ReflectValue(argsValue.FieldByName(fieldName), v)
		if err != nil {
			fmt.Println("[info]using default value, error: " + err.Error())
		}
		params[i] = value
	}
	return params, nil
}
<|endoftext|>


<commit_before>// Efficient URL routing using a Trie data structure.
//
// This Package implements a URL Router, but instead of using the usual
// "evaluate all the routes and return the first regexp that matches" strategy,
// it uses a Trie data structure to perform the routing. This is more efficient,
// and scales better for a large number of routes.
// It supports the :param and *splat placeholders in the route strings.
//
// Example:
//	router := urlrouter.Router{
//		Routes: []urlrouter.Route{
//			urlrouter.Route{
//				PathExp: "/resources/:id",
//				Dest:    "one_resource",
//			},
//			urlrouter.Route{
//				PathExp: "/resources",
//				Dest:    "all_resources",
//			},
//		},
//	}
//
//	err := router.Start()
//	if err != nil {
//		panic(err)
//	}
//
//	input := "http://example.org/resources/123"
//	route, params, err := router.FindRoute(input)
//	if err != nil {
//		panic(err)
//	}
//	fmt.Print(route.Dest)  // one_resource
//	fmt.Print(params["id"])  // 123
//
// (Blog Post: http://blog.ant0ine.com/typepad/2013/02/better-url-routing-golang-1.html)
package urlrouter

import (
	"errors"
	"github.com/ant0ine/go-urlrouter/trie"
	"net/url"
)

// TODO
// support for http method routing ?
// support for #param placeholder ?
// replace map[string]string by a PathParams object for more flexibility, and support for multiple param with the same names, eg: /users/:id/blogs/:id ?

type Route struct {
	// A string defining the route, like "/resource/:id.json".
	// Placeholders supported are:
	// :param that matches any char to the first '/' or '.'
	// *splat that matches everything to the end of the string
	PathExp string
	// Can be anything useful to point to the code to run for this route.
	Dest interface{}
}

type Router struct {
	// list of Routes, the order matters, if multiple Routes match, the first defined will be used.
	Routes                 []Route
	disableTrieCompression bool
	index                  map[*Route]int
	trie                   *trie.Trie
}

// This validates the Routes and prepares the Trie data structure.
// It must be called once the Routes are defined and before trying to find Routes.
func (self *Router) Start() error {

	self.trie = trie.New()
	self.index = map[*Route]int{}
	unique := map[string]bool{}

	for i, _ := range self.Routes {
		// pointer to the Route
		route := &self.Routes[i]
		// unique
		if unique[route.PathExp] == true {
			return errors.New("duplicated PathExp")
		}
		unique[route.PathExp] = true
		// index
		self.index[route] = i
		// insert in the Trie
		err := self.trie.AddRoute(route.PathExp, route)
		if err != nil {
			return err
		}
	}

	if self.disableTrieCompression == false {
		self.trie.Compress()
	}

	// TODO validation of the PathExp ? start with a /
	// TODO url encoding

	return nil
}

// Return the first matching Route and the corresponding parameters for a given URL object.
func (self *Router) FindRouteFromURL(urlObj *url.URL) (*Route, map[string]string) {

	// lookup the routes in the Trie
	// TODO verify url encoding
	matches := self.trie.FindRoutes(urlObj.Path)

	// only return the first Route that matches
	minIndex := -1
	matchesByIndex := map[int]*trie.Match{}

	for _, match := range matches {
		route := match.Route.(*Route)
		routeIndex := self.index[route]
		matchesByIndex[routeIndex] = match
		if minIndex == -1 || routeIndex < minIndex {
			minIndex = routeIndex
		}
	}

	if minIndex == -1 {
		// no route found
		return nil, nil
	}

	// and the corresponding params
	match := matchesByIndex[minIndex]

	return match.Route.(*Route), match.Params
}

// Parse the url string (complete or just the path) and return the first matching Route and the corresponding parameters.
func (self *Router) FindRoute(urlStr string) (*Route, map[string]string, error) {

	// parse the url
	urlObj, err := url.Parse(urlStr)
	if err != nil {
		return nil, nil, err
	}

	route, params := self.FindRouteFromURL(urlObj)
	return route, params, nil
}
<commit_msg>implemented on the go-json-rest side.<commit_after>// Efficient URL routing using a Trie data structure.
//
// This Package implements a URL Router, but instead of using the usual
// "evaluate all the routes and return the first regexp that matches" strategy,
// it uses a Trie data structure to perform the routing. This is more efficient,
// and scales better for a large number of routes.
// It supports the :param and *splat placeholders in the route strings.
//
// Example:
//	router := urlrouter.Router{
//		Routes: []urlrouter.Route{
//			urlrouter.Route{
//				PathExp: "/resources/:id",
//				Dest:    "one_resource",
//			},
//			urlrouter.Route{
//				PathExp: "/resources",
//				Dest:    "all_resources",
//			},
//		},
//	}
//
//	err := router.Start()
//	if err != nil {
//		panic(err)
//	}
//
//	input := "http://example.org/resources/123"
//	route, params, err := router.FindRoute(input)
//	if err != nil {
//		panic(err)
//	}
//	fmt.Print(route.Dest)  // one_resource
//	fmt.Print(params["id"])  // 123
//
// (Blog Post: http://blog.ant0ine.com/typepad/2013/02/better-url-routing-golang-1.html)
package urlrouter

import (
	"errors"
	"github.com/ant0ine/go-urlrouter/trie"
	"net/url"
)

// TODO
// support for #param placeholder ?
// replace map[string]string by a PathParams object for more flexibility, and support for multiple param with the same names, eg: /users/:id/blogs/:id ?

type Route struct {
	// A string defining the route, like "/resource/:id.json".
	// Placeholders supported are:
	// :param that matches any char to the first '/' or '.'
	// *splat that matches everything to the end of the string
	PathExp string
	// Can be anything useful to point to the code to run for this route.
	Dest interface{}
}

type Router struct {
	// list of Routes, the order matters, if multiple Routes match, the first defined will be used.
	Routes                 []Route
	disableTrieCompression bool
	index                  map[*Route]int
	trie                   *trie.Trie
}

// This validates the Routes and prepares the Trie data structure.
// It must be called once the Routes are defined and before trying to find Routes.
func (self *Router) Start() error {

	self.trie = trie.New()
	self.index = map[*Route]int{}
	unique := map[string]bool{}

	for i, _ := range self.Routes {
		// pointer to the Route
		route := &self.Routes[i]
		// unique
		if unique[route.PathExp] == true {
			return errors.New("duplicated PathExp")
		}
		unique[route.PathExp] = true
		// index
		self.index[route] = i
		// insert in the Trie
		err := self.trie.AddRoute(route.PathExp, route)
		if err != nil {
			return err
		}
	}

	if self.disableTrieCompression == false {
		self.trie.Compress()
	}

	// TODO validation of the PathExp ? start with a /
	// TODO url encoding

	return nil
}

// Return the first matching Route and the corresponding parameters for a given URL object.
func (self *Router) FindRouteFromURL(urlObj *url.URL) (*Route, map[string]string) {

	// lookup the routes in the Trie
	// TODO verify url encoding
	matches := self.trie.FindRoutes(urlObj.Path)

	// only return the first Route that matches
	minIndex := -1
	matchesByIndex := map[int]*trie.Match{}

	for _, match := range matches {
		route := match.Route.(*Route)
		routeIndex := self.index[route]
		matchesByIndex[routeIndex] = match
		if minIndex == -1 || routeIndex < minIndex {
			minIndex = routeIndex
		}
	}

	if minIndex == -1 {
		// no route found
		return nil, nil
	}

	// and the corresponding params
	match := matchesByIndex[minIndex]

	return match.Route.(*Route), match.Params
}

// Parse the url string (complete or just the path) and return the first matching Route and the corresponding parameters.
func (self *Router) FindRoute(urlStr string) (*Route, map[string]string, error) {

	// parse the url
	urlObj, err := url.Parse(urlStr)
	if err != nil {
		return nil, nil, err
	}

	route, params := self.FindRouteFromURL(urlObj)
	return route, params, nil
}
<|endoftext|>


<commit_before>package deliver

import (
	"net/http"
	"fmt"
)

type Router struct {
	// Slice of routes
	routes		[]*Route
	// Slice of global middleware
	middleware	[]*Middleware
}

// Initialize new router.
func NewRouter() *Router {
	return &Router{
		routes: []*Route{},
	}
}

// Add new route with HandlerFunc handler.
func (r *Router) Route(path string, handler HandlerFunc) *Route {
	route := NewRoute(path, handler)
	r.routes = append(r.routes, route)
	return route
}

// Add new route with http.HandlerFunc handler.
func (r *Router) RouteHandler(path string, handler http.HandlerFunc) *Route {
	route := NewRouteHandler(path, handler)
	r.routes = append(r.routes, route)
	return route
}

// Add new GET route.
func (r *Router) GET(path string, handler HandlerFunc) *Route {
	return r.Route(path, handler).GET()
}

// Add new POST route.
func (r *Router) POST(path string, handler HandlerFunc) *Route {
	return r.Route(path, handler).POST()
}

// Add new PUT route.
func (r *Router) PUT(path string, handler HandlerFunc) *Route {
	return r.Route(path, handler).PUT()
}

// Add new DELETE route.
func (r *Router) DELETE(path string, handler HandlerFunc) *Route {
	return r.Route(path, handler).DELETE()
}

func (r *Router) Use(handler HandlerNext) *Router {
	r.middleware = append(r.middleware, NewMiddleware(handler))
	return r
}

func (r *Router) UseHandler(handler http.Handler) *Router {
	r.middleware = append(r.middleware, NewMiddlewareHandler(handler))
	return r
}

func (r *Router) UseHandlerNext(handler HttpHandlerNext) *Router {
	r.middleware = append(r.middleware, NewMiddlewareHandlerNext(handler))
	return r
}

// ServeHTTP makes the router implement the http.Handler interface.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {

	// Initialize request specific context
	context := &Context{}

	// Collect params and splats
	params := map[string]string{}
	splats := []string{}

	// Store matched route
	var routeMatch *Route

	fmt.Println(req.Proto, req.Method, req.URL)

	// Loop through available routes for a match
	for _, route := range r.routes {
		if matches := route.Match(req); matches != nil && len(matches) > 0 {

			// Collect matches to params and splats
			for i, n := range matches[1:] {
				if route.keys[i] != "" {
					// Found a key, append to params
					params[route.keys[i]] = n
				} else {
					// No key found, append to splats
					splats = append(splats, n)
				}
			}

			// Match found, store route and break the loop
			routeMatch = route
			break
		}
	}

	response := NewResponse(w)
	request := NewRequest(req, context, params, splats)

	// Handle global middleware
	r.handleMiddleware(response, request, r.middleware, func() {
		// Global middleware OK, proceed to route specific middleware
		if routeMatch != nil {
			// Handle route specific middleware
			r.handleMiddleware(response, request, routeMatch.middleware, func() {
				// Finally call the route handler
				routeMatch.handler(response, request)
			})
		} else {
			// Default 404 handler
			http.NotFound(w, req)
		}
	})
}

func (r *Router) handleMiddleware(res Response, req *Request, middleware []*Middleware, proceed func()) {
	index := 0

	var next func()
	next = func() {
		if len(middleware) == 0 || index > len(middleware) - 1 {
			// No middleware available, proceed
			proceed()
			return
		}

		handler := middleware[index].handler
		index += 1

		// Call middleware
		handler.ServeHTTP(res, req, next)
	}

	// Loop through available middleware
	next()
}<commit_msg>Combined global and local middleware handling.<commit_after>package deliver

import (
	"net/http"
	"fmt"
)

type Router struct {
	// Slice of routes
	routes		[]*Route
	// Slice of global middleware
	middleware	[]*Middleware
}

// Initialize new router.
func NewRouter() *Router {
	return &Router{
		routes: []*Route{},
	}
}

// Add new route with HandlerFunc handler.
func (r *Router) Route(path string, handler HandlerFunc) *Route {
	route := NewRoute(path, handler)
	r.routes = append(r.routes, route)
	return route
}

// Add new route with http.HandlerFunc handler.
func (r *Router) RouteHandler(path string, handler http.HandlerFunc) *Route {
	route := NewRouteHandler(path, handler)
	r.routes = append(r.routes, route)
	return route
}

// Add new GET route.
func (r *Router) GET(path string, handler HandlerFunc) *Route {
	return r.Route(path, handler).GET()
}

// Add new POST route.
func (r *Router) POST(path string, handler HandlerFunc) *Route {
	return r.Route(path, handler).POST()
}

// Add new PUT route.
func (r *Router) PUT(path string, handler HandlerFunc) *Route {
	return r.Route(path, handler).PUT()
}

// Add new DELETE route.
func (r *Router) DELETE(path string, handler HandlerFunc) *Route {
	return r.Route(path, handler).DELETE()
}

func (r *Router) Use(handler HandlerNext) *Router {
	r.middleware = append(r.middleware, NewMiddleware(handler))
	return r
}

func (r *Router) UseHandler(handler http.Handler) *Router {
	r.middleware = append(r.middleware, NewMiddlewareHandler(handler))
	return r
}

func (r *Router) UseHandlerNext(handler HttpHandlerNext) *Router {
	r.middleware = append(r.middleware, NewMiddlewareHandlerNext(handler))
	return r
}

// ServeHTTP makes the router implement the http.Handler interface.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {

	// Initialize request specific context
	context := &Context{}

	// Collect params and splats
	params := map[string]string{}
	splats := []string{}

	// Store matched route
	var routeMatch *Route

	fmt.Println(req.Proto, req.Method, req.URL)

	// Loop through available routes for a match
	for _, route := range r.routes {
		if matches := route.Match(req); matches != nil && len(matches) > 0 {

			// Collect matches to params and splats
			for i, n := range matches[1:] {
				if route.keys[i] != "" {
					// Found a key, append to params
					params[route.keys[i]] = n
				} else {
					// No key found, append to splats
					splats = append(splats, n)
				}
			}

			// Match found, store route and break the loop
			routeMatch = route
			break
		}
	}

	response := NewResponse(w)
	request := NewRequest(req, context, params, splats)

	// Middleware to handle
	// Include route specific middleware if any
	middleware := r.middleware
	if routeMatch != nil {
		middleware = append(middleware, routeMatch.middleware...)
	}

	// Handle middleware
	r.handleMiddleware(response, request, middleware, func() {
		// Moddleware done, proceed to route handler
		if routeMatch != nil {
			routeMatch.handler(response, request)
		} else {
			// Default 404 handler in case route not found
			http.NotFound(w, req)
		}
	})
}

func (r *Router) handleMiddleware(res Response, req *Request, middleware []*Middleware, proceed func()) {
	index := 0

	var next func()
	next = func() {
		if len(middleware) == 0 || index > len(middleware) - 1 {
			// No middleware available, proceed
			proceed()
			return
		}

		handler := middleware[index].handler
		index += 1

		// Call middleware
		handler.ServeHTTP(res, req, next)
	}

	// Loop through available middleware
	next()
}<|endoftext|>


<commit_before>package gotodo

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"html/template"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strconv"

	"github.com/gorilla/mux"
	"golang.org/x/net/context"
	"google.golang.org/appengine/log"
	"google.golang.org/appengine/user"
)

// Router ...
func Router(helper Helper) *mux.Router {
	router := mux.NewRouter()
	app := &App{helper: helper}
	router.HandleFunc("/api/todos", app.HandleGetAllTodos).Methods("GET")
	router.HandleFunc("/api/todos/{id}", app.HandleGetTodo).Methods("GET")
	router.HandleFunc("/api/todos", app.HandlePutTodo).Methods("PUT")
	router.HandleFunc("/api/todos", app.HandlePostTodo).Methods("POST")
	router.HandleFunc("/api/todos", app.HandleDeleteDoneTodos).Methods("DELETE")
	router.HandleFunc("/api/todos/{id}", app.HandleDeleteTodo).Methods("DELETE")
	router.HandleFunc("/", app.HandleRoot).Methods("GET")
	return router
}

// App ...
type App struct {
	helper Helper
}

var _userWrapper = struct {
	LoginURL  func(c context.Context, dest string) (string, error)
	LogoutURL func(c context.Context, dest string) (string, error)
}{
	user.LoginURL,
	user.LogoutURL,
}

// HandleRoot ...
func (app *App) HandleRoot(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-type", "text/html; charset=utf-8")
	ctx := app.helper.Context(r)
	u := app.helper.CurrentUser(ctx)
	if u == nil {
		url, err := _userWrapper.LoginURL(ctx, r.URL.String())
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		w.Header().Set("Location", url)
		w.WriteHeader(http.StatusFound)
		return
	}

	url, _ := _userWrapper.LogoutURL(ctx, "/")
	templ := template.Must(template.ParseFiles(filepath.Join("templates", "todo.html")))
	templ.Execute(w, map[string]string{"EMAIL": u.Email, "LOGOUT": url})
}

// HandleGetAllTodos ...
func (app *App) HandleGetAllTodos(w http.ResponseWriter, r *http.Request) {
	app.commonJSONHandler(w, r, func(c context.Context, repo TodoRepository) (interface{}, error) {
		return repo.ReadAllTodos(c)
	})
}

// HandleGetTodo ...
func (app *App) HandleGetTodo(w http.ResponseWriter, r *http.Request) {
	app.commonJSONHandler(w, r, func(c context.Context, repo TodoRepository) (interface{}, error) {
		id, err := app.parseID(r)
		if err != nil {
			return nil, err
		}
		return repo.ReadTodo(c, id)
	})
}

// HandleDeleteDoneTodos ...
func (app *App) HandleDeleteDoneTodos(w http.ResponseWriter, r *http.Request) {
	app.commonJSONHandler(w, r, func(c context.Context, repo TodoRepository) (interface{}, error) {
		return nil, repo.DeleteDoneTodos(c)
	})
}

// HandleDeleteTodo ...
func (app *App) HandleDeleteTodo(w http.ResponseWriter, r *http.Request) {
	app.commonJSONHandler(w, r, func(c context.Context, repo TodoRepository) (interface{}, error) {
		id, err := app.parseID(r)
		if err != nil {
			return nil, err
		}
		return nil, repo.DeleteTodo(c, id)
	})
}

// HandlePutTodo ...
func (app *App) HandlePutTodo(w http.ResponseWriter, r *http.Request) {
	app.commonJSONHandler(w, r, func(c context.Context, repo TodoRepository) (interface{}, error) {
		todo, err := app.json2Todo(r.Body)
		if err != nil {
			return nil, err
		}
		return repo.UpdateTodo(c, todo)
	})
}

// HandlePostTodo ...
func (app *App) HandlePostTodo(w http.ResponseWriter, r *http.Request) {
	app.commonJSONHandler(w, r, func(c context.Context, repo TodoRepository) (interface{}, error) {
		todo, err := app.json2Todo(r.Body)
		if err != nil {
			return nil, err
		}
		return repo.CreateTodo(c, todo)
	})
}

func (app *App) commonJSONHandler(
	w http.ResponseWriter, r *http.Request,
	fn func(c context.Context, repo TodoRepository) (interface{}, error)) {

	ctx := app.helper.Context(r)
	user := app.helper.CurrentUser(ctx)
	if user == nil {
		message := "Please Login"
		log.Errorf(ctx, message)
		http.Error(w, message, http.StatusInternalServerError)
		return
	}

	repo := app.helper.TodoRepository(toHash(user.ID))
	val, err := fn(ctx, repo)
	if err == nil {
		err = json.NewEncoder(w).Encode(val)
	}

	if err != nil {
		log.Errorf(ctx, "todo error: %#v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func (app *App) json2Todo(r io.ReadCloser) (*Todo, error) {
	defer r.Close()
	var todo Todo
	err := json.NewDecoder(r).Decode(&todo)
	return &todo, err
}

func (app *App) parseID(r *http.Request) (id int64, err error) {
	vars := mux.Vars(r)
	return strconv.ParseInt(vars["id"], 10, 64)
}

func toHash(text string) string {
	salt := os.Getenv("SALT")
	if salt == "" {
		salt = "salt"
	}
	bytes := sha256.Sum256([]byte(text + salt))
	return hex.EncodeToString(bytes[:])
}
<commit_msg>Refactored error message<commit_after>package gotodo

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"html/template"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strconv"

	"github.com/gorilla/mux"
	"golang.org/x/net/context"
	"google.golang.org/appengine/log"
	"google.golang.org/appengine/user"
)

// Router ...
func Router(helper Helper) *mux.Router {
	router := mux.NewRouter()
	app := &App{helper: helper}
	router.HandleFunc("/api/todos", app.HandleGetAllTodos).Methods("GET")
	router.HandleFunc("/api/todos/{id}", app.HandleGetTodo).Methods("GET")
	router.HandleFunc("/api/todos", app.HandlePutTodo).Methods("PUT")
	router.HandleFunc("/api/todos", app.HandlePostTodo).Methods("POST")
	router.HandleFunc("/api/todos", app.HandleDeleteDoneTodos).Methods("DELETE")
	router.HandleFunc("/api/todos/{id}", app.HandleDeleteTodo).Methods("DELETE")
	router.HandleFunc("/", app.HandleRoot).Methods("GET")
	return router
}

// App ...
type App struct {
	helper Helper
}

var _userWrapper = struct {
	LoginURL  func(c context.Context, dest string) (string, error)
	LogoutURL func(c context.Context, dest string) (string, error)
}{
	user.LoginURL,
	user.LogoutURL,
}

// HandleRoot ...
func (app *App) HandleRoot(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-type", "text/html; charset=utf-8")
	ctx := app.helper.Context(r)
	u := app.helper.CurrentUser(ctx)
	if u == nil {
		url, err := _userWrapper.LoginURL(ctx, r.URL.String())
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		w.Header().Set("Location", url)
		w.WriteHeader(http.StatusFound)
		return
	}

	url, _ := _userWrapper.LogoutURL(ctx, "/")
	templ := template.Must(template.ParseFiles(filepath.Join("templates", "todo.html")))
	templ.Execute(w, map[string]string{"EMAIL": u.Email, "LOGOUT": url})
}

// HandleGetAllTodos ...
func (app *App) HandleGetAllTodos(w http.ResponseWriter, r *http.Request) {
	app.commonJSONHandler(w, r, func(c context.Context, repo TodoRepository) (interface{}, error) {
		return repo.ReadAllTodos(c)
	})
}

// HandleGetTodo ...
func (app *App) HandleGetTodo(w http.ResponseWriter, r *http.Request) {
	app.commonJSONHandler(w, r, func(c context.Context, repo TodoRepository) (interface{}, error) {
		id, err := app.parseID(r)
		if err != nil {
			return nil, err
		}
		return repo.ReadTodo(c, id)
	})
}

// HandleDeleteDoneTodos ...
func (app *App) HandleDeleteDoneTodos(w http.ResponseWriter, r *http.Request) {
	app.commonJSONHandler(w, r, func(c context.Context, repo TodoRepository) (interface{}, error) {
		return nil, repo.DeleteDoneTodos(c)
	})
}

// HandleDeleteTodo ...
func (app *App) HandleDeleteTodo(w http.ResponseWriter, r *http.Request) {
	app.commonJSONHandler(w, r, func(c context.Context, repo TodoRepository) (interface{}, error) {
		id, err := app.parseID(r)
		if err != nil {
			return nil, err
		}
		return nil, repo.DeleteTodo(c, id)
	})
}

// HandlePutTodo ...
func (app *App) HandlePutTodo(w http.ResponseWriter, r *http.Request) {
	app.commonJSONHandler(w, r, func(c context.Context, repo TodoRepository) (interface{}, error) {
		todo, err := app.json2Todo(r.Body)
		if err != nil {
			return nil, err
		}
		return repo.UpdateTodo(c, todo)
	})
}

// HandlePostTodo ...
func (app *App) HandlePostTodo(w http.ResponseWriter, r *http.Request) {
	app.commonJSONHandler(w, r, func(c context.Context, repo TodoRepository) (interface{}, error) {
		todo, err := app.json2Todo(r.Body)
		if err != nil {
			return nil, err
		}
		return repo.CreateTodo(c, todo)
	})
}

func (app *App) commonJSONHandler(
	w http.ResponseWriter, r *http.Request,
	fn func(c context.Context, repo TodoRepository) (interface{}, error)) {

	ctx := app.helper.Context(r)
	u := app.helper.CurrentUser(ctx)
	if u == nil {
		log.Errorf(ctx, "User is not login: %#v", u)
		http.Error(w, "Please login!!", http.StatusInternalServerError)
		return
	}

	repo := app.helper.TodoRepository(toHash(u.ID))
	val, err := fn(ctx, repo)
	if err == nil {
		err = json.NewEncoder(w).Encode(val)
	}

	if err != nil {
		log.Errorf(ctx, "todo error: %#v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func (app *App) json2Todo(r io.ReadCloser) (*Todo, error) {
	defer r.Close()
	var todo Todo
	err := json.NewDecoder(r).Decode(&todo)
	return &todo, err
}

func (app *App) parseID(r *http.Request) (id int64, err error) {
	vars := mux.Vars(r)
	return strconv.ParseInt(vars["id"], 10, 64)
}

func toHash(text string) string {
	salt := os.Getenv("SALT")
	if salt == "" {
		salt = "salt"
	}
	bytes := sha256.Sum256([]byte(text + salt))
	return hex.EncodeToString(bytes[:])
}
<|endoftext|>


<commit_before>package artichoke

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

// :identifier
// this is a full Unicode-compliant regex
var varRegex = regexp.MustCompile(":([\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}][\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nd}]*)")

type Route struct {
	// GET, POST, PUT, etc.
	Method string
	// passed by the client as a string or regexp, but always a regexp when used
	Pattern interface{}
	Handler Middleware
	// variable names by position in regexp match
	// this will be not be nil only if Pattern has at least one variable
	vars []string

	// the regex from the user or the Regexp generated
	reg *regexp.Regexp
}

// helper that takes user input and gives it meaning
func prepRoute(r *Route) {
	if r.Handler == nil {
		panic("Every route must have a func that implements artichoke.Middleware")
	}
	if r.Method == "" {
		panic("Every route must have a method: GET, POST, etc.")
	}

	r.Method = strings.ToUpper(r.Method)

	switch t := r.Pattern.(type) {
	case string:
		pattern := t

		// turn sinatra-style routing into a named submatch
		// for example: '/:root' => '/(?P<root>[^/?#])'
		pattern = varRegex.ReplaceAllString(pattern, "(?P<$1>[^/?#]*)")

		// tack on an ending anchor; user must account for it
		if pattern[len(pattern)-1] != '$' {
			pattern += "$"
		}

		// store the into this Route object
		// go ahead and panic; all panics will occur during debugging anyway
		r.reg = regexp.MustCompile(pattern)

		// grab the variable names from the regex
		// only compute this once
		r.vars = r.reg.SubexpNames()[1:]
	case regexp.Regexp:
		r.reg = &t
	default:
		if _, ok := r.Pattern.(*regexp.Regexp); ok {
			panic("Pattern is not a string or a regexp!")
		}
	}
}

type Router interface {
	Add(...*Route)
	Remove(...*Route)
	Middleware() Middleware
}

type router struct {
	routes []*Route
	sem chan bool
}

func NewRouter(routes ...*Route) Router {
	r := new(router)
	r.sem = make(chan bool, 1)
	r.Add(routes...)

	return r
}

func (r *router) Add(routes ...*Route) {
	for _, r := range routes {
		prepRoute(r)
	}

	// make sure nobody can mess with routes while we're modifying it
	defer func() {
		<-r.sem
	}()
	r.sem <- true

	r.routes = append(r.routes, routes...)
}

func (r *router) Remove(routes ...*Route) {
	var keep []*Route

	// make sure nobody can mess with routes while we're modifying it
	defer func() {
		<-r.sem
	}()
	r.sem <- true

	for _, route := range routes {
		keep = append(keep, route)
	}

	r.routes = keep
}

// returns a closure with access to the router
func (r *router) Middleware() Middleware {
	return func(w http.ResponseWriter, req *http.Request, d Data) bool {
		for _, v := range r.routes {
			// use Contains because v.Method could have comma-separated methods
			if !strings.Contains(v.Method, req.Method) && v.Method != "*" {
				continue
			}

			// check if there's a match
			matches := v.reg.FindAllStringSubmatch(req.URL.Path, -1)
			if matches == nil {
				continue
			}

			// params is in this scope so there is no cross-over of keys between routes
			params := make(map[string]string)

			for _, m := range matches {
				// skip the full string match
				for i, val := range m[1:] {
					params[v.vars[i]] = val
				}
			}

			d.Set("params", NewParams(params))
			if res := v.Handler(w, req, d); res {
				return true
			}
		}
		return false
	}
}

type Params struct {
	raw map[string]string
}

func NewParams(raw map[string]string) *Params {
	p := new(Params)
	p.raw = raw

	return p
}

func (p *Params) Get(key string) string {
	return p.raw[key]
}

func GetParams(d Data) *Params {
	if p, ok := d.Get("params"); ok {
		return p.(*Params)
	}
	return nil
}

func StaticRouter(routes ...*Route) Middleware {
	router := NewRouter(routes...)
	return router.Middleware()
}
<commit_msg>Router- always get submatch names<commit_after>package artichoke

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

// :identifier
// this is a full Unicode-compliant regex
var varRegex = regexp.MustCompile(":([\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}][\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nd}]*)")

type Route struct {
	// GET, POST, PUT, etc.
	Method string
	// passed by the client as a string or regexp, but always a regexp when used
	Pattern interface{}
	Handler Middleware
	// variable names by position in regexp match
	// this will be not be nil only if Pattern has at least one variable
	vars []string

	// the regex from the user or the Regexp generated
	reg *regexp.Regexp
}

// helper that takes user input and gives it meaning
func prepRoute(r *Route) {
	if r.Handler == nil {
		panic("Every route must have a func that implements artichoke.Middleware")
	}
	if r.Method == "" {
		panic("Every route must have a method: GET, POST, etc.")
	}

	r.Method = strings.ToUpper(r.Method)

	switch t := r.Pattern.(type) {
	case string:
		pattern := t

		// turn sinatra-style routing into a named submatch
		// for example: '/:root' => '/(?P<root>[^/?#])'
		pattern = varRegex.ReplaceAllString(pattern, "(?P<$1>[^/?#]*)")

		// tack on an ending anchor; user must account for it
		if pattern[len(pattern)-1] != '$' {
			pattern += "$"
		}

		// store the into this Route object
		// go ahead and panic; all panics will occur during debugging anyway
		r.reg = regexp.MustCompile(pattern)

	case regexp.Regexp:
		r.reg = &t

	case *regexp.Regexp:
		r.reg = t

	default:
		panic("Pattern is not a string or a regexp!")
	}

	// grab the variable names from the regex
	// only compute this once
	r.vars = r.reg.SubexpNames()[1:]
}

type Router interface {
	Add(...*Route)
	Remove(...*Route)
	Middleware() Middleware
}

type router struct {
	routes []*Route
	sem chan bool
}

func NewRouter(routes ...*Route) Router {
	r := new(router)
	r.sem = make(chan bool, 1)
	r.Add(routes...)

	return r
}

func (r *router) Add(routes ...*Route) {
	for _, r := range routes {
		prepRoute(r)
	}

	// make sure nobody can mess with routes while we're modifying it
	defer func() {
		<-r.sem
	}()
	r.sem <- true

	r.routes = append(r.routes, routes...)
}

func (r *router) Remove(routes ...*Route) {
	var keep []*Route

	// make sure nobody can mess with routes while we're modifying it
	defer func() {
		<-r.sem
	}()
	r.sem <- true

	for _, route := range routes {
		keep = append(keep, route)
	}

	r.routes = keep
}

// returns a closure with access to the router
func (r *router) Middleware() Middleware {
	return func(w http.ResponseWriter, req *http.Request, d Data) bool {
		for _, v := range r.routes {
			// use Contains because v.Method could have comma-separated methods
			if !strings.Contains(v.Method, req.Method) && v.Method != "*" {
				continue
			}

			// check if there's a match
			matches := v.reg.FindAllStringSubmatch(req.URL.Path, -1)
			if matches == nil {
				continue
			}

			// params is in this scope so there is no cross-over of keys between routes
			params := make(map[string]string)

			for _, m := range matches {
				// skip the full string match
				for i, val := range m[1:] {
					params[v.vars[i]] = val
				}
			}

			d.Set("params", NewParams(params))
			if res := v.Handler(w, req, d); res {
				return true
			}
		}
		return false
	}
}

type Params struct {
	raw map[string]string
}

func NewParams(raw map[string]string) *Params {
	p := new(Params)
	p.raw = raw

	return p
}

func (p *Params) Get(key string) string {
	return p.raw[key]
}

func GetParams(d Data) *Params {
	if p, ok := d.Get("params"); ok {
		return p.(*Params)
	}
	return nil
}

func StaticRouter(routes ...*Route) Middleware {
	router := NewRouter(routes...)
	return router.Middleware()
}
<|endoftext|>