<commit_before>/*
 * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
 *
 * 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.
 */

package cmd

import (
	"net/http"
	"path"
	"strings"
	"time"

	humanize "github.com/dustin/go-humanize"
	router "github.com/gorilla/mux"
	"github.com/rs/cors"
)

// HandlerFunc - useful to chain different middleware http.Handler
type HandlerFunc func(http.Handler) http.Handler

func registerHandlers(mux *router.Router, handlerFns ...HandlerFunc) http.Handler {
	var f http.Handler
	f = mux
	for _, hFn := range handlerFns {
		f = hFn(f)
	}
	return f
}

// Adds limiting body size middleware

// Maximum allowed form data field values. 64MiB is a guessed practical value
// which is more than enough to accommodate any form data fields and headers.
const requestFormDataSize = 64 * humanize.MiByte

// For any HTTP request, request body should be not more than 5GiB + requestFormDataSize
// where, 5GiB is the maximum allowed object size for object upload.
const requestMaxBodySize = 5*humanize.GiByte + requestFormDataSize

type requestSizeLimitHandler struct {
	handler     http.Handler
	maxBodySize int64
}

func setRequestSizeLimitHandler(h http.Handler) http.Handler {
	return requestSizeLimitHandler{handler: h, maxBodySize: requestMaxBodySize}
}

func (h requestSizeLimitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Restricting read data to a given maximum length
	r.Body = http.MaxBytesReader(w, r.Body, h.maxBodySize)
	h.handler.ServeHTTP(w, r)
}

// Reserved bucket.
const (
	reservedBucket = "/minio"
)

// Adds redirect rules for incoming requests.
type redirectHandler struct {
	handler http.Handler
}

func setBrowserRedirectHandler(h http.Handler) http.Handler {
	return redirectHandler{handler: h}
}

// Fetch redirect location if urlPath satisfies certain
// criteria. Some special names are considered to be
// redirectable, this is purely internal function and
// serves only limited purpose on redirect-handler for
// browser requests.
func getRedirectLocation(urlPath string) (rLocation string) {
	if urlPath == reservedBucket {
		rLocation = reservedBucket + "/"
	}
	if contains([]string{
		"/",
		"/webrpc",
		"/login",
		"/favicon.ico",
	}, urlPath) {
		rLocation = reservedBucket + urlPath
	}
	return rLocation
}

// guessIsBrowserReq - returns true if the request is browser.
// This implementation just validates user-agent and
// looks for "Mozilla" string. This is no way certifiable
// way to know if the request really came from a browser
// since User-Agent's can be arbitrary. But this is just
// a best effort function.
func guessIsBrowserReq(req *http.Request) bool {
	if req == nil {
		return false
	}
	return strings.Contains(req.Header.Get("User-Agent"), "Mozilla")
}

func (h redirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	aType := getRequestAuthType(r)
	// Re-direct only for JWT and anonymous requests from browser.
	if aType == authTypeJWT || aType == authTypeAnonymous {
		// Re-direction is handled specifically for browser requests.
		if guessIsBrowserReq(r) && globalIsBrowserEnabled {
			// Fetch the redirect location if any.
			redirectLocation := getRedirectLocation(r.URL.Path)
			if redirectLocation != "" {
				// Employ a temporary re-direct.
				http.Redirect(w, r, redirectLocation, http.StatusTemporaryRedirect)
				return
			}
		}
	}
	h.handler.ServeHTTP(w, r)
}

// Adds Cache-Control header
type cacheControlHandler struct {
	handler http.Handler
}

func setBrowserCacheControlHandler(h http.Handler) http.Handler {
	return cacheControlHandler{h}
}

func (h cacheControlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method == httpGET && guessIsBrowserReq(r) && globalIsBrowserEnabled {
		// For all browser requests set appropriate Cache-Control policies
		if hasPrefix(r.URL.Path, reservedBucket+"/") {
			if hasSuffix(r.URL.Path, ".js") || r.URL.Path == reservedBucket+"/favicon.ico" {
				// For assets set cache expiry of one year. For each release, the name
				// of the asset name will change and hence it can not be served from cache.
				w.Header().Set("Cache-Control", "max-age=31536000")
			} else {
				// For non asset requests we serve index.html which will never be cached.
				w.Header().Set("Cache-Control", "no-store")
			}
		}
	}

	h.handler.ServeHTTP(w, r)
}

// Adds verification for incoming paths.
type minioPrivateBucketHandler struct {
	handler        http.Handler
	reservedBucket string
}

func setPrivateBucketHandler(h http.Handler) http.Handler {
	return minioPrivateBucketHandler{handler: h, reservedBucket: reservedBucket}
}

func (h minioPrivateBucketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// For all non browser requests, reject access to 'reservedBucket'.
	if !guessIsBrowserReq(r) && path.Clean(r.URL.Path) == reservedBucket {
		writeErrorResponse(w, ErrAllAccessDisabled, r.URL)
		return
	}
	h.handler.ServeHTTP(w, r)
}

type timeValidityHandler struct {
	handler http.Handler
}

// setTimeValidityHandler to validate parsable time over http header
func setTimeValidityHandler(h http.Handler) http.Handler {
	return timeValidityHandler{h}
}

// Supported Amz date formats.
var amzDateFormats = []string{
	time.RFC1123,
	time.RFC1123Z,
	iso8601Format,
	// Add new AMZ date formats here.
}

// Supported Amz date headers.
var amzDateHeaders = []string{
	"x-amz-date",
	"date",
}

// parseAmzDate - parses date string into supported amz date formats.
func parseAmzDate(amzDateStr string) (amzDate time.Time, apiErr APIErrorCode) {
	for _, dateFormat := range amzDateFormats {
		amzDate, err := time.Parse(dateFormat, amzDateStr)
		if err == nil {
			return amzDate, ErrNone
		}
	}
	return time.Time{}, ErrMalformedDate
}

// parseAmzDateHeader - parses supported amz date headers, in
// supported amz date formats.
func parseAmzDateHeader(req *http.Request) (time.Time, APIErrorCode) {
	for _, amzDateHeader := range amzDateHeaders {
		amzDateStr := req.Header.Get(http.CanonicalHeaderKey(amzDateHeader))
		if amzDateStr != "" {
			return parseAmzDate(amzDateStr)
		}
	}
	// Date header missing.
	return time.Time{}, ErrMissingDateHeader
}

func (h timeValidityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	aType := getRequestAuthType(r)
	if aType == authTypeSigned || aType == authTypeSignedV2 || aType == authTypeStreamingSigned {
		// Verify if date headers are set, if not reject the request
		amzDate, apiErr := parseAmzDateHeader(r)
		if apiErr != ErrNone {
			// All our internal APIs are sensitive towards Date
			// header, for all requests where Date header is not
			// present we will reject such clients.
			writeErrorResponse(w, apiErr, r.URL)
			return
		}
		// Verify if the request date header is shifted by less than globalMaxSkewTime parameter in the past
		// or in the future, reject request otherwise.
		curTime := time.Now().UTC()
		if curTime.Sub(amzDate) > globalMaxSkewTime || amzDate.Sub(curTime) > globalMaxSkewTime {
			writeErrorResponse(w, ErrRequestTimeTooSkewed, r.URL)
			return
		}
	}
	h.handler.ServeHTTP(w, r)
}

type resourceHandler struct {
	handler http.Handler
}

// List of http methods.
const (
	httpGET     = "GET"
	httpPUT     = "PUT"
	httpHEAD    = "HEAD"
	httpPOST    = "POST"
	httpDELETE  = "DELETE"
	httpOPTIONS = "OPTIONS"
)

// List of default allowable HTTP methods.
var defaultAllowableHTTPMethods = []string{
	httpGET,
	httpPUT,
	httpHEAD,
	httpPOST,
	httpDELETE,
	httpOPTIONS,
}

// setCorsHandler handler for CORS (Cross Origin Resource Sharing)
func setCorsHandler(h http.Handler) http.Handler {
	c := cors.New(cors.Options{
		AllowedOrigins: []string{"*"},
		AllowedMethods: defaultAllowableHTTPMethods,
		AllowedHeaders: []string{"*"},
		ExposedHeaders: []string{"ETag"},
	})
	return c.Handler(h)
}

// setIgnoreResourcesHandler -
// Ignore resources handler is wrapper handler used for API request resource validation
// Since we do not support all the S3 queries, it is necessary for us to throw back a
// valid error message indicating that requested feature is not implemented.
func setIgnoreResourcesHandler(h http.Handler) http.Handler {
	return resourceHandler{h}
}

// Checks requests for not implemented Bucket resources
func ignoreNotImplementedBucketResources(req *http.Request) bool {
	for name := range req.URL.Query() {
		if notimplementedBucketResourceNames[name] {
			return true
		}
	}
	return false
}

// Checks requests for not implemented Object resources
func ignoreNotImplementedObjectResources(req *http.Request) bool {
	for name := range req.URL.Query() {
		if notimplementedObjectResourceNames[name] {
			return true
		}
	}
	return false
}

// List of not implemented bucket queries
var notimplementedBucketResourceNames = map[string]bool{
	"acl":            true,
	"cors":           true,
	"lifecycle":      true,
	"logging":        true,
	"replication":    true,
	"tagging":        true,
	"versions":       true,
	"requestPayment": true,
	"versioning":     true,
	"website":        true,
}

// List of not implemented object queries
var notimplementedObjectResourceNames = map[string]bool{
	"torrent": true,
	"acl":     true,
	"policy":  true,
}

// Resource handler ServeHTTP() wrapper
func (h resourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	bucketName, objectName := urlPath2BucketObjectName(r.URL)

	// If bucketName is present and not objectName check for bucket level resource queries.
	if bucketName != "" && objectName == "" {
		if ignoreNotImplementedBucketResources(r) {
			writeErrorResponse(w, ErrNotImplemented, r.URL)
			return
		}
	}
	// If bucketName and objectName are present check for its resource queries.
	if bucketName != "" && objectName != "" {
		if ignoreNotImplementedObjectResources(r) {
			writeErrorResponse(w, ErrNotImplemented, r.URL)
			return
		}
	}
	// A put method on path "/" doesn't make sense, ignore it.
	if r.Method == httpPUT && r.URL.Path == "/" {
		writeErrorResponse(w, ErrNotImplemented, r.URL)
		return
	}

	// Serve HTTP.
	h.handler.ServeHTTP(w, r)
}

// httpResponseRecorder wraps http.ResponseWriter
// to record some useful http response data.
type httpResponseRecorder struct {
	http.ResponseWriter
	http.Flusher
	respStatusCode int
}

// Wraps ResponseWriter's Write()
func (rww *httpResponseRecorder) Write(b []byte) (int, error) {
	return rww.ResponseWriter.Write(b)
}

// Wraps ResponseWriter's Flush()
func (rww *httpResponseRecorder) Flush() {
	f, ok := rww.ResponseWriter.(http.Flusher)
	if ok {
		f.Flush()
	}
}

// Wraps ResponseWriter's WriteHeader() and record
// the response status code
func (rww *httpResponseRecorder) WriteHeader(httpCode int) {
	rww.respStatusCode = httpCode
	rww.ResponseWriter.WriteHeader(httpCode)
}

// httpStatsHandler definition: gather HTTP statistics
type httpStatsHandler struct {
	handler http.Handler
}

// setHttpStatsHandler sets a http Stats Handler
func setHTTPStatsHandler(h http.Handler) http.Handler {
	return httpStatsHandler{handler: h}
}

