<commit_before>// Copyright 2012-2014 Apcera Inc. All rights reserved.

package server

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"sync"
	"time"

	// Allow dynamic profiling.
	_ "net/http/pprof"

	"github.com/apcera/gnatsd/sublist"
)

// Info is the information sent to clients to help them understand information
// about this server.
type Info struct {
	ID           string `json:"server_id"`
	Version      string `json:"version"`
	Host         string `json:"host"`
	Port         int    `json:"port"`
	AuthRequired bool   `json:"auth_required"`
	SslRequired  bool   `json:"ssl_required"`
	MaxPayload   int    `json:"max_payload"`
}

// Server is our main struct.
type Server struct {
	mu       sync.Mutex
	info     Info
	infoJSON []byte
	sl       *sublist.Sublist
	gcid     uint64
	opts     *Options
	auth     Auth
	trace    bool
	debug    bool
	running  bool
	listener net.Listener
	clients  map[uint64]*client
	routes   map[uint64]*client
	remotes  map[string]*client
	done     chan bool
	start    time.Time
	http     net.Listener
	stats

	routeListener net.Listener
	grid          uint64
	routeInfo     Info
	routeInfoJSON []byte
	rcQuit        chan bool
}

type stats struct {
	inMsgs   int64
	outMsgs  int64
	inBytes  int64
	outBytes int64
}

// New will setup a new server struct after parsing the options.
func New(opts *Options) *Server {
	processOptions(opts)
	info := Info{
		ID:           genID(),
		Version:      VERSION,
		Host:         opts.Host,
		Port:         opts.Port,
		AuthRequired: false,
		SslRequired:  false,
		MaxPayload:   MAX_PAYLOAD_SIZE,
	}

	s := &Server{
		info:  info,
		sl:    sublist.New(),
		opts:  opts,
		debug: opts.Debug,
		trace: opts.Trace,
		done:  make(chan bool, 1),
		start: time.Now(),
	}

	s.mu.Lock()
	defer s.mu.Unlock()

	// For tracking clients
	s.clients = make(map[uint64]*client)

	// For tracking routes and their remote ids
	s.routes = make(map[uint64]*client)
	s.remotes = make(map[string]*client)

	// Used to kick out all of the route
	// connect Go routines.
	s.rcQuit = make(chan bool)
	s.generateServerInfoJSON()
	s.handleSignals()

	return s
}

// Sets the authentication method
func (s *Server) SetAuthMethod(authMethod Auth) {
	s.info.AuthRequired = true
	s.auth = authMethod

	s.generateServerInfoJSON()
}

func (s *Server) generateServerInfoJSON() {
	// Generate the info json
	b, err := json.Marshal(s.info)
	if err != nil {
		Fatalf("Error marshalling INFO JSON: %+v\n", err)
	}
	s.infoJSON = []byte(fmt.Sprintf("INFO %s %s", b, CR_LF))
}

// PrintAndDie is exported for access in other packages.
func PrintAndDie(msg string) {
	fmt.Fprintf(os.Stderr, "%s\n", msg)
	os.Exit(1)
}

// PrintServerAndExit will print our version and exit.
func PrintServerAndExit() {
	fmt.Printf("gnatsd version %s\n", VERSION)
	os.Exit(0)
}

// Signal Handling
func (s *Server) handleSignals() {
	if s.opts.NoSigs {
		return
	}
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for sig := range c {
			Debugf("Trapped Signal; %v", sig)
			// FIXME, trip running?
			Noticef("Server Exiting..")
			os.Exit(0)
		}
	}()
}

// Protected check on running state
func (s *Server) isRunning() bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.running
}

func (s *Server) logPid() {
	pidStr := strconv.Itoa(os.Getpid())
	err := ioutil.WriteFile(s.opts.PidFile, []byte(pidStr), 0660)
	if err != nil {
		PrintAndDie(fmt.Sprintf("Could not write pidfile: %v\n", err))
	}
}

// Start up the server, this will block.
// Start via a Go routine if needed.
func (s *Server) Start() {
	Noticef("Starting gnatsd version %s", VERSION)
	s.running = true

	// Log the pid to a file
	if s.opts.PidFile != _EMPTY_ {
		s.logPid()
	}

	// Start up the http server if needed.
	if s.opts.HTTPPort != 0 {
		s.StartHTTPMonitoring()
	}

	// Start up routing as well if needed.
	if s.opts.ClusterPort != 0 {
		s.StartRouting()
	}

	// Pprof http endpoint for the profiler.
	if s.opts.ProfPort != 0 {
		s.StartProfiler()
	}

	// Wait for clients.
	s.AcceptLoop()
}

// Shutdown will shutdown the server instance by kicking out the AcceptLoop
// and closing all associated clients.
func (s *Server) Shutdown() {
	s.mu.Lock()

	// Prevent issues with multiple calls.
	if !s.running {
		s.mu.Unlock()
		return
	}

	s.running = false

	conns := make(map[uint64]*client)

	// Copy off the clients
	for i, c := range s.clients {
		conns[i] = c
	}
	// Copy off the routes
	for i, r := range s.routes {
		conns[i] = r
	}

	// Number of done channel responses we expect.
	doneExpected := 0

	// Kick client AcceptLoop()
	if s.listener != nil {
		doneExpected++
		s.listener.Close()
		s.listener = nil
	}

	// Kick route AcceptLoop()
	if s.routeListener != nil {
		doneExpected++
		s.routeListener.Close()
		s.routeListener = nil
	}

	// Kick HTTP monitoring if its running
	if s.http != nil {
		doneExpected++
		s.http.Close()
		s.http = nil
	}

	// Release the solicited routes connect go routines.
	close(s.rcQuit)

	s.mu.Unlock()

	// Close client and route connections
	for _, c := range conns {
		c.closeConnection()
	}

	// Block until the accept loops exit
	for doneExpected > 0 {
		<-s.done
		doneExpected--
	}
}

// AcceptLoop is exported for easier testing.
func (s *Server) AcceptLoop() {
	hp := fmt.Sprintf("%s:%d", s.opts.Host, s.opts.Port)
	Noticef("Listening for client connections on %s", hp)
	l, e := net.Listen("tcp", hp)
	if e != nil {
		Fatalf("Error listening on port: %s, %q", hp, e)
		return
	}

	Noticef("gnatsd is ready")

	// Setup state that can enable shutdown
	s.mu.Lock()
	s.listener = l
	s.mu.Unlock()

	// Write resolved port back to options.
	_, port, err := net.SplitHostPort(l.Addr().String())
	if err != nil {
		Fatalf("Error parsing server address (%s): %s", l.Addr().String(), e)
		return
	}
	portNum, err := strconv.Atoi(port)
	if err != nil {
		Fatalf("Error parsing server address (%s): %s", l.Addr().String(), e)
		return
	}
	s.opts.Port = portNum

	tmpDelay := ACCEPT_MIN_SLEEP

	for s.isRunning() {
		conn, err := l.Accept()
		if err != nil {
			if ne, ok := err.(net.Error); ok && ne.Temporary() {
				Debugf("Temporary Client Accept Error(%v), sleeping %dms",
					ne, tmpDelay/time.Millisecond)
				time.Sleep(tmpDelay)
				tmpDelay *= 2
				if tmpDelay > ACCEPT_MAX_SLEEP {
					tmpDelay = ACCEPT_MAX_SLEEP
				}
			} else if s.isRunning() {
				Noticef("Accept error: %v", err)
			}
			continue
		}
		tmpDelay = ACCEPT_MIN_SLEEP
		s.createClient(conn)
	}
	Noticef("Server Exiting..")
	s.done <- true
}

// StartProfiler is called to enable dynamic profiling.
func (s *Server) StartProfiler() {
	Noticef("Starting profiling on http port %d", s.opts.ProfPort)

	hp := fmt.Sprintf("%s:%d", s.opts.Host, s.opts.ProfPort)
	go func() {
		err := http.ListenAndServe(hp, nil)
		if err != nil {
			Fatalf("error starting monitor server: %s", err)
		}
	}()
}

// StartHTTPMonitoring will enable the HTTP monitoring port.
func (s *Server) StartHTTPMonitoring() {
	Noticef("Starting http monitor on port %d", s.opts.HTTPPort)

	hp := fmt.Sprintf("%s:%d", s.opts.Host, s.opts.HTTPPort)

	l, err := net.Listen("tcp", hp)
	if err != nil {
		Fatalf("Can't listen to the monitor port: %v", err)
	}

	mux := http.NewServeMux()

	// Varz
	mux.HandleFunc("/varz", s.HandleVarz)

	// Connz
	mux.HandleFunc("/connz", s.HandleConnz)

	// Subz
	mux.HandleFunc("/subscriptionsz", s.HandleSubsz)

	srv := &http.Server{
		Addr:           hp,
		Handler:        mux,
		ReadTimeout:    2 * time.Second,
		WriteTimeout:   2 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	s.http = l

	go func() {
		srv.Serve(s.http)
		srv.Handler = nil
		s.done <- true
	}()
}

func (s *Server) createClient(conn net.Conn) *client {
	c := &client{srv: s, nc: conn, opts: defaultOpts}

	// Grab lock
	c.mu.Lock()

	// Initialize
	c.initClient()

	c.Debugf("Client connection created")

	// Send our information.
	s.sendInfo(c)

	// Check for Auth
	if s.info.AuthRequired {
		ttl := secondsToDuration(s.opts.AuthTimeout)
		c.setAuthTimer(ttl)
	}

	// Unlock to register
	c.mu.Unlock()

	// Register with the server.
	s.mu.Lock()
	s.clients[c.cid] = c
	s.mu.Unlock()

	return c
}

// Assume the lock is held upon entry.
func (s *Server) sendInfo(c *client) {
	switch c.typ {
	case CLIENT:
		c.nc.Write(s.infoJSON)
	case ROUTER:
		c.nc.Write(s.routeInfoJSON)
	}
}

func (s *Server) checkClientAuth(c *client) bool {
	if s.auth == nil {
		return true
	}

	return s.auth.Check(c)
}

func (s *Server) checkRouterAuth(c *client) bool {
	if !s.routeInfo.AuthRequired {
		return true
	}
	if s.opts.ClusterUsername != c.opts.Username ||
		s.opts.ClusterPassword != c.opts.Password {
		return false
	}
	return true
}

// Check auth and return boolean indicating if client is ok
func (s *Server) checkAuth(c *client) bool {
	switch c.typ {
	case CLIENT:
		return s.checkClientAuth(c)
	case ROUTER:
		return s.checkRouterAuth(c)
	default:
		return false
	}
}

// Remove a client or route from our internal accounting.
func (s *Server) removeClient(c *client) {
	c.mu.Lock()
	cid := c.cid
	typ := c.typ
	c.mu.Unlock()

	s.mu.Lock()
	switch typ {
	case CLIENT:
		delete(s.clients, cid)
	case ROUTER:
		delete(s.routes, cid)
		if c.route != nil {
			rc, ok := s.remotes[c.route.remoteID]
			// Only delete it if it is us..
			if ok && c == rc {
				delete(s.remotes, c.route.remoteID)
			}
		}
	}
	s.mu.Unlock()
}

/////////////////////////////////////////////////////////////////
// These are some helpers for accounting in functional tests.
/////////////////////////////////////////////////////////////////

// NumRoutes will report the number of registered routes.
func (s *Server) NumRoutes() int {
	s.mu.Lock()
	defer s.mu.Unlock()
	return len(s.routes)
}

// NumRemotes will report number of registered remotes.
func (s *Server) NumRemotes() int {
	s.mu.Lock()
	defer s.mu.Unlock()
	return len(s.remotes)
}

// NumClients will report the number of registered clients.
func (s *Server) NumClients() int {
	s.mu.Lock()
	defer s.mu.Unlock()
	return len(s.clients)
}

// NumSubscriptions will report how many subscriptions are active.
func (s *Server) NumSubscriptions() uint32 {
	s.mu.Lock()
	defer s.mu.Unlock()
	stats := s.sl.Stats()
	return stats.NumSubs
}

// Addr will return the net.Addr object for the current listener.
func (s *Server) Addr() net.Addr {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.listener == nil {
		return nil
	}
	return s.listener.Addr()
}
<commit_msg>Go's atomic align bug workaround.<commit_after>// Copyright 2012-2014 Apcera Inc. All rights reserved.

package server

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"sync"
	"time"

	// Allow dynamic profiling.
	_ "net/http/pprof"

	"github.com/apcera/gnatsd/sublist"
)

