<commit_before>// Package fsd is a client library for accessing StatsD daemon. You can specify
// custom StatsD port via statsd flag
package fsd

import (
	"errors"
	"fmt"
	"math/rand"
	"net"
	"strconv"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/op/go-logging"
)

var (
	address string

	addressConfig chan *etcd.Response
	outgoing      = make(chan []byte, 100000)

	log = logging.MustGetLogger("fsd")

	conn net.Conn
)

func InitWithDynamicConfig(client *etcd.Client, hostname string) {
	addressConfig = make(chan *etcd.Response)

	go watchConfiguration(client, fmt.Sprintf("%v/statsd/address", hostname))
	go processOutgoing()
}

func getCurrentVersion(client *etcd.Client, key string) *etcd.Response {
	for {
		if resp, err := client.Get(key, false, false); err == nil {
			// failed to fetch first value
			return resp
		} else {
			time.Sleep(time.Second)
		}
	}
}
func watchForUpdates(client *etcd.Client, key string, index uint64) {

	for {
		if _, err := client.Watch(key, index, false, addressConfig, nil); err != nil {
			toSleep := 5 * time.Second

			log.Debug("error watching etcd for key %v: %v", key, err)
			log.Debug("retry in %v", toSleep)
			time.Sleep(toSleep)
		}
	}

}

func watchConfiguration(client *etcd.Client, key string) {

	resp := getCurrentVersion(client, key)
	addressConfig <- resp

	watchForUpdates(client, key, resp.EtcdIndex)
}

func connect() (err error) {
	if address == "" {
		return
	}

	log.Debug("fsd connects to %v", address)
	if conn, err = net.Dial("udp", address); err != nil {
		return
	}

	return
}

func processOutgoing() {
	for {
		select {
		case outgoing := <-outgoing:
			if !hasAddress() {
				break
			}

			if _, err := conn.Write(outgoing); err != nil {
				connect()
			}
		case response := <-addressConfig:
			if response.Node != nil && response.Node.Value != "" {
				address = response.Node.Value
				connect()
			}
		}
	}
}

func hasAddress() bool {
	return address != ""
}

// To read about the different semantics check out
// https://github.com/b/statsd_spec
// http://docs.datadoghq.com/guides/dogstatsd/

// Increment the page.views counter.
// page.views:1|c
func Count(name string, value float64) {
	CountL(name, value, 1.0)
}

func CountL(name string, value float64, rate float64) {
	payload, err := rateCheck(rate, createPayload(name, value, "c"))
	if err != nil {
		return
	}

	send(payload)
}

// Record the fuel tank is half-empty
// fuel.level:0.5|g
func Gauge(name string, value float64) {
	payload := createPayload(name, value, "g")
	send(payload)
}

// A request latency
// request.latency:320|ms
// Or a payload of a image
// image.size:2.3|ms
func Timer(name string, duration time.Duration) {
	TimerL(name, duration, 1.0)
}

func TimerL(name string, duration time.Duration, rate float64) {
	HistogramL(name, float64(duration.Nanoseconds()/1000000), rate)
}

func Histogram(name string, value float64) {
	HistogramL(name, value, 1.0)
}

func HistogramL(name string, value float64, rate float64) {
	payload, err := rateCheck(rate, createPayload(name, value, "ms"))
	if err != nil {
		return
	}

	send(payload)
}

// TimeSince records a named timer with the duration since start
func TimeSince(name string, start time.Time) {
	TimeSinceL(name, start, 1.0)
}

// TimeSince records a rated and named timer with the duration since start
func TimeSinceL(name string, start time.Time, rate float64) {
	TimerL(name, time.Now().Sub(start), rate)
}

func Time(name string, lambda func()) {
	TimeL(name, 1.0, lambda)
}

func TimeL(name string, rate float64, lambda func()) {
	start := time.Now()
	lambda()
	TimeSinceL(name, start, rate)
}

// Track a unique visitor id to the site.
// users.uniques:1234|s
func Set(name string, value float64) {
	payload := createPayload(name, value, "s")
	send(payload)
}

func createPayload(name string, value float64, suffix string) string {
	// we spend a lot of time in this code
	return name + ":" + strconv.FormatFloat(value, 'f', -1, 64) + "|" + suffix
	//return fmt.Sprintf("%s:%f|%s", name, value, suffix)
}

func rateCheck(rate float64, payload string) (string, error) {
	if rate < 1 {
		if rand.Float64() < rate {
			return payload + fmt.Sprintf("|@%f", rate), nil
		}
	} else { // rate is 1.0 == all samples should be sent
		return payload, nil
	}

	return "", errors.New("Out of rate limit")
}

func send(payload string) {
	length := float64(len(outgoing))
	capacity := float64(cap(outgoing))

	if length < capacity*0.9 {
		outgoing <- []byte(payload)
	}
}
<commit_msg>Don't do string manipulation if it isn't needed<commit_after>// Package fsd is a client library for accessing StatsD daemon. You can specify
// custom StatsD port via statsd flag
package fsd

import (
	"errors"
	"fmt"
	"math/rand"
	"net"
	"strconv"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/op/go-logging"
)

var (
	address string

	addressConfig chan *etcd.Response
	outgoing      = make(chan []byte, 100000)

	log = logging.MustGetLogger("fsd")

	conn net.Conn
)

func InitWithDynamicConfig(client *etcd.Client, hostname string) {
	addressConfig = make(chan *etcd.Response)

	go watchConfiguration(client, fmt.Sprintf("%v/statsd/address", hostname))
	go processOutgoing()
}

func getCurrentVersion(client *etcd.Client, key string) *etcd.Response {
	for {
		if resp, err := client.Get(key, false, false); err == nil {
			// failed to fetch first value
			return resp
		} else {
			time.Sleep(time.Second)
		}
	}
}
func watchForUpdates(client *etcd.Client, key string, index uint64) {

	for {
		if _, err := client.Watch(key, index, false, addressConfig, nil); err != nil {
			toSleep := 5 * time.Second

			log.Debug("error watching etcd for key %v: %v", key, err)
			log.Debug("retry in %v", toSleep)
			time.Sleep(toSleep)
		}
	}

}

func watchConfiguration(client *etcd.Client, key string) {

	resp := getCurrentVersion(client, key)
	addressConfig <- resp

	watchForUpdates(client, key, resp.EtcdIndex)
}

func connect() (err error) {
	if address == "" {
		return
	}

	log.Debug("fsd connects to %v", address)
	if conn, err = net.Dial("udp", address); err != nil {
		return
	}

	return
}

func processOutgoing() {
	for {
		select {
		case outgoing := <-outgoing:
			if !hasAddress() {
				break
			}

			if _, err := conn.Write(outgoing); err != nil {
				connect()
			}
		case response := <-addressConfig:
			if response.Node != nil && response.Node.Value != "" {
				address = response.Node.Value
				connect()
			}
		}
	}
}

func hasAddress() bool {
	return address != ""
}

// To read about the different semantics check out
// https://github.com/b/statsd_spec
// http://docs.datadoghq.com/guides/dogstatsd/

// Increment the page.views counter.
// page.views:1|c
func Count(name string, value float64) {
	CountL(name, value, 1.0)
}

func CountL(name string, value float64, rate float64) {
	if rand.Float64() > rate {
		return
	}

	payload, err := rateCheck(rate, createPayload(name, value, "c"))
	if err != nil {
		return
	}

	send(payload)
}

// Record the fuel tank is half-empty
// fuel.level:0.5|g
func Gauge(name string, value float64) {
	payload := createPayload(name, value, "g")
	send(payload)
}

// A request latency
// request.latency:320|ms
// Or a payload of a image
// image.size:2.3|ms
func Timer(name string, duration time.Duration) {
	TimerL(name, duration, 1.0)
}

func TimerL(name string, duration time.Duration, rate float64) {
	if rand.Float64() > rate {
		return
	}

	HistogramL(name, float64(duration.Nanoseconds()/1000000), rate)
}

func Histogram(name string, value float64) {
	HistogramL(name, value, 1.0)
}

func HistogramL(name string, value float64, rate float64) {
	if rand.Float64() > rate {
		return
	}

	payload, err := rateCheck(rate, createPayload(name, value, "ms"))
	if err != nil {
		return
	}

	send(payload)
}

// TimeSince records a named timer with the duration since start
func TimeSince(name string, start time.Time) {
	TimeSinceL(name, start, 1.0)
}

// TimeSince records a rated and named timer with the duration since start
func TimeSinceL(name string, start time.Time, rate float64) {
	if rand.Float64() > rate {
		return
	}

	TimerL(name, time.Now().Sub(start), rate)
}

func Time(name string, lambda func()) {
	TimeL(name, 1.0, lambda)
}

func TimeL(name string, rate float64, lambda func()) {
	if rand.Float64() > rate {
		lambda()
	} else {
		start := time.Now()
		lambda()
		TimeSinceL(name, start, rate)
	}
}

// Track a unique visitor id to the site.
// users.uniques:1234|s
func Set(name string, value float64) {
	payload := createPayload(name, value, "s")
	send(payload)
}

func createPayload(name string, value float64, suffix string) string {
	// we spend a lot of time in this code
	return name + ":" + strconv.FormatFloat(value, 'f', -1, 64) + "|" + suffix
	//return fmt.Sprintf("%s:%f|%s", name, value, suffix)
}

func rateCheck(rate float64, payload string) (string, error) {
	if rate < 1 {
		return payload + fmt.Sprintf("|@%f", rate), nil
	} else { // rate is 1.0 == all samples should be sent
		return payload, nil
	}

	return "", errors.New("Out of rate limit")
}

func send(payload string) {
	length := float64(len(outgoing))
	capacity := float64(cap(outgoing))

	if length < capacity*0.9 {
		outgoing <- []byte(payload)
	}
}
<|endoftext|>


<commit_before>package air

import (
	"bytes"
	"errors"
	"fmt"
	"net/http"
	"path"
	"reflect"
	"runtime"
	"sync"
)

type (
	// Air is the top-level framework struct.
	Air struct {
		pregases    []Gas
		gases       []Gas
		paramCap    int
		contextPool *sync.Pool
		server      *server
		router      *router

		Config           *Config
		Logger           Logger
		Binder           Binder
		Minifier         Minifier
		Renderer         Renderer
		Coffer           Coffer
		HTTPErrorHandler HTTPErrorHandler
	}

	// Handler defines a function to serve HTTP requests.
	Handler func(*Context) error

	// Gas defines a function to process gases.
	Gas func(Handler) Handler

	// HTTPError represents an error that occurred while handling an HTTP request.
	HTTPError struct {
		Code    int
		Message string
	}

	// HTTPErrorHandler is a centralized HTTP error handler.
	HTTPErrorHandler func(error, *Context)

	// Map is an alias of the `map[string]interface{}`.
	Map = map[string]interface{}
)

// MIME types
const (
	MIMEApplicationJSON               = "application/json"
	MIMEApplicationJavaScript         = "application/javascript"
	MIMEApplicationXML                = "application/xml"
	MIMEApplicationXWWWFormURLEncoded = "application/x-www-form-urlencoded"
	MIMEImageJPEG                     = "image/jpeg"
	MIMEImagePNG                      = "image/png"
	MIMEImageSVGXML                   = "image/svg+xml"
	MIMEMultipartFormData             = "multipart/form-data"
	MIMETextCSS                       = "text/css"
	MIMETextHTML                      = "text/html"
	MIMETextJavaScript                = "text/javascript"
	MIMETextPlain                     = "text/plain"
	MIMETextXML                       = "text/xml"

	CharsetUTF8 = "; charset=utf-8"
)

// HTTP methods
const (
	GET    = "GET"
	POST   = "POST"
	PUT    = "PUT"
	DELETE = "DELETE"
)

