<commit_before>/*
 * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package main

import (
	"errors"
	"fmt"
	"net"
	"net/http"
	"os"
	"runtime"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/minio/cli"
	"github.com/minio/mc/pkg/console"
)

var serverCmd = cli.Command{
	Name:  "server",
	Usage: "Start Minio cloud storage server.",
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "address",
			Value: ":9000",
		},
	},
	Action: serverMain,
	CustomHelpTemplate: `NAME:
  minio {{.Name}} - {{.Usage}}

USAGE:
  minio {{.Name}} [OPTIONS] PATH

OPTIONS:
  {{range .Flags}}{{.}}
  {{end}}
ENVIRONMENT VARIABLES:
  MINIO_ACCESS_KEY: Access key string of 5 to 20 characters in length.
  MINIO_SECRET_KEY: Secret key string of 8 to 40 characters in length.

EXAMPLES:
  1. Start minio server.
      $ minio {{.Name}} /home/shared

  2. Start minio server bound to a specific IP:PORT, when you have multiple network interfaces.
      $ minio {{.Name}} --address 192.168.1.101:9000 /home/shared

  3. Start minio server on Windows.
      $ minio {{.Name}} C:\MyShare

  4. Start minio server 8 disks to enable erasure coded layer with 4 data and 4 parity.
      $ minio {{.Name}} /mnt/export1/backend /mnt/export2/backend /mnt/export3/backend /mnt/export4/backend \
          /mnt/export5/backend /mnt/export6/backend /mnt/export7/backend /mnt/export8/backend
`,
}

type serverCmdConfig struct {
	serverAddr  string
	exportPaths []string
}

// configureServer configure a new server instance
func configureServer(srvCmdConfig serverCmdConfig) *http.Server {
	// Minio server config
	apiServer := &http.Server{
		Addr: srvCmdConfig.serverAddr,
		// Adding timeout of 10 minutes for unresponsive client connections.
		ReadTimeout:    600 * time.Second,
		WriteTimeout:   600 * time.Second,
		Handler:        configureServerHandler(srvCmdConfig),
		MaxHeaderBytes: 1 << 20,
	}

	// Returns configured HTTP server.
	return apiServer
}

// getListenIPs - gets all the ips to listen on.
func getListenIPs(httpServerConf *http.Server) (hosts []string, port string) {
	host, port, err := net.SplitHostPort(httpServerConf.Addr)
	fatalIf(err, "Unable to parse host port.")

	switch {
	case host != "":
		hosts = append(hosts, host)
	default:
		addrs, err := net.InterfaceAddrs()
		fatalIf(err, "Unable to determine network interface address.")
		for _, addr := range addrs {
			if addr.Network() == "ip+net" {
				host := strings.Split(addr.String(), "/")[0]
				if ip := net.ParseIP(host); ip.To4() != nil {
					hosts = append(hosts, host)
				}
			}
		}
	}
	return hosts, port
}

// Print listen ips.
func printListenIPs(tls bool, hosts []string, port string) {
	for _, host := range hosts {
		if tls {
			console.Printf("    https://%s:%s\n", host, port)
		} else {
			console.Printf("    http://%s:%s\n", host, port)
		}
	}
}

// initServerConfig initialize server config.
func initServerConfig(c *cli.Context) {
	// Save new config.
	err := serverConfig.Save()
	fatalIf(err, "Unable to save config.")

	// Fetch access keys from environment variables if any and update the config.
	accessKey := os.Getenv("MINIO_ACCESS_KEY")
	secretKey := os.Getenv("MINIO_SECRET_KEY")

	// Validate if both keys are specified and they are valid save them.
	if accessKey != "" && secretKey != "" {
		if !isValidAccessKey.MatchString(accessKey) {
			fatalIf(errInvalidArgument, "Invalid access key.")
		}
		if !isValidSecretKey.MatchString(secretKey) {
			fatalIf(errInvalidArgument, "Invalid secret key.")
		}
		serverConfig.SetCredential(credential{
			AccessKeyID:     accessKey,
			SecretAccessKey: secretKey,
		})
	}

	// Set maxOpenFiles, This is necessary since default operating
	// system limits of 1024, 2048 are not enough for Minio server.
	setMaxOpenFiles()
	// Do not fail if this is not allowed, lower limits are fine as well.
}

// Check server arguments.
func checkServerSyntax(c *cli.Context) {
	if !c.Args().Present() || c.Args().First() == "help" {
		cli.ShowCommandHelpAndExit(c, "server", 1)
	}
}

// Extract port number from address address should be of the form host:port.
func getPort(address string) int {
	_, portStr, err := net.SplitHostPort(address)
	fatalIf(err, "Unable to parse host port.")
	portInt, err := strconv.Atoi(portStr)
	fatalIf(err, "Invalid port number.")
	return portInt
}

// Make sure that none of the other processes are listening on the
// specified port on any of the interfaces.
//
// On linux if a process is listening on 127.0.0.1:9000 then Listen()
// on ":9000" fails with the error "port already in use".
// However on Mac OSX Listen() on ":9000" falls back to the IPv6 address.
// This causes confusion on Mac OSX that minio server is not reachable
// on 127.0.0.1 even though minio server is running. So before we start
// the minio server we make sure that the port is free on all the IPs.
func checkPortAvailability(port int) {
	isAddrInUse := func(err error) bool {
		// Check if the syscall error is EADDRINUSE.
		// EADDRINUSE is the system call error if another process is
		// already listening at the specified port.
		neterr, ok := err.(*net.OpError)
		if !ok {
			return false
		}
		osErr, ok := neterr.Err.(*os.SyscallError)
		if !ok {
			return false
		}
		sysErr, ok := osErr.Err.(syscall.Errno)
		if !ok {
			return false
		}
		if sysErr != syscall.EADDRINUSE {
			return false
		}
		return true
	}
	ifcs, err := net.Interfaces()
	if err != nil {
		fatalIf(err, "Unable to list interfaces.")
	}
	for _, ifc := range ifcs {
		addrs, err := ifc.Addrs()
		if err != nil {
			fatalIf(err, "Unable to list addresses on interface %s.", ifc.Name)
		}
		for _, addr := range addrs {
			ipnet, ok := addr.(*net.IPNet)
			if !ok {
				errorIf(errors.New(""), "Failed to assert type on (*net.IPNet) interface.")
				continue
			}
			ip := ipnet.IP
			network := "tcp4"
			if ip.To4() == nil {
				network = "tcp6"
			}
			tcpAddr := net.TCPAddr{IP: ip, Port: port, Zone: ifc.Name}
			l, err := net.ListenTCP(network, &tcpAddr)
			if err != nil {
				if isAddrInUse(err) {
					// Fail if port is already in use.
					fatalIf(err, "Unable to listen on %s:%.d.", tcpAddr.IP, tcpAddr.Port)
				} else {
					// Ignore other errors.
					continue
				}
			}
			if err = l.Close(); err != nil {
				fatalIf(err, "Unable to close listener on %s:%.d.", tcpAddr.IP, tcpAddr.Port)
			}
		}
	}
}

func serverMain(c *cli.Context) {
	// check 'server' cli arguments.
	checkServerSyntax(c)

	// Initialize server config.
	initServerConfig(c)

	// Server address.
	serverAddress := c.String("address")

	host, port, _ := net.SplitHostPort(serverAddress)
	// If port empty, default to port '80'
	if port == "" {
		port = "80"
		// if SSL is enabled, choose port as "443" instead.
		if isSSL() {
			port = "443"
		}
	}

	// Check if requested port is available.
	checkPortAvailability(getPort(net.JoinHostPort(host, port)))

	// Save all command line args as export paths.
	exportPaths := c.Args()

	// Configure server.
	apiServer := configureServer(serverCmdConfig{
		serverAddr:  serverAddress,
		exportPaths: exportPaths,
	})

	// Credential.
	cred := serverConfig.GetCredential()

	// Region.
	region := serverConfig.GetRegion()

	// Print credentials and region.
	console.Println("\n" + cred.String() + "  " + colorMagenta("Region: ") + colorWhite(region))

	hosts, port := getListenIPs(apiServer) // get listen ips and port.
	tls := apiServer.TLSConfig != nil      // 'true' if TLS is enabled.

	console.Println("\nMinio Object Storage:")
	// Print api listen ips.
	printListenIPs(tls, hosts, port)

	console.Println("\nMinio Browser:")
	// Print browser listen ips.
	printListenIPs(tls, hosts, port)

	console.Println("\nTo configure Minio Client:")

	// Figure out right endpoint for 'mc'.
	endpoint := fmt.Sprintf("http://%s:%s", hosts[0], port)
	if tls {
		endpoint = fmt.Sprintf("https://%s:%s", hosts[0], port)
	}

	// Download 'mc' info.
	if runtime.GOOS == "windows" {
		console.Printf("    Download 'mc' from https://dl.minio.io/client/mc/release/%s-%s/mc.exe\n", runtime.GOOS, runtime.GOARCH)
		console.Printf("    $ mc.exe config host add myminio %s %s %s\n", endpoint, cred.AccessKeyID, cred.SecretAccessKey)
	} else {
		console.Printf("    $ wget https://dl.minio.io/client/mc/release/%s-%s/mc\n", runtime.GOOS, runtime.GOARCH)
		console.Printf("    $ chmod 755 mc\n")
		console.Printf("    $ ./mc config host add myminio %s %s %s\n", endpoint, cred.AccessKeyID, cred.SecretAccessKey)
	}

	// Start server.
	var err error
	// Configure TLS if certs are available.
	if isSSL() {
		err = apiServer.ListenAndServeTLS(mustGetCertFile(), mustGetKeyFile())
	} else {
		// Fallback to http.
		err = apiServer.ListenAndServe()
	}
	fatalIf(err, "Failed to start minio server.")
}
<commit_msg>server: fix to have readable timeout value (#1823)<commit_after>/*
 * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package main

import (
	"errors"
	"fmt"
	"net"
	"net/http"
	"os"
	"runtime"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/minio/cli"
	"github.com/minio/mc/pkg/console"
)

var serverCmd = cli.Command{
	Name:  "server",
	Usage: "Start Minio cloud storage server.",
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "address",
			Value: ":9000",
		},
	},
	Action: serverMain,
	CustomHelpTemplate: `NAME:
  minio {{.Name}} - {{.Usage}}

USAGE:
  minio {{.Name}} [OPTIONS] PATH

OPTIONS:
  {{range .Flags}}{{.}}
  {{end}}
ENVIRONMENT VARIABLES:
  MINIO_ACCESS_KEY: Access key string of 5 to 20 characters in length.
  MINIO_SECRET_KEY: Secret key string of 8 to 40 characters in length.

EXAMPLES:
  1. Start minio server.
      $ minio {{.Name}} /home/shared

  2. Start minio server bound to a specific IP:PORT, when you have multiple network interfaces.
      $ minio {{.Name}} --address 192.168.1.101:9000 /home/shared

  3. Start minio server on Windows.
      $ minio {{.Name}} C:\MyShare

  4. Start minio server 8 disks to enable erasure coded layer with 4 data and 4 parity.
      $ minio {{.Name}} /mnt/export1/backend /mnt/export2/backend /mnt/export3/backend /mnt/export4/backend \
          /mnt/export5/backend /mnt/export6/backend /mnt/export7/backend /mnt/export8/backend
`,
}

type serverCmdConfig struct {
	serverAddr  string
	exportPaths []string
}

// configureServer configure a new server instance
func configureServer(srvCmdConfig serverCmdConfig) *http.Server {
	// Minio server config
	apiServer := &http.Server{
		Addr: srvCmdConfig.serverAddr,
		// Adding timeout of 10 minutes for unresponsive client connections.
		ReadTimeout:    10 * time.Minute,
		WriteTimeout:   10 * time.Minute,
		Handler:        configureServerHandler(srvCmdConfig),
		MaxHeaderBytes: 1 << 20,
	}

	// Returns configured HTTP server.
	return apiServer
}

// getListenIPs - gets all the ips to listen on.
func getListenIPs(httpServerConf *http.Server) (hosts []string, port string) {
	host, port, err := net.SplitHostPort(httpServerConf.Addr)
	fatalIf(err, "Unable to parse host port.")

	switch {
	case host != "":
		hosts = append(hosts, host)
	default:
		addrs, err := net.InterfaceAddrs()
		fatalIf(err, "Unable to determine network interface address.")
		for _, addr := range addrs {
			if addr.Network() == "ip+net" {
				host := strings.Split(addr.String(), "/")[0]
				if ip := net.ParseIP(host); ip.To4() != nil {
					hosts = append(hosts, host)
				}
			}
		}
	}
	return hosts, port
}

// Print listen ips.
func printListenIPs(tls bool, hosts []string, port string) {
	for _, host := range hosts {
		if tls {
			console.Printf("    https://%s:%s\n", host, port)
		} else {
			console.Printf("    http://%s:%s\n", host, port)
		}
	}
}

// initServerConfig initialize server config.
func initServerConfig(c *cli.Context) {
	// Save new config.
	err := serverConfig.Save()
	fatalIf(err, "Unable to save config.")

	// Fetch access keys from environment variables if any and update the config.
	accessKey := os.Getenv("MINIO_ACCESS_KEY")
	secretKey := os.Getenv("MINIO_SECRET_KEY")

	// Validate if both keys are specified and they are valid save them.
	if accessKey != "" && secretKey != "" {
		if !isValidAccessKey.MatchString(accessKey) {
			fatalIf(errInvalidArgument, "Invalid access key.")
		}
		if !isValidSecretKey.MatchString(secretKey) {
			fatalIf(errInvalidArgument, "Invalid secret key.")
		}
		serverConfig.SetCredential(credential{
			AccessKeyID:     accessKey,
			SecretAccessKey: secretKey,
		})
	}

	// Set maxOpenFiles, This is necessary since default operating
	// system limits of 1024, 2048 are not enough for Minio server.
	setMaxOpenFiles()
	// Do not fail if this is not allowed, lower limits are fine as well.
}

// Check server arguments.
func checkServerSyntax(c *cli.Context) {
	if !c.Args().Present() || c.Args().First() == "help" {
		cli.ShowCommandHelpAndExit(c, "server", 1)
	}
}

// Extract port number from address address should be of the form host:port.
func getPort(address string) int {
	_, portStr, err := net.SplitHostPort(address)
	fatalIf(err, "Unable to parse host port.")
	portInt, err := strconv.Atoi(portStr)
	fatalIf(err, "Invalid port number.")
	return portInt
}

// Make sure that none of the other processes are listening on the
// specified port on any of the interfaces.
//
// On linux if a process is listening on 127.0.0.1:9000 then Listen()
// on ":9000" fails with the error "port already in use".
// However on Mac OSX Listen() on ":9000" falls back to the IPv6 address.
// This causes confusion on Mac OSX that minio server is not reachable
// on 127.0.0.1 even though minio server is running. So before we start
// the minio server we make sure that the port is free on all the IPs.
func checkPortAvailability(port int) {
	isAddrInUse := func(err error) bool {
		// Check if the syscall error is EADDRINUSE.
		// EADDRINUSE is the system call error if another process is
		// already listening at the specified port.
		neterr, ok := err.(*net.OpError)
		if !ok {
			return false
		}
		osErr, ok := neterr.Err.(*os.SyscallError)
		if !ok {
			return false
		}
		sysErr, ok := osErr.Err.(syscall.Errno)
		if !ok {
			return false
		}
		if sysErr != syscall.EADDRINUSE {
			return false
		}
		return true
	}
	ifcs, err := net.Interfaces()
	if err != nil {
		fatalIf(err, "Unable to list interfaces.")
	}
	for _, ifc := range ifcs {
		addrs, err := ifc.Addrs()
		if err != nil {
			fatalIf(err, "Unable to list addresses on interface %s.", ifc.Name)
		}
		for _, addr := range addrs {
			ipnet, ok := addr.(*net.IPNet)
			if !ok {
				errorIf(errors.New(""), "Failed to assert type on (*net.IPNet) interface.")
				continue
			}
			ip := ipnet.IP
			network := "tcp4"
			if ip.To4() == nil {
				network = "tcp6"
			}
			tcpAddr := net.TCPAddr{IP: ip, Port: port, Zone: ifc.Name}
			l, err := net.ListenTCP(network, &tcpAddr)
			if err != nil {
				if isAddrInUse(err) {
					// Fail if port is already in use.
					fatalIf(err, "Unable to listen on %s:%.d.", tcpAddr.IP, tcpAddr.Port)
				} else {
					// Ignore other errors.
					continue
				}
			}
			if err = l.Close(); err != nil {
				fatalIf(err, "Unable to close listener on %s:%.d.", tcpAddr.IP, tcpAddr.Port)
			}
		}
	}
}

func serverMain(c *cli.Context) {
	// check 'server' cli arguments.
	checkServerSyntax(c)

	// Initialize server config.
	initServerConfig(c)

	// Server address.
	serverAddress := c.String("address")

	host, port, _ := net.SplitHostPort(serverAddress)
	// If port empty, default to port '80'
	if port == "" {
		port = "80"
		// if SSL is enabled, choose port as "443" instead.
		if isSSL() {
			port = "443"
		}
	}

	// Check if requested port is available.
	checkPortAvailability(getPort(net.JoinHostPort(host, port)))

	// Save all command line args as export paths.
	exportPaths := c.Args()

	// Configure server.
	apiServer := configureServer(serverCmdConfig{
		serverAddr:  serverAddress,
		exportPaths: exportPaths,
	})

	// Credential.
	cred := serverConfig.GetCredential()

	// Region.
	region := serverConfig.GetRegion()

	// Print credentials and region.
	console.Println("\n" + cred.String() + "  " + colorMagenta("Region: ") + colorWhite(region))

	hosts, port := getListenIPs(apiServer) // get listen ips and port.
	tls := apiServer.TLSConfig != nil      // 'true' if TLS is enabled.

	console.Println("\nMinio Object Storage:")
	// Print api listen ips.
	printListenIPs(tls, hosts, port)

	console.Println("\nMinio Browser:")
	// Print browser listen ips.
	printListenIPs(tls, hosts, port)

	console.Println("\nTo configure Minio Client:")

	// Figure out right endpoint for 'mc'.
	endpoint := fmt.Sprintf("http://%s:%s", hosts[0], port)
	if tls {
		endpoint = fmt.Sprintf("https://%s:%s", hosts[0], port)
	}

	// Download 'mc' info.
	if runtime.GOOS == "windows" {
		console.Printf("    Download 'mc' from https://dl.minio.io/client/mc/release/%s-%s/mc.exe\n", runtime.GOOS, runtime.GOARCH)
		console.Printf("    $ mc.exe config host add myminio %s %s %s\n", endpoint, cred.AccessKeyID, cred.SecretAccessKey)
	} else {
		console.Printf("    $ wget https://dl.minio.io/client/mc/release/%s-%s/mc\n", runtime.GOOS, runtime.GOARCH)
		console.Printf("    $ chmod 755 mc\n")
		console.Printf("    $ ./mc config host add myminio %s %s %s\n", endpoint, cred.AccessKeyID, cred.SecretAccessKey)
	}

	// Start server.
	var err error
	// Configure TLS if certs are available.
	if isSSL() {
		err = apiServer.ListenAndServeTLS(mustGetCertFile(), mustGetKeyFile())
	} else {
		// Fallback to http.
		err = apiServer.ListenAndServe()
	}
	fatalIf(err, "Failed to start minio server.")
}
<|endoftext|>


<commit_before>package server

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

	raven "github.com/getsentry/raven-go"
	"github.com/gin-gonic/contrib/cors"
	"github.com/gin-gonic/contrib/sentry"
	"github.com/gin-gonic/gin"
	"github.com/thoas/picfit/config"
	"github.com/thoas/picfit/middleware"
	"github.com/thoas/picfit/middleware/context"
	"github.com/thoas/picfit/server/handlers"
	"github.com/thoas/stats"
)

type HTTPServer struct {
	engine *gin.Engine
	config config.Config
}

func NewHTTPServer(cfg config.Config, opt ...Option) (*HTTPServer, error) {
	opts := NewOptions(opt...)

	server := &HTTPServer{
		config: cfg,
	}
	err := server.Init(opts)
	if err != nil {
		return nil, err
	}

	return server, nil
}

func (s *HTTPServer) Init(opts Options) error {
	router := gin.New()

	if s.config.Debug {
		router.Use(gin.Recovery())
	}

	if s.config.Logger.GetLevel() == "debug" {
		router.Use(gin.Logger())
	}

	methods := map[string]gin.HandlerFunc{
		"redirect": handlers.Redirect,
		"display":  handlers.Display,
		"get":      handlers.Get,
	}

	if s.config.Sentry != nil {
		client, err := raven.NewClient(s.config.Sentry.DSN, s.config.Sentry.Tags)

		if err != nil {
			return err
		}

		router.Use(sentry.Recovery(client, true))
	}

	router.Use(context.SetContext(opts.Context))

	if s.config.AllowedOrigins != nil && s.config.AllowedMethods != nil {
		allowedOrigins := s.config.AllowedOrigins

		allowAllOrigins := false

		if len(allowedOrigins) == 1 {
			if allowedOrigins[0] == "*" {
				allowAllOrigins = true
			}
		}

		if allowAllOrigins {
			allowedOrigins = nil
		}

		router.Use(cors.New(cors.Config{
			AllowAllOrigins: allowAllOrigins,
			AllowedOrigins:  allowedOrigins,
			AllowedMethods:  s.config.AllowedMethods,
			AllowedHeaders:  s.config.AllowedHeaders,
		}))
	}

	router.GET("/healthcheck", handlers.Healthcheck)

	if s.config.Options.EnableStats {
		s := stats.New()

		router.Use(func() gin.HandlerFunc {
			return func(c *gin.Context) {
				beginning, recorder := s.Begin(c.Writer)
				c.Next()
				s.End(beginning, recorder)
			}
		}())

		router.GET("/stats", func(c *gin.Context) {
			c.JSON(http.StatusOK, s.Data())
		})
	}

	for name, view := range methods {
		views := []gin.HandlerFunc{
			middleware.ParametersParser(),
			middleware.KeyParser(),
			middleware.Security(s.config.SecretKey),
			middleware.URLParser(s.config.Options.MimetypeDetector),
			middleware.OperationParser(),
			middleware.RestrictSizes(s.config.Options.AllowedSizes),
			view,
		}

		router.GET(fmt.Sprintf("/%s", name), views...)

		if s.config.Storage != nil && s.config.Storage.Source != nil {
			router.GET(fmt.Sprintf("/%s/*parameters", name), views...)
		}
	}

	if s.config.Options.EnableUpload {
		router.POST("/upload", handlers.Upload)
	}

	if s.config.Options.EnableDelete {
		router.DELETE("/*path", handlers.Delete)
	}

	s.engine = router

	return nil
}

func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	s.engine.ServeHTTP(w, req)
}

// Run loads a new http server
func (s *HTTPServer) Run() error {
	s.engine.Run(fmt.Sprintf(":%s", strconv.Itoa(s.config.Port)))

	return nil
}
<commit_msg>ref: remove embedding for HTTPServer<commit_after>package server

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

	raven "github.com/getsentry/raven-go"
	"github.com/gin-gonic/contrib/cors"
	"github.com/gin-gonic/contrib/sentry"
	"github.com/gin-gonic/gin"
	"github.com/thoas/picfit/config"
	"github.com/thoas/picfit/middleware"
	"github.com/thoas/picfit/middleware/context"
	"github.com/thoas/picfit/server/handlers"
	"github.com/thoas/stats"
)

type HTTPServer struct {
	*gin.Engine
	config config.Config
}

func NewHTTPServer(cfg config.Config, opt ...Option) (*HTTPServer, error) {
	opts := NewOptions(opt...)

	server := &HTTPServer{
		config: cfg,
	}
	err := server.Init(opts)
	if err != nil {
		return nil, err
	}

	return server, nil
}

func (s *HTTPServer) Init(opts Options) error {
	router := gin.New()

	if s.config.Debug {
		router.Use(gin.Recovery())
	}

	if s.config.Logger.GetLevel() == "debug" {
		router.Use(gin.Logger())
	}

	methods := map[string]gin.HandlerFunc{
		"redirect": handlers.Redirect,
		"display":  handlers.Display,
		"get":      handlers.Get,
	}

	if s.config.Sentry != nil {
		client, err := raven.NewClient(s.config.Sentry.DSN, s.config.Sentry.Tags)

		if err != nil {
			return err
		}

		router.Use(sentry.Recovery(client, true))
	}

	router.Use(context.SetContext(opts.Context))

	if s.config.AllowedOrigins != nil && s.config.AllowedMethods != nil {
		allowedOrigins := s.config.AllowedOrigins

		allowAllOrigins := false

		if len(allowedOrigins) == 1 {
			if allowedOrigins[0] == "*" {
				allowAllOrigins = true
			}
		}

		if allowAllOrigins {
			allowedOrigins = nil
		}

		router.Use(cors.New(cors.Config{
			AllowAllOrigins: allowAllOrigins,
			AllowedOrigins:  allowedOrigins,
			AllowedMethods:  s.config.AllowedMethods,
			AllowedHeaders:  s.config.AllowedHeaders,
		}))
	}

	router.GET("/healthcheck", handlers.Healthcheck)

	if s.config.Options.EnableStats {
		s := stats.New()

		router.Use(func() gin.HandlerFunc {
			return func(c *gin.Context) {
				beginning, recorder := s.Begin(c.Writer)
				c.Next()
				s.End(beginning, recorder)
			}
		}())

		router.GET("/stats", func(c *gin.Context) {
			c.JSON(http.StatusOK, s.Data())
		})
	}

	for name, view := range methods {
		views := []gin.HandlerFunc{
			middleware.ParametersParser(),
			middleware.KeyParser(),
			middleware.Security(s.config.SecretKey),
			middleware.URLParser(s.config.Options.MimetypeDetector),
			middleware.OperationParser(),
			middleware.RestrictSizes(s.config.Options.AllowedSizes),
			view,
		}

		router.GET(fmt.Sprintf("/%s", name), views...)

		if s.config.Storage != nil && s.config.Storage.Source != nil {
			router.GET(fmt.Sprintf("/%s/*parameters", name), views...)
		}
	}

	if s.config.Options.EnableUpload {
		router.POST("/upload", handlers.Upload)
	}

	if s.config.Options.EnableDelete {
		router.DELETE("/*path", handlers.Delete)
	}

	s.Engine = router

	return nil
}

// Run loads a new http server
func (s *HTTPServer) Run() error {
	s.Engine.Run(fmt.Sprintf(":%s", strconv.Itoa(s.config.Port)))

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/json"
	"flag"
	"fmt"
	"gnat"
	"log"
	"net/http"

	"strings"

	"time"

	"os"

	"github.com/ccding/go-stun/stun"
	b58 "github.com/jbenet/go-base58"
)

// cap on how many routing nodes
// should be remembered
var maxRouteCacheSize = 500

// dht of peer GNAT nodes
var dht *gnat.DHT

// websocket hub to handle client messages
var hub *Hub

// mapping of ip addresses to gnat peers
var forwardingCache map[string]*gnat.NetworkNode

func main() {

	printWelcomeMessage()

	forwardingCache = make(map[string]*gnat.NetworkNode)

	// test the network to discovery what type of NAT (if any)
	// the client is behind.
	fmt.Print("1) Testing network...")
	nat, host, err := stun.NewClient().Discover()
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: a problem occured while testing your network.")
		fmt.Fprintf(os.Stderr, "Try again later.")
		os.Exit(1)
	}
	fmt.Println("done.")

	// make sure network is behind leniant NAT
	fmt.Println("  Network NAT configuration detected: " + nat.String())
	fmt.Println("  Public IP address: " + host.String())
	if nat == stun.NATNone || nat == stun.NATFull || nat == stun.NATPortRestricted {

		// generate an ID from the digest of the IP addr
		selfIP := strings.Split(host.String(), ":")[0]
		ipDigest := sha256.Sum256([]byte(selfIP))

		// initialize the GNAT dht node
		initializeDHT(ipDigest[:])

		// listen for clients
		setupServer("127.0.0.1:2222")

		fmt.Println("GNAT node running!")

	} else {
		fmt.Fprintf(os.Stderr, "error: your network configuration does not support running a GNAT node.")
		fmt.Fprintf(os.Stderr, "Update your router/firewall settings to be less restrictive and try again.")
		os.Exit(1)
	}
}

func sendMessageToClient(sendToIP string, message []byte) error {
	return hub.sendMessageToClient(sendToIP, message)
}

func aknowledgementHandler(success bool, errCode int, errMsg string) {
	fmt.Printf("Did forward: %v, %v\n", success, errMsg)
}

func forwardingRequestHandler(fromIP string, header map[string]string, data []byte) {

	sendTo := header["to"]
	if sendTo == "" {
		return
	}

	ipDigest := sha256.Sum256([]byte(sendTo))
	id := b58.Encode(ipDigest[:])

	fmt.Println("Received forwarding request from " + fromIP)

	msg := make(map[string]string)
	msg["from"] = fromIP
	msg["ts"] = time.Now().Format(time.RFC3339)
	msgHeader, _ := json.Marshal(msg)

	var err error
	var foundNode *gnat.NetworkNode
	if node, ok := forwardingCache[id]; ok {
		foundNode = node
	} else {
		fmt.Println("Performing Kademlia.FIND_NODE operation")
		foundNode, err = dht.FindNode(id)
		forwardingCache[id] = foundNode

		// if cache too big, reset it
		if len(forwardingCache) > maxRouteCacheSize {
			forwardingCache = make(map[string]*gnat.NetworkNode)
		}
	}

	if err != nil {
		fmt.Println(err.Error())
	} else {
		fmt.Println("Forwarding data to " + foundNode.IP.String())
		dht.ForwardDataVia(foundNode, gnat.NewNetworkNode(sendTo, "0"), append(msgHeader, data...), aknowledgementHandler)
	}
}

func handConnectionRequest(w http.ResponseWriter, r *http.Request) {

	// generate digest hash of IP address
	clientIP := strings.Split(r.RemoteAddr, ":")[0]
	ipDigest := sha256.Sum256([]byte(clientIP))
	id := b58.Encode(ipDigest[:])

	// find the node connected to this client ip
	node, err := dht.FindNode(id)

	if err == nil {
		if bytes.Compare(node.ID, dht.GetSelfID()) == 0 {
			fmt.Println("New connection from " + r.RemoteAddr)

			if r.URL.Path != "/" {
				http.Error(w, "Not found", 404)
				return
			}

			if r.Method != "GET" {
				http.Error(w, "Method not allowed", 405)
				return
			}

			http.ServeFile(w, r, "./static/home.html")

		} else {
			fmt.Println("Redirecting " + r.RemoteAddr + " to http://" + node.IP.String() + ":2222")
			http.Redirect(w, r, "http://"+node.IP.String()+":2222", 301)
		}

	} else {
		fmt.Println(err)
	}
}

func setupServer(host string) {

	fmt.Print("4) Setting up HTTP server...")
	flag.Parse()
	hub = newHub()
	go hub.run(forwardingRequestHandler)
	http.HandleFunc("/", handConnectionRequest)
	http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
		serveWs(hub, w, r)
	})
	fmt.Println("done")
	fmt.Println("Listening on " + host)
	err := http.ListenAndServe(":2222", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

func initializeDHT(selfID []byte) {
	var ip = flag.String("ip", "0.0.0.0", "IP address to use")
	var port = flag.String("port", "1443", "Port to use")
	var bIP = flag.String("bip", "45.55.18.163", "IP address of bootstrap server")
	var bPort = flag.String("bport", "1443", "Port of bootstrap server")
	var stun = flag.Bool("stun", true, "Use STUN")

	flag.Parse()

	var bootstrapNodes []*gnat.NetworkNode
	if *bIP != "" || *bPort != "" {
		bootstrapNode := gnat.NewNetworkNode(*bIP, *bPort)
		bootstrapNodes = append(bootstrapNodes, bootstrapNode)
	}

	var err error
	dht, err = gnat.NewDHT(&gnat.Options{
		BootstrapNodes:    bootstrapNodes,
		IP:                *ip,
		ID:                selfID,
		Port:              *port,
		UseStun:           *stun,
		ForwardingHandler: sendMessageToClient,
	})

	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Print("2) Opening socket...")

	err = dht.CreateSocket()
	if err != nil {
		panic(err)
	}

	fmt.Println("done")
	go func() {
		err := dht.Listen()
		panic(err)
	}()

	if len(bootstrapNodes) > 0 {
		fmt.Print("3) Bootstrapping into GNAT network...")
		dht.Bootstrap()
		fmt.Println("done")
	} else {
		fmt.Println("3) Skipping GNAT bootstrap")
	}
}

func printWelcomeMessage() {
	fmt.Println("\n  _____ _   _       _______  ")
	fmt.Println(" / ____| \\ | |   /\\|__   __|")
	fmt.Println("| |  __|  \\| |  /  \\  | |")
	fmt.Println("| | |_ | . ` | / /\\ \\ | |")
	fmt.Println("| |__| | |\\  |/ ____ \\| |")
	fmt.Println(" \\_____|_| \\_/_/    \\_\\_|")
	fmt.Println("  GNAT Server Node v0.0.1")
	fmt.Println("    *Documentation:   https://gnat.cs.brown.edu/docs")
	fmt.Println("    *Support:         https://gnat.cs.brown.edu/support")
	fmt.Println("    *GitHub:          https://github.com/ogisan/gnat")
	fmt.Println("    For more information, visit: http://gnat.cs.brown.edu")
}
<commit_msg>added backup in case stun fails<commit_after>package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/json"
	"flag"
	"fmt"
	"gnat"
	"log"
	"net/http"
	"os/exec"

	"strings"

	"time"

	"os"

	"github.com/ccding/go-stun/stun"
	b58 "github.com/jbenet/go-base58"
)

// cap on how many routing nodes
// should be remembered
var maxRouteCacheSize = 500

// dht of peer GNAT nodes
var dht *gnat.DHT

// websocket hub to handle client messages
var hub *Hub

// mapping of ip addresses to gnat peers
var forwardingCache map[string]*gnat.NetworkNode

func main() {

	printWelcomeMessage()

	forwardingCache = make(map[string]*gnat.NetworkNode)

	// test the network to discovery what type of NAT (if any)
	// the client is behind.
	fmt.Print("1) Testing network...")
	nat, host, err := stun.NewClient().Discover()
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: a problem occured while testing your network.")
		fmt.Fprintf(os.Stderr, "Try again later.")
		os.Exit(1)
	}
	fmt.Println("done.")

	var hostAddr string
	if host == nil {
		out, _ := exec.Command("curl ipinfo.io/ip").Output()
		hostAddr = string(out)
	} else {
		hostAddr = host.String()
	}

	// make sure network is behind leniant NAT
	fmt.Println("  Network NAT configuration detected: " + nat.String())
	fmt.Println("  Public IP address: " + hostAddr)
	if nat == stun.NATNone || nat == stun.NATFull || nat == stun.NATPortRestricted {

		// generate an ID from the digest of the IP addr
		selfIP := strings.Split(hostAddr, ":")[0]
		ipDigest := sha256.Sum256([]byte(selfIP))

		// initialize the GNAT dht node
		initializeDHT(ipDigest[:])

		// listen for clients
		setupServer("127.0.0.1:2222")

		fmt.Println("GNAT node running!")

	} else {
		fmt.Fprintf(os.Stderr, "error: your network configuration does not support running a GNAT node.")
		fmt.Fprintf(os.Stderr, "Update your router/firewall settings to be less restrictive and try again.")
		os.Exit(1)
	}
}

func sendMessageToClient(sendToIP string, message []byte) error {
	return hub.sendMessageToClient(sendToIP, message)
}

func aknowledgementHandler(success bool, errCode int, errMsg string) {
	fmt.Printf("Did forward: %v, %v\n", success, errMsg)
}

func forwardingRequestHandler(fromIP string, header map[string]string, data []byte) {

	sendTo := header["to"]
	if sendTo == "" {
		return
	}

	ipDigest := sha256.Sum256([]byte(sendTo))
	id := b58.Encode(ipDigest[:])

	fmt.Println("Received forwarding request from " + fromIP)

	msg := make(map[string]string)
	msg["from"] = fromIP
	msg["ts"] = time.Now().Format(time.RFC3339)
	msgHeader, _ := json.Marshal(msg)

	var err error
	var foundNode *gnat.NetworkNode
	if node, ok := forwardingCache[id]; ok {
		foundNode = node
	} else {
		fmt.Println("Performing Kademlia.FIND_NODE operation")
		foundNode, err = dht.FindNode(id)
		forwardingCache[id] = foundNode

		// if cache too big, reset it
		if len(forwardingCache) > maxRouteCacheSize {
			forwardingCache = make(map[string]*gnat.NetworkNode)
		}
	}

	if err != nil {
		fmt.Println(err.Error())
	} else {
		fmt.Println("Forwarding data to " + foundNode.IP.String())
		dht.ForwardDataVia(foundNode, gnat.NewNetworkNode(sendTo, "0"), append(msgHeader, data...), aknowledgementHandler)
	}
}

func handConnectionRequest(w http.ResponseWriter, r *http.Request) {

	// generate digest hash of IP address
	clientIP := strings.Split(r.RemoteAddr, ":")[0]
	ipDigest := sha256.Sum256([]byte(clientIP))
	id := b58.Encode(ipDigest[:])

	// find the node connected to this client ip
	node, err := dht.FindNode(id)

	if err == nil {
		if bytes.Compare(node.ID, dht.GetSelfID()) == 0 {
			fmt.Println("New connection from " + r.RemoteAddr)

			if r.URL.Path != "/" {
				http.Error(w, "Not found", 404)
				return
			}

			if r.Method != "GET" {
				http.Error(w, "Method not allowed", 405)
				return
			}

			http.ServeFile(w, r, "./static/home.html")

		} else {
			fmt.Println("Redirecting " + r.RemoteAddr + " to http://" + node.IP.String() + ":2222")
			http.Redirect(w, r, "http://"+node.IP.String()+":2222", 301)
		}

	} else {
		fmt.Println(err)
	}
}

func setupServer(host string) {

	fmt.Print("4) Setting up HTTP server...")
	flag.Parse()
	hub = newHub()
	go hub.run(forwardingRequestHandler)
	http.HandleFunc("/", handConnectionRequest)
	http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
		serveWs(hub, w, r)
	})
	fmt.Println("done")
	fmt.Println("Listening on " + host)
	err := http.ListenAndServe(":2222", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

func initializeDHT(selfID []byte) {
	var ip = flag.String("ip", "0.0.0.0", "IP address to use")
	var port = flag.String("port", "1443", "Port to use")
	var bIP = flag.String("bip", "45.55.18.163", "IP address of bootstrap server")
	var bPort = flag.String("bport", "1443", "Port of bootstrap server")
	var stun = flag.Bool("stun", true, "Use STUN")

	flag.Parse()

	var bootstrapNodes []*gnat.NetworkNode
	if *bIP != "" || *bPort != "" {
		bootstrapNode := gnat.NewNetworkNode(*bIP, *bPort)
		bootstrapNodes = append(bootstrapNodes, bootstrapNode)
	}

	var err error
	dht, err = gnat.NewDHT(&gnat.Options{
		BootstrapNodes:    bootstrapNodes,
		IP:                *ip,
		ID:                selfID,
		Port:              *port,
		UseStun:           *stun,
		ForwardingHandler: sendMessageToClient,
	})

	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Print("2) Opening socket...")

	err = dht.CreateSocket()
	if err != nil {
		panic(err)
	}

	fmt.Println("done")
	go func() {
		err := dht.Listen()
		panic(err)
	}()

	if len(bootstrapNodes) > 0 {
		fmt.Print("3) Bootstrapping into GNAT network...")
		dht.Bootstrap()
		fmt.Println("done")
	} else {
		fmt.Println("3) Skipping GNAT bootstrap")
	}
}

func printWelcomeMessage() {
	fmt.Println("\n  _____ _   _       _______  ")
	fmt.Println(" / ____| \\ | |   /\\|__   __|")
	fmt.Println("| |  __|  \\| |  /  \\  | |")
	fmt.Println("| | |_ | . ` | / /\\ \\ | |")
	fmt.Println("| |__| | |\\  |/ ____ \\| |")
	fmt.Println(" \\_____|_| \\_/_/    \\_\\_|")
	fmt.Println("  GNAT Server Node v0.0.1")
	fmt.Println("    *Documentation:   https://gnat.cs.brown.edu/docs")
	fmt.Println("    *Support:         https://gnat.cs.brown.edu/support")
	fmt.Println("    *GitHub:          https://github.com/ogisan/gnat")
	fmt.Println("    For more information, visit: http://gnat.cs.brown.edu")
}
<|endoftext|>


<commit_before>package server;

<commit_msg>test listing files on a grpc request<commit_after>package server;

import (
	pb "github.com/iamneal/book_parser/server/proto"
	drive "google.golang.org/api/drive/v3"
	"golang.org/x/net/context"
	"fmt"
	"net"
	"google.golang.org/grpc"
)

type Server struct {
	driveServ *drive.Service
}

func NewRpcDriveService(d *drive.Service) *Server {
	return &Server{driveServ: d}
}

func (s *Server) RunRpcServer(conn string) error {
	listener, err := net.Listen("tcp", conn)
	if err != nil {
		return err
	}
	server := grpc.NewServer()

	pb.RegisterBookParserServer(server, s)
	return server.Serve(listener)
}

func (s *Server) PullBook(ctx context.Context, file *pb.File) (*pb.Empty, error) {
	fmt.Printf("rpc Server recieved: %+v", file)

	fileService := drive.NewFilesService(driveService)
	list, err := fileService.List().Corpora("user").Context(context.Background()).
		Spaces("drive").Do()

	fmt.Printf("listed files: %+v\n", list)
	for _, f := range list.Files {
		fmt.Printf("fileId: %s\n Name: %s\n\n" ,f.Id, f.Name)
	}
	return &pb.Empty{}, nil
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net"
	"path/filepath"

	"fmt"
	pb "github.com/fermayo/charger/charger"
	"github.com/urfave/cli"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
)

const (
	port = ":9000"
)

type server struct{}

type streamWriter struct {
	stream pb.Charger_ExecCommandServer
}

var (
	app *cli.App
)

func (s *server) ExecCommand(in *pb.CommandRequest, stream pb.Charger_ExecCommandServer) error {
	w := &streamWriter{stream: stream}
	app.Writer = w
	app.ErrWriter = w
	app.Name = filepath.Base(in.Args[0])
	app.UsageText = fmt.Sprintf("%s COMMAND", filepath.Base(in.Args[0]))
	app.Run(in.Args)
	return nil
}

func (s *streamWriter) Write(p []byte) (n int, err error) {
	if err := s.stream.Send(&pb.CommandResponse{Buffer: p}); err != nil {
		return 0, err
	}
	return len(p), nil
}

func main() {
	// Set up CLI handler
	app = cli.NewApp()
	app.Usage = "Charge all the things"

	// Start GRPC server
	lis, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterChargerServer(s, &server{})
	reflection.Register(s)
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}
<commit_msg>Handle USAGE and VERSION via env vars<commit_after>package main

import (
	"log"
	"net"
	"path/filepath"

	"fmt"
	pb "github.com/fermayo/charger/charger"
	"github.com/urfave/cli"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
	"os"
)

const (
	port = ":9000"
)

type server struct{}

type streamWriter struct {
	stream pb.Charger_ExecCommandServer
}

var (
	app *cli.App
)

func (s *server) ExecCommand(in *pb.CommandRequest, stream pb.Charger_ExecCommandServer) error {
	w := &streamWriter{stream: stream}
	app.Writer = w
	app.ErrWriter = w
	app.Name = filepath.Base(in.Args[0])
	app.UsageText = fmt.Sprintf("%s COMMAND", filepath.Base(in.Args[0]))
	app.Run(in.Args)
	return nil
}

func (s *streamWriter) Write(p []byte) (n int, err error) {
	if err := s.stream.Send(&pb.CommandResponse{Buffer: p}); err != nil {
		return 0, err
	}
	return len(p), nil
}

func main() {
	// Set up CLI handler
	app = cli.NewApp()
	app.Usage = os.Getenv("USAGE")
	app.Version = os.Getenv("VERSION")
	if app.Version == "" {
		app.HideVersion = true
	}

	// Start GRPC server
	lis, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterChargerServer(s, &server{})
	reflection.Register(s)
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"text/template"
	"time"

	"fknsrs.biz/p/ottoext/fetch"
	"fknsrs.biz/p/ottoext/loop"
	"github.com/MathieuTurcotte/sourcemap"
	"github.com/Sirupsen/logrus"
	"github.com/codegangsta/negroni"
	"github.com/gorilla/mux"
	"github.com/meatballhat/negroni-logrus"
	"github.com/robertkrimen/otto"
)

func logDuration(s string) func() {
	before := time.Now()

	logrus.Debugf("%s [before]", s)

	return func() {
		logrus.WithField("duration", time.Now().Sub(before).String()).Debugf("%s [after]", s)
	}
}

func dumpDebugError(err error) string {
	if oerr, ok := err.(*otto.Error); ok {
		return oerr.String()
	}

	return ""
}

func main() {
	logrus.StandardLogger().Level = logrus.DebugLevel

	logrus.Debug("starting up")

	baseVM := otto.New()

	smRead := logDuration("read sourcemap")
	smData, err := ioutil.ReadFile("../client/public/bundle.server.js.map")
	if err != nil {
		panic(err)
	}
	smRead()

	smParse := logDuration("parse sourcemap")
	sm, err := sourcemap.Read(bytes.NewReader(smData))
	if err != nil {
		panic(err)
	}
	smParse()

	jsRead := logDuration("read javascript")
	jsData, err := ioutil.ReadFile("../client/public/bundle.server.js")
	if err != nil {
		panic(err)
	}
	jsRead()

	// lodash has a lookahead regex that we don't care about, and it makes otto
	// unhappy. this is a hack to get rid of it.
	jsData = bytes.Replace(jsData, []byte(`(?=`), []byte(`(`), -1)

	jsCompiled := logDuration("compile javascript")
	s, err := baseVM.CompileWithSourceMap("bundle.js", string(jsData), &sm)
	if err != nil {
		if st := dumpDebugError(err); st != "" {
			fmt.Println(st)
		}
		panic(err)
	}
	jsCompiled()

	jsInitialised := logDuration("initialise javascript")
	if _, err := baseVM.Run(s); err != nil {
		if st := dumpDebugError(err); st != "" {
			fmt.Println(st)
		}
		panic(err)
	}
	jsInitialised()

	tpl, err := template.ParseFiles("./index.template")
	if err != nil {
		panic(err)
	}

	apiRouter := mux.NewRouter()

	var cats = []Cat{
		Cat{Name: "fluffy", FavouriteFood: "marshmallows"},
		Cat{Name: "beans", FavouriteFood: "legume"},
		Cat{Name: "misty", FavouriteFood: "meat"},
		Cat{Name: "sara", FavouriteFood: "fish"},
	}

	apiRouter.Path("/api/v1/cats").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("search") == "" {
			w.Header().Set("content-type", "application/json")
			if err := json.NewEncoder(w).Encode(cats); err != nil {
				panic(err)
			}

			return
		}

		var d []Cat
		for _, c := range cats {
			if strings.Contains(strings.ToLower(c.Name), strings.ToLower(r.URL.Query().Get("search"))) {
				d = append(d, c)
			}
		}

		w.Header().Set("content-type", "application/json")
		if err := json.NewEncoder(w).Encode(d); err != nil {
			panic(err)
		}
	})

	apiRouter.Path("/api/v1/cats/{name}").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)

		for _, c := range cats {
			if strings.ToLower(c.Name) == vars["name"] {
				w.Header().Set("content-type", "application/json")
				if err := json.NewEncoder(w).Encode(c); err != nil {
					panic(err)
				}

				return
			}
		}

		http.NotFound(w, r)
	})

	m := mux.NewRouter()

	m.PathPrefix("/api/v1").Handler(apiRouter)

	m.PathPrefix("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		vmCopied := logDuration("copy vm")
		vm := baseVM.Copy()
		vmCopied()

		l := loop.New(vm)
		if err := fetch.DefineWithHandler(vm, l, apiRouter); err != nil {
			panic(err)
		}

		fn, err := vm.Get("renderApplication")
		if err != nil {
			panic(err)
		}

		m := map[string]interface{}{
			"redirect": func(u string) {
				http.Redirect(w, r, u, 301)
			},
			"error": func(e string) {
				http.Error(w, e, http.StatusInternalServerError)
			},
			"notFound": func() {
				http.NotFound(w, r)
			},
			"success": func(initialState, html string) {
				w.Header().Set("content-type", "text/html")

				tpl.Execute(w, map[string]string{
					"initialState": initialState,
					"html":         html,
				})
			},
		}

		v, err := vm.ToValue(m)
		if err != nil {
			panic(err)
		}

		jsRun := logDuration("run javascript")
		if _, err := fn.Call(otto.UndefinedValue(), r.URL.String(), otto.NullValue(), v); err != nil {
			if st := dumpDebugError(err); st != "" {
				panic(fmt.Errorf("%s", st))
			}
			panic(err)
		}
		jsRun()

		jsFinished := logDuration("wait for js")
		if err := l.Run(); err != nil {
			if st := dumpDebugError(err); st != "" {
				panic(fmt.Errorf("%s", st))
			}
			panic(err)
		}
		jsFinished()
	})

	n := negroni.New()

	n.Use(negroni.NewRecovery())
	n.Use(negronilogrus.NewMiddleware())
	st := negroni.NewStatic(http.Dir("../client/public"))
	st.IndexFile = "NOT_USED_I_HOPE"
	n.Use(st)
	n.UseHandler(m)

	logrus.Info("listening")
	if err := http.ListenAndServe(":3000", n); err != nil {
		panic(err)
	}
}
<commit_msg>Copy the baseVM in a goroutine and obtain via a channel<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"text/template"
	"time"

	"fknsrs.biz/p/ottoext/fetch"
	"fknsrs.biz/p/ottoext/loop"
	"github.com/MathieuTurcotte/sourcemap"
	"github.com/Sirupsen/logrus"
	"github.com/codegangsta/negroni"
	"github.com/gorilla/mux"
	"github.com/meatballhat/negroni-logrus"
	"github.com/robertkrimen/otto"
)

func logDuration(s string) func() {
	before := time.Now()

	logrus.Debugf("%s [before]", s)

	return func() {
		logrus.WithField("duration", time.Now().Sub(before).String()).Debugf("%s [after]", s)
	}
}

func dumpDebugError(err error) string {
	if oerr, ok := err.(*otto.Error); ok {
		return oerr.String()
	}

	return ""
}

func vmCopier(baseVM *otto.Otto, c chan *otto.Otto) {
	for {
		c <- baseVM.Copy()
	}
}

func main() {
	logrus.StandardLogger().Level = logrus.DebugLevel

	logrus.Debug("starting up")

	baseVM := otto.New()

	smRead := logDuration("read sourcemap")
	smData, err := ioutil.ReadFile("../client/public/bundle.server.js.map")
	if err != nil {
		panic(err)
	}
	smRead()

	smParse := logDuration("parse sourcemap")
	sm, err := sourcemap.Read(bytes.NewReader(smData))
	if err != nil {
		panic(err)
	}
	smParse()

	jsRead := logDuration("read javascript")
	jsData, err := ioutil.ReadFile("../client/public/bundle.server.js")
	if err != nil {
		panic(err)
	}
	jsRead()

	// lodash has a lookahead regex that we don't care about, and it makes otto
	// unhappy. this is a hack to get rid of it.
	jsData = bytes.Replace(jsData, []byte(`(?=`), []byte(`(`), -1)

	jsCompiled := logDuration("compile javascript")
	s, err := baseVM.CompileWithSourceMap("bundle.js", string(jsData), &sm)
	if err != nil {
		if st := dumpDebugError(err); st != "" {
			fmt.Println(st)
		}
		panic(err)
	}
	jsCompiled()

	jsInitialised := logDuration("initialise javascript")
	if _, err := baseVM.Run(s); err != nil {
		if st := dumpDebugError(err); st != "" {
			fmt.Println(st)
		}
		panic(err)
	}
	jsInitialised()

	vmChan := make(chan *otto.Otto)
	go vmCopier(baseVM, vmChan)

	tpl, err := template.ParseFiles("./index.template")
	if err != nil {
		panic(err)
	}

	apiRouter := mux.NewRouter()

	var cats = []Cat{
		Cat{Name: "fluffy", FavouriteFood: "marshmallows"},
		Cat{Name: "beans", FavouriteFood: "legume"},
		Cat{Name: "misty", FavouriteFood: "meat"},
		Cat{Name: "sara", FavouriteFood: "fish"},
	}

	apiRouter.Path("/api/v1/cats").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("search") == "" {
			w.Header().Set("content-type", "application/json")
			if err := json.NewEncoder(w).Encode(cats); err != nil {
				panic(err)
			}

			return
		}

		var d []Cat
		for _, c := range cats {
			if strings.Contains(strings.ToLower(c.Name), strings.ToLower(r.URL.Query().Get("search"))) {
				d = append(d, c)
			}
		}

		w.Header().Set("content-type", "application/json")
		if err := json.NewEncoder(w).Encode(d); err != nil {
			panic(err)
		}
	})

	apiRouter.Path("/api/v1/cats/{name}").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)

		for _, c := range cats {
			if strings.ToLower(c.Name) == vars["name"] {
				w.Header().Set("content-type", "application/json")
				if err := json.NewEncoder(w).Encode(c); err != nil {
					panic(err)
				}

				return
			}
		}

		http.NotFound(w, r)
	})

	m := mux.NewRouter()

	m.PathPrefix("/api/v1").Handler(apiRouter)

	m.PathPrefix("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		vmCopied := logDuration("copy vm")
		vm := <-vmChan
		vmCopied()

		l := loop.New(vm)
		if err := fetch.DefineWithHandler(vm, l, apiRouter); err != nil {
			panic(err)
		}

		fn, err := vm.Get("renderApplication")
		if err != nil {
			panic(err)
		}

		m := map[string]interface{}{
			"redirect": func(u string) {
				http.Redirect(w, r, u, 301)
			},
			"error": func(e string) {
				http.Error(w, e, http.StatusInternalServerError)
			},
			"notFound": func() {
				http.NotFound(w, r)
			},
			"success": func(initialState, html string) {
				w.Header().Set("content-type", "text/html")

				tpl.Execute(w, map[string]string{
					"initialState": initialState,
					"html":         html,
				})
			},
		}

		v, err := vm.ToValue(m)
		if err != nil {
			panic(err)
		}

		jsRun := logDuration("run javascript")
		if _, err := fn.Call(otto.UndefinedValue(), r.URL.String(), otto.NullValue(), v); err != nil {
			if st := dumpDebugError(err); st != "" {
				panic(fmt.Errorf("%s", st))
			}
			panic(err)
		}
		jsRun()

		jsFinished := logDuration("wait for js")
		if err := l.Run(); err != nil {
			if st := dumpDebugError(err); st != "" {
				panic(fmt.Errorf("%s", st))
			}
			panic(err)
		}
		jsFinished()
	})

	n := negroni.New()

	n.Use(negroni.NewRecovery())
	n.Use(negronilogrus.NewMiddleware())
	st := negroni.NewStatic(http.Dir("../client/public"))
	st.IndexFile = "NOT_USED_I_HOPE"
	n.Use(st)
	n.UseHandler(m)

	logrus.Info("listening")
	if err := http.ListenAndServe(":3000", n); err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"regexp"
	"sort"
	"strings"
	"time"

	"github.com/gorilla/mux"
	"github.com/gorilla/websocket"
	gonanoid "github.com/matoous/go-nanoid"
	"github.com/rs/xid"
)

var port = 13000
var addr = fmt.Sprintf(":%d", port)

var wsUpgrader = websocket.Upgrader{}

var RPsByID map[string]*RP
var SlugMap map[string]SlugInfo
var Revisions map[string][]json.RawMessage

func main() {
	// Print "Goodbye" after all defer statements are done
	defer log.Println("Goodbye!")

	RPsByID = make(map[string]*RP)
	SlugMap = make(map[string]SlugInfo)
	Revisions = make(map[string][]json.RawMessage)
	rooms = make(map[string]*room)
	// create router
	router := mux.NewRouter().StrictSlash(true)

	// api
	api := router.PathPrefix("/api").Subrouter()
	api.HandleFunc("/health", health).Methods("GET")
	api.HandleFunc("/dashboard", dashboard).Methods("POST")
	api.HandleFunc("/rp", createRp).Methods("POST")
	api.HandleFunc("/rp/import", todo).Methods("POST")
	api.HandleFunc("/rp/import/{slug:[-0-9a-zA-Z]+}", todo).Methods("POST")
	api.HandleFunc("/user", createUser).Methods("POST")
	api.HandleFunc("/user/verify", verifyUser).Methods("GET")
	roomAPI := api.PathPrefix("/rp/{slug:[-0-9a-zA-Z]+}").Subrouter()
	roomAPI.HandleFunc("/chat", rpChatStream).Methods("GET")
	roomAPI.HandleFunc("/pages", todo).Methods("GET")
	roomAPI.HandleFunc("/pages/{pageNum:[1-9][0-9]*}", todo).Methods("GET")
	roomAPI.HandleFunc("/download.txt", todo).Methods("GET")
	roomAPI.HandleFunc("/export", todo).Methods("GET")
	roomAPI.HandleFunc("/msgs", rpSendMsg).Methods("POST")
	roomAPI.HandleFunc("/charas", rpSendChara).Methods("POST")
	roomAPI.HandleFunc("/msgs/{docId:[0-9a-z]+}", rpUpdateMsg).Methods("PUT")
	roomAPI.HandleFunc("/charas/{docId:[0-9a-z]+}", rpUpdateChara).Methods("PUT")
	roomAPI.HandleFunc("/msgs/{docId:[0-9a-z]+}/history", rpGetThingHistory).Methods("GET")
	roomAPI.HandleFunc("/charas/{docId:[0-9a-z]+}/history", rpGetThingHistory).Methods("GET")
	api.PathPrefix("/").HandlerFunc(apiMalformed)

	// routes
	router.HandleFunc("/", indexHTML).Methods("GET")
	router.HandleFunc("/import", indexHTML).Methods("GET")
	router.HandleFunc("/format", indexHTML).Methods("GET")
	router.HandleFunc("/rp/{rpCode}", indexHTML).Methods("GET")
	router.HandleFunc("/read/{rpCode}", indexHTML).Methods("GET")
	router.HandleFunc("/read/{rpCode}/page/{page}", indexHTML).Methods("GET")

	// assets
	router.PathPrefix("/").Handler(http.FileServer(http.Dir("../views/dist")))

	// listen
	srv := &http.Server{
		Addr: addr,
		// Good practice to set timeouts to avoid Slowloris attacks.
		WriteTimeout: time.Second * 15,
		ReadTimeout:  time.Second * 15,
		IdleTimeout:  time.Second * 60,
		Handler:      router, // Pass our instance of gorilla/mux in.
	}
	go func() {
		if err := srv.ListenAndServe(); err != http.ErrServerClosed {
			log.Fatalf("listen and serve: %s", err)
		}
	}()
	// defer gracefully closing the server
	defer func() {
		ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
		defer cancel()

		if err := srv.Shutdown(ctx); err != nil {
			log.Fatalf("http shutdown: %s", err)
		}
		log.Println("Http server stopped")
	}()

	// server is ready
	log.Printf("Listening on %s\n", addr)

	// await kill signal
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	<-c
}

func health(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, `{"rpnow":"ok"}`)
}

func dashboard(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, `{"canCreate":true,"canImport":true}`)
}

func createRp(w http.ResponseWriter, r *http.Request) {
	// parse rp header fields
	var header struct {
		Title string
	}
	err := json.NewDecoder(r.Body).Decode(&header)
	if err != nil {
		panic(err)
	}
	// generate slug
	slug, err := gonanoid.Generate("abcdefhjknpstxyz23456789", 20)
	if err != nil {
		panic(err)
	}
	readSlug, err := gonanoid.Generate("abcdefhjknpstxyz23456789", 20)
	if err != nil {
		panic(err)
	}
	readSlug = strings.ToLower(header.Title) + "-" + readSlug
	readSlug = regexp.MustCompile("[^a-z0-9]+").ReplaceAllString(readSlug, "-")
	// generate rpid
	rpid := "rp_" + xid.New().String()

	// add to db
	SlugMap[slug] = SlugInfo{rpid, "normal"}
	SlugMap[readSlug] = SlugInfo{rpid, "read"}
	RPsByID[rpid] = &RP{rpid, header.Title, readSlug, []RpMessage{}, []RpChara{}}
	// tell user the created response slug
	json.NewEncoder(w).Encode(map[string]string{"rpCode": slug})
}

func rpChatStream(w http.ResponseWriter, r *http.Request) {
	// parse slug
	params := mux.Vars(r)

	// get rpid from slug
	slugInfo := SlugMap[params["slug"]]
	// TODO if empty...
	if slugInfo.Access != "normal" {
		log.Println("No chat access on " + params["slug"])
		w.WriteHeader(403)
		return
	}
	rp := RPsByID[slugInfo.Rpid]

	conn, err := wsUpgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}
	defer conn.Close()

	if err := conn.WriteJSON(map[string]interface{}{
		"type": "init",
		"data": rp,
	}); err != nil {
		log.Fatal(err)
	}

	join(conn, rp.Rpid)
}

func rpSendThing(w http.ResponseWriter, r *http.Request, obj Doc, doAppend func(*RP, Doc)) {
	// generate key for new object
	obj.Meta().ID = xid.New().String()

	params := mux.Vars(r)

	slugInfo := SlugMap[params["slug"]]
	rp := RPsByID[slugInfo.Rpid]
	// TODO if empty...

	// populate received body
	err := obj.ParseBody(r.Body)
	if err != nil {
		panic(err)
	}
	// validate
	err = obj.Validate()
	if err != nil {
		log.Fatal(err)
	}

	// More
	obj.Meta().Timestamp = time.Now()
	obj.Meta().Userid = "nobody09c39024f1ef"
	obj.Meta().Revision = 0

	// put it in the db
	doAppend(rp, obj)

	// store revision as raw json?? meh
	revid := rp.Rpid + "/" + obj.Meta().ID
	js, _ := json.Marshal(obj)
	Revisions[revid] = append(Revisions[revid], js)
	rooms[rp.Rpid].broadcast <- js

	// bounce it back and send
	w.WriteHeader(204)
}

func rpSendMsg(w http.ResponseWriter, r *http.Request) {
	rpSendThing(w, r, NewRpMessage(), func(rp *RP, obj Doc) {
		rp.Messages = append(rp.Messages, obj.(RpMessage))
	})
}

func rpSendChara(w http.ResponseWriter, r *http.Request) {
	rpSendThing(w, r, NewRpChara(), func(rp *RP, obj Doc) {
		rp.Charas = append(rp.Charas, obj.(RpChara))
	})
}

func rpUpdateThing(w http.ResponseWriter, r *http.Request, getOldDoc func(*RP, string) Doc, doUpdate func(*RP, Doc)) {
	params := mux.Vars(r)

	slugInfo := SlugMap[params["slug"]]
	rp := RPsByID[slugInfo.Rpid]
	// TODO if empty...

	id := params["docId"]
	obj := getOldDoc(rp, id)

	// populate received body
	err := obj.ParseBody(r.Body)
	if err != nil {
		panic(err)
	}
	// validate
	err = obj.Validate()
	if err != nil {
		log.Fatal(err)
	}

	// More
	obj.Meta().Timestamp = time.Now()
	obj.Meta().Userid = "nobody09c39024f1ef"
	obj.Meta().Revision++

	// put it in the db
	doUpdate(rp, obj)

	// store revision as raw json?? meh
	revid := rp.Rpid + "/" + obj.Meta().ID
	js, _ := json.Marshal(obj)
	Revisions[revid] = append(Revisions[revid], js)
	rooms[rp.Rpid].broadcast <- js

	// bounce it back and send
	w.WriteHeader(204)
}

func rpUpdateMsg(w http.ResponseWriter, r *http.Request) {
	rpUpdateThing(w, r, func(rp *RP, id string) Doc {
		i := sort.Search(len(rp.Messages), func(i int) bool { return id <= rp.Messages[i].ID })
		return rp.Messages[i]
	}, func(rp *RP, obj Doc) {
		// rp.Messages = append(rp.Messages, obj.(RpMessage))
	})
}

func rpUpdateChara(w http.ResponseWriter, r *http.Request) {
	rpUpdateThing(w, r, func(rp *RP, id string) Doc {
		i := sort.Search(len(rp.Charas), func(i int) bool { return id <= rp.Charas[i].ID })
		return rp.Charas[i]
	}, func(rp *RP, obj Doc) {
		// rp.Charas = append(rp.Charas, obj.(RpChara))
	})
}

func rpGetThingHistory(w http.ResponseWriter, r *http.Request) {
	params := mux.Vars(r)

	slugInfo := SlugMap[params["slug"]]
	rp := RPsByID[slugInfo.Rpid]
	// TODO if empty...

	id := params["docId"]

	revisions := Revisions[rp.Rpid+"/"+id]

	// bounce it back and send
	json.NewEncoder(w).Encode(revisions)
}

func createUser(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, `{"userid":"nobody09c39024f1ef","token":"x"}`)
}

func verifyUser(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusNoContent)
}

func indexHTML(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "../views/dist/index.html")
}

func apiMalformed(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusBadRequest)
	fmt.Fprintln(w, "{\"error\":\"Malformed request\"}")
}

func todo(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusNotImplemented)
	fmt.Fprintln(w, "TODO")
}
<commit_msg>don't export things from main.go<commit_after>package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"regexp"
	"sort"
	"strings"
	"time"

	"github.com/gorilla/mux"
	"github.com/gorilla/websocket"
	gonanoid "github.com/matoous/go-nanoid"
	"github.com/rs/xid"
)

var port = 13000
var addr = fmt.Sprintf(":%d", port)

var wsUpgrader = websocket.Upgrader{}

var rpsByID map[string]*RP
var slugMap map[string]SlugInfo
var revisions map[string][]json.RawMessage

func main() {
	// Print "Goodbye" after all defer statements are done
	defer log.Println("Goodbye!")

	rpsByID = make(map[string]*RP)
	slugMap = make(map[string]SlugInfo)
	revisions = make(map[string][]json.RawMessage)
	rooms = make(map[string]*room)
	// create router
	router := mux.NewRouter().StrictSlash(true)

	// api
	api := router.PathPrefix("/api").Subrouter()
	api.HandleFunc("/health", health).Methods("GET")
	api.HandleFunc("/dashboard", dashboard).Methods("POST")
	api.HandleFunc("/rp", createRp).Methods("POST")
	api.HandleFunc("/rp/import", todo).Methods("POST")
	api.HandleFunc("/rp/import/{slug:[-0-9a-zA-Z]+}", todo).Methods("POST")
	api.HandleFunc("/user", createUser).Methods("POST")
	api.HandleFunc("/user/verify", verifyUser).Methods("GET")
	roomAPI := api.PathPrefix("/rp/{slug:[-0-9a-zA-Z]+}").Subrouter()
	roomAPI.HandleFunc("/chat", rpChatStream).Methods("GET")
	roomAPI.HandleFunc("/pages", todo).Methods("GET")
	roomAPI.HandleFunc("/pages/{pageNum:[1-9][0-9]*}", todo).Methods("GET")
	roomAPI.HandleFunc("/download.txt", todo).Methods("GET")
	roomAPI.HandleFunc("/export", todo).Methods("GET")
	roomAPI.HandleFunc("/msgs", rpSendMsg).Methods("POST")
	roomAPI.HandleFunc("/charas", rpSendChara).Methods("POST")
	roomAPI.HandleFunc("/msgs/{docId:[0-9a-z]+}", rpUpdateMsg).Methods("PUT")
	roomAPI.HandleFunc("/charas/{docId:[0-9a-z]+}", rpUpdateChara).Methods("PUT")
	roomAPI.HandleFunc("/msgs/{docId:[0-9a-z]+}/history", rpGetThingHistory).Methods("GET")
	roomAPI.HandleFunc("/charas/{docId:[0-9a-z]+}/history", rpGetThingHistory).Methods("GET")
	api.PathPrefix("/").HandlerFunc(apiMalformed)

	// routes
	router.HandleFunc("/", indexHTML).Methods("GET")
	router.HandleFunc("/import", indexHTML).Methods("GET")
	router.HandleFunc("/format", indexHTML).Methods("GET")
	router.HandleFunc("/rp/{rpCode}", indexHTML).Methods("GET")
	router.HandleFunc("/read/{rpCode}", indexHTML).Methods("GET")
	router.HandleFunc("/read/{rpCode}/page/{page}", indexHTML).Methods("GET")

	// assets
	router.PathPrefix("/").Handler(http.FileServer(http.Dir("../views/dist")))

	// listen
	srv := &http.Server{
		Addr: addr,
		// Good practice to set timeouts to avoid Slowloris attacks.
		WriteTimeout: time.Second * 15,
		ReadTimeout:  time.Second * 15,
		IdleTimeout:  time.Second * 60,
		Handler:      router, // Pass our instance of gorilla/mux in.
	}
	go func() {
		if err := srv.ListenAndServe(); err != http.ErrServerClosed {
			log.Fatalf("listen and serve: %s", err)
		}
	}()
	// defer gracefully closing the server
	defer func() {
		ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
		defer cancel()

		if err := srv.Shutdown(ctx); err != nil {
			log.Fatalf("http shutdown: %s", err)
		}
		log.Println("Http server stopped")
	}()

	// server is ready
	log.Printf("Listening on %s\n", addr)

	// await kill signal
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	<-c
}

func health(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, `{"rpnow":"ok"}`)
}

func dashboard(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, `{"canCreate":true,"canImport":true}`)
}

func createRp(w http.ResponseWriter, r *http.Request) {
	// parse rp header fields
	var header struct {
		Title string
	}
	err := json.NewDecoder(r.Body).Decode(&header)
	if err != nil {
		panic(err)
	}
	// generate slug
	slug, err := gonanoid.Generate("abcdefhjknpstxyz23456789", 20)
	if err != nil {
		panic(err)
	}
	readSlug, err := gonanoid.Generate("abcdefhjknpstxyz23456789", 20)
	if err != nil {
		panic(err)
	}
	readSlug = strings.ToLower(header.Title) + "-" + readSlug
	readSlug = regexp.MustCompile("[^a-z0-9]+").ReplaceAllString(readSlug, "-")
	// generate rpid
	rpid := "rp_" + xid.New().String()

	// add to db
	slugMap[slug] = SlugInfo{rpid, "normal"}
	slugMap[readSlug] = SlugInfo{rpid, "read"}
	rpsByID[rpid] = &RP{rpid, header.Title, readSlug, []RpMessage{}, []RpChara{}}
	// tell user the created response slug
	json.NewEncoder(w).Encode(map[string]string{"rpCode": slug})
}

func rpChatStream(w http.ResponseWriter, r *http.Request) {
	// parse slug
	params := mux.Vars(r)

	// get rpid from slug
	slugInfo := slugMap[params["slug"]]
	// TODO if empty...
	if slugInfo.Access != "normal" {
		log.Println("No chat access on " + params["slug"])
		w.WriteHeader(403)
		return
	}
	rp := rpsByID[slugInfo.Rpid]

	conn, err := wsUpgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}
	defer conn.Close()

	if err := conn.WriteJSON(map[string]interface{}{
		"type": "init",
		"data": rp,
	}); err != nil {
		log.Fatal(err)
	}

	join(conn, rp.Rpid)
}

func rpSendThing(w http.ResponseWriter, r *http.Request, obj Doc, doAppend func(*RP, Doc)) {
	// generate key for new object
	obj.Meta().ID = xid.New().String()

	params := mux.Vars(r)

	slugInfo := slugMap[params["slug"]]
	rp := rpsByID[slugInfo.Rpid]
	// TODO if empty...

	// populate received body
	err := obj.ParseBody(r.Body)
	if err != nil {
		panic(err)
	}
	// validate
	err = obj.Validate()
	if err != nil {
		log.Fatal(err)
	}

	// More
	obj.Meta().Timestamp = time.Now()
	obj.Meta().Userid = "nobody09c39024f1ef"
	obj.Meta().Revision = 0

	// put it in the db
	doAppend(rp, obj)

	// store revision as raw json?? meh
	revid := rp.Rpid + "/" + obj.Meta().ID
	js, _ := json.Marshal(obj)
	revisions[revid] = append(revisions[revid], js)
	rooms[rp.Rpid].broadcast <- js

	// bounce it back and send
	w.WriteHeader(204)
}

func rpSendMsg(w http.ResponseWriter, r *http.Request) {
	rpSendThing(w, r, NewRpMessage(), func(rp *RP, obj Doc) {
		rp.Messages = append(rp.Messages, obj.(RpMessage))
	})
}

func rpSendChara(w http.ResponseWriter, r *http.Request) {
	rpSendThing(w, r, NewRpChara(), func(rp *RP, obj Doc) {
		rp.Charas = append(rp.Charas, obj.(RpChara))
	})
}

func rpUpdateThing(w http.ResponseWriter, r *http.Request, getOldDoc func(*RP, string) Doc, doUpdate func(*RP, Doc)) {
	params := mux.Vars(r)

	slugInfo := slugMap[params["slug"]]
	rp := rpsByID[slugInfo.Rpid]
	// TODO if empty...

	id := params["docId"]
	obj := getOldDoc(rp, id)

	// populate received body
	err := obj.ParseBody(r.Body)
	if err != nil {
		panic(err)
	}
	// validate
	err = obj.Validate()
	if err != nil {
		log.Fatal(err)
	}

	// More
	obj.Meta().Timestamp = time.Now()
	obj.Meta().Userid = "nobody09c39024f1ef"
	obj.Meta().Revision++

	// put it in the db
	doUpdate(rp, obj)

	// store revision as raw json?? meh
	revid := rp.Rpid + "/" + obj.Meta().ID
	js, _ := json.Marshal(obj)
	revisions[revid] = append(revisions[revid], js)
	rooms[rp.Rpid].broadcast <- js

	// bounce it back and send
	w.WriteHeader(204)
}

func rpUpdateMsg(w http.ResponseWriter, r *http.Request) {
	rpUpdateThing(w, r, func(rp *RP, id string) Doc {
		i := sort.Search(len(rp.Messages), func(i int) bool { return id <= rp.Messages[i].ID })
		return rp.Messages[i]
	}, func(rp *RP, obj Doc) {
		// rp.Messages = append(rp.Messages, obj.(RpMessage))
	})
}

func rpUpdateChara(w http.ResponseWriter, r *http.Request) {
	rpUpdateThing(w, r, func(rp *RP, id string) Doc {
		i := sort.Search(len(rp.Charas), func(i int) bool { return id <= rp.Charas[i].ID })
		return rp.Charas[i]
	}, func(rp *RP, obj Doc) {
		// rp.Charas = append(rp.Charas, obj.(RpChara))
	})
}

func rpGetThingHistory(w http.ResponseWriter, r *http.Request) {
	params := mux.Vars(r)

	slugInfo := slugMap[params["slug"]]
	rp := rpsByID[slugInfo.Rpid]
	// TODO if empty...

	id := params["docId"]

	docRevisions := revisions[rp.Rpid+"/"+id]

	// bounce it back and send
	json.NewEncoder(w).Encode(docRevisions)
}

func createUser(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, `{"userid":"nobody09c39024f1ef","token":"x"}`)
}

func verifyUser(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusNoContent)
}

func indexHTML(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "../views/dist/index.html")
}

func apiMalformed(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusBadRequest)
	fmt.Fprintln(w, "{\"error\":\"Malformed request\"}")
}

func todo(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusNotImplemented)
	fmt.Fprintln(w, "TODO")
}
<|endoftext|>


<commit_before>// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 server

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

	"github.com/coreos/etcd/clientv3"
	"github.com/golang/glog"
	"github.com/google/trillian"
	"github.com/google/trillian/extension"
	"github.com/google/trillian/monitoring"
	"github.com/google/trillian/server/admin"
	"github.com/google/trillian/server/interceptor"
	"github.com/google/trillian/util"
	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/naming"
	"google.golang.org/grpc/reflection"

	etcdnaming "github.com/coreos/etcd/clientv3/naming"
)

const (
	// DefaultTreeDeleteThreshold is the suggested threshold for tree deletion.
	// It represents the minimum time a tree has to remain Deleted before being hard-deleted.
	DefaultTreeDeleteThreshold = 7 * 24 * time.Hour

	// DefaultTreeDeleteMinInterval is the suggested min interval between tree GC sweeps.
	// A tree GC sweep consists of listing deleted trees older than the deletion threshold and
	// hard-deleting them.
	// Actual runs happen randomly between [minInterval,2*minInterval).
	DefaultTreeDeleteMinInterval = 4 * time.Hour
)

// Main encapsulates the data and logic to start a Trillian server (Log or Map).
type Main struct {
	// Endpoints for RPC and HTTP/REST servers.
	// HTTP/REST is optional, if empty it'll not be bound.
	RPCEndpoint, HTTPEndpoint string

	// TLS Certificate and Key files for the server.
	TLSCertFile, TLSKeyFile string

	DBClose func() error

	Registry extension.Registry

	StatsPrefix string
	QuotaDryRun bool

	// RegisterHandlerFn is called to register REST-proxy handlers.
	RegisterHandlerFn func(context.Context, *runtime.ServeMux, string, []grpc.DialOption) error
	// RegisterServerFn is called to register RPC servers.
	RegisterServerFn func(*grpc.Server, extension.Registry) error

	// AllowedTreeTypes determines which types of trees may be created through the Admin Server
	// bound by Main. nil means unrestricted.
	AllowedTreeTypes []trillian.TreeType

	TreeGCEnabled         bool
	TreeDeleteThreshold   time.Duration
	TreeDeleteMinInterval time.Duration
}

// Run starts the configured server. Blocks until the server exits.
func (m *Main) Run(ctx context.Context) error {
	glog.CopyStandardLogTo("WARNING")

	srv, err := m.newGRPCServer()
	if err != nil {
		glog.Exitf("Error creating gRPC server: %v", err)
	}
	defer srv.GracefulStop()

	defer m.DBClose()

	if err := m.RegisterServerFn(srv, m.Registry); err != nil {
		return err
	}
	trillian.RegisterTrillianAdminServer(srv, admin.New(m.Registry, m.AllowedTreeTypes))
	reflection.Register(srv)

	if endpoint := m.HTTPEndpoint; endpoint != "" {
		gatewayMux := runtime.NewServeMux()
		opts := []grpc.DialOption{grpc.WithInsecure()}
		if err := m.RegisterHandlerFn(ctx, gatewayMux, m.RPCEndpoint, opts); err != nil {
			return err
		}
		if err := trillian.RegisterTrillianAdminHandlerFromEndpoint(ctx, gatewayMux, m.RPCEndpoint, opts); err != nil {
			return err
		}

		httpMux := http.DefaultServeMux
		httpMux.Handle("/", gatewayMux)
		httpMux.Handle("/metrics", promhttp.Handler())

		go func() {
			glog.Infof("HTTP server starting on %v", endpoint)

			var err error
			// Let http.ListenAndServeTLS handle the error case when only one of the flags is set.
			if m.TLSCertFile != "" || m.TLSKeyFile != "" {
				err = http.ListenAndServeTLS(endpoint, m.TLSCertFile, m.TLSKeyFile, httpMux)
			} else {
				err = http.ListenAndServe(endpoint, httpMux)
			}

			if err != nil {
				glog.Errorf("HTTP server stopped: %v", err)
			}
		}()
	}

	glog.Infof("RPC server starting on %v", m.RPCEndpoint)
	lis, err := net.Listen("tcp", m.RPCEndpoint)
	if err != nil {
		return err
	}
	go util.AwaitSignal(srv.Stop)

	if m.TreeGCEnabled {
		go func() {
			glog.Info("Deleted tree GC started")
			gc := admin.NewDeletedTreeGC(
				m.Registry.AdminStorage,
				m.TreeDeleteThreshold,
				m.TreeDeleteMinInterval,
				m.Registry.MetricFactory)
			gc.Run(ctx)
		}()
	}

	if err := srv.Serve(lis); err != nil {
		glog.Errorf("RPC server terminated: %v", err)
	}

	glog.Infof("Stopping server, about to exit")
	glog.Flush()

	// Give things a few seconds to tidy up
	time.Sleep(time.Second * 5)

	return nil
}

// newGRPCServer starts a new Trillian gRPC server.
func (m *Main) newGRPCServer() (*grpc.Server, error) {
	ts := util.SystemTimeSource{}
	stats := monitoring.NewRPCStatsInterceptor(ts, m.StatsPrefix, m.Registry.MetricFactory)
	ti := interceptor.New(
		m.Registry.AdminStorage, m.Registry.QuotaManager, m.QuotaDryRun, m.Registry.MetricFactory)
	netInterceptor := interceptor.Combine(stats.Interceptor(), interceptor.ErrorWrapper, ti.UnaryInterceptor)

	serverOpts := []grpc.ServerOption{
		grpc.UnaryInterceptor(netInterceptor),
	}

	// Let credentials.NewServerTLSFromFile handle the error case when only one of the flags is set.
	if m.TLSCertFile != "" || m.TLSKeyFile != "" {
		serverCreds, err := credentials.NewServerTLSFromFile(m.TLSCertFile, m.TLSKeyFile)
		if err != nil {
			return nil, err
		}
		serverOpts = append(serverOpts, grpc.Creds(serverCreds))
	}

	s := grpc.NewServer(serverOpts...)

	return s, nil
}

// AnnounceSelf announces this binary's presence to etcd.  Returns a function that
// should be called on process exit.
// AnnounceSelf does nothing if client is nil.
func AnnounceSelf(ctx context.Context, client *clientv3.Client, etcdService, endpoint string) func() {
	if client == nil {
		return func() {}
	}

	res := etcdnaming.GRPCResolver{Client: client}

	// Get a lease so our entry self-destructs.
	leaseRsp, err := client.Grant(ctx, 30)
	if err != nil {
		glog.Exitf("Failed to get lease from etcd: %v", err)
	}
	client.KeepAlive(ctx, leaseRsp.ID)

	update := naming.Update{Op: naming.Add, Addr: endpoint} // nolint: megacheck
	res.Update(ctx, etcdService, update, clientv3.WithLease(leaseRsp.ID))
	glog.Infof("Announcing our presence in %v with %+v", etcdService, update)

	bye := naming.Update{Op: naming.Delete, Addr: endpoint} // nolint: megacheck
	return func() {
		// Use a background context because the original context may have been cancelled.
		glog.Infof("Removing our presence in %v with %+v", etcdService, bye)
		ctx := context.Background()
		res.Update(ctx, etcdService, bye)
		client.Revoke(ctx, leaseRsp.ID)
	}
}
<commit_msg>Remove unneeded httpMux var<commit_after>// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 server

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

	"github.com/coreos/etcd/clientv3"
	"github.com/golang/glog"
	"github.com/google/trillian"
	"github.com/google/trillian/extension"
	"github.com/google/trillian/monitoring"
	"github.com/google/trillian/server/admin"
	"github.com/google/trillian/server/interceptor"
	"github.com/google/trillian/util"
	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/naming"
	"google.golang.org/grpc/reflection"

	etcdnaming "github.com/coreos/etcd/clientv3/naming"
)

const (
	// DefaultTreeDeleteThreshold is the suggested threshold for tree deletion.
	// It represents the minimum time a tree has to remain Deleted before being hard-deleted.
	DefaultTreeDeleteThreshold = 7 * 24 * time.Hour

	// DefaultTreeDeleteMinInterval is the suggested min interval between tree GC sweeps.
	// A tree GC sweep consists of listing deleted trees older than the deletion threshold and
	// hard-deleting them.
	// Actual runs happen randomly between [minInterval,2*minInterval).
	DefaultTreeDeleteMinInterval = 4 * time.Hour
)

// Main encapsulates the data and logic to start a Trillian server (Log or Map).
type Main struct {
	// Endpoints for RPC and HTTP/REST servers.
	// HTTP/REST is optional, if empty it'll not be bound.
	RPCEndpoint, HTTPEndpoint string

	// TLS Certificate and Key files for the server.
	TLSCertFile, TLSKeyFile string

	DBClose func() error

	Registry extension.Registry

	StatsPrefix string
	QuotaDryRun bool

	// RegisterHandlerFn is called to register REST-proxy handlers.
	RegisterHandlerFn func(context.Context, *runtime.ServeMux, string, []grpc.DialOption) error
	// RegisterServerFn is called to register RPC servers.
	RegisterServerFn func(*grpc.Server, extension.Registry) error

	// AllowedTreeTypes determines which types of trees may be created through the Admin Server
	// bound by Main. nil means unrestricted.
	AllowedTreeTypes []trillian.TreeType

	TreeGCEnabled         bool
	TreeDeleteThreshold   time.Duration
	TreeDeleteMinInterval time.Duration
}

// Run starts the configured server. Blocks until the server exits.
func (m *Main) Run(ctx context.Context) error {
	glog.CopyStandardLogTo("WARNING")

	srv, err := m.newGRPCServer()
	if err != nil {
		glog.Exitf("Error creating gRPC server: %v", err)
	}
	defer srv.GracefulStop()

	defer m.DBClose()

	if err := m.RegisterServerFn(srv, m.Registry); err != nil {
		return err
	}
	trillian.RegisterTrillianAdminServer(srv, admin.New(m.Registry, m.AllowedTreeTypes))
	reflection.Register(srv)

	if endpoint := m.HTTPEndpoint; endpoint != "" {
		gatewayMux := runtime.NewServeMux()
		opts := []grpc.DialOption{grpc.WithInsecure()}
		if err := m.RegisterHandlerFn(ctx, gatewayMux, m.RPCEndpoint, opts); err != nil {
			return err
		}
		if err := trillian.RegisterTrillianAdminHandlerFromEndpoint(ctx, gatewayMux, m.RPCEndpoint, opts); err != nil {
			return err
		}

		http.Handle("/", gatewayMux)
		http.Handle("/metrics", promhttp.Handler())

		go func() {
			glog.Infof("HTTP server starting on %v", endpoint)

			var err error
			// Let http.ListenAndServeTLS handle the error case when only one of the flags is set.
			if m.TLSCertFile != "" || m.TLSKeyFile != "" {
				err = http.ListenAndServeTLS(endpoint, m.TLSCertFile, m.TLSKeyFile, nil)
			} else {
				err = http.ListenAndServe(endpoint, nil)
			}

			if err != nil {
				glog.Errorf("HTTP server stopped: %v", err)
			}
		}()
	}

	glog.Infof("RPC server starting on %v", m.RPCEndpoint)
	lis, err := net.Listen("tcp", m.RPCEndpoint)
	if err != nil {
		return err
	}
	go util.AwaitSignal(srv.Stop)

	if m.TreeGCEnabled {
		go func() {
			glog.Info("Deleted tree GC started")
			gc := admin.NewDeletedTreeGC(
				m.Registry.AdminStorage,
				m.TreeDeleteThreshold,
				m.TreeDeleteMinInterval,
				m.Registry.MetricFactory)
			gc.Run(ctx)
		}()
	}

	if err := srv.Serve(lis); err != nil {
		glog.Errorf("RPC server terminated: %v", err)
	}

	glog.Infof("Stopping server, about to exit")
	glog.Flush()

	// Give things a few seconds to tidy up
	time.Sleep(time.Second * 5)

	return nil
}

// newGRPCServer starts a new Trillian gRPC server.
func (m *Main) newGRPCServer() (*grpc.Server, error) {
	ts := util.SystemTimeSource{}
	stats := monitoring.NewRPCStatsInterceptor(ts, m.StatsPrefix, m.Registry.MetricFactory)
	ti := interceptor.New(
		m.Registry.AdminStorage, m.Registry.QuotaManager, m.QuotaDryRun, m.Registry.MetricFactory)
	netInterceptor := interceptor.Combine(stats.Interceptor(), interceptor.ErrorWrapper, ti.UnaryInterceptor)

	serverOpts := []grpc.ServerOption{
		grpc.UnaryInterceptor(netInterceptor),
	}

	// Let credentials.NewServerTLSFromFile handle the error case when only one of the flags is set.
	if m.TLSCertFile != "" || m.TLSKeyFile != "" {
		serverCreds, err := credentials.NewServerTLSFromFile(m.TLSCertFile, m.TLSKeyFile)
		if err != nil {
			return nil, err
		}
		serverOpts = append(serverOpts, grpc.Creds(serverCreds))
	}

	s := grpc.NewServer(serverOpts...)

	return s, nil
}

// AnnounceSelf announces this binary's presence to etcd.  Returns a function that
// should be called on process exit.
// AnnounceSelf does nothing if client is nil.
func AnnounceSelf(ctx context.Context, client *clientv3.Client, etcdService, endpoint string) func() {
	if client == nil {
		return func() {}
	}

	res := etcdnaming.GRPCResolver{Client: client}

	// Get a lease so our entry self-destructs.
	leaseRsp, err := client.Grant(ctx, 30)
	if err != nil {
		glog.Exitf("Failed to get lease from etcd: %v", err)
	}
	client.KeepAlive(ctx, leaseRsp.ID)

	update := naming.Update{Op: naming.Add, Addr: endpoint} // nolint: megacheck
	res.Update(ctx, etcdService, update, clientv3.WithLease(leaseRsp.ID))
	glog.Infof("Announcing our presence in %v with %+v", etcdService, update)

	bye := naming.Update{Op: naming.Delete, Addr: endpoint} // nolint: megacheck
	return func() {
		// Use a background context because the original context may have been cancelled.
		glog.Infof("Removing our presence in %v with %+v", etcdService, bye)
		ctx := context.Background()
		res.Update(ctx, etcdService, bye)
		client.Revoke(ctx, leaseRsp.ID)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"log"
	"net/http"
	"regexp"
	"runtime/pprof"
	"sync"
	"testing"
	"time"

	"github.com/gorilla/websocket"
	lige "github.com/lucibus/lige/output"

	. "github.com/smartystreets/goconvey/convey"
)

var stateOneDimmer = []byte(`
	{
		"live": {
			"level": 1,
			"systems": [{
				"type": "filter",
				"level": 1,
				"specifiers": {
					"address": 1
				}
			}]
		}
	}
`)

var stateNoDimmers = InitialStateBytes

func shouldSend(conn *websocket.Conn, message []byte) {
	err := conn.WriteMessage(websocket.TextMessage, message)
	So(err, ShouldBeNil)
}

func shouldGet(conn *websocket.Conn, message []byte) {
	messageType, p, err := conn.ReadMessage()
	So(err, ShouldBeNil)
	So(messageType, ShouldEqual, websocket.TextMessage)
	So(p, ShouldResemble, message)
}

func shouldCloseConnection(actual interface{}, _ ...interface{}) string {
	conn := actual.(*websocket.Conn)
	err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
	if err != nil {
		return "Websocket connection didn't close properly: " + err.Error()
	}
	return ""
}

func shouldNotBeRunningGoroutines(actual interface{}, _ ...interface{}) string {
	module := actual.(string)
	var b bytes.Buffer
	pprof.Lookup("goroutine").WriteTo(&b, 1)
	scanner := bufio.NewScanner(&b)
	for scanner.Scan() {
		t := scanner.Text()
		runningInModule := regexp.MustCompile(module).MatchString(t)
		runningTest := regexp.MustCompile("_test").MatchString(t)
		runningOtherFileInModule := runningInModule && !runningTest
		if runningOtherFileInModule {
			pprof.Lookup("goroutine").WriteTo(&b, 2)
			return "Was running other goroutines: " + b.String()
		}
	}
	return ""
}

type testOutputDevice struct {
	output lige.State
	sync.Mutex
}

func (od *testOutputDevice) Set(s lige.State) error {
	od.Lock()
	od.output = s
	od.Unlock()
	return nil
}

func (od *testOutputDevice) get() lige.State {
	od.Lock()
	defer od.Unlock()
	return od.output
}

func TestServer(t *testing.T) {
	Convey("when i start the server", t, func() {
		port := 9001
		od := testOutputDevice{}
		cancelF, err := CreateServer(port, &od)
		So(err, ShouldBeNil)
		url := fmt.Sprintf("ws://localhost:%v/", port)
		d := websocket.Dialer{}
		Convey("and connect", func() {
			conn, _, err := d.Dial(url, http.Header{})
			So(err, ShouldBeNil)
			Convey("it should return a default state", func() {
				shouldGet(conn, stateNoDimmers)

				Convey("it should take an updated state", func() {
					newState := stateOneDimmer
					shouldSend(conn, newState)

					Convey("and it should output the new state", func() {
						time.Sleep(time.Second / 2)
						So(od.get(), ShouldResemble, lige.State{1: 255})
					})

					Convey("and reconnect", func() {
						shouldCloseConnection(conn)
						conn, _, err = d.Dial(url, http.Header{})
						So(err, ShouldBeNil)

						Convey("it should return the same state", func() {
							shouldGet(conn, newState)
						})
					})
					Convey("and connect with another client", func() {
						conn2, _, err := d.Dial(url, http.Header{})
						So(err, ShouldBeNil)
						Convey("it should initally recieve the state set by the first", func() {
							shouldGet(conn2, newState)
							Convey("and then when the first changes the state, it should get it", func() {
								secondNewState := []byte(`{"test2": "hi"}`)
								shouldSend(conn, secondNewState)
								shouldGet(conn2, secondNewState)
								Convey("and vice versa", func() {
									thirdNewState := []byte(`{"test3": "hi"}`)
									shouldSend(conn2, thirdNewState)
									shouldGet(conn, thirdNewState)
								})

							})
							conn.WriteMessage(websocket.TextMessage, newState)
						})
						Reset(func() {
							So(conn2.Close(), ShouldBeNil)
						})
					})
				})
			})

			Convey("output should be blank", func() {
				So(od.get(), ShouldResemble, lige.State{})
			})
			Reset(func() {
				shouldCloseConnection(conn)
			})

		})

		Reset(func() {
			cancelF()
			time.Sleep(time.Millisecond)
			So("subicul", shouldNotBeRunningGoroutines)
		})

	})

}

func BenchmarkServer(b *testing.B) {
	//	runtime.GOMAXPROCS(runtime.NumCPU())
	port := 9001
	od := testOutputDevice{}
	cancelF, err := CreateServer(port, &od)
	if err != nil {
		log.Panicln("error in create server", err)
	}
	url := fmt.Sprintf("ws://localhost:%v/", port)
	d := websocket.Dialer{}
	conn, _, err := d.Dial(url, http.Header{})
	if err != nil {
		log.Panicln("error in dial", err)
	}
	conn.ReadMessage()

	toggleSendOneDimmer := true
	var message []byte
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		b.StopTimer()
		prevLength := len(od.output)
		if toggleSendOneDimmer {
			message = stateOneDimmer
		} else {
			message = stateNoDimmers
		}
		toggleSendOneDimmer = !toggleSendOneDimmer
		b.StartTimer()
		conn.WriteMessage(websocket.TextMessage, message)
		for {
			time.Sleep(time.Nanosecond)
			if len(od.output) != prevLength {
				break
			}
		}
	}
	b.StopTimer()
	shouldCloseConnection(conn)
	cancelF()
	time.Sleep(time.Nanosecond)
}
<commit_msg>Fix test checking for goroutines.<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"log"
	"net/http"
	"regexp"
	"runtime/pprof"
	"sync"
	"testing"
	"time"

	"github.com/gorilla/websocket"
	lige "github.com/lucibus/lige/output"

	. "github.com/smartystreets/goconvey/convey"
)

var stateOneDimmer = []byte(`
	{
		"live": {
			"level": 1,
			"systems": [{
				"type": "filter",
				"level": 1,
				"specifiers": {
					"address": 1
				}
			}]
		}
	}
`)

var stateNoDimmers = InitialStateBytes

func shouldSend(conn *websocket.Conn, message []byte) {
	err := conn.WriteMessage(websocket.TextMessage, message)
	So(err, ShouldBeNil)
}

func shouldGet(conn *websocket.Conn, message []byte) {
	messageType, p, err := conn.ReadMessage()
	So(err, ShouldBeNil)
	So(messageType, ShouldEqual, websocket.TextMessage)
	So(p, ShouldResemble, message)
}

func shouldCloseConnection(actual interface{}, _ ...interface{}) string {
	conn := actual.(*websocket.Conn)
	err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
	if err != nil {
		return "Websocket connection didn't close properly: " + err.Error()
	}
	return ""
}

func shouldNotBeRunningGoroutines(actual interface{}, _ ...interface{}) string {
	module := actual.(string)
	var b bytes.Buffer
	pprof.Lookup("goroutine").WriteTo(&b, 1)
	scanner := bufio.NewScanner(&b)
	for scanner.Scan() {
		t := scanner.Text()
		runningInModule := regexp.MustCompile(module).MatchString(t)
		runningTest := regexp.MustCompile("_test").MatchString(t)
		runningExternal := regexp.MustCompile("Godeps").MatchString(t)
		runningOtherFileInModule := runningInModule && !runningTest && !runningExternal
		if runningOtherFileInModule {
			pprof.Lookup("goroutine").WriteTo(&b, 2)
			return "Was running other goroutines: " + t + b.String()
		}
	}
	return ""
}

type testOutputDevice struct {
	output lige.State
	sync.Mutex
}

func (od *testOutputDevice) Set(s lige.State) error {
	od.Lock()
	od.output = s
	od.Unlock()
	return nil
}

func (od *testOutputDevice) get() lige.State {
	od.Lock()
	defer od.Unlock()
	return od.output
}

func TestServer(t *testing.T) {
	Convey("when i start the server", t, func() {
		port := 9001
		od := testOutputDevice{}
		cancelF, err := CreateServer(port, &od)
		So(err, ShouldBeNil)
		url := fmt.Sprintf("ws://localhost:%v/", port)
		d := websocket.Dialer{}
		Convey("and connect", func() {
			conn, _, err := d.Dial(url, http.Header{})
			So(err, ShouldBeNil)
			Convey("it should return a default state", func() {
				shouldGet(conn, stateNoDimmers)

				Convey("it should take an updated state", func() {
					newState := stateOneDimmer
					shouldSend(conn, newState)

					Convey("and it should output the new state", func() {
						time.Sleep(time.Second / 2)
						So(od.get(), ShouldResemble, lige.State{1: 255})
					})

					Convey("and reconnect", func() {
						shouldCloseConnection(conn)
						conn, _, err = d.Dial(url, http.Header{})
						So(err, ShouldBeNil)

						Convey("it should return the same state", func() {
							shouldGet(conn, newState)
						})
					})
					Convey("and connect with another client", func() {
						conn2, _, err := d.Dial(url, http.Header{})
						So(err, ShouldBeNil)
						Convey("it should initally recieve the state set by the first", func() {
							shouldGet(conn2, newState)
							Convey("and then when the first changes the state, it should get it", func() {
								secondNewState := []byte(`{"test2": "hi"}`)
								shouldSend(conn, secondNewState)
								shouldGet(conn2, secondNewState)
								Convey("and vice versa", func() {
									thirdNewState := []byte(`{"test3": "hi"}`)
									shouldSend(conn2, thirdNewState)
									shouldGet(conn, thirdNewState)
								})

							})
							conn.WriteMessage(websocket.TextMessage, newState)
						})
						Reset(func() {
							So(conn2.Close(), ShouldBeNil)
						})
					})
				})
			})

			Convey("output should be blank", func() {
				So(od.get(), ShouldResemble, lige.State{})
			})
			Reset(func() {
				shouldCloseConnection(conn)
			})

		})

		Reset(func() {
			cancelF()
			time.Sleep(time.Millisecond)
			So("subicul", shouldNotBeRunningGoroutines)
		})

	})

}

func BenchmarkServer(b *testing.B) {
	//	runtime.GOMAXPROCS(runtime.NumCPU())
	port := 9001
	od := testOutputDevice{}
	cancelF, err := CreateServer(port, &od)
	if err != nil {
		log.Panicln("error in create server", err)
	}
	url := fmt.Sprintf("ws://localhost:%v/", port)
	d := websocket.Dialer{}
	conn, _, err := d.Dial(url, http.Header{})
	if err != nil {
		log.Panicln("error in dial", err)
	}
	conn.ReadMessage()

	toggleSendOneDimmer := true
	var message []byte
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		b.StopTimer()
		prevLength := len(od.output)
		if toggleSendOneDimmer {
			message = stateOneDimmer
		} else {
			message = stateNoDimmers
		}
		toggleSendOneDimmer = !toggleSendOneDimmer
		b.StartTimer()
		conn.WriteMessage(websocket.TextMessage, message)
		for {
			time.Sleep(time.Nanosecond)
			if len(od.output) != prevLength {
				break
			}
		}
	}
	b.StopTimer()
	shouldCloseConnection(conn)
	cancelF()
	time.Sleep(time.Nanosecond)
}
<|endoftext|>