<commit_before>package main

import (
	"log"
	"math/rand"
	"net/http"
	"path"
	"time"

	"github.com/chenjiandongx/go-echarts/charts"
)

const (
	host   = "http://127.0.0.1:8080"
	maxNum = 50
)

type router struct {
	name string
	charts.RouterOpts
}

var (
	nameItems = []string{"衬衫", "牛仔裤", "运动裤", "袜子", "冲锋衣", "羊毛衫"}
	foodItems = []string{"面包", "牛奶", "奶茶", "棒棒糖", "加多宝", "可口可乐"}

	rangeColor = []string{
		"#313695", "#4575b4", "#74add1", "#abd9e9", "#e0f3f8",
		"#fee090", "#fdae61", "#f46d43", "#d73027", "#a50026",
	}

	hours = [...]string{
		"12a", "1a", "2a", "3a", "4a", "5a", "6a", "7a", "8a", "9a", "10a", "11a",
		"12p", "1p", "2p", "3p", "4p", "5p", "6p", "7p", "8p", "9p", "10p", "11p",
	}

	days = [...]string{"Saturday", "Friday", "Thursday", "Wednesday", "Tuesday", "Monday", "Sunday"}

	mapData = map[string]float32{
		"北京":   float32(rand.Intn(150)),
		"上海":   float32(rand.Intn(150)),
		"深圳":   float32(rand.Intn(150)),
		"辽宁":   float32(rand.Intn(150)),
		"青岛":   float32(rand.Intn(150)),
		"山西":   float32(rand.Intn(150)),
		"陕西":   float32(rand.Intn(150)),
		"乌鲁木齐": float32(rand.Intn(150)),
		"齐齐哈尔": float32(rand.Intn(150)),
	}

	guangdongData = map[string]float32{
		"深圳市": float32(rand.Intn(150)),
		"广州市": float32(rand.Intn(150)),
		"湛江市": float32(rand.Intn(150)),
		"汕头市": float32(rand.Intn(150)),
		"东莞市": float32(rand.Intn(150)),
		"佛山市": float32(rand.Intn(150)),
		"云浮市": float32(rand.Intn(150)),
		"肇庆市": float32(rand.Intn(150)),
		"梅州市": float32(rand.Intn(150)),
	}

	shantouData = map[string]float32{
		"澄海区": float32(rand.Intn(150)),
		"潮阳区": float32(rand.Intn(150)),
		"潮南区": float32(rand.Intn(150)),
		"南澳县": float32(rand.Intn(150)),
	}

	routers = []router{
		{"bar", charts.RouterOpts{URL: host + "/bar", Text: "Bar-(柱状图)"}},
		{"bar3D", charts.RouterOpts{URL: host + "/bar3D", Text: "Bar3D-(3D 柱状图)"}},
		{"boxPlot", charts.RouterOpts{URL: host + "/boxPlot", Text: "BoxPlot-(箱线图)"}},
		{"effectScatter", charts.RouterOpts{URL: host + "/effectScatter", Text: "EffectScatter-(动态散点图)"}},
		{"funnel", charts.RouterOpts{URL: host + "/funnel", Text: "Funnel-(漏斗图)"}},
		{"gauge", charts.RouterOpts{URL: host + "/gauge", Text: "Gauge-仪表盘"}},
		{"geo", charts.RouterOpts{URL: host + "/geo", Text: "Geo-地理坐标系"}},
		{"graph", charts.RouterOpts{URL: host + "/graph", Text: "Graph-关系图"}},
		{"heatMap", charts.RouterOpts{URL: host + "/heatMap", Text: "HeatMap-热力图"}},
		{"kline", charts.RouterOpts{URL: host + "/kline", Text: "Kline-K 线图"}},
		{"line", charts.RouterOpts{URL: host + "/line", Text: "Line-(折线图)"}},
		{"line3D", charts.RouterOpts{URL: host + "/line3D", Text: "Line3D-(3D 折线图)"}},
		{"liquid", charts.RouterOpts{URL: host + "/liquid", Text: "Liquid-(水球图)"}},
		{"map", charts.RouterOpts{URL: host + "/map", Text: "Map-(地图)"}},
		{"overlap", charts.RouterOpts{URL: host + "/overlap", Text: "Overlap-(重叠图)"}},
		{"parallel", charts.RouterOpts{URL: host + "/parallel", Text: "Parallel-(平行坐标系)"}},
		{"pie", charts.RouterOpts{URL: host + "/pie", Text: "Pie-(饼图)"}},
		{"radar", charts.RouterOpts{URL: host + "/radar", Text: "Radar-(雷达图)"}},
		{"sankey", charts.RouterOpts{URL: host + "/sankey", Text: "Sankey-(桑基图)"}},
		{"scatter", charts.RouterOpts{URL: host + "/scatter", Text: "Scatter-(散点图)"}},
		{"scatter3D", charts.RouterOpts{URL: host + "/scatter3D", Text: "Scatter-(3D 散点图)"}},
		{"surface3D", charts.RouterOpts{URL: host + "/surface3D", Text: "Surface3D-(3D 曲面图)"}},
		{"themeRiver", charts.RouterOpts{URL: host + "/themeRiver", Text: "ThemeRiver-(主题河流图)"}},
		{"wordCloud", charts.RouterOpts{URL: host + "/wordCloud", Text: "WordCloud-(词云图)"}},
		{"page", charts.RouterOpts{URL: host + "/page", Text: "Page-(顺序多图)"}},
	}
)

func orderRouters(chartType string) []charts.RouterOpts {
	for i := 0; i < len(routers); i++ {
		if routers[i].name == chartType {
			routers[i], routers[0] = routers[0], routers[i]
			break
		}
	}

	rs := make([]charts.RouterOpts, 0)
	for i := 0; i < len(routers); i++ {
		rs = append(rs, routers[i].RouterOpts)
	}
	return rs
}

func getRenderPath(f string) string {
	return path.Join("html", f)
}

func logTracing(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Printf("Tracing request for %s\n", r.RequestURI)
		next.ServeHTTP(w, r)
	}
}

var seed = rand.NewSource(time.Now().UnixNano())

func randInt() []int {
	cnt := len(nameItems)
	r := make([]int, 0)
	for i := 0; i < cnt; i++ {
		r = append(r, int(seed.Int63())%maxNum)
	}
	return r
}

func genKvData() map[string]interface{} {
	m := make(map[string]interface{})
	for i := 0; i < len(nameItems); i++ {
		m[nameItems[i]] = rand.Intn(maxNum)
	}
	return m
}

func main() {
	http.HandleFunc("/bar", logTracing(barHandler))
	http.HandleFunc("/bar3D", logTracing(bar3DHandler))
	http.HandleFunc("/boxPlot", logTracing(boxPlotHandler))
	http.HandleFunc("/effectScatter", logTracing(esHandler))
	http.HandleFunc("/funnel", logTracing(funnelHandler))
	http.HandleFunc("/gauge", logTracing(gaugeHandler))
	http.HandleFunc("/geo", logTracing(geoHandler))
	http.HandleFunc("/graph", logTracing(graphHandler))
	http.HandleFunc("/heatMap", logTracing(heatMapHandler))
	http.HandleFunc("/kline", logTracing(klineHandler))
	http.HandleFunc("/line", logTracing(lineHandler))
	http.HandleFunc("/line3D", logTracing(line3DHandler))
	http.HandleFunc("/liquid", logTracing(liquidHandler))
	http.HandleFunc("/map", logTracing(mapHandler))
	http.HandleFunc("/overlap", logTracing(overlapHandler))
	http.HandleFunc("/parallel", logTracing(parallelHandler))
	http.HandleFunc("/pie", logTracing(pieHandler))
	http.HandleFunc("/radar", logTracing(radarHandler))
	http.HandleFunc("/sankey", logTracing(sankeyHandler))
	http.HandleFunc("/scatter", logTracing(scatterHandler))
	http.HandleFunc("/scatter3D", logTracing(scatter3DHandler))
	http.HandleFunc("/surface3D", logTracing(surface3DHandler))
	http.HandleFunc("/themeRiver", logTracing(themeRiverHandler))
	http.HandleFunc("/wordCloud", logTracing(wcHandler))
	http.HandleFunc("/page", logTracing(pageHandler))

	log.Println("Run server at " + host)
	http.ListenAndServe(":8080", nil)
}
<commit_msg>add handler for root endpoint (#20)<commit_after>package main

import (
	"log"
	"math/rand"
	"net/http"
	"path"
	"time"

	"github.com/chenjiandongx/go-echarts/charts"
)

const (
	host   = "http://127.0.0.1:8080"
	maxNum = 50
)

type router struct {
	name string
	charts.RouterOpts
}

var (
	nameItems = []string{"衬衫", "牛仔裤", "运动裤", "袜子", "冲锋衣", "羊毛衫"}
	foodItems = []string{"面包", "牛奶", "奶茶", "棒棒糖", "加多宝", "可口可乐"}

	rangeColor = []string{
		"#313695", "#4575b4", "#74add1", "#abd9e9", "#e0f3f8",
		"#fee090", "#fdae61", "#f46d43", "#d73027", "#a50026",
	}

	hours = [...]string{
		"12a", "1a", "2a", "3a", "4a", "5a", "6a", "7a", "8a", "9a", "10a", "11a",
		"12p", "1p", "2p", "3p", "4p", "5p", "6p", "7p", "8p", "9p", "10p", "11p",
	}

	days = [...]string{"Saturday", "Friday", "Thursday", "Wednesday", "Tuesday", "Monday", "Sunday"}

	mapData = map[string]float32{
		"北京":   float32(rand.Intn(150)),
		"上海":   float32(rand.Intn(150)),
		"深圳":   float32(rand.Intn(150)),
		"辽宁":   float32(rand.Intn(150)),
		"青岛":   float32(rand.Intn(150)),
		"山西":   float32(rand.Intn(150)),
		"陕西":   float32(rand.Intn(150)),
		"乌鲁木齐": float32(rand.Intn(150)),
		"齐齐哈尔": float32(rand.Intn(150)),
	}

	guangdongData = map[string]float32{
		"深圳市": float32(rand.Intn(150)),
		"广州市": float32(rand.Intn(150)),
		"湛江市": float32(rand.Intn(150)),
		"汕头市": float32(rand.Intn(150)),
		"东莞市": float32(rand.Intn(150)),
		"佛山市": float32(rand.Intn(150)),
		"云浮市": float32(rand.Intn(150)),
		"肇庆市": float32(rand.Intn(150)),
		"梅州市": float32(rand.Intn(150)),
	}

	shantouData = map[string]float32{
		"澄海区": float32(rand.Intn(150)),
		"潮阳区": float32(rand.Intn(150)),
		"潮南区": float32(rand.Intn(150)),
		"南澳县": float32(rand.Intn(150)),
	}

	routers = []router{
		{"bar", charts.RouterOpts{URL: host + "/bar", Text: "Bar-(柱状图)"}},
		{"bar3D", charts.RouterOpts{URL: host + "/bar3D", Text: "Bar3D-(3D 柱状图)"}},
		{"boxPlot", charts.RouterOpts{URL: host + "/boxPlot", Text: "BoxPlot-(箱线图)"}},
		{"effectScatter", charts.RouterOpts{URL: host + "/effectScatter", Text: "EffectScatter-(动态散点图)"}},
		{"funnel", charts.RouterOpts{URL: host + "/funnel", Text: "Funnel-(漏斗图)"}},
		{"gauge", charts.RouterOpts{URL: host + "/gauge", Text: "Gauge-仪表盘"}},
		{"geo", charts.RouterOpts{URL: host + "/geo", Text: "Geo-地理坐标系"}},
		{"graph", charts.RouterOpts{URL: host + "/graph", Text: "Graph-关系图"}},
		{"heatMap", charts.RouterOpts{URL: host + "/heatMap", Text: "HeatMap-热力图"}},
		{"kline", charts.RouterOpts{URL: host + "/kline", Text: "Kline-K 线图"}},
		{"line", charts.RouterOpts{URL: host + "/line", Text: "Line-(折线图)"}},
		{"line3D", charts.RouterOpts{URL: host + "/line3D", Text: "Line3D-(3D 折线图)"}},
		{"liquid", charts.RouterOpts{URL: host + "/liquid", Text: "Liquid-(水球图)"}},
		{"map", charts.RouterOpts{URL: host + "/map", Text: "Map-(地图)"}},
		{"overlap", charts.RouterOpts{URL: host + "/overlap", Text: "Overlap-(重叠图)"}},
		{"parallel", charts.RouterOpts{URL: host + "/parallel", Text: "Parallel-(平行坐标系)"}},
		{"pie", charts.RouterOpts{URL: host + "/pie", Text: "Pie-(饼图)"}},
		{"radar", charts.RouterOpts{URL: host + "/radar", Text: "Radar-(雷达图)"}},
		{"sankey", charts.RouterOpts{URL: host + "/sankey", Text: "Sankey-(桑基图)"}},
		{"scatter", charts.RouterOpts{URL: host + "/scatter", Text: "Scatter-(散点图)"}},
		{"scatter3D", charts.RouterOpts{URL: host + "/scatter3D", Text: "Scatter-(3D 散点图)"}},
		{"surface3D", charts.RouterOpts{URL: host + "/surface3D", Text: "Surface3D-(3D 曲面图)"}},
		{"themeRiver", charts.RouterOpts{URL: host + "/themeRiver", Text: "ThemeRiver-(主题河流图)"}},
		{"wordCloud", charts.RouterOpts{URL: host + "/wordCloud", Text: "WordCloud-(词云图)"}},
		{"page", charts.RouterOpts{URL: host + "/page", Text: "Page-(顺序多图)"}},
	}
)

func orderRouters(chartType string) []charts.RouterOpts {
	for i := 0; i < len(routers); i++ {
		if routers[i].name == chartType {
			routers[i], routers[0] = routers[0], routers[i]
			break
		}
	}

	rs := make([]charts.RouterOpts, 0)
	for i := 0; i < len(routers); i++ {
		rs = append(rs, routers[i].RouterOpts)
	}
	return rs
}

func getRenderPath(f string) string {
	return path.Join("html", f)
}

func logTracing(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Printf("Tracing request for %s\n", r.RequestURI)
		next.ServeHTTP(w, r)
	}
}