func (h httpStatsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Wraps w to record http response information
	ww := &httpResponseRecorder{ResponseWriter: w}

	// Execute the request
	h.handler.ServeHTTP(ww, r)

	// Update http statistics
	globalHTTPStats.updateStats(r, ww)
}
<commit_msg>Let hijacker and flusher interfaces to be reflected. (#3709)<commit_after>/*
 * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
 *
 * 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.
 */

package cmd

import (
	"bufio"
	"net"
	"net/http"
	"path"
	"strings"
	"time"

	humanize "github.com/dustin/go-humanize"
	router "github.com/gorilla/mux"
	"github.com/rs/cors"
)

// HandlerFunc - useful to chain different middleware http.Handler
type HandlerFunc func(http.Handler) http.Handler

func registerHandlers(mux *router.Router, handlerFns ...HandlerFunc) http.Handler {
	var f http.Handler
	f = mux
	for _, hFn := range handlerFns {
		f = hFn(f)
	}
	return f
}

// Adds limiting body size middleware

// Maximum allowed form data field values. 64MiB is a guessed practical value
// which is more than enough to accommodate any form data fields and headers.
const requestFormDataSize = 64 * humanize.MiByte

// For any HTTP request, request body should be not more than 5GiB + requestFormDataSize
// where, 5GiB is the maximum allowed object size for object upload.
const requestMaxBodySize = 5*humanize.GiByte + requestFormDataSize

type requestSizeLimitHandler struct {
	handler     http.Handler
	maxBodySize int64
}

func setRequestSizeLimitHandler(h http.Handler) http.Handler {
	return requestSizeLimitHandler{handler: h, maxBodySize: requestMaxBodySize}
}

func (h requestSizeLimitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Restricting read data to a given maximum length
	r.Body = http.MaxBytesReader(w, r.Body, h.maxBodySize)
	h.handler.ServeHTTP(w, r)
}

// Reserved bucket.
const (
	reservedBucket = "/minio"
)

// Adds redirect rules for incoming requests.
type redirectHandler struct {
	handler http.Handler
}

func setBrowserRedirectHandler(h http.Handler) http.Handler {
	return redirectHandler{handler: h}
}

// Fetch redirect location if urlPath satisfies certain
// criteria. Some special names are considered to be
// redirectable, this is purely internal function and
// serves only limited purpose on redirect-handler for
// browser requests.
func getRedirectLocation(urlPath string) (rLocation string) {
	if urlPath == reservedBucket {
		rLocation = reservedBucket + "/"
	}
	if contains([]string{
		"/",
		"/webrpc",
		"/login",
		"/favicon.ico",
	}, urlPath) {
		rLocation = reservedBucket + urlPath
	}
	return rLocation
}

// guessIsBrowserReq - returns true if the request is browser.
// This implementation just validates user-agent and
// looks for "Mozilla" string. This is no way certifiable
// way to know if the request really came from a browser
// since User-Agent's can be arbitrary. But this is just
// a best effort function.
func guessIsBrowserReq(req *http.Request) bool {
	if req == nil {
		return false
	}
	return strings.Contains(req.Header.Get("User-Agent"), "Mozilla")
}

func (h redirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	aType := getRequestAuthType(r)
	// Re-direct only for JWT and anonymous requests from browser.
	if aType == authTypeJWT || aType == authTypeAnonymous {
		// Re-direction is handled specifically for browser requests.
		if guessIsBrowserReq(r) && globalIsBrowserEnabled {
			// Fetch the redirect location if any.
			redirectLocation := getRedirectLocation(r.URL.Path)
			if redirectLocation != "" {
				// Employ a temporary re-direct.
				http.Redirect(w, r, redirectLocation, http.StatusTemporaryRedirect)
				return
			}
		}
	}
	h.handler.ServeHTTP(w, r)
}

// Adds Cache-Control header
type cacheControlHandler struct {
	handler http.Handler
}

func setBrowserCacheControlHandler(h http.Handler) http.Handler {
	return cacheControlHandler{h}
}

func (h cacheControlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method == httpGET && guessIsBrowserReq(r) && globalIsBrowserEnabled {
		// For all browser requests set appropriate Cache-Control policies
		if hasPrefix(r.URL.Path, reservedBucket+"/") {
			if hasSuffix(r.URL.Path, ".js") || r.URL.Path == reservedBucket+"/favicon.ico" {
				// For assets set cache expiry of one year. For each release, the name
				// of the asset name will change and hence it can not be served from cache.
				w.Header().Set("Cache-Control", "max-age=31536000")
			} else {
				// For non asset requests we serve index.html which will never be cached.
				w.Header().Set("Cache-Control", "no-store")
			}
		}
	}

	h.handler.ServeHTTP(w, r)
}

// Adds verification for incoming paths.
type minioPrivateBucketHandler struct {
	handler        http.Handler
	reservedBucket string
}

func setPrivateBucketHandler(h http.Handler) http.Handler {
	return minioPrivateBucketHandler{handler: h, reservedBucket: reservedBucket}
}

func (h minioPrivateBucketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// For all non browser requests, reject access to 'reservedBucket'.
	if !guessIsBrowserReq(r) && path.Clean(r.URL.Path) == reservedBucket {
		writeErrorResponse(w, ErrAllAccessDisabled, r.URL)
		return
	}
	h.handler.ServeHTTP(w, r)
}

type timeValidityHandler struct {
	handler http.Handler
}

// setTimeValidityHandler to validate parsable time over http header
func setTimeValidityHandler(h http.Handler) http.Handler {
	return timeValidityHandler{h}
}

// Supported Amz date formats.
var amzDateFormats = []string{
	time.RFC1123,
	time.RFC1123Z,
	iso8601Format,
	// Add new AMZ date formats here.
}

// Supported Amz date headers.
var amzDateHeaders = []string{
	"x-amz-date",
	"date",
}

// parseAmzDate - parses date string into supported amz date formats.
func parseAmzDate(amzDateStr string) (amzDate time.Time, apiErr APIErrorCode) {
	for _, dateFormat := range amzDateFormats {
		amzDate, err := time.Parse(dateFormat, amzDateStr)
		if err == nil {
			return amzDate, ErrNone
		}
	}
	return time.Time{}, ErrMalformedDate
}

// parseAmzDateHeader - parses supported amz date headers, in
// supported amz date formats.
func parseAmzDateHeader(req *http.Request) (time.Time, APIErrorCode) {
	for _, amzDateHeader := range amzDateHeaders {
		amzDateStr := req.Header.Get(http.CanonicalHeaderKey(amzDateHeader))
		if amzDateStr != "" {
			return parseAmzDate(amzDateStr)
		}
	}
	// Date header missing.
	return time.Time{}, ErrMissingDateHeader
}

func (h timeValidityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	aType := getRequestAuthType(r)
	if aType == authTypeSigned || aType == authTypeSignedV2 || aType == authTypeStreamingSigned {
		// Verify if date headers are set, if not reject the request
		amzDate, apiErr := parseAmzDateHeader(r)
		if apiErr != ErrNone {
			// All our internal APIs are sensitive towards Date
			// header, for all requests where Date header is not
			// present we will reject such clients.
			writeErrorResponse(w, apiErr, r.URL)
			return
		}
		// Verify if the request date header is shifted by less than globalMaxSkewTime parameter in the past
		// or in the future, reject request otherwise.
		curTime := time.Now().UTC()
		if curTime.Sub(amzDate) > globalMaxSkewTime || amzDate.Sub(curTime) > globalMaxSkewTime {
			writeErrorResponse(w, ErrRequestTimeTooSkewed, r.URL)
			return
		}
	}
	h.handler.ServeHTTP(w, r)
}

type resourceHandler struct {
	handler http.Handler
}

// List of http methods.
const (
	httpGET     = "GET"
	httpPUT     = "PUT"
	httpHEAD    = "HEAD"
	httpPOST    = "POST"
	httpDELETE  = "DELETE"
	httpOPTIONS = "OPTIONS"
)

// List of default allowable HTTP methods.
var defaultAllowableHTTPMethods = []string{
	httpGET,
	httpPUT,
	httpHEAD,
	httpPOST,
	httpDELETE,
	httpOPTIONS,
}

// setCorsHandler handler for CORS (Cross Origin Resource Sharing)
func setCorsHandler(h http.Handler) http.Handler {
	c := cors.New(cors.Options{
		AllowedOrigins: []string{"*"},
		AllowedMethods: defaultAllowableHTTPMethods,
		AllowedHeaders: []string{"*"},
		ExposedHeaders: []string{"ETag"},
	})
	return c.Handler(h)
}

// setIgnoreResourcesHandler -
// Ignore resources handler is wrapper handler used for API request resource validation
// Since we do not support all the S3 queries, it is necessary for us to throw back a
// valid error message indicating that requested feature is not implemented.
func setIgnoreResourcesHandler(h http.Handler) http.Handler {
	return resourceHandler{h}
}

// Checks requests for not implemented Bucket resources
func ignoreNotImplementedBucketResources(req *http.Request) bool {
	for name := range req.URL.Query() {
		if notimplementedBucketResourceNames[name] {
			return true
		}
	}
	return false
}

// Checks requests for not implemented Object resources
func ignoreNotImplementedObjectResources(req *http.Request) bool {
	for name := range req.URL.Query() {
		if notimplementedObjectResourceNames[name] {
			return true
		}
	}
	return false
}

// List of not implemented bucket queries
var notimplementedBucketResourceNames = map[string]bool{
	"acl":            true,
	"cors":           true,
	"lifecycle":      true,
	"logging":        true,
	"replication":    true,
	"tagging":        true,
	"versions":       true,
	"requestPayment": true,
	"versioning":     true,
	"website":        true,
}

// List of not implemented object queries
var notimplementedObjectResourceNames = map[string]bool{
	"torrent": true,
	"acl":     true,
	"policy":  true,
}

// Resource handler ServeHTTP() wrapper
func (h resourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	bucketName, objectName := urlPath2BucketObjectName(r.URL)

	// If bucketName is present and not objectName check for bucket level resource queries.
	if bucketName != "" && objectName == "" {
		if ignoreNotImplementedBucketResources(r) {
			writeErrorResponse(w, ErrNotImplemented, r.URL)
			return
		}
	}
	// If bucketName and objectName are present check for its resource queries.
	if bucketName != "" && objectName != "" {
		if ignoreNotImplementedObjectResources(r) {
			writeErrorResponse(w, ErrNotImplemented, r.URL)
			return
		}
	}
	// A put method on path "/" doesn't make sense, ignore it.
	if r.Method == httpPUT && r.URL.Path == "/" {
		writeErrorResponse(w, ErrNotImplemented, r.URL)
		return
	}

	// Serve HTTP.
	h.handler.ServeHTTP(w, r)
}

// httpResponseRecorder wraps http.ResponseWriter
// to record some useful http response data.
type httpResponseRecorder struct {
	http.ResponseWriter
	respStatusCode int
}

// Wraps ResponseWriter's Write()
func (rww *httpResponseRecorder) Write(b []byte) (int, error) {
	return rww.ResponseWriter.Write(b)
}

// Wraps ResponseWriter's Flush()
func (rww *httpResponseRecorder) Flush() {
	rww.ResponseWriter.(http.Flusher).Flush()
}

// Wraps ResponseWriter's WriteHeader() and record
// the response status code
func (rww *httpResponseRecorder) WriteHeader(httpCode int) {
	rww.respStatusCode = httpCode
	rww.ResponseWriter.WriteHeader(httpCode)
}

func (rww *httpResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	return rww.ResponseWriter.(http.Hijacker).Hijack()
}

// httpStatsHandler definition: gather HTTP statistics
type httpStatsHandler struct {
	handler http.Handler
}

// setHttpStatsHandler sets a http Stats Handler
func setHTTPStatsHandler(h http.Handler) http.Handler {
	return httpStatsHandler{handler: h}
}

func (h httpStatsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Wraps w to record http response information
	ww := &httpResponseRecorder{ResponseWriter: w}

	// Execute the request
	h.handler.ServeHTTP(ww, r)

	// Update http statistics
	globalHTTPStats.updateStats(r, ww)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"go/build"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"text/template"
	"time"

	"github.com/naoina/kocha"
	"github.com/naoina/kocha/util"
)

type buildCommand struct {
	option struct {
		All  bool   `short:"a" long:"all"`
		Tag  string `short:"t" long:"tag"`
		Help bool   `short:"h" long:"help"`
	}
}

func (c *buildCommand) Name() string {
	return "kocha build"
}

func (c *buildCommand) Usage() string {
	return fmt.Sprintf(`Usage: %s [OPTIONS]

Build your application.

Options:
    -a, --all         make the true all-in-one binary
    -t, --tag=TAG     specify version tag
    -h, --help        display this help and exit

`, c.Name())
}

func (c *buildCommand) Option() interface{} {
	return &c.option
}

func (c *buildCommand) Run(args []string) error {
	dir, err := os.Getwd()
	if err != nil {
		return err
	}
	appDir, err := util.FindAppDir()
	if err != nil {
		return err
	}
	appName := filepath.Base(dir)
	configPkg, err := getPackage(path.Join(appDir, "config"))
	if err != nil {
		return fmt.Errorf(`cannot import "%s": %v`, path.Join(appDir, "config"), err)
	}
	var dbImportPath string
	if dbPkg, err := getPackage(path.Join(appDir, "db")); err == nil {
		dbImportPath = dbPkg.ImportPath
	}
	var migrationImportPath string
	if migrationPkg, err := getPackage(path.Join(appDir, "db", "migration")); err == nil {
		migrationImportPath = migrationPkg.ImportPath
	}
	tmpDir, err := filepath.Abs("tmp")
	if err != nil {
		return err
	}
	if err := os.Mkdir(tmpDir, 0755); err != nil && !os.IsExist(err) {
		return fmt.Errorf("failed to create directory: %v", err)
	}
	_, filename, _, _ := runtime.Caller(0)
	baseDir := filepath.Dir(filename)
	skeletonDir := filepath.Join(baseDir, "skeleton", "build")
	mainTemplate, err := ioutil.ReadFile(filepath.Join(skeletonDir, "main.go"+util.TemplateSuffix))
	if err != nil {
		return err
	}
	mainFilePath := filepath.ToSlash(filepath.Join(tmpDir, "main.go"))
	builderFilePath := filepath.ToSlash(filepath.Join(tmpDir, "builder.go"))
	file, err := os.Create(builderFilePath)
	if err != nil {
		return fmt.Errorf("failed to create file: %v", err)
	}
	defer file.Close()
	builderTemplatePath := filepath.ToSlash(filepath.Join(skeletonDir, "builder.go"+util.TemplateSuffix))
	t := template.Must(template.ParseFiles(builderTemplatePath))
	var resources map[string]string
	if c.option.All {
		resources = collectResourcePaths(filepath.Join(dir, kocha.StaticDir))
	}
	tag, err := c.detectVersionTag()
	if err != nil {
		return err
	}
	data := map[string]interface{}{
		"configImportPath":    configPkg.ImportPath,
		"dbImportPath":        dbImportPath,
		"migrationImportPath": migrationImportPath,
		"mainTemplate":        string(mainTemplate),
		"mainFilePath":        mainFilePath,
		"resources":           resources,
		"version":             tag,
	}
	if err := t.Execute(file, data); err != nil {
		return fmt.Errorf("failed to write file: %v", err)
	}
	file.Close()
	execName := appName
	if runtime.GOOS == "windows" {
		execName += ".exe"
	}
	if err := execCmd("go", "run", builderFilePath); err != nil {
		return err
	}
	execArgs := []string{"build", "-o", execName}
	// On Linux, works fine. On Windows, doesn't work.
	// On other platforms, not tested.
	if runtime.GOOS == "linux" {
		execArgs = append(execArgs, "-ldflags", `-extldflags "-static"`)
	}
	execArgs = append(execArgs, mainFilePath)
	if err := execCmd("go", execArgs...); err != nil {
		return err
	}
	if err := os.RemoveAll(tmpDir); err != nil {
		return err
	}
	if err := util.PrintEnv(); err != nil {
		return err
	}
	fmt.Printf("build all-in-one binary to %v\n", filepath.Join(dir, execName))
	util.PrintGreen("Build successful!\n")
	return nil
}

func getPackage(importPath string) (*build.Package, error) {
	return build.Import(importPath, "", build.FindOnly)
}

func execCmd(cmd string, args ...string) error {
	command := exec.Command(cmd, args...)
	if msg, err := command.CombinedOutput(); err != nil {
		return fmt.Errorf("build failed: %v\n%v", err, string(msg))
	}
	return nil
}

func collectResourcePaths(root string) map[string]string {
	result := make(map[string]string)
	filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.Name()[0] == '.' {
			if info.IsDir() {
				return filepath.SkipDir
			}
			return nil
		}
		if info.IsDir() {
			return nil
		}
		rel, err := filepath.Rel(root, path)
		if err != nil {
			return err
		}
		result[rel] = filepath.ToSlash(path)
		return nil
	})
	return result
}

