<commit_before>package pubsub_test

import (
	"context"
	"fmt"
	"runtime/debug"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/tendermint/tmlibs/log"
	"github.com/tendermint/tmlibs/pubsub"
	"github.com/tendermint/tmlibs/pubsub/query"
)

const (
	clientID = "test-client"
)

func TestSubscribe(t *testing.T) {
	s := pubsub.NewServer()
	s.SetLogger(log.TestingLogger())
	s.Start()
	defer s.Stop()

	ctx := context.Background()
	ch := make(chan interface{}, 1)
	err := s.Subscribe(ctx, clientID, query.Empty{}, ch)
	require.NoError(t, err)
	err = s.Publish(ctx, "Ka-Zar")
	require.NoError(t, err)
	assertReceive(t, "Ka-Zar", ch)

	err = s.Publish(ctx, "Quicksilver")
	require.NoError(t, err)
	assertReceive(t, "Quicksilver", ch)
}

func TestDifferentClients(t *testing.T) {
	s := pubsub.NewServer()
	s.SetLogger(log.TestingLogger())
	s.Start()
	defer s.Stop()

	ctx := context.Background()
	ch1 := make(chan interface{}, 1)
	err := s.Subscribe(ctx, "client-1", query.MustParse("tm.events.type=NewBlock"), ch1)
	require.NoError(t, err)
	err = s.PublishWithTags(ctx, "Iceman", map[string]interface{}{"tm.events.type": "NewBlock"})
	require.NoError(t, err)
	assertReceive(t, "Iceman", ch1)

	ch2 := make(chan interface{}, 1)
	err = s.Subscribe(ctx, "client-2", query.MustParse("tm.events.type=NewBlock AND abci.account.name=Igor"), ch2)
	require.NoError(t, err)
	err = s.PublishWithTags(ctx, "Ultimo", map[string]interface{}{"tm.events.type": "NewBlock", "abci.account.name": "Igor"})
	require.NoError(t, err)
	assertReceive(t, "Ultimo", ch1)
	assertReceive(t, "Ultimo", ch2)

	ch3 := make(chan interface{}, 1)
	err = s.Subscribe(ctx, "client-3", query.MustParse("tm.events.type=NewRoundStep AND abci.account.name=Igor AND abci.invoice.number = 10"), ch3)
	require.NoError(t, err)
	err = s.PublishWithTags(ctx, "Valeria Richards", map[string]interface{}{"tm.events.type": "NewRoundStep"})
	require.NoError(t, err)
	assert.Zero(t, len(ch3))
}

func TestClientResubscribes(t *testing.T) {
	s := pubsub.NewServer()
	s.SetLogger(log.TestingLogger())
	s.Start()
	defer s.Stop()

	ctx := context.Background()
	q := query.MustParse("tm.events.type=NewBlock")

	ch1 := make(chan interface{}, 1)
	err := s.Subscribe(ctx, clientID, q, ch1)
	require.NoError(t, err)
	err = s.PublishWithTags(ctx, "Goblin Queen", map[string]interface{}{"tm.events.type": "NewBlock"})
	require.NoError(t, err)
	assertReceive(t, "Goblin Queen", ch1)

	ch2 := make(chan interface{}, 1)
	err = s.Subscribe(ctx, clientID, q, ch2)
	require.NoError(t, err)

	_, ok := <-ch1
	assert.False(t, ok)

	err = s.PublishWithTags(ctx, "Spider-Man", map[string]interface{}{"tm.events.type": "NewBlock"})
	require.NoError(t, err)
	assertReceive(t, "Spider-Man", ch2)
}

func TestUnsubscribe(t *testing.T) {
	s := pubsub.NewServer()
	s.SetLogger(log.TestingLogger())
	s.Start()
	defer s.Stop()

	ctx := context.Background()
	ch := make(chan interface{})
	err := s.Subscribe(ctx, clientID, query.Empty{}, ch)
	require.NoError(t, err)
	err = s.Unsubscribe(ctx, clientID, query.Empty{})
	require.NoError(t, err)

	err = s.Publish(ctx, "Nick Fury")
	require.NoError(t, err)
	assert.Zero(t, len(ch), "Should not receive anything after Unsubscribe")

	_, ok := <-ch
	assert.False(t, ok)
}

func TestUnsubscribeAll(t *testing.T) {
	s := pubsub.NewServer()
	s.SetLogger(log.TestingLogger())
	s.Start()
	defer s.Stop()

	ctx := context.Background()
	ch1, ch2 := make(chan interface{}, 1), make(chan interface{}, 1)
	err := s.Subscribe(ctx, clientID, query.MustParse("tm.events.type=NewBlock"), ch1)
	require.NoError(t, err)
	err = s.Subscribe(ctx, clientID, query.MustParse("tm.events.type=NewBlockHeader"), ch2)
	require.NoError(t, err)

	err = s.UnsubscribeAll(ctx, clientID)
	require.NoError(t, err)

	err = s.Publish(ctx, "Nick Fury")
	require.NoError(t, err)
	assert.Zero(t, len(ch1), "Should not receive anything after UnsubscribeAll")
	assert.Zero(t, len(ch2), "Should not receive anything after UnsubscribeAll")

	_, ok := <-ch1
	assert.False(t, ok)
	_, ok = <-ch2
	assert.False(t, ok)
}

func TestBufferCapacity(t *testing.T) {
	s := pubsub.NewServer(pubsub.BufferCapacity(2))
	s.SetLogger(log.TestingLogger())

	ctx := context.Background()
	err := s.Publish(ctx, "Nighthawk")
	require.NoError(t, err)
	err = s.Publish(ctx, "Sage")
	require.NoError(t, err)

	ctx, cancel := context.WithTimeout(ctx, 10*time.Millisecond)
	defer cancel()
	err = s.Publish(ctx, "Ironclad")
	if assert.Error(t, err) {
		assert.Equal(t, context.DeadlineExceeded, err)
	}
}

func Benchmark10Clients(b *testing.B)   { benchmarkNClients(10, b) }
func Benchmark100Clients(b *testing.B)  { benchmarkNClients(100, b) }
func Benchmark1000Clients(b *testing.B) { benchmarkNClients(1000, b) }

func Benchmark10ClientsOneQuery(b *testing.B)   { benchmarkNClientsOneQuery(10, b) }
func Benchmark100ClientsOneQuery(b *testing.B)  { benchmarkNClientsOneQuery(100, b) }
func Benchmark1000ClientsOneQuery(b *testing.B) { benchmarkNClientsOneQuery(1000, b) }

func benchmarkNClients(n int, b *testing.B) {
	s := pubsub.NewServer()
	s.Start()
	defer s.Stop()

	ctx := context.Background()
	for i := 0; i < n; i++ {
		ch := make(chan interface{})
		go func() {
			for range ch {
			}
		}()
		s.Subscribe(ctx, clientID, query.MustParse(fmt.Sprintf("abci.Account.Owner = Ivan AND abci.Invoices.Number = %d", i)), ch)
	}

	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		s.PublishWithTags(ctx, "Gamora", map[string]interface{}{"abci.Account.Owner": "Ivan", "abci.Invoices.Number": i})
	}
}

func benchmarkNClientsOneQuery(n int, b *testing.B) {
	s := pubsub.NewServer()
	s.Start()
	defer s.Stop()

	ctx := context.Background()
	q := query.MustParse("abci.Account.Owner = Ivan AND abci.Invoices.Number = 1")
	for i := 0; i < n; i++ {
		ch := make(chan interface{})
		go func() {
			for range ch {
			}
		}()
		s.Subscribe(ctx, clientID, q, ch)
	}

	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		s.PublishWithTags(ctx, "Gamora", map[string]interface{}{"abci.Account.Owner": "Ivan", "abci.Invoices.Number": 1})
	}
}

///////////////////////////////////////////////////////////////////////////////
/// HELPERS
///////////////////////////////////////////////////////////////////////////////

func assertReceive(t *testing.T, expected interface{}, ch <-chan interface{}, msgAndArgs ...interface{}) {
	select {
	case actual := <-ch:
		if actual != nil {
			assert.Equal(t, expected, actual, msgAndArgs...)
		}
	case <-time.After(1 * time.Second):
		t.Errorf("Expected to receive %v from the channel, got nothing after 1s", expected)
		debug.PrintStack()
	}
}
<commit_msg>rename TestClientResubscribes to TestClientSubscribesTwice<commit_after>package pubsub_test

import (
	"context"
	"fmt"
	"runtime/debug"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/tendermint/tmlibs/log"
	"github.com/tendermint/tmlibs/pubsub"
	"github.com/tendermint/tmlibs/pubsub/query"
)

const (
	clientID = "test-client"
)

func TestSubscribe(t *testing.T) {
	s := pubsub.NewServer()
	s.SetLogger(log.TestingLogger())
	s.Start()
	defer s.Stop()

	ctx := context.Background()
	ch := make(chan interface{}, 1)
	err := s.Subscribe(ctx, clientID, query.Empty{}, ch)
	require.NoError(t, err)
	err = s.Publish(ctx, "Ka-Zar")
	require.NoError(t, err)
	assertReceive(t, "Ka-Zar", ch)

	err = s.Publish(ctx, "Quicksilver")
	require.NoError(t, err)
	assertReceive(t, "Quicksilver", ch)
}

func TestDifferentClients(t *testing.T) {
	s := pubsub.NewServer()
	s.SetLogger(log.TestingLogger())
	s.Start()
	defer s.Stop()

	ctx := context.Background()
	ch1 := make(chan interface{}, 1)
	err := s.Subscribe(ctx, "client-1", query.MustParse("tm.events.type=NewBlock"), ch1)
	require.NoError(t, err)
	err = s.PublishWithTags(ctx, "Iceman", map[string]interface{}{"tm.events.type": "NewBlock"})
	require.NoError(t, err)
	assertReceive(t, "Iceman", ch1)

	ch2 := make(chan interface{}, 1)
	err = s.Subscribe(ctx, "client-2", query.MustParse("tm.events.type=NewBlock AND abci.account.name=Igor"), ch2)
	require.NoError(t, err)
	err = s.PublishWithTags(ctx, "Ultimo", map[string]interface{}{"tm.events.type": "NewBlock", "abci.account.name": "Igor"})
	require.NoError(t, err)
	assertReceive(t, "Ultimo", ch1)
	assertReceive(t, "Ultimo", ch2)

	ch3 := make(chan interface{}, 1)
	err = s.Subscribe(ctx, "client-3", query.MustParse("tm.events.type=NewRoundStep AND abci.account.name=Igor AND abci.invoice.number = 10"), ch3)
	require.NoError(t, err)
	err = s.PublishWithTags(ctx, "Valeria Richards", map[string]interface{}{"tm.events.type": "NewRoundStep"})
	require.NoError(t, err)
	assert.Zero(t, len(ch3))
}

func TestClientSubscribesTwice(t *testing.T) {
	s := pubsub.NewServer()
	s.SetLogger(log.TestingLogger())
	s.Start()
	defer s.Stop()

	ctx := context.Background()
	q := query.MustParse("tm.events.type=NewBlock")

	ch1 := make(chan interface{}, 1)
	err := s.Subscribe(ctx, clientID, q, ch1)
	require.NoError(t, err)
	err = s.PublishWithTags(ctx, "Goblin Queen", map[string]interface{}{"tm.events.type": "NewBlock"})
	require.NoError(t, err)
	assertReceive(t, "Goblin Queen", ch1)

	ch2 := make(chan interface{}, 1)
	err = s.Subscribe(ctx, clientID, q, ch2)
	require.NoError(t, err)

	_, ok := <-ch1
	assert.False(t, ok)

	err = s.PublishWithTags(ctx, "Spider-Man", map[string]interface{}{"tm.events.type": "NewBlock"})
	require.NoError(t, err)
	assertReceive(t, "Spider-Man", ch2)
}