// For easy for-range
var methods = [4]string{GET, POST, PUT, DELETE}

// HTTP headers
const (
	HeaderAccept                          = "Accept"
	HeaderAcceptCharset                   = "Accept-Charset"
	HeaderAcceptEncoding                  = "Accept-Encoding"
	HeaderAcceptLanguage                  = "Accept-Language"
	HeaderAcceptRanges                    = "Accept-Ranges"
	HeaderAccessControlAllowCredentials   = "Access-Control-Allow-Credentials"
	HeaderAccessControlAllowHeaders       = "Access-Control-Allow-Headers"
	HeaderAccessControlAllowMethods       = "Access-Control-Allow-Methods"
	HeaderAccessControlAllowOrigin        = "Access-Control-Allow-Origin"
	HeaderAccessControlExposeHeaders      = "Access-Control-Expose-Headers"
	HeaderAccessControlMaxAge             = "Access-Control-Max-Age"
	HeaderAccessControlRequestHeaders     = "Access-Control-Request-Headers"
	HeaderAccessControlRequestMethod      = "Access-Control-Request-Method"
	HeaderAge                             = "Age"
	HeaderAllow                           = "Allow"
	HeaderAuthorization                   = "Authorization"
	HeaderCacheControl                    = "Cache-Control"
	HeaderConnection                      = "Connection"
	HeaderContentDisposition              = "Content-Disposition"
	HeaderContentEncoding                 = "Content-Encoding"
	HeaderContentLanguage                 = "Content-Language"
	HeaderContentLength                   = "Content-Length"
	HeaderContentLocation                 = "Content-Location"
	HeaderContentSecurityPolicy           = "Content-Security-Policy"
	HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only"
	HeaderContentType                     = "Content-Type"
	HeaderCookie                          = "Cookie"
	HeaderDNT                             = "DNT"
	HeaderDate                            = "Date"
	HeaderETag                            = "ETag"
	HeaderExpires                         = "Expires"
	HeaderForm                            = "Form"
	HeaderHost                            = "Host"
	HeaderIfMatch                         = "If-Match"
	HeaderIfModifiedSince                 = "If-Modified-Since"
	HeaderIfNoneMatch                     = "If-None-Match"
	HeaderIfRange                         = "If-Range"
	HeaderIfUnmodifiedSince               = "If-Unmodified-Since"
	HeaderKeepAlive                       = "Keep-Alive"
	HeaderLastModified                    = "Last-Modified"
	HeaderLocation                        = "Location"
	HeaderOrigin                          = "Origin"
	HeaderPublicKeyPins                   = "Public-Key-Pins"
	HeaderPublicKeyPinsReportOnly         = "Public-Key-Pins-Report-Only"
	HeaderReferer                         = "Referer"
	HeaderReferrerPolicy                  = "Referrer-Policy"
	HeaderRetryAfter                      = "Retry-After"
	HeaderServer                          = "Server"
	HeaderSetCookie                       = "Set-Cookie"
	HeaderStrictTransportSecurity         = "Strict-Transport-Security"
	HeaderTE                              = "TE"
	HeaderTK                              = "TK"
	HeaderTrailer                         = "Trailer"
	HeaderTransferEncoding                = "Transfer-Encoding"
	HeaderUpgrade                         = "Upgrade"
	HeaderUpgradeInsecureRequests         = "Upgrade-Insecure-Requests"
	HeaderUserAgent                       = "User-Agent"
	HeaderVary                            = "Vary"
	HeaderVia                             = "Via"
	HeaderWWWAuthenticate                 = "WWW-Authenticate"
	HeaderWarning                         = "Warning"
	HeaderXCSRFToken                      = "X-CSRF-Token"
	HeaderXContentTypeOptions             = "X-Content-Type-Options"
	HeaderXDNSPrefetchControl             = "X-DNS-Prefetch-Control"
	HeaderXForwardedFor                   = "X-Forwarded-For"
	HeaderXForwardedProto                 = "X-Forwarded-Proto"
	HeaderXFrameOptions                   = "X-Frame-Options"
	HeaderXHTTPMethodOverride             = "X-HTTP-Method-Override"
	HeaderXRealIP                         = "X-Real-IP"
	HeaderXXSSProtection                  = "X-XSS-Protection"
)

// HTTP errors
var (
	ErrUnauthorized          = NewHTTPError(http.StatusUnauthorized)          // 401
	ErrNotFound              = NewHTTPError(http.StatusNotFound)              // 404
	ErrMethodNotAllowed      = NewHTTPError(http.StatusMethodNotAllowed)      // 405
	ErrRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge) // 413
	ErrUnsupportedMediaType  = NewHTTPError(http.StatusUnsupportedMediaType)  // 415

	ErrInternalServerError = NewHTTPError(http.StatusInternalServerError) // 500
	ErrBadGateway          = NewHTTPError(http.StatusBadGateway)          // 502
	ErrServiceUnavailable  = NewHTTPError(http.StatusServiceUnavailable)  // 503
	ErrGatewayTimeout      = NewHTTPError(http.StatusGatewayTimeout)      // 504

	ErrInvalidRedirectCode = errors.New("invalid redirect status code")
)

// HTTP error handlers
var (
	NotFoundHandler = func(c *Context) error {
		return ErrNotFound
	}

	MethodNotAllowedHandler = func(c *Context) error {
		return ErrMethodNotAllowed
	}
)

// New returns a pointer of a new instance of the `Air`.
func New() *Air {
	a := &Air{}

	a.contextPool = &sync.Pool{
		New: func() interface{} {
			return NewContext(a)
		},
	}
	a.server = newServer(a)
	a.router = newRouter(a)

	a.Config = NewConfig("config.toml")
	a.Logger = newLogger(a)
	a.Binder = newBinder()
	a.Minifier = newMinifier()
	a.Renderer = newRenderer(a)
	a.Coffer = newCoffer(a)
	a.HTTPErrorHandler = DefaultHTTPErrorHandler

	return a
}

// Precontain adds the gases to the chain which is perform before the router.
func (a *Air) Precontain(gases ...Gas) {
	a.pregases = append(a.pregases, gases...)
}

// Contain adds the gases to the chain which is perform after the router.
func (a *Air) Contain(gases ...Gas) {
	a.gases = append(a.gases, gases...)
}

// GET registers a new GET route for the path with the matching h in the router with the optional
// route-level gases.
func (a *Air) GET(path string, h Handler, gases ...Gas) {
	a.add(GET, path, h, gases...)
}

// POST registers a new POST route for the path with the matching h in the router with the optional
// route-level gases.
func (a *Air) POST(path string, h Handler, gases ...Gas) {
	a.add(POST, path, h, gases...)
}

// PUT registers a new PUT route for the path with the matching h in the router with the optional
// route-level gases.
func (a *Air) PUT(path string, h Handler, gases ...Gas) {
	a.add(PUT, path, h, gases...)
}

// DELETE registers a new DELETE route for the path with the matching h in the router with the
// optional route-level gases.
func (a *Air) DELETE(path string, h Handler, gases ...Gas) {
	a.add(DELETE, path, h, gases...)
}

// Static registers a new route with the path prefix to serve the static files from the provided
// root directory.
func (a *Air) Static(prefix, root string) {
	a.GET(prefix+"*", func(c *Context) error {
		return c.File(path.Join(root, c.Param("*")))
	})
}

// File registers a new route with the path to serve a static file.
func (a *Air) File(path, file string) {
	a.GET(path, func(c *Context) error {
		return c.File(file)
	})
}

// add registers a new route for the path with the method and the matching h in the router with the
// optional route-level gases.
func (a *Air) add(method, path string, h Handler, gases ...Gas) {
	hn := handlerName(h)

	a.router.add(method, path, func(c *Context) error {
		h := h
		for i := len(gases) - 1; i >= 0; i-- {
			h = gases[i](h)
		}
		return h(c)
	})

	a.router.routes[method+path] = &route{
		method:  method,
		path:    path,
		handler: hn,
	}
}

// URL returns an URL generated from the h with the optional params.
func (a *Air) URL(h Handler, params ...interface{}) string {
	url := &bytes.Buffer{}
	hn := handlerName(h)
	ln := len(params)
	n := 0

	for _, r := range a.router.routes {
		if r.handler == hn {
			for i, l := 0, len(r.path); i < l; i++ {
				if r.path[i] == ':' && n < ln {
					for ; i < l && r.path[i] != '/'; i++ {
					}
					url.WriteString(fmt.Sprintf("%v", params[n]))
					n++
				}

				if i < l {
					url.WriteByte(r.path[i])
				}
			}

			break
		}
	}

	return url.String()
}

// Serve starts the HTTP server.
func (a *Air) Serve() error {
	if a.Config.DebugMode {
		a.Config.LoggerEnabled = true
		a.Logger.Debug("serving in debug mode")
	}

	go func() {
		if err := a.Minifier.Init(); err != nil {
			a.Logger.Error(err)
		}

		if err := a.Renderer.Init(); err != nil {
			a.Logger.Error(err)
		}

		if err := a.Coffer.Init(); err != nil {
			a.Logger.Error(err)
		}
	}()

	return a.server.serve()
}

// Close closes the HTTP server immediately.
func (a *Air) Close() error {
	return a.server.Close()
}

// Shutdown gracefully shuts down the HTTP server without interrupting any active connections.
func (a *Air) Shutdown(c *Context) error {
	return a.server.Shutdown(c.Context)
}

// handlerName returns the func name of the h.
func handlerName(h Handler) string {
	return runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
}

// WrapHandler wraps the h into the `Handler`.
func WrapHandler(h http.Handler) Handler {
	return func(c *Context) error {
		h.ServeHTTP(c.Response, c.Request.Request)
		return nil
	}
}

// WrapGas wraps the h into the `Gas`.
func WrapGas(h Handler) Gas {
	return func(next Handler) Handler {
		return func(c *Context) error {
			if err := h(c); err != nil {
				return err
			}
			return next(c)
		}
	}
}

// NewHTTPError returns a pointer of a new instance of the `HTTPError`.
func NewHTTPError(code int, messages ...interface{}) *HTTPError {
	he := &HTTPError{Code: code, Message: http.StatusText(code)}
	if len(messages) > 0 {
		he.Message = fmt.Sprint(messages...)
	}
	return he
}

// Error implements the `error#Error()`.
func (he *HTTPError) Error() string {
	return he.Message
}

// DefaultHTTPErrorHandler is the default HTTP error handler.
func DefaultHTTPErrorHandler(err error, c *Context) {
	he := ErrInternalServerError
	if che, ok := err.(*HTTPError); ok {
		he = che
	} else if c.Air.Config.DebugMode {
		he.Message = err.Error()
	}

	if !c.Response.Written {
		c.Response.StatusCode = he.Code
		c.String(he.Message)
	}

	c.Air.Logger.Error(err)
}
<commit_msg>refactor: simplify the `Air#Shutdown()`<commit_after>package air

import (
	"bytes"
	"errors"
	"fmt"
	"net/http"
	"path"
	"reflect"
	"runtime"
	"sync"
)

type (
	// Air is the top-level framework struct.
	Air struct {
		pregases    []Gas
		gases       []Gas
		paramCap    int
		contextPool *sync.Pool
		server      *server
		router      *router

		Config           *Config
		Logger           Logger
		Binder           Binder
		Minifier         Minifier
		Renderer         Renderer
		Coffer           Coffer
		HTTPErrorHandler HTTPErrorHandler
	}

	// Handler defines a function to serve HTTP requests.
	Handler func(*Context) error

	// Gas defines a function to process gases.
	Gas func(Handler) Handler

	// HTTPError represents an error that occurred while handling an HTTP request.
	HTTPError struct {
		Code    int
		Message string
	}

	// HTTPErrorHandler is a centralized HTTP error handler.
	HTTPErrorHandler func(error, *Context)

	// Map is an alias of the `map[string]interface{}`.
	Map = map[string]interface{}
)