var seed = rand.NewSource(time.Now().UnixNano())

func randInt() []int {
	cnt := len(nameItems)
	r := make([]int, 0)
	for i := 0; i < cnt; i++ {
		r = append(r, int(seed.Int63())%maxNum)
	}
	return r
}

func genKvData() map[string]interface{} {
	m := make(map[string]interface{})
	for i := 0; i < len(nameItems); i++ {
		m[nameItems[i]] = rand.Intn(maxNum)
	}
	return m
}

func main() {
	// Avoid "404 page not found".
	http.HandleFunc("/", logTracing(barHandler))

	http.HandleFunc("/bar", logTracing(barHandler))
	http.HandleFunc("/bar3D", logTracing(bar3DHandler))
	http.HandleFunc("/boxPlot", logTracing(boxPlotHandler))
	http.HandleFunc("/effectScatter", logTracing(esHandler))
	http.HandleFunc("/funnel", logTracing(funnelHandler))
	http.HandleFunc("/gauge", logTracing(gaugeHandler))
	http.HandleFunc("/geo", logTracing(geoHandler))
	http.HandleFunc("/graph", logTracing(graphHandler))
	http.HandleFunc("/heatMap", logTracing(heatMapHandler))
	http.HandleFunc("/kline", logTracing(klineHandler))
	http.HandleFunc("/line", logTracing(lineHandler))
	http.HandleFunc("/line3D", logTracing(line3DHandler))
	http.HandleFunc("/liquid", logTracing(liquidHandler))
	http.HandleFunc("/map", logTracing(mapHandler))
	http.HandleFunc("/overlap", logTracing(overlapHandler))
	http.HandleFunc("/parallel", logTracing(parallelHandler))
	http.HandleFunc("/pie", logTracing(pieHandler))
	http.HandleFunc("/radar", logTracing(radarHandler))
	http.HandleFunc("/sankey", logTracing(sankeyHandler))
	http.HandleFunc("/scatter", logTracing(scatterHandler))
	http.HandleFunc("/scatter3D", logTracing(scatter3DHandler))
	http.HandleFunc("/surface3D", logTracing(surface3DHandler))
	http.HandleFunc("/themeRiver", logTracing(themeRiverHandler))
	http.HandleFunc("/wordCloud", logTracing(wcHandler))
	http.HandleFunc("/page", logTracing(pageHandler))

	log.Println("Run server at " + host)
	http.ListenAndServe(":8080", nil)
}
<|endoftext|>


<commit_before>// +build ignore

package main

import (
	redigo "github.com/garyburd/redigo/redis"
	"github.com/pierrre/imageserver"
	imageserver_cache_chain "github.com/pierrre/imageserver/cache/chain"
	imageserver_cache_memory "github.com/pierrre/imageserver/cache/memory"
	imageserver_cache_prefix "github.com/pierrre/imageserver/cache/prefix"
	imageserver_cache_redis "github.com/pierrre/imageserver/cache/redis"
	imageserver_http "github.com/pierrre/imageserver/http"
	imageserver_http_parser_graphicsmagick "github.com/pierrre/imageserver/http/parser/graphicsmagick"
	imageserver_http_parser_merge "github.com/pierrre/imageserver/http/parser/merge"
	imageserver_http_parser_source "github.com/pierrre/imageserver/http/parser/source"
	imageserver_processor_graphicsmagick "github.com/pierrre/imageserver/processor/graphicsmagick"
	imageserver_processor_limit "github.com/pierrre/imageserver/processor/limit"
	imageserver_provider_cache "github.com/pierrre/imageserver/provider/cache"
	imageserver_provider_http "github.com/pierrre/imageserver/provider/http"
	"log"
	"net/http"
	"os"
	"strconv"
	"time"
)

func main() {
	hostname, err := os.Hostname()
	if err != nil {
		panic(err)
	}

	cache := imageserver_cache_chain.ChainCache{
		imageserver_cache_memory.New(10 * 1024 * 1024),
		&imageserver_cache_redis.RedisCache{
			Pool: &redigo.Pool{
				Dial: func() (redigo.Conn, error) {
					return redigo.Dial("tcp", "localhost:6379")
				},
				MaxIdle: 50,
			},
			Expire: time.Duration(7 * 24 * time.Hour),
		},
	}

	imageServer := &imageserver.Server{
		Cache: &imageserver_cache_prefix.PrefixCache{
			Prefix: "processed:",
			Cache:  cache,
		},
		Provider: &imageserver_provider_cache.CacheProvider{
			Cache: &imageserver_cache_prefix.PrefixCache{
				Prefix: "source:",
				Cache:  cache,
			},
			Provider: &imageserver_provider_http.HttpProvider{},
		},
		Processor: imageserver_processor_limit.New(16, &imageserver_processor_graphicsmagick.GraphicsMagickProcessor{
			Executable: "gm",
			Timeout:    time.Duration(5 * time.Second),
			AllowedFormats: []string{
				"jpeg",
				"png",
				"bmp",
				"gif",
			},
			DefaultQualities: map[string]string{
				"jpeg": "85",
			},
		}),
	}

	httpImageServer := &imageserver_http.Server{
		Parser: &imageserver_http_parser_merge.MergeParser{
			&imageserver_http_parser_source.SourceParser{},
			&imageserver_http_parser_graphicsmagick.GraphicsMagickParser{},
		},
		ImageServer: imageServer,
		Expire:      time.Duration(7 * 24 * time.Hour),
		RequestFunc: func(request *http.Request) error {
			url := request.URL
			query := url.Query()
			errorCodeString := query.Get("error")
			if len(errorCodeString) == 0 {
				return nil
			}
			errorCode, err := strconv.Atoi(errorCodeString)
			if err != nil {
				return imageserver.NewError(err.Error())
			}
			return imageserver_http.NewError(errorCode)
		},
		HeaderFunc: func(header http.Header, request *http.Request, err error) {
			header.Set("X-Hostname", hostname)
		},
		ErrorFunc: func(err error, request *http.Request) {
			log.Println(err)
		},
		ResponseFunc: func(request *http.Request, statusCode int, contentSize int64, err error) {
			var errString string
			if err != nil {
				errString = err.Error()
			}
			log.Println(request.Method, strconv.Quote(request.URL.String()), statusCode, contentSize, strconv.Quote(errString))
		},
	}

	http.Handle("/", httpImageServer)
	err = http.ListenAndServe(":8080", nil)
	if err != nil {
		panic(err)
	}
}
<commit_msg>Add remote addr in example log<commit_after>// +build ignore

package main

import (
	redigo "github.com/garyburd/redigo/redis"
	"github.com/pierrre/imageserver"
	imageserver_cache_chain "github.com/pierrre/imageserver/cache/chain"
	imageserver_cache_memory "github.com/pierrre/imageserver/cache/memory"
	imageserver_cache_prefix "github.com/pierrre/imageserver/cache/prefix"
	imageserver_cache_redis "github.com/pierrre/imageserver/cache/redis"
	imageserver_http "github.com/pierrre/imageserver/http"
	imageserver_http_parser_graphicsmagick "github.com/pierrre/imageserver/http/parser/graphicsmagick"
	imageserver_http_parser_merge "github.com/pierrre/imageserver/http/parser/merge"
	imageserver_http_parser_source "github.com/pierrre/imageserver/http/parser/source"
	imageserver_processor_graphicsmagick "github.com/pierrre/imageserver/processor/graphicsmagick"
	imageserver_processor_limit "github.com/pierrre/imageserver/processor/limit"
	imageserver_provider_cache "github.com/pierrre/imageserver/provider/cache"
	imageserver_provider_http "github.com/pierrre/imageserver/provider/http"
	"log"
	"net/http"
	"os"
	"strconv"
	"time"
)