// Info is the information sent to clients to help them understand information
// about this server.
type Info struct {
	ID           string `json:"server_id"`
	Version      string `json:"version"`
	Host         string `json:"host"`
	Port         int    `json:"port"`
	AuthRequired bool   `json:"auth_required"`
	SslRequired  bool   `json:"ssl_required"`
	MaxPayload   int    `json:"max_payload"`
}

// Server is our main struct.
type Server struct {
	gcid     uint64
	grid     uint64
	mu       sync.Mutex
	info     Info
	infoJSON []byte
	sl       *sublist.Sublist
	opts     *Options
	auth     Auth
	trace    bool
	debug    bool
	running  bool
	listener net.Listener
	clients  map[uint64]*client
	routes   map[uint64]*client
	remotes  map[string]*client
	done     chan bool
	start    time.Time
	http     net.Listener
	stats

	routeListener net.Listener
	routeInfo     Info
	routeInfoJSON []byte
	rcQuit        chan bool
}

type stats struct {
	inMsgs   int64
	outMsgs  int64
	inBytes  int64
	outBytes int64
}

// New will setup a new server struct after parsing the options.
func New(opts *Options) *Server {
	processOptions(opts)
	info := Info{
		ID:           genID(),
		Version:      VERSION,
		Host:         opts.Host,
		Port:         opts.Port,
		AuthRequired: false,
		SslRequired:  false,
		MaxPayload:   MAX_PAYLOAD_SIZE,
	}

	s := &Server{
		info:  info,
		sl:    sublist.New(),
		opts:  opts,
		debug: opts.Debug,
		trace: opts.Trace,
		done:  make(chan bool, 1),
		start: time.Now(),
	}

	s.mu.Lock()
	defer s.mu.Unlock()

	// For tracking clients
	s.clients = make(map[uint64]*client)

	// For tracking routes and their remote ids
	s.routes = make(map[uint64]*client)
	s.remotes = make(map[string]*client)

	// Used to kick out all of the route
	// connect Go routines.
	s.rcQuit = make(chan bool)
	s.generateServerInfoJSON()
	s.handleSignals()

	return s
}

// Sets the authentication method
func (s *Server) SetAuthMethod(authMethod Auth) {
	s.info.AuthRequired = true
	s.auth = authMethod

	s.generateServerInfoJSON()
}

func (s *Server) generateServerInfoJSON() {
	// Generate the info json
	b, err := json.Marshal(s.info)
	if err != nil {
		Fatalf("Error marshalling INFO JSON: %+v\n", err)
	}
	s.infoJSON = []byte(fmt.Sprintf("INFO %s %s", b, CR_LF))
}

// PrintAndDie is exported for access in other packages.
func PrintAndDie(msg string) {
	fmt.Fprintf(os.Stderr, "%s\n", msg)
	os.Exit(1)
}

// PrintServerAndExit will print our version and exit.
func PrintServerAndExit() {
	fmt.Printf("gnatsd version %s\n", VERSION)
	os.Exit(0)
}

// Signal Handling
func (s *Server) handleSignals() {
	if s.opts.NoSigs {
		return
	}
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for sig := range c {
			Debugf("Trapped Signal; %v", sig)
			// FIXME, trip running?
			Noticef("Server Exiting..")
			os.Exit(0)
		}
	}()
}

// Protected check on running state
func (s *Server) isRunning() bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.running
}

func (s *Server) logPid() {
	pidStr := strconv.Itoa(os.Getpid())
	err := ioutil.WriteFile(s.opts.PidFile, []byte(pidStr), 0660)
	if err != nil {
		PrintAndDie(fmt.Sprintf("Could not write pidfile: %v\n", err))
	}
}

// Start up the server, this will block.
// Start via a Go routine if needed.
func (s *Server) Start() {
	Noticef("Starting gnatsd version %s", VERSION)
	s.running = true

	// Log the pid to a file
	if s.opts.PidFile != _EMPTY_ {
		s.logPid()
	}

	// Start up the http server if needed.
	if s.opts.HTTPPort != 0 {
		s.StartHTTPMonitoring()
	}

	// Start up routing as well if needed.
	if s.opts.ClusterPort != 0 {
		s.StartRouting()
	}

	// Pprof http endpoint for the profiler.
	if s.opts.ProfPort != 0 {
		s.StartProfiler()
	}

	// Wait for clients.
	s.AcceptLoop()
}

// Shutdown will shutdown the server instance by kicking out the AcceptLoop
// and closing all associated clients.
func (s *Server) Shutdown() {
	s.mu.Lock()

	// Prevent issues with multiple calls.
	if !s.running {
		s.mu.Unlock()
		return
	}

	s.running = false

	conns := make(map[uint64]*client)

	// Copy off the clients
	for i, c := range s.clients {
		conns[i] = c
	}
	// Copy off the routes
	for i, r := range s.routes {
		conns[i] = r
	}

	// Number of done channel responses we expect.
	doneExpected := 0

	// Kick client AcceptLoop()
	if s.listener != nil {
		doneExpected++
		s.listener.Close()
		s.listener = nil
	}

	// Kick route AcceptLoop()
	if s.routeListener != nil {
		doneExpected++
		s.routeListener.Close()
		s.routeListener = nil
	}

	// Kick HTTP monitoring if its running
	if s.http != nil {
		doneExpected++
		s.http.Close()
		s.http = nil
	}

	// Release the solicited routes connect go routines.
	close(s.rcQuit)

	s.mu.Unlock()

	// Close client and route connections
	for _, c := range conns {
		c.closeConnection()
	}

	// Block until the accept loops exit
	for doneExpected > 0 {
		<-s.done
		doneExpected--
	}
}

// AcceptLoop is exported for easier testing.
func (s *Server) AcceptLoop() {
	hp := fmt.Sprintf("%s:%d", s.opts.Host, s.opts.Port)
	Noticef("Listening for client connections on %s", hp)
	l, e := net.Listen("tcp", hp)
	if e != nil {
		Fatalf("Error listening on port: %s, %q", hp, e)
		return
	}

	Noticef("gnatsd is ready")

	// Setup state that can enable shutdown
	s.mu.Lock()
	s.listener = l
	s.mu.Unlock()

	// Write resolved port back to options.
	_, port, err := net.SplitHostPort(l.Addr().String())
	if err != nil {
		Fatalf("Error parsing server address (%s): %s", l.Addr().String(), e)
		return
	}
	portNum, err := strconv.Atoi(port)
	if err != nil {
		Fatalf("Error parsing server address (%s): %s", l.Addr().String(), e)
		return
	}
	s.opts.Port = portNum

	tmpDelay := ACCEPT_MIN_SLEEP

	for s.isRunning() {
		conn, err := l.Accept()
		if err != nil {
			if ne, ok := err.(net.Error); ok && ne.Temporary() {
				Debugf("Temporary Client Accept Error(%v), sleeping %dms",
					ne, tmpDelay/time.Millisecond)
				time.Sleep(tmpDelay)
				tmpDelay *= 2
				if tmpDelay > ACCEPT_MAX_SLEEP {
					tmpDelay = ACCEPT_MAX_SLEEP
				}
			} else if s.isRunning() {
				Noticef("Accept error: %v", err)
			}
			continue
		}
		tmpDelay = ACCEPT_MIN_SLEEP
		s.createClient(conn)
	}
	Noticef("Server Exiting..")
	s.done <- true
}

// StartProfiler is called to enable dynamic profiling.
func (s *Server) StartProfiler() {
	Noticef("Starting profiling on http port %d", s.opts.ProfPort)

	hp := fmt.Sprintf("%s:%d", s.opts.Host, s.opts.ProfPort)
	go func() {
		err := http.ListenAndServe(hp, nil)
		if err != nil {
			Fatalf("error starting monitor server: %s", err)
		}
	}()
}

