<commit_before>package manners

import (
	"net"
	"net/http"
	"sync"
)

// Creates a new GracefulServer. The server will begin shutting down when
// a value is passed to the Shutdown channel.
func NewServer() *GracefulServer {
	return &GracefulServer{
		Shutdown: make(chan bool),
	}
}

// A GracefulServer maintains a WaitGroup that counts how many in-flight
// requests the server is handling. When it receives a shutdown signal,
// it stops accepting new requests but does not actually shut down until
// all in-flight requests terminate.
type GracefulServer struct {
	Shutdown        chan bool
	wg              sync.WaitGroup
	shutdownHandler func()
}

// A helper function that emulates the functionality of http.ListenAndServe.
func (s *GracefulServer) ListenAndServe(addr string, handler http.Handler) error {
	oldListener, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	listener := NewListener(oldListener, s)
	err = s.Serve(listener, handler)
	return err
}

// Similar to http.Serve. The listener passed must wrap a GracefulListener.
func (s *GracefulServer) Serve(listener net.Listener, handler http.Handler) error {
	s.shutdownHandler = func() { listener.Close() }
	s.listenForShutdown()
	server := http.Server{Handler: handler}
	server.ConnState = func(conn net.Conn, newState http.ConnState) {
		switch newState {
		case http.StateNew:
			s.StartRoutine()
		case http.StateClosed, http.StateHijacked:
			s.FinishRoutine()
		}
	}
	err := server.Serve(listener)

	// This block is reached when the server has received a shut down command.
	if err == nil {
		s.wg.Wait()
		return nil
	} else if _, ok := err.(listenerAlreadyClosed); ok {
		s.wg.Wait()
		return nil
	}
	return err
}

// Increments the server's WaitGroup. Use this if a web request starts more
// goroutines and these goroutines are not guaranteed to finish before the
// request.
func (s *GracefulServer) StartRoutine() {
	s.wg.Add(1)
}

// Decrement the server's WaitGroup. Used this to complement StartRoutine().
func (s *GracefulServer) FinishRoutine() {
	s.wg.Done()
}

func (s *GracefulServer) listenForShutdown() {
	go func() {
		<-s.Shutdown
		s.shutdownHandler()
	}()
}
<commit_msg>allow access to the inner http server so its settings can be tweaked<commit_after>package manners

import (
	"net"
	"net/http"
	"sync"
)

// Creates a new GracefulServer. The server will begin shutting down when
// a value is passed to the Shutdown channel.
func NewServer() *GracefulServer {
	return &GracefulServer{
		Shutdown: make(chan bool),
	}
}

// A GracefulServer maintains a WaitGroup that counts how many in-flight
// requests the server is handling. When it receives a shutdown signal,
// it stops accepting new requests but does not actually shut down until
// all in-flight requests terminate.
type GracefulServer struct {
	Shutdown        chan bool
	wg              sync.WaitGroup
	shutdownHandler func()
	InnerServer     http.Server
}

// A helper function that emulates the functionality of http.ListenAndServe.
func (s *GracefulServer) ListenAndServe(addr string, handler http.Handler) error {
	oldListener, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	listener := NewListener(oldListener, s)
	err = s.Serve(listener, handler)
	return err
}

// Similar to http.Serve. The listener passed must wrap a GracefulListener.
func (s *GracefulServer) Serve(listener net.Listener, handler http.Handler) error {
	s.shutdownHandler = func() { listener.Close() }
	s.listenForShutdown()
	s.InnerServer.Handler = handler
	s.InnerServer.ConnState = func(conn net.Conn, newState http.ConnState) {
		switch newState {
		case http.StateNew:
			s.StartRoutine()
		case http.StateClosed, http.StateHijacked:
			s.FinishRoutine()
		}
	}
	err := s.InnerServer.Serve(listener)

	// This block is reached when the server has received a shut down command.
	if err == nil {
		s.wg.Wait()
		return nil
	} else if _, ok := err.(listenerAlreadyClosed); ok {
		s.wg.Wait()
		return nil
	}
	return err
}

// Increments the server's WaitGroup. Use this if a web request starts more
// goroutines and these goroutines are not guaranteed to finish before the
// request.
func (s *GracefulServer) StartRoutine() {
	s.wg.Add(1)
}

// Decrement the server's WaitGroup. Used this to complement StartRoutine().
func (s *GracefulServer) FinishRoutine() {
	s.wg.Done()
}

func (s *GracefulServer) listenForShutdown() {
	go func() {
		<-s.Shutdown
		s.shutdownHandler()
	}()
}
<|endoftext|>


<commit_before>package stager

import (
	"net/http"
)

func Serve(config *Configuration) {
	backends := newBackendManager(config)
	handler := buildHandler(backends)
	http.ListenAndServe(config.Listen, handler)
}

func buildHandler(backends *backendManager) http.HandlerFunc {

	return func(writer http.ResponseWriter, request *http.Request) {
		backend, err := backends.get(request.Host)
		if err != nil {
			writer.WriteHeader(500)
			writer.Write([]byte("Got an internal error finding a backend: " + err.Error()))
		}
		switch backend.state {
		case StateNew:
			writer.WriteHeader(200)
			writer.Write([]byte("The backend you requested is being built. Check back momentarily."))
		case StateStarted:
			writer.WriteHeader(200)
			writer.Write([]byte("The backend you requested is starting up. Check back momentarily."))
		case StateRunning:
			backend.proxy.ServeHTTP(writer, request)
		case StateFinished:
			writer.WriteHeader(200)
			writer.Write([]byte("The backend you requested is starting up. Check back momentarily."))
		}
	}
}
<commit_msg>Error case should not fall through.<commit_after>package stager

import (
	"net/http"
)

func Serve(config *Configuration) {
	backends := newBackendManager(config)
	handler := buildHandler(backends)
	http.ListenAndServe(config.Listen, handler)
}

func buildHandler(backends *backendManager) http.HandlerFunc {

	return func(writer http.ResponseWriter, request *http.Request) {
		backend, err := backends.get(request.Host)
		if err != nil {
			writer.WriteHeader(500)
			writer.Write([]byte("Got an internal error finding a backend: " + err.Error()))
			return
		}
		switch backend.state {
		case StateNew:
			writer.WriteHeader(200)
			writer.Write([]byte("The backend you requested is being built. Check back momentarily."))
		case StateStarted:
			writer.WriteHeader(200)
			writer.Write([]byte("The backend you requested is starting up. Check back momentarily."))
		case StateRunning:
			backend.proxy.ServeHTTP(writer, request)
		case StateFinished:
			writer.WriteHeader(200)
			writer.Write([]byte("The backend you requested is starting up. Check back momentarily."))
		}
	}
}
<|endoftext|>


<commit_before>package kuja

import (
	"crypto/tls"
	"io"
	"net"
	"net/http"
	"reflect"
	"strings"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/golang/snappy"
	"github.com/kr/pretty"
	"github.com/plimble/errors"
	"github.com/plimble/kuja/broker"
	"github.com/plimble/kuja/encoder"
	"github.com/plimble/kuja/encoder/json"
	"github.com/plimble/kuja/registry"
	"github.com/satori/go.uuid"
	"golang.org/x/net/netutil"
	"gopkg.in/tylerb/graceful.v1"
)

const (
	DEVELOPMENT = "development"
	STAGING     = "staging"
	PRODUCTION  = "production"
)

type Handler func(ctx *Context, w http.ResponseWriter, r *http.Request) error

type Server struct {
	stage           string
	id              string
	pool            sync.Pool
	middleware      []Handler
	mu              sync.Mutex // protects the serviceMap
	serviceMap      map[string]*service
	subscriberMap   map[string]*subscriber
	broker          broker.Broker
	encoder         encoder.Encoder
	snappy          bool
	serviceError    ServiceErrorFunc
	subscriberError SubscriberErrorFunc
	registry        registry.Registry
	srv             *graceful.Server
}

func NewServer() *Server {
	server := &Server{
		stage:           DEVELOPMENT,
		id:              uuid.NewV1().String(),
		serviceMap:      make(map[string]*service),
		encoder:         json.NewEncoder(),
		serviceError:    defaulServiceErr,
		subscriberError: defaulSubscriberErr,
		subscriberMap:   make(map[string]*subscriber),
	}

	server.pool.New = func() interface{} {
		return &Context{
			ReqMetadata:  make(Metadata),
			RespMetadata: make(Metadata),
			returnValues: make([]reflect.Value, 1),
			serviceError: server.serviceError,
			isResp:       false,
		}
	}

	return server
}

func (server *Server) Id() string {
	return server.id
}

func (server *Server) Use(h ...Handler) {
	server.middleware = append(server.middleware, h...)
}

func (server *Server) Stage(stage string) {
	server.stage = stage
}

func (server *Server) Snappy(enable bool) {
	server.snappy = enable
}

func (server *Server) Registry(r registry.Registry) {
	server.registry = r
}

func (server *Server) Encoder(enc encoder.Encoder) {
	server.encoder = enc
}

func (server *Server) Broker(b broker.Broker) {
	server.broker = b
}

func (server *Server) Run(addr string, timeout time.Duration) {
	server.srv = &graceful.Server{
		Timeout: timeout,
		Server: &http.Server{
			Addr:    addr,
			Handler: server,
		},
	}

	l, err := net.Listen("tcp", addr)
	if err != nil {
		log.Error(err)
		return
	}

	if server.srv.ListenLimit != 0 {
		l = netutil.LimitListener(l, server.srv.ListenLimit)
	}

	if err := server.startRegistry("http://", addr); err != nil {
		log.Error(err)
		return
	}

	if err := server.startBroker(); err != nil {
		log.Error(err)
		return
	}

	if err := server.startSubscribe(); err != nil {
		log.Error(err)
		return
	}

	log.Infof("Start server id %s on %s stage %s", server.id, addr, server.stage)
	server.srv.Serve(l)
	log.Info("Stop server")
	server.stopRegistry()
	server.stopBroker()
}

func (server *Server) Close() {
	server.srv.Stop(0)
}

func (server *Server) RunTLS(addr string, timeout time.Duration, certFile, keyFile string) {
	server.srv = &graceful.Server{
		Timeout: timeout,
		Server: &http.Server{
			Addr:    addr,
			Handler: server,
		},
	}

	config := &tls.Config{}
	if server.srv.TLSConfig != nil {
		*config = *server.srv.TLSConfig
	}

	if config.NextProtos == nil {
		config.NextProtos = []string{"http/1.1"}
	}

	var err error
	config.Certificates = make([]tls.Certificate, 1)
	config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		log.Error(err)
		return
	}

	conn, err := net.Listen("tcp", addr)
	if err != nil {
		log.Error(err)
		return
	}

	tlsListener := tls.NewListener(conn, config)

	if err := server.startRegistry("https://", addr); err != nil {
		log.Error(err)
		return
	}

	if err := server.startBroker(); err != nil {
		log.Error(err)
		return
	}

	if err := server.startSubscribe(); err != nil {
		log.Error(err)
		return
	}

	log.Infof("Start server id %s on %s stage %s", server.id, addr, server.stage)
	server.srv.Serve(tlsListener)
	log.Info("Stop server")
	server.stopRegistry()
	server.stopBroker()
}

func (server *Server) startRegistry(scheme, addr string) error {
	if server.registry == nil {
		return nil
	}

	var host string
	var port string
	parts := strings.Split(addr, ":")
	if len(parts) > 1 {
		host = strings.Join(parts[:len(parts)-1], ":")
		port = parts[len(parts)-1]
	} else {
		host = parts[0]
	}

	for _, service := range server.serviceMap {
		service.node = &registry.Node{
			Id:      service.id,
			Name:    service.name,
			Host:    host,
			Port:    port,
			Address: addr,
			URL:     scheme + addr,
		}
		err := server.registry.Register(service.node)
		log.Infof("Registerd %s %s %s", service.name, service.id, addr)
		if err != nil {
			return err
		}
	}

	return nil
}

func (server *Server) stopRegistry() {
	if server.registry != nil {
		for _, service := range server.serviceMap {
			if service.node == nil {
				continue
			}
			err := server.registry.Deregister(service.node)
			if err != nil {
				log.WithFields(log.Fields{
					"error":      err,
					"service":    service.name,
					"service_id": service.id,
				}).Error("Unable to deregister")
				continue
			}
			log.Infof("Deregisterd %s %s", service.name, service.id)
		}
		server.registry.Close()
	}
}

func (server *Server) startBroker() error {
	if server.broker == nil {
		return nil
	}

	if err := server.broker.Connect(); err != nil {
		return err
	}

	log.Infof("Connected to broker")

	return nil
}