func TestUnsubscribe(t *testing.T) {
	s := pubsub.NewServer()
	s.SetLogger(log.TestingLogger())
	s.Start()
	defer s.Stop()

	ctx := context.Background()
	ch := make(chan interface{})
	err := s.Subscribe(ctx, clientID, query.Empty{}, ch)
	require.NoError(t, err)
	err = s.Unsubscribe(ctx, clientID, query.Empty{})
	require.NoError(t, err)

	err = s.Publish(ctx, "Nick Fury")
	require.NoError(t, err)
	assert.Zero(t, len(ch), "Should not receive anything after Unsubscribe")

	_, ok := <-ch
	assert.False(t, ok)
}

func TestUnsubscribeAll(t *testing.T) {
	s := pubsub.NewServer()
	s.SetLogger(log.TestingLogger())
	s.Start()
	defer s.Stop()

	ctx := context.Background()
	ch1, ch2 := make(chan interface{}, 1), make(chan interface{}, 1)
	err := s.Subscribe(ctx, clientID, query.Empty{}, ch1)
	require.NoError(t, err)
	err = s.Subscribe(ctx, clientID, query.Empty{}, ch2)
	require.NoError(t, err)

	err = s.UnsubscribeAll(ctx, clientID)
	require.NoError(t, err)

	err = s.Publish(ctx, "Nick Fury")
	require.NoError(t, err)
	assert.Zero(t, len(ch1), "Should not receive anything after UnsubscribeAll")
	assert.Zero(t, len(ch2), "Should not receive anything after UnsubscribeAll")

	_, ok := <-ch1
	assert.False(t, ok)
	_, ok = <-ch2
	assert.False(t, ok)
}

func TestBufferCapacity(t *testing.T) {
	s := pubsub.NewServer(pubsub.BufferCapacity(2))
	s.SetLogger(log.TestingLogger())

	assert.Equal(t, 2, s.BufferCapacity())

	ctx := context.Background()
	err := s.Publish(ctx, "Nighthawk")
	require.NoError(t, err)
	err = s.Publish(ctx, "Sage")
	require.NoError(t, err)

	ctx, cancel := context.WithTimeout(ctx, 10*time.Millisecond)
	defer cancel()
	err = s.Publish(ctx, "Ironclad")
	if assert.Error(t, err) {
		assert.Equal(t, context.DeadlineExceeded, err)
	}
}

func Benchmark10Clients(b *testing.B)   { benchmarkNClients(10, b) }
func Benchmark100Clients(b *testing.B)  { benchmarkNClients(100, b) }
func Benchmark1000Clients(b *testing.B) { benchmarkNClients(1000, b) }

func Benchmark10ClientsOneQuery(b *testing.B)   { benchmarkNClientsOneQuery(10, b) }
func Benchmark100ClientsOneQuery(b *testing.B)  { benchmarkNClientsOneQuery(100, b) }
func Benchmark1000ClientsOneQuery(b *testing.B) { benchmarkNClientsOneQuery(1000, b) }

func benchmarkNClients(n int, b *testing.B) {
	s := pubsub.NewServer()
	s.Start()
	defer s.Stop()

	ctx := context.Background()
	for i := 0; i < n; i++ {
		ch := make(chan interface{})
		go func() {
			for range ch {
			}
		}()
		s.Subscribe(ctx, clientID, query.MustParse(fmt.Sprintf("abci.Account.Owner = Ivan AND abci.Invoices.Number = %d", i)), ch)
	}

	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		s.PublishWithTags(ctx, "Gamora", map[string]interface{}{"abci.Account.Owner": "Ivan", "abci.Invoices.Number": i})
	}
}

func benchmarkNClientsOneQuery(n int, b *testing.B) {
	s := pubsub.NewServer()
	s.Start()
	defer s.Stop()

	ctx := context.Background()
	q := query.MustParse("abci.Account.Owner = Ivan AND abci.Invoices.Number = 1")
	for i := 0; i < n; i++ {
		ch := make(chan interface{})
		go func() {
			for range ch {
			}
		}()
		s.Subscribe(ctx, clientID, q, ch)
	}

	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		s.PublishWithTags(ctx, "Gamora", map[string]interface{}{"abci.Account.Owner": "Ivan", "abci.Invoices.Number": 1})
	}
}

///////////////////////////////////////////////////////////////////////////////
/// HELPERS
///////////////////////////////////////////////////////////////////////////////

func assertReceive(t *testing.T, expected interface{}, ch <-chan interface{}, msgAndArgs ...interface{}) {
	select {
	case actual := <-ch:
		if actual != nil {
			assert.Equal(t, expected, actual, msgAndArgs...)
		}
	case <-time.After(1 * time.Second):
		t.Errorf("Expected to receive %v from the channel, got nothing after 1s", expected)
		debug.PrintStack()
	}
}
<|endoftext|>


<commit_before>package dev

import (
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/bmizerany/pat"
	"github.com/puma/puma-dev/httpu"
	"github.com/puma/puma-dev/httputil"
)

type HTTPServer struct {
	Address    string
	TLSAddress string
	Pool       *AppPool
	Debug      bool
	Events     *Events

	mux       *pat.PatternServeMux
	transport *httpu.Transport
	proxy     *httputil.ReverseProxy
}

func (h *HTTPServer) Setup() {
	h.transport = &httpu.Transport{
		Dial: (&net.Dialer{
			Timeout:   5 * time.Second,
			KeepAlive: 10 * time.Second,
		}).Dial,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
	}

	h.Pool.AppClosed = h.AppClosed

	h.proxy = &httputil.ReverseProxy{
		Proxy:         h.proxyReq,
		Transport:     h.transport,
		FlushInterval: 1 * time.Second,
		Debug:         h.Debug,
	}

	h.mux = pat.New()

	h.mux.Get("/status", http.HandlerFunc(h.status))
	h.mux.Get("/events", http.HandlerFunc(h.events))
}

func (h *HTTPServer) AppClosed(app *App) {
	// Whenever an app is closed, wipe out all idle conns. This
	// obviously closes down more than just this one apps connections
	// but that's ok.
	h.transport.CloseIdleConnections()
}

func pruneSub(name string) string {
	dot := strings.IndexByte(name, '.')
	if dot == -1 {
		return ""
	}

	return name[dot+1:]
}

func (h *HTTPServer) findApp(name string) (*App, error) {
	var (
		app *App
		err error
	)

	for name != "" {
		app, err = h.Pool.App(name)
		if err != nil {
			if err == ErrUnknownApp {
				name = pruneSub(name)
				continue
			}

			return nil, err
		}

		break
	}

	if app == nil {
		app, err = h.Pool.App("default")
		if err != nil {
			return nil, err
		}
	}

	err = app.WaitTilReady()
	if err != nil {
		return nil, err
	}

	return app, nil
}

func (h *HTTPServer) hostForApp(name string) (string, string, error) {
	var (
		app *App
		err error
	)

	for name != "" {
		app, err = h.Pool.App(name)
		if err != nil {
			if err == ErrUnknownApp {
				name = pruneSub(name)
				continue
			}

			return "", "", err
		}

		break
	}

	if app == nil {
		app, err = h.Pool.App("default")
		if err != nil {
			return "", "", err
		}
	}

	err = app.WaitTilReady()
	if err != nil {
		return "", "", err
	}

	return app.Scheme, app.Address(), nil
}

func (h *HTTPServer) removeTLD(host string) string {
	colon := strings.LastIndexByte(host, ':')
	if colon != -1 {
		if h, _, err := net.SplitHostPort(host); err == nil {
			host = h
		}
	}

	if strings.HasSuffix(host, ".xip.io") {
		parts := strings.Split(host, ".")
		if len(parts) < 6 {
			return ""
		}

		name := strings.Join(parts[:len(parts)-6], ".")

		return name
	}

	dot := strings.LastIndexByte(host, '.')

	if dot == -1 {
		return host
	} else {
		return host[:dot]
	}
}

func (h *HTTPServer) proxyReq(w http.ResponseWriter, req *http.Request) error {
	name := h.removeTLD(req.Host)

	app, err := h.findApp(name)
	if err != nil {
		if err == ErrUnknownApp {
			h.Events.Add("unknown_app", "name", name, "host", req.Host)
		} else {
			h.Events.Add("lookup_error", "error", err.Error())
		}

		return err
	}

	if app.Public && req.URL.Path != "/" {
		path := filepath.Join(app.dir, "public", path.Clean(req.URL.Path))

		fi, err := os.Stat(path)
		if err == nil && fi.Mode().IsRegular() {
			http.ServeFile(w, req, path)
			return httputil.ErrHandled
		}
	}

	req.URL.Scheme, req.URL.Host = app.Scheme, app.Address()
	return err
}

func (h *HTTPServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if h.Debug {
		fmt.Fprintf(os.Stderr, "%s: %s '%s' (host=%s)\n",
			time.Now().Format(time.RFC3339Nano),
			req.Method, req.URL.Path, req.Host)
	}

	if req.Host == "puma-dev" {
		h.mux.ServeHTTP(w, req)
	} else {
		h.proxy.ServeHTTP(w, req)
	}
}

func (h *HTTPServer) status(w http.ResponseWriter, req *http.Request) {
	type appStatus struct {
		Scheme  string `json:"scheme"`
		Address string `json:"address"`
		Status  string `json:"status"`
		Log     string `json:"log"`
	}

	statuses := map[string]appStatus{}

	h.Pool.ForApps(func(a *App) {
		var status string

		switch a.Status() {
		case Dead:
			status = "dead"
		case Booting:
			status = "booting"
		case Running:
			status = "running"
		default:
			status = "unknown"
		}

		statuses[a.Name] = appStatus{
			Scheme:  a.Scheme,
			Address: a.Address(),
			Status:  status,
			Log:     a.Log(),
		}
	})

	json.NewEncoder(w).Encode(statuses)
}

func (h *HTTPServer) events(w http.ResponseWriter, req *http.Request) {
	h.Events.WriteTo(w)
}
<commit_msg>exclude directories only from public folder serving<commit_after>package dev

import (
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/bmizerany/pat"
	"github.com/puma/puma-dev/httpu"
	"github.com/puma/puma-dev/httputil"
)

type HTTPServer struct {
	Address    string
	TLSAddress string
	Pool       *AppPool
	Debug      bool
	Events     *Events

	mux       *pat.PatternServeMux
	transport *httpu.Transport
	proxy     *httputil.ReverseProxy
}

func (h *HTTPServer) Setup() {
	h.transport = &httpu.Transport{
		Dial: (&net.Dialer{
			Timeout:   5 * time.Second,
			KeepAlive: 10 * time.Second,
		}).Dial,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
	}

	h.Pool.AppClosed = h.AppClosed

	h.proxy = &httputil.ReverseProxy{
		Proxy:         h.proxyReq,
		Transport:     h.transport,
		FlushInterval: 1 * time.Second,
		Debug:         h.Debug,
	}

	h.mux = pat.New()

	h.mux.Get("/status", http.HandlerFunc(h.status))
	h.mux.Get("/events", http.HandlerFunc(h.events))
}

func (h *HTTPServer) AppClosed(app *App) {
	// Whenever an app is closed, wipe out all idle conns. This
	// obviously closes down more than just this one apps connections
	// but that's ok.
	h.transport.CloseIdleConnections()
}

func pruneSub(name string) string {
	dot := strings.IndexByte(name, '.')
	if dot == -1 {
		return ""
	}

	return name[dot+1:]
}

func (h *HTTPServer) findApp(name string) (*App, error) {
	var (
		app *App
		err error
	)

	for name != "" {
		app, err = h.Pool.App(name)
		if err != nil {
			if err == ErrUnknownApp {
				name = pruneSub(name)
				continue
			}

			return nil, err
		}

		break
	}

	if app == nil {
		app, err = h.Pool.App("default")
		if err != nil {
			return nil, err
		}
	}

	err = app.WaitTilReady()
	if err != nil {
		return nil, err
	}

	return app, nil
}

func (h *HTTPServer) hostForApp(name string) (string, string, error) {
	var (
		app *App
		err error
	)

	for name != "" {
		app, err = h.Pool.App(name)
		if err != nil {
			if err == ErrUnknownApp {
				name = pruneSub(name)
				continue
			}

			return "", "", err
		}

		break
	}

	if app == nil {
		app, err = h.Pool.App("default")
		if err != nil {
			return "", "", err
		}
	}

	err = app.WaitTilReady()
	if err != nil {
		return "", "", err
	}

	return app.Scheme, app.Address(), nil
}

