<commit_before>package server

import (
	"fmt"
	"log"
	"net/http"
	"strconv"

	"github.com/gorilla/mux"
	"github.com/thomasjpfan/docker-scaler/service"
)

// Server runs service that scales docker services
type Server struct {
	scaler service.ScalerServicer
	logger *log.Logger
}

// NewServer creates Server
func NewServer(
	scaler service.ScalerServicer,
	logger *log.Logger) *Server {
	return &Server{
		scaler: scaler,
		logger: logger,
	}
}

// MakeRouter routes url paths to handlers
func (s *Server) MakeRouter() *mux.Router {
	m := mux.NewRouter()
	m.Path("/scale").
		Queries("service", "{service}", "delta", "{delta}").
		Methods("POST").
		HandlerFunc(s.ScaleService).
		Name("ScaleService")
	return m
}

// Run starts server
func (s *Server) Run(port uint16) {
	address := fmt.Sprintf(":%d", port)
	m := s.MakeRouter()
	log.Fatal(http.ListenAndServe(address, m))
}

// ScaleService scales service
func (s *Server) ScaleService(w http.ResponseWriter, r *http.Request) {

	q := r.URL.Query()
	serviceID := q.Get("service")
	deltaStr := q.Get("delta")
	delta, err := strconv.Atoi(deltaStr)

	s.logger.Printf("Request to scale service: %s, delta: %s", serviceID, deltaStr)

	if err != nil {
		message := fmt.Sprintf("Incorrect delta query: %v", deltaStr)
		respondWithError(w, http.StatusBadRequest, message)
		s.logger.Print(message)
		return
	}

	// Get scaled service
	minReplicas, maxReplicas, err := s.scaler.GetMinMaxReplicas(serviceID)
	if err != nil {
		respondWithError(w, http.StatusInternalServerError, err.Error())
		s.logger.Print(err.Error())
		return
	}

	replicas, err := s.scaler.GetReplicas(serviceID)
	if err != nil {
		respondWithError(w, http.StatusInternalServerError, err.Error())
		s.logger.Print(err.Error())
		return
	}
	newReplicasInt := int(replicas) + delta

	if newReplicasInt <= 0 {
		message := fmt.Sprintf("Delta %d results in a negative number of replicas for service: %s", delta, serviceID)
		respondWithError(w, http.StatusBadRequest, message)
		s.logger.Print(message)
		return
	}

	newReplicas := uint64(newReplicasInt)
	if newReplicas > maxReplicas {
		message := fmt.Sprintf("%s is already scaled to the maximum number of %d replicas", serviceID, maxReplicas)
		respondWithError(w, http.StatusPreconditionFailed, message)
		s.logger.Print(message)
		return
	} else if newReplicas < minReplicas {
		message := fmt.Sprintf("%s is already descaled to the minimum number of %d replicas", serviceID, minReplicas)
		respondWithError(w, http.StatusPreconditionFailed, message)
		return
	}

	err = s.scaler.SetReplicas(serviceID, newReplicas)
	if err != nil {
		respondWithError(w, http.StatusInternalServerError, err.Error())
		return
	}
	message := fmt.Sprintf("Scaling %s to %d replicas", serviceID, newReplicas)
	s.logger.Print(message)
	respondWithJSON(w, http.StatusOK, Response{Status: "OK", Message: message})
}
<commit_msg>RFC: Error messages<commit_after>package server

import (
	"fmt"
	"log"
	"net/http"
	"strconv"

	"github.com/gorilla/mux"
	"github.com/thomasjpfan/docker-scaler/service"
)

// Server runs service that scales docker services
type Server struct {
	scaler service.ScalerServicer
	logger *log.Logger
}

// NewServer creates Server
func NewServer(
	scaler service.ScalerServicer,
	logger *log.Logger) *Server {
	return &Server{
		scaler: scaler,
		logger: logger,
	}
}

// MakeRouter routes url paths to handlers
func (s *Server) MakeRouter() *mux.Router {
	m := mux.NewRouter()
	m.Path("/scale").
		Queries("service", "{service}", "delta", "{delta}").
		Methods("POST").
		HandlerFunc(s.ScaleService).
		Name("ScaleService")
	return m
}

// Run starts server
func (s *Server) Run(port uint16) {
	address := fmt.Sprintf(":%d", port)
	m := s.MakeRouter()
	log.Fatal(http.ListenAndServe(address, m))
}

// ScaleService scales service
func (s *Server) ScaleService(w http.ResponseWriter, r *http.Request) {

	q := r.URL.Query()
	serviceID := q.Get("service")
	deltaStr := q.Get("delta")
	delta, err := strconv.Atoi(deltaStr)

	s.logger.Printf("Request to scale service: %s, delta: %s", serviceID, deltaStr)

	if err != nil {
		message := fmt.Sprintf("Incorrect delta query: %v", deltaStr)
		respondWithError(w, http.StatusBadRequest, message)
		s.logger.Print(message)
		return
	}

	// Get scaled service
	minReplicas, maxReplicas, err := s.scaler.GetMinMaxReplicas(serviceID)
	if err != nil {
		message := err.Error()
		respondWithError(w, http.StatusInternalServerError, message)
		s.logger.Print(message)
		return
	}

	replicas, err := s.scaler.GetReplicas(serviceID)
	if err != nil {
		message := err.Error()
		respondWithError(w, http.StatusInternalServerError, message)
		s.logger.Print(message)
		return
	}
	newReplicasInt := int(replicas) + delta

	if newReplicasInt <= 0 {
		message := fmt.Sprintf("Delta %d results in a negative number of replicas for service: %s", delta, serviceID)
		respondWithError(w, http.StatusBadRequest, message)
		s.logger.Print(message)
		return
	}

	newReplicas := uint64(newReplicasInt)
	if newReplicas > maxReplicas {
		message := fmt.Sprintf("%s is already scaled to the maximum number of %d replicas", serviceID, maxReplicas)
		respondWithError(w, http.StatusPreconditionFailed, message)
		s.logger.Print(message)
		return
	} else if newReplicas < minReplicas {
		message := fmt.Sprintf("%s is already descaled to the minimum number of %d replicas", serviceID, minReplicas)
		respondWithError(w, http.StatusPreconditionFailed, message)
		return
	}

	err = s.scaler.SetReplicas(serviceID, newReplicas)
	if err != nil {
		message := err.Error()
		respondWithError(w, http.StatusInternalServerError, message)
		s.logger.Print(message)
		return
	}
	message := fmt.Sprintf("Scaling %s to %d replicas", serviceID, newReplicas)
	s.logger.Print(message)
	respondWithJSON(w, http.StatusOK, Response{Status: "OK", Message: message})
}
<|endoftext|>


<commit_before>package server

import (
	"context"
	"io/ioutil"
	"net"
	"net/http"
	"reflect"
	"strings"
	"time"

	"github.com/getlantern/errors"
	"github.com/getlantern/golog"
	"github.com/getlantern/proxy"
	"github.com/getlantern/proxy/filters"
	"github.com/getlantern/tlsdefaults"

	"github.com/getlantern/http-proxy/buffers"
	"github.com/getlantern/http-proxy/listeners"
)

var (
	testingLocal = false
	log          = golog.LoggerFor("server")
)

type listenerGenerator func(net.Listener) net.Listener

// Opts are used to configure a Server
type Opts struct {
	IdleTimeout time.Duration
	Filter      filters.Filter
	Dial        proxy.DialFunc
}

// Server is an HTTP proxy server.
type Server struct {
	// Allow is a function that determines whether or not to allow connections
	// from the given IP address. If unspecified, all connections are allowed.
	Allow              func(string) bool
	proxy              proxy.Proxy
	listenerGenerators []listenerGenerator
}

// New constructs a new HTTP proxy server using the given options
func New(opts *Opts) *Server {
	p, _ := proxy.New(&proxy.Opts{
		IdleTimeout:        opts.IdleTimeout,
		Dial:               opts.Dial,
		Filter:             opts.Filter,
		BufferSource:       buffers.Pool(),
		OKWaitsForUpstream: true,
		OnError: func(ctx filters.Context, req *http.Request, read bool, err error) *http.Response {
			status := http.StatusBadGateway
			if read {
				status = http.StatusBadRequest
			}
			return &http.Response{
				Request:    req,
				StatusCode: status,
				Body:       ioutil.NopCloser(strings.NewReader(err.Error())),
			}
		},
	})
	return &Server{
		proxy: p,
	}
}

func (s *Server) AddListenerWrappers(listenerGens ...listenerGenerator) {
	for _, g := range listenerGens {
		s.listenerGenerators = append(s.listenerGenerators, g)
	}
}

func (s *Server) ListenAndServeHTTP(addr string, readyCb func(addr string)) error {
	listener, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	log.Debugf("Listen http on %s", addr)
	return s.serve(s.wrapListenerIfNecessary(listener), readyCb)
}

func (s *Server) ListenAndServeHTTPS(addr, keyfile, certfile string, readyCb func(addr string)) error {
	l, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	listener, err := tlsdefaults.NewListener(s.wrapListenerIfNecessary(l), keyfile, certfile)
	if err != nil {
		return err
	}
	log.Debugf("Listen https on %s", addr)
	return s.serve(listener, readyCb)
}

func (s *Server) Serve(listener net.Listener, readyCb func(addr string)) error {
	return s.serve(s.wrapListenerIfNecessary(listener), readyCb)
}

func (s *Server) serve(listener net.Listener, readyCb func(addr string)) error {
	l := listeners.NewDefaultListener(listener)

	for _, wrap := range s.listenerGenerators {
		l = wrap(l)
	}

	if readyCb != nil {
		readyCb(l.Addr().String())
	}

	var tempDelay time.Duration // how long to sleep on accept failure
	for {
		conn, err := l.Accept()
		if err != nil {
			if ne, ok := err.(net.Error); ok && ne.Temporary() {
				// delay code based on net/http.Server
				if tempDelay == 0 {
					tempDelay = 5 * time.Millisecond
				} else {
					tempDelay *= 2
				}
				if max := 1 * time.Second; tempDelay > max {
					tempDelay = max
				}
				log.Errorf("http: Accept error: %v; retrying in %v", err, tempDelay)
				time.Sleep(tempDelay)
				continue
			}
			return errors.New("Error accepting: %v", err)
		}
		tempDelay = 0
		s.handle(conn)
	}
}

func (s *Server) handle(conn net.Conn) {
	wrapConn, isWrapConn := conn.(listeners.WrapConn)
	if isWrapConn {
		wrapConn.OnState(http.StateNew)
	}
	go s.doHandle(conn, isWrapConn, wrapConn)
}

func (s *Server) doHandle(conn net.Conn, isWrapConn bool, wrapConn listeners.WrapConn) {
	defer func() {
		p := recover()
		if p != nil {
			log.Errorf("Caught panic handling connection: %v", p)
			safeClose(conn)
		}
	}()

	err := s.proxy.Handle(context.Background(), conn, conn)
	if err != nil {
		log.Errorf("Error handling connection: %v", err)
	}
	if isWrapConn {
		wrapConn.OnState(http.StateClosed)
	}
}

func safeClose(conn net.Conn) {
	defer func() {
		p := recover()
		if p != nil {
			log.Errorf("Panic on closing connection: %v", p)
		}
	}()

	conn.Close()
}

func (s *Server) wrapListenerIfNecessary(l net.Listener) net.Listener {
	if s.Allow != nil {
		log.Debug("Wrapping listener with Allow")
		return &allowinglistener{l, s.Allow}
	}
	return l
}

type allowinglistener struct {
	wrapped net.Listener
	allow   func(string) bool
}

func (l *allowinglistener) Accept() (net.Conn, error) {
	conn, err := l.wrapped.Accept()
	if err != nil {
		return conn, err
	}

	ip := ""
	remoteAddr := conn.RemoteAddr()
	switch addr := remoteAddr.(type) {
	case *net.TCPAddr:
		ip = addr.IP.String()
	case *net.UDPAddr:
		ip = addr.IP.String()
	default:
		log.Errorf("Remote addr %v is of unknown type %v, unable to determine IP", remoteAddr, reflect.TypeOf(remoteAddr))
		return conn, err
	}
	if !l.allow(ip) {
		conn.Close()
		// Note - we don't return an error, because that causes http.Server to stop
		// serving.
	}

	return conn, err
}

func (l *allowinglistener) Close() error {
	return l.wrapped.Close()
}

func (l *allowinglistener) Addr() net.Addr {
	return l.wrapped.Addr()
}
<commit_msg>Logging address of remote connection when there's a problem handling it<commit_after>package server

import (
	"context"
	"io/ioutil"
	"net"
	"net/http"
	"reflect"
	"strings"
	"time"

	"github.com/getlantern/errors"
	"github.com/getlantern/golog"
	"github.com/getlantern/proxy"
	"github.com/getlantern/proxy/filters"
	"github.com/getlantern/tlsdefaults"

	"github.com/getlantern/http-proxy/buffers"
	"github.com/getlantern/http-proxy/listeners"
)