func main() {
	hostname, err := os.Hostname()
	if err != nil {
		panic(err)
	}

	cache := imageserver_cache_chain.ChainCache{
		imageserver_cache_memory.New(10 * 1024 * 1024),
		&imageserver_cache_redis.RedisCache{
			Pool: &redigo.Pool{
				Dial: func() (redigo.Conn, error) {
					return redigo.Dial("tcp", "localhost:6379")
				},
				MaxIdle: 50,
			},
			Expire: time.Duration(7 * 24 * time.Hour),
		},
	}

	imageServer := &imageserver.Server{
		Cache: &imageserver_cache_prefix.PrefixCache{
			Prefix: "processed:",
			Cache:  cache,
		},
		Provider: &imageserver_provider_cache.CacheProvider{
			Cache: &imageserver_cache_prefix.PrefixCache{
				Prefix: "source:",
				Cache:  cache,
			},
			Provider: &imageserver_provider_http.HttpProvider{},
		},
		Processor: imageserver_processor_limit.New(16, &imageserver_processor_graphicsmagick.GraphicsMagickProcessor{
			Executable: "gm",
			Timeout:    time.Duration(5 * time.Second),
			AllowedFormats: []string{
				"jpeg",
				"png",
				"bmp",
				"gif",
			},
			DefaultQualities: map[string]string{
				"jpeg": "85",
			},
		}),
	}

	httpImageServer := &imageserver_http.Server{
		Parser: &imageserver_http_parser_merge.MergeParser{
			&imageserver_http_parser_source.SourceParser{},
			&imageserver_http_parser_graphicsmagick.GraphicsMagickParser{},
		},
		ImageServer: imageServer,
		Expire:      time.Duration(7 * 24 * time.Hour),
		RequestFunc: func(request *http.Request) error {
			url := request.URL
			query := url.Query()
			errorCodeString := query.Get("error")
			if len(errorCodeString) == 0 {
				return nil
			}
			errorCode, err := strconv.Atoi(errorCodeString)
			if err != nil {
				return imageserver.NewError(err.Error())
			}
			return imageserver_http.NewError(errorCode)
		},
		HeaderFunc: func(header http.Header, request *http.Request, err error) {
			header.Set("X-Hostname", hostname)
		},
		ErrorFunc: func(err error, request *http.Request) {
			log.Println(err)
		},
		ResponseFunc: func(request *http.Request, statusCode int, contentSize int64, err error) {
			var errString string
			if err != nil {
				errString = err.Error()
			}
			log.Println(request.RemoteAddr, request.Method, strconv.Quote(request.URL.String()), statusCode, contentSize, strconv.Quote(errString))
		},
	}

	http.Handle("/", httpImageServer)
	err = http.ListenAndServe(":8080", nil)
	if err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>package server

import (
	"net/http"
)

type interceptingResponseWriter struct {
	http.ResponseWriter
	Flusher http.Flusher
}

func (i *interceptingResponseWriter) WriteHeader(status int) {
	i.ResponseWriter.WriteHeader(status)
}

// Flush is here because the underlying HTTP chunked transfer response writer
// to implement http.Flusher.  Without it data is silently buffered.  This
// was discovered when proxying kapacitor chunked logs.
func (i *interceptingResponseWriter) Flush() {
	if i.Flusher != nil {
		i.Flusher.Flush()
	}
}

// PrefixedRedirect alters the Location header of downstream http.Handlers
// to include a specified prefix
func PrefixedRedirect(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		iw := &interceptingResponseWriter{
			ResponseWriter: w,
		}
		if flusher, ok := w.(http.Flusher); ok {
			iw.Flusher = flusher
		}
		next.ServeHTTP(iw, r)
	})
}
<commit_msg>Refactor interceptingResponseWrite Flusher for clarity<commit_after>package server

import (
	"net/http"
)

type interceptingResponseWriter struct {
	http.ResponseWriter
}

func (i *interceptingResponseWriter) WriteHeader(status int) {
	i.ResponseWriter.WriteHeader(status)
}

// Flush is here because the underlying HTTP chunked transfer response writer
// to implement http.Flusher.  Without it data is silently buffered.  This
// was discovered when proxying kapacitor chunked logs.
func (i *interceptingResponseWriter) Flush() {
	if flusher, ok := i.ResponseWriter.(http.Flusher); ok {
		flusher.Flush()
	}
}

// PrefixedRedirect alters the Location header of downstream http.Handlers
// to include a specified prefix
func PrefixedRedirect(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		iw := &interceptingResponseWriter{
			ResponseWriter: w,
		}
		next.ServeHTTP(iw, r)
	})
}
<|endoftext|>


<commit_before>package server

import (
	"crypto/tls"
	"net/http"
	"net/url"
	"testing"
)

func TestHostName(t *testing.T) {
	testcases := []struct {
		url      string
		hostName string
		port     string
		expected string
	}{
		{
			// With hostname & port unset in config, expect host:port matching URL
			url:      "http://localhost:8080/capabilities",
			expected: "localhost:8080",
		},
		{
			// With hostname set and port set to "none" in config, expect "cdn.tegola.io"
			url:      "http://localhost:8080/capabilities",
			hostName: "cdn.tegola.io",
			port:     "none",
			expected: "cdn.tegola.io",
		},
		{
			// Hostname set, no port in config, but port in url.  Expect <config_host>:<url_port>.
			url:      "http://localhost:8080/capabilities",
			hostName: "cdn.tegola.io",
			expected: "cdn.tegola.io:8080",
		},
		{
			// Hostname set, no port in config or url, expect hostname to match config.
			url:      "http://localhost/capabilities",
			hostName: "cdn.tegola.io",
			expected: "cdn.tegola.io",
		},
		{
			// Hostname unset, no port in config or url, expect hostname to match url host.
			url:      "http://localhost/capabilities",
			expected: "localhost",
		},
	}

	for i, tc := range testcases {
		//	set the package variable
		HostName = tc.hostName
		Port = tc.port

		url, err := url.Parse(tc.url)
		if err != nil {
			t.Errorf("testcase (%v) failed. could not create url.URL from (%v): %v", tc.url, err)
		}

		req := http.Request{URL: url, Host: url.Host}

		output := hostName(&req)
		if output != tc.expected {
			t.Errorf("testcase (%v) failed. expected (%v) does not match result (%v)", i, tc.expected, output)
		}
	}
}

func TestScheme(t *testing.T) {
	testcases := []struct {
		request  http.Request
		expected string
	}{
		{
			request:  http.Request{},
			expected: "http",
		},
		{
			request: http.Request{
				TLS: &tls.ConnectionState{},
			},
			expected: "https",
		},
		{
			request: http.Request{
				Header: map[string][]string{
					"X-Forwarded-Proto": []string{
						"https",
						"http",
					},
				},
			},
			expected: "https",
		},
	}

	for i, tc := range testcases {
		output := scheme(&tc.request)
		if output != tc.expected {
			t.Errorf("testcase (%v) failed. expected (%v) does not match result (%v)", i, tc.expected, output)
		}
	}
}
<commit_msg>fixed test case error index in TestHostName. #199<commit_after>package server

import (
	"crypto/tls"
	"net/http"
	"net/url"
	"testing"
)

func TestHostName(t *testing.T) {
	testcases := []struct {
		url      string
		hostName string
		port     string
		expected string
	}{
		{
			// With hostname & port unset in config, expect host:port matching URL
			url:      "http://localhost:8080/capabilities",
			expected: "localhost:8080",
		},
		{
			// With hostname set and port set to "none" in config, expect "cdn.tegola.io"
			url:      "http://localhost:8080/capabilities",
			hostName: "cdn.tegola.io",
			port:     "none",
			expected: "cdn.tegola.io",
		},
		{
			// Hostname set, no port in config, but port in url.  Expect <config_host>:<url_port>.
			url:      "http://localhost:8080/capabilities",
			hostName: "cdn.tegola.io",
			expected: "cdn.tegola.io:8080",
		},
		{
			// Hostname set, no port in config or url, expect hostname to match config.
			url:      "http://localhost/capabilities",
			hostName: "cdn.tegola.io",
			expected: "cdn.tegola.io",
		},
		{
			// Hostname unset, no port in config or url, expect hostname to match url host.
			url:      "http://localhost/capabilities",
			expected: "localhost",
		},
	}

	for i, tc := range testcases {
		//	set the package variable
		HostName = tc.hostName
		Port = tc.port

		url, err := url.Parse(tc.url)
		if err != nil {
			t.Errorf("testcase (%v) failed. could not create url.URL from (%v): %v", i, tc.url, err)
		}

		req := http.Request{URL: url, Host: url.Host}

		output := hostName(&req)
		if output != tc.expected {
			t.Errorf("testcase (%v) failed. expected (%v) does not match result (%v)", i, tc.expected, output)
		}
	}
}

func TestScheme(t *testing.T) {
	testcases := []struct {
		request  http.Request
		expected string
	}{
		{
			request:  http.Request{},
			expected: "http",
		},
		{
			request: http.Request{
				TLS: &tls.ConnectionState{},
			},
			expected: "https",
		},
		{
			request: http.Request{
				Header: map[string][]string{
					"X-Forwarded-Proto": []string{
						"https",
						"http",
					},
				},
			},
			expected: "https",
		},
	}

	for i, tc := range testcases {
		output := scheme(&tc.request)
		if output != tc.expected {
			t.Errorf("testcase (%v) failed. expected (%v) does not match result (%v)", i, tc.expected, output)
		}
	}
}
<|endoftext|>


<commit_before>package models

import (
	"time"

	"github.com/brocaar/lorawan"
	"github.com/brocaar/lorawan/band"
)

// RXPacket contains the PHYPayload received from GatewayMAC.
type RXPacket struct {
	RXInfo     RXInfo             `json:"rxInfo"`
	PHYPayload lorawan.PHYPayload `json:"phyPayload"`
}

// RXInfo contains the RX information.
type RXInfo struct {
	MAC       lorawan.EUI64 `json:"mac"`       // MAC address of the gateway
	Time      time.Time     `json:"time"`      // receive time
	Timestamp uint32        `json:"timestamp"` // gateway internal receive timestamp with microsecond precision, will rollover every ~ 72 minutes
	Frequency int           `json:"frequency"` // frequency in Hz
	Channel   int           `json:"channel"`   // concentrator IF channel used for RX
	RFChain   int           `json:"rfChain"`   // RF chain used for RX
	CRCStatus int           `json:"crcStatus"` // 1 = OK, -1 = fail, 0 = no CRC
	CodeRate  string        `json:"codeRate"`  // ECC code rate
	RSSI      int           `json:"rssi"`      // RSSI in dBm
	LoRaSNR   float64       `json:"loRaSNR"`   // LoRa signal-to-noise ratio in dB
	Size      int           `json:"size"`      // packet payload size
	DataRate  band.DataRate `json:"dataRate"`  // RX datarate (either LoRa or FSK)
}

// TXPacket contains the PHYPayload which should be send to the
// gateway.
type TXPacket struct {
	TXInfo     TXInfo             `json:"txInfo"`
	PHYPayload lorawan.PHYPayload `json:"phyPayload"`
}

// TXInfo contains the information used for TX.
type TXInfo struct {
	MAC         lorawan.EUI64 `json:"mac"`         // MAC address of the gateway
	Immediately bool          `json:"immediately"` // send the packet immediately (ignore Time)
	Timestamp   uint32        `json:"timestamp"`   // gateway internal receive timestamp with microsecond precision, will rollover every ~ 72 minutes
	Frequency   int           `json:"frequency"`   // frequency in Hz
	Power       int           `json:"power"`       // TX power to use in dBm
	DataRate    band.DataRate `json:"dataRate"`    // TX datarate (either LoRa or FSK)
	CodeRate    string        `json:"codeRate"`    // ECC code rate
}

// GatewayStatsPacket contains the information of a gateway.
type GatewayStatsPacket struct {
	MAC                 lorawan.EUI64 `json:"mac"`
	Time                time.Time     `json:"time"`
	Latitude            float64       `json:"latitude"`
	Longitude           float64       `json:"longitude"`
	Altitude            float64       `json:"altitude"`
	RXPacketsReceived   int           `json:"rxPacketsReceived"`
	RXPacketsReceivedOK int           `json:"rxPacketsReceivedOK"`
}

// RXPayload contains the received (decrypted) payload from the node
// which will be sent to the application.
type RXPayload struct {
	DevEUI       lorawan.EUI64 `json:"devEUI"`
	FPort        uint8         `json:"fPort"`
	GatewayCount int           `json:"gatewayCount"`
	RSSI         int           `json:"rssi"`
	Data         []byte        `json:"data"`
}