func (server *Server) stopBroker() {
	if server.broker == nil {
		return
	}

	server.broker.Close()
	log.Infof("Close broker connection")
}

func (server *Server) startSubscribe() error {
	if server.broker == nil {
		return nil
	}

	for _, s := range server.subscriberMap {
		server.broker.Subscribe(s.topic, s.queue, server.id, s.size, s.handler)
		log.Infof("Subscribe topic: %s queue: %s size: %d", s.topic, s.queue, s.size)
	}

	return nil
}

func getServiceMethod(s string) (string, string) {
	if strings.HasPrefix(s, "/") {
		s = s[1:]
	}

	if strings.HasSuffix(s, "/") {
		s = s[:len(s)-1]
	}

	for i := 0; i < len(s); i++ {
		if s[i] == '/' {
			return s[:i], s[i+1:]
		}
	}

	return "", ""
}

func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if req.Method == "GET" && req.URL.Path == "/health" {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.WriteHeader(http.StatusOK)
		io.WriteString(w, "ok\n")
		return
	}

	if req.Method != "POST" {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.WriteHeader(http.StatusMethodNotAllowed)
		io.WriteString(w, "405 must POST\n")
		return
	}

	serviceName, methodName := getServiceMethod(req.URL.Path)

	if serviceName == "" || methodName == "" {
		w.WriteHeader(404)
		w.Write([]byte("rpc: can't find service or method"))
		return
	}

	ctx := server.pool.Get().(*Context)

	for name, vals := range req.Header {
		ctx.ReqMetadata[name] = vals[0]
	}

	// server.mu.Lock()
	s := server.serviceMap[serviceName]
	// server.mu.Unlock()
	if s == nil {
		w.WriteHeader(404)
		w.Write([]byte("rpc: can't find service " + serviceName))
		return
	}
	mt := s.method[methodName]
	if mt == nil {
		w.WriteHeader(404)
		w.Write([]byte("rpc: can't find method " + methodName))
		return
	}

	ctx.handlers = s.handlers
	ctx.mt = mt
	ctx.req = req
	ctx.w = w
	ctx.rcvr = s.rcvr
	ctx.encoder = server.encoder
	ctx.snappy = server.snappy
	ctx.ServiceID = s.id
	ctx.ServiceName = serviceName
	ctx.MethodName = methodName

	if len(s.handlers) > 0 {
		if err := s.handlers[0](ctx, w, req); err != nil && !ctx.isResp {
			respError(err, ctx)
		}
	} else {
		if err := serve(ctx); err != nil && !ctx.isResp {
			respError(err, ctx)
		}
	}
	server.pool.Put(ctx)
}

func respError(err error, ctx *Context) {
	if errs, ok := err.(errors.Error); ok {
		ctx.isResp = true
		go ctx.serviceError(ctx.ServiceID, ctx.ServiceName, ctx.MethodName, errs.Code(), err)
		ctx.w.WriteHeader(errs.Code())
		ctx.w.Write([]byte(errs.Error()))
	} else {
		ctx.isResp = true
		go ctx.serviceError(ctx.ServiceID, ctx.ServiceName, ctx.MethodName, 500, err)
		ctx.w.WriteHeader(500)
		ctx.w.Write([]byte(err.Error()))
	}
}

func serve(ctx *Context) error {
	argv := reflect.New(ctx.mt.ArgType.Elem())
	var replyv interface{}

	argvInter := argv.Interface()
	err := ctx.encoder.Decode(ctx.req.Body, argvInter)
	ctx.req.Body.Close()
	if err != nil {
		return errors.InternalError("unable to encode response")
	}

	function := ctx.mt.method.Func
	if ctx.mt.ContextType == nil {
		ctx.returnValues = function.Call([]reflect.Value{ctx.rcvr, argv})
	} else {
		ctx.returnValues = function.Call([]reflect.Value{ctx.rcvr, ctx.mt.prepareContext(ctx), argv})
	}

	if len(ctx.returnValues) == 1 {
		if ctx.returnValues[0].Interface() != nil {
			return ctx.returnValues[0].Interface().(error)
		}
	} else if len(ctx.returnValues) == 2 {
		if ctx.returnValues[1].Interface() != nil {
			return ctx.returnValues[1].Interface().(error)
		}
		replyv = ctx.returnValues[0].Interface()
	}

	pretty.Println(replyv)

	for name, val := range ctx.RespMetadata {
		ctx.w.Header().Set(name, val)
	}

	if replyv != nil {
		if ctx.snappy {
			data, err := ctx.encoder.Marshal(replyv)
			if err != nil {
				return err
			}
			data = snappy.Encode(nil, data)
			ctx.isResp = true
			ctx.w.Header().Set("Snappy", "true")
			ctx.w.WriteHeader(200)
			ctx.w.Write(data)
		} else {
			ctx.isResp = true
			ctx.w.WriteHeader(200)
			ctx.encoder.Encode(ctx.w, replyv)
		}
	} else {
		ctx.isResp = true
		ctx.w.WriteHeader(200)
		ctx.w.Write(nil)
	}

	return nil
}
<commit_msg>update<commit_after>package kuja

import (
	"crypto/tls"
	"io"
	"net"
	"net/http"
	"reflect"
	"strings"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/golang/snappy"
	"github.com/plimble/errors"
	"github.com/plimble/kuja/broker"
	"github.com/plimble/kuja/encoder"
	"github.com/plimble/kuja/encoder/json"
	"github.com/plimble/kuja/registry"
	"github.com/satori/go.uuid"
	"golang.org/x/net/netutil"
	"gopkg.in/tylerb/graceful.v1"
)

const (
	DEVELOPMENT = "development"
	STAGING     = "staging"
	PRODUCTION  = "production"
)

type Handler func(ctx *Context, w http.ResponseWriter, r *http.Request) error

type Server struct {
	stage           string
	id              string
	pool            sync.Pool
	middleware      []Handler
	mu              sync.Mutex // protects the serviceMap
	serviceMap      map[string]*service
	subscriberMap   map[string]*subscriber
	broker          broker.Broker
	encoder         encoder.Encoder
	snappy          bool
	serviceError    ServiceErrorFunc
	subscriberError SubscriberErrorFunc
	registry        registry.Registry
	srv             *graceful.Server
}

func NewServer() *Server {
	server := &Server{
		stage:           DEVELOPMENT,
		id:              uuid.NewV1().String(),
		serviceMap:      make(map[string]*service),
		encoder:         json.NewEncoder(),
		serviceError:    defaulServiceErr,
		subscriberError: defaulSubscriberErr,
		subscriberMap:   make(map[string]*subscriber),
	}

	server.pool.New = func() interface{} {
		return &Context{
			ReqMetadata:  make(Metadata),
			RespMetadata: make(Metadata),
			returnValues: make([]reflect.Value, 1),
			serviceError: server.serviceError,
			isResp:       false,
		}
	}

	return server
}

func (server *Server) Id() string {
	return server.id
}

func (server *Server) Use(h ...Handler) {
	server.middleware = append(server.middleware, h...)
}

func (server *Server) Stage(stage string) {
	server.stage = stage
}

func (server *Server) Snappy(enable bool) {
	server.snappy = enable
}

func (server *Server) Registry(r registry.Registry) {
	server.registry = r
}

func (server *Server) Encoder(enc encoder.Encoder) {
	server.encoder = enc
}

func (server *Server) Broker(b broker.Broker) {
	server.broker = b
}

func (server *Server) Run(addr string, timeout time.Duration) {
	server.srv = &graceful.Server{
		Timeout: timeout,
		Server: &http.Server{
			Addr:    addr,
			Handler: server,
		},
	}

	l, err := net.Listen("tcp", addr)
	if err != nil {
		log.Error(err)
		return
	}

	if server.srv.ListenLimit != 0 {
		l = netutil.LimitListener(l, server.srv.ListenLimit)
	}

	if err := server.startRegistry("http://", addr); err != nil {
		log.Error(err)
		return
	}

	if err := server.startBroker(); err != nil {
		log.Error(err)
		return
	}

	if err := server.startSubscribe(); err != nil {
		log.Error(err)
		return
	}

	log.Infof("Start server id %s on %s stage %s", server.id, addr, server.stage)
	server.srv.Serve(l)
	log.Info("Stop server")
	server.stopRegistry()
	server.stopBroker()
}

func (server *Server) Close() {
	server.srv.Stop(0)
}

func (server *Server) RunTLS(addr string, timeout time.Duration, certFile, keyFile string) {
	server.srv = &graceful.Server{
		Timeout: timeout,
		Server: &http.Server{
			Addr:    addr,
			Handler: server,
		},
	}

	config := &tls.Config{}
	if server.srv.TLSConfig != nil {
		*config = *server.srv.TLSConfig
	}

	if config.NextProtos == nil {
		config.NextProtos = []string{"http/1.1"}
	}

	var err error
	config.Certificates = make([]tls.Certificate, 1)
	config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		log.Error(err)
		return
	}

	conn, err := net.Listen("tcp", addr)
	if err != nil {
		log.Error(err)
		return
	}

	tlsListener := tls.NewListener(conn, config)

	if err := server.startRegistry("https://", addr); err != nil {
		log.Error(err)
		return
	}

	if err := server.startBroker(); err != nil {
		log.Error(err)
		return
	}

	if err := server.startSubscribe(); err != nil {
		log.Error(err)
		return
	}

	log.Infof("Start server id %s on %s stage %s", server.id, addr, server.stage)
	server.srv.Serve(tlsListener)
	log.Info("Stop server")
	server.stopRegistry()
	server.stopBroker()
}

func (server *Server) startRegistry(scheme, addr string) error {
	if server.registry == nil {
		return nil
	}

	var host string
	var port string
	parts := strings.Split(addr, ":")
	if len(parts) > 1 {
		host = strings.Join(parts[:len(parts)-1], ":")
		port = parts[len(parts)-1]
	} else {
		host = parts[0]
	}

	for _, service := range server.serviceMap {
		service.node = &registry.Node{
			Id:      service.id,
			Name:    service.name,
			Host:    host,
			Port:    port,
			Address: addr,
			URL:     scheme + addr,
		}
		err := server.registry.Register(service.node)
		log.Infof("Registerd %s %s %s", service.name, service.id, addr)
		if err != nil {
			return err
		}
	}

	return nil
}

func (server *Server) stopRegistry() {
	if server.registry != nil {
		for _, service := range server.serviceMap {
			if service.node == nil {
				continue
			}
			err := server.registry.Deregister(service.node)
			if err != nil {
				log.WithFields(log.Fields{
					"error":      err,
					"service":    service.name,
					"service_id": service.id,
				}).Error("Unable to deregister")
				continue
			}
			log.Infof("Deregisterd %s %s", service.name, service.id)
		}
		server.registry.Close()
	}
}

func (server *Server) startBroker() error {
	if server.broker == nil {
		return nil
	}

	if err := server.broker.Connect(); err != nil {
		return err
	}

	log.Infof("Connected to broker")

	return nil
}

func (server *Server) stopBroker() {
	if server.broker == nil {
		return
	}

	server.broker.Close()
	log.Infof("Close broker connection")
}

func (server *Server) startSubscribe() error {
	if server.broker == nil {
		return nil
	}

	for _, s := range server.subscriberMap {
		server.broker.Subscribe(s.topic, s.queue, server.id, s.size, s.handler)
		log.Infof("Subscribe topic: %s queue: %s size: %d", s.topic, s.queue, s.size)
	}

	return nil
}

func getServiceMethod(s string) (string, string) {
	if strings.HasPrefix(s, "/") {
		s = s[1:]
	}

	if strings.HasSuffix(s, "/") {
		s = s[:len(s)-1]
	}

	for i := 0; i < len(s); i++ {
		if s[i] == '/' {
			return s[:i], s[i+1:]
		}
	}

	return "", ""
}

func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if req.Method == "GET" && req.URL.Path == "/health" {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.WriteHeader(http.StatusOK)
		io.WriteString(w, "ok\n")
		return
	}

	if req.Method != "POST" {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.WriteHeader(http.StatusMethodNotAllowed)
		io.WriteString(w, "405 must POST\n")
		return
	}

	serviceName, methodName := getServiceMethod(req.URL.Path)

	if serviceName == "" || methodName == "" {
		w.WriteHeader(404)
		w.Write([]byte("rpc: can't find service or method"))
		return
	}

	ctx := server.pool.Get().(*Context)

	for name, vals := range req.Header {
		ctx.ReqMetadata[name] = vals[0]
	}

	// server.mu.Lock()
	s := server.serviceMap[serviceName]
	// server.mu.Unlock()
	if s == nil {
		w.WriteHeader(404)
		w.Write([]byte("rpc: can't find service " + serviceName))
		return
	}
	mt := s.method[methodName]
	if mt == nil {
		w.WriteHeader(404)
		w.Write([]byte("rpc: can't find method " + methodName))
		return
	}

	ctx.handlers = s.handlers
	ctx.mt = mt
	ctx.req = req
	ctx.w = w
	ctx.rcvr = s.rcvr
	ctx.encoder = server.encoder
	ctx.snappy = server.snappy
	ctx.ServiceID = s.id
	ctx.ServiceName = serviceName
	ctx.MethodName = methodName

	if len(s.handlers) > 0 {
		if err := s.handlers[0](ctx, w, req); err != nil && !ctx.isResp {
			respError(err, ctx)
		}
	} else {
		if err := serve(ctx); err != nil && !ctx.isResp {
			respError(err, ctx)
		}
	}
	server.pool.Put(ctx)
}