// MIME types
const (
	MIMEApplicationJSON               = "application/json"
	MIMEApplicationJavaScript         = "application/javascript"
	MIMEApplicationXML                = "application/xml"
	MIMEApplicationXWWWFormURLEncoded = "application/x-www-form-urlencoded"
	MIMEImageJPEG                     = "image/jpeg"
	MIMEImagePNG                      = "image/png"
	MIMEImageSVGXML                   = "image/svg+xml"
	MIMEMultipartFormData             = "multipart/form-data"
	MIMETextCSS                       = "text/css"
	MIMETextHTML                      = "text/html"
	MIMETextJavaScript                = "text/javascript"
	MIMETextPlain                     = "text/plain"
	MIMETextXML                       = "text/xml"

	CharsetUTF8 = "; charset=utf-8"
)

// HTTP methods
const (
	GET    = "GET"
	POST   = "POST"
	PUT    = "PUT"
	DELETE = "DELETE"
)

// For easy for-range
var methods = [4]string{GET, POST, PUT, DELETE}

// HTTP headers
const (
	HeaderAccept                          = "Accept"
	HeaderAcceptCharset                   = "Accept-Charset"
	HeaderAcceptEncoding                  = "Accept-Encoding"
	HeaderAcceptLanguage                  = "Accept-Language"
	HeaderAcceptRanges                    = "Accept-Ranges"
	HeaderAccessControlAllowCredentials   = "Access-Control-Allow-Credentials"
	HeaderAccessControlAllowHeaders       = "Access-Control-Allow-Headers"
	HeaderAccessControlAllowMethods       = "Access-Control-Allow-Methods"
	HeaderAccessControlAllowOrigin        = "Access-Control-Allow-Origin"
	HeaderAccessControlExposeHeaders      = "Access-Control-Expose-Headers"
	HeaderAccessControlMaxAge             = "Access-Control-Max-Age"
	HeaderAccessControlRequestHeaders     = "Access-Control-Request-Headers"
	HeaderAccessControlRequestMethod      = "Access-Control-Request-Method"
	HeaderAge                             = "Age"
	HeaderAllow                           = "Allow"
	HeaderAuthorization                   = "Authorization"
	HeaderCacheControl                    = "Cache-Control"
	HeaderConnection                      = "Connection"
	HeaderContentDisposition              = "Content-Disposition"
	HeaderContentEncoding                 = "Content-Encoding"
	HeaderContentLanguage                 = "Content-Language"
	HeaderContentLength                   = "Content-Length"
	HeaderContentLocation                 = "Content-Location"
	HeaderContentSecurityPolicy           = "Content-Security-Policy"
	HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only"
	HeaderContentType                     = "Content-Type"
	HeaderCookie                          = "Cookie"
	HeaderDNT                             = "DNT"
	HeaderDate                            = "Date"
	HeaderETag                            = "ETag"
	HeaderExpires                         = "Expires"
	HeaderForm                            = "Form"
	HeaderHost                            = "Host"
	HeaderIfMatch                         = "If-Match"
	HeaderIfModifiedSince                 = "If-Modified-Since"
	HeaderIfNoneMatch                     = "If-None-Match"
	HeaderIfRange                         = "If-Range"
	HeaderIfUnmodifiedSince               = "If-Unmodified-Since"
	HeaderKeepAlive                       = "Keep-Alive"
	HeaderLastModified                    = "Last-Modified"
	HeaderLocation                        = "Location"
	HeaderOrigin                          = "Origin"
	HeaderPublicKeyPins                   = "Public-Key-Pins"
	HeaderPublicKeyPinsReportOnly         = "Public-Key-Pins-Report-Only"
	HeaderReferer                         = "Referer"
	HeaderReferrerPolicy                  = "Referrer-Policy"
	HeaderRetryAfter                      = "Retry-After"
	HeaderServer                          = "Server"
	HeaderSetCookie                       = "Set-Cookie"
	HeaderStrictTransportSecurity         = "Strict-Transport-Security"
	HeaderTE                              = "TE"
	HeaderTK                              = "TK"
	HeaderTrailer                         = "Trailer"
	HeaderTransferEncoding                = "Transfer-Encoding"
	HeaderUpgrade                         = "Upgrade"
	HeaderUpgradeInsecureRequests         = "Upgrade-Insecure-Requests"
	HeaderUserAgent                       = "User-Agent"
	HeaderVary                            = "Vary"
	HeaderVia                             = "Via"
	HeaderWWWAuthenticate                 = "WWW-Authenticate"
	HeaderWarning                         = "Warning"
	HeaderXCSRFToken                      = "X-CSRF-Token"
	HeaderXContentTypeOptions             = "X-Content-Type-Options"
	HeaderXDNSPrefetchControl             = "X-DNS-Prefetch-Control"
	HeaderXForwardedFor                   = "X-Forwarded-For"
	HeaderXForwardedProto                 = "X-Forwarded-Proto"
	HeaderXFrameOptions                   = "X-Frame-Options"
	HeaderXHTTPMethodOverride             = "X-HTTP-Method-Override"
	HeaderXRealIP                         = "X-Real-IP"
	HeaderXXSSProtection                  = "X-XSS-Protection"
)

// HTTP errors
var (
	ErrUnauthorized          = NewHTTPError(http.StatusUnauthorized)          // 401
	ErrNotFound              = NewHTTPError(http.StatusNotFound)              // 404
	ErrMethodNotAllowed      = NewHTTPError(http.StatusMethodNotAllowed)      // 405
	ErrRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge) // 413
	ErrUnsupportedMediaType  = NewHTTPError(http.StatusUnsupportedMediaType)  // 415

	ErrInternalServerError = NewHTTPError(http.StatusInternalServerError) // 500
	ErrBadGateway          = NewHTTPError(http.StatusBadGateway)          // 502
	ErrServiceUnavailable  = NewHTTPError(http.StatusServiceUnavailable)  // 503
	ErrGatewayTimeout      = NewHTTPError(http.StatusGatewayTimeout)      // 504

	ErrInvalidRedirectCode = errors.New("invalid redirect status code")
)

// HTTP error handlers
var (
	NotFoundHandler = func(c *Context) error {
		return ErrNotFound
	}

	MethodNotAllowedHandler = func(c *Context) error {
		return ErrMethodNotAllowed
	}
)

// New returns a pointer of a new instance of the `Air`.
func New() *Air {
	a := &Air{}

	a.contextPool = &sync.Pool{
		New: func() interface{} {
			return NewContext(a)
		},
	}
	a.server = newServer(a)
	a.router = newRouter(a)

	a.Config = NewConfig("config.toml")
	a.Logger = newLogger(a)
	a.Binder = newBinder()
	a.Minifier = newMinifier()
	a.Renderer = newRenderer(a)
	a.Coffer = newCoffer(a)
	a.HTTPErrorHandler = DefaultHTTPErrorHandler

	return a
}

// Precontain adds the gases to the chain which is perform before the router.
func (a *Air) Precontain(gases ...Gas) {
	a.pregases = append(a.pregases, gases...)
}

// Contain adds the gases to the chain which is perform after the router.
func (a *Air) Contain(gases ...Gas) {
	a.gases = append(a.gases, gases...)
}

// GET registers a new GET route for the path with the matching h in the router with the optional
// route-level gases.
func (a *Air) GET(path string, h Handler, gases ...Gas) {
	a.add(GET, path, h, gases...)
}

// POST registers a new POST route for the path with the matching h in the router with the optional
// route-level gases.
func (a *Air) POST(path string, h Handler, gases ...Gas) {
	a.add(POST, path, h, gases...)
}

// PUT registers a new PUT route for the path with the matching h in the router with the optional
// route-level gases.
func (a *Air) PUT(path string, h Handler, gases ...Gas) {
	a.add(PUT, path, h, gases...)
}

// DELETE registers a new DELETE route for the path with the matching h in the router with the
// optional route-level gases.
func (a *Air) DELETE(path string, h Handler, gases ...Gas) {
	a.add(DELETE, path, h, gases...)
}

// Static registers a new route with the path prefix to serve the static files from the provided
// root directory.
func (a *Air) Static(prefix, root string) {
	a.GET(prefix+"*", func(c *Context) error {
		return c.File(path.Join(root, c.Param("*")))
	})
}

// File registers a new route with the path to serve a static file.
func (a *Air) File(path, file string) {
	a.GET(path, func(c *Context) error {
		return c.File(file)
	})
}

// add registers a new route for the path with the method and the matching h in the router with the
// optional route-level gases.
func (a *Air) add(method, path string, h Handler, gases ...Gas) {
	hn := handlerName(h)

	a.router.add(method, path, func(c *Context) error {
		h := h
		for i := len(gases) - 1; i >= 0; i-- {
			h = gases[i](h)
		}
		return h(c)
	})

	a.router.routes[method+path] = &route{
		method:  method,
		path:    path,
		handler: hn,
	}
}

// URL returns an URL generated from the h with the optional params.
func (a *Air) URL(h Handler, params ...interface{}) string {
	url := &bytes.Buffer{}
	hn := handlerName(h)
	ln := len(params)
	n := 0

	for _, r := range a.router.routes {
		if r.handler == hn {
			for i, l := 0, len(r.path); i < l; i++ {
				if r.path[i] == ':' && n < ln {
					for ; i < l && r.path[i] != '/'; i++ {
					}
					url.WriteString(fmt.Sprintf("%v", params[n]))
					n++
				}

				if i < l {
					url.WriteByte(r.path[i])
				}
			}

			break
		}
	}

	return url.String()
}

// Serve starts the HTTP server.
func (a *Air) Serve() error {
	if a.Config.DebugMode {
		a.Config.LoggerEnabled = true
		a.Logger.Debug("serving in debug mode")
	}

	go func() {
		if err := a.Minifier.Init(); err != nil {
			a.Logger.Error(err)
		}

		if err := a.Renderer.Init(); err != nil {
			a.Logger.Error(err)
		}

		if err := a.Coffer.Init(); err != nil {
			a.Logger.Error(err)
		}
	}()

	return a.server.serve()
}

// Close closes the HTTP server immediately.
func (a *Air) Close() error {
	return a.server.Close()
}

// Shutdown gracefully shuts down the HTTP server without interrupting any active connections.
func (a *Air) Shutdown(c *Context) error {
	return a.server.Shutdown(c)
}

// handlerName returns the func name of the h.
func handlerName(h Handler) string {
	return runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
}

// WrapHandler wraps the h into the `Handler`.
func WrapHandler(h http.Handler) Handler {
	return func(c *Context) error {
		h.ServeHTTP(c.Response, c.Request.Request)
		return nil
	}
}

// WrapGas wraps the h into the `Gas`.
func WrapGas(h Handler) Gas {
	return func(next Handler) Handler {
		return func(c *Context) error {
			if err := h(c); err != nil {
				return err
			}
			return next(c)
		}
	}
}

// NewHTTPError returns a pointer of a new instance of the `HTTPError`.
func NewHTTPError(code int, messages ...interface{}) *HTTPError {
	he := &HTTPError{Code: code, Message: http.StatusText(code)}
	if len(messages) > 0 {
		he.Message = fmt.Sprint(messages...)
	}
	return he
}

// Error implements the `error#Error()`.
func (he *HTTPError) Error() string {
	return he.Message
}

// DefaultHTTPErrorHandler is the default HTTP error handler.
func DefaultHTTPErrorHandler(err error, c *Context) {
	he := ErrInternalServerError
	if che, ok := err.(*HTTPError); ok {
		he = che
	} else if c.Air.Config.DebugMode {
		he.Message = err.Error()
	}

	if !c.Response.Written {
		c.Response.StatusCode = he.Code
		c.String(he.Message)
	}

	c.Air.Logger.Error(err)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"log"
	"time"

	"github.com/diginatu/nagome/nicolive"
)