// TXPayload contains the payload to be sent to the node.
type TXPayload struct {
	Reference string        `json:"reference"` // external reference that needs to be defined by the application, used for error notifications
	Confirmed bool          `json:"confirmed"` // indicates if the packet needs to be confirmed with an ACK
	DevEUI    lorawan.EUI64 `json:"devEUI"`    // the DevEUI of the node to send the payload to
	FPort     uint8         `json:"fPort"`     // the FPort
	Data      []byte        `json:"data"`      // the data to send (unencrypted, in JSON this must be the base64 representation of the data)
}

// MACPayload contains data from a MAC command.
type MACPayload struct {
	Reference  string        `json:"reference,omitempty"` // external reference that needs to be defined by the network-controller
	FRMPayload bool          `json:"frmPayload"`          // indicating if the mac command was or must be sent as a FRMPayload (and thus encrypted)
	DevEUI     lorawan.EUI64 `json:"devEUI"`
	MACCommand []byte        `json:"macCommand"`
}

// RXInfoPayload defines the payload containing the RX information sent to
// the network-controller on each received packet.
type RXInfoPayload struct {
	DevEUI lorawan.EUI64 `json:"devEUI"`
	ADR    bool          `json:"adr"`
	FCnt   uint32        `json:"fCnt"`
	RXInfo []RXInfo      `json:"rxInfo"`
}

// ErrorPayload defines the payload sent on an error event.
type ErrorPayload struct {
	Reference string        `json:"reference"` // refers to the given reference
	DevEUI    lorawan.EUI64 `json:"devEUI"`
	Message   string        `json:"message"`
}
<commit_msg>Add TxPacketsEmitted (txnb in Semtech protocol)<commit_after>package models

import (
	"time"

	"github.com/brocaar/lorawan"
	"github.com/brocaar/lorawan/band"
)

// RXPacket contains the PHYPayload received from GatewayMAC.
type RXPacket struct {
	RXInfo     RXInfo             `json:"rxInfo"`
	PHYPayload lorawan.PHYPayload `json:"phyPayload"`
}

// RXInfo contains the RX information.
type RXInfo struct {
	MAC       lorawan.EUI64 `json:"mac"`       // MAC address of the gateway
	Time      time.Time     `json:"time"`      // receive time
	Timestamp uint32        `json:"timestamp"` // gateway internal receive timestamp with microsecond precision, will rollover every ~ 72 minutes
	Frequency int           `json:"frequency"` // frequency in Hz
	Channel   int           `json:"channel"`   // concentrator IF channel used for RX
	RFChain   int           `json:"rfChain"`   // RF chain used for RX
	CRCStatus int           `json:"crcStatus"` // 1 = OK, -1 = fail, 0 = no CRC
	CodeRate  string        `json:"codeRate"`  // ECC code rate
	RSSI      int           `json:"rssi"`      // RSSI in dBm
	LoRaSNR   float64       `json:"loRaSNR"`   // LoRa signal-to-noise ratio in dB
	Size      int           `json:"size"`      // packet payload size
	DataRate  band.DataRate `json:"dataRate"`  // RX datarate (either LoRa or FSK)
}

// TXPacket contains the PHYPayload which should be send to the
// gateway.
type TXPacket struct {
	TXInfo     TXInfo             `json:"txInfo"`
	PHYPayload lorawan.PHYPayload `json:"phyPayload"`
}

// TXInfo contains the information used for TX.
type TXInfo struct {
	MAC         lorawan.EUI64 `json:"mac"`         // MAC address of the gateway
	Immediately bool          `json:"immediately"` // send the packet immediately (ignore Time)
	Timestamp   uint32        `json:"timestamp"`   // gateway internal receive timestamp with microsecond precision, will rollover every ~ 72 minutes
	Frequency   int           `json:"frequency"`   // frequency in Hz
	Power       int           `json:"power"`       // TX power to use in dBm
	DataRate    band.DataRate `json:"dataRate"`    // TX datarate (either LoRa or FSK)
	CodeRate    string        `json:"codeRate"`    // ECC code rate
}

// GatewayStatsPacket contains the information of a gateway.
type GatewayStatsPacket struct {
	MAC                 lorawan.EUI64 `json:"mac"`
	Time                time.Time     `json:"time"`
	Latitude            float64       `json:"latitude"`
	Longitude           float64       `json:"longitude"`
	Altitude            float64       `json:"altitude"`
	RXPacketsReceived   int           `json:"rxPacketsReceived"`
	RXPacketsReceivedOK int           `json:"rxPacketsReceivedOK"`
	TxPacketsEmitted    int           `json:"txPacketsEmitted"`
}

// RXPayload contains the received (decrypted) payload from the node
// which will be sent to the application.
type RXPayload struct {
	DevEUI       lorawan.EUI64 `json:"devEUI"`
	FPort        uint8         `json:"fPort"`
	GatewayCount int           `json:"gatewayCount"`
	RSSI         int           `json:"rssi"`
	Data         []byte        `json:"data"`
}

// TXPayload contains the payload to be sent to the node.
type TXPayload struct {
	Reference string        `json:"reference"` // external reference that needs to be defined by the application, used for error notifications
	Confirmed bool          `json:"confirmed"` // indicates if the packet needs to be confirmed with an ACK
	DevEUI    lorawan.EUI64 `json:"devEUI"`    // the DevEUI of the node to send the payload to
	FPort     uint8         `json:"fPort"`     // the FPort
	Data      []byte        `json:"data"`      // the data to send (unencrypted, in JSON this must be the base64 representation of the data)
}

// MACPayload contains data from a MAC command.
type MACPayload struct {
	Reference  string        `json:"reference,omitempty"` // external reference that needs to be defined by the network-controller
	FRMPayload bool          `json:"frmPayload"`          // indicating if the mac command was or must be sent as a FRMPayload (and thus encrypted)
	DevEUI     lorawan.EUI64 `json:"devEUI"`
	MACCommand []byte        `json:"macCommand"`
}

// RXInfoPayload defines the payload containing the RX information sent to
// the network-controller on each received packet.
type RXInfoPayload struct {
	DevEUI lorawan.EUI64 `json:"devEUI"`
	ADR    bool          `json:"adr"`
	FCnt   uint32        `json:"fCnt"`
	RXInfo []RXInfo      `json:"rxInfo"`
}

// ErrorPayload defines the payload sent on an error event.
type ErrorPayload struct {
	Reference string        `json:"reference"` // refers to the given reference
	DevEUI    lorawan.EUI64 `json:"devEUI"`
	Message   string        `json:"message"`
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"net/url"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/go-ini/ini"
	"gopkg.in/yaml.v2"
	"io/ioutil"
)

const CACHE_INTERVAL = 1 * time.Hour
const ORDERING_YAML_NAME = "ordering.yaml"

type Album struct {
	site *Site

	Path         string
	BucketPrefix string

	AuthUser string
	AuthPass string

	MetaTitle  string
	AlbumTitle string
	Ordering AlbumOrdering

	InIndex bool

	KeyCache        atomic.Value
	OrderingCache        atomic.Value
	LastKeyCacheUpdate time.Time
	LastOrderingCacheUpdate time.Time

	KeyCacheUpdateMutex      sync.Mutex
	AlbumOrderingUpdateMutex sync.Mutex
}

type AlbumOrdering struct {
	Cover string
	Thumbnails []string
	Ordering []string
}

type GetFromKeyCacheResult struct {
	keys []string
	err  error
}

type GetFromOrderingCacheResult struct {
	ordering AlbumOrdering
	err  error
}

func NewAlbumFromConfig(section *ini.Section, s *Site) (*Album, error) {
	album := &Album{site: s, InIndex: true}
	if err := section.MapTo(album); err != nil {
		return nil, err
	}

	if err := album.IsValid(); err != nil {
		return nil, err
	}

	album.Canonicalize()
	return album, nil
}

func NewAlbum(s *Site, path string, bucketPrefix string, authUser string, authPass string, metaTitle string, albumTitle string) (*Album, error) {
	album := &Album{
		site:         s,
		Path:         path,
		BucketPrefix: bucketPrefix,
		AuthUser:     authUser,
		AuthPass:     authPass,
		MetaTitle:    metaTitle,
		AlbumTitle:   albumTitle,
		InIndex:      true,
	}

	if err := album.IsValid(); err != nil {
		return nil, err
	}

	album.Canonicalize()
	return album, nil
}

func (a *Album) IsValid() error {
	if a.Path == "" {
		return errors.New("'Path' is a required parameters that must have a valid value.")
	}

	if a.InIndex && a.HasOwnAuth() {
		return errors.New("An album that requires authentication can't be shown in the index. If you need authentication please add it to the site.")
	}
	return nil
}

func (a *Album) Canonicalize() {
	if a.Path[len(a.Path)-1] != '/' {
		a.Path = a.Path + "/"
	}
}

func (a *Album) HasOwnAuth() bool {
	return a.AuthUser != "" && a.AuthPass != ""
}

// An album inherits it's sites auth settings if the album config doesn't override them. If both the site and album have
// auth enabled, the album auth takes precedence
func (a *Album) HasAuth() bool {
	return a.site.HasAuth() || a.HasOwnAuth()
}

func (a *Album) GetAuthUser() string {
	if a.AuthUser != "" {
		return a.AuthUser
	} else {
		return a.site.AuthUser
	}
}

func (a *Album) GetAuthPass() string {
	if a.AuthPass != "" {
		return a.AuthPass
	} else {
		return a.site.AuthPass
	}
}

func (a *Album) GetCanonicalUrl() *url.URL {
	u := a.site.GetCanonicalUrl()
	u.Path = a.Path
	return u
}

func (a *Album) GetCoverPhoto() (Renderable, error) {
	//TODO pick this up through the ordering struct
	if photos, err := a.GetOrderedPhotos(); err != nil {
		return nil, err
	} else {
		if len(photos) > 0 {
			return photos[0], nil
		}
	}

	return &ErrorPhoto{}, nil
}

func (a *Album) GetCoverPhotoForTemplate() Renderable {
	if photo, err := a.GetCoverPhoto(); err != nil {
		fmt.Printf("Unable to get cover photo. Error: %s\n", err.Error())
		return &ErrorPhoto{}
	} else {
		return photo
	}
}

func (a *Album) GetThumbnailPhotosForTemplate() []Renderable {
	if photos, err := a.GetOrderedPhotos(); err != nil {
		fmt.Printf("Unable to get thumbnail photos. Error: %s\n", err.Error())
		return nil
	} else {
		//TODO pick this up through the ordering struct
		if len(photos) > 6 {
			return photos[1:6]
		} else if len(photos) > 0 {
			return photos[1:]
		} else {
			return nil
		}
	}
}

//lowest level, gets the list of objects in the bucket and prefix that
//corresponds to the album it is acting on, it's an object with multiple
//fields.
func (a *Album) GetAllObjects() ([]*s3.Object, error) {
	svc, err := a.site.GetS3Service()
	if err != nil {
		return nil, err
	}

	objects, err := svc.ListObjects(&s3.ListObjectsInput{
		Bucket:    aws.String(a.site.BucketName),
		Prefix:    aws.String(a.BucketPrefix),
		Delimiter: aws.String("/"),
	})
	if err != nil {
		return nil, err
	}
	return objects.Contents, nil
}