func respError(err error, ctx *Context) {
	if errs, ok := err.(errors.Error); ok {
		ctx.isResp = true
		go ctx.serviceError(ctx.ServiceID, ctx.ServiceName, ctx.MethodName, errs.Code(), err)
		ctx.w.WriteHeader(errs.Code())
		ctx.w.Write([]byte(errs.Error()))
	} else {
		ctx.isResp = true
		go ctx.serviceError(ctx.ServiceID, ctx.ServiceName, ctx.MethodName, 500, err)
		ctx.w.WriteHeader(500)
		ctx.w.Write([]byte(err.Error()))
	}
}

func serve(ctx *Context) error {
	argv := reflect.New(ctx.mt.ArgType.Elem())
	var replyv interface{}

	argvInter := argv.Interface()
	err := ctx.encoder.Decode(ctx.req.Body, argvInter)
	ctx.req.Body.Close()
	if err != nil {
		return errors.InternalError("unable to encode response")
	}

	function := ctx.mt.method.Func
	if ctx.mt.ContextType == nil {
		ctx.returnValues = function.Call([]reflect.Value{ctx.rcvr, argv})
	} else {
		ctx.returnValues = function.Call([]reflect.Value{ctx.rcvr, ctx.mt.prepareContext(ctx), argv})
	}

	if len(ctx.returnValues) == 1 {
		if ctx.returnValues[0].Interface() != nil {
			return ctx.returnValues[0].Interface().(error)
		}
	} else if len(ctx.returnValues) == 2 {
		if ctx.returnValues[1].Interface() != nil {
			return ctx.returnValues[1].Interface().(error)
		}
		replyv = ctx.returnValues[0].Interface()
	}

	for name, val := range ctx.RespMetadata {
		ctx.w.Header().Set(name, val)
	}

	if replyv != nil {
		if ctx.snappy {
			data, err := ctx.encoder.Marshal(replyv)
			if err != nil {
				return err
			}
			data = snappy.Encode(nil, data)
			ctx.isResp = true
			ctx.w.Header().Set("Snappy", "true")
			ctx.w.WriteHeader(200)
			ctx.w.Write(data)
		} else {
			ctx.isResp = true
			ctx.w.WriteHeader(200)
			ctx.encoder.Encode(ctx.w, replyv)
		}
	} else {
		ctx.isResp = true
		ctx.w.WriteHeader(200)
		ctx.w.Write(nil)
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/jangie/goloadbalancers/bestof"
	"github.com/jangie/goloadbalancers/random"
	"github.com/jangie/goloadbalancers/util"
	"github.com/vulcand/oxy/forward"
)

//Test harness
type testHarness struct {
	next http.Handler
	port int
}

func (t *testHarness) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if req.URL.Path == "/" {
		fmt.Fprintf(w, "This is a dumb server which is meant to be used with the node file testServer.js.\n - Run the node server (which will hold onto :8080)\n - Add pointers into your hosts file for 127.0.0.1 testa, testb, testc\n - Hit localhost:%d/simulateServers, and see which server you get balanced to", t.port)
	} else {
		t.next.ServeHTTP(w, req)
	}
}

func main() {
	var fwd, _ = forward.New()
	var bal = bestof.NewChoiceOfBalancer(
		[]string{"http://testa:8080", "http://testb:8080", "http://testc:8080"},
		bestof.ChoiceOfBalancerOptions{
			Choices:         2,
			RandomGenerator: util.GoRandom{},
		},
		fwd,
	)
	var tbestof = testHarness{
		next: bal,
		port: 8090,
	}

	var random = random.NewRandomBalancer([]string{"http://testa:8080", "http://testb:8080", "http://testc:8080"},
		random.RandomBalancerOptions{
			RandomGenerator: util.GoRandom{},
		},
		fwd,
	)
	var trandom = testHarness{
		next: random,
		port: 8091,
	}
	go http.ListenAndServe(":8090", &tbestof)
	go http.ListenAndServe(":8091", &trandom)
	for true == true {
		time.Sleep(1000)
	}
}
<commit_msg>add informative message to console<commit_after>package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/jangie/goloadbalancers/bestof"
	"github.com/jangie/goloadbalancers/random"
	"github.com/jangie/goloadbalancers/util"
	"github.com/vulcand/oxy/forward"
)

//Test harness
type testHarness struct {
	next http.Handler
	port int
}

func (t *testHarness) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if req.URL.Path == "/" {
		fmt.Fprintf(w, "This is a dumb server which is meant to be used with the node file testServer.js.\n - Run the node server (which will hold onto :8080)\n - Add pointers into your hosts file for 127.0.0.1 testa, testb, testc\n - Hit localhost:%d/simulateServers, and see which server you get balanced to", t.port)
	} else {
		t.next.ServeHTTP(w, req)
	}
}

func main() {
	var fwd, _ = forward.New()
	var bal = bestof.NewChoiceOfBalancer(
		[]string{"http://testa:8080", "http://testb:8080", "http://testc:8080"},
		bestof.ChoiceOfBalancerOptions{
			Choices:         2,
			RandomGenerator: util.GoRandom{},
		},
		fwd,
	)
	var tbestof = testHarness{
		next: bal,
		port: 8090,
	}

	var random = random.NewRandomBalancer([]string{"http://testa:8080", "http://testb:8080", "http://testc:8080"},
		random.RandomBalancerOptions{
			RandomGenerator: util.GoRandom{},
		},
		fwd,
	)
	var trandom = testHarness{
		next: random,
		port: 8091,
	}
	go http.ListenAndServe(":8090", &tbestof)
	go http.ListenAndServe(":8091", &trandom)
	fmt.Print("Listening on http://localhost:8090 [bestof lb] and http://localhost:8091 [random lb]\n\n")
	for true == true {
		time.Sleep(1000)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/go-martini/martini"
)

type Point struct {
	X int
	Y int
}

type Car struct {
	Color int
	Point Point
}

type State struct {
	X       int  `json: "x"`
	Y       int  `json: "y"`
	Crashed bool `json: "crashed"`
	Speed   int  `json: "speed"`
}

type User struct {
	Name     string `json: "name"`
	Email    string `json: "email"`
	Password string `json: "password"`
}

func main() {
	m := martini.Classic()
	m.Get("/", Index)

	m.Get("/coords", Coords)
	m.Get("/move/:x/:y", Move) // ToDo: make this post reqeust
	m.Get("/state", GetState)
	m.Get("/account/profile", Profile)
	m.Get("/account/login", Login)
	m.Get("/account/logout", Logout)
	m.Run()
}

func Index() string {
	return "This is index"
}

func Coords() string {
	return "coords"
}

func Move(params martini.Params) string { // this is fake function for now. ToDo: change it to use actual Point
	fmt.Println("Scuka kruta")
	return "Hello " + params["x"] + params["y"]
}

func GetState(params martini.Params) (result string) { // Todo: change return type to json
	current_state := State()
	serialized_state, err := json.Marshal(current_state)
	if err != nil {
		result := "{'status', 'failed'}"
	} else {
		result := string(jsonObj)
	}
	return
}

func Profile(params martini.Params) (result string) {
	result = "Profile"
	return
}

func Login(params martini.Params) (result string) {
	result = "Login"
	return
}

func Logout(params martini.Params) (result string) {
	result = "Logout"
	return
}
<commit_msg>fixed server<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"github.com/go-martini/martini"
)

type Point struct {
	X int
	Y int
}

type Car struct {
	Color int
	Point Point
}

type State struct {
	X       int  `json: "x"`
	Y       int  `json: "y"`
	Crashed bool `json: "crashed"`
	Speed   int  `json: "speed"`
}

type User struct {
	Name     string `json: "name"`
	Email    string `json: "email"`
	Password string `json: "password"`
}

func main() {
	m := martini.Classic()
	m.Get("/", Index)

	m.Get("/coords", Coords)
	m.Get("/move/:x/:y", Move) // ToDo: make this post reqeust
	m.Get("/state", GetState)
	m.Get("/account/profile", Profile)
	m.Get("/account/login", Login)
	m.Get("/account/logout", Logout)
	m.Run()
}

func Index() string {
	return "This is index"
}

func Coords() string {
	return "coords"
}

func Move(params martini.Params) string { // this is fake function for now. ToDo: change it to use actual Point
	fmt.Println("Scuka kruta")
	return "Hello " + params["x"] + params["y"]
}

func GetState(params martini.Params) (result string) { // Todo: change return type to json
	current_state := State{}
	serialized_state, err := json.Marshal(current_state)
	if err != nil {
		result = "{'status', 'failed'}"
	} else {
		result = string(serialized_state)
	}
	return
}

func Profile(params martini.Params) (result string) {
	result = "Profile"
	return
}

func Login(params martini.Params) (result string) {
	result = "Login"
	return
}

func Logout(params martini.Params) (result string) {
	result = "Logout"
	return
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"
)

func main() {
	fmt.Println("ReactJS Server running on 3000")
	http.Handle("/", http.FileServer(http.Dir("./")))
	http.ListenAndServe(":3000", nil)
}
<commit_msg>changed the port to 4000<commit_after>package main

import (
	"fmt"
	"net/http"
)

func main() {
	fmt.Println("ReactJS Server running on 4000")
	http.Handle("/", http.FileServer(http.Dir("./")))
	http.ListenAndServe(":4000", nil)
}
<|endoftext|>


<commit_before>package main

import (
    "flag"
    "database/sql"
    "github.com/orc/db"
    "github.com/orc/resources"
    "github.com/orc/router"
    "github.com/orc/mvc/controllers"
    "log"
    "net/http"
    "os"
)

var err error

func main() {
    log.Println("Server started.")

    db.DB, err = sql.Open("postgres", os.Getenv("DATABASE_URL"))
    defer db.DB.Close()

    if err != nil {
        log.Println("Error db connection: ", err.Error())
        os.Exit(1)
    }

    log.Println("DB CONNECTED")

    testData := flag.Bool("test-data", false, "to load test data")
    flag.Parse()

    db.Init()
    controllers.CreateRegistrationEvent()
    resources.LoadAdmin()

    if *testData == true {
        resources.Load()
    }

    // base := new(controllers.BaseController)
    // base.Index().LoadContestsFromCats()

    http.Handle("/", new(router.FastCGIServer))
    http.Handle("/js/", http.StripPrefix("/js/", http.FileServer(http.Dir("./static/js"))))
    http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("./static/css"))))
    http.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("./static/img"))))

    port := os.Getenv("PORT")
    if port == "" {
        port = "5000"
    }

    if err := http.ListenAndServe(":" + port, nil); err != nil {
        log.Println("Error listening: ", err.Error())
        os.Exit(1)
    }
}
<commit_msg>db init off<commit_after>package main

import (
    "flag"
    "database/sql"
    "github.com/orc/db"
    "github.com/orc/resources"
    "github.com/orc/router"
    "github.com/orc/mvc/controllers"
    "log"
    "net/http"
    "os"
)

var err error

func main() {
    log.Println("Server started.")

    db.DB, err = sql.Open("postgres", os.Getenv("DATABASE_URL"))
    defer db.DB.Close()

    if err != nil {
        log.Println("Error db connection: ", err.Error())
        os.Exit(1)
    }

    log.Println("DB CONNECTED")

    testData := flag.Bool("test-data", false, "to load test data")
    flag.Parse()

    // db.Init()
    controllers.CreateRegistrationEvent()
    resources.LoadAdmin()

    if *testData == true {
        resources.Load()
    }

    // base := new(controllers.BaseController)
    // base.Index().LoadContestsFromCats()

    http.Handle("/", new(router.FastCGIServer))
    http.Handle("/js/", http.StripPrefix("/js/", http.FileServer(http.Dir("./static/js"))))
    http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("./static/css"))))
    http.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("./static/img"))))

    port := os.Getenv("PORT")
    if port == "" {
        port = "5000"
    }

    if err := http.ListenAndServe(":" + port, nil); err != nil {
        log.Println("Error listening: ", err.Error())
        os.Exit(1)
    }
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"io"
	"log"
	"net/http"
	"net/url"
	"strings"

	"github.com/flynn/flynn-host/types"
	"github.com/flynn/go-discoverd"
	"github.com/flynn/go-dockerclient"
	"github.com/flynn/go-flynn/cluster"
	strowgerc "github.com/flynn/strowger/client"
	"github.com/flynn/strowger/types"
	"github.com/titanous/go-tigertonic"
)