// StartHTTPMonitoring will enable the HTTP monitoring port.
func (s *Server) StartHTTPMonitoring() {
	Noticef("Starting http monitor on port %d", s.opts.HTTPPort)

	hp := fmt.Sprintf("%s:%d", s.opts.Host, s.opts.HTTPPort)

	l, err := net.Listen("tcp", hp)
	if err != nil {
		Fatalf("Can't listen to the monitor port: %v", err)
	}

	mux := http.NewServeMux()

	// Varz
	mux.HandleFunc("/varz", s.HandleVarz)

	// Connz
	mux.HandleFunc("/connz", s.HandleConnz)

	// Subz
	mux.HandleFunc("/subscriptionsz", s.HandleSubsz)

	srv := &http.Server{
		Addr:           hp,
		Handler:        mux,
		ReadTimeout:    2 * time.Second,
		WriteTimeout:   2 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	s.http = l

	go func() {
		srv.Serve(s.http)
		srv.Handler = nil
		s.done <- true
	}()
}

func (s *Server) createClient(conn net.Conn) *client {
	c := &client{srv: s, nc: conn, opts: defaultOpts}

	// Grab lock
	c.mu.Lock()

	// Initialize
	c.initClient()

	c.Debugf("Client connection created")

	// Send our information.
	s.sendInfo(c)

	// Check for Auth
	if s.info.AuthRequired {
		ttl := secondsToDuration(s.opts.AuthTimeout)
		c.setAuthTimer(ttl)
	}

	// Unlock to register
	c.mu.Unlock()

	// Register with the server.
	s.mu.Lock()
	s.clients[c.cid] = c
	s.mu.Unlock()

	return c
}

// Assume the lock is held upon entry.
func (s *Server) sendInfo(c *client) {
	switch c.typ {
	case CLIENT:
		c.nc.Write(s.infoJSON)
	case ROUTER:
		c.nc.Write(s.routeInfoJSON)
	}
}

func (s *Server) checkClientAuth(c *client) bool {
	if s.auth == nil {
		return true
	}

	return s.auth.Check(c)
}

func (s *Server) checkRouterAuth(c *client) bool {
	if !s.routeInfo.AuthRequired {
		return true
	}
	if s.opts.ClusterUsername != c.opts.Username ||
		s.opts.ClusterPassword != c.opts.Password {
		return false
	}
	return true
}

// Check auth and return boolean indicating if client is ok
func (s *Server) checkAuth(c *client) bool {
	switch c.typ {
	case CLIENT:
		return s.checkClientAuth(c)
	case ROUTER:
		return s.checkRouterAuth(c)
	default:
		return false
	}
}

// Remove a client or route from our internal accounting.
func (s *Server) removeClient(c *client) {
	c.mu.Lock()
	cid := c.cid
	typ := c.typ
	c.mu.Unlock()

	s.mu.Lock()
	switch typ {
	case CLIENT:
		delete(s.clients, cid)
	case ROUTER:
		delete(s.routes, cid)
		if c.route != nil {
			rc, ok := s.remotes[c.route.remoteID]
			// Only delete it if it is us..
			if ok && c == rc {
				delete(s.remotes, c.route.remoteID)
			}
		}
	}
	s.mu.Unlock()
}

/////////////////////////////////////////////////////////////////
// These are some helpers for accounting in functional tests.
/////////////////////////////////////////////////////////////////

// NumRoutes will report the number of registered routes.
func (s *Server) NumRoutes() int {
	s.mu.Lock()
	defer s.mu.Unlock()
	return len(s.routes)
}

// NumRemotes will report number of registered remotes.
func (s *Server) NumRemotes() int {
	s.mu.Lock()
	defer s.mu.Unlock()
	return len(s.remotes)
}

// NumClients will report the number of registered clients.
func (s *Server) NumClients() int {
	s.mu.Lock()
	defer s.mu.Unlock()
	return len(s.clients)
}

// NumSubscriptions will report how many subscriptions are active.
func (s *Server) NumSubscriptions() uint32 {
	s.mu.Lock()
	defer s.mu.Unlock()
	stats := s.sl.Stats()
	return stats.NumSubs
}

// Addr will return the net.Addr object for the current listener.
func (s *Server) Addr() net.Addr {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.listener == nil {
		return nil
	}
	return s.listener.Addr()
}
<|endoftext|>


<commit_before>//  Copyright (c) 2013 Couchbase, 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 server

import (
	"fmt"
	"strings"

	"github.com/couchbaselabs/tuqtng/catalog"
	"github.com/couchbaselabs/tuqtng/catalog/couchbase"
	"github.com/couchbaselabs/tuqtng/catalog/file"
	"github.com/couchbaselabs/tuqtng/catalog/mock"
	"github.com/couchbaselabs/tuqtng/network"
	"github.com/couchbaselabs/tuqtng/qpipeline/static"

	"github.com/couchbaselabs/clog"
)

func Site(s string) (catalog.Site, error) {
	if strings.HasPrefix(s, ".") || strings.HasPrefix(s, "/") {
		return file.NewSite(s)
	}
	if strings.HasPrefix(s, "dir:") {
		return file.NewSite(s[4:])
	}
	if strings.HasPrefix(s, "mock:") {
		return mock.NewSite(s)
	}
	return couchbase.NewSite(s)
}

func Server(version, siteName, defaultPoolName string,
	queryChannel network.QueryChannel) error {
	site, err := Site(siteName)
	if err != nil {
		return fmt.Errorf("Unable to access site %s, err: %v", site, err)
	}

	// create a StaticQueryPipeline we use to process queries
	queryPipeline := static.NewStaticPipeline(site, defaultPoolName)

	clog.Log("tuqtng started...")
	clog.Log("version: %s", version)
	clog.Log("site: %s", siteName)

	// dispatch each query that comes in
	for query := range queryChannel {
		go queryPipeline.DispatchQuery(query)
	}

	return nil
}
<commit_msg>fix bug printing error message<commit_after>//  Copyright (c) 2013 Couchbase, 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 server

import (
	"fmt"
	"strings"

	"github.com/couchbaselabs/tuqtng/catalog"
	"github.com/couchbaselabs/tuqtng/catalog/couchbase"
	"github.com/couchbaselabs/tuqtng/catalog/file"
	"github.com/couchbaselabs/tuqtng/catalog/mock"
	"github.com/couchbaselabs/tuqtng/network"
	"github.com/couchbaselabs/tuqtng/qpipeline/static"

	"github.com/couchbaselabs/clog"
)

func Site(s string) (catalog.Site, error) {
	if strings.HasPrefix(s, ".") || strings.HasPrefix(s, "/") {
		return file.NewSite(s)
	}
	if strings.HasPrefix(s, "dir:") {
		return file.NewSite(s[4:])
	}
	if strings.HasPrefix(s, "mock:") {
		return mock.NewSite(s)
	}
	return couchbase.NewSite(s)
}

func Server(version, siteName, defaultPoolName string,
	queryChannel network.QueryChannel) error {
	site, err := Site(siteName)
	if err != nil {
		return fmt.Errorf("Unable to access site %s, err: %v", siteName, err)
	}

	// create a StaticQueryPipeline we use to process queries
	queryPipeline := static.NewStaticPipeline(site, defaultPoolName)

	clog.Log("tuqtng started...")
	clog.Log("version: %s", version)
	clog.Log("site: %s", siteName)

	// dispatch each query that comes in
	for query := range queryChannel {
		go queryPipeline.DispatchQuery(query)
	}

	return nil
}
<|endoftext|>


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

package server

import (
	"errors"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"sync"

	"github.com/jroimartin/orujo"
	olog "github.com/jroimartin/orujo-handlers/log"
)

type Server struct {
	Addr   string
	CmdDir string

	logger   *log.Logger
	commands map[string]*command
	mutex    sync.RWMutex
}

func NewServer() *Server {
	s := new(Server)
	s.logger = log.New(os.Stdout, "[intelengine] ", log.LstdFlags)
	return s
}

func (s *Server) Start() error {
	if s.Addr == "" || s.CmdDir == "" {
		return errors.New("Server.Addr and Server.CmdDir cannot be empty strings")
	}

	s.initCommands()

	websrv := orujo.NewServer(s.Addr)

	logHandler := olog.NewLogHandler(s.logger, logLine)

	websrv.RouteDefault(http.NotFoundHandler(), orujo.M(logHandler))

	websrv.Route(`^/cmd/refresh$`,
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			s.initCommands()
		}),
		http.HandlerFunc(s.listCommandsHandler),
		orujo.M(logHandler))

	websrv.Route(`^/cmd/list$`,
		http.HandlerFunc(s.listCommandsHandler),
		orujo.M(logHandler))

	websrv.Route(`^/cmd/exec/\w+$`,
		http.HandlerFunc(s.runCommandHandler),
		orujo.M(logHandler))

	if err := websrv.ListenAndServe(); err != nil {
		return err
	}

	return nil
}

func (s *Server) initCommands() {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	s.commands = make(map[string]*command)

	files, err := ioutil.ReadDir(s.CmdDir)
	if err != nil {
		s.logger.Println("initCommands warning:", err)
		return
	}

	for _, f := range files {
		if f.IsDir() || path.Ext(f.Name()) != cmdExt {
			continue
		}

		filename := path.Join(s.CmdDir, f.Name())
		cmd, err := newCommand(filename)
		if err != nil {
			s.logger.Println("initCommands warning:", err)
			return
		}

		s.commands[cmd.Name] = cmd
		s.logger.Println("command registered:", cmd.Name)
	}
}

func (s *Server) command(name string) *command {
	s.mutex.RLock()
	defer s.mutex.RUnlock()

	for _, cmd := range s.commands {
		if cmd.Name == name {
			return cmd
		}
	}
	return nil
}

const logLine = `{{.Req.RemoteAddr}} - {{.Req.Method}} {{.Req.RequestURI}}
{{range  $err := .Errors}}  Err: {{$err}}
{{end}}`
<commit_msg>Rename initCommands() to refreshCommands()<commit_after>// Copyright 2014 The intelengine Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package server

import (
	"errors"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"sync"

	"github.com/jroimartin/orujo"
	olog "github.com/jroimartin/orujo-handlers/log"
)

type Server struct {
	Addr   string
	CmdDir string

	logger   *log.Logger
	commands map[string]*command
	mutex    sync.RWMutex
}

func NewServer() *Server {
	s := new(Server)
	s.logger = log.New(os.Stdout, "[intelengine] ", log.LstdFlags)
	return s
}

func (s *Server) Start() error {
	if s.Addr == "" || s.CmdDir == "" {
		return errors.New("Server.Addr and Server.CmdDir cannot be empty strings")
	}

	s.refreshCommands()

	websrv := orujo.NewServer(s.Addr)

	logHandler := olog.NewLogHandler(s.logger, logLine)

	websrv.RouteDefault(http.NotFoundHandler(), orujo.M(logHandler))

	websrv.Route(`^/cmd/refresh$`,
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			s.refreshCommands()
		}),
		http.HandlerFunc(s.listCommandsHandler),
		orujo.M(logHandler))

	websrv.Route(`^/cmd/list$`,
		http.HandlerFunc(s.listCommandsHandler),
		orujo.M(logHandler))

	websrv.Route(`^/cmd/exec/\w+$`,
		http.HandlerFunc(s.runCommandHandler),
		orujo.M(logHandler))

	if err := websrv.ListenAndServe(); err != nil {
		return err
	}

	return nil
}

func (s *Server) refreshCommands() {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	s.commands = make(map[string]*command)

	files, err := ioutil.ReadDir(s.CmdDir)
	if err != nil {
		s.logger.Println("refreshCommands warning:", err)
		return
	}

	for _, f := range files {
		if f.IsDir() || path.Ext(f.Name()) != cmdExt {
			continue
		}

		filename := path.Join(s.CmdDir, f.Name())
		cmd, err := newCommand(filename)
		if err != nil {
			s.logger.Println("refreshCommands warning:", err)
			return
		}

		s.commands[cmd.Name] = cmd
		s.logger.Println("command registered:", cmd.Name)
	}
}

func (s *Server) command(name string) *command {
	s.mutex.RLock()
	defer s.mutex.RUnlock()

	for _, cmd := range s.commands {
		if cmd.Name == name {
			return cmd
		}
	}
	return nil
}

const logLine = `{{.Req.RemoteAddr}} - {{.Req.Method}} {{.Req.RequestURI}}
{{range  $err := .Errors}}  Err: {{$err}}
{{end}}`
<|endoftext|>


<commit_before>package command

import (
	"fmt"
	"strings"
	"strconv"
	"time"
	"os"
)

func init() {
	cmdFuse.Run = runFuse // break init cycle
}