//wrapper around the lowest level method to extract out the fields of relevance, namely
//the key of an object, also drops prefixes (i.e: the folder path) from that output.
func (a *Album) GetAllObjectKeysFromBucket() ([]string, error) {
	objects, err := a.GetAllObjects()
	if err != nil {
		return nil, err
	}

	var imageKeys []string
	for _, obj := range objects {
		key := *obj.Key
		if key[len(*obj.Key)-1] != '/' {
			//check for 'folder' name vs actual object - objects end without trailing /
			imageKeys = append(imageKeys, key)
		}
	}

	return imageKeys, nil
}

//highest level, acts on an album to return processed renderable imageurls, here we must also
//filter out any non-renderables and process any other metadata we expect to find.
func (a *Album) GetOrderedPhotos() ([]Renderable, error) {
	var imageUrls []Renderable

	albumOrdering, err := a.GetAlbumOrdering()

	if err != nil {
		fmt.Printf("Unable to pick up album ordering from S3 ordering.yaml because: %s", err.Error())
	}

	fmt.Print(albumOrdering)

	imageKeys, err := a.GetAllObjectKeys()
	if err != nil {
		fmt.Printf("Unable to get object keys from S3. Error: %s\n", err.Error())
		return imageUrls, err
	}

	for _, v := range imageKeys {
		if strings.HasSuffix(v, "ordering.yaml") {
			//for now, just do nothing, we simply want to avoid appending,
			//when we agree on a list of valid formats, we can ditch this check.
		} else {
			imageUrl := a.site.GetPhotoForKey(v)
			imageUrls = append(imageUrls, imageUrl)
		}
	}

	return imageUrls, nil
}

//wrapper around GetAllObjectKeysFromBucket to add in a caching layer, nothing below
//this layer filters or reorders the list of **objects** returned from S3.
//note that this DOES filter out the album prefix.
func (a *Album) GetAllObjectKeys() ([]string, error) {
	c := make(chan *GetFromKeyCacheResult)
	go func() {
		var keys []string
		var err error

		if a.KeyCache.Load() != nil {
			c <- &GetFromKeyCacheResult{a.KeyCache.Load().([]string), nil}

			a.KeyCacheUpdateMutex.Lock()
			if a.NeedsKeyCacheUpdate() {
				keys, err = a.GetAllObjectKeysFromBucket()
				if err == nil {
					a.KeyCache.Store(keys)
					a.LastKeyCacheUpdate = time.Now()
				}
			}

			a.KeyCacheUpdateMutex.Unlock()
		} else {
			a.KeyCacheUpdateMutex.Lock()

			keys, err = a.GetAllObjectKeysFromBucket()
			if err == nil {
				a.KeyCache.Store(keys)
				a.LastKeyCacheUpdate = time.Now()
			}
			c <- &GetFromKeyCacheResult{keys, err}

			a.KeyCacheUpdateMutex.Unlock()
		}
	}()

	result := <-c
	if result.err != nil {
		return nil, result.err
	} else {
		return result.keys, result.err
	}
}

//retrieves the actual album ordering from s3, it expects a file as hard-coded in
// the constant ORDERING_YAML_NAME
func (a *Album) GetAlbumOrderingFromS3() (AlbumOrdering, error) {
	var albumOrdering AlbumOrdering

	svc, err := a.site.GetS3Service()
	if err != nil {
		return albumOrdering, err
	}

	orderingYAMLKey := strings.Join([]string{a.BucketPrefix, ORDERING_YAML_NAME}, "/")
	yaml_object, err := svc.GetObject(&s3.GetObjectInput{
		Bucket: aws.String(a.site.BucketName),
		Key:    aws.String(orderingYAMLKey),
	})

	if err != nil {
		return albumOrdering, err
	}

	//extract the contents from what we read so we can then parse the yaml
	data_bytes, err := ioutil.ReadAll(yaml_object.Body)
	//data_string := string(data_bytes)
	err = yaml.Unmarshal(data_bytes, &albumOrdering)

	if err != nil {
		return albumOrdering, err
	}

	return albumOrdering, nil
}

//note that this also caches negative values, i.e: adding a ordering file may take an hour
//to be rechecked.
func (a *Album) GetAlbumOrdering() (AlbumOrdering, error) {
	c := make(chan *GetFromOrderingCacheResult)
	go func() {
		if a.OrderingCache.Load() != nil {
			c <- &GetFromOrderingCacheResult{a.OrderingCache.Load().(AlbumOrdering), nil}

			a.AlbumOrderingUpdateMutex.Lock()
			if a.NeedsOrderingCacheUpdate() {

				albumOrdering, err := a.GetAlbumOrderingFromS3()
				if err == nil {
					a.OrderingCache.Store(albumOrdering)
					a.LastOrderingCacheUpdate = time.Now()
				}
			}
			a.AlbumOrderingUpdateMutex.Unlock()
		} else {
			a.AlbumOrderingUpdateMutex.Lock()
			albumOrdering, err := a.GetAlbumOrderingFromS3()
			if err == nil {
				a.OrderingCache.Store(albumOrdering)
				a.LastOrderingCacheUpdate = time.Now()
			}

			c <- &GetFromOrderingCacheResult{albumOrdering, err}

			a.AlbumOrderingUpdateMutex.Unlock()
		}
	}()

	var albumOrdering AlbumOrdering
	result := <-c
	if result.err != nil {
		//consumer should be checking err
		return albumOrdering, result.err
	} else {
		return result.ordering, result.err
	}
}

func (a *Album) ImageExists(slug string) bool {
	svc, err := a.site.GetS3Service()
	if err != nil {
		return false
	}

	key := strings.Join([]string{a.BucketPrefix, slug}, "/")
	_, err = svc.HeadObject(&s3.HeadObjectInput{
		Bucket: aws.String(a.site.BucketName),
		Key:    aws.String(key),
	})

	if err != nil {
		return false
	}

	return true
}

func (a *Album) NeedsKeyCacheUpdate() bool {
	return time.Now().Sub(a.LastKeyCacheUpdate) > CACHE_INTERVAL
}

func (a *Album) NeedsOrderingCacheUpdate() bool {
	return time.Now().Sub(a.LastOrderingCacheUpdate) > CACHE_INTERVAL
}
<commit_msg>add in negative caching for cases where key does not exist and where the file is not valid yaml<commit_after>package main

import (
	"errors"
	"fmt"
	"net/url"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/go-ini/ini"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"github.com/aws/aws-sdk-go/aws/awserr"
)

const CACHE_INTERVAL = 1 * time.Hour
const ORDERING_YAML_NAME = "ordering.yaml"

type Album struct {
	site *Site

	Path         string
	BucketPrefix string

	AuthUser string
	AuthPass string

	MetaTitle  string
	AlbumTitle string
	Ordering AlbumOrdering

	InIndex bool

	KeyCache        atomic.Value
	OrderingCache        atomic.Value
	LastKeyCacheUpdate time.Time
	LastOrderingCacheUpdate time.Time

	KeyCacheUpdateMutex      sync.Mutex
	AlbumOrderingUpdateMutex sync.Mutex
}

type AlbumOrdering struct {
	Cover             string
	Thumbnails        []string
	Ordering          []string
	negativeCacheThis bool
}

type GetFromKeyCacheResult struct {
	keys []string
	err  error
}

type GetFromOrderingCacheResult struct {
	ordering AlbumOrdering
	err  error
}

func NewAlbumFromConfig(section *ini.Section, s *Site) (*Album, error) {
	album := &Album{site: s, InIndex: true}
	if err := section.MapTo(album); err != nil {
		return nil, err
	}

	if err := album.IsValid(); err != nil {
		return nil, err
	}

	album.Canonicalize()
	return album, nil
}

func NewAlbum(s *Site, path string, bucketPrefix string, authUser string, authPass string, metaTitle string, albumTitle string) (*Album, error) {
	album := &Album{
		site:         s,
		Path:         path,
		BucketPrefix: bucketPrefix,
		AuthUser:     authUser,
		AuthPass:     authPass,
		MetaTitle:    metaTitle,
		AlbumTitle:   albumTitle,
		InIndex:      true,
	}

	if err := album.IsValid(); err != nil {
		return nil, err
	}

	album.Canonicalize()
	return album, nil
}

func (a *Album) IsValid() error {
	if a.Path == "" {
		return errors.New("'Path' is a required parameters that must have a valid value.")
	}

	if a.InIndex && a.HasOwnAuth() {
		return errors.New("An album that requires authentication can't be shown in the index. If you need authentication please add it to the site.")
	}
	return nil
}

func (a *Album) Canonicalize() {
	if a.Path[len(a.Path)-1] != '/' {
		a.Path = a.Path + "/"
	}
}

func (a *Album) HasOwnAuth() bool {
	return a.AuthUser != "" && a.AuthPass != ""
}

// An album inherits it's sites auth settings if the album config doesn't override them. If both the site and album have
// auth enabled, the album auth takes precedence
func (a *Album) HasAuth() bool {
	return a.site.HasAuth() || a.HasOwnAuth()
}

func (a *Album) GetAuthUser() string {
	if a.AuthUser != "" {
		return a.AuthUser
	} else {
		return a.site.AuthUser
	}
}

func (a *Album) GetAuthPass() string {
	if a.AuthPass != "" {
		return a.AuthPass
	} else {
		return a.site.AuthPass
	}
}

func (a *Album) GetCanonicalUrl() *url.URL {
	u := a.site.GetCanonicalUrl()
	u.Path = a.Path
	return u
}

func (a *Album) GetCoverPhoto() (Renderable, error) {
	//TODO pick this up through the ordering struct
	if photos, err := a.GetOrderedPhotos(); err != nil {
		return nil, err
	} else {
		if len(photos) > 0 {
			return photos[0], nil
		}
	}

	return &ErrorPhoto{}, nil
}

func (a *Album) GetCoverPhotoForTemplate() Renderable {
	if photo, err := a.GetCoverPhoto(); err != nil {
		fmt.Printf("Unable to get cover photo. Error: %s\n", err.Error())
		return &ErrorPhoto{}
	} else {
		return photo
	}
}

func (a *Album) GetThumbnailPhotosForTemplate() []Renderable {
	if photos, err := a.GetOrderedPhotos(); err != nil {
		fmt.Printf("Unable to get thumbnail photos. Error: %s\n", err.Error())
		return nil
	} else {
		//TODO pick this up through the ordering struct
		if len(photos) > 6 {
			return photos[1:6]
		} else if len(photos) > 0 {
			return photos[1:]
		} else {
			return nil
		}
	}
}

//lowest level, gets the list of objects in the bucket and prefix that
//corresponds to the album it is acting on, it's an object with multiple
//fields.
func (a *Album) GetAllObjects() ([]*s3.Object, error) {
	svc, err := a.site.GetS3Service()
	if err != nil {
		return nil, err
	}

	objects, err := svc.ListObjects(&s3.ListObjectsInput{
		Bucket:    aws.String(a.site.BucketName),
		Prefix:    aws.String(a.BucketPrefix),
		Delimiter: aws.String("/"),
	})
	if err != nil {
		return nil, err
	}
	return objects.Contents, nil
}

//wrapper around the lowest level method to extract out the fields of relevance, namely
//the key of an object, also drops prefixes (i.e: the folder path) from that output.
func (a *Album) GetAllObjectKeysFromBucket() ([]string, error) {
	objects, err := a.GetAllObjects()
	if err != nil {
		return nil, err
	}

	var imageKeys []string
	for _, obj := range objects {
		key := *obj.Key
		if key[len(*obj.Key)-1] != '/' {
			//check for 'folder' name vs actual object - objects end without trailing /
			imageKeys = append(imageKeys, key)
		}
	}

	return imageKeys, nil
}