func main() {
	var err error
	clusterc, err = cluster.NewClient()
	if err != nil {
		log.Fatal(err)
	}
	router, err = strowgerc.New()
	if err != nil {
		log.Fatal(err)
	}

	mux := tigertonic.NewTrieServeMux()
	mux.Handle("PUT", "/apps/{app_id}/domains/{domain}", tigertonic.Marshaled(addDomain))
	mux.Handle("POST", "/apps/{app_id}/formation/{formation_id}", tigertonic.Marshaled(changeFormation))
	mux.Handle("GET", "/apps/{app_id}/jobs", tigertonic.Marshaled(getJobs))
	mux.HandleFunc("GET", "/apps/{app_id}/jobs/{job_id}/logs", getJobLog)
	mux.HandleFunc("POST", "/apps/{app_id}/jobs", runJob)
	http.ListenAndServe("127.0.0.1:1200", tigertonic.Logged(mux, nil))
}

var clusterc *cluster.Client
var router strowgerc.Client

type Job struct {
	ID   string `json:"id"`
	Type string `json:"type"`
}

func addDomain(u *url.URL, h http.Header, data *struct{}) (int, http.Header, struct{}, error) {
	q := u.Query()
	if err := router.AddFrontend(&strowger.Config{Service: q.Get("app_id"), HTTPDomain: q.Get("domain")}); err != nil {
		return 500, nil, struct{}{}, err
	}
	return 200, nil, struct{}{}, nil
}

// GET /apps/{app_id}/jobs
func getJobs(u *url.URL, h http.Header) (int, http.Header, []Job, error) {
	hosts, err := clusterc.ListHosts()
	if err != nil {
		return 500, nil, nil, err
	}

	q := u.Query()
	prefix := q.Get("app_id") + "-"
	jobs := make([]Job, 0)
	for _, host := range hosts {
		for _, job := range host.Jobs {
			if strings.HasPrefix(job.ID, prefix) {
				typ := strings.Split(job.ID[len(prefix):], ".")[0]
				jobs = append(jobs, Job{ID: job.ID, Type: typ})
			}
		}
	}

	return 200, nil, jobs, nil
}

type Formation struct {
	Quantity int    `json:"quantity"`
	Type     string `json:"type"`
}

func shelfURL() string {
	services, _ := discoverd.Services("shelf", discoverd.DefaultTimeout)
	if len(services) < 1 {
		panic("Shelf is not discoverable")
	}
	return services[0].Addr
}

// POST /apps/{app_id}/formation/{formation_id}
func changeFormation(u *url.URL, h http.Header, req *Formation) (int, http.Header, *Formation, error) {
	hosts, err := clusterc.ListHosts()
	if err != nil {
		log.Println("scheduler state error", err)
		return 500, nil, nil, err
	}

	q := u.Query()
	req.Type = q.Get("formation_id")
	prefix := q.Get("app_id") + "-" + req.Type + "."
	var jobs []*host.Job
	for _, h := range hosts {
		for _, job := range h.Jobs {
			if strings.HasPrefix(job.ID, prefix) {
				if job.Attributes == nil {
					job.Attributes = make(map[string]string)
				}
				job.Attributes["host_id"] = h.ID
				jobs = append(jobs, job)
			}
		}
	}

	if req.Quantity < 0 {
		req.Quantity = 0
	}
	diff := req.Quantity - len(jobs)
	log.Printf("have %d %s, diff %d", len(jobs), req.Type, diff)
	if diff > 0 {
		config := &docker.Config{
			Image:        "flynn/slugrunner",
			Cmd:          []string{"start", req.Type},
			AttachStdout: true,
			AttachStderr: true,
			Env:          []string{"SLUG_URL=http://" + shelfURL() + "/" + q.Get("app_id") + ".tgz"},
		}
		if req.Type == "web" {
			config.Env = append(config.Env, "SD_NAME="+q.Get("app_id"))
		}
		addReq := &host.AddJobsReq{
			HostJobs: make(map[string][]*host.Job),
		}
	outer:
		for {
			for h := range hosts {
				addReq.HostJobs[h] = append(addReq.HostJobs[h], &host.Job{ID: cluster.RandomJobID(prefix), TCPPorts: 1, Config: config})
				diff--
				if diff == 0 {
					break outer
				}
			}
		}

		_, err = clusterc.AddJobs(addReq)
		if err != nil {
			log.Println("schedule error", err)
			return 500, nil, nil, err
		}
	} else if diff < 0 {
		for _, job := range jobs[:-diff] {
			host, err := clusterc.ConnectHost(job.Attributes["host_id"])
			if err != nil {
				log.Println("error connecting to", job.Attributes["host_id"], err)
				continue
			}
			if err := host.StopJob(job.ID); err != nil {
				log.Println("error stopping", job.ID, "on", job.Attributes["host_id"], err)
			}
		}
	}

	return 200, nil, req, nil
}

// GET /apps/{app_id}/jobs/{job_id}/logs
func getJobLog(w http.ResponseWriter, req *http.Request) {
	hosts, err := clusterc.ListHosts()
	if err != nil {
		w.WriteHeader(500)
		log.Println(err)
		return
	}

	q := req.URL.Query()
	jobID := q.Get("job_id")
	if prefix := q.Get("app_id") + "-"; !strings.HasPrefix(jobID, prefix) {
		jobID = prefix + jobID
	}
	var job *host.Job
	var h host.Host
outer:
	for _, h = range hosts {
		for _, job = range h.Jobs {
			if job.ID == jobID {
				break outer
			}
		}
		job = nil
	}
	if job == nil {
		w.WriteHeader(404)
		return
	}

	attachReq := &host.AttachReq{
		JobID: job.ID,
		Flags: host.AttachFlagStdout | host.AttachFlagStderr | host.AttachFlagLogs,
	}

	client, err := clusterc.ConnectHost(h.ID)
	if err != nil {
		w.WriteHeader(500)
		log.Println("lorne connect failed", err)
		return
	}
	attachConn, _, err := client.Attach(attachReq, false)
	if err != nil {
		w.WriteHeader(500)
		log.Println("attach failed", err)
		return
	}
	defer attachConn.Close()
	io.Copy(w, attachConn)
}

type NewJob struct {
	Cmd     []string          `json:"cmd"`
	Env     map[string]string `json:"env"`
	Attach  bool              `json:"attach"`
	TTY     bool              `json:"tty"`
	Columns int               `json:"tty_columns"`
	Lines   int               `json:"tty_lines"`
}

// POST /apps/{app_id}/jobs
func runJob(w http.ResponseWriter, req *http.Request) {
	var jobReq NewJob
	if err := json.NewDecoder(req.Body).Decode(&jobReq); err != nil {
		w.WriteHeader(500)
		log.Println(err)
		return
	}

	hosts, err := clusterc.ListHosts()
	if err != nil {
		w.WriteHeader(500)
		log.Println(err)
		return
	}
	// pick a random host
	var hostID string
	for hostID = range hosts {
		break
	}
	if hostID == "" {
		w.WriteHeader(500)
		log.Println("no hosts found")
		return
	}

	env := make([]string, 0, len(jobReq.Env))
	for k, v := range jobReq.Env {
		env = append(env, k+"="+v)
	}

	q := req.URL.Query()
	job := &host.Job{
		ID: cluster.RandomJobID(q.Get("app_id") + "-run."),
		Config: &docker.Config{
			Image:        "flynn/slugrunner",
			Cmd:          jobReq.Cmd,
			AttachStdin:  true,
			AttachStdout: true,
			AttachStderr: true,
			StdinOnce:    true,
			Env:          append(env, "SLUG_URL=http://"+shelfURL()+"/"+q.Get("app_id")+".tgz"),
		},
	}
	if jobReq.TTY {
		job.Config.Tty = true
	}
	if jobReq.Attach {
		job.Config.AttachStdin = true
		job.Config.StdinOnce = true
		job.Config.OpenStdin = true
	}

	var attachConn cluster.ReadWriteCloser
	var attachWait func() error
	if jobReq.Attach {
		attachReq := &host.AttachReq{
			JobID:  job.ID,
			Flags:  host.AttachFlagStdout | host.AttachFlagStderr | host.AttachFlagStdin | host.AttachFlagStream,
			Height: jobReq.Lines,
			Width:  jobReq.Columns,
		}
		client, err := clusterc.ConnectHost(hostID)
		if err != nil {
			w.WriteHeader(500)
			log.Println("lorne connect failed", err)
			return
		}
		attachConn, attachWait, err = client.Attach(attachReq, true)
		if err != nil {
			w.WriteHeader(500)
			log.Println("attach failed", err)
			return
		}
		defer attachConn.Close()
	}

	_, err = clusterc.AddJobs(&host.AddJobsReq{HostJobs: map[string][]*host.Job{hostID: {job}}})
	if err != nil {
		w.WriteHeader(500)
		log.Println("schedule failed", err)
		return
	}

	if jobReq.Attach {
		if err := attachWait(); err != nil {
			w.WriteHeader(500)
			log.Println("attach wait failed", err)
			return
		}
		w.Header().Set("Content-Type", "application/vnd.flynn.hijack")
		w.Header().Set("Content-Length", "0")
		w.WriteHeader(200)
		conn, _, err := w.(http.Hijacker).Hijack()
		if err != nil {
			return
		}
		defer conn.Close()

		done := make(chan struct{})
		copy := func(to cluster.ReadWriteCloser, from io.Reader) {
			io.Copy(to, from)
			to.CloseWrite()
			done <- struct{}{}
		}
		go copy(conn.(cluster.ReadWriteCloser), attachConn)
		go copy(attachConn, conn)
		<-done
		<-done

		return
	}
	w.WriteHeader(200)
}
<commit_msg>Update strowger client usage<commit_after>package main

import (
	"encoding/json"
	"io"
	"log"
	"net/http"
	"net/url"
	"strings"

	"github.com/flynn/flynn-host/types"
	"github.com/flynn/go-discoverd"
	"github.com/flynn/go-dockerclient"
	"github.com/flynn/go-flynn/cluster"
	strowgerc "github.com/flynn/strowger/client"
	"github.com/flynn/strowger/types"
	"github.com/titanous/go-tigertonic"
)

func main() {
	var err error
	clusterc, err = cluster.NewClient()
	if err != nil {
		log.Fatal(err)
	}
	router, err = strowgerc.New()
	if err != nil {
		log.Fatal(err)
	}

	mux := tigertonic.NewTrieServeMux()
	mux.Handle("PUT", "/apps/{app_id}/domains/{domain}", tigertonic.Marshaled(addDomain))
	mux.Handle("POST", "/apps/{app_id}/formation/{formation_id}", tigertonic.Marshaled(changeFormation))
	mux.Handle("GET", "/apps/{app_id}/jobs", tigertonic.Marshaled(getJobs))
	mux.HandleFunc("GET", "/apps/{app_id}/jobs/{job_id}/logs", getJobLog)
	mux.HandleFunc("POST", "/apps/{app_id}/jobs", runJob)
	http.ListenAndServe("127.0.0.1:1200", tigertonic.Logged(mux, nil))
}

var clusterc *cluster.Client
var router strowgerc.Client

type Job struct {
	ID   string `json:"id"`
	Type string `json:"type"`
}

func addDomain(u *url.URL, h http.Header, data *struct{}) (int, http.Header, struct{}, error) {
	q := u.Query()
	if err := router.AddRoute(&strowger.Config{Service: q.Get("app_id"), HTTPDomain: q.Get("domain")}); err != nil {
		return 500, nil, struct{}{}, err
	}
	return 200, nil, struct{}{}, nil
}

// GET /apps/{app_id}/jobs
func getJobs(u *url.URL, h http.Header) (int, http.Header, []Job, error) {
	hosts, err := clusterc.ListHosts()
	if err != nil {
		return 500, nil, nil, err
	}

	q := u.Query()
	prefix := q.Get("app_id") + "-"
	jobs := make([]Job, 0)
	for _, host := range hosts {
		for _, job := range host.Jobs {
			if strings.HasPrefix(job.ID, prefix) {
				typ := strings.Split(job.ID[len(prefix):], ".")[0]
				jobs = append(jobs, Job{ID: job.ID, Type: typ})
			}
		}
	}

	return 200, nil, jobs, nil
}