// Message is base API struct for plugin
type Message struct {
	Domain  string          `json:"domain"`
	Command string          `json:"command"`
	Content json.RawMessage `json:"content,omitempty"` // The structure of Content is depend on the Command (and Domain).

	prgno int
}

// NewMessage returns new Message with the given values.
func NewMessage(dom, com string, con interface{}) (*Message, error) {
	var conj json.RawMessage
	var err error

	if con == nil {
		conj = nil
	} else {
		conj, err = json.Marshal(con)
		if err != nil {
			return nil, err
		}
	}

	m := &Message{
		Domain:  dom,
		Command: com,
		Content: conj,
		prgno:   -1,
	}
	return m, nil
}

// NewMessageMust is same as NewMessage but assume no error.
func NewMessageMust(dom, com string, con interface{}) *Message {
	m, err := NewMessage(dom, com, con)
	if err != nil {
		log.Fatalln(err)
	}
	return m
}

// Dimain names
const (
	DomainNagome    = "nagome"
	DomainQuery     = "nagome_query"
	DomainComment   = "nagome_comment"
	DomainUI        = "nagome_ui"
	DomainAntenna   = "nagome_antenna"
	DomainDirect    = "nagome_direct"    // DomainDirect is a special domain (from plugin).
	DomainDirectngm = "nagome_directngm" // DomainDirectNgm is a domain for direct message from Nagome.

	// Adding DomainSuffixFilter to the end of domain name in "subscribe" in your plugin.yml enables filtering messages by the plugin.
	DomainSuffixFilter = "@filter"
)

// Command names
const (
	// DomainNagome
	CommNagomeBroadOpen    = "Broad.Open"
	CommNagomeBroadClose   = "Broad.Close"
	CommNagomeBroadInfo    = "Broad.Info"
	CommNagomeCommentSend  = "Comment.Send"
	CommNagomeAntennaOpen  = "Antenna.Open"
	CommNagomeAntennaClose = "Antenna.Close"

	// DomainComment
	// This domain is for only sending comments.
	CommCommentGot = "Got"

	// DomainQuery
	// Query from plugin to Nagome.
	CommQueryBroadConnect     = "Broad.Connect"
	CommQueryBroadDisconnect  = "Broad.Disconnect"
	CommQueryBroadSendComment = "Broad.SendComment"

	CommQueryAccountSet   = "Account.Set"   // Set the given content value as account values.
	CommQueryAccountLogin = "Account.Login" // Login and set the user session to account.
	CommQueryAccountLoad  = "Account.Load"
	CommQueryAccountSave  = "Account.Save"

	CommQueryLogPrint = "Log.Print" // Print string using logger of Nagome

	CommQuerySettingsSetCurrent = "Settings.SetCurrent" // Set settings to current slot.
	CommQuerySettingsSetAll     = "Settings.SetAll"     // Set all slots of settings.

	CommQueryPlugEnable = "Plug.Enable" // Enable or disable a plugin.

	// DomainUI
	// Event to be processed by UI plugin.
	CommUIDialog        = "Dialog"
	CommUIClearComments = "ClearComments"

	// DomainAntenna
	// All antenna items (started live).
	CommAntennaGot = "Got"

	// DomainDirect (special domain)
	// The messages is sent between a plugin and Nagome.  It is not broadcasted and can not be filtered.

	// plugin to Nagome
	CommDirectNo       = "No"        // Tell plugin number to Nagome when the connection started.  (TCP at first time only)
	CommDirectPlugList = "Plug.List" // Request a list of plugins.

	CommDirectSettingsCurrent = "Settings.Current" // Request current settings message.
	CommDirectSettingsAll     = "Settings.All"     // Request all slots of settings message.

	// Nagome to plugin
	CommDirectngmPlugEnabled  = "Plug.Enabled"  // Sent when the plugin is enabled.
	CommDirectngmPlugDisabled = "Plug.Disabled" // Sent when the plugin is disabled.
	CommDirectngmPlugList     = "Plug.List"

	CommDirectngmSettingsCurrent = "Settings.Current"
	CommDirectngmSettingsAll     = "Settings.All"
)

// Contents
//
// Contents in the Message API

// CtNagomeBroadOpen is a content of CommNagomeBroadOpen
type CtNagomeBroadOpen struct {
	BroadID     string `json:"broad_id"`
	Title       string `json:"title"`
	Description string `json:"description"`
	CommunityID string `json:"community_id"`
	OwnerID     string `json:"owner_id"`
	OwnerName   string `json:"owner_name"`
	OwnerBroad  bool   `json:"owner_broad"`

	OpenTime  time.Time `json:"open_time"`
	StartTime time.Time `json:"start_time"`
	EndTime   time.Time `json:"end_time"`
}

// CtNagomeBroadInfo is a content of CommNagomeBroadInfo
type CtNagomeBroadInfo struct {
	WatchCount   string `json:"watch_count"`
	CommentCount string `json:"comment_count"`
}

// CtQueryBroadConnect is a content of CommQueryBroadConnect
type CtQueryBroadConnect struct {
	BroadID string `json:"broad_id"`
}

// CtQueryBroadSendComment is a content of CommQueryBroadSendComment
type CtQueryBroadSendComment struct {
	Text  string `json:"text"`
	Iyayo bool   `json:"iyayo"`
}

// CtQueryAccountSet is a content of CommQueryAccountSet
type CtQueryAccountSet nicolive.Account

// CtQueryLogPrint is a content of CommQueryLogPrint
type CtQueryLogPrint struct {
	Text string `json:"text"`
}

// CtQuerySettingsSetCurrent is a content of CommQuerySettingsSetCurrent
type CtQuerySettingsSetCurrent SettingsSlot

// CtQuerySettingsSetAll is a content of CommQuerySettingsSetAll
type CtQuerySettingsSetAll SettingsSlots

// CtQueryPlugEnable is a content of CommQueryPlugEnable
type CtQueryPlugEnable struct {
	No     int  `json:"no"`
	Enable bool `json:"enable"`
}

// A CtCommentGot is a content of CommCommentGot
type CtCommentGot struct {
	No      int       `json:"no"`
	Date    time.Time `json:"date"`
	Raw     string    `json:"raw"`
	Comment string    `json:"comment"`

	UserID           string `json:"user_id"`
	UserName         string `json:"user_name"`
	UserThumbnailURL string `json:"user_thumbnail_url,omitempty"`
	Score            int    `json:"score,omitempty"`
	IsPremium        bool   `json:"is_premium"`
	IsBroadcaster    bool   `json:"is_broadcaster"`
	IsStaff          bool   `json:"is_staff"`
	IsAnonymity      bool   `json:"is_anonymity"`
}

// CtUIDialog is a content of CommUIDialog
type CtUIDialog struct {
	// Select type from below const string
	Type        string `json:"type"`
	Title       string `json:"title"`
	Description string `json:"description"`
}

// CtAntennaGot is a content of CommAntennaGot
type CtAntennaGot struct {
	BroadID     string `json:"broad_id"`
	CommunityID string `json:"community_id"`
	UserID      string `json:"user_id"`
}

// type of CtUIDialog
const (
	CtUIDialogTypeInfo string = "Info"
	CtUIDialogTypeWarn        = "Warn"
)

// CtDirectNo is a content for CommDirectNo
type CtDirectNo struct {
	No int `json:"no"`
}

// CtDirectngmPlugList is a content for CommDirectngmPlugList
type CtDirectngmPlugList struct {
	Plugins *[]*Plugin `json:"plugins"`
}

// CtDirectngmSettingsCurrent is a content for CommDirectngmSettingsCurrent
type CtDirectngmSettingsCurrent SettingsSlot

// CtDirectngmSettingsAll is a content for CommDirectngmSettingsAll
type CtDirectngmSettingsAll SettingsSlots
<commit_msg>make message stringer to print content<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"time"

	"github.com/diginatu/nagome/nicolive"
)

// Message is base API struct for plugin
type Message struct {
	Domain  string          `json:"domain"`
	Command string          `json:"command"`
	Content json.RawMessage `json:"content,omitempty"` // The structure of Content is depend on the Command (and Domain).

	prgno int
}

func (m *Message) String() string {
	return fmt.Sprintf("{%s %s %s plug:%d}", m.Domain, m.Command, m.Content, m.prgno)
}

// NewMessage returns new Message with the given values.
func NewMessage(dom, com string, con interface{}) (*Message, error) {
	var conj json.RawMessage
	var err error

	if con == nil {
		conj = nil
	} else {
		conj, err = json.Marshal(con)
		if err != nil {
			return nil, err
		}
	}

	m := &Message{
		Domain:  dom,
		Command: com,
		Content: conj,
		prgno:   -1,
	}
	return m, nil
}

// NewMessageMust is same as NewMessage but assume no error.
func NewMessageMust(dom, com string, con interface{}) *Message {
	m, err := NewMessage(dom, com, con)
	if err != nil {
		log.Fatalln(err)
	}
	return m
}

// Dimain names
const (
	DomainNagome    = "nagome"
	DomainQuery     = "nagome_query"
	DomainComment   = "nagome_comment"
	DomainUI        = "nagome_ui"
	DomainAntenna   = "nagome_antenna"
	DomainDirect    = "nagome_direct"    // DomainDirect is a special domain (from plugin).
	DomainDirectngm = "nagome_directngm" // DomainDirectNgm is a domain for direct message from Nagome.

	// Adding DomainSuffixFilter to the end of domain name in "subscribe" in your plugin.yml enables filtering messages by the plugin.
	DomainSuffixFilter = "@filter"
)

// Command names
const (
	// DomainNagome
	CommNagomeBroadOpen    = "Broad.Open"
	CommNagomeBroadClose   = "Broad.Close"
	CommNagomeBroadInfo    = "Broad.Info"
	CommNagomeCommentSend  = "Comment.Send"
	CommNagomeAntennaOpen  = "Antenna.Open"
	CommNagomeAntennaClose = "Antenna.Close"

	// DomainComment
	// This domain is for only sending comments.
	CommCommentGot = "Got"

	// DomainQuery
	// Query from plugin to Nagome.
	CommQueryBroadConnect     = "Broad.Connect"
	CommQueryBroadDisconnect  = "Broad.Disconnect"
	CommQueryBroadSendComment = "Broad.SendComment"

	CommQueryAccountSet   = "Account.Set"   // Set the given content value as account values.
	CommQueryAccountLogin = "Account.Login" // Login and set the user session to account.
	CommQueryAccountLoad  = "Account.Load"
	CommQueryAccountSave  = "Account.Save"

	CommQueryLogPrint = "Log.Print" // Print string using logger of Nagome

	CommQuerySettingsSetCurrent = "Settings.SetCurrent" // Set settings to current slot.
	CommQuerySettingsSetAll     = "Settings.SetAll"     // Set all slots of settings.

	CommQueryPlugEnable = "Plug.Enable" // Enable or disable a plugin.

	// DomainUI
	// Event to be processed by UI plugin.
	CommUIDialog        = "Dialog"
	CommUIClearComments = "ClearComments"

	// DomainAntenna
	// All antenna items (started live).
	CommAntennaGot = "Got"

	// DomainDirect (special domain)
	// The messages is sent between a plugin and Nagome.  It is not broadcasted and can not be filtered.

	// plugin to Nagome
	CommDirectNo       = "No"        // Tell plugin number to Nagome when the connection started.  (TCP at first time only)
	CommDirectPlugList = "Plug.List" // Request a list of plugins.

	CommDirectSettingsCurrent = "Settings.Current" // Request current settings message.
	CommDirectSettingsAll     = "Settings.All"     // Request all slots of settings message.

	// Nagome to plugin
	CommDirectngmPlugEnabled  = "Plug.Enabled"  // Sent when the plugin is enabled.
	CommDirectngmPlugDisabled = "Plug.Disabled" // Sent when the plugin is disabled.
	CommDirectngmPlugList     = "Plug.List"

	CommDirectngmSettingsCurrent = "Settings.Current"
	CommDirectngmSettingsAll     = "Settings.All"
)