func (c *buildCommand) detectVersionTag() (string, error) {
	if c.option.Tag != "" {
		return c.option.Tag, nil
	}
	var repo string
	for _, dir := range []string{".git", ".hg"} {
		if info, err := os.Stat(dir); err == nil && info.IsDir() {
			repo = dir
			break
		}
	}
	version := time.Now().Format(time.RFC1123Z)
	switch repo {
	case ".git":
		bin, err := exec.LookPath("git")
		if err != nil {
			fmt.Fprintf(os.Stderr, "%s: WARNING: git repository found, but `git` command not found. use \"%s\" as version\n", c.Name(), version)
			break
		}
		line, err := exec.Command(bin, "rev-parse", "HEAD").Output()
		if err != nil {
			return "", fmt.Errorf("unexpected error: %v\nplease specify the version using '--tag' option to avoid the this error", err)
		}
		version = strings.TrimSpace(string(line))
	case ".hg":
		bin, err := exec.LookPath("hg")
		if err != nil {
			fmt.Fprintf(os.Stderr, "%s: WARNING: hg repository found, but `hg` command not found. use \"%s\" as version\n", c.Name(), version)
			break
		}
		line, err := exec.Command(bin, "identify").Output()
		if err != nil {
			return "", fmt.Errorf("unexpected error: %v\nplease specify version using '--tag' option to avoid the this error", err)
		}
		version = strings.TrimSpace(string(line))
	}
	if version == "" {
		// Probably doesn't reach here.
		version = time.Now().Format(time.RFC1123Z)
		fmt.Fprintf(os.Stderr, `%s: WARNING: version is empty, use "%s" as version`, c.Name(), version)
	}
	return version, nil
}

func main() {
	util.RunCommand(&buildCommand{})
}
<commit_msg>cmd/kocha-build: Add build flags to avoid to become a dynamic linked binary<commit_after>package main

import (
	"fmt"
	"go/build"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"text/template"
	"time"

	"github.com/naoina/kocha"
	"github.com/naoina/kocha/util"
)

type buildCommand struct {
	option struct {
		All  bool   `short:"a" long:"all"`
		Tag  string `short:"t" long:"tag"`
		Help bool   `short:"h" long:"help"`
	}
}

func (c *buildCommand) Name() string {
	return "kocha build"
}

func (c *buildCommand) Usage() string {
	return fmt.Sprintf(`Usage: %s [OPTIONS]

Build your application.

Options:
    -a, --all         make the true all-in-one binary
    -t, --tag=TAG     specify version tag
    -h, --help        display this help and exit

`, c.Name())
}

func (c *buildCommand) Option() interface{} {
	return &c.option
}

func (c *buildCommand) Run(args []string) error {
	dir, err := os.Getwd()
	if err != nil {
		return err
	}
	appDir, err := util.FindAppDir()
	if err != nil {
		return err
	}
	appName := filepath.Base(dir)
	configPkg, err := getPackage(path.Join(appDir, "config"))
	if err != nil {
		return fmt.Errorf(`cannot import "%s": %v`, path.Join(appDir, "config"), err)
	}
	var dbImportPath string
	if dbPkg, err := getPackage(path.Join(appDir, "db")); err == nil {
		dbImportPath = dbPkg.ImportPath
	}
	var migrationImportPath string
	if migrationPkg, err := getPackage(path.Join(appDir, "db", "migration")); err == nil {
		migrationImportPath = migrationPkg.ImportPath
	}
	tmpDir, err := filepath.Abs("tmp")
	if err != nil {
		return err
	}
	if err := os.Mkdir(tmpDir, 0755); err != nil && !os.IsExist(err) {
		return fmt.Errorf("failed to create directory: %v", err)
	}
	_, filename, _, _ := runtime.Caller(0)
	baseDir := filepath.Dir(filename)
	skeletonDir := filepath.Join(baseDir, "skeleton", "build")
	mainTemplate, err := ioutil.ReadFile(filepath.Join(skeletonDir, "main.go"+util.TemplateSuffix))
	if err != nil {
		return err
	}
	mainFilePath := filepath.ToSlash(filepath.Join(tmpDir, "main.go"))
	builderFilePath := filepath.ToSlash(filepath.Join(tmpDir, "builder.go"))
	file, err := os.Create(builderFilePath)
	if err != nil {
		return fmt.Errorf("failed to create file: %v", err)
	}
	defer file.Close()
	builderTemplatePath := filepath.ToSlash(filepath.Join(skeletonDir, "builder.go"+util.TemplateSuffix))
	t := template.Must(template.ParseFiles(builderTemplatePath))
	var resources map[string]string
	if c.option.All {
		resources = collectResourcePaths(filepath.Join(dir, kocha.StaticDir))
	}
	tag, err := c.detectVersionTag()
	if err != nil {
		return err
	}
	data := map[string]interface{}{
		"configImportPath":    configPkg.ImportPath,
		"dbImportPath":        dbImportPath,
		"migrationImportPath": migrationImportPath,
		"mainTemplate":        string(mainTemplate),
		"mainFilePath":        mainFilePath,
		"resources":           resources,
		"version":             tag,
	}
	if err := t.Execute(file, data); err != nil {
		return fmt.Errorf("failed to write file: %v", err)
	}
	file.Close()
	execName := appName
	if runtime.GOOS == "windows" {
		execName += ".exe"
	}
	if err := execCmd("go", "run", builderFilePath); err != nil {
		return err
	}
	// To avoid to become a dynamic linked binary.
	// See https://github.com/golang/go/issues/9344
	execArgs := []string{"build", "-o", execName, "-installsuffix", "."}
	// On Linux, works fine. On Windows, doesn't work.
	// On other platforms, not tested.
	if runtime.GOOS == "linux" {
		execArgs = append(execArgs, "-ldflags", `-extldflags "-static"`)
	}
	execArgs = append(execArgs, mainFilePath)
	if err := execCmd("go", execArgs...); err != nil {
		return err
	}
	if err := os.RemoveAll(tmpDir); err != nil {
		return err
	}
	if err := util.PrintEnv(); err != nil {
		return err
	}
	fmt.Printf("build all-in-one binary to %v\n", filepath.Join(dir, execName))
	util.PrintGreen("Build successful!\n")
	return nil
}

func getPackage(importPath string) (*build.Package, error) {
	return build.Import(importPath, "", build.FindOnly)
}

func execCmd(cmd string, args ...string) error {
	command := exec.Command(cmd, args...)
	if msg, err := command.CombinedOutput(); err != nil {
		return fmt.Errorf("build failed: %v\n%v", err, string(msg))
	}
	return nil
}

func collectResourcePaths(root string) map[string]string {
	result := make(map[string]string)
	filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.Name()[0] == '.' {
			if info.IsDir() {
				return filepath.SkipDir
			}
			return nil
		}
		if info.IsDir() {
			return nil
		}
		rel, err := filepath.Rel(root, path)
		if err != nil {
			return err
		}
		result[rel] = filepath.ToSlash(path)
		return nil
	})
	return result
}

func (c *buildCommand) detectVersionTag() (string, error) {
	if c.option.Tag != "" {
		return c.option.Tag, nil
	}
	var repo string
	for _, dir := range []string{".git", ".hg"} {
		if info, err := os.Stat(dir); err == nil && info.IsDir() {
			repo = dir
			break
		}
	}
	version := time.Now().Format(time.RFC1123Z)
	switch repo {
	case ".git":
		bin, err := exec.LookPath("git")
		if err != nil {
			fmt.Fprintf(os.Stderr, "%s: WARNING: git repository found, but `git` command not found. use \"%s\" as version\n", c.Name(), version)
			break
		}
		line, err := exec.Command(bin, "rev-parse", "HEAD").Output()
		if err != nil {
			return "", fmt.Errorf("unexpected error: %v\nplease specify the version using '--tag' option to avoid the this error", err)
		}
		version = strings.TrimSpace(string(line))
	case ".hg":
		bin, err := exec.LookPath("hg")
		if err != nil {
			fmt.Fprintf(os.Stderr, "%s: WARNING: hg repository found, but `hg` command not found. use \"%s\" as version\n", c.Name(), version)
			break
		}
		line, err := exec.Command(bin, "identify").Output()
		if err != nil {
			return "", fmt.Errorf("unexpected error: %v\nplease specify version using '--tag' option to avoid the this error", err)
		}
		version = strings.TrimSpace(string(line))
	}
	if version == "" {
		// Probably doesn't reach here.
		version = time.Now().Format(time.RFC1123Z)
		fmt.Fprintf(os.Stderr, `%s: WARNING: version is empty, use "%s" as version`, c.Name(), version)
	}
	return version, nil
}

func main() {
	util.RunCommand(&buildCommand{})
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"flag"
	"fmt"
	l "log"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/Shopify/sarama"
	"github.com/grafana/globalconf"
	"github.com/grafana/metrictank/api"
	"github.com/grafana/metrictank/cluster"
	"github.com/grafana/metrictank/idx"
	"github.com/grafana/metrictank/idx/memory"
	"github.com/grafana/metrictank/input"
	inKafka "github.com/grafana/metrictank/input/kafkamdm"
	"github.com/grafana/metrictank/logger"
	"github.com/grafana/metrictank/mdata"
	"github.com/grafana/metrictank/schema"
	"github.com/grafana/metrictank/schema/msg"
	cassandraStore "github.com/grafana/metrictank/store/cassandra"
	"github.com/raintank/dur"
	log "github.com/sirupsen/logrus"
)

var (
	// metrictank
	aggMetrics  *mdata.AggMetrics
	metricIndex idx.MetricIndex
	inputKafka  input.Plugin
	store       mdata.Store

	// config file
	confFile = flag.String("config", "/etc/metrictank/metrictank.ini", "config file path")

	// Data: the following configs are the same with the normal metrictank configs:
	chunkMaxStaleStr  = flag.String("chunk-max-stale", "1m", "chunk max stale age.")
	metricMaxStaleStr = flag.String("metric-max-stale", "5m", "metric max stale age.")
	gcIntervalStr     = flag.String("gc-interval", "2m", "gc interval.")
	publicOrg         = flag.Int("public-org", 0, "org Id")
	timeout           = flag.Int("timeout", 10, "the tool will exit if no kafka message is received during this interval ")
	logLevel          = flag.String("log-level", "info", "log level. panic|fatal|error|warning|info|debug")

	// backfilling
	lastRcvTime int64        // epoch time when the previous kafka message was received
	rcvdPoints  int64        // number of received datapoints
	mux         sync.Mutex   // mutex to protect lastRcvTime
	handler     inputHandler // input message handler to track the last kafka receive event and handles kafka messages
)

// a kafka message handler that implements the input.Handler interface
type inputHandler struct {
	handler  input.DefaultHandler // default handler that processes metric metadata and points
	finished chan bool
}

func newInputHandler(metrics mdata.Metrics, metricIndex idx.MetricIndex, pluginName string) inputHandler {
	dh := input.NewDefaultHandler(metrics, metricIndex, pluginName)
	return inputHandler{
		handler:  dh,
		finished: make(chan bool),
	}
}

// input.Handler interface
func (ih inputHandler) ProcessMetricData(metric *schema.MetricData, partition int32) {
	ih.handler.ProcessMetricData(metric, partition)
	mux.Lock()
	defer mux.Unlock()
	lastRcvTime = int64(time.Now().Unix())
	rcvdPoints++
	if rcvdPoints%10000 == 0 {
		log.Infof("Received %d datapoints", rcvdPoints)
	}
}