type Formation struct {
	Quantity int    `json:"quantity"`
	Type     string `json:"type"`
}

func shelfURL() string {
	services, _ := discoverd.Services("shelf", discoverd.DefaultTimeout)
	if len(services) < 1 {
		panic("Shelf is not discoverable")
	}
	return services[0].Addr
}

// POST /apps/{app_id}/formation/{formation_id}
func changeFormation(u *url.URL, h http.Header, req *Formation) (int, http.Header, *Formation, error) {
	hosts, err := clusterc.ListHosts()
	if err != nil {
		log.Println("scheduler state error", err)
		return 500, nil, nil, err
	}

	q := u.Query()
	req.Type = q.Get("formation_id")
	prefix := q.Get("app_id") + "-" + req.Type + "."
	var jobs []*host.Job
	for _, h := range hosts {
		for _, job := range h.Jobs {
			if strings.HasPrefix(job.ID, prefix) {
				if job.Attributes == nil {
					job.Attributes = make(map[string]string)
				}
				job.Attributes["host_id"] = h.ID
				jobs = append(jobs, job)
			}
		}
	}

	if req.Quantity < 0 {
		req.Quantity = 0
	}
	diff := req.Quantity - len(jobs)
	log.Printf("have %d %s, diff %d", len(jobs), req.Type, diff)
	if diff > 0 {
		config := &docker.Config{
			Image:        "flynn/slugrunner",
			Cmd:          []string{"start", req.Type},
			AttachStdout: true,
			AttachStderr: true,
			Env:          []string{"SLUG_URL=http://" + shelfURL() + "/" + q.Get("app_id") + ".tgz"},
		}
		if req.Type == "web" {
			config.Env = append(config.Env, "SD_NAME="+q.Get("app_id"))
		}
		addReq := &host.AddJobsReq{
			HostJobs: make(map[string][]*host.Job),
		}
	outer:
		for {
			for h := range hosts {
				addReq.HostJobs[h] = append(addReq.HostJobs[h], &host.Job{ID: cluster.RandomJobID(prefix), TCPPorts: 1, Config: config})
				diff--
				if diff == 0 {
					break outer
				}
			}
		}

		_, err = clusterc.AddJobs(addReq)
		if err != nil {
			log.Println("schedule error", err)
			return 500, nil, nil, err
		}
	} else if diff < 0 {
		for _, job := range jobs[:-diff] {
			host, err := clusterc.ConnectHost(job.Attributes["host_id"])
			if err != nil {
				log.Println("error connecting to", job.Attributes["host_id"], err)
				continue
			}
			if err := host.StopJob(job.ID); err != nil {
				log.Println("error stopping", job.ID, "on", job.Attributes["host_id"], err)
			}
		}
	}

	return 200, nil, req, nil
}

// GET /apps/{app_id}/jobs/{job_id}/logs
func getJobLog(w http.ResponseWriter, req *http.Request) {
	hosts, err := clusterc.ListHosts()
	if err != nil {
		w.WriteHeader(500)
		log.Println(err)
		return
	}

	q := req.URL.Query()
	jobID := q.Get("job_id")
	if prefix := q.Get("app_id") + "-"; !strings.HasPrefix(jobID, prefix) {
		jobID = prefix + jobID
	}
	var job *host.Job
	var h host.Host
outer:
	for _, h = range hosts {
		for _, job = range h.Jobs {
			if job.ID == jobID {
				break outer
			}
		}
		job = nil
	}
	if job == nil {
		w.WriteHeader(404)
		return
	}

	attachReq := &host.AttachReq{
		JobID: job.ID,
		Flags: host.AttachFlagStdout | host.AttachFlagStderr | host.AttachFlagLogs,
	}

	client, err := clusterc.ConnectHost(h.ID)
	if err != nil {
		w.WriteHeader(500)
		log.Println("lorne connect failed", err)
		return
	}
	attachConn, _, err := client.Attach(attachReq, false)
	if err != nil {
		w.WriteHeader(500)
		log.Println("attach failed", err)
		return
	}
	defer attachConn.Close()
	io.Copy(w, attachConn)
}

type NewJob struct {
	Cmd     []string          `json:"cmd"`
	Env     map[string]string `json:"env"`
	Attach  bool              `json:"attach"`
	TTY     bool              `json:"tty"`
	Columns int               `json:"tty_columns"`
	Lines   int               `json:"tty_lines"`
}

// POST /apps/{app_id}/jobs
func runJob(w http.ResponseWriter, req *http.Request) {
	var jobReq NewJob
	if err := json.NewDecoder(req.Body).Decode(&jobReq); err != nil {
		w.WriteHeader(500)
		log.Println(err)
		return
	}

	hosts, err := clusterc.ListHosts()
	if err != nil {
		w.WriteHeader(500)
		log.Println(err)
		return
	}
	// pick a random host
	var hostID string
	for hostID = range hosts {
		break
	}
	if hostID == "" {
		w.WriteHeader(500)
		log.Println("no hosts found")
		return
	}

	env := make([]string, 0, len(jobReq.Env))
	for k, v := range jobReq.Env {
		env = append(env, k+"="+v)
	}

	q := req.URL.Query()
	job := &host.Job{
		ID: cluster.RandomJobID(q.Get("app_id") + "-run."),
		Config: &docker.Config{
			Image:        "flynn/slugrunner",
			Cmd:          jobReq.Cmd,
			AttachStdin:  true,
			AttachStdout: true,
			AttachStderr: true,
			StdinOnce:    true,
			Env:          append(env, "SLUG_URL=http://"+shelfURL()+"/"+q.Get("app_id")+".tgz"),
		},
	}
	if jobReq.TTY {
		job.Config.Tty = true
	}
	if jobReq.Attach {
		job.Config.AttachStdin = true
		job.Config.StdinOnce = true
		job.Config.OpenStdin = true
	}

	var attachConn cluster.ReadWriteCloser
	var attachWait func() error
	if jobReq.Attach {
		attachReq := &host.AttachReq{
			JobID:  job.ID,
			Flags:  host.AttachFlagStdout | host.AttachFlagStderr | host.AttachFlagStdin | host.AttachFlagStream,
			Height: jobReq.Lines,
			Width:  jobReq.Columns,
		}
		client, err := clusterc.ConnectHost(hostID)
		if err != nil {
			w.WriteHeader(500)
			log.Println("lorne connect failed", err)
			return
		}
		attachConn, attachWait, err = client.Attach(attachReq, true)
		if err != nil {
			w.WriteHeader(500)
			log.Println("attach failed", err)
			return
		}
		defer attachConn.Close()
	}

	_, err = clusterc.AddJobs(&host.AddJobsReq{HostJobs: map[string][]*host.Job{hostID: {job}}})
	if err != nil {
		w.WriteHeader(500)
		log.Println("schedule failed", err)
		return
	}

	if jobReq.Attach {
		if err := attachWait(); err != nil {
			w.WriteHeader(500)
			log.Println("attach wait failed", err)
			return
		}
		w.Header().Set("Content-Type", "application/vnd.flynn.hijack")
		w.Header().Set("Content-Length", "0")
		w.WriteHeader(200)
		conn, _, err := w.(http.Hijacker).Hijack()
		if err != nil {
			return
		}
		defer conn.Close()

		done := make(chan struct{})
		copy := func(to cluster.ReadWriteCloser, from io.Reader) {
			io.Copy(to, from)
			to.CloseWrite()
			done <- struct{}{}
		}
		go copy(conn.(cluster.ReadWriteCloser), attachConn)
		go copy(attachConn, conn)
		<-done
		<-done

		return
	}
	w.WriteHeader(200)
}
<|endoftext|>


<commit_before>package owl

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = FDescribe("Tests AcquireLock(...)", itSkip.PrependBeforeEach(func() {

	var (
		scheduleName = "test-schedule-3"
	)

	AfterEach(inTx(
		`
		DELETE sl
		FROM owl_schedule sch
		LEFT JOIN owl_schedule_log sl
		ON sch.sch_id = sl.sl_sch_id
		WHERE sch_name LIKE 'test-schedule-%'
		`,
		`DELETE FROM owl_schedule WHERE sch_name LIKE 'test-schedule-%'`,
	))

	Context("when schedule non-exists", func() {
		It("should return uuid correctly", func() {
			s := &Schedule{
				Name:    scheduleName,
				Timeout: 1,
			}
			err := AcquireLock(s)

			GinkgoT().Logf("UUID=%v", s.Uuid)
			Expect(err).NotTo(HaveOccurred())
		})
	})

}))
<commit_msg>[OWL-1997] Remove focus.<commit_after>package owl

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Tests AcquireLock(...)", itSkip.PrependBeforeEach(func() {

	var (
		scheduleName = "test-schedule-3"
	)

	AfterEach(inTx(
		`
		DELETE sl
		FROM owl_schedule sch
		LEFT JOIN owl_schedule_log sl
		ON sch.sch_id = sl.sl_sch_id
		WHERE sch_name LIKE 'test-schedule-%'
		`,
		`DELETE FROM owl_schedule WHERE sch_name LIKE 'test-schedule-%'`,
	))

	Context("when schedule non-exists", func() {
		It("should return uuid correctly", func() {
			s := &Schedule{
				Name:    scheduleName,
				Timeout: 1,
			}
			err := AcquireLock(s)

			GinkgoT().Logf("UUID=%v", s.Uuid)
			Expect(err).NotTo(HaveOccurred())
		})
	})

}))
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"strconv"

	"github.com/codegangsta/cli"
	"github.com/toorop/govh"
	"github.com/toorop/govh/dedicated/server"
)