func (h *HTTPServer) removeTLD(host string) string {
	colon := strings.LastIndexByte(host, ':')
	if colon != -1 {
		if h, _, err := net.SplitHostPort(host); err == nil {
			host = h
		}
	}

	if strings.HasSuffix(host, ".xip.io") {
		parts := strings.Split(host, ".")
		if len(parts) < 6 {
			return ""
		}

		name := strings.Join(parts[:len(parts)-6], ".")

		return name
	}

	dot := strings.LastIndexByte(host, '.')

	if dot == -1 {
		return host
	} else {
		return host[:dot]
	}
}

func (h *HTTPServer) proxyReq(w http.ResponseWriter, req *http.Request) error {
	name := h.removeTLD(req.Host)

	app, err := h.findApp(name)
	if err != nil {
		if err == ErrUnknownApp {
			h.Events.Add("unknown_app", "name", name, "host", req.Host)
		} else {
			h.Events.Add("lookup_error", "error", err.Error())
		}

		return err
	}

	if app.Public && req.URL.Path != "/" {
		path := filepath.Join(app.dir, "public", path.Clean(req.URL.Path))

		fi, err := os.Stat(path)
		if err == nil && !fi.IsDir() {
			http.ServeFile(w, req, path)
			return httputil.ErrHandled
		}
	}

	req.URL.Scheme, req.URL.Host = app.Scheme, app.Address()
	return err
}

func (h *HTTPServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if h.Debug {
		fmt.Fprintf(os.Stderr, "%s: %s '%s' (host=%s)\n",
			time.Now().Format(time.RFC3339Nano),
			req.Method, req.URL.Path, req.Host)
	}

	if req.Host == "puma-dev" {
		h.mux.ServeHTTP(w, req)
	} else {
		h.proxy.ServeHTTP(w, req)
	}
}

func (h *HTTPServer) status(w http.ResponseWriter, req *http.Request) {
	type appStatus struct {
		Scheme  string `json:"scheme"`
		Address string `json:"address"`
		Status  string `json:"status"`
		Log     string `json:"log"`
	}

	statuses := map[string]appStatus{}

	h.Pool.ForApps(func(a *App) {
		var status string

		switch a.Status() {
		case Dead:
			status = "dead"
		case Booting:
			status = "booting"
		case Running:
			status = "running"
		default:
			status = "unknown"
		}

		statuses[a.Name] = appStatus{
			Scheme:  a.Scheme,
			Address: a.Address(),
			Status:  status,
			Log:     a.Log(),
		}
	})

	json.NewEncoder(w).Encode(statuses)
}

func (h *HTTPServer) events(w http.ResponseWriter, req *http.Request) {
	h.Events.WriteTo(w)
}
<|endoftext|>


<commit_before>package main_test

import (
	. "github.com/alphagov/cloudflare-configure"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/ghttp"

	"fmt"
	"log"
	"net/http"
)

var _ = Describe("CloudFlare", func() {
	var (
		server     *ghttp.Server
		query      *CloudFlareQuery
		logbuf     *gbytes.Buffer
		cloudFlare *CloudFlare
	)

	BeforeEach(func() {
		server = ghttp.NewServer()
		query = &CloudFlareQuery{RootURL: server.URL()}

		logbuf = gbytes.NewBuffer()
		logger := log.New(logbuf, "", 0)

		cloudFlare = NewCloudFlare(query, logger)
	})

	AfterEach(func() {
		server.Close()
	})

	Describe("CloudFlareSettings", func() {
		Describe("ConfigItems()", func() {
			It("should return ConfigItems", func() {
				settings := CloudFlareSettings{
					CloudFlareSetting{
						ID:         "always_online",
						Value:      "off",
						ModifiedOn: "2014-07-09T11:50:56.595672Z",
						Editable:   true,
					},
					CloudFlareSetting{
						ID:         "browser_cache_ttl",
						Value:      14400,
						ModifiedOn: "2014-07-09T11:50:56.595672Z",
						Editable:   true,
					},
				}

				Expect(settings.ConfigItems()).To(Equal(ConfigItems{
					"always_online":     "off",
					"browser_cache_ttl": 14400,
				}))
			})
		})
	})

	Describe("Zones()", func() {
		BeforeEach(func() {
			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/zones"),
					ghttp.RespondWith(http.StatusOK, `{
						"errors": [],
						"messages": [],
						"result": [
							{"id": "123", "name": "foo"},
							{"id": "456", "name": "bar"}
						],
						"success": true
					}`),
				),
			)
		})

		It("should return two CloudFlareZoneItems", func() {
			zones, err := cloudFlare.Zones()

			Expect(zones).To(Equal([]CloudFlareZoneItem{
				CloudFlareZoneItem{
					ID:   "123",
					Name: "foo",
				},
				CloudFlareZoneItem{
					ID:   "456",
					Name: "bar",
				},
			}))
			Expect(err).To(BeNil())
		})
	})

	Describe("MakeRequest()", func() {
		var req *http.Request

		BeforeEach(func() {
			req, _ = query.NewRequest("GET", "/something")
		})

		Context("200, success: false, errors: []", func() {
			BeforeEach(func() {
				server.AppendHandlers(
					ghttp.CombineHandlers(
						ghttp.VerifyRequest("GET", "/something"),
						ghttp.RespondWith(http.StatusOK, `{
							"errors": [],
							"messages": [],
							"result": [],
							"success": false
						}`),
					),
				)
			})

			It("should return error", func() {
				resp, err := cloudFlare.MakeRequest(req)

				Expect(resp).To(BeNil())
				Expect(err).ToNot(BeNil())
			})
		})

		Context("200, success: true, errors: [something bad]", func() {
			BeforeEach(func() {
				server.AppendHandlers(
					ghttp.CombineHandlers(
						ghttp.VerifyRequest("GET", "/something"),
						ghttp.RespondWith(http.StatusOK, `{
							"errors": ["something bad"],
							"messages": [],
							"result": [],
							"success": true
						}`),
					),
				)
			})

			It("should return error", func() {
				resp, err := cloudFlare.MakeRequest(req)

				Expect(resp).To(BeNil())
				Expect(err).ToNot(BeNil())
			})
		})

		Context("500, empty body", func() {
			BeforeEach(func() {
				server.AppendHandlers(
					ghttp.CombineHandlers(
						ghttp.VerifyRequest("GET", "/something"),
						ghttp.RespondWith(http.StatusServiceUnavailable, ""),
					),
				)
			})

			It("should return error", func() {
				resp, err := cloudFlare.MakeRequest(req)

				Expect(resp).To(BeNil())
				Expect(err).ToNot(BeNil())
			})
		})
	})

	Describe("Settings()", func() {
		zoneID := "123"

		BeforeEach(func() {
			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET",
						fmt.Sprintf("/zones/%s/settings", zoneID),
					),
					ghttp.RespondWith(http.StatusOK, `{
						"errors": [],
						"messages": [],
						"result": [
							{
								"id": "always_online",
								"value": "off",
								"modified_on": "2014-07-09T11:50:56.595672Z",
								"editable": true
							},
							{
								"id": "browser_cache_ttl",
								"value": 14400,
								"modified_on": "2014-07-09T11:50:56.595672Z",
								"editable": true
							}
						],
						"success": true
					}`),
				),
			)
		})

		It("should return two CloudFlareSettings", func() {
			settings, err := cloudFlare.Settings(zoneID)

			Expect(settings).To(Equal(CloudFlareSettings{
				CloudFlareSetting{
					ID:         "always_online",
					Value:      "off",
					ModifiedOn: "2014-07-09T11:50:56.595672Z",
					Editable:   true,
				},
				CloudFlareSetting{
					ID:         "browser_cache_ttl",
					Value:      float64(14400),
					ModifiedOn: "2014-07-09T11:50:56.595672Z",
					Editable:   true,
				},
			}))
			Expect(err).To(BeNil())
		})
	})

	Describe("Set()", func() {
		zoneID := "123"
		settingKey := "always_online"
		settingVal := "off"

		BeforeEach(func() {
			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("PATCH",
						fmt.Sprintf("/zones/%s/settings/%s", zoneID, settingKey),
					),
					ghttp.VerifyJSON(fmt.Sprintf(`{"value": "%s"}`, settingVal)),

					ghttp.RespondWith(http.StatusOK, `{
						"errors": [],
						"messages": [],
						"result": {
							"id": "always_online",
							"value": "off",
							"modified_on": "2014-07-09T11:50:56.595672Z",
							"editable": true
						},
						"success": true
					}`),
				),
			)
		})

		It("should set the value with no errors", func() {
			Expect(cloudFlare.Set(zoneID, settingKey, settingVal)).To(BeNil())
		})
	})

	Describe("Update()", func() {
		zoneID := "123"
		settingValAlwaysOnline := "on"
		settingValBrowserCache := 123

		BeforeEach(func() {
			server.RouteToHandler("PATCH", fmt.Sprintf("/zones/%s/settings/always_online", zoneID),
				ghttp.CombineHandlers(
					ghttp.VerifyJSON(fmt.Sprintf(`{"value": "%s"}`, settingValAlwaysOnline)),
					ghttp.RespondWithJSONEncoded(http.StatusOK, CloudFlareResponse{Success: true}),
				),
			)
			server.RouteToHandler("PATCH", fmt.Sprintf("/zones/%s/settings/browser_cache_ttl", zoneID),
				ghttp.CombineHandlers(
					ghttp.VerifyJSON(fmt.Sprintf(`{"value": %d}`, settingValBrowserCache)),
					ghttp.RespondWithJSONEncoded(http.StatusOK, CloudFlareResponse{Success: true}),
				),
			)
			server.RouteToHandler("PATCH", fmt.Sprintf("/zones/%s/settings/non_existent_devops_hero", zoneID),
				ghttp.CombineHandlers(
					ghttp.RespondWithJSONEncoded(http.StatusBadRequest, CloudFlareResponse{
						Success: false,
						Errors: []CloudFlareError{CloudFlareError{
							Code:    1006,
							Message: "Unrecognized zone setting name",
						}},
					}),
				),
			)
		})

		Context("logOnly false", func() {
			logOnly := false

			It("should set two config items and log progress", func() {
				config := ConfigItemsForUpdate{
					"always_online": ConfigItemForUpdate{
						Current:  "off",
						Expected: settingValAlwaysOnline,
					},
					"browser_cache_ttl": ConfigItemForUpdate{
						Current:  nil,
						Expected: settingValBrowserCache,
					},
				}

				err := cloudFlare.Update(zoneID, config, logOnly)

				Expect(server.ReceivedRequests()).To(HaveLen(2))
				Expect(err).To(BeNil())

				Expect(logbuf).To(gbytes.Say(fmt.Sprintf(
					`Changing setting "always_online" from "off" to "%s"`, settingValAlwaysOnline,
				)))
				Expect(logbuf).To(gbytes.Say(fmt.Sprintf(
					`Changing setting "browser_cache_ttl" from <nil> to %d`, settingValBrowserCache,
				)))
			})

			It("should return a public error when key is not supported by remote", func() {
				config := ConfigItemsForUpdate{
					"non_existent_devops_hero": ConfigItemForUpdate{
						Current:  nil,
						Expected: "always devopsing",
					},
					"browser_cache_ttl": ConfigItemForUpdate{
						Current:  nil,
						Expected: settingValBrowserCache,
					},
				}

				err := cloudFlare.Update(zoneID, config, false)

				Expect(server.ReceivedRequests()).To(HaveLen(1))
				Expect(err).ToNot(BeNil())

				// TODO:
				// What happens when we get a non-200 error for a given key we're
				// trying to set? What should we do and how should it be handled?
			})
		})

		Context("logOnly true", func() {
			logOnly := true

			It("should log progress without setting config items", func() {
				config := ConfigItemsForUpdate{
					"always_online": ConfigItemForUpdate{
						Current:  "off",
						Expected: settingValAlwaysOnline,
					},
					"browser_cache_ttl": ConfigItemForUpdate{
						Current:  nil,
						Expected: settingValBrowserCache,
					},
				}

				err := cloudFlare.Update(zoneID, config, logOnly)

				Expect(server.ReceivedRequests()).To(HaveLen(0))
				Expect(err).To(BeNil())

				Expect(logbuf).To(gbytes.Say(fmt.Sprintf(
					`Would have changed setting "always_online" from "off" to "%s"`, settingValAlwaysOnline,
				)))
				Expect(logbuf).To(gbytes.Say(fmt.Sprintf(
					`Would have changed setting "browser_cache_ttl" from <nil> to %d`, settingValBrowserCache,
				)))
			})
		})
	})
})
<commit_msg>Simplify declarations of objects<commit_after>package main_test