// input.Handler interface
func (ih inputHandler) ProcessMetricPoint(point schema.MetricPoint, format msg.Format, partition int32) {
	ih.handler.ProcessMetricPoint(point, format, partition)
	mux.Lock()
	defer mux.Unlock()
	lastRcvTime = int64(time.Now().Unix())
	rcvdPoints++
	if rcvdPoints%10000 == 0 {
		log.Infof("Received %d datapoints", rcvdPoints)
	}
}

func main() {
	flag.Parse()

	// logger
	formatter := &logger.TextFormatter{}
	formatter.TimestampFormat = "2006-01-02 15:04:05.000"
	log.SetFormatter(formatter)
	lvl, err := log.ParseLevel(*logLevel)
	if err != nil {
		log.Fatalf("failed to parse log-level, %s", err.Error())
	}
	log.SetLevel(lvl)
	log.Infof("logging level set to '%s'", *logLevel)
	log.Infof("metrictank backfilling")

	// Only try and parse the conf file if it exists
	path := ""
	if _, err := os.Stat(*confFile); err == nil {
		path = *confFile
	}
	config, err := globalconf.NewWithOptions(&globalconf.Options{
		Filename:  path,
		EnvPrefix: "MT_",
	})
	if err != nil {
		fmt.Fprintf(os.Stderr, "configuration file error: %s", err)
		os.Exit(1)
	}

	// load configs
	inKafka.ConfigSetup()
	memory.ConfigSetup()
	api.ConfigSetup()
	cluster.ConfigSetup()
	mdata.ConfigSetup()
	cassandraStore.ConfigSetup()
	config.ParseAll()

	// cluster is required because of aggMetric.add()
	// this should be configured as single mode
	api.ConfigProcess()
	cluster.ConfigProcess()
	addrParts := strings.Split(api.Addr, ":")
	port, err := strconv.ParseInt(addrParts[len(addrParts)-1], 10, 64)
	if err != nil {
		log.Fatalf("Could not parse port from listenAddr. %s", api.Addr)
	}
	cluster.Init("backfill", "none", time.Now(), "http", int(port))

	// other settings
	inKafka.ConfigProcess("backfill")
	mdata.ConfigProcess()
	memory.ConfigProcess()

	// interrupt signals
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	// backend store
	cassandraStore.CliConfig.Enabled = true
	store, err = cassandraStore.NewCassandraStore(cassandraStore.CliConfig, mdata.TTLs(), uint32(cassandraStore.CliConfig.MaxChunkSpan.Seconds()))
	if err != nil {
		log.Fatalf("failed to initialize cassandra store. %s", err)
	}

	// memory store
	chunkMaxStale := dur.MustParseNDuration("chunk-max-stale", *chunkMaxStaleStr)
	metricMaxStale := dur.MustParseNDuration("metric-max-stale", *metricMaxStaleStr)
	gcInterval := time.Duration(dur.MustParseNDuration("gc-interval", *gcIntervalStr)) * time.Second
	aggMetrics = mdata.NewAggMetrics(store, nil, false, nil /*todo*/, chunkMaxStale, metricMaxStale, gcInterval)

	// input
	// we use kafkamdm as input
	inKafka.Enabled = true
	sarama.Logger = l.New(os.Stdout, "[Sarama] ", l.LstdFlags)
	inputKafka = inKafka.New()

	// cluster manager
	cluster.Start()

	if *publicOrg < 0 {
		log.Fatal("public-org cannot be <0")
	}
	idx.OrgIdPublic = uint32(*publicOrg)

	memory.Enabled = true
	metricIndex = memory.New()

	// load index entries
	err = metricIndex.Init()
	if err != nil {
		log.Fatalf("Failed to initialize metricIndex: %s", err.Error())
	}

	// start input
	ctx, cancel := context.WithCancel(context.Background())
	handler = newInputHandler(aggMetrics, metricIndex, "kafkamdm")
	err = inputKafka.Start(handler, cancel)
	if err != nil {
		shutdown()
		log.Warn("Cannot start input.")
		return
	}
	inputKafka.MaintainPriority()
	lastRcvTime = int64(time.Now().Unix())
	go handlerTimeout()

	cluster.Manager.SetReady()

	// wait for shutdown
	select {
	case sig := <-sigChan:
		log.Infof("Received signal %q. Shutting down", sig)
	case <-ctx.Done():
		log.Info("The input plugin signalled a fatal error. Shutting down")
	case <-handler.finished:
		log.Infof("Received finished signal from input handler. Shutting down")
	}
	shutdown()

	defer cancel()
}

// if there is no update in 3 mins, shut down the tool
func handlerTimeout() {
	ticker := time.NewTicker(time.Second)
	for {
		select {
		case now := <-ticker.C:
			mux.Lock()
			prevRcv := lastRcvTime
			mux.Unlock()
			// wait for timeout seconds, if there is no more input, shut down
			if now.Unix()-prevRcv > int64(*timeout) {
				log.Infof("Handler timeout, shutting down")
				handler.finished <- true
				ticker.Stop()
				return
			}
		}
	}
}

// normal shutdown
func shutdown() {
	cluster.Stop()
	timer := time.NewTimer(time.Second * 20)
	kafkaStopped := make(chan bool)
	go func() {
		log.Infof("Shutting down kafka consumer")
		inputKafka.Stop()
		log.Infof("kafka consumer finished shutdown")
		kafkaStopped <- true
	}()

	select {
	case <-timer.C:
		log.Warn("Plugin shutdown timeout.")
	case <-kafkaStopped:
		timer.Stop()
	}
	store.Stop()
	log.Info("shutting down.")
}
<commit_msg>Add more usage details<commit_after>package main

import (
	"context"
	"flag"
	"fmt"
	l "log"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/Shopify/sarama"
	"github.com/grafana/globalconf"
	"github.com/grafana/metrictank/api"
	"github.com/grafana/metrictank/cluster"
	"github.com/grafana/metrictank/idx"
	"github.com/grafana/metrictank/idx/memory"
	"github.com/grafana/metrictank/input"
	inKafka "github.com/grafana/metrictank/input/kafkamdm"
	"github.com/grafana/metrictank/logger"
	"github.com/grafana/metrictank/mdata"
	"github.com/grafana/metrictank/schema"
	"github.com/grafana/metrictank/schema/msg"
	cassandraStore "github.com/grafana/metrictank/store/cassandra"
	"github.com/raintank/dur"
	log "github.com/sirupsen/logrus"
)

var (
	// metrictank
	aggMetrics  *mdata.AggMetrics
	metricIndex idx.MetricIndex
	inputKafka  input.Plugin
	store       mdata.Store

	// config file
	confFile = flag.String("config", "/etc/metrictank/metrictank.ini", "config file path")

	// Data: the following configs are the same with the normal metrictank configs:
	chunkMaxStaleStr  = flag.String("chunk-max-stale", "1m", "chunk max stale age.")
	metricMaxStaleStr = flag.String("metric-max-stale", "5m", "metric max stale age.")
	gcIntervalStr     = flag.String("gc-interval", "2m", "gc interval.")
	publicOrg         = flag.Int("public-org", 0, "org Id")
	timeout           = flag.Int("timeout", 10, "the tool will exit if no kafka message is received during this interval ")
	logLevel          = flag.String("log-level", "info", "log level. panic|fatal|error|warning|info|debug")

	// backfilling
	lastRcvTime int64        // epoch time when the previous kafka message was received
	rcvdPoints  int64        // number of received datapoints
	mux         sync.Mutex   // mutex to protect lastRcvTime
	handler     inputHandler // input message handler to track the last kafka receive event and handles kafka messages
)

// a kafka message handler that implements the input.Handler interface
type inputHandler struct {
	handler  input.DefaultHandler // default handler that processes metric metadata and points
	finished chan bool
}

func newInputHandler(metrics mdata.Metrics, metricIndex idx.MetricIndex, pluginName string) inputHandler {
	dh := input.NewDefaultHandler(metrics, metricIndex, pluginName)
	return inputHandler{
		handler:  dh,
		finished: make(chan bool),
	}
}

// input.Handler interface
func (ih inputHandler) ProcessMetricData(metric *schema.MetricData, partition int32) {
	ih.handler.ProcessMetricData(metric, partition)
	mux.Lock()
	defer mux.Unlock()
	lastRcvTime = int64(time.Now().Unix())
	rcvdPoints++
	if rcvdPoints%10000 == 0 {
		log.Infof("Received %d datapoints", rcvdPoints)
	}
}

// input.Handler interface
func (ih inputHandler) ProcessMetricPoint(point schema.MetricPoint, format msg.Format, partition int32) {
	ih.handler.ProcessMetricPoint(point, format, partition)
	mux.Lock()
	defer mux.Unlock()
	lastRcvTime = int64(time.Now().Unix())
	rcvdPoints++
	if rcvdPoints%10000 == 0 {
		log.Infof("Received %d datapoints", rcvdPoints)
	}
}

func main() {

	flag.Usage = func() {
		fmt.Println("mt-backfill")
		fmt.Println("Consumes data from Kafka and backfills chunks to Cassandra (only supports cassandra).")
		fmt.Println("Does not update the index table. Useful when existing series in Metrictank need historical data.")
		fmt.Println()
		fmt.Printf("Parameters:\n\n")
		flag.PrintDefaults()
		fmt.Println()
		fmt.Println("Config file supports same elements as `metrictank` command, but only supports Kafka in and Cassandra out.")
		fmt.Println()
		fmt.Printf("Example:\n\n")
		fmt.Printf("  mt-backfill -config /etc/metrictank/backfill.ini -timeout 600\n\n")
	}

	flag.Parse()

	// logger
	formatter := &logger.TextFormatter{}
	formatter.TimestampFormat = "2006-01-02 15:04:05.000"
	log.SetFormatter(formatter)
	lvl, err := log.ParseLevel(*logLevel)
	if err != nil {
		log.Fatalf("failed to parse log-level, %s", err.Error())
	}
	log.SetLevel(lvl)
	log.Infof("logging level set to '%s'", *logLevel)
	log.Infof("metrictank backfilling")

	// Only try and parse the conf file if it exists
	path := ""
	if _, err := os.Stat(*confFile); err == nil {
		path = *confFile
	}
	config, err := globalconf.NewWithOptions(&globalconf.Options{
		Filename:  path,
		EnvPrefix: "MT_",
	})
	if err != nil {
		fmt.Fprintf(os.Stderr, "configuration file error: %s", err)
		os.Exit(1)
	}

	// load configs
	inKafka.ConfigSetup()
	memory.ConfigSetup()
	api.ConfigSetup()
	cluster.ConfigSetup()
	mdata.ConfigSetup()
	cassandraStore.ConfigSetup()
	config.ParseAll()

	// cluster is required because of aggMetric.add()
	// this should be configured as single mode
	api.ConfigProcess()
	cluster.ConfigProcess()
	addrParts := strings.Split(api.Addr, ":")
	port, err := strconv.ParseInt(addrParts[len(addrParts)-1], 10, 64)
	if err != nil {
		log.Fatalf("Could not parse port from listenAddr. %s", api.Addr)
	}
	cluster.Init("backfill", "none", time.Now(), "http", int(port))

	// other settings
	inKafka.ConfigProcess("backfill")
	mdata.ConfigProcess()
	memory.ConfigProcess()

	// interrupt signals
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	// backend store
	cassandraStore.CliConfig.Enabled = true
	store, err = cassandraStore.NewCassandraStore(cassandraStore.CliConfig, mdata.TTLs(), uint32(cassandraStore.CliConfig.MaxChunkSpan.Seconds()))
	if err != nil {
		log.Fatalf("failed to initialize cassandra store. %s", err)
	}

	// memory store
	chunkMaxStale := dur.MustParseNDuration("chunk-max-stale", *chunkMaxStaleStr)
	metricMaxStale := dur.MustParseNDuration("metric-max-stale", *metricMaxStaleStr)
	gcInterval := time.Duration(dur.MustParseNDuration("gc-interval", *gcIntervalStr)) * time.Second
	aggMetrics = mdata.NewAggMetrics(store, nil, false, nil /*todo*/, chunkMaxStale, metricMaxStale, gcInterval)

	// input
	// we use kafkamdm as input
	inKafka.Enabled = true
	sarama.Logger = l.New(os.Stdout, "[Sarama] ", l.LstdFlags)
	inputKafka = inKafka.New()

	// cluster manager
	cluster.Start()

	if *publicOrg < 0 {
		log.Fatal("public-org cannot be <0")
	}
	idx.OrgIdPublic = uint32(*publicOrg)

	memory.Enabled = true
	metricIndex = memory.New()

	// load index entries
	err = metricIndex.Init()
	if err != nil {
		log.Fatalf("Failed to initialize metricIndex: %s", err.Error())
	}

	// start input
	ctx, cancel := context.WithCancel(context.Background())
	handler = newInputHandler(aggMetrics, metricIndex, "kafkamdm")
	err = inputKafka.Start(handler, cancel)
	if err != nil {
		shutdown()
		log.Warn("Cannot start input.")
		return
	}
	inputKafka.MaintainPriority()
	lastRcvTime = int64(time.Now().Unix())
	go handlerTimeout()

	cluster.Manager.SetReady()

	// wait for shutdown
	select {
	case sig := <-sigChan:
		log.Infof("Received signal %q. Shutting down", sig)
	case <-ctx.Done():
		log.Info("The input plugin signalled a fatal error. Shutting down")
	case <-handler.finished:
		log.Infof("Received finished signal from input handler. Shutting down")
	}
	shutdown()

	defer cancel()
}