// getFwCmds return commands for firewall subsection
func getServerCmds(client *govh.OVHClient) (serverCmds []cli.Command) {
	sr, err := server.New(client)
	if err != nil {
		return
	}

	serverCmds = []cli.Command{
		{
			Name:        "list",
			Usage:       "Return a list of server ",
			Description: "ovh server list" + NLTAB + "Example: ovh server list",
			Flags: []cli.Flag{
				cli.BoolFlag{Name: "json", Usage: "output as JSON"},
			},
			Action: func(c *cli.Context) {
				servers, err := sr.List()
				dieOnError(err)
				for _, server := range servers {
					fmt.Println(server)
				}
				dieOk()
			},
		},
		{
			Name:        "properties",
			Usage:       "Return properties of a server ",
			Description: "ovh server properties SERVER [--json]" + NLTAB + "Example: ovh server properties ks323462.kimsufi.com",
			Flags: []cli.Flag{
				cli.BoolFlag{Name: "json", Usage: "output as JSON"},
			},
			Action: func(c *cli.Context) {
				dieIfArgsMiss(len(c.Args()), 1)
				properties, err := sr.GetProperties(c.Args().First())
				dieOnError(err)
				println(formatOutput(properties, c.Bool("json")))
				dieOk()
			},
		},

		{
			Name:        "tasks",
			Usage:       "Return a list of tasks for a server ",
			Description: "ovh server tasks SERVER [--function FUNCTION] [--status STATUS] [--json]" + NLTAB + "Example: ovh server tasks ns309865.ovh.net --function hardReboot --status done --json",
			Flags: []cli.Flag{
				cli.StringFlag{Name: "function", Value: "", Usage: "(optional) - filter by function. See https://api.ovh.com/console/#/dedicated/server/%7BserviceName%%7D/task#GET for availables functions.)"},
				cli.StringFlag{Name: "status", Value: "", Usage: "(optional) : filter by status. See [OVH doc](https://api.ovh.com/console/#/dedicated/server/%7BserviceName%%7D/task#GET) for availables status."},
				cli.BoolFlag{Name: "json", Usage: "output as JSON"},
			},
			Action: func(c *cli.Context) {
				dieIfArgsMiss(len(c.Args()), 1)
				tasks, err := sr.GetTasks(c.Args().First(), c.String("function"), c.String("status"))
				dieOnError(err)
				if c.Bool("json") {
					r, _ := json.Marshal(tasks)
					dieOk(string(r))
				}
				for _, task := range tasks {
					fmt.Println(task)
				}
				dieOk()
			},
		},

		{
			Name:        "task",
			Usage:       "Return properties of a server task",
			Description: "ovh server task SERVER TASKID" + NLTAB + "Example: ovh server task ns309865.ovh.net 456",
			Flags: []cli.Flag{
				cli.BoolFlag{Name: "json", Usage: "output as JSON"},
			},
			Action: func(c *cli.Context) {
				dieIfArgsMiss(len(c.Args()), 2)
				taskID, err := strconv.ParseInt(c.Args().Get(1), 10, 64)
				if err != nil {
					dieError(err)
				}
				task, err := sr.GetTask(c.Args().First(), int(taskID))
				dieOnError(err)
				println(formatOutput(task, c.Bool("json")))
				dieOk()
			},
		},

		{
			Name:        "canceltask",
			Usage:       "Cancel a server task",
			Description: "ovh server canceltask SERVER TASKID" + NLTAB + "Example: ovh server canceltask ks323462.kimsufi.com 4319579",
			Action: func(c *cli.Context) {
				taskID, err := strconv.ParseUint(c.Args().Get(1), 10, 64)
				if err != nil {
					dieError(err)
				}
				err = sr.CancelTask(c.Args().Get(0), int(taskID))
				dieOnError(err)
				dieOk()
			},
		},
		/*
			{
				Name:        "reboot",
				Usage:       "Create a new reboot task",
				Description: "ovh server reboot SERVER" + NLTAB + "Example: ovh server reboot ks323462.kimsufi.com",
				Action: func(c *cli.Context) {
					dieIfArgsMiss(len(c.Args()), 1)
					task, err := sr.Reboot(c.Args().First())
					dieOnError(err)
					fmt.Printf("Task ID: %d%s", task.Id, NL)
					fmt.Printf("Function: %s%s", task.Function, NL)
					fmt.Printf("Status: %s%s", task.Status, NL)
					fmt.Printf("Comment: %s%s", task.Comment, NL)
					fmt.Printf("Last Upadte: %s%s", task.LastUpdate, NL)
					fmt.Printf("Start Date: %s%s", task.StartDate, NL)
					fmt.Printf("Done Date: %s%s", task.DoneDate, NL)
					dieOk()
				},
			},*/
	}

	/*
		switch cmd.Action {
		// List
		case "list":
			servers, err := serverR.List()
			if err != nil {
				return err
			}
			for _, s := range servers {
				resp = fmt.Sprintf("%s%s\r\n", resp, s)
			}
			if len(resp) > 2 {
				resp = resp[0 : len(resp)-2]
			}
			dieOk(resp)
			break
		// get server properties
		// ./ovh server getTasks SERVER_NAME FUNCTION STATUS
		case "properties":
			if len(cmd.Args) != 3 {
				return errors.New("\"server properties\" needs an argument see doc at https://github.com/Toorop/govh/blob/master/cli/README.md")
			}

			properties, err := serverR.GetProperties(strings.ToLower(cmd.Args[2]))
			if err != nil {
				return err
			}
			fmt.Printf("ID: %d%s", properties.Id, NL)
			fmt.Printf("Name: %s%s", properties.Name, NL)
			fmt.Printf("Ip: %s%s", properties.Ip, NL)
			fmt.Printf("Datacenter: %s%s", properties.Datacenter, NL)
			fmt.Printf("ProfessionalUse: %t%s", properties.ProfessionalUse, NL)
			fmt.Printf("CommercialRange: %s%s", properties.CommercialRange, NL)
			fmt.Printf("Os: %s%s", properties.Os, NL)
			fmt.Printf("State: %s%s", properties.State, NL)
			fmt.Printf("Reverse: %s%s", properties.Reverse, NL)
			fmt.Printf("Monitored: %t%s", properties.Monitored, NL)
			fmt.Printf("Rack: %s%s", properties.Rack, NL)
			fmt.Printf("RootDevice: %s%s", properties.RootDevice, NL)
			fmt.Printf("LinkSpeed: %d%s", properties.LinkSpeed, NL)
			fmt.Printf("Bootid: %d%s", properties.BootId, NL)
			dieOk("")
			break

		// Get available netboots ID for this server
		case "availableNetboots":
			if len(cmd.Args) < 3 {
				return errors.New("\"server availableNetboots\" needs an argument see doc at https://github.com/Toorop/govh/blob/master/cli/README.md")
			}
			var netbootIds []int
			if len(cmd.Args) == 3 {
				netbootIds, err = serverR.GetNetboots(strings.ToLower(cmd.Args[2]))
			} else {
				netbootIds, err = serverR.GetNetboots(strings.ToLower(cmd.Args[2]), cmd.Args[3])
			}
			if err != nil {
				return
			}
			for _, id := range netbootIds {
				fmt.Println(id)
			}
			break

		// Get server tasks
		case "getTasks":
			function := "all"
			status := "all"

			if len(cmd.Args) < 3 {
				return errors.New("\"server getTasks\" needs an argument see doc at https://github.com/Toorop/govh/blob/master/cli/README.md")
			}

			if len(cmd.Args) > 5 {
				return errors.New("\"server getTasks\" too many arguments - see doc at https://github.com/Toorop/govh/blob/master/cli/README.md")
			}

			// serverName
			serverName := strings.ToLower(cmd.Args[2])

			// function
			if len(cmd.Args) > 3 {
				function = cmd.Args[3]
				if len(cmd.Args) > 4 {
					status = cmd.Args[4]
				}
			}

			tasks, err := serverR.GetTasks(serverName, function, status)
			if err != nil {
				return err
			}
			resp := ""
			for _, task := range tasks {
				resp = fmt.Sprintf("%s%d\r\n", resp, task)
			}
			if len(resp) > 2 {
				resp = resp[0 : len(resp)-2]
			}
			dieOk(resp)
			break

		// Get task properties
		// ./ovh server getTaskProperties SERVER_NAME TASK_ID
		case "getTaskProperties":
			if len(cmd.Args) != 4 {
				return errors.New("\"server getTaskProperties\" needs an argument see doc at https://github.com/Toorop/govh/blob/master/cli/README.md")
			}

			serverName := strings.ToLower(cmd.Args[2])
			taskId, err := strconv.ParseUint(cmd.Args[3], 10, 64)
			if err != nil {
				return err
			}
			task, err := serverR.GetTaskProperties(serverName, taskId)
			if err != nil {
				return err
			}
			fmt.Printf("Task ID: %d%s", task.Id, NL)
			fmt.Printf("Function: %s%s", task.Function, NL)
			fmt.Printf("Status: %s%s", task.Status, NL)
			fmt.Printf("Comment: %s%s", task.Comment, NL)
			fmt.Printf("Last Upadte: %s%s", task.LastUpdate, NL)
			fmt.Printf("Start Date: %s%s", task.StartDate, NL)
			fmt.Printf("Done Date: %s%s", task.DoneDate, NL)
			dieOk("")
			break

		// Cancel task (if possible)
		// ./ovh server cancelTask SERVER_NAME TASK_ID
		case "cancelTask":
			if len(cmd.Args) != 4 {
				return errors.New("\"server cancel Task\" needs an argument see doc at https://github.com/Toorop/govh/blob/master/cli/README.md")
			}
			serverName := strings.ToLower(cmd.Args[2])
			taskId, err := strconv.ParseUint(cmd.Args[3], 10, 64)
			if err != nil {
				return err
			}
			err = serverR.CancelTask(serverName, taskId)
			if err != nil {
				return err
			}
			dieOk("Success task " + cmd.Args[3] + "cancelled")
			break

		// Reboot
		case "reboot":
			if len(cmd.Args) != 3 {
				return errors.New("\"server reboot\" needs an argument see doc at https://github.com/Toorop/govh/blob/master/cli/README.md")
			}
			task, err := serverR.Reboot(strings.ToLower(cmd.Args[2]))
			if err != nil {
				return err
			}
			fmt.Printf("Task ID: %d%s", task.Id, NL)
			fmt.Printf("Function: %s%s", task.Function, NL)
			fmt.Printf("Status: %s%s", task.Status, NL)
			fmt.Printf("Comment: %s%s", task.Comment, NL)
			fmt.Printf("Last Upadte: %s%s", task.LastUpdate, NL)
			fmt.Printf("Start Date: %s%s", task.StartDate, NL)
			fmt.Printf("Done Date: %s%s", task.DoneDate, NL)
			dieOk("")
			break

		default:
			return errors.New(fmt.Sprintf("This action : '%s' is not valid or not implemented yet !", strings.Join(cmd.Args, " ")))
		}*/
	return
}
<commit_msg>server reboot<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"strconv"

	"github.com/codegangsta/cli"
	"github.com/toorop/govh"
	"github.com/toorop/govh/dedicated/server"
)