import (
	. "github.com/alphagov/cloudflare-configure"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/ghttp"

	"fmt"
	"log"
	"net/http"
)

var _ = Describe("CloudFlare", func() {
	var (
		server     *ghttp.Server
		query      *CloudFlareQuery
		logbuf     *gbytes.Buffer
		cloudFlare *CloudFlare
	)

	BeforeEach(func() {
		server = ghttp.NewServer()
		query = &CloudFlareQuery{RootURL: server.URL()}

		logbuf = gbytes.NewBuffer()
		logger := log.New(logbuf, "", 0)

		cloudFlare = NewCloudFlare(query, logger)
	})

	AfterEach(func() {
		server.Close()
	})

	Describe("CloudFlareSettings", func() {
		Describe("ConfigItems()", func() {
			It("should return ConfigItems", func() {
				settings := CloudFlareSettings{
					CloudFlareSetting{
						ID:         "always_online",
						Value:      "off",
						ModifiedOn: "2014-07-09T11:50:56.595672Z",
						Editable:   true,
					},
					CloudFlareSetting{
						ID:         "browser_cache_ttl",
						Value:      14400,
						ModifiedOn: "2014-07-09T11:50:56.595672Z",
						Editable:   true,
					},
				}

				Expect(settings.ConfigItems()).To(Equal(ConfigItems{
					"always_online":     "off",
					"browser_cache_ttl": 14400,
				}))
			})
		})
	})

	Describe("Zones()", func() {
		BeforeEach(func() {
			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/zones"),
					ghttp.RespondWith(http.StatusOK, `{
						"errors": [],
						"messages": [],
						"result": [
							{"id": "123", "name": "foo"},
							{"id": "456", "name": "bar"}
						],
						"success": true
					}`),
				),
			)
		})

		It("should return two CloudFlareZoneItems", func() {
			zones, err := cloudFlare.Zones()

			Expect(zones).To(Equal([]CloudFlareZoneItem{
				{
					ID:   "123",
					Name: "foo",
				},
				{
					ID:   "456",
					Name: "bar",
				},
			}))
			Expect(err).To(BeNil())
		})
	})

	Describe("MakeRequest()", func() {
		var req *http.Request

		BeforeEach(func() {
			req, _ = query.NewRequest("GET", "/something")
		})

		Context("200, success: false, errors: []", func() {
			BeforeEach(func() {
				server.AppendHandlers(
					ghttp.CombineHandlers(
						ghttp.VerifyRequest("GET", "/something"),
						ghttp.RespondWith(http.StatusOK, `{
							"errors": [],
							"messages": [],
							"result": [],
							"success": false
						}`),
					),
				)
			})

			It("should return error", func() {
				resp, err := cloudFlare.MakeRequest(req)

				Expect(resp).To(BeNil())
				Expect(err).ToNot(BeNil())
			})
		})

		Context("200, success: true, errors: [something bad]", func() {
			BeforeEach(func() {
				server.AppendHandlers(
					ghttp.CombineHandlers(
						ghttp.VerifyRequest("GET", "/something"),
						ghttp.RespondWith(http.StatusOK, `{
							"errors": ["something bad"],
							"messages": [],
							"result": [],
							"success": true
						}`),
					),
				)
			})

			It("should return error", func() {
				resp, err := cloudFlare.MakeRequest(req)

				Expect(resp).To(BeNil())
				Expect(err).ToNot(BeNil())
			})
		})

		Context("500, empty body", func() {
			BeforeEach(func() {
				server.AppendHandlers(
					ghttp.CombineHandlers(
						ghttp.VerifyRequest("GET", "/something"),
						ghttp.RespondWith(http.StatusServiceUnavailable, ""),
					),
				)
			})

			It("should return error", func() {
				resp, err := cloudFlare.MakeRequest(req)

				Expect(resp).To(BeNil())
				Expect(err).ToNot(BeNil())
			})
		})
	})

	Describe("Settings()", func() {
		zoneID := "123"

		BeforeEach(func() {
			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET",
						fmt.Sprintf("/zones/%s/settings", zoneID),
					),
					ghttp.RespondWith(http.StatusOK, `{
						"errors": [],
						"messages": [],
						"result": [
							{
								"id": "always_online",
								"value": "off",
								"modified_on": "2014-07-09T11:50:56.595672Z",
								"editable": true
							},
							{
								"id": "browser_cache_ttl",
								"value": 14400,
								"modified_on": "2014-07-09T11:50:56.595672Z",
								"editable": true
							}
						],
						"success": true
					}`),
				),
			)
		})

		It("should return two CloudFlareSettings", func() {
			settings, err := cloudFlare.Settings(zoneID)

			Expect(settings).To(Equal(CloudFlareSettings{
				CloudFlareSetting{
					ID:         "always_online",
					Value:      "off",
					ModifiedOn: "2014-07-09T11:50:56.595672Z",
					Editable:   true,
				},
				CloudFlareSetting{
					ID:         "browser_cache_ttl",
					Value:      float64(14400),
					ModifiedOn: "2014-07-09T11:50:56.595672Z",
					Editable:   true,
				},
			}))
			Expect(err).To(BeNil())
		})
	})

	Describe("Set()", func() {
		zoneID := "123"
		settingKey := "always_online"
		settingVal := "off"

		BeforeEach(func() {
			server.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("PATCH",
						fmt.Sprintf("/zones/%s/settings/%s", zoneID, settingKey),
					),
					ghttp.VerifyJSON(fmt.Sprintf(`{"value": "%s"}`, settingVal)),

					ghttp.RespondWith(http.StatusOK, `{
						"errors": [],
						"messages": [],
						"result": {
							"id": "always_online",
							"value": "off",
							"modified_on": "2014-07-09T11:50:56.595672Z",
							"editable": true
						},
						"success": true
					}`),
				),
			)
		})

		It("should set the value with no errors", func() {
			Expect(cloudFlare.Set(zoneID, settingKey, settingVal)).To(BeNil())
		})
	})

	Describe("Update()", func() {
		zoneID := "123"
		settingValAlwaysOnline := "on"
		settingValBrowserCache := 123

		BeforeEach(func() {
			server.RouteToHandler("PATCH", fmt.Sprintf("/zones/%s/settings/always_online", zoneID),
				ghttp.CombineHandlers(
					ghttp.VerifyJSON(fmt.Sprintf(`{"value": "%s"}`, settingValAlwaysOnline)),
					ghttp.RespondWithJSONEncoded(http.StatusOK, CloudFlareResponse{Success: true}),
				),
			)
			server.RouteToHandler("PATCH", fmt.Sprintf("/zones/%s/settings/browser_cache_ttl", zoneID),
				ghttp.CombineHandlers(
					ghttp.VerifyJSON(fmt.Sprintf(`{"value": %d}`, settingValBrowserCache)),
					ghttp.RespondWithJSONEncoded(http.StatusOK, CloudFlareResponse{Success: true}),
				),
			)
			server.RouteToHandler("PATCH", fmt.Sprintf("/zones/%s/settings/non_existent_devops_hero", zoneID),
				ghttp.CombineHandlers(
					ghttp.RespondWithJSONEncoded(http.StatusBadRequest, CloudFlareResponse{
						Success: false,
						Errors: []CloudFlareError{{
							Code:    1006,
							Message: "Unrecognized zone setting name",
						}},
					}),
				),
			)
		})

		Context("logOnly false", func() {
			logOnly := false

			It("should set two config items and log progress", func() {
				config := ConfigItemsForUpdate{
					"always_online": ConfigItemForUpdate{
						Current:  "off",
						Expected: settingValAlwaysOnline,
					},
					"browser_cache_ttl": ConfigItemForUpdate{
						Current:  nil,
						Expected: settingValBrowserCache,
					},
				}

				err := cloudFlare.Update(zoneID, config, logOnly)

				Expect(server.ReceivedRequests()).To(HaveLen(2))
				Expect(err).To(BeNil())

				Expect(logbuf).To(gbytes.Say(fmt.Sprintf(
					`Changing setting "always_online" from "off" to "%s"`, settingValAlwaysOnline,
				)))
				Expect(logbuf).To(gbytes.Say(fmt.Sprintf(
					`Changing setting "browser_cache_ttl" from <nil> to %d`, settingValBrowserCache,
				)))
			})

			It("should return a public error when key is not supported by remote", func() {
				config := ConfigItemsForUpdate{
					"non_existent_devops_hero": ConfigItemForUpdate{
						Current:  nil,
						Expected: "always devopsing",
					},
					"browser_cache_ttl": ConfigItemForUpdate{
						Current:  nil,
						Expected: settingValBrowserCache,
					},
				}

				err := cloudFlare.Update(zoneID, config, false)

				Expect(server.ReceivedRequests()).To(HaveLen(1))
				Expect(err).ToNot(BeNil())

				// TODO:
				// What happens when we get a non-200 error for a given key we're
				// trying to set? What should we do and how should it be handled?
			})
		})

		Context("logOnly true", func() {
			logOnly := true

			It("should log progress without setting config items", func() {
				config := ConfigItemsForUpdate{
					"always_online": ConfigItemForUpdate{
						Current:  "off",
						Expected: settingValAlwaysOnline,
					},
					"browser_cache_ttl": ConfigItemForUpdate{
						Current:  nil,
						Expected: settingValBrowserCache,
					},
				}

				err := cloudFlare.Update(zoneID, config, logOnly)

				Expect(server.ReceivedRequests()).To(HaveLen(0))
				Expect(err).To(BeNil())

				Expect(logbuf).To(gbytes.Say(fmt.Sprintf(
					`Would have changed setting "always_online" from "off" to "%s"`, settingValAlwaysOnline,
				)))
				Expect(logbuf).To(gbytes.Say(fmt.Sprintf(
					`Would have changed setting "browser_cache_ttl" from <nil> to %d`, settingValBrowserCache,
				)))
			})
		})
	})
})
<|endoftext|>


<commit_before>package jsonmap

import (
	"errors"
	"fmt"
	"net/http"
	"reflect"
	"regexp"
	"strconv"
	"time"
)

// This is the overarching struct used to transform structs into url params
// and vice versa
type QueryMap struct {
	UnderlyingType interface{}
	Parameters     []MappedParameter
}

// Taking a struct and turning it into a url param. The precise mechanisms of doing
// so are are defined in the individual MappedParameter
func (qm QueryMap) Encode(src interface{}, urlQuery map[string][]string) error {
	srcVal := reflect.ValueOf(src)

	for _, p := range qm.Parameters {
		fieldVal := srcVal.FieldByName(p.StructFieldName)

		if fieldVal.IsZero() && p.OmitEmpty {
			continue
		}

		strVal, err := p.Mapper.Encode(fieldVal)
		if err != nil {
			return errors.New("error in encoding struct: " + err.Error())
		}

		urlQuery[p.ParameterName] = strVal
	}

	return nil
}

// Taking a URL Query (or any string->[]string struct) and shoving it into the struct
// as specified by qm.UnderlyingType
func (qm QueryMap) Decode(urlQuery map[string][]string, dst interface{}) error {
	// First sanity check to ensure that the struct passed in matches
	// the struct the QueryMap was designed to handle
	if reflect.ValueOf(dst).Elem().Type() != reflect.TypeOf(qm.UnderlyingType) {
		return fmt.Errorf("attempting to decode into mismatched struct: expected %s but got %s",
			reflect.TypeOf(qm.UnderlyingType),
			reflect.ValueOf(dst).Elem().Type(),
		)
	}

	errs := &MultiValidationError{}
	dstVal := reflect.ValueOf(dst).Elem()
	for _, param := range qm.Parameters {
		paramValue := urlQuery[param.ParameterName]
		if len(paramValue) == 0 {
			continue
		}

		field := dstVal.FieldByName(param.StructFieldName)
		decodedParam, err := param.Mapper.Decode(paramValue)
		if err != nil {
			 errs.AddError(NewValidationError("error ocurred while reading value (%s) into param %s: %s",
				urlQuery[param.ParameterName],
				param.StructFieldName,
				err.Error(),
			))
			continue
		}

		field.Set(reflect.ValueOf(decodedParam))
	}

	if len(errs.Errors()) == 0 {
		return nil
	}
	return errs
}