// Contents
//
// Contents in the Message API

// CtNagomeBroadOpen is a content of CommNagomeBroadOpen
type CtNagomeBroadOpen struct {
	BroadID     string `json:"broad_id"`
	Title       string `json:"title"`
	Description string `json:"description"`
	CommunityID string `json:"community_id"`
	OwnerID     string `json:"owner_id"`
	OwnerName   string `json:"owner_name"`
	OwnerBroad  bool   `json:"owner_broad"`

	OpenTime  time.Time `json:"open_time"`
	StartTime time.Time `json:"start_time"`
	EndTime   time.Time `json:"end_time"`
}

// CtNagomeBroadInfo is a content of CommNagomeBroadInfo
type CtNagomeBroadInfo struct {
	WatchCount   string `json:"watch_count"`
	CommentCount string `json:"comment_count"`
}

// CtQueryBroadConnect is a content of CommQueryBroadConnect
type CtQueryBroadConnect struct {
	BroadID string `json:"broad_id"`
}

// CtQueryBroadSendComment is a content of CommQueryBroadSendComment
type CtQueryBroadSendComment struct {
	Text  string `json:"text"`
	Iyayo bool   `json:"iyayo"`
}

// CtQueryAccountSet is a content of CommQueryAccountSet
type CtQueryAccountSet nicolive.Account

// CtQueryLogPrint is a content of CommQueryLogPrint
type CtQueryLogPrint struct {
	Text string `json:"text"`
}

// CtQuerySettingsSetCurrent is a content of CommQuerySettingsSetCurrent
type CtQuerySettingsSetCurrent SettingsSlot

// CtQuerySettingsSetAll is a content of CommQuerySettingsSetAll
type CtQuerySettingsSetAll SettingsSlots

// CtQueryPlugEnable is a content of CommQueryPlugEnable
type CtQueryPlugEnable struct {
	No     int  `json:"no"`
	Enable bool `json:"enable"`
}

// A CtCommentGot is a content of CommCommentGot
type CtCommentGot struct {
	No      int       `json:"no"`
	Date    time.Time `json:"date"`
	Raw     string    `json:"raw"`
	Comment string    `json:"comment"`

	UserID           string `json:"user_id"`
	UserName         string `json:"user_name"`
	UserThumbnailURL string `json:"user_thumbnail_url,omitempty"`
	Score            int    `json:"score,omitempty"`
	IsPremium        bool   `json:"is_premium"`
	IsBroadcaster    bool   `json:"is_broadcaster"`
	IsStaff          bool   `json:"is_staff"`
	IsAnonymity      bool   `json:"is_anonymity"`
}

// CtUIDialog is a content of CommUIDialog
type CtUIDialog struct {
	// Select type from below const string
	Type        string `json:"type"`
	Title       string `json:"title"`
	Description string `json:"description"`
}

// CtAntennaGot is a content of CommAntennaGot
type CtAntennaGot struct {
	BroadID     string `json:"broad_id"`
	CommunityID string `json:"community_id"`
	UserID      string `json:"user_id"`
}

// type of CtUIDialog
const (
	CtUIDialogTypeInfo string = "Info"
	CtUIDialogTypeWarn        = "Warn"
)

// CtDirectNo is a content for CommDirectNo
type CtDirectNo struct {
	No int `json:"no"`
}

// CtDirectngmPlugList is a content for CommDirectngmPlugList
type CtDirectngmPlugList struct {
	Plugins *[]*Plugin `json:"plugins"`
}

// CtDirectngmSettingsCurrent is a content for CommDirectngmSettingsCurrent
type CtDirectngmSettingsCurrent SettingsSlot

// CtDirectngmSettingsAll is a content for CommDirectngmSettingsAll
type CtDirectngmSettingsAll SettingsSlots
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net/http"
)

var apiServeMux = http.NewServeMux()

func init() {
	apiServeMux.Handle("/debug/pprof/", http.DefaultServeMux)
	apiServeMux.HandleFunc("/proxy.pac", handlePACFile)
	apiServeMux.HandleFunc("/reload", handleReload)
	apiServeMux.HandleFunc("/classify", handleClassification)

	apiServeMux.HandleFunc("/per-user-ports", handlePerUserPortList)
	apiServeMux.HandleFunc("/per-user-ports/authenticate", handlePerUserAuthenticate)
}

func handleAPI(w http.ResponseWriter, r *http.Request) {
	conf := getConfig()

	authUser := ""
	if user, pass, ok := r.BasicAuth(); ok {
		if conf.ValidCredentials(user, pass) {
			authUser = user
		} else {
			log.Printf("Incorrect username or password for API request from %v: %s:%s", r.RemoteAddr, user, pass)
		}
	}

	acls := conf.APIACLs.requestACLs(r, authUser)
	possibleActions := []string{"allow", "block"}
	if authUser == "" {
		possibleActions = append(possibleActions, "require-auth")
	}
	thisRule := conf.APIACLs.ChooseACLAction(acls, possibleActions...)

	switch thisRule.Action {
	case "require-auth":
		w.Header().Set("WWW-Authenticate", `Basic realm="Redwood API"`)
		http.Error(w, "Authentication required", http.StatusUnauthorized)
		log.Printf("Missing required API authentication from %v to %v", r.RemoteAddr, r.URL)
		return

	case "block":
		http.Error(w, "You do not have access to this page.", http.StatusForbidden)
	}

	apiServeMux.ServeHTTP(w, r)
}
<commit_msg>When blocking access to the API, actually block.<commit_after>package main

import (
	"log"
	"net/http"
)

var apiServeMux = http.NewServeMux()

func init() {
	apiServeMux.Handle("/debug/pprof/", http.DefaultServeMux)
	apiServeMux.HandleFunc("/proxy.pac", handlePACFile)
	apiServeMux.HandleFunc("/reload", handleReload)
	apiServeMux.HandleFunc("/classify", handleClassification)

	apiServeMux.HandleFunc("/per-user-ports", handlePerUserPortList)
	apiServeMux.HandleFunc("/per-user-ports/authenticate", handlePerUserAuthenticate)
}

func handleAPI(w http.ResponseWriter, r *http.Request) {
	conf := getConfig()

	authUser := ""
	if user, pass, ok := r.BasicAuth(); ok {
		if conf.ValidCredentials(user, pass) {
			authUser = user
		} else {
			log.Printf("Incorrect username or password for API request from %v: %s:%s", r.RemoteAddr, user, pass)
		}
	}

	acls := conf.APIACLs.requestACLs(r, authUser)
	possibleActions := []string{"allow", "block"}
	if authUser == "" {
		possibleActions = append(possibleActions, "require-auth")
	}
	thisRule := conf.APIACLs.ChooseACLAction(acls, possibleActions...)

	switch thisRule.Action {
	case "require-auth":
		w.Header().Set("WWW-Authenticate", `Basic realm="Redwood API"`)
		http.Error(w, "Authentication required", http.StatusUnauthorized)
		log.Printf("Missing required API authentication from %v to %v", r.RemoteAddr, r.URL)
		return

	case "block":
		http.Error(w, "You do not have access to this page.", http.StatusForbidden)
		return
	}

	apiServeMux.ServeHTTP(w, r)
}
<|endoftext|>


<commit_before>// Copyright 2013 Matthew Baird
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//     http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License 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.

/*
The API package provides the basic support for using HTTP to talk to the Mandrill and Mailchimp API's.
Each Struct contains a Key, Transport and endpoint property
*/
package gochimp

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
)

const (
	mandrill_uri     = "mandrillapp.com/api/"
	mandrill_version = "1.0"
)

type MandrillAPI struct {
	Key       string
	Transport http.RoundTripper
	endpoint  string
}

type ChimpAPI struct {
	Key       string
	Transport http.RoundTripper
	endpoint  string
}

// see https://mandrillapp.com/api/docs/
// currently supporting json output formats
func NewMandrill(apiKey string) (*MandrillAPI, error) {
	u := url.URL{}
	u.Scheme = "https"
	u.Host = mandrill_uri
	u.Path = mandrill_version
	return &MandrillAPI{Key: apiKey, endpoint: u.String()}, nil
}

const mailchimp_uri string = "%s.api.mailchimp.com"
const mailchimp_version string = "/2.0"
const debug bool = false

var mailchimp_datacenter = regexp.MustCompile("[a-z]+[0-9]+$")

func NewChimp(apiKey string, https bool) *ChimpAPI {
	u := url.URL{}
	if https {
		u.Scheme = "https"
	} else {
		u.Scheme = "http"
	}
	u.Host = fmt.Sprintf("%s.api.mailchimp.com", mailchimp_datacenter.FindString(apiKey))
	u.Path = mailchimp_version
	return &ChimpAPI{Key: apiKey, endpoint: u.String()}
}

func runChimp(api *ChimpAPI, path string, parameters interface{}) ([]byte, error) {
	b, err := json.Marshal(parameters)
	if err != nil {
		return nil, err
	}
	requestUrl := fmt.Sprintf("%s%s", api.endpoint, path)
	if debug {
		log.Printf("Request URL:%s", requestUrl)
	}
	client := &http.Client{Transport: api.Transport}
	resp, err := client.Post(requestUrl, "application/json", bytes.NewBuffer(b))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if debug {
		log.Printf("Response Body:%s", string(body))
	}
	if err = chimpErrorCheck(body); err != nil {
		return nil, err
	}
	return body, nil
}

func runMandrill(api *MandrillAPI, path string, parameters map[string]interface{}) ([]byte, error) {
	if parameters == nil {
		parameters = make(map[string]interface{})
	}
	parameters["key"] = api.Key
	b, err := json.Marshal(parameters)
	if debug {
		log.Printf("Payload:%s", string(b))
	}
	if err != nil {
		return nil, err
	}
	requestUrl := fmt.Sprintf("%s%s", api.endpoint, path)
	if debug {
		log.Printf("Request URL:%s", requestUrl)
	}
	client := &http.Client{Transport: api.Transport}
	resp, err := client.Post(requestUrl, "application/json", bytes.NewBuffer(b))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if debug {
		log.Printf("Response Body:%s", string(body))
	}
	if err = mandrillErrorCheck(body); err != nil {
		return nil, err
	}
	return body, nil
}

func parseString(body []byte, err error) (string, error) {
	if err != nil {
		return "", err
	}
	return strconv.Unquote(string(body))
}

func parseMandrillJson(api *MandrillAPI, path string, parameters map[string]interface{}, retval interface{}) error {
	body, err := runMandrill(api, path, parameters)
	if err != nil {
		return err
	}
	if err := json.Unmarshal(body, retval); err != nil {
		return err
	}

	return nil
}

func parseChimpJson(api *ChimpAPI, method string, parameters interface{}, retval interface{}) error {
	body, err := runChimp(api, method, parameters)

	if err != nil {
		return err
	}
	if retval != nil {
		return parseJson(body, retval)
	}
	return nil
}

type JsonAlterer interface {
	alterJson(b []byte) []byte
}

func parseJson(body []byte, retval interface{}) error {
	switch r := retval.(type) {
	case JsonAlterer:
		return json.Unmarshal(r.alterJson(body), retval)
	default:
		return json.Unmarshal(body, retval)
	}
}
<commit_msg>Fix for "percent-encoded characters in host" issue<commit_after>// Copyright 2013 Matthew Baird
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//     http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License 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.