//highest level, acts on an album to return processed renderable imageurls, here we must also
//filter out any non-renderables and process any other metadata we expect to find.
func (a *Album) GetOrderedPhotos() ([]Renderable, error) {
	var imageUrls []Renderable

	albumOrdering, err := a.GetAlbumOrdering()

	if err != nil {
		fmt.Printf("Unable to pick up album ordering from S3 ordering.yaml because: %s", err.Error())
	}

	fmt.Print(albumOrdering)

	imageKeys, err := a.GetAllObjectKeys()
	if err != nil {
		fmt.Printf("Unable to get object keys from S3. Error: %s\n", err.Error())
		return imageUrls, err
	}

	for _, v := range imageKeys {
		if strings.HasSuffix(v, "ordering.yaml") {
			//for now, just do nothing, we simply want to avoid appending,
			//when we agree on a list of valid formats, we can ditch this check.
		} else {
			imageUrl := a.site.GetPhotoForKey(v)
			imageUrls = append(imageUrls, imageUrl)
		}
	}

	return imageUrls, nil
}

//wrapper around GetAllObjectKeysFromBucket to add in a caching layer, nothing below
//this layer filters or reorders the list of **objects** returned from S3.
//note that this DOES filter out the album prefix.
func (a *Album) GetAllObjectKeys() ([]string, error) {
	c := make(chan *GetFromKeyCacheResult)
	go func() {
		var keys []string
		var err error

		if a.KeyCache.Load() != nil {
			c <- &GetFromKeyCacheResult{a.KeyCache.Load().([]string), nil}

			a.KeyCacheUpdateMutex.Lock()
			if a.NeedsKeyCacheUpdate() {
				keys, err = a.GetAllObjectKeysFromBucket()
				if err == nil {
					a.KeyCache.Store(keys)
					a.LastKeyCacheUpdate = time.Now()
				}
			}

			a.KeyCacheUpdateMutex.Unlock()
		} else {
			a.KeyCacheUpdateMutex.Lock()

			keys, err = a.GetAllObjectKeysFromBucket()
			if err == nil {
				a.KeyCache.Store(keys)
				a.LastKeyCacheUpdate = time.Now()
			}
			c <- &GetFromKeyCacheResult{keys, err}

			a.KeyCacheUpdateMutex.Unlock()
		}
	}()

	result := <-c
	if result.err != nil {
		return nil, result.err
	} else {
		return result.keys, result.err
	}
}

//retrieves the actual album ordering from s3, it expects a file as hard-coded in
// the constant ORDERING_YAML_NAME
func (a *Album) GetAlbumOrderingFromS3() (AlbumOrdering, error) {
	var albumOrdering AlbumOrdering
	svc, err := a.site.GetS3Service()
	if err != nil {
		return albumOrdering, err
	}

	orderingYAMLKey := strings.Join([]string{a.BucketPrefix, ORDERING_YAML_NAME}, "/")
	yaml_object, err := svc.GetObject(&s3.GetObjectInput{
		Bucket: aws.String(a.site.BucketName),
		Key:    aws.String(orderingYAMLKey),
	})

	if err != nil {
		if aerr, ok := err.(awserr.RequestFailure); ok {
			if aerr.StatusCode() == 404 {
				albumOrdering.negativeCacheThis = true
			}
		}
		//basically, we only want to negatively cache 404's, so we can mark this as such.
		//should be retried later, but exception handling is up to the caller.
		return albumOrdering, err
	}

	//extract the contents from what we read so we can then parse the yaml
	data_bytes, err := ioutil.ReadAll(yaml_object.Body)
	//data_string := string(data_bytes)
	err = yaml.Unmarshal(data_bytes, &albumOrdering)

	if err != nil {
		//we were unable to read what the yaml was, it's likely malformed, and that may not change
		//anytime soon, so we negatively cache it, the caller should be aware
		//that it's going to be a bad result though, so raise the error
		albumOrdering.negativeCacheThis = true
		return albumOrdering, err
	}

	return albumOrdering, nil
}

//note that this also caches negative values, i.e: adding a ordering file may take an hour
//to be rechecked.
func (a *Album) GetAlbumOrdering() (AlbumOrdering, error) {
	c := make(chan *GetFromOrderingCacheResult)
	go func() {
		if a.OrderingCache.Load() != nil {
			c <- &GetFromOrderingCacheResult{a.OrderingCache.Load().(AlbumOrdering), nil}

			a.AlbumOrderingUpdateMutex.Lock()
			if a.NeedsOrderingCacheUpdate() {

				albumOrdering, err := a.GetAlbumOrderingFromS3()
				if err == nil || albumOrdering.negativeCacheThis {
					// whether the item is valid or we should be negatively
					// caching this result (probs err!=nil, but the value
					// should be there and a valid boolean.
					a.OrderingCache.Store(albumOrdering)
					a.LastOrderingCacheUpdate = time.Now()
				}

			}
			a.AlbumOrderingUpdateMutex.Unlock()
		} else {
			a.AlbumOrderingUpdateMutex.Lock()
			albumOrdering, err := a.GetAlbumOrderingFromS3()
			if err == nil || albumOrdering.negativeCacheThis {
				// whether the item is valid or we should be negatively
				// caching this result (probs err!=nil, but the value
				// should be there and a valid boolean.
				a.OrderingCache.Store(albumOrdering)
				a.LastOrderingCacheUpdate = time.Now()
			}

			c <- &GetFromOrderingCacheResult{albumOrdering, err}

			a.AlbumOrderingUpdateMutex.Unlock()
		}
	}()

	var albumOrdering AlbumOrdering
	result := <-c
	if result.err != nil {
		//consumer should be checking err
		return albumOrdering, result.err
	} else {
		return result.ordering, result.err
	}
}

func (a *Album) ImageExists(slug string) bool {
	svc, err := a.site.GetS3Service()
	if err != nil {
		return false
	}

	key := strings.Join([]string{a.BucketPrefix, slug}, "/")
	_, err = svc.HeadObject(&s3.HeadObjectInput{
		Bucket: aws.String(a.site.BucketName),
		Key:    aws.String(key),
	})

	if err != nil {
		return false
	}

	return true
}

func (a *Album) NeedsKeyCacheUpdate() bool {
	return time.Now().Sub(a.LastKeyCacheUpdate) > CACHE_INTERVAL
}

func (a *Album) NeedsOrderingCacheUpdate() bool {
	return time.Now().Sub(a.LastOrderingCacheUpdate) > CACHE_INTERVAL
}
<|endoftext|>


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

package mint

import "strconv"

type Alert uint8

const (
	// alert level
	AlertLevelWarning = 1
	AlertLevelError   = 2
)

const (
	AlertCloseNotify                 Alert = 0
	AlertUnexpectedMessage           Alert = 10
	AlertBadRecordMAC                Alert = 20
	AlertDecryptionFailed            Alert = 21
	AlertRecordOverflow              Alert = 22
	AlertDecompressionFailure        Alert = 30
	AlertHandshakeFailure            Alert = 40
	AlertBadCertificate              Alert = 42
	AlertUnsupportedCertificate      Alert = 43
	AlertCertificateRevoked          Alert = 44
	AlertCertificateExpired          Alert = 45
	AlertCertificateUnknown          Alert = 46
	AlertIllegalParameter            Alert = 47
	AlertUnknownCA                   Alert = 48
	AlertAccessDenied                Alert = 49
	AlertDecodeError                 Alert = 50
	AlertDecryptError                Alert = 51
	AlertProtocolVersion             Alert = 70
	AlertInsufficientSecurity        Alert = 71
	AlertInternalError               Alert = 80
	AlertInappropriateFallback       Alert = 86
	AlertUserCanceled                Alert = 90
	AlertNoRenegotiation             Alert = 100
	AlertMissingExtension            Alert = 109
	AlertUnsupportedExtension        Alert = 110
	AlertCertificateUnobtainable     Alert = 111
	AlertUnrecognizedName            Alert = 112
	AlertBadCertificateStatsResponse Alert = 113
	AlertBadCertificateHashValue     Alert = 114
	AlertUnknownPSKIdentity          Alert = 115
	AlertNoApplicationProtocol       Alert = 120
	AlertWouldBlock			 Alert = 254
	AlertNoAlert                     Alert = 255
)

var alertText = map[Alert]string{
	AlertCloseNotify:                 "close notify",
	AlertUnexpectedMessage:           "unexpected message",
	AlertBadRecordMAC:                "bad record MAC",
	AlertDecryptionFailed:            "decryption failed",
	AlertRecordOverflow:              "record overflow",
	AlertDecompressionFailure:        "decompression failure",
	AlertHandshakeFailure:            "handshake failure",
	AlertBadCertificate:              "bad certificate",
	AlertUnsupportedCertificate:      "unsupported certificate",
	AlertCertificateRevoked:          "revoked certificate",
	AlertCertificateExpired:          "expired certificate",
	AlertCertificateUnknown:          "unknown certificate",
	AlertIllegalParameter:            "illegal parameter",
	AlertUnknownCA:                   "unknown certificate authority",
	AlertAccessDenied:                "access denied",
	AlertDecodeError:                 "error decoding message",
	AlertDecryptError:                "error decrypting message",
	AlertProtocolVersion:             "protocol version not supported",
	AlertInsufficientSecurity:        "insufficient security level",
	AlertInternalError:               "internal error",
	AlertInappropriateFallback:       "inappropriate fallback",
	AlertUserCanceled:                "user canceled",
	AlertMissingExtension:            "missing extension",
	AlertUnsupportedExtension:        "unsupported extension",
	AlertCertificateUnobtainable:     "certificate unobtainable",
	AlertUnrecognizedName:            "unrecognized name",
	AlertBadCertificateStatsResponse: "bad certificate status response",
	AlertBadCertificateHashValue:     "bad certificate hash value",
	AlertUnknownPSKIdentity:          "unknown PSK identity",
	AlertNoApplicationProtocol:       "no application protocol",
	AlertNoRenegotiation:             "no renegotiation",
	AlertNoAlert:                     "no alert",
}

func (e Alert) String() string {
	s, ok := alertText[e]
	if ok {
		return s
	}
	return "alert(" + strconv.Itoa(int(e)) + ")"
}

func (e Alert) Error() string {
	return e.String()
}
<commit_msg>Clarify WouldBlock<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package mint

import "strconv"

type Alert uint8

const (
	// alert level
	AlertLevelWarning = 1
	AlertLevelError   = 2
)

const (
	AlertCloseNotify                 Alert = 0
	AlertUnexpectedMessage           Alert = 10
	AlertBadRecordMAC                Alert = 20
	AlertDecryptionFailed            Alert = 21
	AlertRecordOverflow              Alert = 22
	AlertDecompressionFailure        Alert = 30
	AlertHandshakeFailure            Alert = 40
	AlertBadCertificate              Alert = 42
	AlertUnsupportedCertificate      Alert = 43
	AlertCertificateRevoked          Alert = 44
	AlertCertificateExpired          Alert = 45
	AlertCertificateUnknown          Alert = 46
	AlertIllegalParameter            Alert = 47
	AlertUnknownCA                   Alert = 48
	AlertAccessDenied                Alert = 49
	AlertDecodeError                 Alert = 50
	AlertDecryptError                Alert = 51
	AlertProtocolVersion             Alert = 70
	AlertInsufficientSecurity        Alert = 71
	AlertInternalError               Alert = 80
	AlertInappropriateFallback       Alert = 86
	AlertUserCanceled                Alert = 90
	AlertNoRenegotiation             Alert = 100
	AlertMissingExtension            Alert = 109
	AlertUnsupportedExtension        Alert = 110
	AlertCertificateUnobtainable     Alert = 111
	AlertUnrecognizedName            Alert = 112
	AlertBadCertificateStatsResponse Alert = 113
	AlertBadCertificateHashValue     Alert = 114
	AlertUnknownPSKIdentity          Alert = 115
	AlertNoApplicationProtocol       Alert = 120
	AlertWouldBlock			 Alert = 254
	AlertNoAlert                     Alert = 255
)