var (
	testingLocal = false
	log          = golog.LoggerFor("server")
)

type listenerGenerator func(net.Listener) net.Listener

// Opts are used to configure a Server
type Opts struct {
	IdleTimeout time.Duration
	Filter      filters.Filter
	Dial        proxy.DialFunc
}

// Server is an HTTP proxy server.
type Server struct {
	// Allow is a function that determines whether or not to allow connections
	// from the given IP address. If unspecified, all connections are allowed.
	Allow              func(string) bool
	proxy              proxy.Proxy
	listenerGenerators []listenerGenerator
}

// New constructs a new HTTP proxy server using the given options
func New(opts *Opts) *Server {
	p, _ := proxy.New(&proxy.Opts{
		IdleTimeout:        opts.IdleTimeout,
		Dial:               opts.Dial,
		Filter:             opts.Filter,
		BufferSource:       buffers.Pool(),
		OKWaitsForUpstream: true,
		OnError: func(ctx filters.Context, req *http.Request, read bool, err error) *http.Response {
			status := http.StatusBadGateway
			if read {
				status = http.StatusBadRequest
			}
			return &http.Response{
				Request:    req,
				StatusCode: status,
				Body:       ioutil.NopCloser(strings.NewReader(err.Error())),
			}
		},
	})
	return &Server{
		proxy: p,
	}
}

func (s *Server) AddListenerWrappers(listenerGens ...listenerGenerator) {
	for _, g := range listenerGens {
		s.listenerGenerators = append(s.listenerGenerators, g)
	}
}

func (s *Server) ListenAndServeHTTP(addr string, readyCb func(addr string)) error {
	listener, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	log.Debugf("Listen http on %s", addr)
	return s.serve(s.wrapListenerIfNecessary(listener), readyCb)
}

func (s *Server) ListenAndServeHTTPS(addr, keyfile, certfile string, readyCb func(addr string)) error {
	l, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	listener, err := tlsdefaults.NewListener(s.wrapListenerIfNecessary(l), keyfile, certfile)
	if err != nil {
		return err
	}
	log.Debugf("Listen https on %s", addr)
	return s.serve(listener, readyCb)
}

func (s *Server) Serve(listener net.Listener, readyCb func(addr string)) error {
	return s.serve(s.wrapListenerIfNecessary(listener), readyCb)
}

func (s *Server) serve(listener net.Listener, readyCb func(addr string)) error {
	l := listeners.NewDefaultListener(listener)

	for _, wrap := range s.listenerGenerators {
		l = wrap(l)
	}

	if readyCb != nil {
		readyCb(l.Addr().String())
	}

	var tempDelay time.Duration // how long to sleep on accept failure
	for {
		conn, err := l.Accept()
		if err != nil {
			if ne, ok := err.(net.Error); ok && ne.Temporary() {
				// delay code based on net/http.Server
				if tempDelay == 0 {
					tempDelay = 5 * time.Millisecond
				} else {
					tempDelay *= 2
				}
				if max := 1 * time.Second; tempDelay > max {
					tempDelay = max
				}
				log.Errorf("http: Accept error: %v; retrying in %v", err, tempDelay)
				time.Sleep(tempDelay)
				continue
			}
			return errors.New("Error accepting: %v", err)
		}
		tempDelay = 0
		s.handle(conn)
	}
}

func (s *Server) handle(conn net.Conn) {
	wrapConn, isWrapConn := conn.(listeners.WrapConn)
	if isWrapConn {
		wrapConn.OnState(http.StateNew)
	}
	go s.doHandle(conn, isWrapConn, wrapConn)
}

func (s *Server) doHandle(conn net.Conn, isWrapConn bool, wrapConn listeners.WrapConn) {
	defer func() {
		p := recover()
		if p != nil {
			log.Errorf("Caught panic handling connection from %v: %v", conn.RemoteAddr(), p)
			safeClose(conn)
		}
	}()

	err := s.proxy.Handle(context.Background(), conn, conn)
	if err != nil {
		log.Errorf("Error handling connection from %v: %v", conn.RemoteAddr(), err)
	}
	if isWrapConn {
		wrapConn.OnState(http.StateClosed)
	}
}

func safeClose(conn net.Conn) {
	defer func() {
		p := recover()
		if p != nil {
			log.Errorf("Panic on closing connection from %v: %v", conn.RemoteAddr(), p)
		}
	}()

	conn.Close()
}

func (s *Server) wrapListenerIfNecessary(l net.Listener) net.Listener {
	if s.Allow != nil {
		log.Debug("Wrapping listener with Allow")
		return &allowinglistener{l, s.Allow}
	}
	return l
}

type allowinglistener struct {
	wrapped net.Listener
	allow   func(string) bool
}

func (l *allowinglistener) Accept() (net.Conn, error) {
	conn, err := l.wrapped.Accept()
	if err != nil {
		return conn, err
	}

	ip := ""
	remoteAddr := conn.RemoteAddr()
	switch addr := remoteAddr.(type) {
	case *net.TCPAddr:
		ip = addr.IP.String()
	case *net.UDPAddr:
		ip = addr.IP.String()
	default:
		log.Errorf("Remote addr %v is of unknown type %v, unable to determine IP", remoteAddr, reflect.TypeOf(remoteAddr))
		return conn, err
	}
	if !l.allow(ip) {
		conn.Close()
		// Note - we don't return an error, because that causes http.Server to stop
		// serving.
	}

	return conn, err
}

func (l *allowinglistener) Close() error {
	return l.wrapped.Close()
}

func (l *allowinglistener) Addr() net.Addr {
	return l.wrapped.Addr()
}
<|endoftext|>


<commit_before>package server

import (
	"crypto/tls"
	"github.com/gabstv/sandpiper/pathtree"
	"github.com/gabstv/sandpiper/route"
	"github.com/gabstv/sandpiper/util"
	"golang.org/x/crypto/acme/autocert"
	"log"
	"net/http"
	"os"
	"runtime"
	"time"
)

type Server struct {
	Cfg         Config
	trieDomains *pathtree.Trie
	domains     map[string]*route.Route
	Logger      *log.Logger
}

func Default() *Server {
	s := &Server{}
	s.trieDomains = pathtree.NewTrie(".")
	s.domains = make(map[string]*route.Route, 0)
	s.Logger = log.New(os.Stderr, "[sp server] ", log.LstdFlags)
	return s
}

func (s *Server) DebugLog() {

}

func (s *Server) Add(r route.Route) error {
	rr := &route.Route{}
	*rr = r
	if rr.WsCFG.ReadBufferSize == 0 {
		rr.WsCFG.ReadBufferSize = 2048
	}
	if rr.WsCFG.WriteBufferSize == 0 {
		rr.WsCFG.WriteBufferSize = 2048
	}
	if rr.WsCFG.ReadDeadlineSeconds == 0 {
		rr.WsCFG.ReadDeadlineSeconds = time.Second * 60
	} else {
		if rr.WsCFG.ReadDeadlineSeconds < time.Millisecond {
			rr.WsCFG.ReadDeadlineSeconds = time.Duration(rr.WsCFG.ReadDeadlineSeconds) * time.Second
		}
	}
	err := s.trieDomains.Add(r.Domain, rr)
	if err != nil {
		return err
	}
	s.domains[r.Domain] = rr
	return nil
}

func (s *Server) Run() error {
	s.Init()
	errc := make(chan error, 3)
	go func() {
		errc <- http.ListenAndServe(s.Cfg.ListenAddr, s)
	}()

	// Autocert
	autocdomains := make([]string, 0)
	for _, v := range s.domains {
		if v.Autocert {
			autocdomains = append(autocdomains, v.Domain)
		}
	}
	var sv *http.Server
	if len(autocdomains) > 0 {
		m := autocert.Manager{
			Prompt:     autocert.AcceptTOS,
			HostPolicy: autocert.HostWhitelist(autocdomains...),
		}
		sv = &http.Server{
			Addr:      s.Cfg.ListenAddrTLS,
			TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
		}
	} else {
		sv = &http.Server{
			Addr: s.Cfg.ListenAddrTLS,
		}
	}

	sv.Handler = s
	certs := make([]util.Certificate, 0, len(s.domains))
	for _, v := range s.domains {
		if v.Certificate.CertFile != "" {
			certs = append(certs, v.Certificate)
		}
	}
	if len(certs) > 0 || len(autocdomains) > 0 {
		go func() {
			errc <- util.ListenAndServeTLSSNI(sv, certs)
		}()
	}
	err := <-errc
	return err
}

func (s *Server) Init() {
	// first start setting the number of cpu cores to use
	ncpu := runtime.NumCPU()
	if s.Cfg.NumCPU > 0 && s.Cfg.NumCPU < ncpu {
		ncpu = s.Cfg.NumCPU
	}
	runtime.GOMAXPROCS(ncpu)
}

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h := r.Host
	if s.Cfg.Debug {
		if ho := r.Header.Get("X-Sandpiper-Host"); ho != "" {
			h = ho
		}
		s.Logger.Println("Host: " + h)
	}
	res := s.trieDomains.Find(h)
	if res == nil {
		if len(s.Cfg.FallbackDomain) > 0 {
			if s.Cfg.Debug {
				s.Logger.Println("FALLBACK DOMAIN", s.Cfg.FallbackDomain)
			}
			dom := s.domains[s.Cfg.FallbackDomain]
			if dom != nil {
				dom.ReverseProxy(w, r)
				return
			}
		} else {
			if s.Cfg.Debug {
				s.Logger.Println("DOMAIN NOT FOUND")
			}
			http.Error(w, "domain not found "+h, http.StatusInternalServerError)
			return
		}
	}
	if res.EndRoute == nil {
		if s.Cfg.Debug {
			s.Logger.Println("ROUTE IS NULL")
		}
		http.Error(w, "route is null", http.StatusInternalServerError)
		return
	}
	res.EndRoute.ReverseProxy(w, r)
}
<commit_msg>use both auto certs and original certs<commit_after>package server

import (
	"crypto/tls"
	"github.com/gabstv/sandpiper/pathtree"
	"github.com/gabstv/sandpiper/route"
	"github.com/gabstv/sandpiper/util"
	"golang.org/x/crypto/acme/autocert"
	"log"
	"net/http"
	"os"
	"runtime"
	"time"
)

type Server struct {
	Cfg         Config
	trieDomains *pathtree.Trie
	domains     map[string]*route.Route
	Logger      *log.Logger
}

func Default() *Server {
	s := &Server{}
	s.trieDomains = pathtree.NewTrie(".")
	s.domains = make(map[string]*route.Route, 0)
	s.Logger = log.New(os.Stderr, "[sp server] ", log.LstdFlags)
	return s
}

func (s *Server) DebugLog() {

}

func (s *Server) Add(r route.Route) error {
	rr := &route.Route{}
	*rr = r
	if rr.WsCFG.ReadBufferSize == 0 {
		rr.WsCFG.ReadBufferSize = 2048
	}
	if rr.WsCFG.WriteBufferSize == 0 {
		rr.WsCFG.WriteBufferSize = 2048
	}
	if rr.WsCFG.ReadDeadlineSeconds == 0 {
		rr.WsCFG.ReadDeadlineSeconds = time.Second * 60
	} else {
		if rr.WsCFG.ReadDeadlineSeconds < time.Millisecond {
			rr.WsCFG.ReadDeadlineSeconds = time.Duration(rr.WsCFG.ReadDeadlineSeconds) * time.Second
		}
	}
	err := s.trieDomains.Add(r.Domain, rr)
	if err != nil {
		return err
	}
	s.domains[r.Domain] = rr
	return nil
}

func (s *Server) Run() error {
	s.Init()
	errc := make(chan error, 3)
	go func() {
		errc <- http.ListenAndServe(s.Cfg.ListenAddr, s)
	}()

	// Autocert
	autocdomains := make([]string, 0)
	for _, v := range s.domains {
		if v.Autocert {
			autocdomains = append(autocdomains, v.Domain)
		}
	}
	var sv *http.Server
	if len(autocdomains) > 0 {
		m := autocert.Manager{
			Prompt:     autocert.AcceptTOS,
			HostPolicy: autocert.HostWhitelist(autocdomains...),
		}

		certs := make(map[string]tls.Certificate)
		for k, v := range s.domains {
			if !v.Autocert && len(v.Certificate.KeyFile) > 0 && len(v.Certificate.CertFile) > 0 {
				ncert, err := tls.LoadX509KeyPair(v.Certificate.CertFile, v.Certificate.KeyFile)
				if err != nil {
					s.Logger.Println("Error loading certificate for", k, err.Error())
				} else {
					certs[k] = ncert
				}
			}
		}

		haveLoveWillGetCert := func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
			s.Logger.Println("cert host is", clientHello.ServerName)
			if dom, ok := certs[clientHello.ServerName]; ok {
				return &dom, nil
			}
			return m.GetCertificate(clientHello)
		}
		sv = &http.Server{
			Addr:      s.Cfg.ListenAddrTLS,
			TLSConfig: &tls.Config{GetCertificate: haveLoveWillGetCert},
		}
	} else {
		sv = &http.Server{
			Addr: s.Cfg.ListenAddrTLS,
		}
	}

	sv.Handler = s
	certs := make([]util.Certificate, 0, len(s.domains))
	for _, v := range s.domains {
		if v.Certificate.CertFile != "" {
			certs = append(certs, v.Certificate)
		}
	}
	if len(certs) > 0 || len(autocdomains) > 0 {
		go func() {
			errc <- util.ListenAndServeTLSSNI(sv, certs)
		}()
	}
	err := <-errc
	return err
}