// if there is no update in 3 mins, shut down the tool
func handlerTimeout() {
	ticker := time.NewTicker(time.Second)
	for {
		select {
		case now := <-ticker.C:
			mux.Lock()
			prevRcv := lastRcvTime
			mux.Unlock()
			// wait for timeout seconds, if there is no more input, shut down
			if now.Unix()-prevRcv > int64(*timeout) {
				log.Infof("Handler timeout, shutting down")
				handler.finished <- true
				ticker.Stop()
				return
			}
		}
	}
}

// normal shutdown
func shutdown() {
	cluster.Stop()
	timer := time.NewTimer(time.Second * 20)
	kafkaStopped := make(chan bool)
	go func() {
		log.Infof("Shutting down kafka consumer")
		inputKafka.Stop()
		log.Infof("kafka consumer finished shutdown")
		kafkaStopped <- true
	}()

	select {
	case <-timer.C:
		log.Warn("Plugin shutdown timeout.")
	case <-kafkaStopped:
		timer.Stop()
	}
	store.Stop()
	log.Info("shutting down.")
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"strings"
	"time"

	"github.com/DataDog/datadog-go/statsd"
	"github.com/Sirupsen/logrus"
	"github.com/stripe/veneur"
)

var (
	configFile = flag.String("f", "", "The Veneur config file to read for settings.")
	hostport   = flag.String("hostport", "", "Hostname and port of destination. Must be used if config file is not present.")
	name       = flag.String("name", "", "Name of metric to report. Ex: daemontools.service.starts")
	gauge      = flag.Float64("gauge", 0, "Report a 'gauge' metric. Value must be float64.")
	timing     = flag.Duration("timing", time.Now().Sub(time.Now()), "Report a 'timing' metric. Value must be parseable by time.ParseDuration.")
	timeinms   = flag.Float64("timeinms", 0, "Report a 'timing' metric, in milliseconds. Value must be float64.")
	count      = flag.Int64("count", 0, "Report a 'count' metric. Value must be an integer.")
	tag        = flag.String("tag", "", "Tag(s) for metric, comma separated. Ex: service:airflow")
	debug      = flag.Bool("debug", false, "Turns on debug messages.")
)

// MinimalClient represents the functions that we call on Clients in veneur-emit.
type MinimalClient interface {
	Gauge(name string, value float64, tags []string, rate float64) error
	Count(name string, value int64, tags []string, rate float64) error
	Timing(name string, value time.Duration, tags []string, rate float64) error
	TimeInMilliseconds(name string, value float64, tags []string, rate float64) error
}

func main() {
	passedFlags := flags()

	if *debug {
		logrus.SetLevel(logrus.DebugLevel)
	}

	var config *veneur.Config
	var err error
	if passedFlags["f"] {
		conf, e := veneur.ReadConfig(*configFile)
		if e != nil {
			logrus.WithError(err).Fatal("Error reading configuration file.")
		}
		config = &conf
	}

	addr, err := addr(passedFlags, config, hostport)
	if err != nil {
		logrus.WithError(err).Fatal("Error!")
	}
	logrus.Debugf("destination: %s", addr)

	var conn MinimalClient
	conn, err = statsd.New(addr)
	if err != nil {
		logrus.WithError(err).Fatal("Error!")
	}

	tags := tags(*tag)
	err = sendMetrics(conn, passedFlags, *name, tags)
	if err != nil {
		logrus.WithError(err).Fatal("Error!")
	}
}

func flags() map[string]bool {
	flag.Parse()
	// hacky way to detect which flags were *actually* set
	passedFlags := make(map[string]bool)
	flag.Visit(func(f *flag.Flag) {
		passedFlags[f.Name] = true
	})
	return passedFlags
}

func tags(tag string) []string {
	var tags []string
	for _, elem := range strings.Split(tag, ",") {
		tags = append(tags, elem)
	}
	return tags
}

func addr(passedFlags map[string]bool, conf *veneur.Config, hostport *string) (string, error) {
	addr := ""
	var err error
	if passedFlags["f"] && (conf != nil) {
		addr = conf.UdpAddress // TODO: conf.TcpAddress
	} else if passedFlags["hostport"] && !(hostport == nil || *hostport == "" || !strings.Contains(*hostport, ":")) {
		addr = *hostport
	} else {
		err = errors.New("you must either specify a Veneur config file or a valid hostport")
	}
	return addr, err
}

func sendMetrics(client MinimalClient, passedFlags map[string]bool, name string, tags []string) error {
	var err error
	if passedFlags["gauge"] {
		logrus.Debugf("Sending gauge '%s' -> %f", name, *gauge)
		err = client.Gauge(name, *gauge, tags, 1)
		if err != nil {
			return err
		}
	}
	if passedFlags["timing"] {
		logrus.Debugf("Sending timing '%s' -> %f", name, *timing)
		err = client.Timing(name, *timing, tags, 1)
		if err != nil {
			return err
		}
	}
	if passedFlags["timeinms"] {
		logrus.Debugf("Sending timeinms '%s' -> %f", name, *timeinms)
		err = client.TimeInMilliseconds(name, *timeinms, tags, 1)
		if err != nil {
			return err
		}
	}
	if passedFlags["count"] {
		logrus.Debugf("Sending count '%s' -> %d", name, *count)
		err = client.Count(name, *count, tags, 1)
		if err != nil {
			return err
		}
	}
	if !passedFlags["gauge"] && !passedFlags["timing"] && !passedFlags["timeinms"] && !passedFlags["count"] {
		logrus.Info("No metrics reported.")
	}
	return err
	// TODO: error on no metrics?
}
<commit_msg>Fixed error messages<commit_after>package main

import (
	"errors"
	"flag"
	"strings"
	"time"

	"github.com/DataDog/datadog-go/statsd"
	"github.com/Sirupsen/logrus"
	"github.com/stripe/veneur"
)

var (
	configFile = flag.String("f", "", "The Veneur config file to read for settings.")
	hostport   = flag.String("hostport", "", "Hostname and port of destination. Must be used if config file is not present.")
	name       = flag.String("name", "", "Name of metric to report. Ex: daemontools.service.starts")
	gauge      = flag.Float64("gauge", 0, "Report a 'gauge' metric. Value must be float64.")
	timing     = flag.Duration("timing", time.Now().Sub(time.Now()), "Report a 'timing' metric. Value must be parseable by time.ParseDuration.")
	timeinms   = flag.Float64("timeinms", 0, "Report a 'timing' metric, in milliseconds. Value must be float64.")
	count      = flag.Int64("count", 0, "Report a 'count' metric. Value must be an integer.")
	tag        = flag.String("tag", "", "Tag(s) for metric, comma separated. Ex: service:airflow")
	debug      = flag.Bool("debug", false, "Turns on debug messages.")
)

// MinimalClient represents the functions that we call on Clients in veneur-emit.
type MinimalClient interface {
	Gauge(name string, value float64, tags []string, rate float64) error
	Count(name string, value int64, tags []string, rate float64) error
	Timing(name string, value time.Duration, tags []string, rate float64) error
	TimeInMilliseconds(name string, value float64, tags []string, rate float64) error
}

func main() {
	passedFlags := flags()

	if *debug {
		logrus.SetLevel(logrus.DebugLevel)
	}

	var config *veneur.Config
	if passedFlags["f"] {
		conf, err := veneur.ReadConfig(*configFile)
		if err != nil {
			logrus.WithError(err).Fatal("Error reading configuration file.")
		}
		config = &conf
	}

	addr, err := addr(passedFlags, config, hostport)
	if err != nil {
		logrus.WithError(err).Fatal("Error getting destination address.")
	}
	logrus.Debugf("destination: %s", addr)

	var conn MinimalClient
	conn, err = statsd.New(addr)
	if err != nil {
		logrus.WithError(err).Fatal("Error!")
	}

	tags := tags(*tag)
	err = sendMetrics(conn, passedFlags, *name, tags)
	if err != nil {
		logrus.WithError(err).Fatal("Error sending metric(s).")
	}
}

func flags() map[string]bool {
	flag.Parse()
	// hacky way to detect which flags were *actually* set
	passedFlags := make(map[string]bool)
	flag.Visit(func(f *flag.Flag) {
		passedFlags[f.Name] = true
	})
	return passedFlags
}

func tags(tag string) []string {
	var tags []string
	for _, elem := range strings.Split(tag, ",") {
		tags = append(tags, elem)
	}
	return tags
}

func addr(passedFlags map[string]bool, conf *veneur.Config, hostport *string) (string, error) {
	addr := ""
	var err error
	if passedFlags["f"] && (conf != nil) {
		addr = conf.UdpAddress // TODO: conf.TcpAddress
	} else if passedFlags["hostport"] && !(hostport == nil || *hostport == "" || !strings.Contains(*hostport, ":")) {
		addr = *hostport
	} else {
		err = errors.New("you must either specify a Veneur config file or a valid hostport")
	}
	return addr, err
}

func sendMetrics(client MinimalClient, passedFlags map[string]bool, name string, tags []string) error {
	var err error
	if passedFlags["gauge"] {
		logrus.Debugf("Sending gauge '%s' -> %f", name, *gauge)
		err = client.Gauge(name, *gauge, tags, 1)
		if err != nil {
			return err
		}
	}
	if passedFlags["timing"] {
		logrus.Debugf("Sending timing '%s' -> %f", name, *timing)
		err = client.Timing(name, *timing, tags, 1)
		if err != nil {
			return err
		}
	}
	if passedFlags["timeinms"] {
		logrus.Debugf("Sending timeinms '%s' -> %f", name, *timeinms)
		err = client.TimeInMilliseconds(name, *timeinms, tags, 1)
		if err != nil {
			return err
		}
	}
	if passedFlags["count"] {
		logrus.Debugf("Sending count '%s' -> %d", name, *count)
		err = client.Count(name, *count, tags, 1)
		if err != nil {
			return err
		}
	}
	if !passedFlags["gauge"] && !passedFlags["timing"] && !passedFlags["timeinms"] && !passedFlags["count"] {
		logrus.Info("No metrics reported.")
	}
	return err
	// TODO: error on no metrics?
}
<|endoftext|>


<commit_before>package cmds

import (
	"fmt"
	"os"
	"regexp"
	"strconv"
	"strings"

	"go/ast"
	"go/build"
	"go/parser"
	"go/token"

	"path/filepath"

	"encoding/json"
	"io/ioutil"

	"github.com/maximilien/i18n4go/common"
)

type extractStrings struct {
	options common.Options

	i18nFilename string
	poFilename   string

	Filename      string
	OutputDirname string

	ExtractedStrings map[string]common.StringInfo
	FilteredStrings  map[string]string
	FilteredRegexps  []*regexp.Regexp

	TotalStringsDir int
	TotalStrings    int
	TotalFiles      int

	IgnoreRegexp *regexp.Regexp
}

func NewExtractStrings(options common.Options) extractStrings {
	var compiledRegexp *regexp.Regexp
	if options.IgnoreRegexpFlag != "" {
		compiledReg, err := regexp.Compile(options.IgnoreRegexpFlag)
		if err != nil {
			fmt.Println("WARNING compiling ignore-regexp:", err)
		}
		compiledRegexp = compiledReg
	}

	return extractStrings{options: options,
		Filename:         "extracted_strings.json",
		OutputDirname:    options.OutputDirFlag,
		ExtractedStrings: nil,
		FilteredStrings:  nil,
		FilteredRegexps:  nil,
		TotalStringsDir:  0,
		TotalStrings:     0,
		TotalFiles:       0,
		IgnoreRegexp:     compiledRegexp}
}

func (es *extractStrings) Options() common.Options {
	return es.options
}

func (es *extractStrings) Println(a ...interface{}) (int, error) {
	if es.options.VerboseFlag {
		return fmt.Println(a...)
	}

	return 0, nil
}

func (es *extractStrings) Printf(msg string, a ...interface{}) (int, error) {
	if es.options.VerboseFlag {
		return fmt.Printf(msg, a...)
	}

	return 0, nil
}

func (es *extractStrings) Run() error {
	if es.options.FilenameFlag != "" {
		return es.InspectFile(es.options.FilenameFlag)
	} else {
		err := es.InspectDir(es.options.DirnameFlag, es.options.RecurseFlag)
		if err != nil {
			es.Println("gi18n: could not extract strings from directory:", es.options.DirnameFlag)
			return err
		}
		es.Println()
		es.Println("Total files parsed:", es.TotalFiles)
		es.Println("Total extracted strings:", es.TotalStrings)
	}
	return nil
}