func runFuse(cmd *Command, args []string) bool {
	argsLen := len(args)
	options := []string{}

	// at least target mount path should be passed
	if argsLen < 1 {
		return false
	}

	// first option is always target mount path
	mountOptions.dir = &args[0]

	// scan parameters looking for one or more -o options
	// -o options receive parameters on format key=value[,key=value]...
	for i := 0; i < argsLen; i++ {
		if args[i] == "-o" && i+1 <= argsLen {
			options = strings.Split(args[i+1], ",")
			i++
		}
	}

	// for each option passed with -o
	for _, option := range options {
		// split just first = character
		parts := strings.SplitN(option, "=", 2)

		// if doesn't key and value skip
		if len(parts) != 2 {
			continue
		}

		// switch key keeping "weed mount" parameters
		switch parts[0] {
			case "filer":
				mountOptions.filer = &parts[1]
			case "filer.path":
				mountOptions.filerMountRootPath = &parts[1]
			case "dirAutoCreate":
				if value, err := strconv.ParseBool(parts[1]); err != nil {
					mountOptions.dirAutoCreate = &value
				} else {
					panic(fmt.Errorf("dirAutoCreate: %s", err))
				}
			case "collection":
				mountOptions.collection = &parts[1]
			case "replication":
				mountOptions.replication = &parts[1]
			case "disk":
				mountOptions.diskType = &parts[1]
			case "ttl":
				if value, err := strconv.ParseInt(parts[1], 0, 32); err != nil {
					intValue := int(value)
					mountOptions.ttlSec = &intValue
				} else {
					panic(fmt.Errorf("ttl: %s", err))
				}
			case "chunkSizeLimitMB":
				if value, err := strconv.ParseInt(parts[1], 0, 32); err != nil {
					intValue := int(value)
					mountOptions.chunkSizeLimitMB = &intValue
				} else {
					panic(fmt.Errorf("chunkSizeLimitMB: %s", err))
				}
			case "concurrentWriters":
				if value, err := strconv.ParseInt(parts[1], 0, 32); err != nil {
					intValue := int(value)
					mountOptions.concurrentWriters = &intValue
				} else {
					panic(fmt.Errorf("concurrentWriters: %s", err))
				}
			case "cacheDir":
				mountOptions.cacheDir = &parts[1]
			case "cacheCapacityMB":
				if value, err := strconv.ParseInt(parts[1], 0, 64); err != nil {
					mountOptions.cacheSizeMB = &value
				} else {
					panic(fmt.Errorf("cacheCapacityMB: %s", err))
				}
			case "dataCenter":
				mountOptions.dataCenter = &parts[1]
			case "allowOthers":
				if value, err := strconv.ParseBool(parts[1]); err != nil {
					mountOptions.allowOthers = &value
				} else {
					panic(fmt.Errorf("allowOthers: %s", err))
				}
			case "umask":
				mountOptions.umaskString = &parts[1]
			case "nonempty":
				if value, err := strconv.ParseBool(parts[1]); err != nil {
					mountOptions.nonempty = &value
				} else {
					panic(fmt.Errorf("nonempty: %s", err))
				}
			case "volumeServerAccess":
				mountOptions.volumeServerAccess = &parts[1]
			case "map.uid":
				mountOptions.uidMap = &parts[1]
			case "map.gid":
				mountOptions.gidMap = &parts[1]
			case "readOnly":
				if value, err := strconv.ParseBool(parts[1]); err != nil {
					mountOptions.readOnly = &value
				} else {
					panic(fmt.Errorf("readOnly: %s", err))
				}
			case "cpuprofile":
				mountCpuProfile = &parts[1]
			case "memprofile":
				mountMemProfile = &parts[1]
			case "readRetryTime":
				if value, err := time.ParseDuration(parts[1]); err != nil {
					mountReadRetryTime = &value
				} else {
					panic(fmt.Errorf("readRetryTime: %s", err))
				}
		}
	}

	// I don't know why PATH environment variable is lost
	if err := os.Setenv("PATH", "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"); err != nil {
		panic(fmt.Errorf("setenv: %s", err))
	}

	// just call "weed mount" command
	return runMount(cmdMount, []string{})
}

var cmdFuse = &Command{
	UsageLine: "fuse /mnt/mount/point -o \"filer=localhost:8888,filer.remote=/\"",
	Short: "Allow use weed with linux's mount command",
	Long: `Allow use weed with linux's mount command

  You can use -t weed on mount command:
  mv weed /sbin/mount.weed
  mount -t weed fuse /mnt -o "filer=localhost:8888,filer.remote=/"

  Or you can use -t fuse on mount command:
  mv weed /sbin/weed
  mount -t fuse.weed fuse /mnt -o "filer=localhost:8888,filer.remote=/"
  mount -t fuse "weed#fuse" /mnt -o "filer=localhost:8888,filer.remote=/"

  To use without mess with your /sbin:
  mount -t fuse./home/user/bin/weed fuse /mnt -o "filer=localhost:8888,filer.remote=/"
  mount -t fuse "/home/user/bin/weed#fuse" /mnt -o "filer=localhost:8888,filer.remote=/"

  To check valid options look "weed mount --help"
  `,
}
<commit_msg>replace filer.remote and parts[1]<commit_after>package command

import (
	"fmt"
	"strings"
	"strconv"
	"time"
	"os"
)

func init() {
	cmdFuse.Run = runFuse // break init cycle
}

func runFuse(cmd *Command, args []string) bool {
	argsLen := len(args)
	options := []string{}

	// at least target mount path should be passed
	if argsLen < 1 {
		return false
	}

	// first option is always target mount path
	mountOptions.dir = &args[0]

	// scan parameters looking for one or more -o options
	// -o options receive parameters on format key=value[,key=value]...
	for i := 0; i < argsLen; i++ {
		if args[i] == "-o" && i+1 <= argsLen {
			options = strings.Split(args[i+1], ",")
			i++
		}
	}

	// for each option passed with -o
	for _, option := range options {
		// split just first = character
		parts := strings.SplitN(option, "=", 2)

		// if doesn't key and value skip
		if len(parts) != 2 {
			continue
		}

		key, value := parts[0], parts[1]

		// switch key keeping "weed mount" parameters
		switch key {
			case "filer":
				mountOptions.filer = &value
			case "filer.path":
				mountOptions.filerMountRootPath = &value
			case "dirAutoCreate":
				if parsed, err := strconv.ParseBool(value); err != nil {
					mountOptions.dirAutoCreate = &parsed
				} else {
					panic(fmt.Errorf("dirAutoCreate: %s", err))
				}
			case "collection":
				mountOptions.collection = &value
			case "replication":
				mountOptions.replication = &value
			case "disk":
				mountOptions.diskType = &value
			case "ttl":
				if parsed, err := strconv.ParseInt(value, 0, 32); err != nil {
					intValue := int(parsed)
					mountOptions.ttlSec = &intValue
				} else {
					panic(fmt.Errorf("ttl: %s", err))
				}
			case "chunkSizeLimitMB":
				if parsed, err := strconv.ParseInt(value, 0, 32); err != nil {
					intValue := int(parsed)
					mountOptions.chunkSizeLimitMB = &intValue
				} else {
					panic(fmt.Errorf("chunkSizeLimitMB: %s", err))
				}
			case "concurrentWriters":
				if parsed, err := strconv.ParseInt(value, 0, 32); err != nil {
					intValue := int(parsed)
					mountOptions.concurrentWriters = &intValue
				} else {
					panic(fmt.Errorf("concurrentWriters: %s", err))
				}
			case "cacheDir":
				mountOptions.cacheDir = &value
			case "cacheCapacityMB":
				if parsed, err := strconv.ParseInt(value, 0, 64); err != nil {
					mountOptions.cacheSizeMB = &parsed
				} else {
					panic(fmt.Errorf("cacheCapacityMB: %s", err))
				}
			case "dataCenter":
				mountOptions.dataCenter = &value
			case "allowOthers":
				if parsed, err := strconv.ParseBool(value); err != nil {
					mountOptions.allowOthers = &parsed
				} else {
					panic(fmt.Errorf("allowOthers: %s", err))
				}
			case "umask":
				mountOptions.umaskString = &value
			case "nonempty":
				if parsed, err := strconv.ParseBool(value); err != nil {
					mountOptions.nonempty = &parsed
				} else {
					panic(fmt.Errorf("nonempty: %s", err))
				}
			case "volumeServerAccess":
				mountOptions.volumeServerAccess = &value
			case "map.uid":
				mountOptions.uidMap = &value
			case "map.gid":
				mountOptions.gidMap = &value
			case "readOnly":
				if parsed, err := strconv.ParseBool(value); err != nil {
					mountOptions.readOnly = &parsed
				} else {
					panic(fmt.Errorf("readOnly: %s", err))
				}
			case "cpuprofile":
				mountCpuProfile = &value
			case "memprofile":
				mountMemProfile = &value
			case "readRetryTime":
				if parsed, err := time.ParseDuration(value); err != nil {
					mountReadRetryTime = &parsed
				} else {
					panic(fmt.Errorf("readRetryTime: %s", err))
				}
		}
	}

	// I don't know why PATH environment variable is lost
	if err := os.Setenv("PATH", "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"); err != nil {
		panic(fmt.Errorf("setenv: %s", err))
	}

	// just call "weed mount" command
	return runMount(cmdMount, []string{})
}

var cmdFuse = &Command{
	UsageLine: "fuse /mnt/mount/point -o \"filer=localhost:8888,filer.path=/\"",
	Short: "Allow use weed with linux's mount command",
	Long: `Allow use weed with linux's mount command

  You can use -t weed on mount command:
  mv weed /sbin/mount.weed
  mount -t weed fuse /mnt -o "filer=localhost:8888,filer.path=/"

  Or you can use -t fuse on mount command:
  mv weed /sbin/weed
  mount -t fuse.weed fuse /mnt -o "filer=localhost:8888,filer.path=/"
  mount -t fuse "weed#fuse" /mnt -o "filer=localhost:8888,filer.path=/"

  To use without mess with your /sbin:
  mount -t fuse./home/user/bin/weed fuse /mnt -o "filer=localhost:8888,filer.path=/"
  mount -t fuse "/home/user/bin/weed#fuse" /mnt -o "filer=localhost:8888,filer.path=/"

  To check valid options look "weed mount --help"
  `,
}
<|endoftext|>


<commit_before>package server

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

	Name() string
	Description() string

	RegisterEndpoint(endpoint *Endpoint)
	DeregisterEndpoint(pattern string)
}

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

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

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

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

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

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

// Config defines the config a server needs to start up, and serve requests
type Config struct {
	Name        string
	Description string
}
<commit_msg>Change Init to return the server so it can be chained<commit_after>package server

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

	Name() string
	Description() string

	RegisterEndpoint(endpoint *Endpoint)
	DeregisterEndpoint(pattern string)

	RegisterAuthenticationProvider(auth.AuthenticationProvider)
}

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

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

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

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

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

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

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