func (s *Server) Init() {
	// first start setting the number of cpu cores to use
	ncpu := runtime.NumCPU()
	if s.Cfg.NumCPU > 0 && s.Cfg.NumCPU < ncpu {
		ncpu = s.Cfg.NumCPU
	}
	runtime.GOMAXPROCS(ncpu)
}

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h := r.Host
	if s.Cfg.Debug {
		if ho := r.Header.Get("X-Sandpiper-Host"); ho != "" {
			h = ho
		}
		s.Logger.Println("Host: " + h)
	}
	res := s.trieDomains.Find(h)
	if res == nil {
		if len(s.Cfg.FallbackDomain) > 0 {
			if s.Cfg.Debug {
				s.Logger.Println("FALLBACK DOMAIN", s.Cfg.FallbackDomain)
			}
			dom := s.domains[s.Cfg.FallbackDomain]
			if dom != nil {
				dom.ReverseProxy(w, r)
				return
			}
		} else {
			if s.Cfg.Debug {
				s.Logger.Println("DOMAIN NOT FOUND")
			}
			http.Error(w, "domain not found "+h, http.StatusInternalServerError)
			return
		}
	}
	if res.EndRoute == nil {
		if s.Cfg.Debug {
			s.Logger.Println("ROUTE IS NULL")
		}
		http.Error(w, "route is null", http.StatusInternalServerError)
		return
	}
	res.EndRoute.ReverseProxy(w, r)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/distribution/manifest/schema1"
	"github.com/docker/docker/cliconfig"
	"github.com/docker/engine-api/types"
	"github.com/jessfraz/reg/registry"
	"github.com/urfave/cli"
)

const (
	// VERSION is the binary version.
	VERSION = "v0.1.0"

	dockerConfigPath = ".docker/config.json"
)

var (
	updating = false
)

// preload initializes any global options and configuration
// before the main or sub commands are run.
func preload(c *cli.Context) (err error) {
	if c.GlobalBool("debug") {
		logrus.SetLevel(logrus.DebugLevel)
	}

	return nil
}

func main() {
	app := cli.NewApp()
	app.Name = "reg-server"
	app.Version = VERSION
	app.Author = "@jessfraz"
	app.Email = "no-reply@butts.com"
	app.Usage = "Docker registry v2 static UI server."
	app.Before = preload
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "debug, d",
			Usage: "run in debug mode",
		},
		cli.StringFlag{
			Name:  "username, u",
			Usage: "username for the registry",
		},
		cli.StringFlag{
			Name:  "password, p",
			Usage: "password for the registry",
		},
		cli.StringFlag{
			Name:  "registry, r",
			Usage: "URL to the provate registry (ex. r.j3ss.co)",
		},
		cli.StringFlag{
			Name:  "port",
			Value: "8080",
			Usage: "port for server to run on",
		},
		cli.StringFlag{
			Name:  "cert",
			Usage: "path to ssl cert",
		},
		cli.StringFlag{
			Name:  "key",
			Usage: "path to ssl key",
		},
		cli.StringFlag{
			Name:  "interval",
			Value: "5m",
			Usage: "interval to generate new index.html's at",
		},
	}
	app.Action = func(c *cli.Context) error {
		auth, err := getAuthConfig(c)
		if err != nil {
			return err
		}

		// create the registry client
		r, err := registry.New(auth, c.GlobalBool("debug"))
		if err != nil {
			return err
		}

		// get the path to the static directory
		wd, err := os.Getwd()
		if err != nil {
			return err
		}
		staticDir := filepath.Join(wd, "static")

		// create the initial index
		if err := createStaticIndex(r, staticDir); err != nil {
			return err
		}

		// parse the duration
		dur, err := time.ParseDuration(c.String("interval"))
		if err != nil {
			logrus.Fatalf("parsing %s as duration failed: %v", c.String("interval"), err)
		}
		ticker := time.NewTicker(dur)

		go func() {
			// create more indexes every X minutes based off interval
			for range ticker.C {
				if !updating {
					if err := createStaticIndex(r, staticDir); err != nil {
						logrus.Warnf("creating static index failed: %v", err)
					}
				}
			}
		}()

		// create mux server
		mux := http.NewServeMux()

		// static files handler
		staticHandler := http.FileServer(http.Dir(staticDir))
		mux.Handle("/", staticHandler)
		// TODO: add handler for individual repos

		// set up the server
		port := c.String("port")
		server := &http.Server{
			Addr:    ":" + port,
			Handler: mux,
		}
		logrus.Infof("Starting server on port %q", port)
		if c.String("cert") != "" && c.String("key") != "" {
			logrus.Fatal(server.ListenAndServeTLS(c.String("cert"), c.String("key")))
		} else {
			logrus.Fatal(server.ListenAndServe())
		}

		return nil
	}

	app.Run(os.Args)
}

func getAuthConfig(c *cli.Context) (types.AuthConfig, error) {
	if c.GlobalString("username") != "" && c.GlobalString("password") != "" && c.GlobalString("registry") != "" {
		return types.AuthConfig{
			Username:      c.GlobalString("username"),
			Password:      c.GlobalString("password"),
			ServerAddress: c.GlobalString("registry"),
		}, nil
	}

	dcfg, err := cliconfig.Load(cliconfig.ConfigDir())
	if err != nil {
		return types.AuthConfig{}, fmt.Errorf("Loading config file failed: %v", err)
	}

	// return error early if there are no auths saved
	if !dcfg.ContainsAuth() {
		if c.GlobalString("registry") != "" {
			return types.AuthConfig{
				ServerAddress: c.GlobalString("registry"),
			}, nil
		}
		return types.AuthConfig{}, fmt.Errorf("No auth was present in %s, please pass a registry, username, and password", cliconfig.ConfigDir())
	}

	// if they passed a specific registry, return those creds _if_ they exist
	if c.GlobalString("registry") != "" {
		if creds, ok := dcfg.AuthConfigs[c.GlobalString("registry")]; ok {
			return creds, nil
		}
		return types.AuthConfig{}, fmt.Errorf("No authentication credentials exist for %s", c.GlobalString("registry"))
	}

	// set the auth config as the registryURL, username and Password
	for _, creds := range dcfg.AuthConfigs {
		return creds, nil
	}

	return types.AuthConfig{}, fmt.Errorf("Could not find any authentication credentials")
}

func getRepoAndRef(c *cli.Context) (repo, ref string, err error) {
	if len(c.Args()) < 1 {
		return "", "", errors.New("pass the name of the repository")
	}

	arg := c.Args()[0]
	parts := []string{}
	if strings.Contains(arg, "@") {
		parts = strings.Split(c.Args()[0], "@")
	} else if strings.Contains(arg, ":") {
		parts = strings.Split(c.Args()[0], ":")
	} else {
		parts = []string{arg}
	}

	repo = parts[0]
	ref = "latest"
	if len(parts) > 1 {
		ref = parts[1]
	}

	return
}

type data struct {
	RegistryURL string
	LastUpdated string
	Repos       []repository
}

type repository struct {
	Name        string
	Tag         string
	RepoURI     string
	CreatedDate string
}

type v1Compatibility struct {
	ID      string    `json:"id"`
	Created time.Time `json:"created"`
}

func createStaticIndex(r *registry.Registry, staticDir string) error {
	updating = true
	logrus.Info("fetching catalog")
	repoList, err := r.Catalog()
	if err != nil {
		return fmt.Errorf("getting catalog failed: %v", err)
	}

	logrus.Info("fetching tags")
	var repos []repository
	for _, repo := range repoList {
		// get the tags
		tags, err := r.Tags(repo)
		if err != nil {
			return fmt.Errorf("getting tags for %s failed: %v", repo, err)
		}
		for _, tag := range tags {
			// get the manifest

			manifest, err := r.Manifest(repo, tag)
			if err != nil {
				return fmt.Errorf("getting tags for %s:%s failed: %v", repo, tag, err)
			}

			var createdDate string
			if m1, ok := manifest.(schema1.SignedManifest); ok {
				history := m1.History
				for _, h := range history {
					var comp v1Compatibility
					if err := json.Unmarshal([]byte(h.V1Compatibility), &comp); err != nil {
						return fmt.Errorf("unmarshal v1compatibility failed: %v", err)
					}
					createdDate = comp.Created.Format(time.RFC1123)
					break
				}
			}

			repoURI := fmt.Sprintf("%s/%s", r.Domain, repo)
			if tag != "latest" {
				repoURI += ":" + tag
			}

			repos = append(repos, repository{
				Name:        repo,
				Tag:         tag,
				RepoURI:     repoURI,
				CreatedDate: createdDate,
			})
		}
	}

	// create temporoary file to save template to
	logrus.Info("creating temporary file for template")
	f, err := ioutil.TempFile("", "reg-server")
	if err != nil {
		return fmt.Errorf("creating temp file failed: %v", err)
	}
	defer f.Close()
	defer os.Remove(f.Name())

	// parse & execute the template
	logrus.Info("parsing and executing the template")
	templateDir := filepath.Join(staticDir, "../templates")
	lp := filepath.Join(templateDir, "layout.html")

	d := data{
		RegistryURL: r.Domain,
		Repos:       repos,
		LastUpdated: time.Now().Format(time.RFC1123),
	}
	tmpl := template.Must(template.New("").ParseFiles(lp))
	if err := tmpl.ExecuteTemplate(f, "layout", d); err != nil {
		return fmt.Errorf("execute template failed: %v", err)
	}
	f.Close()

	logrus.Info("renaming the temporary file to index.html")
	index := filepath.Join(staticDir, "index.html")
	if err := os.Rename(f.Name(), index); err != nil {
		return fmt.Errorf("renaming result from %s to %s failed: %v", f.Name(), index, err)
	}
	updating = false
	return nil
}
<commit_msg>prettydate<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/distribution/manifest/schema1"
	"github.com/docker/docker/cliconfig"
	"github.com/docker/engine-api/types"
	"github.com/jessfraz/reg/registry"
	"github.com/urfave/cli"
)

const (
	// VERSION is the binary version.
	VERSION = "v0.1.0"

	dockerConfigPath = ".docker/config.json"
)

var (
	updating = false
)

// preload initializes any global options and configuration
// before the main or sub commands are run.
func preload(c *cli.Context) (err error) {
	if c.GlobalBool("debug") {
		logrus.SetLevel(logrus.DebugLevel)
	}

	return nil
}

func main() {
	app := cli.NewApp()
	app.Name = "reg-server"
	app.Version = VERSION
	app.Author = "@jessfraz"
	app.Email = "no-reply@butts.com"
	app.Usage = "Docker registry v2 static UI server."
	app.Before = preload
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "debug, d",
			Usage: "run in debug mode",
		},
		cli.StringFlag{
			Name:  "username, u",
			Usage: "username for the registry",
		},
		cli.StringFlag{
			Name:  "password, p",
			Usage: "password for the registry",
		},
		cli.StringFlag{
			Name:  "registry, r",
			Usage: "URL to the provate registry (ex. r.j3ss.co)",
		},
		cli.StringFlag{
			Name:  "port",
			Value: "8080",
			Usage: "port for server to run on",
		},
		cli.StringFlag{
			Name:  "cert",
			Usage: "path to ssl cert",
		},
		cli.StringFlag{
			Name:  "key",
			Usage: "path to ssl key",
		},
		cli.StringFlag{
			Name:  "interval",
			Value: "5m",
			Usage: "interval to generate new index.html's at",
		},
	}
	app.Action = func(c *cli.Context) error {
		auth, err := getAuthConfig(c)
		if err != nil {
			return err
		}

		// create the registry client
		r, err := registry.New(auth, c.GlobalBool("debug"))
		if err != nil {
			return err
		}

		// get the path to the static directory
		wd, err := os.Getwd()
		if err != nil {
			return err
		}
		staticDir := filepath.Join(wd, "static")

		// create the initial index
		if err := createStaticIndex(r, staticDir); err != nil {
			return err
		}

		// parse the duration
		dur, err := time.ParseDuration(c.String("interval"))
		if err != nil {
			logrus.Fatalf("parsing %s as duration failed: %v", c.String("interval"), err)
		}
		ticker := time.NewTicker(dur)

		go func() {
			// create more indexes every X minutes based off interval
			for range ticker.C {
				if !updating {
					if err := createStaticIndex(r, staticDir); err != nil {
						logrus.Warnf("creating static index failed: %v", err)
					}
				}
			}
		}()

		// create mux server
		mux := http.NewServeMux()

		// static files handler
		staticHandler := http.FileServer(http.Dir(staticDir))
		mux.Handle("/", staticHandler)
		// TODO: add handler for individual repos

		// set up the server
		port := c.String("port")
		server := &http.Server{
			Addr:    ":" + port,
			Handler: mux,
		}
		logrus.Infof("Starting server on port %q", port)
		if c.String("cert") != "" && c.String("key") != "" {
			logrus.Fatal(server.ListenAndServeTLS(c.String("cert"), c.String("key")))
		} else {
			logrus.Fatal(server.ListenAndServe())
		}

		return nil
	}

	app.Run(os.Args)
}