/*
The API package provides the basic support for using HTTP to talk to the Mandrill and Mailchimp API's.
Each Struct contains a Key, Transport and endpoint property
*/
package gochimp

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
)

const (
	mandrill_uri     = "mandrillapp.com"
	mandrill_version = "/api/1.0"
)

type MandrillAPI struct {
	Key       string
	Transport http.RoundTripper
	endpoint  string
}

type ChimpAPI struct {
	Key       string
	Transport http.RoundTripper
	endpoint  string
}

// see https://mandrillapp.com/api/docs/
// currently supporting json output formats
func NewMandrill(apiKey string) (*MandrillAPI, error) {
	u := url.URL{}
	u.Scheme = "https"
	u.Host = mandrill_uri
	u.Path = mandrill_version
	return &MandrillAPI{Key: apiKey, endpoint: u.String()}, nil
}

const mailchimp_uri string = "%s.api.mailchimp.com"
const mailchimp_version string = "/2.0"
const debug bool = false

var mailchimp_datacenter = regexp.MustCompile("[a-z]+[0-9]+$")

func NewChimp(apiKey string, https bool) *ChimpAPI {
	u := url.URL{}
	if https {
		u.Scheme = "https"
	} else {
		u.Scheme = "http"
	}
	u.Host = fmt.Sprintf("%s.api.mailchimp.com", mailchimp_datacenter.FindString(apiKey))
	u.Path = mailchimp_version
	return &ChimpAPI{Key: apiKey, endpoint: u.String()}
}

func runChimp(api *ChimpAPI, path string, parameters interface{}) ([]byte, error) {
	b, err := json.Marshal(parameters)
	if err != nil {
		return nil, err
	}
	requestUrl := fmt.Sprintf("%s%s", api.endpoint, path)
	if debug {
		log.Printf("Request URL:%s", requestUrl)
	}
	client := &http.Client{Transport: api.Transport}
	resp, err := client.Post(requestUrl, "application/json", bytes.NewBuffer(b))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if debug {
		log.Printf("Response Body:%s", string(body))
	}
	if err = chimpErrorCheck(body); err != nil {
		return nil, err
	}
	return body, nil
}

func runMandrill(api *MandrillAPI, path string, parameters map[string]interface{}) ([]byte, error) {
	if parameters == nil {
		parameters = make(map[string]interface{})
	}
	parameters["key"] = api.Key
	b, err := json.Marshal(parameters)
	if debug {
		log.Printf("Payload:%s", string(b))
	}
	if err != nil {
		return nil, err
	}
	requestUrl := fmt.Sprintf("%s%s", api.endpoint, path)
	if debug {
		log.Printf("Request URL:%s", requestUrl)
	}
	client := &http.Client{Transport: api.Transport}
	resp, err := client.Post(requestUrl, "application/json", bytes.NewBuffer(b))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if debug {
		log.Printf("Response Body:%s", string(body))
	}
	if err = mandrillErrorCheck(body); err != nil {
		return nil, err
	}
	return body, nil
}

func parseString(body []byte, err error) (string, error) {
	if err != nil {
		return "", err
	}
	return strconv.Unquote(string(body))
}

func parseMandrillJson(api *MandrillAPI, path string, parameters map[string]interface{}, retval interface{}) error {
	body, err := runMandrill(api, path, parameters)
	if err != nil {
		return err
	}
	if err := json.Unmarshal(body, retval); err != nil {
		return err
	}

	return nil
}

func parseChimpJson(api *ChimpAPI, method string, parameters interface{}, retval interface{}) error {
	body, err := runChimp(api, method, parameters)

	if err != nil {
		return err
	}
	if retval != nil {
		return parseJson(body, retval)
	}
	return nil
}

type JsonAlterer interface {
	alterJson(b []byte) []byte
}

func parseJson(body []byte, retval interface{}) error {
	switch r := retval.(type) {
	case JsonAlterer:
		return json.Unmarshal(r.alterJson(body), retval)
	default:
		return json.Unmarshal(body, retval)
	}
}
<|endoftext|>


<commit_before>package gobot

import (
	"encoding/json"
	"github.com/codegangsta/martini"
	"net/http"
)

func Api(bot *Gobot) {
	m := martini.Classic()

	m.Get("/robots", func() string {
		return toJson(bot.Robots)
	})

	m.Get("/robots/:robotname", func(params martini.Params) string {
		return toJson(bot.FindRobot(params["robotname"]))
	})

	m.Get("/robots/:robotname/devices", func(params martini.Params) string {
		return toJson(bot.FindRobot(params["robotname"]).GetDevices())
	})

	m.Get("/robots/:robotname/devices/:devicename", func(params martini.Params) string {
		return toJson(bot.FindRobotDevice(params["robotname"], params["devicename"]))
	})

	m.Get("/robots/:robotname/devices/:devicename/commands", func(params martini.Params) string {
		return toJson(bot.FindRobotDevice(params["robotname"], params["devicename"]).Commands())
	})

	m.Post("/robots/:robotname/devices/:devicename/commands/:command", func(params martini.Params, res http.ResponseWriter, req *http.Request) string {
		decoder := json.NewDecoder(req.Body)
		var body map[string]interface{}
		decoder.Decode(&body)
		robot := bot.FindRobotDevice(params["robotname"], params["devicename"])
		commands := robot.Commands().([]string)
		for command := range commands {
			if commands[command] == params["command"] {
				ret := Call(robot.Driver, params["command"], body)
				return toJson(map[string]interface{}{"results": ret})
			}
		}
		return toJson(map[string]interface{}{"results": "Unknown Command"})
	})

	go m.Run()
}
<commit_msg>Accept POST and GET for commands<commit_after>package gobot

import (
	"encoding/json"
	"github.com/codegangsta/martini"
	"net/http"
)

type api struct{}

func Api(bot *Gobot) {
	a := new(api)
	m := martini.Classic()

	m.Get("/robots", func() string {
		return toJson(bot.Robots)
	})

	m.Get("/robots/:robotname", func(params martini.Params) string {
		return toJson(bot.FindRobot(params["robotname"]))
	})

	m.Get("/robots/:robotname/devices", func(params martini.Params) string {
		return toJson(bot.FindRobot(params["robotname"]).GetDevices())
	})

	m.Get("/robots/:robotname/devices/:devicename", func(params martini.Params) string {
		return toJson(bot.FindRobotDevice(params["robotname"], params["devicename"]))
	})

	m.Get("/robots/:robotname/devices/:devicename/commands", func(params martini.Params) string {
		return toJson(bot.FindRobotDevice(params["robotname"], params["devicename"]).Commands())
	})

	command_route := "/robots/:robotname/devices/:devicename/commands/:command"

	m.Get(command_route, func(params martini.Params, res http.ResponseWriter, req *http.Request) string {
		return a.executeCommand(bot, params, res, req)
	})
	m.Post(command_route, func(params martini.Params, res http.ResponseWriter, req *http.Request) string {
		return a.executeCommand(bot, params, res, req)
	})

	go m.Run()
}

func (a *api) executeCommand(bot *Gobot, params martini.Params, res http.ResponseWriter, req *http.Request) string {
	decoder := json.NewDecoder(req.Body)
	var body map[string]interface{}
	decoder.Decode(&body)
	robot := bot.FindRobotDevice(params["robotname"], params["devicename"])
	commands := robot.Commands().([]string)
	for command := range commands {
		if commands[command] == params["command"] {
			ret := Call(robot.Driver, params["command"], body)
			return toJson(map[string]interface{}{"results": ret})
		}
	}
	return toJson(map[string]interface{}{"results": "Unknown Command"})
}
<|endoftext|>


<commit_before>package air

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"net/http"
	"path"
	"reflect"
	"runtime"
	"sync"

	"golang.org/x/net/context"
)

type (
	// Air is the top-level framework struct.
	Air struct {
		pregases         []GasFunc
		gases            []GasFunc
		maxParam         *int
		notFoundHandler  HandlerFunc
		httpErrorHandler HTTPErrorHandler
		binder           Binder
		renderer         Renderer
		pool             sync.Pool
		debug            bool
		router           *Router
		logger           Logger
	}

	// Route contains a handler and information for matching against requests.
	Route struct {
		Method  string
		Path    string
		Handler string
	}

	// HTTPError represents an error that occurred while handling a request.
	HTTPError struct {
		Code    int
		Message string
	}

	// GasFunc defines a function to process gas.
	GasFunc func(HandlerFunc) HandlerFunc

	// HandlerFunc defines a function to server HTTP requests.
	HandlerFunc func(Context) error

	// HTTPErrorHandler is a centralized HTTP error handler.
	HTTPErrorHandler func(error, Context)

	// Validator is the interface that wraps the Validate function.
	Validator interface {
		Validate() error
	}

	// Renderer is the interface that wraps the Render function.
	Renderer interface {
		Render(io.Writer, string, interface{}, Context) error
	}
)

// HTTP methods (which follows the REST principles)
const (
	GET    = "GET"
	POST   = "POST"
	PUT    = "PUT" // The Air advise you to forget the PATCH.
	DELETE = "DELETE"
)

// MIME types
const (
	MIMEApplicationJSON       = "application/json; charset=utf-8"
	MIMEApplicationJavaScript = "application/javascript; charset=utf-8"
	MIMEApplicationXML        = "application/xml; charset=utf-8"
	MIMEApplicationForm       = "application/x-www-form-urlencoded"
	MIMEApplicationProtobuf   = "application/protobuf"
	MIMEApplicationMsgpack    = "application/msgpack"
	MIMETextHTML              = "text/html; charset=utf-8"
	MIMETextPlain             = "text/plain; charset=utf-8"
	MIMEMultipartForm         = "multipart/form-data"
	MIMEOctetStream           = "application/octet-stream"
)

// Headers
const (
	HeaderAcceptEncoding                = "Accept-Encoding"
	HeaderAllow                         = "Allow"
	HeaderAuthorization                 = "Authorization"
	HeaderContentDisposition            = "Content-Disposition"
	HeaderContentEncoding               = "Content-Encoding"
	HeaderContentLength                 = "Content-Length"
	HeaderContentType                   = "Content-Type"
	HeaderCookie                        = "Cookie"
	HeaderSetCookie                     = "Set-Cookie"
	HeaderIfModifiedSince               = "If-Modified-Since"
	HeaderLastModified                  = "Last-Modified"
	HeaderLocation                      = "Location"
	HeaderUpgrade                       = "Upgrade"
	HeaderVary                          = "Vary"
	HeaderWWWAuthenticate               = "WWW-Authenticate"
	HeaderXForwardedProto               = "X-Forwarded-Proto"
	HeaderXHTTPMethodOverride           = "X-HTTP-Method-Override"
	HeaderXForwardedFor                 = "X-Forwarded-For"
	HeaderXRealIP                       = "X-Real-IP"
	HeaderServer                        = "Server"
	HeaderOrigin                        = "Origin"
	HeaderAccessControlRequestMethod    = "Access-Control-Request-Method"
	HeaderAccessControlRequestHeaders   = "Access-Control-Request-Headers"
	HeaderAccessControlAllowOrigin      = "Access-Control-Allow-Origin"
	HeaderAccessControlAllowMethods     = "Access-Control-Allow-Methods"
	HeaderAccessControlAllowHeaders     = "Access-Control-Allow-Headers"
	HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
	HeaderAccessControlExposeHeaders    = "Access-Control-Expose-Headers"
	HeaderAccessControlMaxAge           = "Access-Control-Max-Age"

	// Security
	HeaderStrictTransportSecurity = "Strict-Transport-Security"
	HeaderXContentTypeOptions     = "X-Content-Type-Options"
	HeaderXXSSProtection          = "X-XSS-Protection"
	HeaderXFrameOptions           = "X-Frame-Options"
	HeaderContentSecurityPolicy   = "Content-Security-Policy"
	HeaderXCSRFToken              = "X-CSRF-Token"
)