var alertText = map[Alert]string{
	AlertCloseNotify:                 "close notify",
	AlertUnexpectedMessage:           "unexpected message",
	AlertBadRecordMAC:                "bad record MAC",
	AlertDecryptionFailed:            "decryption failed",
	AlertRecordOverflow:              "record overflow",
	AlertDecompressionFailure:        "decompression failure",
	AlertHandshakeFailure:            "handshake failure",
	AlertBadCertificate:              "bad certificate",
	AlertUnsupportedCertificate:      "unsupported certificate",
	AlertCertificateRevoked:          "revoked certificate",
	AlertCertificateExpired:          "expired certificate",
	AlertCertificateUnknown:          "unknown certificate",
	AlertIllegalParameter:            "illegal parameter",
	AlertUnknownCA:                   "unknown certificate authority",
	AlertAccessDenied:                "access denied",
	AlertDecodeError:                 "error decoding message",
	AlertDecryptError:                "error decrypting message",
	AlertProtocolVersion:             "protocol version not supported",
	AlertInsufficientSecurity:        "insufficient security level",
	AlertInternalError:               "internal error",
	AlertInappropriateFallback:       "inappropriate fallback",
	AlertUserCanceled:                "user canceled",
	AlertMissingExtension:            "missing extension",
	AlertUnsupportedExtension:        "unsupported extension",
	AlertCertificateUnobtainable:     "certificate unobtainable",
	AlertUnrecognizedName:            "unrecognized name",
	AlertBadCertificateStatsResponse: "bad certificate status response",
	AlertBadCertificateHashValue:     "bad certificate hash value",
	AlertUnknownPSKIdentity:          "unknown PSK identity",
	AlertNoApplicationProtocol:       "no application protocol",
	AlertNoRenegotiation:             "no renegotiation",
	AlertWouldBlock:		  "would have blocked",
	AlertNoAlert:                     "no alert",
}

func (e Alert) String() string {
	s, ok := alertText[e]
	if ok {
		return s
	}
	return "alert(" + strconv.Itoa(int(e)) + ")"
}

func (e Alert) Error() string {
	return e.String()
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	"log"
	"math"
	"sync"
)

// A LoadBalancer is an interface for algorithms
// that implement various methods for returning a backend.
type LoadBalancer interface {
	// Return a single backend instance.
	// If none can be found nil will be returned.
	Backend() Backend

	// Close all backends and stop monitoring them
	Close()
}

// NewLoadBalancer returns a new load balancer described by the
// supplied configuration and inventory.
func NewLoadBalancer(conf LBConfig, i *Inventory) (LoadBalancer, error) {
	switch conf.Type {
	case "roundrobin":
		return newRoundRobin(i), nil
	case "leastconn":
		return newLeastConn(i), nil
	default:
		return nil, fmt.Errorf("Unknown load balancer type %s", conf.Type)
	}
	return nil, fmt.Errorf("NewLoadBalancer: No balancer")
}

// lbBase is common functionality for all load balancers
type lbBase struct {
	mu  sync.Mutex
	inv *Inventory
}

// roundRobin is a load balancer that
// switches between all the healthy backends.
type roundRobin struct {
	lbBase
	next int
}

// Close all backends in the inventory
func (r *lbBase) Close() {
	r.mu.Lock()
	r.inv.Close()
	r.mu.Unlock()
}

// NewRoundRobin Returns a new round-robin loadbalancer
func newRoundRobin(b *Inventory) LoadBalancer {
	return &roundRobin{lbBase: lbBase{inv: b}}
}

// Backend will return next server in a round-robin.
// Will return nil if no healthy backend can be found.
func (r *roundRobin) Backend() Backend {
	r.mu.Lock()
	defer r.mu.Unlock()
	first := r.next
	for {
		ni := r.next % len(r.inv.backends)
		be := r.inv.backends[ni]
		r.next = ni + 1
		if be.Healthy() {
			return be
		}
		if r.next == first {
			log.Println("Unable to find a healthy backend")
			return nil
		}
	}
}

// leastConn is a load balancer that
// returns the backend with the fewest connections.
type leastConn struct {
	lbBase
}

// NewRoundRobin Returns a new least-connections loadbalancer
func newLeastConn(b *Inventory) LoadBalancer {
	return &leastConn{lbBase: lbBase{inv: b}}
}

// Backend will return the backend with the least connections
// Will return nil if no healthy backend can be found
func (r *leastConn) Backend() Backend {
	r.mu.Lock()
	defer r.mu.Unlock()
	var best Backend
	lowest := math.MaxInt32
	for _, be := range r.inv.backends {
		if !be.Healthy() {
			continue
		}
		conn := be.Connections()
		if conn < lowest {
			best = be
			lowest = conn
		}
	}
	if lowest == math.MaxInt32 {
		log.Println("Unable to find a healthy backend")
		return nil
	}
	return best
}

// TODO: Implement
type lowestLatency struct {
}
<commit_msg>Remove unreachable code.<commit_after>package server

import (
	"fmt"
	"log"
	"math"
	"sync"
)

// A LoadBalancer is an interface for algorithms
// that implement various methods for returning a backend.
type LoadBalancer interface {
	// Return a single backend instance.
	// If none can be found nil will be returned.
	Backend() Backend

	// Close all backends and stop monitoring them
	Close()
}

// NewLoadBalancer returns a new load balancer described by the
// supplied configuration and inventory.
func NewLoadBalancer(conf LBConfig, i *Inventory) (LoadBalancer, error) {
	switch conf.Type {
	case "roundrobin":
		return newRoundRobin(i), nil
	case "leastconn":
		return newLeastConn(i), nil
	default:
		return nil, fmt.Errorf("Unknown load balancer type %s", conf.Type)
	}
}

// lbBase is common functionality for all load balancers
type lbBase struct {
	mu  sync.Mutex
	inv *Inventory
}

// roundRobin is a load balancer that
// switches between all the healthy backends.
type roundRobin struct {
	lbBase
	next int
}

// Close all backends in the inventory
func (r *lbBase) Close() {
	r.mu.Lock()
	r.inv.Close()
	r.mu.Unlock()
}

// NewRoundRobin Returns a new round-robin loadbalancer
func newRoundRobin(b *Inventory) LoadBalancer {
	return &roundRobin{lbBase: lbBase{inv: b}}
}

// Backend will return next server in a round-robin.
// Will return nil if no healthy backend can be found.
func (r *roundRobin) Backend() Backend {
	r.mu.Lock()
	defer r.mu.Unlock()
	first := r.next
	for {
		ni := r.next % len(r.inv.backends)
		be := r.inv.backends[ni]
		r.next = ni + 1
		if be.Healthy() {
			return be
		}
		if r.next == first {
			log.Println("Unable to find a healthy backend")
			return nil
		}
	}
}

// leastConn is a load balancer that
// returns the backend with the fewest connections.
type leastConn struct {
	lbBase
}

// NewRoundRobin Returns a new least-connections loadbalancer
func newLeastConn(b *Inventory) LoadBalancer {
	return &leastConn{lbBase: lbBase{inv: b}}
}

// Backend will return the backend with the least connections
// Will return nil if no healthy backend can be found
func (r *leastConn) Backend() Backend {
	r.mu.Lock()
	defer r.mu.Unlock()
	var best Backend
	lowest := math.MaxInt32
	for _, be := range r.inv.backends {
		if !be.Healthy() {
			continue
		}
		conn := be.Connections()
		if conn < lowest {
			best = be
			lowest = conn
		}
	}
	if lowest == math.MaxInt32 {
		log.Println("Unable to find a healthy backend")
		return nil
	}
	return best
}

// TODO: Implement
type lowestLatency struct {
}
<|endoftext|>


<commit_before>// 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
//
//     https://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.
//
// Original source: github.com/micro/go-micro/v3/store/memory/memory.go

// Package memory is a in-memory store store
package memory

import (
	"path/filepath"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/micro/micro/v3/service/store"
	"github.com/patrickmn/go-cache"
	"github.com/pkg/errors"
)

// NewStore returns a memory store
func NewStore(opts ...store.Option) store.Store {
	s := &memoryStore{
		options: store.Options{
			Database: "micro",
			Table:    "micro",
		},
		stores: map[string]*cache.Cache{}, // cache.New(cache.NoExpiration, 5*time.Minute),
	}
	for _, o := range opts {
		o(&s.options)
	}
	return s
}

type memoryStore struct {
	sync.RWMutex
	options store.Options

	stores map[string]*cache.Cache
}

type storeRecord struct {
	key       string
	value     []byte
	metadata  map[string]interface{}
	expiresAt time.Time
}

func (m *memoryStore) prefix(database, table string) string {
	if len(database) == 0 {
		database = m.options.Database
	}
	if len(table) == 0 {
		table = m.options.Table
	}
	return filepath.Join(database, table)
}

func (m *memoryStore) getStore(prefix string) *cache.Cache {
	m.RLock()
	store := m.stores[prefix]
	m.RUnlock()
	if store == nil {
		m.Lock()
		if m.stores[prefix] == nil {
			m.stores[prefix] = cache.New(cache.NoExpiration, 5*time.Minute)
		}
		store = m.stores[prefix]
		m.Unlock()
	}
	return store
}

func (m *memoryStore) get(prefix, key string) (*store.Record, error) {
	var storedRecord *storeRecord
	r, found := m.getStore(prefix).Get(key)
	if !found {
		return nil, store.ErrNotFound
	}

	storedRecord, ok := r.(*storeRecord)
	if !ok {
		return nil, errors.New("Retrieved a non *storeRecord from the cache")
	}

	// Copy the record on the way out
	newRecord := &store.Record{}
	newRecord.Key = strings.TrimPrefix(storedRecord.key, prefix+"/")
	newRecord.Value = make([]byte, len(storedRecord.value))
	newRecord.Metadata = make(map[string]interface{})

	// copy the value into the new record
	copy(newRecord.Value, storedRecord.value)

	// check if we need to set the expiry
	if !storedRecord.expiresAt.IsZero() {
		newRecord.Expiry = time.Until(storedRecord.expiresAt)
	}

	// copy in the metadata
	for k, v := range storedRecord.metadata {
		newRecord.Metadata[k] = v
	}

	return newRecord, nil
}

func (m *memoryStore) set(prefix string, r *store.Record) {
	// copy the incoming record and then
	// convert the expiry in to a hard timestamp
	i := &storeRecord{}
	i.key = r.Key
	i.value = make([]byte, len(r.Value))
	i.metadata = make(map[string]interface{})

	// copy the the value
	copy(i.value, r.Value)

	// set the expiry
	if r.Expiry != 0 {
		i.expiresAt = time.Now().Add(r.Expiry)
	}

	// set the metadata
	for k, v := range r.Metadata {
		i.metadata[k] = v
	}

	m.getStore(prefix).Set(r.Key, i, r.Expiry)
}

func (m *memoryStore) delete(prefix, key string) {
	m.getStore(prefix).Delete(key)
}