func getAuthConfig(c *cli.Context) (types.AuthConfig, error) {
	if c.GlobalString("username") != "" && c.GlobalString("password") != "" && c.GlobalString("registry") != "" {
		return types.AuthConfig{
			Username:      c.GlobalString("username"),
			Password:      c.GlobalString("password"),
			ServerAddress: c.GlobalString("registry"),
		}, nil
	}

	dcfg, err := cliconfig.Load(cliconfig.ConfigDir())
	if err != nil {
		return types.AuthConfig{}, fmt.Errorf("Loading config file failed: %v", err)
	}

	// return error early if there are no auths saved
	if !dcfg.ContainsAuth() {
		if c.GlobalString("registry") != "" {
			return types.AuthConfig{
				ServerAddress: c.GlobalString("registry"),
			}, nil
		}
		return types.AuthConfig{}, fmt.Errorf("No auth was present in %s, please pass a registry, username, and password", cliconfig.ConfigDir())
	}

	// if they passed a specific registry, return those creds _if_ they exist
	if c.GlobalString("registry") != "" {
		if creds, ok := dcfg.AuthConfigs[c.GlobalString("registry")]; ok {
			return creds, nil
		}
		return types.AuthConfig{}, fmt.Errorf("No authentication credentials exist for %s", c.GlobalString("registry"))
	}

	// set the auth config as the registryURL, username and Password
	for _, creds := range dcfg.AuthConfigs {
		return creds, nil
	}

	return types.AuthConfig{}, fmt.Errorf("Could not find any authentication credentials")
}

func getRepoAndRef(c *cli.Context) (repo, ref string, err error) {
	if len(c.Args()) < 1 {
		return "", "", errors.New("pass the name of the repository")
	}

	arg := c.Args()[0]
	parts := []string{}
	if strings.Contains(arg, "@") {
		parts = strings.Split(c.Args()[0], "@")
	} else if strings.Contains(arg, ":") {
		parts = strings.Split(c.Args()[0], ":")
	} else {
		parts = []string{arg}
	}

	repo = parts[0]
	ref = "latest"
	if len(parts) > 1 {
		ref = parts[1]
	}

	return
}

type data struct {
	RegistryURL string
	LastUpdated string
	Repos       []repository
}

type repository struct {
	Name        string
	Tag         string
	RepoURI     string
	CreatedDate string
}

type v1Compatibility struct {
	ID      string    `json:"id"`
	Created time.Time `json:"created"`
}

func createStaticIndex(r *registry.Registry, staticDir string) error {
	updating = true
	logrus.Info("fetching catalog")
	repoList, err := r.Catalog()
	if err != nil {
		return fmt.Errorf("getting catalog failed: %v", err)
	}

	logrus.Info("fetching tags")
	var repos []repository
	for _, repo := range repoList {
		// get the tags
		tags, err := r.Tags(repo)
		if err != nil {
			return fmt.Errorf("getting tags for %s failed: %v", repo, err)
		}
		for _, tag := range tags {
			// get the manifest

			manifest, err := r.Manifest(repo, tag)
			if err != nil {
				return fmt.Errorf("getting tags for %s:%s failed: %v", repo, tag, err)
			}

			var createdDate string
			if m1, ok := manifest.(schema1.SignedManifest); ok {
				history := m1.History
				for _, h := range history {
					var comp v1Compatibility
					if err := json.Unmarshal([]byte(h.V1Compatibility), &comp); err != nil {
						return fmt.Errorf("unmarshal v1compatibility failed: %v", err)
					}
					createdDate = comp.Created.String()
					break
				}
			}

			repoURI := fmt.Sprintf("%s/%s", r.Domain, repo)
			if tag != "latest" {
				repoURI += ":" + tag
			}

			repos = append(repos, repository{
				Name:        repo,
				Tag:         tag,
				RepoURI:     repoURI,
				CreatedDate: createdDate,
			})
		}
	}

	// create temporoary file to save template to
	logrus.Info("creating temporary file for template")
	f, err := ioutil.TempFile("", "reg-server")
	if err != nil {
		return fmt.Errorf("creating temp file failed: %v", err)
	}
	defer f.Close()
	defer os.Remove(f.Name())

	// parse & execute the template
	logrus.Info("parsing and executing the template")
	templateDir := filepath.Join(staticDir, "../templates")
	lp := filepath.Join(templateDir, "layout.html")

	d := data{
		RegistryURL: r.Domain,
		Repos:       repos,
		LastUpdated: time.Now().Format(time.RFC1123),
	}
	tmpl := template.Must(template.New("").ParseFiles(lp))
	if err := tmpl.ExecuteTemplate(f, "layout", d); err != nil {
		return fmt.Errorf("execute template failed: %v", err)
	}
	f.Close()

	logrus.Info("renaming the temporary file to index.html")
	index := filepath.Join(staticDir, "index.html")
	if err := os.Rename(f.Name(), index); err != nil {
		return fmt.Errorf("renaming result from %s to %s failed: %v", f.Name(), index, err)
	}
	updating = false
	return nil
}
<|endoftext|>


<commit_before>package server

import (
	"encoding/json"
	"errors"
	"log"
	"net"
	"sync"
	"time"
)

const (
	RCV_BUFFER_SIZE = 4096
	SND_BUFFER_SIZE = 4096
)

type ServerConfig struct {
	Host             string
	MaxGame          int
	MaxPlayerPerGame int
}

type Server struct {
	sync.Mutex
	Config ServerConfig
	Games  GameList
}

func New(config ServerConfig) *Server {
	return &Server{
		Config: config,
	}
}

func (s *Server) Listen() {
	listener, err := net.Listen("tcp", s.Config.Host)
	if err != nil {
		log.Fatal(err.Error())
	}
	log.Println("Listening for connection on ", listener.Addr().String())

	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println(err.Error())
		}

		go s.acceptConn(conn)
	}
}

func (s *Server) acceptConn(conn net.Conn) {
	buf := make([]byte, RCV_BUFFER_SIZE)

	numBytes, err := conn.Read(buf)
	if err != nil {
		//We won't accept this incomming connection
		log.Println(err.Error())
		conn.Close()
		return
	}

	r := StandardRequest{}
	if err = json.Unmarshal(buf[:numBytes], &r); err != nil {
		conn.Write(getErrorResponse(err.Error()))
		conn.Close()
		return
	}

	var reply []byte
	closeConn := false

	switch r.Request {
	case RequestListGame:
		reply = getListGameResponse(s.listGame())
		closeConn = true
	case RequestCreateGame:
		gameId, err := s.newGame(r, conn)
		if err != nil {
			reply = getErrorResponse(err.Error())
			closeConn = true
		} else {
			reply = getGameCreatedResponse(gameId)
		}
	case RequestJoinGame:
		err = s.joinGame(r, conn)
		if err != nil {
			reply = getErrorResponse(err.Error())
			closeConn = true
		} else {
			reply = getOkResponse()
		}
	default:
		reply = getErrorResponse("invalid request")
		closeConn = true
	}

	conn.Write(reply)
	if closeConn {
		conn.Close()
	}
}

func (s *Server) newGame(req StandardRequest, conn net.Conn) (int, error) {
	if len(s.Games) >= s.Config.MaxGame {
		return 0, errors.New("Server is full")
	}

	gn, found := req.Param["name"]
	if !found {
		return 0, errors.New("Missing parameter: name")
	}

	gameName, ok := gn.(string)
	if !ok {
		return 0, errors.New("Invalid parameter: name must be a string")
	}

	bn, found := req.Param["botName"]
	if !found {
		return 0, errors.New("Missing parameter: botName")
	}

	botName, ok := bn.(string)
	if !ok {
		return 0, errors.New("Invalid parameter: botName must be a string")
	}

	width := BOARD_DEFAULT_WIDTH
	height := BOARD_DEFAULT_HEIGHT

	if wi, found := req.Param["width"]; found {
		w, ok := wi.(float64)
		if !ok {
			return 0, errors.New("Invalid parameter: width must be numeric")
		}
		width = int(w)
	}

	if hi, found := req.Param["height"]; found {
		h, ok := hi.(float64)
		if !ok {
			return 0, errors.New("Invalid parameter: height must be numeric")
		}
		height = int(h)
	}

	s.Lock()
	defer s.Unlock()
	g := NewGame(gameName, width, height)
	p := NewPlayer(botName, conn)
	g.Players = append(g.Players, p)
	g.Board.Spawn(p.Bot)

	s.Games = append(s.Games, g)

	go func(g *Game) {
		log.Printf("Game %s will start in %s", g.Name, GAME_DEFAULT_START_TIME)
		time.Sleep(GAME_DEFAULT_START_TIME)
		log.Printf("Game %s is starting", g.Name)
		g.Start()
	}(g)

	return len(s.Games), nil
}

func (s *Server) listGame() []GameDescription {
	var games []GameDescription

	s.Lock()
	defer s.Unlock()

	for id, game := range s.Games {
		d := GameDescription{
			Id:   id + 1,
			Name: game.Name,
		}

		for _, p := range game.Players {
			d.Bots = append(d.Bots, p.Bot.Name)
		}

		games = append(games, d)
	}

	return games
}

func (s *Server) joinGame(req StandardRequest, conn net.Conn) error {

	id, found := req.Param["gameId"]
	if !found {
		return errors.New("Missing parameter: gameId")
	}

	gameId, ok := id.(float64)
	if !ok {
		return errors.New("Invalid parameter: gameId must be numeric")
	}

	bn, found := req.Param["botName"]
	if !found {
		return errors.New("Missing parameter: botName")
	}

	botName, ok := bn.(string)
	if !ok {
		errors.New("Invalid parameter: botName must be a string")
	}

	if gameId <= 0 {
		return errors.New("Invalid parameter: gameId must be > 0")
	}

	game := s.Games[int(gameId)-1]
	if game == nil {
		return errors.New("Invalid parameter: game does not exist")
	}

	game.Lock()
	defer game.Unlock()

	if game.InProgress {
		return errors.New("Game is already started")
	}

	if len(game.Players) >= s.Config.MaxPlayerPerGame {
		return errors.New("Game is full")
	}

	p := NewPlayer(botName, conn)
	game.Players = append(game.Players, p)
	game.Board.Spawn(p.Bot)

	return nil
}
<commit_msg>Ensured that listGame return game with remaining spot<commit_after>package server

import (
	"encoding/json"
	"errors"
	"log"
	"net"
	"sync"
	"time"
)

const (
	RCV_BUFFER_SIZE = 4096
	SND_BUFFER_SIZE = 4096
)

type ServerConfig struct {
	Host             string
	MaxGame          int
	MaxPlayerPerGame int
}

type Server struct {
	sync.Mutex
	Config ServerConfig
	Games  GameList
}

func New(config ServerConfig) *Server {
	return &Server{
		Config: config,
	}
}

func (s *Server) Listen() {
	listener, err := net.Listen("tcp", s.Config.Host)
	if err != nil {
		log.Fatal(err.Error())
	}
	log.Println("Listening for connection on ", listener.Addr().String())

	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println(err.Error())
		}

		go s.acceptConn(conn)
	}
}

func (s *Server) acceptConn(conn net.Conn) {
	buf := make([]byte, RCV_BUFFER_SIZE)

	numBytes, err := conn.Read(buf)
	if err != nil {
		//We won't accept this incomming connection
		log.Println(err.Error())
		conn.Close()
		return
	}

	r := StandardRequest{}
	if err = json.Unmarshal(buf[:numBytes], &r); err != nil {
		conn.Write(getErrorResponse(err.Error()))
		conn.Close()
		return
	}

	var reply []byte
	closeConn := false

	switch r.Request {
	case RequestListGame:
		reply = getListGameResponse(s.listGame())
		closeConn = true
	case RequestCreateGame:
		gameId, err := s.newGame(r, conn)
		if err != nil {
			reply = getErrorResponse(err.Error())
			closeConn = true
		} else {
			reply = getGameCreatedResponse(gameId)
		}
	case RequestJoinGame:
		err = s.joinGame(r, conn)
		if err != nil {
			reply = getErrorResponse(err.Error())
			closeConn = true
		} else {
			reply = getOkResponse()
		}
	default:
		reply = getErrorResponse("invalid request")
		closeConn = true
	}

	conn.Write(reply)
	if closeConn {
		conn.Close()
	}
}