func (es *extractStrings) InspectFile(filename string) error {
	es.Println("gi18n: extracting strings from file:", filename)
	if es.options.DryRunFlag {
		es.Println("WARNING running in -dry-run mode")
	}

	es.ExtractedStrings = make(map[string]common.StringInfo)
	es.FilteredStrings = make(map[string]string)
	es.FilteredRegexps = []*regexp.Regexp{}

	es.setFilename(filename)
	es.setI18nFilename(filename)
	es.setPoFilename(filename)

	fset := token.NewFileSet()

	var absFilePath = filename
	if !filepath.IsAbs(absFilePath) {
		absFilePath = filepath.Join(os.Getenv("PWD"), absFilePath)
	}

	fileInfo, err := common.GetAbsFileInfo(absFilePath)
	if err != nil {
		es.Println(err)
	}

	if strings.HasPrefix(fileInfo.Name(), ".") {
		es.Println("WARNING ignoring file:", absFilePath)
		return nil
	}

	astFile, err := parser.ParseFile(fset, absFilePath, nil, parser.ParseComments|parser.AllErrors)
	if err != nil {
		es.Println(err)
		return err
	}

	err = es.loadExcludedStrings()
	if err != nil {
		es.Println(err)
		return err
	}
	es.Println(fmt.Sprintf("Loaded %d excluded strings", len(es.FilteredStrings)))

	err = es.loadExcludedRegexps()
	if err != nil {
		es.Println(err)
		return err
	}
	es.Println(fmt.Sprintf("Loaded %d excluded regexps", len(es.FilteredRegexps)))

	es.excludeImports(astFile)

	es.extractString(astFile, fset)
	es.TotalStringsDir += len(es.ExtractedStrings)
	es.TotalStrings += len(es.ExtractedStrings)
	es.TotalFiles += 1

	es.Printf("Extracted %d strings from file: %s\n", len(es.ExtractedStrings), absFilePath)

	var outputDirname = es.OutputDirname
	if es.options.OutputDirFlag != "" {
		if es.options.OutputMatchImportFlag {
			outputDirname, err = es.findImportPath(absFilePath)
			if err != nil {
				es.Println(err)
				return err
			}
		} else if es.options.OutputMatchPackageFlag {
			outputDirname, err = es.findPackagePath(absFilePath)
			if err != nil {
				es.Println(err)
				return err
			}
		}
	} else {
		outputDirname, err = common.FindFilePath(absFilePath)
		if err != nil {
			es.Println(err)
			return err
		}
	}

	if es.options.MetaFlag {
		err = es.saveExtractedStrings(outputDirname)
		if err != nil {
			es.Println(err)
			return err
		}
	}

	err = common.SaveStrings(es, es.Options(), es.ExtractedStrings, outputDirname, es.i18nFilename)
	if err != nil {
		es.Println(err)
		return err
	}

	if es.options.PoFlag {
		err = common.SaveStringsInPo(es, es.Options(), es.ExtractedStrings, outputDirname, es.poFilename)
		if err != nil {
			es.Println(err)
			return err
		}
	}

	return nil
}

func (es *extractStrings) InspectDir(dirName string, recursive bool) error {
	es.Printf("gi18n: inspecting dir %s, recursive: %t\n", dirName, recursive)
	es.Println()

	fset := token.NewFileSet()
	es.TotalStringsDir = 0

	packages, err := parser.ParseDir(fset, dirName, nil, parser.ParseComments)
	if err != nil {
		es.Println(err)
		return err
	}

	for k, pkg := range packages {
		es.Println("Extracting strings in package:", k)
		for fileName, _ := range pkg.Files {
			if es.IgnoreRegexp != nil && es.IgnoreRegexp.MatchString(fileName) {
				es.Println("Using ignore-regexp:", es.options.IgnoreRegexpFlag)
				continue
			} else {
				es.Println("No match for ignore-regexp:", es.options.IgnoreRegexpFlag)
			}

			if strings.HasSuffix(fileName, ".go") {
				err = es.InspectFile(fileName)
				if err != nil {
					es.Println(err)
				}
			}
		}
	}
	es.Printf("Extracted total of %d strings\n\n", es.TotalStringsDir)

	if recursive {
		fileInfos, _ := ioutil.ReadDir(dirName)
		for _, fileInfo := range fileInfos {
			if fileInfo.IsDir() && !strings.HasPrefix(fileInfo.Name(), ".") {
				err = es.InspectDir(filepath.Join(dirName, fileInfo.Name()), recursive)
				if err != nil {
					es.Println(err)
				}
			}
		}
	}

	return nil
}

func (es *extractStrings) findImportPath(filename string) (string, error) {
	path := es.OutputDirname

	filePath, err := common.FindFilePath(filename)
	if err != nil {
		fmt.Println("ERROR opening file", err)
		return "", err
	}

	pkg, err := build.ImportDir(filePath, 0)
	srcPath := "src" + string(os.PathSeparator)
	if strings.HasPrefix(pkg.Dir, srcPath) {
		path = filepath.Join(path, pkg.Dir[len(srcPath):len(pkg.Dir)])
	}

	return path, nil
}

func (es *extractStrings) findPackagePath(filename string) (string, error) {
	path := es.OutputDirname

	filePath, err := common.FindFilePath(filename)
	if err != nil {
		fmt.Println("ERROR opening file", err)
		return "", err
	}

	pkg, err := build.ImportDir(filePath, 0)
	if err != nil {
		fmt.Println("ERROR opening file", err)
		return "", err
	}

	return filepath.Join(path, pkg.Name), nil
}

func (es *extractStrings) saveExtractedStrings(outputDirname string) error {
	if len(es.ExtractedStrings) != 0 {
		es.Println("Saving extracted strings to file:", es.Filename)
	}

	if !es.options.DryRunFlag {
		err := common.CreateOutputDirsIfNeeded(outputDirname)
		if err != nil {
			es.Println(err)
			return err
		}
	}

	stringInfos := make([]common.StringInfo, 0)
	for _, stringInfo := range es.ExtractedStrings {
		stringInfo.Filename = strings.Split(es.Filename, ".extracted.json")[0]

		stringInfos = append(stringInfos, stringInfo)
	}

	jsonData, err := json.MarshalIndent(stringInfos, "", "   ")
	if err != nil {
		es.Println(err)
		return err
	}
	jsonData = common.UnescapeHTML(jsonData)

	if !es.options.DryRunFlag && len(stringInfos) != 0 {
		file, err := os.Create(filepath.Join(outputDirname, es.Filename[strings.LastIndex(es.Filename, string(os.PathSeparator))+1:len(es.Filename)]))
		defer file.Close()
		if err != nil {
			es.Println(err)
			return err
		}

		file.Write(jsonData)
	}

	return nil
}

func (es *extractStrings) setFilename(filename string) {
	es.Filename = filename + ".extracted.json"
}

func (es *extractStrings) setI18nFilename(filename string) {
	es.i18nFilename = filename + ".en.json"
}

func (es *extractStrings) setPoFilename(filename string) {
	es.poFilename = filename + ".en.po"
}

func (es *extractStrings) loadExcludedStrings() error {
	_, err := os.Stat(es.options.ExcludedFilenameFlag)
	if os.IsNotExist(err) {
		es.Println("Could not find:", es.options.ExcludedFilenameFlag)
		return nil
	}

	es.Println("Excluding strings in file:", es.options.ExcludedFilenameFlag)

	content, err := ioutil.ReadFile(es.options.ExcludedFilenameFlag)
	if err != nil {
		fmt.Print(err)
		return err
	}

	var excludedStrings common.ExcludedStrings
	err = json.Unmarshal(content, &excludedStrings)
	if err != nil {
		fmt.Print(err)
		return err
	}

	for i := range excludedStrings.ExcludedStrings {
		es.FilteredStrings[excludedStrings.ExcludedStrings[i]] = excludedStrings.ExcludedStrings[i]
	}

	return nil
}

func (es *extractStrings) loadExcludedRegexps() error {
	_, err := os.Stat(es.options.ExcludedFilenameFlag)
	if os.IsNotExist(err) {
		es.Println("Could not find:", es.options.ExcludedFilenameFlag)
		return nil
	}

	es.Println("Excluding regexps in file:", es.options.ExcludedFilenameFlag)

	content, err := ioutil.ReadFile(es.options.ExcludedFilenameFlag)
	if err != nil {
		fmt.Print(err)
		return err
	}

	var excludedRegexps common.ExcludedStrings
	err = json.Unmarshal(content, &excludedRegexps)
	if err != nil {
		fmt.Print(err)
		return err
	}

	for _, regexpString := range excludedRegexps.ExcludedRegexps {
		compiledRegexp, err := regexp.Compile(regexpString)
		if err != nil {
			fmt.Println("WARNING error compiling regexp:", regexpString)
		}

		es.FilteredRegexps = append(es.FilteredRegexps, compiledRegexp)
	}

	return nil
}

func (es *extractStrings) extractString(f *ast.File, fset *token.FileSet) error {
	ast.Inspect(f, func(n ast.Node) bool {
		var s string
		switch x := n.(type) {
		case *ast.BasicLit:
			s, _ = strconv.Unquote(x.Value)
			if len(s) > 0 && x.Kind == token.STRING && s != "\t" && s != "\n" && s != " " && !es.filter(s) { //TODO: fix to remove these: s != "\\t" && s != "\\n" && s != " "
				position := fset.Position(n.Pos())
				stringInfo := common.StringInfo{Value: s,
					Filename: position.Filename,
					Offset:   position.Offset,
					Line:     position.Line,
					Column:   position.Column}
				es.ExtractedStrings[s] = stringInfo
			}
		}
		return true
	})

	return nil
}

func (es *extractStrings) excludeImports(astFile *ast.File) {
	for i := range astFile.Imports {
		importString, _ := strconv.Unquote(astFile.Imports[i].Path.Value)
		es.FilteredStrings[importString] = importString
	}

}

func (es *extractStrings) filter(aString string) bool {
	for i := range common.BLANKS {
		if aString == common.BLANKS[i] {
			return true
		}
	}

	if _, ok := es.FilteredStrings[aString]; ok {
		return true
	}

	for _, compiledRegexp := range es.FilteredRegexps {
		if compiledRegexp.MatchString(aString) {
			return true
		}
	}

	return false
}
<commit_msg>fixes issue #16 by looking for parent and whether to process basic lit<commit_after>package cmds

import (
	"fmt"
	"os"
	"regexp"
	"strconv"
	"strings"

	"go/ast"
	"go/build"
	"go/parser"
	"go/token"

	"path/filepath"

	"encoding/json"
	"io/ioutil"

	"github.com/maximilien/i18n4go/common"
)

type extractStrings struct {
	options common.Options

	i18nFilename string
	poFilename   string

	Filename      string
	OutputDirname string

	ExtractedStrings map[string]common.StringInfo
	FilteredStrings  map[string]string
	FilteredRegexps  []*regexp.Regexp

	TotalStringsDir int
	TotalStrings    int
	TotalFiles      int

	IgnoreRegexp *regexp.Regexp
}

func NewExtractStrings(options common.Options) extractStrings {
	var compiledRegexp *regexp.Regexp
	if options.IgnoreRegexpFlag != "" {
		compiledReg, err := regexp.Compile(options.IgnoreRegexpFlag)
		if err != nil {
			fmt.Println("WARNING compiling ignore-regexp:", err)
		}
		compiledRegexp = compiledReg
	}

	return extractStrings{options: options,
		Filename:         "extracted_strings.json",
		OutputDirname:    options.OutputDirFlag,
		ExtractedStrings: nil,
		FilteredStrings:  nil,
		FilteredRegexps:  nil,
		TotalStringsDir:  0,
		TotalStrings:     0,
		TotalFiles:       0,
		IgnoreRegexp:     compiledRegexp}
}

func (es *extractStrings) Options() common.Options {
	return es.options
}

func (es *extractStrings) Println(a ...interface{}) (int, error) {
	if es.options.VerboseFlag {
		return fmt.Println(a...)
	}

	return 0, nil
}

func (es *extractStrings) Printf(msg string, a ...interface{}) (int, error) {
	if es.options.VerboseFlag {
		return fmt.Printf(msg, a...)
	}

	return 0, nil
}

func (es *extractStrings) Run() error {
	if es.options.FilenameFlag != "" {
		return es.InspectFile(es.options.FilenameFlag)
	} else {
		err := es.InspectDir(es.options.DirnameFlag, es.options.RecurseFlag)
		if err != nil {
			es.Println("gi18n: could not extract strings from directory:", es.options.DirnameFlag)
			return err
		}
		es.Println()
		es.Println("Total files parsed:", es.TotalFiles)
		es.Println("Total extracted strings:", es.TotalStrings)
	}
	return nil
}