<commit_before>package server

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/jpillora/cloud-torrent/engine"
	"github.com/jpillora/cloud-torrent/static"
	"github.com/jpillora/cookieauth"
	"github.com/jpillora/gziphandler"
	"github.com/jpillora/requestlog"
	"github.com/jpillora/scraper/scraper"
	"github.com/jpillora/velox"
	"github.com/skratchdot/open-golang/open"
)

//Server is the "State" portion of the diagram
type Server struct {
	//config
	Title      string `help:"Title of this instance" env:"TITLE"`
	Port       int    `help:"Listening port" env:"PORT"`
	Host       string `help:"Listening interface (default all)"`
	Auth       string `help:"Optional basic auth in form 'user:password'" env:"AUTH"`
	ConfigPath string `help:"Configuration file path"`
	KeyPath    string `help:"TLS Key file path"`
	CertPath   string `help:"TLS Certicate file path" short:"r"`
	Log        bool   `help:"Enable request logging"`
	Open       bool   `help:"Open now with your default browser"`
	//http handlers
	files, static http.Handler
	scraper       *scraper.Handler
	scraperh      http.Handler
	//torrent engine
	engine *engine.Engine
	state  struct {
		velox.State
		sync.Mutex
		Config          engine.Config
		SearchProviders scraper.Config
		Downloads       *fsNode
		Torrents        map[string]*engine.Torrent
		Users           map[string]string
		Stats           struct {
			Title   string
			Version string
			Runtime string
			Uptime  time.Time
		}
	}
}

func (s *Server) Run(version string) error {

	tls := s.CertPath != "" || s.KeyPath != "" //poor man's XOR
	if tls && (s.CertPath == "" || s.KeyPath == "") {
		return fmt.Errorf("You must provide both key and cert paths")
	}

	s.state.Stats.Title = s.Title
	s.state.Stats.Version = version
	s.state.Stats.Runtime = strings.TrimPrefix(runtime.Version(), "go")
	s.state.Stats.Uptime = time.Now()

	//init maps
	s.state.Users = map[string]string{}
	//will use a the local embed/ dir if it exists, otherwise will use the hardcoded embedded binaries
	s.files = http.HandlerFunc(s.serveFiles)
	s.static = ctstatic.FileSystemHandler()
	s.scraper = &scraper.Handler{
		Log: false, Debug: false,
		Headers: map[string]string{
			//we're a trusty browser :)
			"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
		},
	}
	if err := s.scraper.LoadConfig(defaultSearchConfig); err != nil {
		log.Fatal(err)
	}

	s.state.SearchProviders = s.scraper.Config //share scraper config
	go s.fetchSearchConfigLoop()
	s.scraperh = http.StripPrefix("/search", s.scraper)

	s.engine = engine.New()

	//configure engine
	c := engine.Config{
		DownloadDirectory: "./downloads",
		EnableUpload:      true,
		AutoStart:         true,
	}
	if _, err := os.Stat(s.ConfigPath); err == nil {
		if b, err := ioutil.ReadFile(s.ConfigPath); err != nil {
			return fmt.Errorf("Read configuration error: %s", err)
		} else if len(b) == 0 {
			//ignore empty file
		} else if err := json.Unmarshal(b, &c); err != nil {
			return fmt.Errorf("Malformed configuration: %s", err)
		}
	}
	if c.IncomingPort <= 0 || c.IncomingPort >= 65535 {
		c.IncomingPort = 50007
	}
	if err := s.reconfigure(c); err != nil {
		return fmt.Errorf("initial configure failed: %s", err)
	}

	//poll torrents and files
	go func() {
		for {
			s.state.Lock()
			s.state.Torrents = s.engine.GetTorrents()
			s.state.Downloads = s.listFiles()
			s.state.Unlock()
			s.state.Push()
			time.Sleep(1 * time.Second)
		}
	}()

	host := s.Host
	if host == "" {
		host = "0.0.0.0"
	}
	addr := fmt.Sprintf("%s:%d", host, s.Port)
	proto := "http"
	if tls {
		proto += "s"
	}
	if s.Open {
		openhost := host
		if openhost == "0.0.0.0" {
			openhost = "localhost"
		}
		go func() {
			time.Sleep(1 * time.Second)
			open.Run(fmt.Sprintf("%s://%s:%d", proto, openhost, s.Port))
		}()
	}
	//define handler chain, from last to first
	h := http.Handler(http.HandlerFunc(s.handle))
	h = gziphandler.GzipHandler(h)
	if s.Auth != "" {
		user := s.Auth
		pass := ""
		if s := strings.SplitN(s.Auth, ":", 2); len(s) == 2 {
			user = s[0]
			pass = s[1]
		}
		h = cookieauth.Wrap(h, user, pass)
		log.Printf("Enabled HTTP authentication")
	}
	if s.Log {
		h = requestlog.Wrap(h)
	}
	log.Printf("Listening at %s://%s", proto, addr)
	//serve!
	if tls {
		return http.ListenAndServeTLS(addr, s.CertPath, s.KeyPath, h)
	}
	return http.ListenAndServe(addr, h)
}

func (s *Server) reconfigure(c engine.Config) error {
	dldir, err := filepath.Abs(c.DownloadDirectory)
	if err != nil {
		return fmt.Errorf("Invalid path")
	}
	c.DownloadDirectory = dldir
	if err := s.engine.Configure(c); err != nil {
		return err
	}
	b, _ := json.MarshalIndent(&c, "", "  ")
	ioutil.WriteFile(s.ConfigPath, b, 0755)
	s.state.Config = c
	s.state.Push()
	return nil
}

func (s *Server) handle(w http.ResponseWriter, r *http.Request) {
	//handle realtime client library
	if r.URL.Path == "/js/velox.js" {
		velox.JS.ServeHTTP(w, r)
		return
	}
	//handle realtime client connections
	if r.URL.Path == "/sync" {
		conn, err := velox.Sync(&s.state, w, r)
		if err != nil {
			log.Printf("sync failed: %s", err)
			return
		}
		s.state.Users[conn.ID()] = r.RemoteAddr
		s.state.Push()
		conn.Wait()
		delete(s.state.Users, conn.ID())
		s.state.Push()
		return
	}
	//search
	if strings.HasPrefix(r.URL.Path, "/search") {
		s.scraperh.ServeHTTP(w, r)
		return
	}
	//api call
	if strings.HasPrefix(r.URL.Path, "/api/") {
		//only pass request in, expect error out
		if err := s.api(r); err == nil {
			w.WriteHeader(http.StatusOK)
			w.Write([]byte("OK"))
		} else {
			w.WriteHeader(http.StatusBadRequest)
			w.Write([]byte(err.Error()))
		}
		return
	}
	//no match, assume static file
	s.files.ServeHTTP(w, r)
}
<commit_msg>resolve sync error when using tls, closes #153 (disabled http2 support until velox bug is resolved)<commit_after>package server

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/jpillora/cloud-torrent/engine"
	"github.com/jpillora/cloud-torrent/static"
	"github.com/jpillora/cookieauth"
	"github.com/jpillora/gziphandler"
	"github.com/jpillora/requestlog"
	"github.com/jpillora/scraper/scraper"
	"github.com/jpillora/velox"
	"github.com/skratchdot/open-golang/open"
)

//Server is the "State" portion of the diagram
type Server struct {
	//config
	Title      string `help:"Title of this instance" env:"TITLE"`
	Port       int    `help:"Listening port" env:"PORT"`
	Host       string `help:"Listening interface (default all)"`
	Auth       string `help:"Optional basic auth in form 'user:password'" env:"AUTH"`
	ConfigPath string `help:"Configuration file path"`
	KeyPath    string `help:"TLS Key file path"`
	CertPath   string `help:"TLS Certicate file path" short:"r"`
	Log        bool   `help:"Enable request logging"`
	Open       bool   `help:"Open now with your default browser"`
	//http handlers
	files, static http.Handler
	scraper       *scraper.Handler
	scraperh      http.Handler
	//torrent engine
	engine *engine.Engine
	state  struct {
		velox.State
		sync.Mutex
		Config          engine.Config
		SearchProviders scraper.Config
		Downloads       *fsNode
		Torrents        map[string]*engine.Torrent
		Users           map[string]string
		Stats           struct {
			Title   string
			Version string
			Runtime string
			Uptime  time.Time
		}
	}
}

func (s *Server) Run(version string) error {

	isTLS := s.CertPath != "" || s.KeyPath != "" //poor man's XOR
	if isTLS && (s.CertPath == "" || s.KeyPath == "") {
		return fmt.Errorf("You must provide both key and cert paths")
	}

	s.state.Stats.Title = s.Title
	s.state.Stats.Version = version
	s.state.Stats.Runtime = strings.TrimPrefix(runtime.Version(), "go")
	s.state.Stats.Uptime = time.Now()

	//init maps
	s.state.Users = map[string]string{}
	//will use a the local embed/ dir if it exists, otherwise will use the hardcoded embedded binaries
	s.files = http.HandlerFunc(s.serveFiles)
	s.static = ctstatic.FileSystemHandler()
	s.scraper = &scraper.Handler{
		Log: false, Debug: false,
		Headers: map[string]string{
			//we're a trusty browser :)
			"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
		},
	}
	if err := s.scraper.LoadConfig(defaultSearchConfig); err != nil {
		log.Fatal(err)
	}

	s.state.SearchProviders = s.scraper.Config //share scraper config
	go s.fetchSearchConfigLoop()
	s.scraperh = http.StripPrefix("/search", s.scraper)

	s.engine = engine.New()

	//configure engine
	c := engine.Config{
		DownloadDirectory: "./downloads",
		EnableUpload:      true,
		AutoStart:         true,
	}
	if _, err := os.Stat(s.ConfigPath); err == nil {
		if b, err := ioutil.ReadFile(s.ConfigPath); err != nil {
			return fmt.Errorf("Read configuration error: %s", err)
		} else if len(b) == 0 {
			//ignore empty file
		} else if err := json.Unmarshal(b, &c); err != nil {
			return fmt.Errorf("Malformed configuration: %s", err)
		}
	}
	if c.IncomingPort <= 0 || c.IncomingPort >= 65535 {
		c.IncomingPort = 50007
	}
	if err := s.reconfigure(c); err != nil {
		return fmt.Errorf("initial configure failed: %s", err)
	}

	//poll torrents and files
	go func() {
		for {
			s.state.Lock()
			s.state.Torrents = s.engine.GetTorrents()
			s.state.Downloads = s.listFiles()
			s.state.Unlock()
			s.state.Push()
			time.Sleep(1 * time.Second)
		}
	}()

	host := s.Host
	if host == "" {
		host = "0.0.0.0"
	}
	addr := fmt.Sprintf("%s:%d", host, s.Port)
	proto := "http"
	if isTLS {
		proto += "s"
	}
	if s.Open {
		openhost := host
		if openhost == "0.0.0.0" {
			openhost = "localhost"
		}
		go func() {
			time.Sleep(1 * time.Second)
			open.Run(fmt.Sprintf("%s://%s:%d", proto, openhost, s.Port))
		}()
	}
	//define handler chain, from last to first
	h := http.Handler(http.HandlerFunc(s.handle))
	h = gziphandler.GzipHandler(h)
	if s.Auth != "" {
		user := s.Auth
		pass := ""
		if s := strings.SplitN(s.Auth, ":", 2); len(s) == 2 {
			user = s[0]
			pass = s[1]
		}
		h = cookieauth.Wrap(h, user, pass)
		log.Printf("Enabled HTTP authentication")
	}
	if s.Log {
		h = requestlog.Wrap(h)
	}
	log.Printf("Listening at %s://%s", proto, addr)
	//serve!
	server := http.Server{
		//disable http2 due to velox bug
		TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){},
		//address
		Addr: addr,
		//handler stack
		Handler: h,
	}
	if isTLS {
		return server.ListenAndServeTLS(s.CertPath, s.KeyPath)
	}
	return server.ListenAndServe()
}