func (s *Server) newGame(req StandardRequest, conn net.Conn) (int, error) {
	if len(s.Games) >= s.Config.MaxGame {
		return 0, errors.New("Server is full")
	}

	gn, found := req.Param["name"]
	if !found {
		return 0, errors.New("Missing parameter: name")
	}

	gameName, ok := gn.(string)
	if !ok {
		return 0, errors.New("Invalid parameter: name must be a string")
	}

	bn, found := req.Param["botName"]
	if !found {
		return 0, errors.New("Missing parameter: botName")
	}

	botName, ok := bn.(string)
	if !ok {
		return 0, errors.New("Invalid parameter: botName must be a string")
	}

	width := BOARD_DEFAULT_WIDTH
	height := BOARD_DEFAULT_HEIGHT

	if wi, found := req.Param["width"]; found {
		w, ok := wi.(float64)
		if !ok {
			return 0, errors.New("Invalid parameter: width must be numeric")
		}
		width = int(w)
	}

	if hi, found := req.Param["height"]; found {
		h, ok := hi.(float64)
		if !ok {
			return 0, errors.New("Invalid parameter: height must be numeric")
		}
		height = int(h)
	}

	s.Lock()
	defer s.Unlock()
	g := NewGame(gameName, width, height)
	p := NewPlayer(botName, conn)
	g.Players = append(g.Players, p)
	g.Board.Spawn(p.Bot)

	s.Games = append(s.Games, g)

	go func(g *Game) {
		log.Printf("Game %s will start in %s", g.Name, GAME_DEFAULT_START_TIME)
		time.Sleep(GAME_DEFAULT_START_TIME)
		log.Printf("Game %s is starting", g.Name)
		g.Start()
	}(g)

	return len(s.Games), nil
}

func (s *Server) listGame() []GameDescription {
	var games []GameDescription

	s.Lock()
	defer s.Unlock()

	for id, game := range s.Games {
		if len(game.Players) >= s.Config.MaxPlayerPerGame {
			continue
		}

		d := GameDescription{
			Id:   id + 1,
			Name: game.Name,
		}

		for _, p := range game.Players {
			d.Bots = append(d.Bots, p.Bot.Name)
		}

		games = append(games, d)
	}

	return games
}

func (s *Server) joinGame(req StandardRequest, conn net.Conn) error {

	id, found := req.Param["gameId"]
	if !found {
		return errors.New("Missing parameter: gameId")
	}

	gameId, ok := id.(float64)
	if !ok {
		return errors.New("Invalid parameter: gameId must be numeric")
	}

	bn, found := req.Param["botName"]
	if !found {
		return errors.New("Missing parameter: botName")
	}

	botName, ok := bn.(string)
	if !ok {
		errors.New("Invalid parameter: botName must be a string")
	}

	if gameId <= 0 {
		return errors.New("Invalid parameter: gameId must be > 0")
	}

	game := s.Games[int(gameId)-1]
	if game == nil {
		return errors.New("Invalid parameter: game does not exist")
	}

	game.Lock()
	defer game.Unlock()

	if game.InProgress {
		return errors.New("Game is already started")
	}

	if len(game.Players) >= s.Config.MaxPlayerPerGame {
		return errors.New("Game is full")
	}

	p := NewPlayer(botName, conn)
	game.Players = append(game.Players, p)
	game.Board.Spawn(p.Bot)

	return nil
}
<|endoftext|>


<commit_before>// trasrv
package main

import (
	"errors"
	"log"
	"net/rpc"
	"net/http"
	"os"

	"github.com/ssbl/trago/db"
)

type TraSrv int

var (
	localDb     *db.TraDb
	initialized bool
)

const (
	PORT = ":8999"
)

func (t *TraSrv) GetDb(args *int, reply *db.TraDb) error {
	if !initialized {
		return errors.New("uninitialized")
	} else {
		*reply = *localDb
		return nil
	}
}

func (t *TraSrv) PutDb(args *db.TraDb, reply *int) error {
	localDb = &db.TraDb{}
	*localDb = *args

	if err := localDb.UpdateMTimes(); err != nil {
		return err
	}

	return localDb.Write()
}

func (t *TraSrv) PutFile(data *db.FileData, reply *int) error {
	file, err := os.Create(data.Name)
	if err != nil {
		return err
	}
	defer file.Close()

	_, err = file.Write(data.Data)
	return err
}

func (t *TraSrv) RemoveFile(filename *string, reply *int) error {
	return os.Remove(*filename)
}

func (t *TraSrv) ShowConflict(filename *string, reply *int) error {
	log.Printf("Conflict: %s\n", *filename)
	return nil
}

func (t *TraSrv) InitDb(dir *string, reply *int) error {
	err := os.Chdir(*dir)
	if err != nil {
		return err
	}

	localDb, err = db.ParseFile()
	if err == db.FileNotFound {
		initialized = true
		return nil
	} else if err != nil {
		return err
	}

	localDb.VersionVec[localDb.ReplicaId] += 1

	log.Println(localDb)
	if err := localDb.Update(); err != nil {
		return err
	}

	initialized = true
	return nil
}

func main() {
	trasrv := new(TraSrv)
	rpc.Register(trasrv)
	rpc.HandleHTTP()

	if (len(os.Args) != 2) {
		log.Fatal("No port specified")
	}

	port := ":" + os.Args[1]

	http.Handle("/files/", http.StripPrefix("/files/",
		http.FileServer(http.Dir("."))))
	log.Fatal(http.ListenAndServe(port, nil))
}
<commit_msg>remove unused file server.go<commit_after><|endoftext|>


<commit_before>package server

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"github.com/labstack/gommon/log"
	"github.com/mrdoob/glsl-sandbox/server/store"
)

const (
	pathGallery = "./server/assets/gallery.html"
	pathThumbs  = "./data/thumbs"
	perPage     = 50
)

var (
	ErrInvalidData = fmt.Errorf("invalid data")
)

type Template struct {
	templates *template.Template
}

func (t *Template) Render(
	w io.Writer, name string, data interface{}, c echo.Context,
) error {
	return t.templates.ExecuteTemplate(w, name, data)
}

type galleryEffect struct {
	ID      int
	Version int
	Image   string
}

type galleryData struct {
	Effects      []galleryEffect
	IsPrevious   bool
	PreviousPage int
	IsNext       bool
	NextPage     int
}

type Server struct {
	echo     *echo.Echo
	template *Template
	store    store.Store
}

func New(s store.Store) *Server {
	return &Server{
		echo: echo.New(),
		template: &Template{
			templates: template.Must(template.ParseFiles(pathGallery)),
		},
		store: s,
	}
}

func (s *Server) Start() error {
	s.setup()
	return s.echo.Start("localhost:8888")
}

func (s *Server) setup() {
	s.echo.Renderer = s.template
	s.echo.Logger.SetLevel(log.DEBUG)
	s.echo.Use(middleware.Logger())
	s.routes()
}

func (s *Server) routes() {
	s.echo.GET("/", s.indexHandler)
	s.echo.GET("/e", s.effectHandler)
	s.echo.POST("/e", s.saveHandler)
	s.echo.GET("/item/:id", s.itemHandler)

	s.echo.Static("/thumbs", "./data/thumbs")
	s.echo.Static("/css", "./server/assets/css")
	s.echo.Static("/js", "./server/assets/js")
}

func (s *Server) indexHandler(c echo.Context) error {
	pString := c.QueryParam("page")
	if pString == "" {
		pString = "0"
	}
	page, err := strconv.Atoi(pString)
	if err != nil {
		page = 0
	}

	p, err := s.store.Page(page, perPage, false)
	if err != nil {
		return c.String(http.StatusInternalServerError, "error")
	}

	effects := make([]galleryEffect, len(p))
	for i, e := range p {
		effects[i] = galleryEffect{
			ID:      e.ID,
			Version: len(e.Versions) - 1,
			Image:   path.Join("/thumbs", e.ImageName()),
		}
	}

	println(page, len(effects), perPage)
	d := galleryData{
		Effects:      effects,
		IsNext:       len(effects) == perPage,
		NextPage:     page + 1,
		IsPrevious:   page > 0,
		PreviousPage: page - 1,
	}

	err = c.Render(http.StatusOK, "gallery", d)
	if err != nil {
		panic(err)
	}
	return err
}

func (s *Server) effectHandler(c echo.Context) error {
	return c.File("./static/index.html")
}

type itemResponse struct {
	Code   string `json:"code"`
	User   string `json:"user"`
	Parent string `json:"parent,omitempty"`
}

func (s *Server) itemHandler(c echo.Context) error {
	param := c.Param("id")
	id, version, err := idVersion(param)
	if err != nil {
		return c.String(http.StatusBadRequest, "{}")
	}

	effect, err := s.store.Effect(id)
	if err != nil {
		return c.String(http.StatusBadRequest, "{}")
	}

	if version >= len(effect.Versions) || version < 0 {
		return c.String(http.StatusNotFound, "{}")
	}

	parent := ""
	if effect.Parent > 0 {
		parent = fmt.Sprintf("/e#%d.%d", effect.Parent, effect.ParentVersion)
	}

	item := itemResponse{
		Code:   effect.Versions[version].Code,
		User:   effect.User,
		Parent: parent,
	}

	data, err := json.Marshal(item)
	if err != nil {
		return c.String(http.StatusInternalServerError, "{}")
	}

	return c.Blob(http.StatusOK, "application/json", data)
}

type saveQuery struct {
	Code   string `json:"Code"`
	Image  string `json:"Image"`
	User   string `json:"User"`
	CodeID string `json:"CodeID"`
	Parent string `json:"Parent"`
}

func (s *Server) saveHandler(c echo.Context) error {
	if c.Request().Body == nil {
		c.Logger().Errorf("empty body")
		return c.String(http.StatusBadRequest, "")
	}

	data, err := io.ReadAll(c.Request().Body)
	if err != nil {
		c.Logger().Errorf("could not read body: %s", err.Error())
		return c.String(http.StatusInternalServerError, "")
	}

	var save saveQuery
	err = json.Unmarshal(data, &save)
	if err != nil {
		c.Logger().Errorf("could not parse json: %s", err.Error())
		return c.String(http.StatusBadRequest, "")
	}

	parts := strings.Split(save.Image, ",")
	if len(parts) != 2 {
		c.Logger().Errorf("malformed encoded image")
		return c.String(http.StatusBadRequest, "")
	}
	imgData := parts[1]

	img, err := base64.StdEncoding.DecodeString(imgData)
	if err != nil {
		c.Logger().Errorf("could not decode image: %s", err.Error())
		return c.String(http.StatusBadRequest, "")
	}

	var id, version int
	if save.CodeID == "" {
		parent, parentVersion, err := idVersion(save.Parent)
		if err != nil {
			parent, parentVersion = -1, -1
		}

		id, err = s.store.Add(parent, parentVersion, save.User, save.Code)
		if err != nil {
			c.Logger().Errorf("could not save new effect: %s", err.Error())
			return c.String(http.StatusInternalServerError, "")
		}
	} else {
		id, err = strconv.Atoi(save.CodeID)
		if err != nil {
			c.Logger().Errorf("malformed code id: %s", err.Error())
			return c.String(http.StatusBadRequest, "")
		}

		version, err = s.store.AddVersion(id, save.Code)
		if err != nil {
			c.Logger().Errorf("could not save new version: %s", err.Error())
			return c.String(http.StatusInternalServerError, "")
		}
	}

	err = saveImage(id, img)
	if err != nil {
		c.Logger().Errorf("could not save image: %s", err.Error())
		return c.String(http.StatusInternalServerError, "")
	}

	answer := fmt.Sprintf("%d.%d", id, version)
	return c.String(http.StatusOK, answer)
}

func saveImage(id int, data []byte) error {
	name := fmt.Sprintf("%d.png", id)
	path := path.Join(pathThumbs, name)

	f, err := os.Create(path)
	if err != nil {
		return fmt.Errorf("cannot create thumbnail: %w", err)
	}
	defer f.Close()

	_, err = f.Write(data)
	if err != nil {
		return fmt.Errorf("cannot write thumbnail: %w", err)
	}

	return nil
}