func (es *extractStrings) InspectFile(filename string) error {
	es.Println("gi18n: extracting strings from file:", filename)
	if es.options.DryRunFlag {
		es.Println("WARNING running in -dry-run mode")
	}

	es.ExtractedStrings = make(map[string]common.StringInfo)
	es.FilteredStrings = make(map[string]string)
	es.FilteredRegexps = []*regexp.Regexp{}

	es.setFilename(filename)
	es.setI18nFilename(filename)
	es.setPoFilename(filename)

	fset := token.NewFileSet()

	var absFilePath = filename
	if !filepath.IsAbs(absFilePath) {
		absFilePath = filepath.Join(os.Getenv("PWD"), absFilePath)
	}

	fileInfo, err := common.GetAbsFileInfo(absFilePath)
	if err != nil {
		es.Println(err)
	}

	if strings.HasPrefix(fileInfo.Name(), ".") {
		es.Println("WARNING ignoring file:", absFilePath)
		return nil
	}

	astFile, err := parser.ParseFile(fset, absFilePath, nil, parser.ParseComments|parser.AllErrors)
	if err != nil {
		es.Println(err)
		return err
	}

	err = es.loadExcludedStrings()
	if err != nil {
		es.Println(err)
		return err
	}
	es.Println(fmt.Sprintf("Loaded %d excluded strings", len(es.FilteredStrings)))

	err = es.loadExcludedRegexps()
	if err != nil {
		es.Println(err)
		return err
	}
	es.Println(fmt.Sprintf("Loaded %d excluded regexps", len(es.FilteredRegexps)))

	es.excludeImports(astFile)

	es.extractString(astFile, fset)
	es.TotalStringsDir += len(es.ExtractedStrings)
	es.TotalStrings += len(es.ExtractedStrings)
	es.TotalFiles += 1

	es.Printf("Extracted %d strings from file: %s\n", len(es.ExtractedStrings), absFilePath)

	var outputDirname = es.OutputDirname
	if es.options.OutputDirFlag != "" {
		if es.options.OutputMatchImportFlag {
			outputDirname, err = es.findImportPath(absFilePath)
			if err != nil {
				es.Println(err)
				return err
			}
		} else if es.options.OutputMatchPackageFlag {
			outputDirname, err = es.findPackagePath(absFilePath)
			if err != nil {
				es.Println(err)
				return err
			}
		}
	} else {
		outputDirname, err = common.FindFilePath(absFilePath)
		if err != nil {
			es.Println(err)
			return err
		}
	}

	if es.options.MetaFlag {
		err = es.saveExtractedStrings(outputDirname)
		if err != nil {
			es.Println(err)
			return err
		}
	}

	err = common.SaveStrings(es, es.Options(), es.ExtractedStrings, outputDirname, es.i18nFilename)
	if err != nil {
		es.Println(err)
		return err
	}

	if es.options.PoFlag {
		err = common.SaveStringsInPo(es, es.Options(), es.ExtractedStrings, outputDirname, es.poFilename)
		if err != nil {
			es.Println(err)
			return err
		}
	}

	return nil
}

func (es *extractStrings) InspectDir(dirName string, recursive bool) error {
	es.Printf("gi18n: inspecting dir %s, recursive: %t\n", dirName, recursive)
	es.Println()

	fset := token.NewFileSet()
	es.TotalStringsDir = 0

	packages, err := parser.ParseDir(fset, dirName, nil, parser.ParseComments)
	if err != nil {
		es.Println(err)
		return err
	}

	for k, pkg := range packages {
		es.Println("Extracting strings in package:", k)
		for fileName, _ := range pkg.Files {
			if es.IgnoreRegexp != nil && es.IgnoreRegexp.MatchString(fileName) {
				es.Println("Using ignore-regexp:", es.options.IgnoreRegexpFlag)
				continue
			} else {
				es.Println("No match for ignore-regexp:", es.options.IgnoreRegexpFlag)
			}

			if strings.HasSuffix(fileName, ".go") {
				err = es.InspectFile(fileName)
				if err != nil {
					es.Println(err)
				}
			}
		}
	}
	es.Printf("Extracted total of %d strings\n\n", es.TotalStringsDir)

	if recursive {
		fileInfos, _ := ioutil.ReadDir(dirName)
		for _, fileInfo := range fileInfos {
			if fileInfo.IsDir() && !strings.HasPrefix(fileInfo.Name(), ".") {
				err = es.InspectDir(filepath.Join(dirName, fileInfo.Name()), recursive)
				if err != nil {
					es.Println(err)
				}
			}
		}
	}

	return nil
}

func (es *extractStrings) findImportPath(filename string) (string, error) {
	path := es.OutputDirname

	filePath, err := common.FindFilePath(filename)
	if err != nil {
		fmt.Println("ERROR opening file", err)
		return "", err
	}

	pkg, err := build.ImportDir(filePath, 0)
	srcPath := "src" + string(os.PathSeparator)
	if strings.HasPrefix(pkg.Dir, srcPath) {
		path = filepath.Join(path, pkg.Dir[len(srcPath):len(pkg.Dir)])
	}

	return path, nil
}

func (es *extractStrings) findPackagePath(filename string) (string, error) {
	path := es.OutputDirname

	filePath, err := common.FindFilePath(filename)
	if err != nil {
		fmt.Println("ERROR opening file", err)
		return "", err
	}

	pkg, err := build.ImportDir(filePath, 0)
	if err != nil {
		fmt.Println("ERROR opening file", err)
		return "", err
	}

	return filepath.Join(path, pkg.Name), nil
}

func (es *extractStrings) saveExtractedStrings(outputDirname string) error {
	if len(es.ExtractedStrings) != 0 {
		es.Println("Saving extracted strings to file:", es.Filename)
	}

	if !es.options.DryRunFlag {
		err := common.CreateOutputDirsIfNeeded(outputDirname)
		if err != nil {
			es.Println(err)
			return err
		}
	}

	stringInfos := make([]common.StringInfo, 0)
	for _, stringInfo := range es.ExtractedStrings {
		stringInfo.Filename = strings.Split(es.Filename, ".extracted.json")[0]

		stringInfos = append(stringInfos, stringInfo)
	}

	jsonData, err := json.MarshalIndent(stringInfos, "", "   ")
	if err != nil {
		es.Println(err)
		return err
	}
	jsonData = common.UnescapeHTML(jsonData)

	if !es.options.DryRunFlag && len(stringInfos) != 0 {
		file, err := os.Create(filepath.Join(outputDirname, es.Filename[strings.LastIndex(es.Filename, string(os.PathSeparator))+1:len(es.Filename)]))
		defer file.Close()
		if err != nil {
			es.Println(err)
			return err
		}

		file.Write(jsonData)
	}

	return nil
}

func (es *extractStrings) setFilename(filename string) {
	es.Filename = filename + ".extracted.json"
}

func (es *extractStrings) setI18nFilename(filename string) {
	es.i18nFilename = filename + ".en.json"
}

func (es *extractStrings) setPoFilename(filename string) {
	es.poFilename = filename + ".en.po"
}

func (es *extractStrings) loadExcludedStrings() error {
	_, err := os.Stat(es.options.ExcludedFilenameFlag)
	if os.IsNotExist(err) {
		es.Println("Could not find:", es.options.ExcludedFilenameFlag)
		return nil
	}

	es.Println("Excluding strings in file:", es.options.ExcludedFilenameFlag)

	content, err := ioutil.ReadFile(es.options.ExcludedFilenameFlag)
	if err != nil {
		fmt.Print(err)
		return err
	}

	var excludedStrings common.ExcludedStrings
	err = json.Unmarshal(content, &excludedStrings)
	if err != nil {
		fmt.Print(err)
		return err
	}

	for i := range excludedStrings.ExcludedStrings {
		es.FilteredStrings[excludedStrings.ExcludedStrings[i]] = excludedStrings.ExcludedStrings[i]
	}

	return nil
}

func (es *extractStrings) loadExcludedRegexps() error {
	_, err := os.Stat(es.options.ExcludedFilenameFlag)
	if os.IsNotExist(err) {
		es.Println("Could not find:", es.options.ExcludedFilenameFlag)
		return nil
	}

	es.Println("Excluding regexps in file:", es.options.ExcludedFilenameFlag)

	content, err := ioutil.ReadFile(es.options.ExcludedFilenameFlag)
	if err != nil {
		fmt.Print(err)
		return err
	}

	var excludedRegexps common.ExcludedStrings
	err = json.Unmarshal(content, &excludedRegexps)
	if err != nil {
		fmt.Print(err)
		return err
	}

	for _, regexpString := range excludedRegexps.ExcludedRegexps {
		compiledRegexp, err := regexp.Compile(regexpString)
		if err != nil {
			fmt.Println("WARNING error compiling regexp:", regexpString)
		}

		es.FilteredRegexps = append(es.FilteredRegexps, compiledRegexp)
	}

	return nil
}

func (es *extractStrings) extractString(f *ast.File, fset *token.FileSet) error {
	shouldProcessBasicLit := true
	ast.Inspect(f, func(n ast.Node) bool {
		switch x := n.(type) {
		case *ast.BasicLit:
			if shouldProcessBasicLit {
				es.processBasicLit(x, n, fset)
			}
			shouldProcessBasicLit = true
		case *ast.IndexExpr:
			_, ok := x.Index.(*ast.BasicLit)
			if ok {
				shouldProcessBasicLit = false
			}
		case *ast.KeyValueExpr:
			_, ok := x.Key.(*ast.BasicLit)
			if ok {
				shouldProcessBasicLit = false
			}
		}
		return true
	})

	return nil
}

func (es *extractStrings) processBasicLit(basicLit *ast.BasicLit, n ast.Node, fset *token.FileSet) {
	s, _ := strconv.Unquote(basicLit.Value)
	if len(s) > 0 && basicLit.Kind == token.STRING && s != "\t" && s != "\n" && s != " " && !es.filter(s) { //TODO: fix to remove these: s != "\\t" && s != "\\n" && s != " "
		position := fset.Position(n.Pos())
		stringInfo := common.StringInfo{Value: s,
			Filename: position.Filename,
			Offset:   position.Offset,
			Line:     position.Line,
			Column:   position.Column}
		es.ExtractedStrings[s] = stringInfo
	}
}

func (es *extractStrings) excludeImports(astFile *ast.File) {
	for i := range astFile.Imports {
		importString, _ := strconv.Unquote(astFile.Imports[i].Path.Value)
		es.FilteredStrings[importString] = importString
	}

}

func (es *extractStrings) filter(aString string) bool {
	for i := range common.BLANKS {
		if aString == common.BLANKS[i] {
			return true
		}
	}

	if _, ok := es.FilteredStrings[aString]; ok {
		return true
	}

	for _, compiledRegexp := range es.FilteredRegexps {
		if compiledRegexp.MatchString(aString) {
			return true
		}
	}

	return false
}
<|endoftext|>


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

package main

import (
	"crypto/rand"
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"log"
	"net/http"
)

const (
	PortNum  = "8080"
	HtmlPage = `

{{$NoEnc := 0}}
{{$WpaPsk := 1}}
{{$WpaEap := 2}}
{{$connectedEssid := .ConnectedEssid}}
{{$connectingEssid := .ConnectingEssid}}

Please choose your Wifi

    	Essid
    	Identity
    	Password / Passphrase
    	<input type="submit" class="btn" onclick=sendRefresh(this) value="Refresh">

	{{range $idx, $opt := .WifiOpts}}
		{{if eq $opt.AuthSuite $NoEnc}}

    			{{$opt.Essid}}

    			{{if and (eq $connectedEssid $opt.Essid) (ne $connectedEssid "")}}
    				Connected
				{{else if and (and (eq $connectingEssid $opt.Essid) (ne $connectingEssid "")) (ne $connectingEssid $connectedEssid) }}
    				Connecting...
    			{{else}}
    				<input type="submit" class="btn" onclick="sendConnect(this, {{$idx}})" value="Connect">
    			{{end}}

		{{else if eq $opt.AuthSuite $WpaPsk}}

    			{{$opt.Essid}}

    			<input type="password" id="pass{{$idx}}">
    			{{if and (eq $connectedEssid $opt.Essid) (ne $connectedEssid "")}}
    				Connected
				{{else if and (and (eq $connectingEssid $opt.Essid) (ne $connectingEssid "")) (ne $connectingEssid $connectedEssid) }}
    				Connecting...
    			{{else}}
    				<input type="submit" class="btn" onclick="sendConnect(this, {{$idx}})" value="Connect">
    			{{end}}

		{{else if eq $opt.AuthSuite $WpaEap}}

    			{{$opt.Essid}}
    			<input type="text" id="id{{$idx}}">
    			<input type="password" id="pass{{$idx}}">
    			{{if and (eq $connectedEssid $opt.Essid) (ne $connectedEssid "")}}
    				Connected
				{{else if and (and (eq $connectingEssid $opt.Essid) (ne $connectingEssid "")) (ne $connectingEssid $connectedEssid) }}
    				Connecting...
    			{{else}}
    				<input type="submit" class="btn" onclick="sendConnect(this, {{$idx}})" value="Connect">
    			{{end}}

		{{else}}

    			{{$opt.Essid}}
    			Not a supported protocol

		{{end}}
	{{else}}
		No essids found
	{{end}}

{{if and (ne $connectingEssid "") (ne $connectingEssid $connectedEssid) }}

{{end}}

`
)

func userInputValidation(essid, pass, id string) ([]string, error) {
	switch {
	case essid != "" && pass != "" && id != "":
		return []string{essid, pass, id}, nil
	case essid != "" && pass != "" && id == "":
		return []string{essid, pass}, nil
	case essid != "" && pass == "" && id == "":
		return []string{essid}, nil
	default:
		return nil, fmt.Errorf("Invalid user input")
	}
}

func refreshHandle(w http.ResponseWriter, r *http.Request) {
	if err := scanWifi(); err != nil {
		json.NewEncoder(w).Encode(struct{ Error string }{err.Error()})
		return
	}
	json.NewEncoder(w).Encode(nil)

}

type ConnectJsonMsg struct {
	Essid string
	Pass  string
	Id    string
}