func (s *Server) reconfigure(c engine.Config) error {
	dldir, err := filepath.Abs(c.DownloadDirectory)
	if err != nil {
		return fmt.Errorf("Invalid path")
	}
	c.DownloadDirectory = dldir
	if err := s.engine.Configure(c); err != nil {
		return err
	}
	b, _ := json.MarshalIndent(&c, "", "  ")
	ioutil.WriteFile(s.ConfigPath, b, 0755)
	s.state.Config = c
	s.state.Push()
	return nil
}

func (s *Server) handle(w http.ResponseWriter, r *http.Request) {
	//handle realtime client library
	if r.URL.Path == "/js/velox.js" {
		velox.JS.ServeHTTP(w, r)
		return
	}
	//handle realtime client connections
	if r.URL.Path == "/sync" {
		conn, err := velox.Sync(&s.state, w, r)
		if err != nil {
			log.Printf("sync failed: %s", err)
			return
		}
		s.state.Users[conn.ID()] = r.RemoteAddr
		s.state.Push()
		conn.Wait()
		delete(s.state.Users, conn.ID())
		s.state.Push()
		return
	}
	//search
	if strings.HasPrefix(r.URL.Path, "/search") {
		s.scraperh.ServeHTTP(w, r)
		return
	}
	//api call
	if strings.HasPrefix(r.URL.Path, "/api/") {
		//only pass request in, expect error out
		if err := s.api(r); err == nil {
			w.WriteHeader(http.StatusOK)
			w.Write([]byte("OK"))
		} else {
			w.WriteHeader(http.StatusBadRequest)
			w.Write([]byte(err.Error()))
		}
		return
	}
	//no match, assume static file
	s.files.ServeHTTP(w, r)
}
<|endoftext|>


<commit_before>package server

import (
	"github.com/Sirupsen/logrus"
	"github.com/danjac/random_movies/database"
	"github.com/danjac/random_movies/decoders"
	"github.com/danjac/random_movies/errors"
	"github.com/danjac/random_movies/omdb"
	"github.com/gorilla/mux"
	"github.com/gorilla/websocket"
	"github.com/justinas/nosurf"
	"github.com/unrolled/render"
	"net/http"
	"time"
)

const SOCKET_WAIT_FOR = 15 * time.Second

func New(db database.DB, log *logrus.Logger, config *Config) *Server {
	return &Server{
		DB:       db,
		OMDB:     omdb.New(),
		Render:   render.New(),
		Log:      log,
		Config:   config,
		Upgrader: websocket.Upgrader{},
	}
}

// context globals (not threadsafe, so only store thread-safe objects here)
type Server struct {
	Config   *Config
	OMDB     omdb.Finder
	Render   *render.Render
	DB       database.DB
	Log      *logrus.Logger
	Upgrader websocket.Upgrader
}

type Config struct {
	Env          string
	StaticURL    string
	StaticDir    string
	DevServerURL string
}

func (s *Server) Abort(w http.ResponseWriter, r *http.Request, err error) {
	logger := s.Log.WithFields(logrus.Fields{
		"URL":    r.URL,
		"Method": r.Method,
		"Error":  err,
	})
	var msg string
	switch e := err.(error).(type) {
	case errors.Error:
		msg = "HTTP Error"
		http.Error(w, e.Error(), e.Status())
	default:
		msg = "Internal Server Error"
		http.Error(w, "Sorry, an error occurred", http.StatusInternalServerError)
	}
	logger.Error(msg)
}

func (s *Server) Router() *mux.Router {
	router := mux.NewRouter()

	// static content
	router.PathPrefix(
		s.Config.StaticURL).Handler(http.StripPrefix(
		s.Config.StaticURL, http.FileServer(http.Dir(s.Config.StaticDir))))

	// index page
	router.HandleFunc("/", s.indexPage).Methods("GET")

	// API calls
	api := router.PathPrefix("/api/").Subrouter()

	api.HandleFunc("/", s.getRandomMovie).Methods("GET")
	api.HandleFunc("/", s.addMovie).Methods("POST")
	api.HandleFunc("/suggest", s.suggest)
	api.HandleFunc("/movie/{id}", s.getMovie).Methods("GET")
	api.HandleFunc("/movie/{id}", s.deleteMovie).Methods("DELETE")
	api.HandleFunc("/seen/{id}", s.markSeen).Methods("PATCH")
	api.HandleFunc("/all/", s.getMovies).Methods("GET")

	return router
}

func (s *Server) indexPage(w http.ResponseWriter, r *http.Request) {

	var staticHost string

	if s.Config.Env == "dev" {
		staticHost = s.Config.DevServerURL
		s.Log.Info("Running development version")
	}

	csrfToken := nosurf.Token(r)

	ctx := map[string]string{
		"staticHost": staticHost,
		"env":        s.Config.Env,
		"csrfToken":  csrfToken,
	}
	s.Render.HTML(w, http.StatusOK, "index", ctx)
}

func (s *Server) markSeen(w http.ResponseWriter, r *http.Request) {
	if err := s.DB.MarkSeen(mux.Vars(r)["id"]); err != nil {
		s.Abort(w, r, err)
		return
	}
	s.Render.Text(w, http.StatusOK, "Movie seen")
}

func (s *Server) getRandomMovie(w http.ResponseWriter, r *http.Request) {

	movie, err := s.DB.GetRandom()

	if err != nil {
		s.Abort(w, r, err)
		return
	}

	s.Render.JSON(w, http.StatusOK, movie)
}

func (s *Server) suggest(w http.ResponseWriter, r *http.Request) {

	c, err := s.Upgrader.Upgrade(w, r, nil)
	if err != nil {
		s.Abort(w, r, err)
		return
	}
	defer c.Close()

	s.Log.Info("Socket started")

	for {

		movie, err := s.DB.GetRandom()

		if err != nil {
			s.Log.Error(err.Error())
			continue
		}

		if err := c.WriteJSON(movie); err != nil {
			s.Log.Error(err.Error())
			break
		}

		time.Sleep(SOCKET_WAIT_FOR)
	}

}

func (s *Server) getMovie(w http.ResponseWriter, r *http.Request) {

	movie, err := s.DB.Get(mux.Vars(r)["id"])
	if err != nil {
		s.Abort(w, r, err)
		return
	}
	s.Render.JSON(w, http.StatusOK, movie)
}

func (s *Server) deleteMovie(w http.ResponseWriter, r *http.Request) {
	imdbID := mux.Vars(r)["id"]
	if err := s.DB.Delete(imdbID); err != nil {
		s.Abort(w, r, err)
		return
	}
	s.Log.WithFields(logrus.Fields{
		"imdbID": imdbID,
	}).Warn("Movie has been deleted")
	s.Render.Text(w, http.StatusOK, "Movie deleted")
}

func (s *Server) getMovies(w http.ResponseWriter, r *http.Request) {

	movies, err := s.DB.GetAll()
	if err != nil {
		s.Abort(w, r, err)
		return
	}
	s.Render.JSON(w, http.StatusOK, movies)
}

func (s *Server) addMovie(w http.ResponseWriter, r *http.Request) {
	f := &decoders.MovieDecoder{}
	if err := f.Decode(r); err != nil {
		s.Abort(w, r, errors.HTTPError{http.StatusBadRequest, err})
		return
	}

	movie, err := s.OMDB.Find(f.Title)
	if err != nil {
		s.Abort(w, r, err)
		return
	}

	oldMovie, err := s.DB.Get(movie.ImdbID)

	if err == errors.ErrMovieNotFound {
		s.Log.WithFields(logrus.Fields{
			"movie": movie,
		}).Info("Movie already in database")
		s.Render.JSON(w, http.StatusOK, oldMovie)
	}

	if err != nil {
		s.Abort(w, r, err)
		return
	}

	if err := s.DB.Save(movie); err != nil {
		s.Abort(w, r, err)
		return
	}

	s.Log.WithFields(logrus.Fields{
		"movie": movie,
	}).Info("New movie added")
	s.Render.JSON(w, http.StatusCreated, movie)
}
<commit_msg>Handle if movie already exists<commit_after>package server

import (
	"github.com/Sirupsen/logrus"
	"github.com/danjac/random_movies/database"
	"github.com/danjac/random_movies/decoders"
	"github.com/danjac/random_movies/errors"
	"github.com/danjac/random_movies/omdb"
	"github.com/gorilla/mux"
	"github.com/gorilla/websocket"
	"github.com/justinas/nosurf"
	"github.com/unrolled/render"
	"net/http"
	"time"
)

const SOCKET_WAIT_FOR = 15 * time.Second

func New(db database.DB, log *logrus.Logger, config *Config) *Server {
	return &Server{
		DB:       db,
		OMDB:     omdb.New(),
		Render:   render.New(),
		Log:      log,
		Config:   config,
		Upgrader: websocket.Upgrader{},
	}
}

// context globals (not threadsafe, so only store thread-safe objects here)
type Server struct {
	Config   *Config
	OMDB     omdb.Finder
	Render   *render.Render
	DB       database.DB
	Log      *logrus.Logger
	Upgrader websocket.Upgrader
}

type Config struct {
	Env          string
	StaticURL    string
	StaticDir    string
	DevServerURL string
}

func (s *Server) Abort(w http.ResponseWriter, r *http.Request, err error) {
	logger := s.Log.WithFields(logrus.Fields{
		"URL":    r.URL,
		"Method": r.Method,
		"Error":  err,
	})
	var msg string
	switch e := err.(error).(type) {
	case errors.Error:
		msg = "HTTP Error"
		http.Error(w, e.Error(), e.Status())
	default:
		msg = "Internal Server Error"
		http.Error(w, "Sorry, an error occurred", http.StatusInternalServerError)
	}
	logger.Error(msg)
}