// This ignores the case of parameter name in favor of the canonical format of
// http.Header
func (qm QueryMap) EncodeHeader(src interface{}, headers http.Header) error {
	srcVal := reflect.ValueOf(src)

	for _, p := range qm.Parameters {
		fieldVal := srcVal.FieldByName(p.StructFieldName)

		if fieldVal.IsZero() && p.OmitEmpty {
			continue
		}

		sliVal, err := p.Mapper.Encode(fieldVal)
		if err != nil {
			return errors.New("error in encoding struct: " + err.Error())
		}

		// Not using .Set() because it only allows strings and not slices
		headers[http.CanonicalHeaderKey(p.ParameterName)] = sliVal
	}

	return nil
}

func (qm QueryMap) DecodeHeader(headers http.Header, dst interface{}) error {
	if reflect.ValueOf(dst).Elem().Type() != reflect.TypeOf(qm.UnderlyingType) {
		return errors.New("attempting to decode into the wrong struct")
	}

	dstVal := reflect.ValueOf(dst).Elem()
	for _, param := range qm.Parameters {
		field := dstVal.FieldByName(param.StructFieldName)
		headerVal := headers[http.CanonicalHeaderKey(param.ParameterName)]
		if len(headerVal) == 0 {
			continue
		}

		fieldVal, err := param.Mapper.Decode(headerVal)
		if err != nil {
			return err
		}

		field.Set(reflect.ValueOf(fieldVal))
	}
	return nil
}

// MappedParameter corresponds to each field in a specific struct,
// it requires struct's name and the corresponding key value in the URL query
type MappedParameter struct {
	StructFieldName string
	ParameterName   string
	Mapper          QueryParameterMapper
	OmitEmpty       bool
}

// QueryParameterMapper defines how url.Values value ([]string) and struct are to be
// transformed into each other. It is from a slice of strings, reflecting the structure
// of url.Values. These can be specified by their type (whichever struct the Parameter
// mapper will be, and the restrictions defined on the type, defined by Validators slice
// below)
type QueryParameterMapper interface {
	Encode(reflect.Value) ([]string, error)
	Decode([]string) (interface{}, error)
}

// Examples of mappers
type StringQueryParameterMapper struct {
	Validators []func(string) bool
}

func (sqpm StringQueryParameterMapper) Decode(src []string) (interface{}, error) {
	// If more than one value is expected, use StrSliceQueryParameterMapper
	if len(src) != 1 {
		return nil, NewValidationError("expected only one value")
	}

	str := src[0]
	for _, v := range sqpm.Validators {
		if !v(str) {
			return nil, NewValidationError("a validation test failed")
		}
	}

	return str, nil
}

func (sqpm StringQueryParameterMapper) Encode(src reflect.Value) ([]string, error) {
	if src.Kind() != reflect.String {
		return nil, fmt.Errorf("expected string but got: %s", src.Kind())
	}

	return []string{src.String()}, nil
}

// Some useful validators
func StringRangeValidator(min, max int) func(string) bool {
	return func(s string) bool {
		return min <= len(s) && len(s) <= max
	}
}

func StringRegexValidator(r *regexp.Regexp) func(string) bool {
	return func(s string) bool {
		return r.MatchString(s)
	}
}

// Probably doesn't need Validators
type BoolQueryParameterMapper struct{}

func (bqpm BoolQueryParameterMapper) Decode(src []string) (interface{}, error) {
	if len(src) != 1 {
		return nil, NewValidationError("expected only one value")
	}

	// Special case
	if src[0] == "" {
		return false, nil
	}

	b, err := strconv.ParseBool(src[0])
	if err != nil {
		return nil, fmt.Errorf("could not parse into bool: %s", err.Error())
	}
	return b, nil
}

func (bqpm BoolQueryParameterMapper) Encode(src reflect.Value) ([]string, error) {
	if src.Kind() != reflect.Bool {
		return nil, fmt.Errorf("expected boolean but got: %s", src.Kind())
	}
	return []string{strconv.FormatBool(src.Bool())}, nil
}

type IntQueryParameterMapper struct {
	Validators []func(int64) bool
	BitSize	int
}

func (iqpm IntQueryParameterMapper) Decode(src []string) (interface{}, error) {
	if len(src) != 1 {
		return nil, NewValidationError("expected only one value")
	}

	num, err := strconv.ParseInt(src[0], 10, iqpm.BitSize)
	if err != nil {
		return nil, NewValidationError("param could not be converted to integer: %s",
										err.Error(),
					)
	}

	for _, v := range iqpm.Validators {
		if !v(num) {
			return nil, NewValidationError("a validation test failed")
		}
	}

	switch b := iqpm.BitSize; {
	case b == 0:
		return int(num), nil
	case b <= 8:
		return int8(num), nil
	case b <= 16:
		return int16(num), nil
	case b <= 32:
		return int32(num), nil
	}

	return num, nil
}

func (iqpm IntQueryParameterMapper) Encode(src reflect.Value) ([]string, error) {
	switch src.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return []string{strconv.FormatInt(src.Int(), 10)}, nil
	default:
		return nil, fmt.Errorf("expected int-type but got: %s", src.Kind())
	}
}

type UintQueryParameterMapper struct {
	Validators []func(uint64) bool
	BitSize	int
}

func (uqpm UintQueryParameterMapper) Decode(src []string) (interface{}, error) {
	if len(src) != 1 {
		return nil, NewValidationError("expected only one value")
	}

	num, err := strconv.ParseUint(src[0], 10, uqpm.BitSize)
	if err != nil {
		return nil, NewValidationError("param could not be converted to integer: %s",
			err.Error(),
		)
	}

	for _, v := range uqpm.Validators {
		if !v(num) {
			return nil, NewValidationError("a validation test failed")
		}
	}

	switch b := uqpm.BitSize; {
	case b == 0:
		return uint(num), nil
	case b <= 8:
		return uint8(num), nil
	case b <= 16:
		return uint16(num), nil
	case b <= 32:
		return uint32(num), nil
	}

	return num, nil
}

func (uqpm UintQueryParameterMapper) Encode(src reflect.Value) ([]string, error) {
	switch src.Kind() {
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return []string{strconv.FormatUint(src.Uint(), 10)}, nil
	default:
		return nil, fmt.Errorf("expected uint-type but got: %s", src.Kind())
	}
}

type TimeQueryParameterMapper struct {
	Validators []func(time.Time) bool
}

func (tqpm TimeQueryParameterMapper) Decode(src []string) (interface{}, error) {
	if len(src) != 1 {
		return nil, NewValidationError("expected one value, but got %d", len(src))
	}

	t := time.Time{}
	err := t.UnmarshalText([]byte(src[0]))
	if err != nil {
		return nil, NewValidationError("param could not be marshalled to time.Time: %s", err.Error())
	}

	for _, v := range tqpm.Validators {
		if !v(t) {
			return nil, NewValidationError("a validation test failed")
		}
	}
	return t, nil
}

func (tqpm TimeQueryParameterMapper) Encode(src reflect.Value) ([]string, error) {
	if src.Kind() != reflect.Struct {
		return nil, fmt.Errorf("expected struct but got: %s", src.Kind())
	}
	if src.Type() != reflect.TypeOf(time.Time{}) {
		return nil, fmt.Errorf("expected time.Time but got: %s", src.Type())
	}

	b, err := src.Interface().(time.Time).MarshalText()
	if err != nil {
		return nil, err
	}

	return []string{string(b)}, nil
}

type StrSliceQueryParameterMapper struct {
	Validators                     []func([]string) bool
	UnderlyingQueryParameterMapper QueryParameterMapper
}

func (sqpm StrSliceQueryParameterMapper) Decode(src []string) (interface{}, error) {
	for _, val := range sqpm.Validators {
		if !val(src) {
			return nil, NewValidationError("A validation test failed")
		}
	}

	var retVal []string
	// My brain has been sufficiently poisoned by this code.
	// There's probably a better way to do this, but this works and keeps QueryMap.Decode
	// ignorant of the internals of the fields
	for _, s := range src {
		v, err := sqpm.UnderlyingQueryParameterMapper.Decode([]string{s})
		if err != nil {
			return nil, NewValidationError("decoding a slice element failed: %s", err.Error())
		}
		retVal = append(retVal, v.(string))
	}
	return retVal, nil
}

func (sqpm StrSliceQueryParameterMapper) Encode(src reflect.Value) ([]string, error) {
	if src.Kind() != reflect.Slice {
		return nil, fmt.Errorf("expected slice but got: %s", src.Kind())
	}
	var retSlice []string
	for i := 0; i < src.Len(); i++ {
		s, err := sqpm.UnderlyingQueryParameterMapper.Encode(src.Index(i))
		if err != nil {
			return nil, errors.New("error in encoding slice internals: " + err.Error())
		}
		retSlice = append(retSlice, s[0])
	}

	return retSlice, nil
}

type StrPointerQueryParameterMapper struct {
	UnderlyingQueryParameterMapper QueryParameterMapper
}

func (pqpm StrPointerQueryParameterMapper) Decode(src []string) (interface{}, error) {
	if len(src) != 1 {
		return nil, NewValidationError("expected one value, but got %d", len(src))
	}
	v, err := pqpm.UnderlyingQueryParameterMapper.Decode(src)
	if err != nil {
		return nil, NewValidationError("error occurred while decoding struct")
	}
	v2 := v.(string)
	return &v2, nil
}

func (pqpm StrPointerQueryParameterMapper) Encode(src reflect.Value) ([]string, error) {
	if src.Type() != reflect.PtrTo(reflect.TypeOf("")) {
		return nil, fmt.Errorf("expected pointer but got: %s", src.Kind())
	}
	return []string{src.Elem().String()}, nil
}
<commit_msg>Adding errors to header support<commit_after>package jsonmap

import (
	"errors"
	"fmt"
	"net/http"
	"reflect"
	"regexp"
	"strconv"
	"time"
)

// This is the overarching struct used to transform structs into url params
// and vice versa
type QueryMap struct {
	UnderlyingType interface{}
	Parameters     []MappedParameter
}

// Taking a struct and turning it into a url param. The precise mechanisms of doing
// so are are defined in the individual MappedParameter
func (qm QueryMap) Encode(src interface{}, urlQuery map[string][]string) error {
	srcVal := reflect.ValueOf(src)

	for _, p := range qm.Parameters {
		fieldVal := srcVal.FieldByName(p.StructFieldName)

		if fieldVal.IsZero() && p.OmitEmpty {
			continue
		}

		strVal, err := p.Mapper.Encode(fieldVal)
		if err != nil {
			return errors.New("error in encoding struct: " + err.Error())
		}

		urlQuery[p.ParameterName] = strVal
	}

	return nil
}

// Taking a URL Query (or any string->[]string struct) and shoving it into the struct
// as specified by qm.UnderlyingType
func (qm QueryMap) Decode(urlQuery map[string][]string, dst interface{}) error {
	// First sanity check to ensure that the struct passed in matches
	// the struct the QueryMap was designed to handle
	if reflect.ValueOf(dst).Elem().Type() != reflect.TypeOf(qm.UnderlyingType) {
		return fmt.Errorf("attempting to decode into mismatched struct: expected %s but got %s",
			reflect.TypeOf(qm.UnderlyingType),
			reflect.ValueOf(dst).Elem().Type(),
		)
	}

	errs := &MultiValidationError{}
	dstVal := reflect.ValueOf(dst).Elem()
	for _, param := range qm.Parameters {
		paramValue := urlQuery[param.ParameterName]
		if len(paramValue) == 0 {
			continue
		}

		field := dstVal.FieldByName(param.StructFieldName)
		decodedParam, err := param.Mapper.Decode(paramValue)
		if err != nil {
			errs.AddError(NewValidationError("error ocurred while reading value (%s) into param %s: %s",
				urlQuery[param.ParameterName],
				param.StructFieldName,
				err.Error(),
			))
			continue
		}

		field.Set(reflect.ValueOf(decodedParam))
	}

	if len(errs.Errors()) == 0 {
		return nil
	}
	return errs
}