// For easy for-range
var methods = [4]string{GET, POST, PUT, DELETE}

// Errors
var (
	ErrUnauthorized                = NewHTTPError(http.StatusUnauthorized)          // 401
	ErrNotFound                    = NewHTTPError(http.StatusNotFound)              // 404
	ErrMethodNotAllowed            = NewHTTPError(http.StatusMethodNotAllowed)      // 405
	ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge) // 413
	ErrUnsupportedMediaType        = NewHTTPError(http.StatusUnsupportedMediaType)  // 415

	ErrInternalServerError = NewHTTPError(http.StatusInternalServerError) // 500
	ErrBadGateway          = NewHTTPError(http.StatusBadGateway)          // 502
	ErrServiceUnavailable  = NewHTTPError(http.StatusServiceUnavailable)  // 503
	ErrGatewayTimeout      = NewHTTPError(http.StatusGatewayTimeout)      // 504

	ErrRendererNotRegistered = errors.New("Renderer Not Registered")
	ErrInvalidRedirectCode   = errors.New("Invalid Redirect Status Code")
	ErrCookieNotFound        = errors.New("Cookie Not Found")
)

// Error handlers
var (
	NotFoundHandler = func(c Context) error {
		return ErrNotFound
	}

	MethodNotAllowedHandler = func(c Context) error {
		return ErrMethodNotAllowed
	}
)

// New creates an instance of Air.
func New() *Air {
	a := &Air{maxParam: new(int)}
	a.pool.New = func() interface{} {
		return a.NewContext(nil, nil)
	}
	a.router = NewRouter(a)

	// Defaults
	a.SetHTTPErrorHandler(a.DefaultHTTPErrorHandler)
	a.SetBinder(&airBinder{})
	l := NewLogger("air")
	l.SetLevel(ERROR)
	a.SetLogger(l)
	return a
}

// NewContext returns a Context instance.
func (a *Air) NewContext(req Request, res Response) Context {
	return &airContext{
		context:  context.Background(),
		request:  req,
		response: res,
		air:      a,
		pvalues:  make([]string, *a.maxParam),
		handler:  NotFoundHandler,
	}
}

// Router returns router.
func (a *Air) Router() *Router {
	return a.router
}

// Logger returns the logger instance.
func (a *Air) Logger() Logger {
	return a.logger
}

// SetLogger defines a custom logger.
func (a *Air) SetLogger(l Logger) {
	a.logger = l
}

// SetLogOutput sets the output destination for the logger. Default value is `os.Std*`
func (a *Air) SetLogOutput(w io.Writer) {
	a.logger.SetOutput(w)
}

// SetLogLevel sets the log level for the logger. Default value ERROR.
func (a *Air) SetLogLevel(l LoggerLevel) {
	a.logger.SetLevel(l)
}

// DefaultHTTPErrorHandler invokes the default HTTP error handler.
func (a *Air) DefaultHTTPErrorHandler(err error, c Context) {
	code := http.StatusInternalServerError
	msg := http.StatusText(code)
	if he, ok := err.(*HTTPError); ok {
		code = he.Code
		msg = he.Message
	}
	if a.debug {
		msg = err.Error()
	}
	if !c.Response().Committed() {
		c.String(code, msg)
	}
	a.logger.Error(err)
}

// SetHTTPErrorHandler registers a custom Air.HTTPErrorHandler.
func (a *Air) SetHTTPErrorHandler(h HTTPErrorHandler) {
	a.httpErrorHandler = h
}

// Binder returns the binder instance.
func (a *Air) Binder() Binder {
	return a.binder
}

// SetBinder registers a custom binder. It's invoked by `Context#Bind()`.
func (a *Air) SetBinder(b Binder) {
	a.binder = b
}

// SetRenderer registers an HTML template renderer. It's invoked by `Context#Render()`.
func (a *Air) SetRenderer(r Renderer) {
	a.renderer = r
}

// Debug returns debug mode (enabled or disabled).
func (a *Air) Debug() bool {
	return a.debug
}

// SetDebug enable/disable debug mode.
func (a *Air) SetDebug(on bool) {
	a.debug = on
	a.SetLogLevel(DEBUG)
}

// Precontain adds gases to the chain which is run before router.
func (a *Air) Precontain(gases ...GasFunc) {
	a.pregases = append(a.pregases, gases...)
}

// Contain adds gases to the chain which is run after router.
func (a *Air) Contain(gases ...GasFunc) {
	a.gases = append(a.gases, gases...)
}

// GET registers a new GET route for a path with matching handler in the router
// with optional route-level gases.
func (a *Air) GET(path string, handler HandlerFunc, gases ...GasFunc) {
	a.add(GET, path, handler, gases...)
}

// POST registers a new POST route for a path with matching handler in the
// router with optional route-level gases.
func (a *Air) POST(path string, handler HandlerFunc, gases ...GasFunc) {
	a.add(POST, path, handler, gases...)
}

// PUT registers a new PUT route for a path with matching handler in the
// router with optional route-level gases.
func (a *Air) PUT(path string, handler HandlerFunc, gases ...GasFunc) {
	a.add(PUT, path, handler, gases...)
}

// DELETE registers a new DELETE route for a path with matching handler in the router
// with optional route-level gases.
func (a *Air) DELETE(path string, handler HandlerFunc, gases ...GasFunc) {
	a.add(DELETE, path, handler, gases...)
}

// Static registers a new route with path prefix to serve static files from the
// provided root directory.
func (a *Air) Static(prefix, root string) {
	a.GET(prefix+"*", func(c Context) error {
		return c.File(path.Join(root, c.P(0)))
	})
}

// File registers a new route with path to serve a static file.
func (a *Air) File(path, file string) {
	a.GET(path, func(c Context) error {
		return c.File(file)
	})
}

func (a *Air) add(method, path string, handler HandlerFunc, gases ...GasFunc) {
	name := handlerName(handler)
	a.router.Add(method, path, func(c Context) error {
		h := handler
		// Chain gases
		for i := len(gases) - 1; i >= 0; i-- {
			h = gases[i](h)
		}
		return h(c)
	}, a)
	r := Route{
		Method:  method,
		Path:    path,
		Handler: name,
	}
	a.router.routes[method+path] = r
}

// Group creates a new router group with prefix and optional group-level gases.
func (a *Air) Group(prefix string, gases ...GasFunc) *Group {
	g := &Group{prefix: prefix, air: a}
	g.Contain(gases...)
	return g
}

// URI generates a URI from handler.
func (a *Air) URI(handler HandlerFunc, params ...interface{}) string {
	uri := new(bytes.Buffer)
	ln := len(params)
	n := 0
	name := handlerName(handler)
	for _, r := range a.router.routes {
		if r.Handler == name {
			for i, l := 0, len(r.Path); i < l; i++ {
				if r.Path[i] == ':' && n < ln {
					for ; i < l && r.Path[i] != '/'; i++ {
					}
					uri.WriteString(fmt.Sprintf("%v", params[n]))
					n++
				}
				if i < l {
					uri.WriteByte(r.Path[i])
				}
			}
			break
		}
	}
	return uri.String()
}

// Routes returns the registered routes.
func (a *Air) Routes() []Route {
	routes := []Route{}
	for _, v := range a.router.routes {
		routes = append(routes, v)
	}
	return routes
}

// AcquireContext returns an empty `Context` instance from the pool.
// You must be return the context by calling `ReleaseContext()`.
func (a *Air) AcquireContext() Context {
	return a.pool.Get().(Context)
}

// ReleaseContext returns the `Context` instance back to the pool.
// You must call it after `AcquireContext()`.
func (a *Air) ReleaseContext(c Context) {
	a.pool.Put(c)
}

func (a *Air) ServeHTTP(req Request, res Response) {
	c := a.pool.Get().(*airContext)
	c.Reset(req, res)

	// Gases
	h := func(Context) error {
		method := req.Method()
		path := req.URI().Path()
		a.router.Find(method, path, c)
		h := c.handler
		for i := len(a.gases) - 1; i >= 0; i-- {
			h = a.gases[i](h)
		}
		return h(c)
	}

	// Pregases
	for i := len(a.pregases) - 1; i >= 0; i-- {
		h = a.pregases[i](h)
	}

	// Execute chain
	if err := h(c); err != nil {
		a.httpErrorHandler(err, c)
	}

	a.pool.Put(c)
}

// Run starts the HTTP server.
func (a *Air) Run(addr string) {
	s := NewServer(addr)
	s.SetHandler(a)
	s.SetLogger(a.logger)
	if a.Debug() {
		a.logger.Debug("running in debug mode")
	}
	a.logger.Error(s.Start())
}

// NewHTTPError creates a new HTTPError instance.
func NewHTTPError(code int, msg ...string) *HTTPError {
	he := &HTTPError{Code: code, Message: http.StatusText(code)}
	if len(msg) > 0 {
		he.Message = msg[0]
	}
	return he
}

// Error makes it compatible with `error` interface.
func (he *HTTPError) Error() string {
	return he.Message
}

// WrapGas wrap `HandlerFunc` into `GasFunc`.
func WrapGas(handler HandlerFunc) GasFunc {
	return func(next HandlerFunc) HandlerFunc {
		return func(c Context) error {
			if err := handler(c); err != nil {
				return err
			}
			return next(c)
		}
	}
}

func handlerName(handler HandlerFunc) string {
	t := reflect.ValueOf(handler).Type()
	if t.Kind() == reflect.Func {
		return runtime.FuncForPC(reflect.ValueOf(handler).Pointer()).Name()
	}
	return t.String()
}
<commit_msg>Fix air.SetLoglevel bug<commit_after>package air

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"net/http"
	"path"
	"reflect"
	"runtime"
	"sync"

	"golang.org/x/net/context"
)

type (
	// Air is the top-level framework struct.
	Air struct {
		pregases         []GasFunc
		gases            []GasFunc
		maxParam         *int
		notFoundHandler  HandlerFunc
		httpErrorHandler HTTPErrorHandler
		binder           Binder
		renderer         Renderer
		pool             sync.Pool
		debug            bool
		router           *Router
		logger           Logger
	}

	// Route contains a handler and information for matching against requests.
	Route struct {
		Method  string
		Path    string
		Handler string
	}

	// HTTPError represents an error that occurred while handling a request.
	HTTPError struct {
		Code    int
		Message string
	}

	// GasFunc defines a function to process gas.
	GasFunc func(HandlerFunc) HandlerFunc

	// HandlerFunc defines a function to server HTTP requests.
	HandlerFunc func(Context) error

	// HTTPErrorHandler is a centralized HTTP error handler.
	HTTPErrorHandler func(error, Context)

	// Validator is the interface that wraps the Validate function.
	Validator interface {
		Validate() error
	}

	// Renderer is the interface that wraps the Render function.
	Renderer interface {
		Render(io.Writer, string, interface{}, Context) error
	}
)

// HTTP methods (which follows the REST principles)
const (
	GET    = "GET"
	POST   = "POST"
	PUT    = "PUT" // The Air advise you to forget the PATCH.
	DELETE = "DELETE"
)

// MIME types
const (
	MIMEApplicationJSON       = "application/json; charset=utf-8"
	MIMEApplicationJavaScript = "application/javascript; charset=utf-8"
	MIMEApplicationXML        = "application/xml; charset=utf-8"
	MIMEApplicationForm       = "application/x-www-form-urlencoded"
	MIMEApplicationProtobuf   = "application/protobuf"
	MIMEApplicationMsgpack    = "application/msgpack"
	MIMETextHTML              = "text/html; charset=utf-8"
	MIMETextPlain             = "text/plain; charset=utf-8"
	MIMEMultipartForm         = "multipart/form-data"
	MIMEOctetStream           = "application/octet-stream"
)