// getFwCmds return commands for firewall subsection
func getServerCmds(client *govh.OVHClient) (serverCmds []cli.Command) {
	sr, err := server.New(client)
	if err != nil {
		return
	}

	serverCmds = []cli.Command{
		{
			Name:        "list",
			Usage:       "Return a list of server ",
			Description: "ovh server list" + NLTAB + "Example: ovh server list",
			Flags: []cli.Flag{
				cli.BoolFlag{Name: "json", Usage: "output as JSON"},
			},
			Action: func(c *cli.Context) {
				servers, err := sr.List()
				dieOnError(err)
				for _, server := range servers {
					fmt.Println(server)
				}
				dieOk()
			},
		},
		{
			Name:        "properties",
			Usage:       "Return properties of a server ",
			Description: "ovh server properties SERVER [--json]" + NLTAB + "Example: ovh server properties ks323462.kimsufi.com",
			Flags: []cli.Flag{
				cli.BoolFlag{Name: "json", Usage: "output as JSON"},
			},
			Action: func(c *cli.Context) {
				dieIfArgsMiss(len(c.Args()), 1)
				properties, err := sr.GetProperties(c.Args().First())
				dieOnError(err)
				println(formatOutput(properties, c.Bool("json")))
				dieOk()
			},
		},

		{
			Name:        "tasks",
			Usage:       "Return a list of tasks for a server ",
			Description: "ovh server tasks SERVER [--function FUNCTION] [--status STATUS] [--json]" + NLTAB + "Example: ovh server tasks ns309865.ovh.net --function hardReboot --status done --json",
			Flags: []cli.Flag{
				cli.StringFlag{Name: "function", Value: "", Usage: "(optional) - filter by function. See https://api.ovh.com/console/#/dedicated/server/%7BserviceName%%7D/task#GET for availables functions.)"},
				cli.StringFlag{Name: "status", Value: "", Usage: "(optional) : filter by status. See [OVH doc](https://api.ovh.com/console/#/dedicated/server/%7BserviceName%%7D/task#GET) for availables status."},
				cli.BoolFlag{Name: "json", Usage: "output as JSON"},
			},
			Action: func(c *cli.Context) {
				dieIfArgsMiss(len(c.Args()), 1)
				tasks, err := sr.GetTasks(c.Args().First(), c.String("function"), c.String("status"))
				dieOnError(err)
				if c.Bool("json") {
					r, _ := json.Marshal(tasks)
					dieOk(string(r))
				}
				for _, task := range tasks {
					fmt.Println(task)
				}
				dieOk()
			},
		},

		{
			Name:        "task",
			Usage:       "Return properties of a server task",
			Description: "ovh server task SERVER TASKID" + NLTAB + "Example: ovh server task ns309865.ovh.net 456",
			Flags: []cli.Flag{
				cli.BoolFlag{Name: "json", Usage: "output as JSON"},
			},
			Action: func(c *cli.Context) {
				dieIfArgsMiss(len(c.Args()), 2)
				taskID, err := strconv.ParseInt(c.Args().Get(1), 10, 64)
				if err != nil {
					dieError(err)
				}
				task, err := sr.GetTask(c.Args().First(), int(taskID))
				dieOnError(err)
				println(formatOutput(task, c.Bool("json")))
				dieOk()
			},
		},

		{
			Name:        "canceltask",
			Usage:       "Cancel a server task",
			Description: "ovh server canceltask SERVER TASKID" + NLTAB + "Example: ovh server canceltask ks323462.kimsufi.com 4319579",
			Action: func(c *cli.Context) {
				taskID, err := strconv.ParseUint(c.Args().Get(1), 10, 64)
				if err != nil {
					dieError(err)
				}
				err = sr.CancelTask(c.Args().Get(0), int(taskID))
				dieOnError(err)
				dieOk()
			},
		},

		{
			Name:        "reboot",
			Usage:       "Create a new reboot task",
			Description: "ovh server reboot SERVER [--json]" + NLTAB + "Example: ovh server reboot ks323462.kimsufi.com --json",
			Flags: []cli.Flag{
				cli.BoolFlag{Name: "json", Usage: "output as JSON"},
			},
			Action: func(c *cli.Context) {
				dieIfArgsMiss(len(c.Args()), 1)
				task, err := sr.Reboot(c.Args().First())
				dieOnError(err)
				println(formatOutput(task, c.Bool("json")))
				dieOk()
			},
		},
	}

	/*
		switch cmd.Action {
		// List
		case "list":
			servers, err := serverR.List()
			if err != nil {
				return err
			}
			for _, s := range servers {
				resp = fmt.Sprintf("%s%s\r\n", resp, s)
			}
			if len(resp) > 2 {
				resp = resp[0 : len(resp)-2]
			}
			dieOk(resp)
			break
		// get server properties
		// ./ovh server getTasks SERVER_NAME FUNCTION STATUS
		case "properties":
			if len(cmd.Args) != 3 {
				return errors.New("\"server properties\" needs an argument see doc at https://github.com/Toorop/govh/blob/master/cli/README.md")
			}

			properties, err := serverR.GetProperties(strings.ToLower(cmd.Args[2]))
			if err != nil {
				return err
			}
			fmt.Printf("ID: %d%s", properties.Id, NL)
			fmt.Printf("Name: %s%s", properties.Name, NL)
			fmt.Printf("Ip: %s%s", properties.Ip, NL)
			fmt.Printf("Datacenter: %s%s", properties.Datacenter, NL)
			fmt.Printf("ProfessionalUse: %t%s", properties.ProfessionalUse, NL)
			fmt.Printf("CommercialRange: %s%s", properties.CommercialRange, NL)
			fmt.Printf("Os: %s%s", properties.Os, NL)
			fmt.Printf("State: %s%s", properties.State, NL)
			fmt.Printf("Reverse: %s%s", properties.Reverse, NL)
			fmt.Printf("Monitored: %t%s", properties.Monitored, NL)
			fmt.Printf("Rack: %s%s", properties.Rack, NL)
			fmt.Printf("RootDevice: %s%s", properties.RootDevice, NL)
			fmt.Printf("LinkSpeed: %d%s", properties.LinkSpeed, NL)
			fmt.Printf("Bootid: %d%s", properties.BootId, NL)
			dieOk("")
			break

		// Get available netboots ID for this server
		case "availableNetboots":
			if len(cmd.Args) < 3 {
				return errors.New("\"server availableNetboots\" needs an argument see doc at https://github.com/Toorop/govh/blob/master/cli/README.md")
			}
			var netbootIds []int
			if len(cmd.Args) == 3 {
				netbootIds, err = serverR.GetNetboots(strings.ToLower(cmd.Args[2]))
			} else {
				netbootIds, err = serverR.GetNetboots(strings.ToLower(cmd.Args[2]), cmd.Args[3])
			}
			if err != nil {
				return
			}
			for _, id := range netbootIds {
				fmt.Println(id)
			}
			break

		// Get server tasks
		case "getTasks":
			function := "all"
			status := "all"

			if len(cmd.Args) < 3 {
				return errors.New("\"server getTasks\" needs an argument see doc at https://github.com/Toorop/govh/blob/master/cli/README.md")
			}

			if len(cmd.Args) > 5 {
				return errors.New("\"server getTasks\" too many arguments - see doc at https://github.com/Toorop/govh/blob/master/cli/README.md")
			}

			// serverName
			serverName := strings.ToLower(cmd.Args[2])

			// function
			if len(cmd.Args) > 3 {
				function = cmd.Args[3]
				if len(cmd.Args) > 4 {
					status = cmd.Args[4]
				}
			}

			tasks, err := serverR.GetTasks(serverName, function, status)
			if err != nil {
				return err
			}
			resp := ""
			for _, task := range tasks {
				resp = fmt.Sprintf("%s%d\r\n", resp, task)
			}
			if len(resp) > 2 {
				resp = resp[0 : len(resp)-2]
			}
			dieOk(resp)
			break

		// Get task properties
		// ./ovh server getTaskProperties SERVER_NAME TASK_ID
		case "getTaskProperties":
			if len(cmd.Args) != 4 {
				return errors.New("\"server getTaskProperties\" needs an argument see doc at https://github.com/Toorop/govh/blob/master/cli/README.md")
			}

			serverName := strings.ToLower(cmd.Args[2])
			taskId, err := strconv.ParseUint(cmd.Args[3], 10, 64)
			if err != nil {
				return err
			}
			task, err := serverR.GetTaskProperties(serverName, taskId)
			if err != nil {
				return err
			}
			fmt.Printf("Task ID: %d%s", task.Id, NL)
			fmt.Printf("Function: %s%s", task.Function, NL)
			fmt.Printf("Status: %s%s", task.Status, NL)
			fmt.Printf("Comment: %s%s", task.Comment, NL)
			fmt.Printf("Last Upadte: %s%s", task.LastUpdate, NL)
			fmt.Printf("Start Date: %s%s", task.StartDate, NL)
			fmt.Printf("Done Date: %s%s", task.DoneDate, NL)
			dieOk("")
			break

		// Cancel task (if possible)
		// ./ovh server cancelTask SERVER_NAME TASK_ID
		case "cancelTask":
			if len(cmd.Args) != 4 {
				return errors.New("\"server cancel Task\" needs an argument see doc at https://github.com/Toorop/govh/blob/master/cli/README.md")
			}
			serverName := strings.ToLower(cmd.Args[2])
			taskId, err := strconv.ParseUint(cmd.Args[3], 10, 64)
			if err != nil {
				return err
			}
			err = serverR.CancelTask(serverName, taskId)
			if err != nil {
				return err
			}
			dieOk("Success task " + cmd.Args[3] + "cancelled")
			break

		// Reboot
		case "reboot":
			if len(cmd.Args) != 3 {
				return errors.New("\"server reboot\" needs an argument see doc at https://github.com/Toorop/govh/blob/master/cli/README.md")
			}
			task, err := serverR.Reboot(strings.ToLower(cmd.Args[2]))
			if err != nil {
				return err
			}
			fmt.Printf("Task ID: %d%s", task.Id, NL)
			fmt.Printf("Function: %s%s", task.Function, NL)
			fmt.Printf("Status: %s%s", task.Status, NL)
			fmt.Printf("Comment: %s%s", task.Comment, NL)
			fmt.Printf("Last Upadte: %s%s", task.LastUpdate, NL)
			fmt.Printf("Start Date: %s%s", task.StartDate, NL)
			fmt.Printf("Done Date: %s%s", task.DoneDate, NL)
			dieOk("")
			break

		default:
			return errors.New(fmt.Sprintf("This action : '%s' is not valid or not implemented yet !", strings.Join(cmd.Args, " ")))
		}*/
	return
}
<|endoftext|>


<commit_before>package devd

import (
	"crypto/tls"
	"fmt"
	"html/template"
	"net"
	"net/http"
	"regexp"
	"strings"
	"time"

	"golang.org/x/net/context"

	"github.com/GeertJohan/go.rice"
	"github.com/goji/httpauth"

	"github.com/cortesi/devd/httpctx"
	"github.com/cortesi/devd/inject"
	"github.com/cortesi/devd/livereload"
	"github.com/cortesi/devd/ricetemp"
	"github.com/cortesi/devd/slowdown"
	"github.com/cortesi/devd/termlog"
	"github.com/cortesi/devd/timer"
)

const (
	// Version is the current version of devd
	Version  = "0.2"
	portLow  = 8000
	portHigh = 10000
)

func pickPort(addr string, low int, high int, tls bool) (net.Listener, error) {
	firstTry := 80
	if tls {
		firstTry = 443
	}
	hl, err := net.Listen("tcp", fmt.Sprintf("%v:%d", addr, firstTry))
	if err == nil {
		return hl, nil
	}
	for i := low; i < high; i++ {
		hl, err := net.Listen("tcp", fmt.Sprintf("%v:%d", addr, i))
		if err == nil {
			return hl, nil
		}
	}
	return nil, fmt.Errorf("Could not find open port.")
}

func getTLSConfig(path string) (t *tls.Config, err error) {
	config := &tls.Config{}
	if config.NextProtos == nil {
		config.NextProtos = []string{"http/1.1"}
	}
	config.Certificates = make([]tls.Certificate, 1)
	config.Certificates[0], err = tls.LoadX509KeyPair(path, path)
	if err != nil {
		return nil, err
	}
	return config, nil
}

// This filthy hack works in conjunction with hostPortStrip to restore the
// original request host after mux match.
func revertOriginalHost(r *http.Request) {
	original := r.Header.Get("_devd_original_host")
	if original != "" {
		r.Host = original
		r.Header.Del("_devd_original_host")
	}
}

// We can remove the mangling once this is fixed:
// 		https://github.com/golang/go/issues/10463
func hostPortStrip(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		host, _, err := net.SplitHostPort(r.Host)
		if err == nil {
			original := r.Host
			r.Host = host
			r.Header.Set("_devd_original_host", original)
		}
		next.ServeHTTP(w, r)
	})
}

func matchStringAny(regexps []*regexp.Regexp, s string) bool {
	for _, r := range regexps {
		if r.MatchString(s) {
			return true
		}
	}
	return false
}

func formatURL(tls bool, httpIP string, port int) string {
	proto := "http"
	if tls {
		proto = "https"
	}
	host := httpIP
	if httpIP == "0.0.0.0" || httpIP == "127.0.0.1" {
		host = "devd.io"
	}
	if port == 443 && tls {
		return fmt.Sprintf("https://%s", host)
	}
	if port == 80 && !tls {
		return fmt.Sprintf("http://%s", host)
	}
	return fmt.Sprintf("%s://%s:%d", proto, host, port)
}

// Credentials is a simple username/password pair
type Credentials struct {
	username string
	password string
}

// CredentialsFromSpec creates a set of credentials from a spec
func CredentialsFromSpec(spec string) (*Credentials, error) {
	parts := strings.SplitN(spec, ":", 2)
	if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
		return nil, fmt.Errorf("Invalid credential spec: %s", spec)
	}
	return &Credentials{parts[0], parts[1]}, nil
}

// Devd represents the devd server options
type Devd struct {
	Routes RouteCollection

	// Shaping
	Latency  int
	DownKbps uint
	UpKbps   uint

	// Livereload
	LivereloadRoutes bool
	WatchPaths       []string
	Excludes         []string

	// Logging
	IgnoreLogs []*regexp.Regexp

	// Password protection
	Credentials *Credentials
}

// WrapHandler wraps an httpctx.Handler in the paraphernalia needed by devd for
// logging, latency, and so forth.
func (dd *Devd) WrapHandler(log termlog.Logger, next httpctx.Handler) http.Handler {
	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		revertOriginalHost(r)
		timr := timer.Timer{}
		sublog := log.Group()
		defer func() {
			timing := termlog.DefaultPalette.Timestamp.SprintFunc()("timing: ")
			sublog.SayAs("timer", timing+timr.String())
			sublog.Done()
		}()
		if matchStringAny(dd.IgnoreLogs, fmt.Sprintf("%s%s", r.URL.Host, r.RequestURI)) {
			sublog.Quiet()
		}
		timr.RequestHeaders()
		time.Sleep(time.Millisecond * time.Duration(dd.Latency))
		sublog.Say("%s %s", r.Method, r.URL)
		LogHeader(sublog, r.Header)
		ctx := timr.NewContext(context.Background())
		ctx = termlog.NewContext(ctx, sublog)
		next.ServeHTTPContext(
			ctx,
			&ResponseLogWriter{Log: sublog, Resp: w, Timer: &timr},
			r,
		)
	})
	return h
}

// HasLivereload tells us if liverload is enabled
func (dd *Devd) HasLivereload() bool {
	if dd.LivereloadRoutes || len(dd.WatchPaths) > 0 {
		return true
	}
	return false
}

// AddRoutes adds route specifications to the server
func (dd *Devd) AddRoutes(specs []string) error {
	dd.Routes = make(RouteCollection)
	for _, s := range specs {
		err := dd.Routes.Add(s)
		if err != nil {
			return fmt.Errorf("Invalid route specification: %s", err)
		}
	}
	return nil
}

// AddIgnores adds log ignore patterns to the server
func (dd *Devd) AddIgnores(specs []string) error {
	dd.IgnoreLogs = make([]*regexp.Regexp, 0, 0)
	for _, expr := range specs {
		v, err := regexp.Compile(expr)
		if err != nil {
			return fmt.Errorf("%s", err)
		}
		dd.IgnoreLogs = append(dd.IgnoreLogs, v)
	}
	return nil
}

// HandleNotFound handles pages not found. In particular, this handler is used
// when we have no matching route for a request. This also means it's not
// useful to inject the livereload paraphernalia here.
func HandleNotFound(templates *template.Template) httpctx.Handler {
	return httpctx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusNotFound)
		err := templates.Lookup("404.html").Execute(w, nil)
		if err != nil {
			logger := termlog.FromContext(ctx)
			logger.Shout("Could not execute template: %s", err)
		}
	})
}