// This ignores the case of parameter name in favor of the canonical format of
// http.Header
func (qm QueryMap) EncodeHeader(src interface{}, headers http.Header) error {
	srcVal := reflect.ValueOf(src)

	for _, p := range qm.Parameters {
		fieldVal := srcVal.FieldByName(p.StructFieldName)

		if fieldVal.IsZero() && p.OmitEmpty {
			continue
		}

		sliVal, err := p.Mapper.Encode(fieldVal)
		if err != nil {
			return errors.New("error in encoding struct: " + err.Error())
		}

		// Not using .Set() because it only allows strings and not slices
		headers[http.CanonicalHeaderKey(p.ParameterName)] = sliVal
	}

	return nil
}

func (qm QueryMap) DecodeHeader(headers http.Header, dst interface{}) error {
	if reflect.ValueOf(dst).Elem().Type() != reflect.TypeOf(qm.UnderlyingType) {
		return errors.New("attempting to decode into the wrong struct")
	}

	// First sanity check to ensure that the struct passed in matches
	// the struct the QueryMap was designed to handle
	if reflect.ValueOf(dst).Elem().Type() != reflect.TypeOf(qm.UnderlyingType) {
		return fmt.Errorf("attempting to decode into mismatched struct: expected %s but got %s",
			reflect.TypeOf(qm.UnderlyingType),
			reflect.ValueOf(dst).Elem().Type(),
		)
	}

	errs := &MultiValidationError{}
	dstVal := reflect.ValueOf(dst).Elem()
	for _, param := range qm.Parameters {
		headerVal := headers[http.CanonicalHeaderKey(param.ParameterName)]
		if len(headerVal) == 0 {
			continue
		}

		field := dstVal.FieldByName(param.StructFieldName)
		decodedHeader, err := param.Mapper.Decode(headerVal)
		if err != nil {
			errs.AddError(NewValidationError("error ocurred while reading value (%s) into param %s: %s",
				headerVal,
				param.StructFieldName,
				err.Error(),
			))
			continue
		}

		field.Set(reflect.ValueOf(decodedHeader))
	}

	if len(errs.Errors()) == 0 {
		return nil
	}
	return errs
}

// MappedParameter corresponds to each field in a specific struct,
// it requires struct's name and the corresponding key value in the URL query
type MappedParameter struct {
	StructFieldName string
	ParameterName   string
	Mapper          QueryParameterMapper
	OmitEmpty       bool
}

// QueryParameterMapper defines how url.Values value ([]string) and struct are to be
// transformed into each other. It is from a slice of strings, reflecting the structure
// of url.Values. These can be specified by their type (whichever struct the Parameter
// mapper will be, and the restrictions defined on the type, defined by Validators slice
// below)
type QueryParameterMapper interface {
	Encode(reflect.Value) ([]string, error)
	Decode([]string) (interface{}, error)
}

// Examples of mappers
type StringQueryParameterMapper struct {
	Validators []func(string) bool
}

func (sqpm StringQueryParameterMapper) Decode(src []string) (interface{}, error) {
	// If more than one value is expected, use StrSliceQueryParameterMapper
	if len(src) != 1 {
		return nil, NewValidationError("expected only one value")
	}

	str := src[0]
	for _, v := range sqpm.Validators {
		if !v(str) {
			return nil, NewValidationError("a validation test failed")
		}
	}

	return str, nil
}

func (sqpm StringQueryParameterMapper) Encode(src reflect.Value) ([]string, error) {
	if src.Kind() != reflect.String {
		return nil, fmt.Errorf("expected string but got: %s", src.Kind())
	}

	return []string{src.String()}, nil
}

// Some useful validators
func StringRangeValidator(min, max int) func(string) bool {
	return func(s string) bool {
		return min <= len(s) && len(s) <= max
	}
}

func StringRegexValidator(r *regexp.Regexp) func(string) bool {
	return func(s string) bool {
		return r.MatchString(s)
	}
}

// Probably doesn't need Validators
type BoolQueryParameterMapper struct{}

func (bqpm BoolQueryParameterMapper) Decode(src []string) (interface{}, error) {
	if len(src) != 1 {
		return nil, NewValidationError("expected only one value")
	}

	// Special case
	if src[0] == "" {
		return false, nil
	}

	b, err := strconv.ParseBool(src[0])
	if err != nil {
		return nil, fmt.Errorf("could not parse into bool: %s", err.Error())
	}
	return b, nil
}

func (bqpm BoolQueryParameterMapper) Encode(src reflect.Value) ([]string, error) {
	if src.Kind() != reflect.Bool {
		return nil, fmt.Errorf("expected boolean but got: %s", src.Kind())
	}
	return []string{strconv.FormatBool(src.Bool())}, nil
}

type IntQueryParameterMapper struct {
	Validators []func(int64) bool
	BitSize    int
}

func (iqpm IntQueryParameterMapper) Decode(src []string) (interface{}, error) {
	if len(src) != 1 {
		return nil, NewValidationError("expected only one value")
	}

	num, err := strconv.ParseInt(src[0], 10, iqpm.BitSize)
	if err != nil {
		return nil, NewValidationError("param could not be converted to integer: %s",
			err.Error(),
		)
	}

	for _, v := range iqpm.Validators {
		if !v(num) {
			return nil, NewValidationError("a validation test failed")
		}
	}

	switch b := iqpm.BitSize; {
	case b == 0:
		return int(num), nil
	case b <= 8:
		return int8(num), nil
	case b <= 16:
		return int16(num), nil
	case b <= 32:
		return int32(num), nil
	}

	return num, nil
}

func (iqpm IntQueryParameterMapper) Encode(src reflect.Value) ([]string, error) {
	switch src.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return []string{strconv.FormatInt(src.Int(), 10)}, nil
	default:
		return nil, fmt.Errorf("expected int-type but got: %s", src.Kind())
	}
}

type UintQueryParameterMapper struct {
	Validators []func(uint64) bool
	BitSize    int
}

func (uqpm UintQueryParameterMapper) Decode(src []string) (interface{}, error) {
	if len(src) != 1 {
		return nil, NewValidationError("expected only one value")
	}

	num, err := strconv.ParseUint(src[0], 10, uqpm.BitSize)
	if err != nil {
		return nil, NewValidationError("param could not be converted to integer: %s",
			err.Error(),
		)
	}

	for _, v := range uqpm.Validators {
		if !v(num) {
			return nil, NewValidationError("a validation test failed")
		}
	}

	switch b := uqpm.BitSize; {
	case b == 0:
		return uint(num), nil
	case b <= 8:
		return uint8(num), nil
	case b <= 16:
		return uint16(num), nil
	case b <= 32:
		return uint32(num), nil
	}

	return num, nil
}

func (uqpm UintQueryParameterMapper) Encode(src reflect.Value) ([]string, error) {
	switch src.Kind() {
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return []string{strconv.FormatUint(src.Uint(), 10)}, nil
	default:
		return nil, fmt.Errorf("expected uint-type but got: %s", src.Kind())
	}
}

type TimeQueryParameterMapper struct {
	Validators []func(time.Time) bool
}

func (tqpm TimeQueryParameterMapper) Decode(src []string) (interface{}, error) {
	if len(src) != 1 {
		return nil, NewValidationError("expected one value, but got %d", len(src))
	}

	t := time.Time{}
	err := t.UnmarshalText([]byte(src[0]))
	if err != nil {
		return nil, NewValidationError("param could not be marshalled to time.Time: %s", err.Error())
	}

	for _, v := range tqpm.Validators {
		if !v(t) {
			return nil, NewValidationError("a validation test failed")
		}
	}
	return t, nil
}

func (tqpm TimeQueryParameterMapper) Encode(src reflect.Value) ([]string, error) {
	if src.Kind() != reflect.Struct {
		return nil, fmt.Errorf("expected struct but got: %s", src.Kind())
	}
	if src.Type() != reflect.TypeOf(time.Time{}) {
		return nil, fmt.Errorf("expected time.Time but got: %s", src.Type())
	}

	b, err := src.Interface().(time.Time).MarshalText()
	if err != nil {
		return nil, err
	}

	return []string{string(b)}, nil
}

type StrSliceQueryParameterMapper struct {
	Validators                     []func([]string) bool
	UnderlyingQueryParameterMapper QueryParameterMapper
}

func (sqpm StrSliceQueryParameterMapper) Decode(src []string) (interface{}, error) {
	for _, val := range sqpm.Validators {
		if !val(src) {
			return nil, NewValidationError("A validation test failed")
		}
	}

	var retVal []string
	// My brain has been sufficiently poisoned by this code.
	// There's probably a better way to do this, but this works and keeps QueryMap.Decode
	// ignorant of the internals of the fields
	for _, s := range src {
		v, err := sqpm.UnderlyingQueryParameterMapper.Decode([]string{s})
		if err != nil {
			return nil, NewValidationError("decoding a slice element failed: %s", err.Error())
		}
		retVal = append(retVal, v.(string))
	}
	return retVal, nil
}

func (sqpm StrSliceQueryParameterMapper) Encode(src reflect.Value) ([]string, error) {
	if src.Kind() != reflect.Slice {
		return nil, fmt.Errorf("expected slice but got: %s", src.Kind())
	}
	var retSlice []string
	for i := 0; i < src.Len(); i++ {
		s, err := sqpm.UnderlyingQueryParameterMapper.Encode(src.Index(i))
		if err != nil {
			return nil, errors.New("error in encoding slice internals: " + err.Error())
		}
		retSlice = append(retSlice, s[0])
	}

	return retSlice, nil
}

type StrPointerQueryParameterMapper struct {
	UnderlyingQueryParameterMapper QueryParameterMapper
}

func (pqpm StrPointerQueryParameterMapper) Decode(src []string) (interface{}, error) {
	if len(src) != 1 {
		return nil, NewValidationError("expected one value, but got %d", len(src))
	}
	v, err := pqpm.UnderlyingQueryParameterMapper.Decode(src)
	if err != nil {
		return nil, NewValidationError("error occurred while decoding struct")
	}
	v2 := v.(string)
	return &v2, nil
}

func (pqpm StrPointerQueryParameterMapper) Encode(src reflect.Value) ([]string, error) {
	if src.Type() != reflect.PtrTo(reflect.TypeOf("")) {
		return nil, fmt.Errorf("expected pointer but got: %s", src.Kind())
	}
	return []string{src.Elem().String()}, nil
}
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2017 Mainflux
 *
 * Mainflux server is licensed under an Apache license, version 2.0.
 * All rights not explicitly granted in the Apache license, version 2.0 are reserved.
 * See the included LICENSE file for more details.
 */

package main

import (
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"syscall"

	"github.com/drasko/edgex-export/client"
	"github.com/drasko/edgex-export/mongo"

	"go.uber.org/zap"
	"gopkg.in/mgo.v2"
)

const (
	port        int    = 7070
	defMongoURL string = "mongodb://0.0.0.0:27017"
	envMongoURL string = "EXPORT_CLIENT_MONGO_URL"
)

type config struct {
	Port     int
	MongoURL string
}

func main() {
	cfg := loadConfig()

	logger, _ := zap.NewProduction()
	defer logger.Sync()

	client.InitLogger(logger)

	ms := connectToMongo(cfg, logger)
	defer ms.Close()

	repo := mongo.NewMongoRepository(ms)
	client.InitMongoRepository(repo)

	errs := make(chan error, 2)

	go func() {
		p := fmt.Sprintf(":%d", cfg.Port)
		logger.Info("Staring Export Client", zap.String("url", p))
		errs <- http.ListenAndServe(p, client.HTTPServer())
	}()

	go func() {
		c := make(chan os.Signal)
		signal.Notify(c, syscall.SIGINT)
		errs <- fmt.Errorf("%s", <-c)
	}()

	c := <-errs
	logger.Info("terminated", zap.String("error", c.Error()))
}