func (s *Server) Router() *mux.Router {
	router := mux.NewRouter()

	// static content
	router.PathPrefix(
		s.Config.StaticURL).Handler(http.StripPrefix(
		s.Config.StaticURL, http.FileServer(http.Dir(s.Config.StaticDir))))

	// index page
	router.HandleFunc("/", s.indexPage).Methods("GET")

	// API calls
	api := router.PathPrefix("/api/").Subrouter()

	api.HandleFunc("/", s.getRandomMovie).Methods("GET")
	api.HandleFunc("/", s.addMovie).Methods("POST")
	api.HandleFunc("/suggest", s.suggest)
	api.HandleFunc("/movie/{id}", s.getMovie).Methods("GET")
	api.HandleFunc("/movie/{id}", s.deleteMovie).Methods("DELETE")
	api.HandleFunc("/seen/{id}", s.markSeen).Methods("PATCH")
	api.HandleFunc("/all/", s.getMovies).Methods("GET")

	return router
}

func (s *Server) indexPage(w http.ResponseWriter, r *http.Request) {

	var staticHost string

	if s.Config.Env == "dev" {
		staticHost = s.Config.DevServerURL
		s.Log.Info("Running development version")
	}

	csrfToken := nosurf.Token(r)

	ctx := map[string]string{
		"staticHost": staticHost,
		"env":        s.Config.Env,
		"csrfToken":  csrfToken,
	}
	s.Render.HTML(w, http.StatusOK, "index", ctx)
}

func (s *Server) markSeen(w http.ResponseWriter, r *http.Request) {
	if err := s.DB.MarkSeen(mux.Vars(r)["id"]); err != nil {
		s.Abort(w, r, err)
		return
	}
	s.Render.Text(w, http.StatusOK, "Movie seen")
}

func (s *Server) getRandomMovie(w http.ResponseWriter, r *http.Request) {

	movie, err := s.DB.GetRandom()

	if err != nil {
		s.Abort(w, r, err)
		return
	}

	s.Render.JSON(w, http.StatusOK, movie)
}

func (s *Server) suggest(w http.ResponseWriter, r *http.Request) {

	c, err := s.Upgrader.Upgrade(w, r, nil)
	if err != nil {
		s.Abort(w, r, err)
		return
	}
	defer c.Close()

	s.Log.Info("Socket started")

	for {

		movie, err := s.DB.GetRandom()

		if err != nil {
			s.Log.Error(err.Error())
			continue
		}

		if err := c.WriteJSON(movie); err != nil {
			s.Log.Error(err.Error())
			break
		}

		time.Sleep(SOCKET_WAIT_FOR)
	}

}

func (s *Server) getMovie(w http.ResponseWriter, r *http.Request) {

	movie, err := s.DB.Get(mux.Vars(r)["id"])
	if err != nil {
		s.Abort(w, r, err)
		return
	}
	s.Render.JSON(w, http.StatusOK, movie)
}

func (s *Server) deleteMovie(w http.ResponseWriter, r *http.Request) {
	imdbID := mux.Vars(r)["id"]
	if err := s.DB.Delete(imdbID); err != nil {
		s.Abort(w, r, err)
		return
	}
	s.Log.WithFields(logrus.Fields{
		"imdbID": imdbID,
	}).Warn("Movie has been deleted")
	s.Render.Text(w, http.StatusOK, "Movie deleted")
}

func (s *Server) getMovies(w http.ResponseWriter, r *http.Request) {

	movies, err := s.DB.GetAll()
	if err != nil {
		s.Abort(w, r, err)
		return
	}
	s.Render.JSON(w, http.StatusOK, movies)
}

func (s *Server) addMovie(w http.ResponseWriter, r *http.Request) {
	f := &decoders.MovieDecoder{}
	if err := f.Decode(r); err != nil {
		s.Abort(w, r, errors.HTTPError{http.StatusBadRequest, err})
		return
	}

	movie, err := s.OMDB.Find(f.Title)

	if err != nil {
		s.Abort(w, r, err)
		return
	}

	oldMovie, err := s.DB.Get(movie.ImdbID)

	if err == errors.ErrMovieNotFound {

		if err := s.DB.Save(movie); err != nil {
			s.Abort(w, r, err)
			return
		}

		s.Log.WithFields(logrus.Fields{
			"movie": movie,
		}).Info("New movie added")

		s.Render.JSON(w, http.StatusCreated, movie)
		return
	}

	if err != nil {
		s.Abort(w, r, err)
		return
	}

	s.Log.WithFields(logrus.Fields{
		"movie": oldMovie,
	}).Info("Movie already in database")
	s.Render.JSON(w, http.StatusOK, oldMovie)

}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/emicklei/go-restful"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"
)

var gDB *DB

func serveSingle(pattern string, filename string) {
	http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
		log.Println("serving file " + filename)
		http.ServeFile(w, r, filename)
	})
}

func ensureIsLoggedIn(request *restful.Request, response *restful.Response, chain *restful.FilterChain) {
	if !IsLoggedIn(request.Request) {
		response.WriteError(http.StatusUnauthorized, nil)
		return
	}

	chain.ProcessFilter(request, response)
}

func getDeviceForRequest(request *restful.Request, response *restful.Response) *Device {
	id, err := strconv.ParseInt(request.PathParameter("device-id"), 10, 64)
	if err != nil {
		response.WriteError(http.StatusBadRequest, nil)
		return nil
	}

	device, err := gDB.GetDeviceById(id)
	if device != nil && device.User == GetLoginName(request.Request) {
		return device
	}

	response.WriteError(http.StatusNotFound, nil)
	return nil
}

func addDevice(request *restful.Request, response *restful.Response) {
	indevice := new(Device)
	request.ReadEntity(indevice)

	name := indevice.Name
	endpoint := indevice.Endpoint

	if name == "" || endpoint == "" {
		response.WriteError(http.StatusBadRequest, nil)
		return
	}

	device, err := gDB.AddDevice(GetLoginName(request.Request), name, endpoint)
	if err == nil {
		response.WriteEntity(*device)
	} else {
		response.WriteError(http.StatusInternalServerError, nil)
	}
}

func serveDevicesByUser(request *restful.Request, response *restful.Response) {
	devices, _ := gDB.ListDevicesForUser(GetLoginName(request.Request))

	urls := []string{}
	for _, d := range devices {
		urls = append(urls, fmt.Sprintf("/device/%d", d.Id))
	}

	response.WriteEntity(urls)
}

func serveDevice(request *restful.Request, response *restful.Response) {
	if device := getDeviceForRequest(request, response); device != nil {
		response.WriteEntity(*device)
	}
}

func updateDeviceLocation(request *restful.Request, response *restful.Response) {
	device := getDeviceForRequest(request, response)
	if device == nil {
		return
	}

	latitude, err := strconv.ParseFloat(request.QueryParameter("latitude"), 64)
	if err != nil {
		response.WriteError(http.StatusBadRequest, nil)
	}

	longitude, err := strconv.ParseFloat(request.QueryParameter("longitude"), 64)
	if err != nil {
		response.WriteError(http.StatusBadRequest, nil)
	}

	err = gDB.UpdateDeviceLocation(device, latitude, longitude)
	if err != nil {
		response.WriteError(http.StatusInternalServerError, nil)
	}
}

func reportDeviceLost(request *restful.Request, response *restful.Response) {
	device := getDeviceForRequest(request, response)
	if device == nil {
		return
	}

	// Issue push notification to device
	body := fmt.Sprintf("version=%d", uint64(time.Now().Unix()))
	pushRequest, err := http.NewRequest("PUT", device.Endpoint, strings.NewReader(body))
	if err != nil {
		response.WriteError(http.StatusInternalServerError, nil)
	}

	pushRequest.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}

	var client http.Client
	_, err = client.Do(pushRequest)
	if err != nil {
		response.WriteError(http.StatusInternalServerError, nil)
	}
}

func createDeviceWebService() *restful.WebService {
	ws := new(restful.WebService)

	ws.
		Filter(ensureIsLoggedIn).
		Path("/device").
		Consumes(restful.MIME_JSON).
		Produces(restful.MIME_JSON)

	ws.
		Route(ws.GET("/").To(serveDevicesByUser).
		Doc("Retrieve all devices owned by a user").
		Writes([]Device{}))

	ws.
		Route(ws.GET("/{device-id}").To(serveDevice).
		Doc("Retrieve a device based on its id").
		Param(ws.PathParameter("device-id", "The identifier for the device")).
		Writes(Device{}))

	ws.
		Route(ws.PUT("/").To(addDevice).
		Consumes("application/json; charset=UTF-8").
		Doc("Add a device").
		Param(ws.QueryParameter("name", "The name for the device")).
		Param(ws.QueryParameter("endpoint", "The push endpoint for the device")).
		Writes(Device{}))

	ws.
		Route(ws.POST("/location/{device-id}").To(updateDeviceLocation).
		Consumes("application/x-www-form-urlencoded; charset=UTF-8").
		Doc("Update a device's latitude and longitude").
		Param(ws.QueryParameter("latitude", "The latitude where the device was observed")).
		Param(ws.QueryParameter("longitude", "The longitude where the device was observed")).
		Writes(Device{}))

	ws.
		Route(ws.POST("/lost/{device-id}").To(reportDeviceLost).
		Doc("Report a device as lost").
		Writes(Device{}))

	return ws
}

func main() {
	readConfig()
	db, err := OpenDB("db.sqlite")
	if err != nil {
		panic(err)
	}

	gDB = db
	restful.Add(createDeviceWebService())

	// Persona handling
	http.HandleFunc("/auth/check", loginCheckHandler)
	http.HandleFunc("/auth/login", loginHandler)
	http.HandleFunc("/auth/applogin", appLoginHandler)
	http.HandleFunc("/auth/logout", logoutHandler)

	serveSingle("/", "./static/index.html")
	serveSingle("/index.html", "./static/index.html")
	serveSingle("/install.html", "./static/install.html")
	serveSingle("/app.html", "./app/index.html")
	serveSingle("/style.css", "./static/style.css")
	serveSingle("/style-app.css", "./static/style-app.css")
	serveSingle("/style-common.css", "./static/style-common.css")
	serveSingle("/logos/64.png", "./static/logos/64.png")
	serveSingle("/logos/128.png", "./static/logos/128.png")
	serveSingle("/img/persona-login.png", "./static/img/persona-login.png")
	serveSingle("/lib/mustache.js", "./static/lib/mustache.js")

	http.HandleFunc("/manifest.webapp", func(w http.ResponseWriter, r *http.Request) {
		filename := "./app/manifest.webapp"
		log.Println("serving manifest from " + filename)

		w.Header()["Content-Type"] = []string{"application/x-web-app-manifest+json"}
		http.ServeFile(w, r, filename)
	})

	log.Println("Listening on", gServerConfig.Hostname+":"+gServerConfig.Port)

	if gServerConfig.UseTLS {
		err = http.ListenAndServeTLS(gServerConfig.Hostname+":"+gServerConfig.Port,
			gServerConfig.CertFilename,
			gServerConfig.KeyFilename,
			nil)
	} else {
		log.Println("This is a really unsafe way to run the server.  Really.  Don't do this in production.")
		err = http.ListenAndServe(gServerConfig.Hostname+":"+gServerConfig.Port, nil)
	}

	log.Println("Exiting... ", err)
	gDB.Close()
}
<commit_msg>Remove some misplaced calls to Writes() when building API<commit_after>package main