func (m *memoryStore) list(prefix string, order store.Order, limit, offset uint, prefixFilter, suffixFilter string) []string {
	// TODO: sort they keys
	var allItems []string

	for k := range m.getStore(prefix).Items() {
		allItems = append(allItems, k)
	}

	allKeys := make([]string, len(allItems))

	// construct list of keys for this prefix
	i := 0

	// order descending if specified
	if order == store.OrderDesc {
		j := 0
		for i := len(allItems); i > 0; i-- {
			allKeys[j] = allItems[i-1]
			j++
		}
	} else {
		// default order ascending
		for _, k := range allItems {
			allKeys[i] = k
			i++
		}
	}

	keys := make([]string, 0, len(allKeys))
	sort.Slice(allKeys, func(i, j int) bool { return allKeys[i] < allKeys[j] })

	for _, k := range allKeys {
		if prefixFilter != "" && !strings.HasPrefix(k, prefixFilter) {
			continue
		}
		if suffixFilter != "" && !strings.HasSuffix(k, suffixFilter) {
			continue
		}
		if offset > 0 {
			offset--
			continue
		}
		keys = append(keys, k)
		// this check still works if no limit was passed to begin with, you'll just end up with large -ve value
		if limit == 1 {
			break
		}
		limit--
	}
	return keys
}

func (m *memoryStore) Close() error {
	m.Lock()
	defer m.Unlock()
	for _, s := range m.stores {
		s.Flush()
	}
	return nil
}

func (m *memoryStore) Init(opts ...store.Option) error {
	for _, o := range opts {
		o(&m.options)
	}
	return nil
}

func (m *memoryStore) String() string {
	return "memory"
}

func (m *memoryStore) Read(key string, opts ...store.ReadOption) ([]*store.Record, error) {
	readOpts := store.ReadOptions{
		Order: store.OrderAsc,
	}
	for _, o := range opts {
		o(&readOpts)
	}

	prefix := m.prefix(readOpts.Database, readOpts.Table)

	var keys []string
	// Handle Prefix / suffix
	if readOpts.Prefix || readOpts.Suffix {
		prefixFilter := ""
		if readOpts.Prefix {
			prefixFilter = key
		}
		suffixFilter := ""
		if readOpts.Suffix {
			suffixFilter = key
		}
		keys = m.list(prefix, readOpts.Order, readOpts.Limit, readOpts.Offset, prefixFilter, suffixFilter)
	} else {
		keys = []string{key}
	}

	var results []*store.Record

	for _, k := range keys {
		r, err := m.get(prefix, k)
		if err != nil {
			return results, err
		}
		results = append(results, r)
	}

	return results, nil
}

func (m *memoryStore) Write(r *store.Record, opts ...store.WriteOption) error {
	writeOpts := store.WriteOptions{}
	for _, o := range opts {
		o(&writeOpts)
	}

	prefix := m.prefix(writeOpts.Database, writeOpts.Table)

	if len(opts) > 0 {
		// Copy the record before applying options, or the incoming record will be mutated
		newRecord := store.Record{}
		newRecord.Key = r.Key
		newRecord.Value = make([]byte, len(r.Value))
		newRecord.Metadata = make(map[string]interface{})
		copy(newRecord.Value, r.Value)
		newRecord.Expiry = r.Expiry

		for k, v := range r.Metadata {
			newRecord.Metadata[k] = v
		}

		m.set(prefix, &newRecord)
		return nil
	}

	// set
	m.set(prefix, r)

	return nil
}

func (m *memoryStore) Delete(key string, opts ...store.DeleteOption) error {
	deleteOptions := store.DeleteOptions{}
	for _, o := range opts {
		o(&deleteOptions)
	}

	prefix := m.prefix(deleteOptions.Database, deleteOptions.Table)
	m.delete(prefix, key)
	return nil
}

func (m *memoryStore) Options() store.Options {
	return m.options
}

func (m *memoryStore) List(opts ...store.ListOption) ([]string, error) {
	listOptions := store.ListOptions{
		Order: store.OrderAsc,
	}

	for _, o := range opts {
		o(&listOptions)
	}

	prefix := m.prefix(listOptions.Database, listOptions.Table)
	keys := m.list(prefix, listOptions.Order, listOptions.Limit, listOptions.Offset, listOptions.Prefix, listOptions.Suffix)
	return keys, nil
}
<commit_msg>what was I smoking<commit_after>// 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
//
//     https://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.
//
// Original source: github.com/micro/go-micro/v3/store/memory/memory.go

// Package memory is a in-memory store store
package memory

import (
	"path/filepath"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/micro/micro/v3/service/store"
	"github.com/patrickmn/go-cache"
	"github.com/pkg/errors"
)

// NewStore returns a memory store
func NewStore(opts ...store.Option) store.Store {
	s := &memoryStore{
		options: store.Options{
			Database: "micro",
			Table:    "micro",
		},
		stores: map[string]*cache.Cache{}, // cache.New(cache.NoExpiration, 5*time.Minute),
	}
	for _, o := range opts {
		o(&s.options)
	}
	return s
}

type memoryStore struct {
	sync.RWMutex
	options store.Options

	stores map[string]*cache.Cache
}

type storeRecord struct {
	key       string
	value     []byte
	metadata  map[string]interface{}
	expiresAt time.Time
}

func (m *memoryStore) prefix(database, table string) string {
	if len(database) == 0 {
		database = m.options.Database
	}
	if len(table) == 0 {
		table = m.options.Table
	}
	return filepath.Join(database, table)
}

func (m *memoryStore) getStore(prefix string) *cache.Cache {
	m.RLock()
	store := m.stores[prefix]
	m.RUnlock()
	if store == nil {
		m.Lock()
		if m.stores[prefix] == nil {
			m.stores[prefix] = cache.New(cache.NoExpiration, 5*time.Minute)
		}
		store = m.stores[prefix]
		m.Unlock()
	}
	return store
}

func (m *memoryStore) get(prefix, key string) (*store.Record, error) {
	var storedRecord *storeRecord
	r, found := m.getStore(prefix).Get(key)
	if !found {
		return nil, store.ErrNotFound
	}

	storedRecord, ok := r.(*storeRecord)
	if !ok {
		return nil, errors.New("Retrieved a non *storeRecord from the cache")
	}

	// Copy the record on the way out
	newRecord := &store.Record{}
	newRecord.Key = strings.TrimPrefix(storedRecord.key, prefix+"/")
	newRecord.Value = make([]byte, len(storedRecord.value))
	newRecord.Metadata = make(map[string]interface{})

	// copy the value into the new record
	copy(newRecord.Value, storedRecord.value)

	// check if we need to set the expiry
	if !storedRecord.expiresAt.IsZero() {
		newRecord.Expiry = time.Until(storedRecord.expiresAt)
	}

	// copy in the metadata
	for k, v := range storedRecord.metadata {
		newRecord.Metadata[k] = v
	}

	return newRecord, nil
}

func (m *memoryStore) set(prefix string, r *store.Record) {
	// copy the incoming record and then
	// convert the expiry in to a hard timestamp
	i := &storeRecord{}
	i.key = r.Key
	i.value = make([]byte, len(r.Value))
	i.metadata = make(map[string]interface{})

	// copy the the value
	copy(i.value, r.Value)

	// set the expiry
	if r.Expiry != 0 {
		i.expiresAt = time.Now().Add(r.Expiry)
	}

	// set the metadata
	for k, v := range r.Metadata {
		i.metadata[k] = v
	}

	m.getStore(prefix).Set(r.Key, i, r.Expiry)
}

func (m *memoryStore) delete(prefix, key string) {
	m.getStore(prefix).Delete(key)
}

func (m *memoryStore) list(prefix string, order store.Order, limit, offset uint, prefixFilter, suffixFilter string) []string {
	// TODO: sort they keys
	var allItems []string

	for k := range m.getStore(prefix).Items() {
		allItems = append(allItems, k)
	}

	// sort in ascending order
	if order == store.OrderDesc {
		sort.Slice(allItems, func(i, j int) bool { return allItems[i] > allItems[j] })
	} else {
		sort.Slice(allItems, func(i, j int) bool { return allItems[i] < allItems[j] })
	}

	var keys []string

	for _, k := range allItems {
		if prefixFilter != "" && !strings.HasPrefix(k, prefixFilter) {
			continue
		}
		if suffixFilter != "" && !strings.HasSuffix(k, suffixFilter) {
			continue
		}
		if offset > 0 {
			offset--
			continue
		}
		keys = append(keys, k)
		// this check still works if no limit was passed to begin with, you'll just end up with large -ve value
		if limit == 1 {
			break
		}
		limit--
	}

	return keys
}

func (m *memoryStore) Close() error {
	m.Lock()
	defer m.Unlock()
	for _, s := range m.stores {
		s.Flush()
	}
	return nil
}

func (m *memoryStore) Init(opts ...store.Option) error {
	for _, o := range opts {
		o(&m.options)
	}
	return nil
}

func (m *memoryStore) String() string {
	return "memory"
}

func (m *memoryStore) Read(key string, opts ...store.ReadOption) ([]*store.Record, error) {
	readOpts := store.ReadOptions{
		Order: store.OrderAsc,
	}
	for _, o := range opts {
		o(&readOpts)
	}

	prefix := m.prefix(readOpts.Database, readOpts.Table)

	var keys []string
	// Handle Prefix / suffix
	if readOpts.Prefix || readOpts.Suffix {
		prefixFilter := ""
		if readOpts.Prefix {
			prefixFilter = key
		}
		suffixFilter := ""
		if readOpts.Suffix {
			suffixFilter = key
		}
		keys = m.list(prefix, readOpts.Order, readOpts.Limit, readOpts.Offset, prefixFilter, suffixFilter)
	} else {
		keys = []string{key}
	}

	var results []*store.Record

	for _, k := range keys {
		r, err := m.get(prefix, k)
		if err != nil {
			return results, err
		}
		results = append(results, r)
	}

	return results, nil
}

func (m *memoryStore) Write(r *store.Record, opts ...store.WriteOption) error {
	writeOpts := store.WriteOptions{}
	for _, o := range opts {
		o(&writeOpts)
	}

	prefix := m.prefix(writeOpts.Database, writeOpts.Table)

	if len(opts) > 0 {
		// Copy the record before applying options, or the incoming record will be mutated
		newRecord := store.Record{}
		newRecord.Key = r.Key
		newRecord.Value = make([]byte, len(r.Value))
		newRecord.Metadata = make(map[string]interface{})
		copy(newRecord.Value, r.Value)
		newRecord.Expiry = r.Expiry

		for k, v := range r.Metadata {
			newRecord.Metadata[k] = v
		}

		m.set(prefix, &newRecord)
		return nil
	}

	// set
	m.set(prefix, r)

	return nil
}

func (m *memoryStore) Delete(key string, opts ...store.DeleteOption) error {
	deleteOptions := store.DeleteOptions{}
	for _, o := range opts {
		o(&deleteOptions)
	}

	prefix := m.prefix(deleteOptions.Database, deleteOptions.Table)
	m.delete(prefix, key)
	return nil
}

func (m *memoryStore) Options() store.Options {
	return m.options
}

func (m *memoryStore) List(opts ...store.ListOption) ([]string, error) {
	listOptions := store.ListOptions{
		Order: store.OrderAsc,
	}

	for _, o := range opts {
		o(&listOptions)
	}

	prefix := m.prefix(listOptions.Database, listOptions.Table)
	keys := m.list(prefix, listOptions.Order, listOptions.Limit, listOptions.Offset, listOptions.Prefix, listOptions.Suffix)
	return keys, nil
}
<|endoftext|>