func idVersion(param string) (int, int, error) {
	var idString, versionString string
	parts := strings.Split(strings.TrimPrefix(param, "#"), ".")
	switch len(parts) {
	case 1:
		idString = parts[0]
	case 2:
		idString = parts[0]
		versionString = parts[1]
	default:
		return 0, 0, ErrInvalidData
	}

	id, err := strconv.Atoi(idString)
	if err != nil {
		return 0, 0, ErrInvalidData
	}

	version, err := strconv.Atoi(versionString)
	if err != nil {
		return 0, 0, ErrInvalidData
	}

	return id, version, nil
}
<commit_msg>add diff endpoint and fix version save<commit_after>package server

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"github.com/labstack/gommon/log"
	"github.com/mrdoob/glsl-sandbox/server/store"
)

const (
	pathGallery = "./server/assets/gallery.html"
	pathThumbs  = "./data/thumbs"
	perPage     = 50
)

var (
	ErrInvalidData = fmt.Errorf("invalid data")
)

type Template struct {
	templates *template.Template
}

func (t *Template) Render(
	w io.Writer, name string, data interface{}, c echo.Context,
) error {
	return t.templates.ExecuteTemplate(w, name, data)
}

type galleryEffect struct {
	ID      int
	Version int
	Image   string
}

type galleryData struct {
	Effects      []galleryEffect
	IsPrevious   bool
	PreviousPage int
	IsNext       bool
	NextPage     int
}

type Server struct {
	echo     *echo.Echo
	template *Template
	store    store.Store
}

func New(s store.Store) *Server {
	return &Server{
		echo: echo.New(),
		template: &Template{
			templates: template.Must(template.ParseFiles(pathGallery)),
		},
		store: s,
	}
}

func (s *Server) Start() error {
	s.setup()
	return s.echo.Start("localhost:8888")
}

func (s *Server) setup() {
	s.echo.Renderer = s.template
	s.echo.Logger.SetLevel(log.DEBUG)
	s.echo.Use(middleware.Logger())
	s.routes()
}

func (s *Server) routes() {
	s.echo.GET("/", s.indexHandler)
	s.echo.GET("/e", s.effectHandler)
	s.echo.POST("/e", s.saveHandler)
	s.echo.GET("/item/:id", s.itemHandler)

	s.echo.Static("/thumbs", "./data/thumbs")
	s.echo.Static("/css", "./server/assets/css")
	s.echo.Static("/js", "./server/assets/js")
	s.echo.File("/diff", "./server/assets/diff.html")
}

func (s *Server) indexHandler(c echo.Context) error {
	pString := c.QueryParam("page")
	if pString == "" {
		pString = "0"
	}
	page, err := strconv.Atoi(pString)
	if err != nil {
		page = 0
	}

	p, err := s.store.Page(page, perPage, false)
	if err != nil {
		return c.String(http.StatusInternalServerError, "error")
	}

	effects := make([]galleryEffect, len(p))
	for i, e := range p {
		effects[i] = galleryEffect{
			ID:      e.ID,
			Version: len(e.Versions) - 1,
			Image:   path.Join("/thumbs", e.ImageName()),
		}
	}

	d := galleryData{
		Effects:      effects,
		IsNext:       len(effects) == perPage,
		NextPage:     page + 1,
		IsPrevious:   page > 0,
		PreviousPage: page - 1,
	}

	err = c.Render(http.StatusOK, "gallery", d)
	if err != nil {
		panic(err)
	}
	return err
}

func (s *Server) effectHandler(c echo.Context) error {
	return c.File("./static/index.html")
}

type itemResponse struct {
	Code   string `json:"code"`
	User   string `json:"user"`
	Parent string `json:"parent,omitempty"`
}

func (s *Server) itemHandler(c echo.Context) error {
	param := c.Param("id")
	id, version, err := idVersion(param)
	if err != nil {
		return c.String(http.StatusBadRequest, "{}")
	}

	effect, err := s.store.Effect(id)
	if err != nil {
		return c.String(http.StatusBadRequest, "{}")
	}

	if version >= len(effect.Versions) || version < 0 {
		return c.String(http.StatusNotFound, "{}")
	}

	parent := ""
	if effect.Parent > 0 {
		parent = fmt.Sprintf("/e#%d.%d", effect.Parent, effect.ParentVersion)
	}

	item := itemResponse{
		Code:   effect.Versions[version].Code,
		User:   effect.User,
		Parent: parent,
	}

	data, err := json.Marshal(item)
	if err != nil {
		return c.String(http.StatusInternalServerError, "{}")
	}

	return c.Blob(http.StatusOK, "application/json", data)
}

type saveQuery struct {
	Code   string `json:"code"`
	Image  string `json:"image"`
	User   string `json:"user"`
	CodeID string `json:"code_id"`
	Parent string `json:"parent"`
}

func (s *Server) saveHandler(c echo.Context) error {
	if c.Request().Body == nil {
		c.Logger().Errorf("empty body")
		return c.String(http.StatusBadRequest, "")
	}

	data, err := io.ReadAll(c.Request().Body)
	if err != nil {
		c.Logger().Errorf("could not read body: %s", err.Error())
		return c.String(http.StatusInternalServerError, "")
	}

	var save saveQuery
	err = json.Unmarshal(data, &save)
	if err != nil {
		c.Logger().Errorf("could not parse json: %s", err.Error())
		return c.String(http.StatusBadRequest, "")
	}

	parts := strings.Split(save.Image, ",")
	if len(parts) != 2 {
		c.Logger().Errorf("malformed encoded image")
		return c.String(http.StatusBadRequest, "")
	}
	imgData := parts[1]

	img, err := base64.StdEncoding.DecodeString(imgData)
	if err != nil {
		c.Logger().Errorf("could not decode image: %s", err.Error())
		return c.String(http.StatusBadRequest, "")
	}

	var id, version int
	if save.CodeID == "" {
		parent, parentVersion, err := idVersion(save.Parent)
		if err != nil {
			parent, parentVersion = -1, -1
		}

		id, err = s.store.Add(parent, parentVersion, save.User, save.Code)
		if err != nil {
			c.Logger().Errorf("could not save new effect: %s", err.Error())
			return c.String(http.StatusInternalServerError, "")
		}
	} else {
		parts := strings.Split(save.CodeID, ".")
		if len(parts) < 1 {
			c.Logger().Errorf("malformed code id: %s", err.Error())
			return c.String(http.StatusBadRequest, "")
		}
		id, err = strconv.Atoi(parts[0])
		if err != nil {
			c.Logger().Errorf("malformed code id: %s", err.Error())
			return c.String(http.StatusBadRequest, "")
		}

		version, err = s.store.AddVersion(id, save.Code)
		if err != nil {
			c.Logger().Errorf("could not save new version: %s", err.Error())
			return c.String(http.StatusInternalServerError, "")
		}
	}

	err = saveImage(id, img)
	if err != nil {
		c.Logger().Errorf("could not save image: %s", err.Error())
		return c.String(http.StatusInternalServerError, "")
	}

	answer := fmt.Sprintf("%d.%d", id, version)
	return c.String(http.StatusOK, answer)
}

func saveImage(id int, data []byte) error {
	name := fmt.Sprintf("%d.png", id)
	path := path.Join(pathThumbs, name)

	f, err := os.Create(path)
	if err != nil {
		return fmt.Errorf("cannot create thumbnail: %w", err)
	}
	defer f.Close()

	_, err = f.Write(data)
	if err != nil {
		return fmt.Errorf("cannot write thumbnail: %w", err)
	}

	return nil
}

func idVersion(param string) (int, int, error) {
	var idString, versionString string
	parts := strings.Split(strings.TrimPrefix(param, "#"), ".")
	switch len(parts) {
	case 1:
		idString = parts[0]
	case 2:
		idString = parts[0]
		versionString = parts[1]
	default:
		return 0, 0, ErrInvalidData
	}

	id, err := strconv.Atoi(idString)
	if err != nil {
		return 0, 0, ErrInvalidData
	}

	version, err := strconv.Atoi(versionString)
	if err != nil {
		return 0, 0, ErrInvalidData
	}

	return id, version, nil
}
<|endoftext|>


<commit_before>package main

import "container/list"
import "encoding/json"
import "log"
import "net/http"
import "time"

type Message struct {
	Time time.Time
	Text string
}

type StoreRequest struct {
	StartTime time.Time
	Messages  chan<- []Message
}

type Store struct {
	Add chan *Message
	Get chan *StoreRequest
}

// TODO: Monotonic clock

func manage_store(store Store) {
	messages := list.New()
	message_count := 0
	max_messages := 1000
	waiting := list.New()
main:
	for {
		select {
		case new_message, ok := <-store.Add:
			if !ok {
				break main
			}
			messages.PushBack(new_message)
			for waiter := waiting.Front(); waiter != nil; waiter = waiter.Next() {
				waiter.Value.(*StoreRequest).Messages <- []Message{*new_message}
				close(waiter.Value.(*StoreRequest).Messages)
			}
			waiting.Init()
			if message_count < max_messages {
				message_count++
			} else {
				messages.Remove(messages.Front())
			}
		case request, ok := <-store.Get:
			if !ok {
				break main
			}
			if messages.Back() == nil || !request.StartTime.Before(messages.Back().Value.(*Message).Time) {
				waiting.PushBack(request)
			} else {
				start := messages.Back()
				response_size := 1
				if messages.Front().Value.(*Message).Time.After(request.StartTime) {
					start = messages.Front()
					response_size = message_count
				} else {
					for start.Prev().Value.(*Message).Time.After(request.StartTime) {
						start = start.Prev()
						response_size++
					}
				}
				response_messages := make([]Message, 0, response_size)
				for m := start; m != nil; m = m.Next() {
					response_messages = append(response_messages, *m.Value.(*Message))
				}
				request.Messages <- response_messages
			}
		}
	}
}

func start_store() Store {
	store := Store{make(chan *Message, 20), make(chan *StoreRequest, 20)}
	go manage_store(store)
	return store
}