func loadConfig() *config {
	return &config{
		Port:     port,
		MongoURL: env(envMongoURL, defMongoURL),
	}
}

func env(key, fallback string) string {
	value := os.Getenv(key)
	if value == "" {
		return fallback
	}

	return value
}

func connectToMongo(cfg *config, logger *zap.Logger) *mgo.Session {
	ms, err := mgo.Dial(cfg.MongoURL)
	if err != nil {
		logger.Error("Failed to connect to Mongo.", zap.Error(err))
	}

	ms.SetMode(mgo.Monotonic, true)

	return ms
}
<commit_msg>Fix distro cmd, to use the distro package instead of client<commit_after>/**
 * Copyright (c) 2017 Mainflux
 *
 * Mainflux server is licensed under an Apache license, version 2.0.
 * All rights not explicitly granted in the Apache license, version 2.0 are reserved.
 * See the included LICENSE file for more details.
 */

package main

import (
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"syscall"

	"github.com/drasko/edgex-export/distro"
	"github.com/drasko/edgex-export/mongo"

	"go.uber.org/zap"
	"gopkg.in/mgo.v2"
)

const (
	port        int    = 48070
	defMongoURL string = "mongodb://0.0.0.0:27017"
	envMongoURL string = "EXPORT_DISTRO_MONGO_URL"
)

type config struct {
	Port     int
	MongoURL string
}

func main() {
	cfg := loadConfig()

	logger, _ := zap.NewProduction()
	defer logger.Sync()

	distro.InitLogger(logger)

	ms := connectToMongo(cfg, logger)
	defer ms.Close()

	repo := mongo.NewMongoRepository(ms)
	distro.InitMongoRepository(repo)

	errs := make(chan error, 2)

	go func() {
		p := fmt.Sprintf(":%d", cfg.Port)
		logger.Info("Staring Export Distro", zap.String("url", p))
		errs <- http.ListenAndServe(p, distro.HTTPServer())
	}()

	go func() {
		c := make(chan os.Signal)
		signal.Notify(c, syscall.SIGINT)
		errs <- fmt.Errorf("%s", <-c)
	}()

	c := <-errs
	logger.Info("terminated", zap.String("error", c.Error()))
}

func loadConfig() *config {
	return &config{
		Port:     port,
		MongoURL: env(envMongoURL, defMongoURL),
	}
}

func env(key, fallback string) string {
	value := os.Getenv(key)
	if value == "" {
		return fallback
	}

	return value
}

func connectToMongo(cfg *config, logger *zap.Logger) *mgo.Session {
	ms, err := mgo.Dial(cfg.MongoURL)
	if err != nil {
		logger.Error("Failed to connect to Mongo.", zap.Error(err))
	}

	ms.SetMode(mgo.Monotonic, true)

	return ms
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/influxdb/influxdb"
	"github.com/influxdb/influxdb/collectd"
	"github.com/influxdb/influxdb/graphite"
	"github.com/influxdb/influxdb/httpd"
	"github.com/influxdb/influxdb/messaging"
)

// execRun runs the "run" command.
func execRun(args []string) {
	// Parse command flags.
	fs := flag.NewFlagSet("", flag.ExitOnError)
	var (
		configPath = fs.String("config", "", "")
		pidPath    = fs.String("pidfile", "", "")
		hostname   = fs.String("hostname", "", "")
		join       = fs.String("join", "", "")
	)
	fs.Usage = printRunUsage
	fs.Parse(args)

	// Print sweet InfluxDB logo and write the process id to file.
	log.Print(logo)
	log.SetPrefix(`[influxd] `)
	log.SetFlags(log.LstdFlags)
	writePIDFile(*pidPath)

	// Parse the configuration and determine if a broker and/or server exist.
	config := parseConfig(*configPath, *hostname)
	configExists := *configPath != ""
	initializing := !fileExists(config.BrokerDir()) && !fileExists(config.DataDir())

	// Create a logging writer.
	logWriter := os.Stderr
	if config.Logging.File != "" {
		var err error
		logWriter, err = os.OpenFile(config.Logging.File, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0660)
		if err != nil {
			log.Fatalf("unable to open log file %s: %s", config.Logging.File, err.Error())
		}
	}
	log.SetOutput(logWriter)

	// Parse join urls from the --join flag.
	var joinURLs []*url.URL
	if *join == "" {
		joinURLs = parseURLs(config.JoinURLs())
	} else {
		joinURLs = parseURLs(*join)
	}

	// Mark the start of the log.
	log.Printf("influxd starting up")

	// Open broker, initialize or join as necessary.
	b := openBroker(config.BrokerDir(), config.BrokerURL(), initializing, joinURLs, logWriter)

	// Start the broker handler.
	var h *Handler
	if b != nil {
		h = &Handler{brokerHandler: messaging.NewHandler(b)}
		go func() { log.Fatal(http.ListenAndServe(config.BrokerAddr(), h)) }()
		log.Printf("broker listening on %s", config.BrokerAddr())
	}

	// Open server, initialize or join as necessary.
	s := openServer(config.DataDir(), config.DataURL(), b, initializing, configExists, joinURLs, logWriter)

	// Start the server handler. Attach to broker if listening on the same port.
	if s != nil {
		sh := httpd.NewHandler(s, config.Authentication.Enabled, version)
		if h != nil && config.BrokerAddr() == config.DataAddr() {
			h.serverHandler = sh
		} else {
			go func() { log.Fatal(http.ListenAndServe(config.DataAddr(), sh)) }()
		}
		log.Printf("data node #%d listening on %s", s.ID(), config.DataAddr())

		// Spin up the collectd server
		if config.Collectd.Enabled {
			c := config.Collectd
			cs := collectd.NewServer(s, c.TypesDB)
			cs.Database = c.Database
			err := collectd.ListenAndServe(cs, c.ConnectionString(config.BindAddress))
			if err != nil {
				log.Printf("failed to start collectd Server: %v\n", err.Error())
			}
		}
		// Spin up any Graphite servers
		for _, c := range config.Graphites {
			if !c.Enabled {
				continue
			}

			// Configure Graphite parsing.
			parser := graphite.NewParser()
			parser.Separator = c.NameSeparatorString()
			parser.LastEnabled = c.LastEnabled()

			// Start the relevant server.
			if strings.ToLower(c.Protocol) == "tcp" {
				g := graphite.NewTCPServer(parser, s)
				g.Database = c.Database
				err := g.ListenAndServe(c.ConnectionString(config.BindAddress))
				if err != nil {
					log.Printf("failed to start TCP Graphite Server: %v\n", err.Error())
				}
			} else if strings.ToLower(c.Protocol) == "udp" {
				g := graphite.NewUDPServer(parser, s)
				g.Database = c.Database
				err := g.ListenAndServe(c.ConnectionString(config.BindAddress))
				if err != nil {
					log.Printf("failed to start UDP Graphite Server: %v\n", err.Error())
				}
			} else {
				log.Fatalf("unrecognized Graphite Server prototcol %s", c.Protocol)
			}
		}
	}

	// Wait indefinitely.
	<-(chan struct{})(nil)
}

// write the current process id to a file specified by path.
func writePIDFile(path string) {
	if path == "" {
		return
	}

	// Ensure the required directory structure exists.
	err := os.MkdirAll(filepath.Dir(path), 0700)
	if err != nil {
		log.Fatal(err)
	}

	// Retrieve the PID and write it.
	pid := strconv.Itoa(os.Getpid())
	if err := ioutil.WriteFile(path, []byte(pid), 0644); err != nil {
		log.Fatal(err)
	}
}

// parses the configuration from a given path. Sets overrides as needed.
func parseConfig(path, hostname string) *Config {
	if path == "" {
		log.Println("No config provided, using default settings")
		return NewConfig()
	}

	// Parse configuration.
	config, err := ParseConfigFile(path)
	if err != nil {
		log.Fatalf("config: %s", err)
	}

	// Override config properties.
	if hostname != "" {
		config.Hostname = hostname
	}

	return config
}

// creates and initializes a broker.
func openBroker(path string, u *url.URL, initializing bool, joinURLs []*url.URL, w io.Writer) *messaging.Broker {
	// Ignore if there's no existing broker and we're not initializing or joining.
	if !fileExists(path) && !initializing && len(joinURLs) == 0 {
		return nil
	}

	// Create broker.
	b := messaging.NewBroker()
	b.SetLogOutput(w)
	if err := b.Open(path, u); err != nil {
		log.Fatalf("failed to open broker: %s", err)
	}

	// If this is a new broker then we can initialie two ways:
	//   1) Start a brand new cluster.
	//   2) Join an existing cluster.
	if initializing {
		if len(joinURLs) == 0 {
			initializeBroker(b)
		} else {
			joinBroker(b, joinURLs)
		}
	}

	return b
}

// initializes a new broker.
func initializeBroker(b *messaging.Broker) {
	if err := b.Initialize(); err != nil {
		log.Fatalf("initialize: %s", err)
	}
}

// joins a broker to an existing cluster.
func joinBroker(b *messaging.Broker, joinURLs []*url.URL) {
	// Attempts to join each server until successful.
	for _, u := range joinURLs {
		if err := b.Join(u); err != nil {
			log.Printf("join: failed to connect to broker: %s: %s", u, err)
		} else {
			log.Printf("join: connected broker to %s", u)
			return
		}
	}
	log.Fatalf("join: failed to connect broker to any specified server")
}

// creates and initializes a server.
func openServer(path string, u *url.URL, b *messaging.Broker, initializing, configExists bool, joinURLs []*url.URL, w io.Writer) *influxdb.Server {
	// Ignore if there's no existing server and we're not initializing or joining.
	if !fileExists(path) && !initializing && len(joinURLs) == 0 {
		return nil
	}

	// Create and open the server.
	s := influxdb.NewServer()
	s.SetLogOutput(w)
	if err := s.Open(path); err != nil {
		log.Fatalf("failed to open data server: %v", err.Error())
	}

	// If the server is uninitialized then initialize or join it.
	if initializing {
		if len(joinURLs) == 0 {
			initializeServer(s, b, w)
		} else {
			joinServer(s, u, joinURLs)
			openServerClient(s, joinURLs, w)
		}
	} else if !configExists {
		// We are spining up a server that has no config,
		// but already has an initialized data directory
		joinURLs = []*url.URL{b.URL()}
		openServerClient(s, joinURLs, w)
	} else {
		if len(joinURLs) == 0 {
			// If a config exists, but no joinUrls are specified, fall back to the broker URL
			// TODO: Make sure we have a leader, and then spin up the server
			joinURLs = []*url.URL{b.URL()}
		}
		openServerClient(s, joinURLs, w)
	}

	return s
}

// initializes a new server that does not yet have an ID.
func initializeServer(s *influxdb.Server, b *messaging.Broker, w io.Writer) {
	// TODO: Create replica using the messaging client.

	// Create replica on broker.
	if err := b.CreateReplica(1); err != nil {
		log.Fatalf("replica creation error: %s", err)
	}

	// Create messaging client.
	c := messaging.NewClient(1)
	c.SetLogOutput(w)
	if err := c.Open(filepath.Join(s.Path(), messagingClientFile), []*url.URL{b.URL()}); err != nil {
		log.Fatalf("messaging client error: %s", err)
	}
	if err := s.SetClient(c); err != nil {
		log.Fatalf("set client error: %s", err)
	}

	// Initialize the server.
	if err := s.Initialize(b.URL()); err != nil {
		log.Fatalf("server initialization error: %s", err)
	}
}

// joins a server to an existing cluster.
func joinServer(s *influxdb.Server, u *url.URL, joinURLs []*url.URL) {
	// TODO: Use separate broker and data join urls.

	// Create data node on an existing data node.
	for _, joinURL := range joinURLs {
		if err := s.Join(u, joinURL); err != nil {
			log.Printf("join: failed to connect data node: %s: %s", u, err)
		} else {
			log.Printf("join: connected data node to %s", u)
			return
		}
	}
	log.Fatalf("join: failed to connect data node to any specified server")
}