// Headers
const (
	HeaderAcceptEncoding                = "Accept-Encoding"
	HeaderAllow                         = "Allow"
	HeaderAuthorization                 = "Authorization"
	HeaderContentDisposition            = "Content-Disposition"
	HeaderContentEncoding               = "Content-Encoding"
	HeaderContentLength                 = "Content-Length"
	HeaderContentType                   = "Content-Type"
	HeaderCookie                        = "Cookie"
	HeaderSetCookie                     = "Set-Cookie"
	HeaderIfModifiedSince               = "If-Modified-Since"
	HeaderLastModified                  = "Last-Modified"
	HeaderLocation                      = "Location"
	HeaderUpgrade                       = "Upgrade"
	HeaderVary                          = "Vary"
	HeaderWWWAuthenticate               = "WWW-Authenticate"
	HeaderXForwardedProto               = "X-Forwarded-Proto"
	HeaderXHTTPMethodOverride           = "X-HTTP-Method-Override"
	HeaderXForwardedFor                 = "X-Forwarded-For"
	HeaderXRealIP                       = "X-Real-IP"
	HeaderServer                        = "Server"
	HeaderOrigin                        = "Origin"
	HeaderAccessControlRequestMethod    = "Access-Control-Request-Method"
	HeaderAccessControlRequestHeaders   = "Access-Control-Request-Headers"
	HeaderAccessControlAllowOrigin      = "Access-Control-Allow-Origin"
	HeaderAccessControlAllowMethods     = "Access-Control-Allow-Methods"
	HeaderAccessControlAllowHeaders     = "Access-Control-Allow-Headers"
	HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
	HeaderAccessControlExposeHeaders    = "Access-Control-Expose-Headers"
	HeaderAccessControlMaxAge           = "Access-Control-Max-Age"

	// Security
	HeaderStrictTransportSecurity = "Strict-Transport-Security"
	HeaderXContentTypeOptions     = "X-Content-Type-Options"
	HeaderXXSSProtection          = "X-XSS-Protection"
	HeaderXFrameOptions           = "X-Frame-Options"
	HeaderContentSecurityPolicy   = "Content-Security-Policy"
	HeaderXCSRFToken              = "X-CSRF-Token"
)

// For easy for-range
var methods = [4]string{GET, POST, PUT, DELETE}

// Errors
var (
	ErrUnauthorized                = NewHTTPError(http.StatusUnauthorized)          // 401
	ErrNotFound                    = NewHTTPError(http.StatusNotFound)              // 404
	ErrMethodNotAllowed            = NewHTTPError(http.StatusMethodNotAllowed)      // 405
	ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge) // 413
	ErrUnsupportedMediaType        = NewHTTPError(http.StatusUnsupportedMediaType)  // 415

	ErrInternalServerError = NewHTTPError(http.StatusInternalServerError) // 500
	ErrBadGateway          = NewHTTPError(http.StatusBadGateway)          // 502
	ErrServiceUnavailable  = NewHTTPError(http.StatusServiceUnavailable)  // 503
	ErrGatewayTimeout      = NewHTTPError(http.StatusGatewayTimeout)      // 504

	ErrRendererNotRegistered = errors.New("Renderer Not Registered")
	ErrInvalidRedirectCode   = errors.New("Invalid Redirect Status Code")
	ErrCookieNotFound        = errors.New("Cookie Not Found")
)

// Error handlers
var (
	NotFoundHandler = func(c Context) error {
		return ErrNotFound
	}

	MethodNotAllowedHandler = func(c Context) error {
		return ErrMethodNotAllowed
	}
)

// New creates an instance of Air.
func New() *Air {
	a := &Air{maxParam: new(int)}
	a.pool.New = func() interface{} {
		return a.NewContext(nil, nil)
	}
	a.router = NewRouter(a)

	// Defaults
	a.SetHTTPErrorHandler(a.DefaultHTTPErrorHandler)
	a.SetBinder(&airBinder{})
	l := NewLogger("air")
	l.SetLevel(ERROR)
	a.SetLogger(l)
	return a
}

// NewContext returns a Context instance.
func (a *Air) NewContext(req Request, res Response) Context {
	return &airContext{
		context:  context.Background(),
		request:  req,
		response: res,
		air:      a,
		pvalues:  make([]string, *a.maxParam),
		handler:  NotFoundHandler,
	}
}

// Router returns router.
func (a *Air) Router() *Router {
	return a.router
}

// Logger returns the logger instance.
func (a *Air) Logger() Logger {
	return a.logger
}

// SetLogger defines a custom logger.
func (a *Air) SetLogger(l Logger) {
	a.logger = l
}

// SetLogOutput sets the output destination for the logger. Default value is `os.Std*`
func (a *Air) SetLogOutput(w io.Writer) {
	a.logger.SetOutput(w)
}

// SetLogLevel sets the log level for the logger. Default value ERROR.
func (a *Air) SetLogLevel(l LoggerLevel) {
	a.logger.SetLevel(l)
}

// DefaultHTTPErrorHandler invokes the default HTTP error handler.
func (a *Air) DefaultHTTPErrorHandler(err error, c Context) {
	code := http.StatusInternalServerError
	msg := http.StatusText(code)
	if he, ok := err.(*HTTPError); ok {
		code = he.Code
		msg = he.Message
	}
	if a.debug {
		msg = err.Error()
	}
	if !c.Response().Committed() {
		c.String(code, msg)
	}
	a.logger.Error(err)
}

// SetHTTPErrorHandler registers a custom Air.HTTPErrorHandler.
func (a *Air) SetHTTPErrorHandler(h HTTPErrorHandler) {
	a.httpErrorHandler = h
}

// Binder returns the binder instance.
func (a *Air) Binder() Binder {
	return a.binder
}

// SetBinder registers a custom binder. It's invoked by `Context#Bind()`.
func (a *Air) SetBinder(b Binder) {
	a.binder = b
}

// SetRenderer registers an HTML template renderer. It's invoked by `Context#Render()`.
func (a *Air) SetRenderer(r Renderer) {
	a.renderer = r
}

// Debug returns debug mode (enabled or disabled).
func (a *Air) Debug() bool {
	return a.debug
}

// SetDebug enables/disables debug mode.
func (a *Air) SetDebug(on bool) {
	a.debug = on
}

// Precontain adds gases to the chain which is run before router.
func (a *Air) Precontain(gases ...GasFunc) {
	a.pregases = append(a.pregases, gases...)
}

// Contain adds gases to the chain which is run after router.
func (a *Air) Contain(gases ...GasFunc) {
	a.gases = append(a.gases, gases...)
}

// GET registers a new GET route for a path with matching handler in the router
// with optional route-level gases.
func (a *Air) GET(path string, handler HandlerFunc, gases ...GasFunc) {
	a.add(GET, path, handler, gases...)
}

// POST registers a new POST route for a path with matching handler in the
// router with optional route-level gases.
func (a *Air) POST(path string, handler HandlerFunc, gases ...GasFunc) {
	a.add(POST, path, handler, gases...)
}

// PUT registers a new PUT route for a path with matching handler in the
// router with optional route-level gases.
func (a *Air) PUT(path string, handler HandlerFunc, gases ...GasFunc) {
	a.add(PUT, path, handler, gases...)
}

// DELETE registers a new DELETE route for a path with matching handler in the router
// with optional route-level gases.
func (a *Air) DELETE(path string, handler HandlerFunc, gases ...GasFunc) {
	a.add(DELETE, path, handler, gases...)
}

// Static registers a new route with path prefix to serve static files from the
// provided root directory.
func (a *Air) Static(prefix, root string) {
	a.GET(prefix+"*", func(c Context) error {
		return c.File(path.Join(root, c.P(0)))
	})
}

// File registers a new route with path to serve a static file.
func (a *Air) File(path, file string) {
	a.GET(path, func(c Context) error {
		return c.File(file)
	})
}

func (a *Air) add(method, path string, handler HandlerFunc, gases ...GasFunc) {
	name := handlerName(handler)
	a.router.Add(method, path, func(c Context) error {
		h := handler
		// Chain gases
		for i := len(gases) - 1; i >= 0; i-- {
			h = gases[i](h)
		}
		return h(c)
	}, a)
	r := Route{
		Method:  method,
		Path:    path,
		Handler: name,
	}
	a.router.routes[method+path] = r
}

// Group creates a new router group with prefix and optional group-level gases.
func (a *Air) Group(prefix string, gases ...GasFunc) *Group {
	g := &Group{prefix: prefix, air: a}
	g.Contain(gases...)
	return g
}

// URI generates a URI from handler.
func (a *Air) URI(handler HandlerFunc, params ...interface{}) string {
	uri := new(bytes.Buffer)
	ln := len(params)
	n := 0
	name := handlerName(handler)
	for _, r := range a.router.routes {
		if r.Handler == name {
			for i, l := 0, len(r.Path); i < l; i++ {
				if r.Path[i] == ':' && n < ln {
					for ; i < l && r.Path[i] != '/'; i++ {
					}
					uri.WriteString(fmt.Sprintf("%v", params[n]))
					n++
				}
				if i < l {
					uri.WriteByte(r.Path[i])
				}
			}
			break
		}
	}
	return uri.String()
}

// Routes returns the registered routes.
func (a *Air) Routes() []Route {
	routes := []Route{}
	for _, v := range a.router.routes {
		routes = append(routes, v)
	}
	return routes
}

// AcquireContext returns an empty `Context` instance from the pool.
// You must be return the context by calling `ReleaseContext()`.
func (a *Air) AcquireContext() Context {
	return a.pool.Get().(Context)
}

// ReleaseContext returns the `Context` instance back to the pool.
// You must call it after `AcquireContext()`.
func (a *Air) ReleaseContext(c Context) {
	a.pool.Put(c)
}

func (a *Air) ServeHTTP(req Request, res Response) {
	c := a.pool.Get().(*airContext)
	c.Reset(req, res)

	// Gases
	h := func(Context) error {
		method := req.Method()
		path := req.URI().Path()
		a.router.Find(method, path, c)
		h := c.handler
		for i := len(a.gases) - 1; i >= 0; i-- {
			h = a.gases[i](h)
		}
		return h(c)
	}

	// Pregases
	for i := len(a.pregases) - 1; i >= 0; i-- {
		h = a.pregases[i](h)
	}

	// Execute chain
	if err := h(c); err != nil {
		a.httpErrorHandler(err, c)
	}

	a.pool.Put(c)
}

// Run starts the HTTP server.
func (a *Air) Run(addr string) {
	s := NewServer(addr)
	s.SetHandler(a)
	s.SetLogger(a.logger)
	if a.Debug() {
		a.SetLogLevel(DEBUG)
		a.logger.Debug("running in debug mode")
	}
	a.logger.Error(s.Start())
}

// NewHTTPError creates a new HTTPError instance.
func NewHTTPError(code int, msg ...string) *HTTPError {
	he := &HTTPError{Code: code, Message: http.StatusText(code)}
	if len(msg) > 0 {
		he.Message = msg[0]
	}
	return he
}

// Error makes it compatible with `error` interface.
func (he *HTTPError) Error() string {
	return he.Message
}

// WrapGas wrap `HandlerFunc` into `GasFunc`.
func WrapGas(handler HandlerFunc) GasFunc {
	return func(next HandlerFunc) HandlerFunc {
		return func(c Context) error {
			if err := handler(c); err != nil {
				return err
			}
			return next(c)
		}
	}
}

func handlerName(handler HandlerFunc) string {
	t := reflect.ValueOf(handler).Type()
	if t.Kind() == reflect.Func {
		return runtime.FuncForPC(reflect.ValueOf(handler).Pointer()).Name()
	}
	return t.String()
}
<|endoftext|>