import (
	"fmt"
	"github.com/emicklei/go-restful"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"
)

var gDB *DB

func serveSingle(pattern string, filename string) {
	http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
		log.Println("serving file " + filename)
		http.ServeFile(w, r, filename)
	})
}

func ensureIsLoggedIn(request *restful.Request, response *restful.Response, chain *restful.FilterChain) {
	if !IsLoggedIn(request.Request) {
		response.WriteError(http.StatusUnauthorized, nil)
		return
	}

	chain.ProcessFilter(request, response)
}

func getDeviceForRequest(request *restful.Request, response *restful.Response) *Device {
	id, err := strconv.ParseInt(request.PathParameter("device-id"), 10, 64)
	if err != nil {
		response.WriteError(http.StatusBadRequest, nil)
		return nil
	}

	device, err := gDB.GetDeviceById(id)
	if device != nil && device.User == GetLoginName(request.Request) {
		return device
	}

	response.WriteError(http.StatusNotFound, nil)
	return nil
}

func addDevice(request *restful.Request, response *restful.Response) {
	indevice := new(Device)
	request.ReadEntity(indevice)

	name := indevice.Name
	endpoint := indevice.Endpoint

	if name == "" || endpoint == "" {
		response.WriteError(http.StatusBadRequest, nil)
		return
	}

	device, err := gDB.AddDevice(GetLoginName(request.Request), name, endpoint)
	if err == nil {
		response.WriteEntity(*device)
	} else {
		response.WriteError(http.StatusInternalServerError, nil)
	}
}

func serveDevicesByUser(request *restful.Request, response *restful.Response) {
	devices, _ := gDB.ListDevicesForUser(GetLoginName(request.Request))

	urls := []string{}
	for _, d := range devices {
		urls = append(urls, fmt.Sprintf("/device/%d", d.Id))
	}

	response.WriteEntity(urls)
}

func serveDevice(request *restful.Request, response *restful.Response) {
	if device := getDeviceForRequest(request, response); device != nil {
		response.WriteEntity(*device)
	}
}

func updateDeviceLocation(request *restful.Request, response *restful.Response) {
	device := getDeviceForRequest(request, response)
	if device == nil {
		return
	}

	latitude, err := strconv.ParseFloat(request.QueryParameter("latitude"), 64)
	if err != nil {
		response.WriteError(http.StatusBadRequest, nil)
	}

	longitude, err := strconv.ParseFloat(request.QueryParameter("longitude"), 64)
	if err != nil {
		response.WriteError(http.StatusBadRequest, nil)
	}

	err = gDB.UpdateDeviceLocation(device, latitude, longitude)
	if err != nil {
		response.WriteError(http.StatusInternalServerError, nil)
	}
}

func reportDeviceLost(request *restful.Request, response *restful.Response) {
	device := getDeviceForRequest(request, response)
	if device == nil {
		return
	}

	// Issue push notification to device
	body := fmt.Sprintf("version=%d", uint64(time.Now().Unix()))
	pushRequest, err := http.NewRequest("PUT", device.Endpoint, strings.NewReader(body))
	if err != nil {
		response.WriteError(http.StatusInternalServerError, nil)
	}

	pushRequest.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}

	var client http.Client
	_, err = client.Do(pushRequest)
	if err != nil {
		response.WriteError(http.StatusInternalServerError, nil)
	}
}

func createDeviceWebService() *restful.WebService {
	ws := new(restful.WebService)

	ws.
		Filter(ensureIsLoggedIn).
		Path("/device").
		Consumes(restful.MIME_JSON).
		Produces(restful.MIME_JSON)

	ws.
		Route(ws.GET("/").To(serveDevicesByUser).
		Doc("Retrieve all devices owned by a user").
		Writes([]Device{}))

	ws.
		Route(ws.GET("/{device-id}").To(serveDevice).
		Doc("Retrieve a device based on its id").
		Param(ws.PathParameter("device-id", "The identifier for the device")).
		Writes(Device{}))

	ws.
		Route(ws.PUT("/").To(addDevice).
		Consumes("application/json; charset=UTF-8").
		Doc("Add a device").
		Param(ws.QueryParameter("name", "The name for the device")).
		Param(ws.QueryParameter("endpoint", "The push endpoint for the device")).
		Writes(Device{}))

	ws.
		Route(ws.POST("/location/{device-id}").To(updateDeviceLocation).
		Consumes("application/x-www-form-urlencoded; charset=UTF-8").
		Doc("Update a device's latitude and longitude").
		Param(ws.QueryParameter("latitude", "The latitude where the device was observed")).
		Param(ws.QueryParameter("longitude", "The longitude where the device was observed")))

	ws.
		Route(ws.POST("/lost/{device-id}").To(reportDeviceLost).
		Doc("Report a device as lost"))

	return ws
}

func main() {
	readConfig()
	db, err := OpenDB("db.sqlite")
	if err != nil {
		panic(err)
	}

	gDB = db
	restful.Add(createDeviceWebService())

	// Persona handling
	http.HandleFunc("/auth/check", loginCheckHandler)
	http.HandleFunc("/auth/login", loginHandler)
	http.HandleFunc("/auth/applogin", appLoginHandler)
	http.HandleFunc("/auth/logout", logoutHandler)

	serveSingle("/", "./static/index.html")
	serveSingle("/index.html", "./static/index.html")
	serveSingle("/install.html", "./static/install.html")
	serveSingle("/app.html", "./app/index.html")
	serveSingle("/style.css", "./static/style.css")
	serveSingle("/style-app.css", "./static/style-app.css")
	serveSingle("/style-common.css", "./static/style-common.css")
	serveSingle("/logos/64.png", "./static/logos/64.png")
	serveSingle("/logos/128.png", "./static/logos/128.png")
	serveSingle("/img/persona-login.png", "./static/img/persona-login.png")
	serveSingle("/lib/mustache.js", "./static/lib/mustache.js")

	http.HandleFunc("/manifest.webapp", func(w http.ResponseWriter, r *http.Request) {
		filename := "./app/manifest.webapp"
		log.Println("serving manifest from " + filename)

		w.Header()["Content-Type"] = []string{"application/x-web-app-manifest+json"}
		http.ServeFile(w, r, filename)
	})

	log.Println("Listening on", gServerConfig.Hostname+":"+gServerConfig.Port)

	if gServerConfig.UseTLS {
		err = http.ListenAndServeTLS(gServerConfig.Hostname+":"+gServerConfig.Port,
			gServerConfig.CertFilename,
			gServerConfig.KeyFilename,
			nil)
	} else {
		log.Println("This is a really unsafe way to run the server.  Really.  Don't do this in production.")
		err = http.ListenAndServe(gServerConfig.Hostname+":"+gServerConfig.Port, nil)
	}

	log.Println("Exiting... ", err)
	gDB.Close()
}
<|endoftext|>


<commit_before>package server

import (
	"github.com/gabstv/sandpiper/pathtree"
	"github.com/gabstv/sandpiper/route"
	"github.com/gabstv/sandpiper/util"
	"log"
	"net/http"
	"os"
	"runtime"
	"time"
)

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

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

func (s *Server) DebugLog() {

}

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

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

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

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

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

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

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

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

func (s *Server) DebugLog() {

}

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

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

	// Autocert
	domains := []string{"testing1.nutripele.com.br"}
	m := autocert.Manager{
		Prompt:     autocert.AcceptTOS,
		HostPolicy: autocert.HostWhitelist(domains...),
	}

	sv := &http.Server{
		Addr:      s.Cfg.ListenAddrTLS,
		TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
	}
	sv.Handler = s
	certs := make([]util.Certificate, 0, len(s.domains))
	for _, v := range s.domains {
		if v.Certificate.CertFile != "" {
			certs = append(certs, v.Certificate)
		}
	}
	if len(certs) > 0 {
		go func() {
			errc <- util.ListenAndServeTLSSNI(sv, certs)
		}()
	}
	err := <-errc
	return err
}

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

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


<commit_before>/*  reliable-chat - multipath chat
 *  Copyright (C) 2012  Scott Worley <sworley@chkno.net>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package main

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

var port = flag.Int("port", 21059, "Port to listen on")
var localaddress = flag.String("localaddress", "", "Local address to bind to")
var max_messages = flag.Int("maxmessages", 1000, "Maximum number of messages to retain")

var start_time = expvar.NewInt("start_time")
var speak_count = expvar.NewInt("speak_count")
var fetch_count = expvar.NewInt("fetch_count")
var fetch_wait_count = expvar.NewInt("fetch_wait_count")
var fetch_wake_count = expvar.NewInt("fetch_wake_count")
var drop_due_to_limit_count = expvar.NewInt("drop_due_to_limit_count")

type Message struct {
	Time time.Time
	ID   string
	Text string
}

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

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

// TODO: Monotonic clock

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

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

const robots_txt = `User-agent: *
Disallow: /
`

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

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

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

	http.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(robots_txt))
	})

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

func main() {
	flag.Parse()
	start_time.Set(time.Now().UnixNano())
	store := start_store()
	start_server(store)
}
<commit_msg>Monotonic clock<commit_after>/*  reliable-chat - multipath chat
 *  Copyright (C) 2012  Scott Worley <sworley@chkno.net>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package main

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

var port = flag.Int("port", 21059, "Port to listen on")
var localaddress = flag.String("localaddress", "", "Local address to bind to")
var max_messages = flag.Int("maxmessages", 1000, "Maximum number of messages to retain")

var start_time = expvar.NewInt("start_time")
var speak_count = expvar.NewInt("speak_count")
var fetch_count = expvar.NewInt("fetch_count")
var fetch_wait_count = expvar.NewInt("fetch_wait_count")
var fetch_wake_count = expvar.NewInt("fetch_wake_count")
var drop_due_to_limit_count = expvar.NewInt("drop_due_to_limit_count")

type Message struct {
	Time time.Time
	ID   string
	Text string
}

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

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

var monotonic_clock chan chan time.Time

const minimum_clock_increment = time.Millisecond

func start_clock() {
	internal_monotonic_clock := make(chan chan time.Time, 1)
	go func() {
		last_time := time.Now()
	main:
		for {
			select {
			case request, ok := <-internal_monotonic_clock:
				if !ok {
					break main
				}
				earliest_acceptable_time := last_time.Add(minimum_clock_increment)
				current_time := time.Now()
				if current_time.Before(earliest_acceptable_time) {
					current_time = earliest_acceptable_time
				}
				request <- current_time
				last_time = current_time
			}
		}
	}()
	monotonic_clock = internal_monotonic_clock
}

func now() time.Time {
	c := make(chan time.Time, 0)
	monotonic_clock <- c
	return <-c
}

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

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

const robots_txt = `User-agent: *
Disallow: /
`

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

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

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

	http.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(robots_txt))
	})

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

func main() {
	flag.Parse()
	start_clock()
	start_time.Set(now().UnixNano())
	store := start_store()
	start_server(store)
}
<|endoftext|>