func start_server(store Store) {
	http.HandleFunc("/fetch", func(w http.ResponseWriter, r *http.Request) {
		var since time.Time
		url_since := r.FormValue("since")
		if url_since != "" {
			err := json.Unmarshal([]byte(url_since), &since)
			if err != nil {
				log.Print("fetch: parse since: ", err)
				w.WriteHeader(http.StatusBadRequest)
				w.Write([]byte("Could not parse since as date"))
				return
			}
		}
		messages_from_store := make(chan []Message, 1)
		store.Get <- &StoreRequest{since, messages_from_store}

		json_encoded, err := json.Marshal(<-messages_from_store)
		if err != nil {
			log.Print("json encode: ", err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		w.Header().Add("Content-Type", "application/json")
		w.Write(json_encoded)
	})

	http.HandleFunc("/speak", func(w http.ResponseWriter, r *http.Request) {
		store.Add <- &Message{time.Now(), r.FormValue("text")}
	})

	log.Fatal(http.ListenAndServe(":8080", nil))
}

func main() {
	store := start_store()
	start_server(store)
}
<commit_msg>Allow port to be specified.  Default resonably.<commit_after>package main

import "container/list"
import "encoding/json"
import "flag"
import "log"
import "net/http"
import "strconv"
import "time"

var port = flag.Int("port", 21059, "Port to listen on")

type Message struct {
	Time time.Time
	Text string
}

type StoreRequest struct {
	StartTime time.Time
	Messages  chan<- []Message
}

type Store struct {
	Add chan *Message
	Get chan *StoreRequest
}

// TODO: Monotonic clock

func manage_store(store Store) {
	messages := list.New()
	message_count := 0
	max_messages := 1000
	waiting := list.New()
main:
	for {
		select {
		case new_message, ok := <-store.Add:
			if !ok {
				break main
			}
			messages.PushBack(new_message)
			for waiter := waiting.Front(); waiter != nil; waiter = waiter.Next() {
				waiter.Value.(*StoreRequest).Messages <- []Message{*new_message}
				close(waiter.Value.(*StoreRequest).Messages)
			}
			waiting.Init()
			if message_count < max_messages {
				message_count++
			} else {
				messages.Remove(messages.Front())
			}
		case request, ok := <-store.Get:
			if !ok {
				break main
			}
			if messages.Back() == nil || !request.StartTime.Before(messages.Back().Value.(*Message).Time) {
				waiting.PushBack(request)
			} else {
				start := messages.Back()
				response_size := 1
				if messages.Front().Value.(*Message).Time.After(request.StartTime) {
					start = messages.Front()
					response_size = message_count
				} else {
					for start.Prev().Value.(*Message).Time.After(request.StartTime) {
						start = start.Prev()
						response_size++
					}
				}
				response_messages := make([]Message, 0, response_size)
				for m := start; m != nil; m = m.Next() {
					response_messages = append(response_messages, *m.Value.(*Message))
				}
				request.Messages <- response_messages
			}
		}
	}
}

func start_store() Store {
	store := Store{make(chan *Message, 20), make(chan *StoreRequest, 20)}
	go manage_store(store)
	return store
}

func start_server(store Store) {
	http.HandleFunc("/fetch", func(w http.ResponseWriter, r *http.Request) {
		var since time.Time
		url_since := r.FormValue("since")
		if url_since != "" {
			err := json.Unmarshal([]byte(url_since), &since)
			if err != nil {
				log.Print("fetch: parse since: ", err)
				w.WriteHeader(http.StatusBadRequest)
				w.Write([]byte("Could not parse since as date"))
				return
			}
		}
		messages_from_store := make(chan []Message, 1)
		store.Get <- &StoreRequest{since, messages_from_store}

		json_encoded, err := json.Marshal(<-messages_from_store)
		if err != nil {
			log.Print("json encode: ", err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		w.Header().Add("Content-Type", "application/json")
		w.Write(json_encoded)
	})

	http.HandleFunc("/speak", func(w http.ResponseWriter, r *http.Request) {
		store.Add <- &Message{time.Now(), r.FormValue("text")}
	})

	log.Fatal(http.ListenAndServe(":" + strconv.Itoa(*port), nil))
}

func main() {
	store := start_store()
	start_server(store)
}
<|endoftext|>


<commit_before>package server

// Server is an interface that all servers must implement
// so that we can register endpoints, and serve requests
type Server interface {
	Init(*Config)
	Run()
	NotifyConnected() chan bool

	Name() string
	Description() string

	RegisterEndpoint(endpoint Endpoint)
	DeregisterEndpoint(pattern string)
}

// DefaultServer stores a default implementation, for simple usage
var DefaultServer Server = NewAMQPServer()

// Init our DefaultServer with a Config
func Init(c *Config) {
	DefaultServer.Init(c)
}

// RegisterEndpoint with the DefaultServer
func RegisterEndpoint(endpoint Endpoint) {
	DefaultServer.RegisterEndpoint(endpoint)
}

// Run the DefaultServer
func Run() {
	DefaultServer.Run()
}

// NotifyConnected delegates to DefaultServer
func NotifyConnected() chan bool {
	return DefaultServer.NotifyConnected()
}

// Config defines the config a server needs to start up, and serve requests
type Config struct {
	Name        string
	Description string
}
<commit_msg>Add Close() to server interface<commit_after>package server

// Server is an interface that all servers must implement
// so that we can register endpoints, and serve requests
type Server interface {
	Init(*Config)
	Run()
	Close()
	NotifyConnected() chan bool

	Name() string
	Description() string

	RegisterEndpoint(endpoint Endpoint)
	DeregisterEndpoint(pattern string)
}

// DefaultServer stores a default implementation, for simple usage
var DefaultServer Server = NewAMQPServer()

// Init our DefaultServer with a Config
func Init(c *Config) {
	DefaultServer.Init(c)
}

// RegisterEndpoint with the DefaultServer
func RegisterEndpoint(endpoint Endpoint) {
	DefaultServer.RegisterEndpoint(endpoint)
}

// Run the DefaultServer
func Run() {
	DefaultServer.Run()
}

// Close the DefaultServer
func Close() {
	DefaultServer.Close()
}

// NotifyConnected delegates to DefaultServer
func NotifyConnected() chan bool {
	return DefaultServer.NotifyConnected()
}

// Config defines the config a server needs to start up, and serve requests
type Config struct {
	Name        string
	Description string
}
<|endoftext|>


<commit_before>/* Copyright [1999-2014] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/

package main

import (
	"strings"
	"bytes"
	"regexp"
	"strconv"
	"sort"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"go/build"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
)

const (
	projectDirName = "github.com/emepyc/guiHive"
)

var (
	port string
	isVersion = regexp.MustCompile(`^[0-9]+$`)
)

func init() {
	flag.StringVar(&port, "port", "8080", "Port to listen (defaults to 8080)")
	flag.Parse()
}

func checkError(s string, err error, ss ...string) {
	if err != nil {
		log.Fatal(s, err, ss)
	}
}

// Sortable os.fileInfos by name (-> num)
type sortableFiles []os.FileInfo
func (s sortableFiles) Len () int {
	return len(s)
}
func (s sortableFiles) Less (i, j int) bool {
	iVer, err := strconv.Atoi(s[i].Name())
	checkError(fmt.Sprintf("Dir name %s can't be converted to int", s[i].Name()), err)
	jVer, err := strconv.Atoi(s[j].Name())
	checkError(fmt.Sprintf("Dir name %s can't be converted to int", s[j].Name()), err)
	return iVer < jVer;
}
func (s sortableFiles) Swap (i, j int) {
	s[i], s[j] = s[j], s[i]
}

func version(r *http.Request) string {
	parts := strings.SplitN(r.URL.Path, "/", 4)
	version := parts[2]
	if (isVersion.MatchString(version)) {
		return version
	} else {
		path := os.Getenv("GUIHIVE_PROJECTDIR") + "/versions/"
		dir, err := os.Open(path)
		checkError("Can't open dir " + path, err)
		files, err := dir.Readdir(-1)
		checkError("Can't read dir " + path, err)
		sort.Sort(sortableFiles(files))
		version = files[len(files)-1].Name()
		return version
	}
	return ""
}

func unknown(w http.ResponseWriter, r *http.Request) {
	version := version(r)
	fmt.Fprintln(w, r.URL)
	fmt.Fprintf(w, "version %s is currently not supported by guiHive\n", version)
}

func scriptHandler(w http.ResponseWriter, r *http.Request) {
	err := r.ParseForm()
	defer r.Body.Close()
	checkError("Can't parse Form: ", err)

	debug("METHOD: %s", r.Method)
	debug("URL: %s", r.URL)

	var outMsg bytes.Buffer
	var errMsg bytes.Buffer

	fname := os.Getenv("GUIHIVE_PROJECTDIR") + r.URL.Path
	args, err := json.Marshal(r.Form)
	checkError("Can't Marshal JSON:", err)

	debug("EXECUTING SCRIPT: %s", fname)
	debug("ARGS: %s", args)
	version := version(r);
	debug("VERSION: %s", version)

	versionRootDir := os.Getenv("GUIHIVE_PROJECTDIR") + "/versions/" + version + "/";
	ehiveRootDir := versionRootDir + "/ensembl-hive"
	ehiveRootLib := ehiveRootDir + "/modules"
	guihiveRootLib := versionRootDir + "/scripts/lib"
	newPerl5Lib  := addPerl5Lib(ehiveRootLib + ":" + guihiveRootLib)

	debug("EHIVE_ROOT_DIR: %s", ehiveRootDir)
	debug("NEW_PERL5LIB: %s", newPerl5Lib)
	cmd := exec.Command(fname, string(args))
	cmd.Env = make([]string,0)
	cmd.Env = append(cmd.Env, "PERL5LIB=" + newPerl5Lib)
	cmd.Env = append(cmd.Env, "EHIVE_ROOT_DIR=" + ehiveRootDir)
	cmd.Env = append(cmd.Env, "GUIHIVE_BASEDIR=" + versionRootDir)
	cmd.Env = append(cmd.Env, "PATH=" + os.Getenv("PATH"))

	cmd.Stdout = &outMsg
	cmd.Stderr = &errMsg

	if err := cmd.Start(); err != nil {
		log.Println("Error Starting Command: ", err)
	}
	if err := cmd.Wait(); err != nil {
		log.Println("Error Executing Command: ", err)
	}

	debug("OUTMSG: %s", outMsg.Bytes())
	debug("ERRMSG: %s", errMsg.Bytes())
	fmt.Fprintln(w, string(outMsg.Bytes()))

}

func pathExists(name string) bool {
	_, err := os.Stat(name)
	if os.IsNotExist(err) {
		return false
	}
	return err == nil
}

func guessProjectDir() (string, error) {
	// First, we try to find the project dir in the working directory
	serverPath := os.Args[0]
	serverDir := filepath.Dir(serverPath)
	pathToIndex := serverDir + "/../index.html"
	absPathToIndex, err := filepath.Abs(pathToIndex)
	if err != nil {
		debug("ABSPATHTOINDEX: %s\n", absPathToIndex)
		return "", err
	}
	if pathExists(absPathToIndex) {
		return path.Clean(absPathToIndex + "/.."), nil
	}
	for _, srcdir := range build.Default.SrcDirs() {
		dirName := path.Join(srcdir, projectDirName)
		fmt.Println("DIRNAME: ", dirName)
		if pathExists(dirName) {
			return dirName, nil
		}
	}
	return "", errors.New("Project directory not found")
}

func setEnvVar() error {
	projectDirectory, err := guessProjectDir()
	if err != nil {
		return err
	}
	//GUIHIVE_PROJECTDIR
	if err := os.Setenv("GUIHIVE_PROJECTDIR", projectDirectory+"/"); err != nil {
		return err
	}
	debug("PROJECT_DIRECTORY: %s\n", os.Getenv("GUIHIVE_PROJECTDIR"))

	// PER5LIB
	newPerl5Lib := addPerl5Lib(path.Clean(projectDirectory + "/scripts/lib"))
	err = setPerl5Lib(newPerl5Lib)
	if (err != nil) {
		return err
	}

	return nil
}

func addPerl5Lib (newDir string) string {
	perl5lib := os.Getenv("PERL5LIB")
	if perl5lib == "" {
		perl5lib = newDir
	} else {
		perl5lib = newDir + ":" + perl5lib
	}
	return perl5lib
}

func setPerl5Lib (perl5lib string) error {
	err := os.Setenv("PERL5LIB", perl5lib)
	if err != nil {
		return err
	}
	debug("PERL5LIB: %s\n", os.Getenv("PERL5LIB"))

	return nil
}

func main() {

	//  Fix environmental variables
	errV := setEnvVar()
	checkError("Problem setting environmental variables: ", errV)

	relPath := os.Getenv("GUIHIVE_PROJECTDIR")

	http.Handle("/", http.FileServer(http.Dir(relPath)))

	http.HandleFunc("/versions/", unknown)
	http.Handle("/versions/56/", http.FileServer(http.Dir(relPath)))
	http.Handle("/versions/62/", http.FileServer(http.Dir(relPath)))
	http.Handle("/versions/72/", http.FileServer(http.Dir(relPath)))

	http.Handle("/styles/", http.FileServer(http.Dir(relPath)))
	http.Handle("/javascript/", http.FileServer(http.Dir(relPath)))
	http.Handle("/versions/56/javascript/", http.FileServer(http.Dir(relPath)))
	http.Handle("/versions/62/javascript/", http.FileServer(http.Dir(relPath)))
	http.Handle("/versions/72/javascript/", http.FileServer(http.Dir(relPath)))

	http.Handle("/images/", http.FileServer(http.Dir(relPath)))
	http.HandleFunc("/scripts/", scriptHandler)
	http.HandleFunc("/versions/56/scripts/", scriptHandler)
	http.HandleFunc("/versions/62/scripts/", scriptHandler)
	http.HandleFunc("/versions/72/scripts/", scriptHandler)
	debug("Listening to port: %s", port)
	err := http.ListenAndServe(":"+port, nil)
	checkError("ListenAndServe ", err)
}
<commit_msg>Scan versions/ to find the available eHive versions<commit_after>/* Copyright [1999-2014] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/

package main

import (
	"strings"
	"bytes"
	"regexp"
	"strconv"
	"sort"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"go/build"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
)

const (
	projectDirName = "github.com/emepyc/guiHive"
)

var (
	port string
	isVersion = regexp.MustCompile(`^[0-9]+$`)
)

func init() {
	flag.StringVar(&port, "port", "8080", "Port to listen (defaults to 8080)")
	flag.Parse()
}

func checkError(s string, err error, ss ...string) {
	if err != nil {
		log.Fatal(s, err, ss)
	}
}

// Sortable os.fileInfos by name (-> num)
type sortableFiles []os.FileInfo
func (s sortableFiles) Len () int {
	return len(s)
}
func (s sortableFiles) Less (i, j int) bool {
	iVer, err := strconv.Atoi(s[i].Name())
	checkError(fmt.Sprintf("Dir name %s can't be converted to int", s[i].Name()), err)
	jVer, err := strconv.Atoi(s[j].Name())
	checkError(fmt.Sprintf("Dir name %s can't be converted to int", s[j].Name()), err)
	return iVer < jVer;
}
func (s sortableFiles) Swap (i, j int) {
	s[i], s[j] = s[j], s[i]
}

func version(r *http.Request) string {
	parts := strings.SplitN(r.URL.Path, "/", 4)
	version := parts[2]
	if (isVersion.MatchString(version)) {
		return version
	} else {
		path := os.Getenv("GUIHIVE_PROJECTDIR") + "/versions/"
		dir, err := os.Open(path)
		checkError("Can't open dir " + path, err)
		files, err := dir.Readdir(-1)
		checkError("Can't read dir " + path, err)
		sort.Sort(sortableFiles(files))
		version = files[len(files)-1].Name()
		return version
	}
	return ""
}

func unknown(w http.ResponseWriter, r *http.Request) {
	version := version(r)
	fmt.Fprintln(w, r.URL)
	fmt.Fprintf(w, "version %s is currently not supported by guiHive\n", version)
}

func scriptHandler(w http.ResponseWriter, r *http.Request) {
	err := r.ParseForm()
	defer r.Body.Close()
	checkError("Can't parse Form: ", err)

	debug("METHOD: %s", r.Method)
	debug("URL: %s", r.URL)

	var outMsg bytes.Buffer
	var errMsg bytes.Buffer

	fname := os.Getenv("GUIHIVE_PROJECTDIR") + r.URL.Path
	args, err := json.Marshal(r.Form)
	checkError("Can't Marshal JSON:", err)

	debug("EXECUTING SCRIPT: %s", fname)
	debug("ARGS: %s", args)
	version := version(r);
	debug("VERSION: %s", version)

	versionRootDir := os.Getenv("GUIHIVE_PROJECTDIR") + "/versions/" + version + "/";
	ehiveRootDir := versionRootDir + "/ensembl-hive"
	ehiveRootLib := ehiveRootDir + "/modules"
	guihiveRootLib := versionRootDir + "/scripts/lib"
	newPerl5Lib  := addPerl5Lib(ehiveRootLib + ":" + guihiveRootLib)

	debug("EHIVE_ROOT_DIR: %s", ehiveRootDir)
	debug("NEW_PERL5LIB: %s", newPerl5Lib)
	cmd := exec.Command(fname, string(args))
	cmd.Env = make([]string,0)
	cmd.Env = append(cmd.Env, "PERL5LIB=" + newPerl5Lib)
	cmd.Env = append(cmd.Env, "EHIVE_ROOT_DIR=" + ehiveRootDir)
	cmd.Env = append(cmd.Env, "GUIHIVE_BASEDIR=" + versionRootDir)
	cmd.Env = append(cmd.Env, "PATH=" + os.Getenv("PATH"))

	cmd.Stdout = &outMsg
	cmd.Stderr = &errMsg

	if err := cmd.Start(); err != nil {
		log.Println("Error Starting Command: ", err)
	}
	if err := cmd.Wait(); err != nil {
		log.Println("Error Executing Command: ", err)
	}

	debug("OUTMSG: %s", outMsg.Bytes())
	debug("ERRMSG: %s", errMsg.Bytes())
	fmt.Fprintln(w, string(outMsg.Bytes()))

}

func pathExists(name string) bool {
	_, err := os.Stat(name)
	if os.IsNotExist(err) {
		return false
	}
	return err == nil
}

func guessProjectDir() (string, error) {
	// First, we try to find the project dir in the working directory
	serverPath := os.Args[0]
	serverDir := filepath.Dir(serverPath)
	pathToIndex := serverDir + "/../index.html"
	absPathToIndex, err := filepath.Abs(pathToIndex)
	if err != nil {
		debug("ABSPATHTOINDEX: %s\n", absPathToIndex)
		return "", err
	}
	if pathExists(absPathToIndex) {
		return path.Clean(absPathToIndex + "/.."), nil
	}
	for _, srcdir := range build.Default.SrcDirs() {
		dirName := path.Join(srcdir, projectDirName)
		fmt.Println("DIRNAME: ", dirName)
		if pathExists(dirName) {
			return dirName, nil
		}
	}
	return "", errors.New("Project directory not found")
}

func setEnvVar() error {
	projectDirectory, err := guessProjectDir()
	if err != nil {
		return err
	}
	//GUIHIVE_PROJECTDIR
	if err := os.Setenv("GUIHIVE_PROJECTDIR", projectDirectory+"/"); err != nil {
		return err
	}
	debug("PROJECT_DIRECTORY: %s\n", os.Getenv("GUIHIVE_PROJECTDIR"))

	// PER5LIB
	newPerl5Lib := addPerl5Lib(path.Clean(projectDirectory + "/scripts/lib"))
	err = setPerl5Lib(newPerl5Lib)
	if (err != nil) {
		return err
	}

	return nil
}

func addPerl5Lib (newDir string) string {
	perl5lib := os.Getenv("PERL5LIB")
	if perl5lib == "" {
		perl5lib = newDir
	} else {
		perl5lib = newDir + ":" + perl5lib
	}
	return perl5lib
}

func setPerl5Lib (perl5lib string) error {
	err := os.Setenv("PERL5LIB", perl5lib)
	if err != nil {
		return err
	}
	debug("PERL5LIB: %s\n", os.Getenv("PERL5LIB"))

	return nil
}

func main() {

	//  Fix environmental variables
	errV := setEnvVar()
	checkError("Problem setting environmental variables: ", errV)

	relPath := os.Getenv("GUIHIVE_PROJECTDIR")

	http.Handle("/", http.FileServer(http.Dir(relPath)))
	http.HandleFunc("/versions/", unknown)
	http.Handle("/styles/", http.FileServer(http.Dir(relPath)))
	http.Handle("/javascript/", http.FileServer(http.Dir(relPath)))
	http.Handle("/images/", http.FileServer(http.Dir(relPath)))
	http.HandleFunc("/scripts/", scriptHandler)

	versionRootDir := relPath + "/versions/"
	dir, terr := os.Open(versionRootDir)
	checkError("Can't open dir " + versionRootDir, terr)
	files, terr := dir.Readdir(-1)
	for _, verdir := range files {
		if (verdir.IsDir()) {
			debug("Found eHive version %s", verdir.Name())
			http.Handle(fmt.Sprintf("/versions/%s/", verdir.Name()), http.FileServer(http.Dir(relPath)))
			http.Handle(fmt.Sprintf("/versions/%s/javascript/", verdir.Name()), http.FileServer(http.Dir(relPath)))
			http.HandleFunc(fmt.Sprintf("/versions/%s/scripts/", verdir.Name()), scriptHandler)
		}
	}

	debug("Listening to port: %s", port)
	err := http.ListenAndServe(":"+port, nil)
	checkError("ListenAndServe ", err)
}
<|endoftext|>


<commit_before>package router

import (
	"sync"
)

type HttpMetrics map[string]*HttpMetric

type ServerStatus struct {
	// NOTE: Due to this golang bug http://golang.org/issue/3069
	//       embedded anonymous fields are ignored by json marshaller,
	//       so all the fields in HttpMetric won't appear in json message.
	//
	//       Good news is the fix of this bug is targetted at go 1.1
	HttpMetric
	sync.Mutex

	Urls        int                    `json:"urls"`
	Droplets    int                    `json:"droplets"`
	BadRequests int                    `json:"bad_requests"`
	Tags        map[string]HttpMetrics `json:"tags"`

	// TODO: Support in future
	//	RequestsPerSec int                    `json:"requests_per_sec"`
}

type HttpMetric struct {
	Requests     int           `json:"requests"`
	Latency      *Distribution `json:"latency"`
	Responses2xx int           `json:"responses_2xx"`
	Responses3xx int           `json:"responses_3xx"`
	Responses4xx int           `json:"responses_4xx"`
	Responses5xx int           `json:"responses_5xx"`
	ResponsesXxx int           `json:"responses_xxx"`
}

func NewServerStatus() *ServerStatus {
	s := new(ServerStatus)

	s.Tags = make(map[string]HttpMetrics)

	s.Latency = NewDistribution(s, "overall")
	s.Latency.Reset()

	tags := []string{"component", "framework", "runtime"}
	for _, tag := range tags {
		s.Tags[tag] = make(HttpMetrics)
	}

	return s
}

func NewHttpMetric(name string) *HttpMetric {
	m := new(HttpMetric)

	m.Latency = NewDistribution(m, name)
	m.Latency.Reset()

	return m
}

func (s *ServerStatus) IncRequests() {
	s.Lock()
	defer s.Unlock()

	s.Requests++
}

func (s *ServerStatus) IncBadRequests() {
	s.Lock()
	defer s.Unlock()

	s.BadRequests++
}

func (s *ServerStatus) IncRequestsWithTags(tags map[string]string) {
	s.Lock()
	defer s.Unlock()

	for key, value := range tags {
		if s.Tags[key] == nil {
			continue
		}

		if s.Tags[key][value] == nil {
			s.Tags[key][value] = NewHttpMetric(key + "." + value)
		}
		s.Tags[key][value].Requests++
	}
}

func (s *ServerStatus) IncDroplets() {
	s.Lock()
	defer s.Unlock()

	s.Droplets++
}

func (s *ServerStatus) RecordResponse(status int, latency int, tags map[string]string) {
	if latency < 0 {
		return
	}

	s.Lock()
	defer s.Unlock()

	s.record(status, latency)

	for key, value := range tags {
		if s.Tags[key] == nil {
			continue
		}

		if s.Tags[key][value] == nil {
			s.Tags[key][value] = NewHttpMetric(key + "." + value)
		}
		s.Tags[key][value].record(status, latency)
	}
}

func (m *HttpMetric) record(status int, latency int) {
	if status >= 200 && status < 300 {
		m.Responses2xx++
	} else if status >= 300 && status < 400 {
		m.Responses3xx++
	} else if status >= 400 && status < 500 {
		m.Responses4xx++
	} else if status >= 500 && status < 600 {
		m.Responses5xx++
	} else {
		m.ResponsesXxx++
	}

	m.Latency.Add(int64(latency))
}
<commit_msg>Collecting requests_per_seconds in server status<commit_after>package router

import (
	"sync"
	"time"
)

const RPSInterval = 60 // in seconds

type HttpMetrics map[string]*HttpMetric

type ServerStatus struct {
	// NOTE: Due to this golang bug http://golang.org/issue/3069
	//       embedded anonymous fields are ignored by json marshaller,
	//       so all the fields in HttpMetric won't appear in json message.
	//
	//       Good news is the fix of this bug is targetted at go 1.1
	HttpMetric
	sync.Mutex

	Urls           int                    `json:"urls"`
	Droplets       int                    `json:"droplets"`
	BadRequests    int                    `json:"bad_requests"`
	Tags           map[string]HttpMetrics `json:"tags"`
	RequestsPerSec int                    `json:"requests_per_sec"`
}

type HttpMetric struct {
	Requests     int           `json:"requests"`
	Latency      *Distribution `json:"latency"`
	Responses2xx int           `json:"responses_2xx"`
	Responses3xx int           `json:"responses_3xx"`
	Responses4xx int           `json:"responses_4xx"`
	Responses5xx int           `json:"responses_5xx"`
	ResponsesXxx int           `json:"responses_xxx"`
}

func NewServerStatus() *ServerStatus {
	s := new(ServerStatus)

	s.Tags = make(map[string]HttpMetrics)

	s.Latency = NewDistribution(s, "overall")
	s.Latency.Reset()

	tags := []string{"component", "framework", "runtime"}
	for _, tag := range tags {
		s.Tags[tag] = make(HttpMetrics)
	}

	go func() {
		for {
			requests := s.Requests

			time.Sleep(RPSInterval * time.Second)

			s.RequestsPerSec = (s.Requests - requests) / RPSInterval
		}
	}()

	return s
}

func NewHttpMetric(name string) *HttpMetric {
	m := new(HttpMetric)

	m.Latency = NewDistribution(m, name)
	m.Latency.Reset()

	return m
}

func (s *ServerStatus) IncRequests() {
	s.Lock()
	defer s.Unlock()

	s.Requests++
}

func (s *ServerStatus) IncBadRequests() {
	s.Lock()
	defer s.Unlock()

	s.BadRequests++
}

func (s *ServerStatus) IncRequestsWithTags(tags map[string]string) {
	s.Lock()
	defer s.Unlock()

	for key, value := range tags {
		if s.Tags[key] == nil {
			continue
		}

		if s.Tags[key][value] == nil {
			s.Tags[key][value] = NewHttpMetric(key + "." + value)
		}
		s.Tags[key][value].Requests++
	}
}

func (s *ServerStatus) IncDroplets() {
	s.Lock()
	defer s.Unlock()

	s.Droplets++
}

func (s *ServerStatus) RecordResponse(status int, latency int, tags map[string]string) {
	if latency < 0 {
		return
	}

	s.Lock()
	defer s.Unlock()

	s.record(status, latency)

	for key, value := range tags {
		if s.Tags[key] == nil {
			continue
		}

		if s.Tags[key][value] == nil {
			s.Tags[key][value] = NewHttpMetric(key + "." + value)
		}
		s.Tags[key][value].record(status, latency)
	}
}

func (m *HttpMetric) record(status int, latency int) {
	if status >= 200 && status < 300 {
		m.Responses2xx++
	} else if status >= 300 && status < 400 {
		m.Responses3xx++
	} else if status >= 400 && status < 500 {
		m.Responses4xx++
	} else if status >= 500 && status < 600 {
		m.Responses5xx++
	} else {
		m.ResponsesXxx++
	}

	m.Latency.Add(int64(latency))
}
<|endoftext|>