func connectHandle(w http.ResponseWriter, r *http.Request) {
	var msg ConnectJsonMsg
	decoder := json.NewDecoder(r.Body)
	defer r.Body.Close()
	if err := decoder.Decode(&msg); err != nil {
		log.Printf("error: %v", err)
		json.NewEncoder(w).Encode(struct{ Error string }{err.Error()})
		return
	}
	a, err := userInputValidation(msg.Essid, msg.Pass, msg.Id)
	if err != nil {
		log.Printf("error: %v", err)
		json.NewEncoder(w).Encode(struct{ Error string }{err.Error()})
		return
	}

	// Prepare for a connection request
	routineID := make([]byte, 8)
	if _, err := rand.Read(routineID); err != nil {
		log.Printf("error: %v", err)
		json.NewEncoder(w).Encode(struct{ Error string }{err.Error()})
		return
	}
	c := make(chan error)

	// Making a Conncetion Request
	ConnectReqChan <- ConnectReqChanMsg{c, a[0], routineID, false}
	if err := <-c; err != nil {
		log.Printf("error: %v", err)
		json.NewEncoder(w).Encode(struct{ Error string }{err.Error()})
		return
	}

	// if err == nil, we have the OK to connect
	// Since there is only one arbitrator, there is only one OK at any one time
	if err := connectWifi(a...); err != nil {
		ConnectReqChan <- ConnectReqChanMsg{nil, a[0], routineID, false}
		log.Printf("error: %v", err)
		json.NewEncoder(w).Encode(struct{ Error string }{err.Error()})
		return
	}
	ConnectReqChan <- ConnectReqChanMsg{c, a[0], routineID, true}
	json.NewEncoder(w).Encode(nil)
}

func startServer() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		s := getState()
		displayWifi(w, s.NearbyWifis, s.CurEssid, s.ConnectingEssid)
	})
	http.HandleFunc("/refresh", refreshHandle)
	http.HandleFunc("/connect", connectHandle)

	http.ListenAndServe(fmt.Sprintf(":%s", PortNum), nil)
}

func displayWifi(wr io.Writer, wifiOpts []WifiOption, connectedEssid, connectingEssid string) error {
	wifiData := struct {
		WifiOpts        []WifiOption
		ConnectedEssid  string
		ConnectingEssid string
	}{wifiOpts, connectedEssid, connectingEssid}

	tmpl := template.Must(template.New("name").Parse(HtmlPage))

	return tmpl.Execute(wr, wifiData)
}
<commit_msg>wifi: Add Error Status to Error Response<commit_after>// Copyright 2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"crypto/rand"
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"log"
	"net/http"
)

const (
	PortNum  = "8080"
	HtmlPage = `

{{$NoEnc := 0}}
{{$WpaPsk := 1}}
{{$WpaEap := 2}}
{{$connectedEssid := .ConnectedEssid}}
{{$connectingEssid := .ConnectingEssid}}

Please choose your Wifi

    	Essid
    	Identity
    	Password / Passphrase
    	<input type="submit" class="btn" onclick=sendRefresh(this) value="Refresh">

	{{range $idx, $opt := .WifiOpts}}
		{{if eq $opt.AuthSuite $NoEnc}}

    			{{$opt.Essid}}

    			{{if and (eq $connectedEssid $opt.Essid) (ne $connectedEssid "")}}
    				Connected
				{{else if and (and (eq $connectingEssid $opt.Essid) (ne $connectingEssid "")) (ne $connectingEssid $connectedEssid) }}
    				Connecting...
    			{{else}}
    				<input type="submit" class="btn" onclick="sendConnect(this, {{$idx}})" value="Connect">
    			{{end}}

		{{else if eq $opt.AuthSuite $WpaPsk}}

    			{{$opt.Essid}}

    			<input type="password" id="pass{{$idx}}">
    			{{if and (eq $connectedEssid $opt.Essid) (ne $connectedEssid "")}}
    				Connected
				{{else if and (and (eq $connectingEssid $opt.Essid) (ne $connectingEssid "")) (ne $connectingEssid $connectedEssid) }}
    				Connecting...
    			{{else}}
    				<input type="submit" class="btn" onclick="sendConnect(this, {{$idx}})" value="Connect">
    			{{end}}

		{{else if eq $opt.AuthSuite $WpaEap}}

    			{{$opt.Essid}}
    			<input type="text" id="id{{$idx}}">
    			<input type="password" id="pass{{$idx}}">
    			{{if and (eq $connectedEssid $opt.Essid) (ne $connectedEssid "")}}
    				Connected
				{{else if and (and (eq $connectingEssid $opt.Essid) (ne $connectingEssid "")) (ne $connectingEssid $connectedEssid) }}
    				Connecting...
    			{{else}}
    				<input type="submit" class="btn" onclick="sendConnect(this, {{$idx}})" value="Connect">
    			{{end}}

		{{else}}

    			{{$opt.Essid}}
    			Not a supported protocol

		{{end}}
	{{else}}
		No essids found
	{{end}}

{{if and (ne $connectingEssid "") (ne $connectingEssid $connectedEssid) }}

{{end}}

`
)

func userInputValidation(essid, pass, id string) ([]string, error) {
	switch {
	case essid != "" && pass != "" && id != "":
		return []string{essid, pass, id}, nil
	case essid != "" && pass != "" && id == "":
		return []string{essid, pass}, nil
	case essid != "" && pass == "" && id == "":
		return []string{essid}, nil
	default:
		return nil, fmt.Errorf("Invalid user input")
	}
}

func refreshHandle(w http.ResponseWriter, r *http.Request) {
	if err := scanWifi(); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		json.NewEncoder(w).Encode(struct{ Error string }{err.Error()})
		return
	}
	json.NewEncoder(w).Encode(nil)

}

type ConnectJsonMsg struct {
	Essid string
	Pass  string
	Id    string
}

func connectHandle(w http.ResponseWriter, r *http.Request) {
	var msg ConnectJsonMsg
	decoder := json.NewDecoder(r.Body)
	defer r.Body.Close()
	if err := decoder.Decode(&msg); err != nil {
		log.Printf("error: %v", err)
		w.WriteHeader(http.StatusBadRequest)
		json.NewEncoder(w).Encode(struct{ Error string }{err.Error()})
		return
	}
	a, err := userInputValidation(msg.Essid, msg.Pass, msg.Id)
	if err != nil {
		log.Printf("error: %v", err)
		w.WriteHeader(http.StatusBadRequest)
		json.NewEncoder(w).Encode(struct{ Error string }{err.Error()})
		return
	}

	// Prepare for a connection request
	routineID := make([]byte, 8)
	if _, err := rand.Read(routineID); err != nil {
		log.Printf("error: %v", err)
		w.WriteHeader(http.StatusInternalServerError)
		json.NewEncoder(w).Encode(struct{ Error string }{err.Error()})
		return
	}
	c := make(chan error)

	// Making a Conncetion Request
	ConnectReqChan <- ConnectReqChanMsg{c, a[0], routineID, false}
	if err := <-c; err != nil {
		log.Printf("error: %v", err)
		w.WriteHeader(http.StatusInternalServerError)
		json.NewEncoder(w).Encode(struct{ Error string }{err.Error()})
		return
	}

	// if err == nil, we have the OK to connect
	// Since there is only one arbitrator, there is only one OK at any one time
	if err := connectWifi(a...); err != nil {
		ConnectReqChan <- ConnectReqChanMsg{nil, a[0], routineID, false}
		log.Printf("error: %v", err)
		w.WriteHeader(http.StatusInternalServerError)
		json.NewEncoder(w).Encode(struct{ Error string }{err.Error()})
		return
	}
	ConnectReqChan <- ConnectReqChanMsg{c, a[0], routineID, true}
	json.NewEncoder(w).Encode(nil)
}

func startServer() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		s := getState()
		displayWifi(w, s.NearbyWifis, s.CurEssid, s.ConnectingEssid)
	})
	http.HandleFunc("/refresh", refreshHandle)
	http.HandleFunc("/connect", connectHandle)

	http.ListenAndServe(fmt.Sprintf(":%s", PortNum), nil)
}

func displayWifi(wr io.Writer, wifiOpts []WifiOption, connectedEssid, connectingEssid string) error {
	wifiData := struct {
		WifiOpts        []WifiOption
		ConnectedEssid  string
		ConnectingEssid string
	}{wifiOpts, connectedEssid, connectingEssid}

	tmpl := template.Must(template.New("name").Parse(HtmlPage))

	return tmpl.Execute(wr, wifiData)
}
<|endoftext|>


<commit_before>package hackberry

import (
	"fmt"
	"strings"
	"reflect"
)

// a simple action executor, to dispatch actions
type defaultActionDispatcher struct{
	executors map[string]Any
}

func NewDefaultActionDispatcher() *defaultActionDispatcher{
	return &defaultActionDispatcher{make(map[string]Any)}
}

// add a action executor to action dispatcher
func (ad *defaultActionDispatcher)AddActionExecutor(name string, executor Any) *defaultActionDispatcher{
	ad.executors[name] = executor
	return ad
}

// dispath a action
func (ad *defaultActionDispatcher)Dispatch(a Action, context *Context){
	names := strings.Split(a.Name, `.`)
	if len(names) != 2 {
		panic(&IllegalActionError{"Action name format should be like objname.method, but [" + a.Name + "]."})
	}

	execName := names[0]
	methodName := names[1];
	executor := ad.executors[execName]
	if executor == nil {
		panic(&IllegalActionError{"Has no action executor for [" + execName + "]."})
	}

	method := reflect.ValueOf(executor).MethodByName(methodName)
	if !method.IsValid() {
		panic(&IllegalActionError{"Has no method [" + a.Name + "]."})
	}

	methodS, _ := reflect.TypeOf(executor).MethodByName(methodName)
	methodT := methodS.Type

	// NumIn take receiver as the first parameter
	if methodT.NumIn() - 1 != len(a.Parameters) {
		panic(&IllegalActionError{"Parameter number is not correct for method [" + a.Name + "]."})
	}

	params := make([]reflect.Value, len(a.Parameters))
	for i, p := range a.Parameters{
		v := transValue(methodT.In(i + 1).Name(), p, a.Name)
		params[i] = reflect.ValueOf(v)
	}
	method.Call(params)
}

func transValue(name string, v Any, action string) Any{
	s := fmt.Sprintf("%v", v)
	switch name{
		case "bool":
			return parseBool(s)
		case "int8":
			return int8(parseInt(s))
		case "int16":
			return int16(parseInt(s))
		case "int32":
			return int32(parseInt(s))
		case "int64":
			return parseInt(s)
		case "int":
			return int(parseInt(s))
		case "uint8":
			return uint8(parseUint(s))
		case "uint16":
			return uint16(parseUint(s))
		case "uint32":
			return uint32(parseUint(s))
		case "uint64":
			return parseUint(s)
		case "uint":
			return uint(parseUint(s))
		case "float32":
			return float32(parseFloat(s))
		case "float64":
			return parseFloat(s)
		case "string":
			return s
		default:
			msg := fmt.Sprintf("Unsupported parameter type [%s] for action [%s].", name, action)
			panic(&IllegalActionError{msg})
	}
}<commit_msg>return original value when can't transform parameter<commit_after>package hackberry

import (
	"fmt"
	"strings"
	"reflect"
)

// a simple action executor, to dispatch actions
type defaultActionDispatcher struct{
	executors map[string]Any
}

func NewDefaultActionDispatcher() *defaultActionDispatcher{
	return &defaultActionDispatcher{make(map[string]Any)}
}

// add a action executor to action dispatcher
func (ad *defaultActionDispatcher)AddActionExecutor(name string, executor Any) *defaultActionDispatcher{
	ad.executors[name] = executor
	return ad
}

// dispath a action
func (ad *defaultActionDispatcher)Dispatch(a Action, context *Context){
	names := strings.Split(a.Name, `.`)
	if len(names) != 2 {
		panic(&IllegalActionError{"Action name format should be like objname.method, but [" + a.Name + "]."})
	}

	execName := names[0]
	methodName := names[1];
	executor := ad.executors[execName]
	if executor == nil {
		panic(&IllegalActionError{"Has no action executor for [" + execName + "]."})
	}

	method := reflect.ValueOf(executor).MethodByName(methodName)
	if !method.IsValid() {
		panic(&IllegalActionError{"Has no method [" + a.Name + "]."})
	}

	methodS, _ := reflect.TypeOf(executor).MethodByName(methodName)
	methodT := methodS.Type

	// NumIn take receiver as the first parameter
	if methodT.NumIn() - 1 != len(a.Parameters) {
		panic(&IllegalActionError{"Parameter number is not correct for method [" + a.Name + "]."})
	}

	params := make([]reflect.Value, len(a.Parameters))
	for i, p := range a.Parameters{
		v := transValue(methodT.In(i + 1).Name(), p, a.Name)
		params[i] = reflect.ValueOf(v)
	}
	method.Call(params)
}

func transValue(name string, v Any, action string) Any{
	s := fmt.Sprintf("%v", v)
	switch name{
		case "bool":
			return parseBool(s)
		case "int8":
			return int8(parseInt(s))
		case "int16":
			return int16(parseInt(s))
		case "int32":
			return int32(parseInt(s))
		case "int64":
			return parseInt(s)
		case "int":
			return int(parseInt(s))
		case "uint8":
			return uint8(parseUint(s))
		case "uint16":
			return uint16(parseUint(s))
		case "uint32":
			return uint32(parseUint(s))
		case "uint64":
			return parseUint(s)
		case "uint":
			return uint(parseUint(s))
		case "float32":
			return float32(parseFloat(s))
		case "float64":
			return parseFloat(s)
		case "string":
			return s
		default:
			return v
	}
}<|endoftext|>