// Router constructs the main Devd router that serves all requests
func (dd *Devd) Router(logger termlog.Logger, templates *template.Template) (http.Handler, error) {
	mux := http.NewServeMux()
	hasGlobal := false

	ci := inject.CopyInject{}
	if dd.HasLivereload() {
		ci = livereload.Injector
	}

	for match, route := range dd.Routes {
		if match == "/" {
			hasGlobal = true
		}
		handler := dd.WrapHandler(
			logger,
			route.Endpoint.Handler(templates, ci),
		)
		handler = http.StripPrefix(route.Path, handler)
		mux.Handle(match, handler)
	}
	if dd.HasLivereload() {
		lr := livereload.NewServer("livereload", logger)
		mux.Handle(livereload.EndpointPath, lr)
		mux.Handle(livereload.ScriptPath, http.HandlerFunc(lr.ServeScript))
		if dd.LivereloadRoutes {
			err := WatchRoutes(dd.Routes, lr, dd.Excludes, logger)
			if err != nil {
				return nil, fmt.Errorf("Could not watch routes for livereload: %s", err)
			}
		}
		if len(dd.WatchPaths) > 0 {
			err := WatchPaths(dd.WatchPaths, dd.Excludes, lr, logger)
			if err != nil {
				return nil, fmt.Errorf("Could not watch path for livereload: %s", err)
			}
		}
	}
	if !hasGlobal {
		mux.Handle(
			"/",
			dd.WrapHandler(logger, HandleNotFound(templates)),
		)
	}
	var h = http.Handler(mux)
	if dd.Credentials != nil {
		h = httpauth.SimpleBasicAuth(
			dd.Credentials.username, dd.Credentials.password,
		)(h)
	}
	return hostPortStrip(h), nil
}

// Serve starts the devd server. The callback is called with the serving URL
// just before service starts.
func (dd *Devd) Serve(address string, port int, certFile string, logger termlog.Logger, callback func(string)) error {
	templates, err := ricetemp.MakeTemplates(rice.MustFindBox("templates"))
	if err != nil {
		return fmt.Errorf("Error loading templates: %s", err)
	}
	mux, err := dd.Router(logger, templates)
	if err != nil {
		return err
	}
	var tlsConfig *tls.Config
	var tlsEnabled bool
	if certFile != "" {
		tlsConfig, err = getTLSConfig(certFile)
		if err != nil {
			return fmt.Errorf("Could not load certs: %s", err)
		}
		tlsEnabled = true
	}

	var hl net.Listener
	if port > 0 {
		hl, err = net.Listen("tcp", fmt.Sprintf("%v:%d", address, port))
	} else {
		hl, err = pickPort(address, portLow, portHigh, tlsEnabled)
	}
	if err != nil {
		return fmt.Errorf("Could not bind to port: %s", err)
	}

	if tlsConfig != nil {
		hl = tls.NewListener(hl, tlsConfig)
	}

	hl = slowdown.NewSlowListener(hl, dd.UpKbps*1024, dd.DownKbps*1024)
	url := formatURL(tlsEnabled, address, hl.Addr().(*net.TCPAddr).Port)
	logger.Say("Listening on %s (%s)", url, hl.Addr().String())
	server := &http.Server{Addr: hl.Addr().String(), Handler: mux}
	callback(url)
	err = server.Serve(hl)
	logger.Shout("Server stopped: %v", err)
	return nil
}
<commit_msg>Bump version.<commit_after>package devd

import (
	"crypto/tls"
	"fmt"
	"html/template"
	"net"
	"net/http"
	"regexp"
	"strings"
	"time"

	"golang.org/x/net/context"

	"github.com/GeertJohan/go.rice"
	"github.com/goji/httpauth"

	"github.com/cortesi/devd/httpctx"
	"github.com/cortesi/devd/inject"
	"github.com/cortesi/devd/livereload"
	"github.com/cortesi/devd/ricetemp"
	"github.com/cortesi/devd/slowdown"
	"github.com/cortesi/devd/termlog"
	"github.com/cortesi/devd/timer"
)

const (
	// Version is the current version of devd
	Version  = "0.3"
	portLow  = 8000
	portHigh = 10000
)

func pickPort(addr string, low int, high int, tls bool) (net.Listener, error) {
	firstTry := 80
	if tls {
		firstTry = 443
	}
	hl, err := net.Listen("tcp", fmt.Sprintf("%v:%d", addr, firstTry))
	if err == nil {
		return hl, nil
	}
	for i := low; i < high; i++ {
		hl, err := net.Listen("tcp", fmt.Sprintf("%v:%d", addr, i))
		if err == nil {
			return hl, nil
		}
	}
	return nil, fmt.Errorf("Could not find open port.")
}

func getTLSConfig(path string) (t *tls.Config, err error) {
	config := &tls.Config{}
	if config.NextProtos == nil {
		config.NextProtos = []string{"http/1.1"}
	}
	config.Certificates = make([]tls.Certificate, 1)
	config.Certificates[0], err = tls.LoadX509KeyPair(path, path)
	if err != nil {
		return nil, err
	}
	return config, nil
}

// This filthy hack works in conjunction with hostPortStrip to restore the
// original request host after mux match.
func revertOriginalHost(r *http.Request) {
	original := r.Header.Get("_devd_original_host")
	if original != "" {
		r.Host = original
		r.Header.Del("_devd_original_host")
	}
}

// We can remove the mangling once this is fixed:
// 		https://github.com/golang/go/issues/10463
func hostPortStrip(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		host, _, err := net.SplitHostPort(r.Host)
		if err == nil {
			original := r.Host
			r.Host = host
			r.Header.Set("_devd_original_host", original)
		}
		next.ServeHTTP(w, r)
	})
}

func matchStringAny(regexps []*regexp.Regexp, s string) bool {
	for _, r := range regexps {
		if r.MatchString(s) {
			return true
		}
	}
	return false
}

func formatURL(tls bool, httpIP string, port int) string {
	proto := "http"
	if tls {
		proto = "https"
	}
	host := httpIP
	if httpIP == "0.0.0.0" || httpIP == "127.0.0.1" {
		host = "devd.io"
	}
	if port == 443 && tls {
		return fmt.Sprintf("https://%s", host)
	}
	if port == 80 && !tls {
		return fmt.Sprintf("http://%s", host)
	}
	return fmt.Sprintf("%s://%s:%d", proto, host, port)
}

// Credentials is a simple username/password pair
type Credentials struct {
	username string
	password string
}

// CredentialsFromSpec creates a set of credentials from a spec
func CredentialsFromSpec(spec string) (*Credentials, error) {
	parts := strings.SplitN(spec, ":", 2)
	if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
		return nil, fmt.Errorf("Invalid credential spec: %s", spec)
	}
	return &Credentials{parts[0], parts[1]}, nil
}

// Devd represents the devd server options
type Devd struct {
	Routes RouteCollection

	// Shaping
	Latency  int
	DownKbps uint
	UpKbps   uint

	// Livereload
	LivereloadRoutes bool
	WatchPaths       []string
	Excludes         []string

	// Logging
	IgnoreLogs []*regexp.Regexp

	// Password protection
	Credentials *Credentials
}

// WrapHandler wraps an httpctx.Handler in the paraphernalia needed by devd for
// logging, latency, and so forth.
func (dd *Devd) WrapHandler(log termlog.Logger, next httpctx.Handler) http.Handler {
	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		revertOriginalHost(r)
		timr := timer.Timer{}
		sublog := log.Group()
		defer func() {
			timing := termlog.DefaultPalette.Timestamp.SprintFunc()("timing: ")
			sublog.SayAs("timer", timing+timr.String())
			sublog.Done()
		}()
		if matchStringAny(dd.IgnoreLogs, fmt.Sprintf("%s%s", r.URL.Host, r.RequestURI)) {
			sublog.Quiet()
		}
		timr.RequestHeaders()
		time.Sleep(time.Millisecond * time.Duration(dd.Latency))
		sublog.Say("%s %s", r.Method, r.URL)
		LogHeader(sublog, r.Header)
		ctx := timr.NewContext(context.Background())
		ctx = termlog.NewContext(ctx, sublog)
		next.ServeHTTPContext(
			ctx,
			&ResponseLogWriter{Log: sublog, Resp: w, Timer: &timr},
			r,
		)
	})
	return h
}

// HasLivereload tells us if liverload is enabled
func (dd *Devd) HasLivereload() bool {
	if dd.LivereloadRoutes || len(dd.WatchPaths) > 0 {
		return true
	}
	return false
}

// AddRoutes adds route specifications to the server
func (dd *Devd) AddRoutes(specs []string) error {
	dd.Routes = make(RouteCollection)
	for _, s := range specs {
		err := dd.Routes.Add(s)
		if err != nil {
			return fmt.Errorf("Invalid route specification: %s", err)
		}
	}
	return nil
}

// AddIgnores adds log ignore patterns to the server
func (dd *Devd) AddIgnores(specs []string) error {
	dd.IgnoreLogs = make([]*regexp.Regexp, 0, 0)
	for _, expr := range specs {
		v, err := regexp.Compile(expr)
		if err != nil {
			return fmt.Errorf("%s", err)
		}
		dd.IgnoreLogs = append(dd.IgnoreLogs, v)
	}
	return nil
}

// HandleNotFound handles pages not found. In particular, this handler is used
// when we have no matching route for a request. This also means it's not
// useful to inject the livereload paraphernalia here.
func HandleNotFound(templates *template.Template) httpctx.Handler {
	return httpctx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusNotFound)
		err := templates.Lookup("404.html").Execute(w, nil)
		if err != nil {
			logger := termlog.FromContext(ctx)
			logger.Shout("Could not execute template: %s", err)
		}
	})
}

// Router constructs the main Devd router that serves all requests
func (dd *Devd) Router(logger termlog.Logger, templates *template.Template) (http.Handler, error) {
	mux := http.NewServeMux()
	hasGlobal := false

	ci := inject.CopyInject{}
	if dd.HasLivereload() {
		ci = livereload.Injector
	}

	for match, route := range dd.Routes {
		if match == "/" {
			hasGlobal = true
		}
		handler := dd.WrapHandler(
			logger,
			route.Endpoint.Handler(templates, ci),
		)
		handler = http.StripPrefix(route.Path, handler)
		mux.Handle(match, handler)
	}
	if dd.HasLivereload() {
		lr := livereload.NewServer("livereload", logger)
		mux.Handle(livereload.EndpointPath, lr)
		mux.Handle(livereload.ScriptPath, http.HandlerFunc(lr.ServeScript))
		if dd.LivereloadRoutes {
			err := WatchRoutes(dd.Routes, lr, dd.Excludes, logger)
			if err != nil {
				return nil, fmt.Errorf("Could not watch routes for livereload: %s", err)
			}
		}
		if len(dd.WatchPaths) > 0 {
			err := WatchPaths(dd.WatchPaths, dd.Excludes, lr, logger)
			if err != nil {
				return nil, fmt.Errorf("Could not watch path for livereload: %s", err)
			}
		}
	}
	if !hasGlobal {
		mux.Handle(
			"/",
			dd.WrapHandler(logger, HandleNotFound(templates)),
		)
	}
	var h = http.Handler(mux)
	if dd.Credentials != nil {
		h = httpauth.SimpleBasicAuth(
			dd.Credentials.username, dd.Credentials.password,
		)(h)
	}
	return hostPortStrip(h), nil
}

// Serve starts the devd server. The callback is called with the serving URL
// just before service starts.
func (dd *Devd) Serve(address string, port int, certFile string, logger termlog.Logger, callback func(string)) error {
	templates, err := ricetemp.MakeTemplates(rice.MustFindBox("templates"))
	if err != nil {
		return fmt.Errorf("Error loading templates: %s", err)
	}
	mux, err := dd.Router(logger, templates)
	if err != nil {
		return err
	}
	var tlsConfig *tls.Config
	var tlsEnabled bool
	if certFile != "" {
		tlsConfig, err = getTLSConfig(certFile)
		if err != nil {
			return fmt.Errorf("Could not load certs: %s", err)
		}
		tlsEnabled = true
	}

	var hl net.Listener
	if port > 0 {
		hl, err = net.Listen("tcp", fmt.Sprintf("%v:%d", address, port))
	} else {
		hl, err = pickPort(address, portLow, portHigh, tlsEnabled)
	}
	if err != nil {
		return fmt.Errorf("Could not bind to port: %s", err)
	}

	if tlsConfig != nil {
		hl = tls.NewListener(hl, tlsConfig)
	}

	hl = slowdown.NewSlowListener(hl, dd.UpKbps*1024, dd.DownKbps*1024)
	url := formatURL(tlsEnabled, address, hl.Addr().(*net.TCPAddr).Port)
	logger.Say("Listening on %s (%s)", url, hl.Addr().String())
	server := &http.Server{Addr: hl.Addr().String(), Handler: mux}
	callback(url)
	err = server.Serve(hl)
	logger.Shout("Server stopped: %v", err)
	return nil
}
<|endoftext|>