// opens the messaging client and attaches it to the server.
func openServerClient(s *influxdb.Server, joinURLs []*url.URL, w io.Writer) {
	c := messaging.NewClient(s.ID())
	c.SetLogOutput(w)
	if err := c.Open(filepath.Join(s.Path(), messagingClientFile), joinURLs); err != nil {
		log.Fatalf("messaging client error: %s", err)
	}
	if err := s.SetClient(c); err != nil {
		log.Fatalf("set client error: %s", err)
	}
}

// parses a comma-delimited list of URLs.
func parseURLs(s string) (a []*url.URL) {
	if s == "" {
		return nil
	}

	for _, s := range strings.Split(s, ",") {
		u, err := url.Parse(s)
		if err != nil {
			log.Fatalf("cannot parse urls: %s", err)
		}
		a = append(a, u)
	}
	return
}

// returns true if the file exists.
func fileExists(path string) bool {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return false
	}
	return true
}

func printRunUsage() {
	log.Printf(`usage: run [flags]

run starts the broker and data node server. If this is the first time running
the command then a new cluster will be initialized unless the -join argument
is used.

        -config <path>
                          Set the path to the configuration file.

        -hostname <name>
                          Override the hostname, the 'hostname' configuration
                          option will be overridden.

        -join <url>
                          Joins the server to an existing cluster.

        -pidfile <path>
                          Write process ID to a file.
`)
}
<commit_msg>Remove superfluous blank line from help output<commit_after>package main

import (
	"flag"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/influxdb/influxdb"
	"github.com/influxdb/influxdb/collectd"
	"github.com/influxdb/influxdb/graphite"
	"github.com/influxdb/influxdb/httpd"
	"github.com/influxdb/influxdb/messaging"
)

// execRun runs the "run" command.
func execRun(args []string) {
	// Parse command flags.
	fs := flag.NewFlagSet("", flag.ExitOnError)
	var (
		configPath = fs.String("config", "", "")
		pidPath    = fs.String("pidfile", "", "")
		hostname   = fs.String("hostname", "", "")
		join       = fs.String("join", "", "")
	)
	fs.Usage = printRunUsage
	fs.Parse(args)

	// Print sweet InfluxDB logo and write the process id to file.
	log.Print(logo)
	log.SetPrefix(`[influxd] `)
	log.SetFlags(log.LstdFlags)
	writePIDFile(*pidPath)

	// Parse the configuration and determine if a broker and/or server exist.
	config := parseConfig(*configPath, *hostname)
	configExists := *configPath != ""
	initializing := !fileExists(config.BrokerDir()) && !fileExists(config.DataDir())

	// Create a logging writer.
	logWriter := os.Stderr
	if config.Logging.File != "" {
		var err error
		logWriter, err = os.OpenFile(config.Logging.File, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0660)
		if err != nil {
			log.Fatalf("unable to open log file %s: %s", config.Logging.File, err.Error())
		}
	}
	log.SetOutput(logWriter)

	// Parse join urls from the --join flag.
	var joinURLs []*url.URL
	if *join == "" {
		joinURLs = parseURLs(config.JoinURLs())
	} else {
		joinURLs = parseURLs(*join)
	}

	// Mark the start of the log.
	log.Printf("influxd starting up")

	// Open broker, initialize or join as necessary.
	b := openBroker(config.BrokerDir(), config.BrokerURL(), initializing, joinURLs, logWriter)

	// Start the broker handler.
	var h *Handler
	if b != nil {
		h = &Handler{brokerHandler: messaging.NewHandler(b)}
		go func() { log.Fatal(http.ListenAndServe(config.BrokerAddr(), h)) }()
		log.Printf("broker listening on %s", config.BrokerAddr())
	}

	// Open server, initialize or join as necessary.
	s := openServer(config.DataDir(), config.DataURL(), b, initializing, configExists, joinURLs, logWriter)

	// Start the server handler. Attach to broker if listening on the same port.
	if s != nil {
		sh := httpd.NewHandler(s, config.Authentication.Enabled, version)
		if h != nil && config.BrokerAddr() == config.DataAddr() {
			h.serverHandler = sh
		} else {
			go func() { log.Fatal(http.ListenAndServe(config.DataAddr(), sh)) }()
		}
		log.Printf("data node #%d listening on %s", s.ID(), config.DataAddr())

		// Spin up the collectd server
		if config.Collectd.Enabled {
			c := config.Collectd
			cs := collectd.NewServer(s, c.TypesDB)
			cs.Database = c.Database
			err := collectd.ListenAndServe(cs, c.ConnectionString(config.BindAddress))
			if err != nil {
				log.Printf("failed to start collectd Server: %v\n", err.Error())
			}
		}
		// Spin up any Graphite servers
		for _, c := range config.Graphites {
			if !c.Enabled {
				continue
			}

			// Configure Graphite parsing.
			parser := graphite.NewParser()
			parser.Separator = c.NameSeparatorString()
			parser.LastEnabled = c.LastEnabled()

			// Start the relevant server.
			if strings.ToLower(c.Protocol) == "tcp" {
				g := graphite.NewTCPServer(parser, s)
				g.Database = c.Database
				err := g.ListenAndServe(c.ConnectionString(config.BindAddress))
				if err != nil {
					log.Printf("failed to start TCP Graphite Server: %v\n", err.Error())
				}
			} else if strings.ToLower(c.Protocol) == "udp" {
				g := graphite.NewUDPServer(parser, s)
				g.Database = c.Database
				err := g.ListenAndServe(c.ConnectionString(config.BindAddress))
				if err != nil {
					log.Printf("failed to start UDP Graphite Server: %v\n", err.Error())
				}
			} else {
				log.Fatalf("unrecognized Graphite Server prototcol %s", c.Protocol)
			}
		}
	}

	// Wait indefinitely.
	<-(chan struct{})(nil)
}

// write the current process id to a file specified by path.
func writePIDFile(path string) {
	if path == "" {
		return
	}

	// Ensure the required directory structure exists.
	err := os.MkdirAll(filepath.Dir(path), 0700)
	if err != nil {
		log.Fatal(err)
	}

	// Retrieve the PID and write it.
	pid := strconv.Itoa(os.Getpid())
	if err := ioutil.WriteFile(path, []byte(pid), 0644); err != nil {
		log.Fatal(err)
	}
}

// parses the configuration from a given path. Sets overrides as needed.
func parseConfig(path, hostname string) *Config {
	if path == "" {
		log.Println("No config provided, using default settings")
		return NewConfig()
	}

	// Parse configuration.
	config, err := ParseConfigFile(path)
	if err != nil {
		log.Fatalf("config: %s", err)
	}

	// Override config properties.
	if hostname != "" {
		config.Hostname = hostname
	}

	return config
}

// creates and initializes a broker.
func openBroker(path string, u *url.URL, initializing bool, joinURLs []*url.URL, w io.Writer) *messaging.Broker {
	// Ignore if there's no existing broker and we're not initializing or joining.
	if !fileExists(path) && !initializing && len(joinURLs) == 0 {
		return nil
	}

	// Create broker.
	b := messaging.NewBroker()
	b.SetLogOutput(w)
	if err := b.Open(path, u); err != nil {
		log.Fatalf("failed to open broker: %s", err)
	}

	// If this is a new broker then we can initialie two ways:
	//   1) Start a brand new cluster.
	//   2) Join an existing cluster.
	if initializing {
		if len(joinURLs) == 0 {
			initializeBroker(b)
		} else {
			joinBroker(b, joinURLs)
		}
	}

	return b
}

// initializes a new broker.
func initializeBroker(b *messaging.Broker) {
	if err := b.Initialize(); err != nil {
		log.Fatalf("initialize: %s", err)
	}
}

// joins a broker to an existing cluster.
func joinBroker(b *messaging.Broker, joinURLs []*url.URL) {
	// Attempts to join each server until successful.
	for _, u := range joinURLs {
		if err := b.Join(u); err != nil {
			log.Printf("join: failed to connect to broker: %s: %s", u, err)
		} else {
			log.Printf("join: connected broker to %s", u)
			return
		}
	}
	log.Fatalf("join: failed to connect broker to any specified server")
}

// creates and initializes a server.
func openServer(path string, u *url.URL, b *messaging.Broker, initializing, configExists bool, joinURLs []*url.URL, w io.Writer) *influxdb.Server {
	// Ignore if there's no existing server and we're not initializing or joining.
	if !fileExists(path) && !initializing && len(joinURLs) == 0 {
		return nil
	}

	// Create and open the server.
	s := influxdb.NewServer()
	s.SetLogOutput(w)
	if err := s.Open(path); err != nil {
		log.Fatalf("failed to open data server: %v", err.Error())
	}

	// If the server is uninitialized then initialize or join it.
	if initializing {
		if len(joinURLs) == 0 {
			initializeServer(s, b, w)
		} else {
			joinServer(s, u, joinURLs)
			openServerClient(s, joinURLs, w)
		}
	} else if !configExists {
		// We are spining up a server that has no config,
		// but already has an initialized data directory
		joinURLs = []*url.URL{b.URL()}
		openServerClient(s, joinURLs, w)
	} else {
		if len(joinURLs) == 0 {
			// If a config exists, but no joinUrls are specified, fall back to the broker URL
			// TODO: Make sure we have a leader, and then spin up the server
			joinURLs = []*url.URL{b.URL()}
		}
		openServerClient(s, joinURLs, w)
	}

	return s
}

// initializes a new server that does not yet have an ID.
func initializeServer(s *influxdb.Server, b *messaging.Broker, w io.Writer) {
	// TODO: Create replica using the messaging client.

	// Create replica on broker.
	if err := b.CreateReplica(1); err != nil {
		log.Fatalf("replica creation error: %s", err)
	}

	// Create messaging client.
	c := messaging.NewClient(1)
	c.SetLogOutput(w)
	if err := c.Open(filepath.Join(s.Path(), messagingClientFile), []*url.URL{b.URL()}); err != nil {
		log.Fatalf("messaging client error: %s", err)
	}
	if err := s.SetClient(c); err != nil {
		log.Fatalf("set client error: %s", err)
	}

	// Initialize the server.
	if err := s.Initialize(b.URL()); err != nil {
		log.Fatalf("server initialization error: %s", err)
	}
}

// joins a server to an existing cluster.
func joinServer(s *influxdb.Server, u *url.URL, joinURLs []*url.URL) {
	// TODO: Use separate broker and data join urls.

	// Create data node on an existing data node.
	for _, joinURL := range joinURLs {
		if err := s.Join(u, joinURL); err != nil {
			log.Printf("join: failed to connect data node: %s: %s", u, err)
		} else {
			log.Printf("join: connected data node to %s", u)
			return
		}
	}
	log.Fatalf("join: failed to connect data node to any specified server")
}

// opens the messaging client and attaches it to the server.
func openServerClient(s *influxdb.Server, joinURLs []*url.URL, w io.Writer) {
	c := messaging.NewClient(s.ID())
	c.SetLogOutput(w)
	if err := c.Open(filepath.Join(s.Path(), messagingClientFile), joinURLs); err != nil {
		log.Fatalf("messaging client error: %s", err)
	}
	if err := s.SetClient(c); err != nil {
		log.Fatalf("set client error: %s", err)
	}
}

// parses a comma-delimited list of URLs.
func parseURLs(s string) (a []*url.URL) {
	if s == "" {
		return nil
	}

	for _, s := range strings.Split(s, ",") {
		u, err := url.Parse(s)
		if err != nil {
			log.Fatalf("cannot parse urls: %s", err)
		}
		a = append(a, u)
	}
	return
}

// returns true if the file exists.
func fileExists(path string) bool {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return false
	}
	return true
}

func printRunUsage() {
	log.Printf(`usage: run [flags]

run starts the broker and data node server. If this is the first time running
the command then a new cluster will be initialized unless the -join argument
is used.

        -config <path>
                          Set the path to the configuration file.

        -hostname <name>
                          Override the hostname, the 'hostname' configuration
                          option will be overridden.

        -join <url>
                          Joins the server to an existing cluster.

        -pidfile <path>
                          Write process ID to a file.
`)
}
<|endoftext|>