<commit_before>package server_manager

import (
	"log"
	"math/rand"
	"sync"
	"sync/atomic"
	"time"

	"github.com/hashicorp/consul/consul/server_details"
	"github.com/hashicorp/consul/lib"
)

type consulServerEventTypes int

const (
	// clientRPCJitterFraction determines the amount of jitter added to
	// clientRPCMinReuseDuration before a connection is expired and a new
	// connection is established in order to rebalance load across consul
	// servers.  The cluster-wide number of connections per second from
	// rebalancing is applied after this jitter to ensure the CPU impact
	// is always finite.  See newRebalanceConnsPerSecPerServer's comment
	// for additional commentary.
	//
	// For example, in a 10K consul cluster with 5x servers, this default
	// averages out to ~13 new connections from rebalancing per server
	// per second (each connection is reused for 120s to 180s).
	clientRPCJitterFraction = 2

	// clientRPCMinReuseDuration controls the minimum amount of time RPC
	// queries are sent over an established connection to a single server
	clientRPCMinReuseDuration = 120 * time.Second

	// initialRebalanceTimeoutHours is the initial value for the
	// rebalanceTimer.  This value is discarded immediately after the
	// client becomes aware of the first server.
	initialRebalanceTimeoutHours = 24

	// Limit the number of new connections a server receives per second
	// for connection rebalancing.  This limit caps the load caused by
	// continual rebalancing efforts when a cluster is in equilibrium.  A
	// lower value comes at the cost of increased recovery time after a
	// partition.  This parameter begins to take effect when there are
	// more than ~48K clients querying 5x servers or at lower server
	// values when there is a partition.
	//
	// For example, in a 100K consul cluster with 5x servers, it will
	// take ~5min for all servers to rebalance their connections.  If
	// 99,995 agents are in the minority talking to only one server, it
	// will take ~26min for all servers to rebalance.  A 10K cluster in
	// the same scenario will take ~2.6min to rebalance.
	newRebalanceConnsPerSecPerServer = 64
)

type ConsulClusterInfo interface {
	NumNodes() int
}

// serverCfg is the thread-safe configuration structure that is used to
// maintain the list of consul servers in Client.
//
// NOTE(sean@): We are explicitly relying on the fact that serverConfig will
// be copied onto the stack.  Please keep this structure light.
type serverConfig struct {
	// servers tracks the locally known servers.  List membership is
	// maintained by Serf.
	servers []*server_details.ServerDetails
}

type ServerManager struct {
	// serverConfig provides the necessary load/store semantics for the
	// server list.
	serverConfigValue atomic.Value
	serverConfigLock  sync.Mutex

	// shutdownCh is a copy of the channel in consul.Client
	shutdownCh chan struct{}

	logger *log.Logger

	// serf is used to estimate the approximate number of nodes in a
	// cluster and limit the rate at which it rebalances server
	// connections
	clusterInfo ConsulClusterInfo

	// notifyFailedServersBarrier is acts as a barrier to prevent
	// queueing behind serverConfigLog and acts as a TryLock().
	notifyFailedBarrier int32
}

// AddServer takes out an internal write lock and adds a new server.  If the
// server is not known, appends the server to the list.  The new server will
// begin seeing use after the rebalance timer fires or enough servers fail
// organically.  If the server is already known, merge the new server
// details.
func (sm *ServerManager) AddServer(server *server_details.ServerDetails) {
	sm.serverConfigLock.Lock()
	defer sm.serverConfigLock.Unlock()
	serverCfg := sm.getServerConfig()

	// Check if this server is known
	found := false
	for idx, existing := range serverCfg.servers {
		if existing.Name == server.Name {
			newServers := make([]*server_details.ServerDetails, len(serverCfg.servers))
			copy(newServers, serverCfg.servers)

			// Overwrite the existing server details in order to
			// possibly update metadata (e.g. server version)
			newServers[idx] = server

			serverCfg.servers = newServers
			found = true
			break
		}
	}

	// Add to the list if not known
	if !found {
		newServers := make([]*server_details.ServerDetails, len(serverCfg.servers), len(serverCfg.servers)+1)
		copy(newServers, serverCfg.servers)
		newServers = append(newServers, server)
		serverCfg.servers = newServers
	}

	sm.saveServerConfig(serverCfg)
}

// cycleServers returns a new list of servers that has dequeued the first
// server and enqueued it at the end of the list.  cycleServers assumes the
// caller is holding the serverConfigLock.
func (sc *serverConfig) cycleServer() (servers []*server_details.ServerDetails) {
	numServers := len(sc.servers)
	if numServers < 2 {
		return servers // No action required
	}

	newServers := make([]*server_details.ServerDetails, 0, numServers)
	newServers = append(newServers, sc.servers[1:]...)
	newServers = append(newServers, sc.servers[0])
	return newServers
}

// FindHealthyServer takes out an internal "read lock" and searches through
// the list of servers to find a "healthy" server.  If the server is actually
// unhealthy, we rely on Serf to detect this and remove the node from the
// server list.  If the server at the front of the list has failed or fails
// during an RPC call, it is rotated to the end of the list.  If there are no
// servers available, return nil.
func (sm *ServerManager) FindHealthyServer() *server_details.ServerDetails {
	serverCfg := sm.getServerConfig()
	numServers := len(serverCfg.servers)
	if numServers == 0 {
		sm.logger.Printf("[ERR] consul: No servers found in the server config")
		return nil
	} else {
		// Return whatever is at the front of the list because it is
		// assumed to be the oldest in the server list (unless -
		// hypothetically - the server list was rotated right after a
		// server was added).
		return serverCfg.servers[0]
	}
}

// GetNumServers takes out an internal "read lock" and returns the number of
// servers.  numServers includes both healthy and unhealthy servers.
func (sm *ServerManager) GetNumServers() (numServers int) {
	serverCfg := sm.getServerConfig()
	numServers = len(serverCfg.servers)
	return numServers
}

// getServerConfig is a convenience method which hides the locking semantics
// of atomic.Value from the caller.
func (sm *ServerManager) getServerConfig() serverConfig {
	return sm.serverConfigValue.Load().(serverConfig)
}

// NewServerManager is the only way to safely create a new ServerManager
// struct.
func NewServerManager(logger *log.Logger, shutdownCh chan struct{}, clusterInfo ConsulClusterInfo) (sm *ServerManager) {
	// NOTE(sean@): Can't pass *consul.Client due to an import cycle
	sm = new(ServerManager)
	sm.logger = logger
	sm.clusterInfo = clusterInfo
	sm.shutdownCh = shutdownCh

	sc := serverConfig{}
	sc.servers = make([]*server_details.ServerDetails, 0)
	sm.saveServerConfig(sc)
	return sm
}

// NotifyFailedServer marks the passed in server as "failed" by rotating it
// to the end of the server list.
func (sm *ServerManager) NotifyFailedServer(server *server_details.ServerDetails) {
	serverCfg := sm.getServerConfig()

	// If the server being failed is not the first server on the list,
	// this is a noop.  If, however, the server is failed and first on
	// the list, acquire the lock, retest, and take the penalty of moving
	// the server to the end of the list.

	// Use atomic.CAS to emulate a TryLock().
	if len(serverCfg.servers) > 0 && serverCfg.servers[0] == server &&
		atomic.CompareAndSwapInt32(&sm.notifyFailedBarrier, 0, 1) {
		defer atomic.StoreInt32(&sm.notifyFailedBarrier, 0)

		// Grab a lock, retest, and take the hit of cycling the first
		// server to the end.
		sm.serverConfigLock.Lock()
		defer sm.serverConfigLock.Unlock()
		serverCfg = sm.getServerConfig()

		if len(serverCfg.servers) > 0 && serverCfg.servers[0] == server {
			serverCfg.cycleServer()
			sm.saveServerConfig(serverCfg)
		}
	}
}

// RebalanceServers takes out an internal write lock and shuffles the list of
// servers on this agent.  This allows for a redistribution of work across
// consul servers and provides a guarantee that the order of the server list
// isn't related to the age at which the node was added to the cluster.
// Elsewhere we rely on the position in the server list as a hint regarding
// the stability of a server relative to its position in the server list.
// Servers at or near the front of the list are more stable than servers near
// the end of the list.
func (sm *ServerManager) RebalanceServers() {
	sm.serverConfigLock.Lock()
	defer sm.serverConfigLock.Unlock()
	serverCfg := sm.getServerConfig()

	newServers := make([]*server_details.ServerDetails, len(serverCfg.servers))
	copy(newServers, serverCfg.servers)

	// Shuffle the server list on server join.  Servers are selected from
	// the head of the list and are moved to the end of the list on
	// failure.
	for i := len(serverCfg.servers) - 1; i > 0; i-- {
		j := rand.Int31n(int32(i + 1))
		newServers[i], newServers[j] = newServers[j], newServers[i]
	}
	serverCfg.servers = newServers

	sm.saveServerConfig(serverCfg)
}

// RemoveServer takes out an internal write lock and removes a server from
// the server list.
func (sm *ServerManager) RemoveServer(server *server_details.ServerDetails) {
	sm.serverConfigLock.Lock()
	defer sm.serverConfigLock.Unlock()
	serverCfg := sm.getServerConfig()

	// Remove the server if known
	n := len(serverCfg.servers)
	for i := 0; i < n; i++ {
		if serverCfg.servers[i].Name == server.Name {
			newServers := make([]*server_details.ServerDetails, len(serverCfg.servers)-1)
			copy(newServers, serverCfg.servers)

			newServers[i], newServers[n-1] = newServers[n-1], nil
			newServers = newServers[:n-1]
			serverCfg.servers = newServers

			sm.saveServerConfig(serverCfg)
			return
		}
	}
}

// refreshServerRebalanceTimer is only called once the rebalanceTimer
// expires.  Historically this was an expensive routine and is intended to be
// run in isolation in a dedicated, non-concurrent task.
func (sm *ServerManager) refreshServerRebalanceTimer(timer *time.Timer) {
	serverCfg := sm.getServerConfig()
	numConsulServers := len(serverCfg.servers)
	// Limit this connection's life based on the size (and health) of the
	// cluster.  Never rebalance a connection more frequently than
	// connReuseLowWatermarkDuration, and make sure we never exceed
	// clusterWideRebalanceConnsPerSec operations/s across numLANMembers.
	clusterWideRebalanceConnsPerSec := float64(numConsulServers * newRebalanceConnsPerSecPerServer)
	connReuseLowWatermarkDuration := clientRPCMinReuseDuration + lib.RandomStagger(clientRPCMinReuseDuration/clientRPCJitterFraction)

	numLANMembers := sm.clusterInfo.NumNodes()
	connRebalanceTimeout := lib.RateScaledInterval(clusterWideRebalanceConnsPerSec, connReuseLowWatermarkDuration, numLANMembers)
	sm.logger.Printf("[DEBUG] consul: connection will be rebalanced in %v", connRebalanceTimeout)

	timer.Reset(connRebalanceTimeout)
}

// saveServerConfig is a convenience method which hides the locking semantics
// of atomic.Value from the caller.
func (sm *ServerManager) saveServerConfig(sc serverConfig) {
	sm.serverConfigValue.Store(sc)
}

// Start is used to start and manage the task of automatically shuffling and
// rebalancing the list of consul servers.  This maintenance only happens
// periodically based on the expiration of the timer.  Failed servers are
// automatically cycled to the end of the list.  New servers are appended to
// the list.  The order of the server list must be shuffled periodically to
// distribute load across all known and available consul servers.
func (sm *ServerManager) Start() {
	var rebalanceTimer *time.Timer = time.NewTimer(time.Duration(initialRebalanceTimeoutHours * time.Hour))
	var rebalanceTaskDispatched int32

	func() {
		sm.serverConfigLock.Lock()
		defer sm.serverConfigLock.Unlock()

		serverCfgPtr := sm.serverConfigValue.Load()
		if serverCfgPtr == nil {
			panic("server config has not been initialized")
		}
		var serverCfg serverConfig
		serverCfg = serverCfgPtr.(serverConfig)
		sm.saveServerConfig(serverCfg)
	}()

	for {
		select {
		case <-rebalanceTimer.C:
			sm.logger.Printf("[INFO] server manager: server rebalance timeout")
			sm.RebalanceServers()

			// Only run one rebalance task at a time, but do
			// allow for the channel to be drained
			if atomic.CompareAndSwapInt32(&rebalanceTaskDispatched, 0, 1) {
				sm.logger.Printf("[INFO] server manager: Launching rebalance duration task")
				go func() {
					defer atomic.StoreInt32(&rebalanceTaskDispatched, 0)
					sm.refreshServerRebalanceTimer(rebalanceTimer)
				}()
			}

		case <-sm.shutdownCh:
			sm.logger.Printf("[INFO] server manager: shutting down")
			return
		}
	}
}
<commit_msg>cycleServer is a pure function, save the result<commit_after>package server_manager

import (
	"log"
	"math/rand"
	"sync"
	"sync/atomic"
	"time"

	"github.com/hashicorp/consul/consul/server_details"
	"github.com/hashicorp/consul/lib"
)

type consulServerEventTypes int

const (
	// clientRPCJitterFraction determines the amount of jitter added to
	// clientRPCMinReuseDuration before a connection is expired and a new
	// connection is established in order to rebalance load across consul
	// servers.  The cluster-wide number of connections per second from
	// rebalancing is applied after this jitter to ensure the CPU impact
	// is always finite.  See newRebalanceConnsPerSecPerServer's comment
	// for additional commentary.
	//
	// For example, in a 10K consul cluster with 5x servers, this default
	// averages out to ~13 new connections from rebalancing per server
	// per second (each connection is reused for 120s to 180s).
	clientRPCJitterFraction = 2

	// clientRPCMinReuseDuration controls the minimum amount of time RPC
	// queries are sent over an established connection to a single server
	clientRPCMinReuseDuration = 120 * time.Second

	// initialRebalanceTimeoutHours is the initial value for the
	// rebalanceTimer.  This value is discarded immediately after the
	// client becomes aware of the first server.
	initialRebalanceTimeoutHours = 24

	// Limit the number of new connections a server receives per second
	// for connection rebalancing.  This limit caps the load caused by
	// continual rebalancing efforts when a cluster is in equilibrium.  A
	// lower value comes at the cost of increased recovery time after a
	// partition.  This parameter begins to take effect when there are
	// more than ~48K clients querying 5x servers or at lower server
	// values when there is a partition.
	//
	// For example, in a 100K consul cluster with 5x servers, it will
	// take ~5min for all servers to rebalance their connections.  If
	// 99,995 agents are in the minority talking to only one server, it
	// will take ~26min for all servers to rebalance.  A 10K cluster in
	// the same scenario will take ~2.6min to rebalance.
	newRebalanceConnsPerSecPerServer = 64
)

type ConsulClusterInfo interface {
	NumNodes() int
}

// serverCfg is the thread-safe configuration structure that is used to
// maintain the list of consul servers in Client.
//
// NOTE(sean@): We are explicitly relying on the fact that serverConfig will
// be copied onto the stack.  Please keep this structure light.
type serverConfig struct {
	// servers tracks the locally known servers.  List membership is
	// maintained by Serf.
	servers []*server_details.ServerDetails
}

type ServerManager struct {
	// serverConfig provides the necessary load/store semantics for the
	// server list.
	serverConfigValue atomic.Value
	serverConfigLock  sync.Mutex

	// shutdownCh is a copy of the channel in consul.Client
	shutdownCh chan struct{}

	logger *log.Logger

	// serf is used to estimate the approximate number of nodes in a
	// cluster and limit the rate at which it rebalances server
	// connections
	clusterInfo ConsulClusterInfo

	// notifyFailedServersBarrier is acts as a barrier to prevent
	// queueing behind serverConfigLog and acts as a TryLock().
	notifyFailedBarrier int32
}

// AddServer takes out an internal write lock and adds a new server.  If the
// server is not known, appends the server to the list.  The new server will
// begin seeing use after the rebalance timer fires or enough servers fail
// organically.  If the server is already known, merge the new server
// details.
func (sm *ServerManager) AddServer(server *server_details.ServerDetails) {
	sm.serverConfigLock.Lock()
	defer sm.serverConfigLock.Unlock()
	serverCfg := sm.getServerConfig()

	// Check if this server is known
	found := false
	for idx, existing := range serverCfg.servers {
		if existing.Name == server.Name {
			newServers := make([]*server_details.ServerDetails, len(serverCfg.servers))
			copy(newServers, serverCfg.servers)

			// Overwrite the existing server details in order to
			// possibly update metadata (e.g. server version)
			newServers[idx] = server

			serverCfg.servers = newServers
			found = true
			break
		}
	}

	// Add to the list if not known
	if !found {
		newServers := make([]*server_details.ServerDetails, len(serverCfg.servers), len(serverCfg.servers)+1)
		copy(newServers, serverCfg.servers)
		newServers = append(newServers, server)
		serverCfg.servers = newServers
	}

	sm.saveServerConfig(serverCfg)
}

// cycleServers returns a new list of servers that has dequeued the first
// server and enqueued it at the end of the list.  cycleServers assumes the
// caller is holding the serverConfigLock.
func (sc *serverConfig) cycleServer() (servers []*server_details.ServerDetails) {
	numServers := len(sc.servers)
	if numServers < 2 {
		return servers // No action required
	}

	newServers := make([]*server_details.ServerDetails, 0, numServers)
	newServers = append(newServers, sc.servers[1:]...)
	newServers = append(newServers, sc.servers[0])
	return newServers
}

// FindHealthyServer takes out an internal "read lock" and searches through
// the list of servers to find a "healthy" server.  If the server is actually
// unhealthy, we rely on Serf to detect this and remove the node from the
// server list.  If the server at the front of the list has failed or fails
// during an RPC call, it is rotated to the end of the list.  If there are no
// servers available, return nil.
func (sm *ServerManager) FindHealthyServer() *server_details.ServerDetails {
	serverCfg := sm.getServerConfig()
	numServers := len(serverCfg.servers)
	if numServers == 0 {
		sm.logger.Printf("[ERR] consul: No servers found in the server config")
		return nil
	} else {
		// Return whatever is at the front of the list because it is
		// assumed to be the oldest in the server list (unless -
		// hypothetically - the server list was rotated right after a
		// server was added).
		return serverCfg.servers[0]
	}
}

// GetNumServers takes out an internal "read lock" and returns the number of
// servers.  numServers includes both healthy and unhealthy servers.
func (sm *ServerManager) GetNumServers() (numServers int) {
	serverCfg := sm.getServerConfig()
	numServers = len(serverCfg.servers)
	return numServers
}

// getServerConfig is a convenience method which hides the locking semantics
// of atomic.Value from the caller.
func (sm *ServerManager) getServerConfig() serverConfig {
	return sm.serverConfigValue.Load().(serverConfig)
}

// NewServerManager is the only way to safely create a new ServerManager
// struct.
func NewServerManager(logger *log.Logger, shutdownCh chan struct{}, clusterInfo ConsulClusterInfo) (sm *ServerManager) {
	// NOTE(sean@): Can't pass *consul.Client due to an import cycle
	sm = new(ServerManager)
	sm.logger = logger
	sm.clusterInfo = clusterInfo
	sm.shutdownCh = shutdownCh

	sc := serverConfig{}
	sc.servers = make([]*server_details.ServerDetails, 0)
	sm.saveServerConfig(sc)
	return sm
}

// NotifyFailedServer marks the passed in server as "failed" by rotating it
// to the end of the server list.
func (sm *ServerManager) NotifyFailedServer(server *server_details.ServerDetails) {
	serverCfg := sm.getServerConfig()

	// If the server being failed is not the first server on the list,
	// this is a noop.  If, however, the server is failed and first on
	// the list, acquire the lock, retest, and take the penalty of moving
	// the server to the end of the list.

	// Use atomic.CAS to emulate a TryLock().
	if len(serverCfg.servers) > 0 && serverCfg.servers[0] == server &&
		atomic.CompareAndSwapInt32(&sm.notifyFailedBarrier, 0, 1) {
		defer atomic.StoreInt32(&sm.notifyFailedBarrier, 0)

		// Grab a lock, retest, and take the hit of cycling the first
		// server to the end.
		sm.serverConfigLock.Lock()
		defer sm.serverConfigLock.Unlock()
		serverCfg = sm.getServerConfig()

		if len(serverCfg.servers) > 0 && serverCfg.servers[0] == server {
			serverCfg.servers = serverCfg.cycleServer()
			sm.saveServerConfig(serverCfg)
		}
	}
}

// RebalanceServers takes out an internal write lock and shuffles the list of
// servers on this agent.  This allows for a redistribution of work across
// consul servers and provides a guarantee that the order of the server list
// isn't related to the age at which the node was added to the cluster.
// Elsewhere we rely on the position in the server list as a hint regarding
// the stability of a server relative to its position in the server list.
// Servers at or near the front of the list are more stable than servers near
// the end of the list.
func (sm *ServerManager) RebalanceServers() {
	sm.serverConfigLock.Lock()
	defer sm.serverConfigLock.Unlock()
	serverCfg := sm.getServerConfig()

	newServers := make([]*server_details.ServerDetails, len(serverCfg.servers))
	copy(newServers, serverCfg.servers)

	// Shuffle the server list on server join.  Servers are selected from
	// the head of the list and are moved to the end of the list on
	// failure.
	for i := len(serverCfg.servers) - 1; i > 0; i-- {
		j := rand.Int31n(int32(i + 1))
		newServers[i], newServers[j] = newServers[j], newServers[i]
	}
	serverCfg.servers = newServers

	sm.saveServerConfig(serverCfg)
}

// RemoveServer takes out an internal write lock and removes a server from
// the server list.
func (sm *ServerManager) RemoveServer(server *server_details.ServerDetails) {
	sm.serverConfigLock.Lock()
	defer sm.serverConfigLock.Unlock()
	serverCfg := sm.getServerConfig()

	// Remove the server if known
	n := len(serverCfg.servers)
	for i := 0; i < n; i++ {
		if serverCfg.servers[i].Name == server.Name {
			newServers := make([]*server_details.ServerDetails, len(serverCfg.servers)-1)
			copy(newServers, serverCfg.servers)

			newServers[i], newServers[n-1] = newServers[n-1], nil
			newServers = newServers[:n-1]
			serverCfg.servers = newServers

			sm.saveServerConfig(serverCfg)
			return
		}
	}
}

// refreshServerRebalanceTimer is only called once the rebalanceTimer
// expires.  Historically this was an expensive routine and is intended to be
// run in isolation in a dedicated, non-concurrent task.
func (sm *ServerManager) refreshServerRebalanceTimer(timer *time.Timer) {
	serverCfg := sm.getServerConfig()
	numConsulServers := len(serverCfg.servers)
	// Limit this connection's life based on the size (and health) of the
	// cluster.  Never rebalance a connection more frequently than
	// connReuseLowWatermarkDuration, and make sure we never exceed
	// clusterWideRebalanceConnsPerSec operations/s across numLANMembers.
	clusterWideRebalanceConnsPerSec := float64(numConsulServers * newRebalanceConnsPerSecPerServer)
	connReuseLowWatermarkDuration := clientRPCMinReuseDuration + lib.RandomStagger(clientRPCMinReuseDuration/clientRPCJitterFraction)

	numLANMembers := sm.clusterInfo.NumNodes()
	connRebalanceTimeout := lib.RateScaledInterval(clusterWideRebalanceConnsPerSec, connReuseLowWatermarkDuration, numLANMembers)
	sm.logger.Printf("[DEBUG] consul: connection will be rebalanced in %v", connRebalanceTimeout)

	timer.Reset(connRebalanceTimeout)
}

// saveServerConfig is a convenience method which hides the locking semantics
// of atomic.Value from the caller.
func (sm *ServerManager) saveServerConfig(sc serverConfig) {
	sm.serverConfigValue.Store(sc)
}

// Start is used to start and manage the task of automatically shuffling and
// rebalancing the list of consul servers.  This maintenance only happens
// periodically based on the expiration of the timer.  Failed servers are
// automatically cycled to the end of the list.  New servers are appended to
// the list.  The order of the server list must be shuffled periodically to
// distribute load across all known and available consul servers.
func (sm *ServerManager) Start() {
	var rebalanceTimer *time.Timer = time.NewTimer(time.Duration(initialRebalanceTimeoutHours * time.Hour))
	var rebalanceTaskDispatched int32

	func() {
		sm.serverConfigLock.Lock()
		defer sm.serverConfigLock.Unlock()

		serverCfgPtr := sm.serverConfigValue.Load()
		if serverCfgPtr == nil {
			panic("server config has not been initialized")
		}
		var serverCfg serverConfig
		serverCfg = serverCfgPtr.(serverConfig)
		sm.saveServerConfig(serverCfg)
	}()

	for {
		select {
		case <-rebalanceTimer.C:
			sm.logger.Printf("[INFO] server manager: server rebalance timeout")
			sm.RebalanceServers()

			// Only run one rebalance task at a time, but do
			// allow for the channel to be drained
			if atomic.CompareAndSwapInt32(&rebalanceTaskDispatched, 0, 1) {
				sm.logger.Printf("[INFO] server manager: Launching rebalance duration task")
				go func() {
					defer atomic.StoreInt32(&rebalanceTaskDispatched, 0)
					sm.refreshServerRebalanceTimer(rebalanceTimer)
				}()
			}

		case <-sm.shutdownCh:
			sm.logger.Printf("[INFO] server manager: shutting down")
			return
		}
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2013 Adam Langley. All rights reserved
// Copyright (c) 2014 Andres Erbsen

package ratchet

import (
	"bytes"
	"crypto/rand"
	"testing"
	"time"

	"code.google.com/p/go.crypto/curve25519"
)

func nowFunc() time.Time {
	var t time.Time
	return t
}

// TODO: test that messages with bad auth are ignored
var dontFillAuth = func([]byte, []byte, *[32]byte) {}
var dontCheckAuth = func([]byte, []byte, []byte, *[32]byte) error { return nil }

func pairedRatchet() (a, b *Ratchet) {
	var preKeyA, preKeyAPrivate [32]byte
	rand.Read(preKeyAPrivate[:])
	curve25519.ScalarBaseMult(&preKeyA, &preKeyAPrivate)

	a = &Ratchet{Now: nowFunc, FillAuth: dontFillAuth, CheckAuth: dontCheckAuth}
	b = &Ratchet{Now: nowFunc, FillAuth: dontFillAuth, CheckAuth: dontCheckAuth}
	msgBtoA := b.EncryptFirst(nil, nil, &preKeyA)
	_, err := a.DecryptFirst(msgBtoA, &preKeyAPrivate)
	if err != nil {
		panic(err)
	}
	return
}

func TestExchange(t *testing.T) {
	a, b := pairedRatchet()

	msg := []byte("test message")
	encrypted := a.Encrypt(nil, msg)
	result, err := b.Decrypt(encrypted)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(msg, result) {
		t.Fatalf("result doesn't match: %x vs %x", msg, result)
	}
}

type scriptAction struct {
	// object is one of sendA, sendB or sendDelayed. The first two options
	// cause a message to be sent from one party to the other. The latter
	// causes a previously delayed message, identified by id, to be
	// delivered.
	object int
	// result is one of deliver, drop or delay. If delay, then the message
	// is stored using the value in id. This value can be repeated later
	// with a sendDelayed.
	result int
	id     int
}

const (
	sendA = iota
	sendB
	sendDelayed
	deliver
	drop
	delay
)

func reinitRatchet(t *testing.T, r *Ratchet) *Ratchet {
	r.FlushSavedKeys(nowFunc(), 1*time.Hour)
	data, err := r.Marshal()
	if err != nil {
		t.Fatalf("Failed to marshal: %s", err)
	}
	newR := &Ratchet{Now: r.Now, Rand: r.Rand, FillAuth: r.FillAuth, CheckAuth: r.CheckAuth}
	if err := newR.Unmarshal(data); err != nil {
		t.Fatalf("Failed to unmarshal: %s", err)
	}
	return newR

}

func testScript(t *testing.T, script []scriptAction) {
	type delayedMessage struct {
		msg       []byte
		encrypted []byte
		fromA     bool
	}
	delayedMessages := make(map[int]delayedMessage)
	a, b := pairedRatchet()

	for i, action := range script {
		switch action.object {
		case sendA, sendB:
			sender, receiver := a, b
			if action.object == sendB {
				sender, receiver = receiver, sender
			}

			var msg [20]byte
			rand.Reader.Read(msg[:])
			encrypted := sender.Encrypt(nil, msg[:])

			switch action.result {
			case deliver:
				result, err := receiver.Decrypt(encrypted)
				if err != nil {
					t.Fatalf("#%d: receiver returned error: %s", i, err)
				}
				if !bytes.Equal(result, msg[:]) {
					t.Fatalf("#%d: bad message: got %x, not %x", i, result, msg[:])
				}
			case delay:
				if _, ok := delayedMessages[action.id]; ok {
					t.Fatalf("#%d: already have delayed message with id %d", i, action.id)
				}
				delayedMessages[action.id] = delayedMessage{msg[:], encrypted, sender == a}
			case drop:
			}
		case sendDelayed:
			delayed, ok := delayedMessages[action.id]
			if !ok {
				t.Fatalf("#%d: no such delayed message id: %d", i, action.id)
			}

			receiver := a
			if delayed.fromA {
				receiver = b
			}

			result, err := receiver.Decrypt(delayed.encrypted)
			if err != nil {
				t.Fatalf("#%d: receiver returned error: %s", i, err)
			}
			if !bytes.Equal(result, delayed.msg) {
				t.Fatalf("#%d: bad message: got %x, not %x", i, result, delayed.msg)
			}
		}

		a = reinitRatchet(t, a)
		b = reinitRatchet(t, b)
	}
}

func TestBackAndForth(t *testing.T) {
	testScript(t, []scriptAction{
		{sendA, deliver, -1},
		{sendB, deliver, -1},
		{sendA, deliver, -1},
		{sendB, deliver, -1},
		{sendA, deliver, -1},
		{sendB, deliver, -1},
	})
}

func TestReorder(t *testing.T) {
	testScript(t, []scriptAction{
		{sendA, deliver, -1},
		{sendA, delay, 0},
		{sendA, deliver, -1},
		{sendDelayed, deliver, 0},
	})
}

func TestReorderAfterRatchet(t *testing.T) {
	testScript(t, []scriptAction{
		{sendA, deliver, -1},
		{sendA, delay, 0},
		{sendB, deliver, -1},
		{sendA, deliver, -1},
		{sendB, deliver, -1},
		{sendDelayed, deliver, 0},
	})
}

func TestDrop(t *testing.T) {
	testScript(t, []scriptAction{
		{sendA, drop, -1},
		{sendA, drop, -1},
		{sendA, drop, -1},
		{sendA, drop, -1},
		{sendA, deliver, -1},
		{sendB, deliver, -1},
	})
}

func TestOverhead(t *testing.T) {
	var preKeyA, preKeyAPrivate [32]byte
	rand.Read(preKeyAPrivate[:])
	curve25519.ScalarBaseMult(&preKeyA, &preKeyAPrivate)

	a := &Ratchet{Now: nowFunc, FillAuth: dontFillAuth, CheckAuth: dontCheckAuth}
	b := &Ratchet{Now: nowFunc, FillAuth: dontFillAuth, CheckAuth: dontCheckAuth}
	encryptedFirst := b.EncryptFirst(nil, nil, &preKeyA)
	if len(encryptedFirst) != OverheadFirst {
		t.Errorf("expected first message overhead overhead %d, got %d", OverheadFirst, len(encryptedFirst))
	}
	if _, err := a.DecryptFirst(encryptedFirst, &preKeyAPrivate); err != nil {
		t.Fatal(err)
	}

	r, _ := pairedRatchet()
	encrypted := r.Encrypt(nil, nil)
	if len(encrypted) != Overhead {
		t.Errorf("expected subsequent message overhead %d, got %d", Overhead, len(encrypted))
	}
}
<commit_msg>add failing test for ratchet<commit_after>// Copyright (c) 2013 Adam Langley. All rights reserved
// Copyright (c) 2014 Andres Erbsen

package ratchet

import (
	"bytes"
	"crypto/rand"
	"testing"
	"time"

	"code.google.com/p/go.crypto/curve25519"
)

func nowFunc() time.Time {
	var t time.Time
	return t
}

// TODO: test that messages with bad auth are ignored
var dontFillAuth = func([]byte, []byte, *[32]byte) {}
var dontCheckAuth = func([]byte, []byte, []byte, *[32]byte) error { return nil }

func pairedRatchet() (a, b *Ratchet) {
	var preKeyA, preKeyAPrivate [32]byte
	rand.Read(preKeyAPrivate[:])
	curve25519.ScalarBaseMult(&preKeyA, &preKeyAPrivate)

	a = &Ratchet{Now: nowFunc, FillAuth: dontFillAuth, CheckAuth: dontCheckAuth}
	b = &Ratchet{Now: nowFunc, FillAuth: dontFillAuth, CheckAuth: dontCheckAuth}
	msgBtoA := b.EncryptFirst(nil, nil, &preKeyA)
	_, err := a.DecryptFirst(msgBtoA, &preKeyAPrivate)
	if err != nil {
		panic(err)
	}
	return
}

func TestExchange(t *testing.T) {
	a, b := pairedRatchet()

	msg := []byte("test message")
	encrypted := a.Encrypt(nil, msg)
	result, err := b.Decrypt(encrypted)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(msg, result) {
		t.Fatalf("result doesn't match: %x vs %x", msg, result)
	}
}

type scriptAction struct {
	// object is one of sendA, sendB or sendDelayed. The first two options
	// cause a message to be sent from one party to the other. The latter
	// causes a previously delayed message, identified by id, to be
	// delivered.
	object int
	// result is one of deliver, drop or delay. If delay, then the message
	// is stored using the value in id. This value can be repeated later
	// with a sendDelayed.
	result int
	id     int
}

const (
	sendA = iota
	sendB
	sendDelayed
	deliver
	drop
	delay
)

func reinitRatchet(t *testing.T, r *Ratchet) *Ratchet {
	r.FlushSavedKeys(nowFunc(), 1*time.Hour)
	data, err := r.Marshal()
	if err != nil {
		t.Fatalf("Failed to marshal: %s", err)
	}
	newR := &Ratchet{Now: r.Now, Rand: r.Rand, FillAuth: r.FillAuth, CheckAuth: r.CheckAuth}
	if err := newR.Unmarshal(data); err != nil {
		t.Fatalf("Failed to unmarshal: %s", err)
	}
	return newR

}

func testScript(t *testing.T, script []scriptAction) {
	type delayedMessage struct {
		msg       []byte
		encrypted []byte
		fromA     bool
	}
	delayedMessages := make(map[int]delayedMessage)
	a, b := pairedRatchet()

	for i, action := range script {
		switch action.object {
		case sendA, sendB:
			sender, receiver := a, b
			if action.object == sendB {
				sender, receiver = receiver, sender
			}

			var msg [20]byte
			rand.Reader.Read(msg[:])
			encrypted := sender.Encrypt(nil, msg[:])

			switch action.result {
			case deliver:
				result, err := receiver.Decrypt(encrypted)
				if err != nil {
					t.Fatalf("#%d: receiver returned error: %s", i, err)
				}
				if !bytes.Equal(result, msg[:]) {
					t.Fatalf("#%d: bad message: got %x, not %x", i, result, msg[:])
				}
			case delay:
				if _, ok := delayedMessages[action.id]; ok {
					t.Fatalf("#%d: already have delayed message with id %d", i, action.id)
				}
				delayedMessages[action.id] = delayedMessage{msg[:], encrypted, sender == a}
			case drop:
			}
		case sendDelayed:
			delayed, ok := delayedMessages[action.id]
			if !ok {
				t.Fatalf("#%d: no such delayed message id: %d", i, action.id)
			}

			receiver := a
			if delayed.fromA {
				receiver = b
			}

			result, err := receiver.Decrypt(delayed.encrypted)
			if err != nil {
				t.Fatalf("#%d: receiver returned error: %s", i, err)
			}
			if !bytes.Equal(result, delayed.msg) {
				t.Fatalf("#%d: bad message: got %x, not %x", i, result, delayed.msg)
			}
		}

		a = reinitRatchet(t, a)
		b = reinitRatchet(t, b)
	}
}

func TestBackAndForth(t *testing.T) {
	testScript(t, []scriptAction{
		{sendA, deliver, -1},
		{sendB, deliver, -1},
		{sendA, deliver, -1},
		{sendB, deliver, -1},
		{sendA, deliver, -1},
		{sendB, deliver, -1},
	})
}

func TestReorder(t *testing.T) {
	testScript(t, []scriptAction{
		{sendA, deliver, -1},
		{sendA, delay, 0},
		{sendA, deliver, -1},
		{sendDelayed, deliver, 0},
	})
}

func TestReorderAfterRatchet(t *testing.T) {
	testScript(t, []scriptAction{
		{sendA, deliver, -1},
		{sendA, delay, 0},
		{sendB, deliver, -1},
		{sendA, deliver, -1},
		{sendB, deliver, -1},
		{sendDelayed, deliver, 0},
	})
}

func TestReorderMore(t *testing.T) {
	testScript(t, []scriptAction{
		{sendA, deliver, -1},
		{sendA, delay, 0},
		{sendB, delay, 1},
		{sendA, delay, 2},
		{sendB, delay, 3},
		{sendA, deliver, -1},
		{sendB, deliver, -1},
		{sendDelayed, deliver, 2},
		{sendDelayed, deliver, 1},
		{sendDelayed, deliver, 3},
		{sendDelayed, deliver, 0},
	})
}

func TestDrop(t *testing.T) {
	testScript(t, []scriptAction{
		{sendA, drop, -1},
		{sendA, drop, -1},
		{sendA, drop, -1},
		{sendA, drop, -1},
		{sendA, deliver, -1},
		{sendB, deliver, -1},
	})
}

func TestOverhead(t *testing.T) {
	var preKeyA, preKeyAPrivate [32]byte
	rand.Read(preKeyAPrivate[:])
	curve25519.ScalarBaseMult(&preKeyA, &preKeyAPrivate)

	a := &Ratchet{Now: nowFunc, FillAuth: dontFillAuth, CheckAuth: dontCheckAuth}
	b := &Ratchet{Now: nowFunc, FillAuth: dontFillAuth, CheckAuth: dontCheckAuth}
	encryptedFirst := b.EncryptFirst(nil, nil, &preKeyA)
	if len(encryptedFirst) != OverheadFirst {
		t.Errorf("expected first message overhead overhead %d, got %d", OverheadFirst, len(encryptedFirst))
	}
	if _, err := a.DecryptFirst(encryptedFirst, &preKeyAPrivate); err != nil {
		t.Fatal(err)
	}

	r, _ := pairedRatchet()
	encrypted := r.Encrypt(nil, nil)
	if len(encrypted) != Overhead {
		t.Errorf("expected subsequent message overhead %d, got %d", Overhead, len(encrypted))
	}
}
<|endoftext|>


<commit_before>package selenium

/* Element finding options */
const (
	ById              = "id"
	ByXPATH           = "xpath"
	ByLinkText        = "link text"
	ByPartialLinkText = "partial link text"
	ByName            = "name"
	ByTagName         = "tag name"
	ByClassName       = "class name"
	ByCSSSelector     = "css selector"
)

/* Mouse buttons */
const (
	LeftButton = iota
	MiddleButton
	RightButton
)

/* Keys */
const (
	NullKey       = string('\ue000')
	CancelKey     = string('\ue001')
	HelpKey       = string('\ue002')
	BackspaceKey  = string('\ue003')
	TabKey        = string('\ue004')
	ClearKey      = string('\ue005')
	ReturnKey     = string('\ue006')
	EnterKey      = string('\ue007')
	ShiftKey      = string('\ue008')
	ControlKey    = string('\ue009')
	AltKey        = string('\ue00a')
	PauseKey      = string('\ue00b')
	EscapeKey     = string('\ue00c')
	SpaceKey      = string('\ue00d')
	PageUpKey     = string('\ue00e')
	PageDownKey   = string('\ue00f')
	EndKey        = string('\ue010')
	HomeKey       = string('\ue011')
	LeftArrowKey  = string('\ue012')
	UpArrowKey    = string('\ue013')
	RightArrowKey = string('\ue014')
	DownArrowKey  = string('\ue015')
	InsertKey     = string('\ue016')
	DeleteKey     = string('\ue017')
	SemicolonKey  = string('\ue018')
	EqualsKey     = string('\ue019')
	Numpad0Key    = string('\ue01a')
	Numpad1Key    = string('\ue01b')
	Numpad2Key    = string('\ue01c')
	Numpad3Key    = string('\ue01d')
	Numpad4Key    = string('\ue01e')
	Numpad5Key    = string('\ue01f')
	Numpad6Key    = string('\ue020')
	Numpad7Key    = string('\ue021')
	Numpad8Key    = string('\ue022')
	Numpad9Key    = string('\ue023')
	MultiplyKey   = string('\ue024')
	AddKey        = string('\ue025')
	SeparatorKey  = string('\ue026')
	SubstractKey  = string('\ue027')
	DecimalKey    = string('\ue028')
	DivideKey     = string('\ue029')
	F1Key         = string('\ue031')
	F2Key         = string('\ue032')
	F3Key         = string('\ue033')
	F4Key         = string('\ue034')
	F5Key         = string('\ue035')
	F6Key         = string('\ue036')
	F7Key         = string('\ue037')
	F8Key         = string('\ue038')
	F9Key         = string('\ue039')
	F10Key        = string('\ue03a')
	F11Key        = string('\ue03b')
	F12Key        = string('\ue03c')
	MetaKey       = string('\ue03d')
)

/* Browser capabilities, see
http://code.google.com/p/selenium/wiki/JsonWireProtocol#Capabilities_JSON_Object
*/
type Capabilities map[string]interface{}

/* Build object, part of Status return. */
type Build struct {
	Version, Revision, Time string
}

/* OS object, part of Status return. */
type OS struct {
	Arch, Name, Version string
}

/* Information retured by Status method. */
type Status struct {
	Build `json:"build"`
	OS    `json:"os"`
}

/* Point */
type Point struct {
	X, Y float64
}

/* Size */
type Size struct {
	Width  float64 `json:"width"`
	Height float64 `json:"height"`
}

/* Cookie */
type Cookie struct {
	Name   string `json:"name"`
	Value  string `json:"value"`
	Path   string `json:"path"`
	Domain string `json:"domain"`
	Secure bool   `json:"secure"`
	Expiry uint   `json:"expiry"`
}

type WebDriver interface {
	/* Status (info) on server */
	Status() (*Status, error)

	/* Start a new session, return session id */
	NewSession() (string, error)

	/* Current session capabilities */
	Capabilities() (Capabilities, error)
	/* Set the amount of time, in milliseconds, that asynchronous scripts are permitted to run before they are aborted. */
	SetAsyncScriptTimeout(ms uint) error
	/* Set the amount of time, in milliseconds, the driver should wait when searching for elements. */
	SetImplicitWaitTimeout(ms uint) error

	// IME
	/* List all available engines on the machine. */
	AvailableEngines() ([]string, error)
	/* Get the name of the active IME engine. */
	ActiveEngine() (string, error)
	/* Indicates whether IME input is active at the moment. */
	IsEngineActivated() (bool, error)
	/* De-activates the currently-active IME engine. */
	DeactivateEngine() error
	/* Make an engines active */
	ActivateEngine(engine string) error

	/* Quit (end) current session */
	Quit() error

	// Page information and manipulation
	/* Return id of current window handle. */
	CurrentWindowHandle() (string, error)
	/* Return ids of current open windows. */
	WindowHandles() ([]string, error)
	/* Current url. */
	CurrentURL() (string, error)
	/* Page title. */
	Title() (string, error)
	/* Get page source. */
	PageSource() (string, error)
	/* Close current window. */
	Close() error
	/* Switch to frame, frame parameter can be name or id. */
	SwitchFrame(frame string) error
	/* Swtich to window. */
	SwitchWindow(name string) error
	/* Close window. */
	CloseWindow(name string) error
	/* Get window size */
	WindowSize(name string) (*Size, error)
	/* Get window position */
	WindowPosition(name string) (*Point, error)

	// ResizeWindow resizes the named window.
	ResizeWindow(name string, to Size) error

	// Navigation
	/* Open url. */
	Get(url string) error
	/* Move forward in history. */
	Forward() error
	/* Move backward in history. */
	Back() error
	/* Refresh page. */
	Refresh() error

	// Finding element(s)
	/* Find, return one element. */
	FindElement(by, value string) (WebElement, error)
	/* Find, return list of elements. */
	FindElements(by, value string) ([]WebElement, error)
	/* Current active element. */
	ActiveElement() (WebElement, error)

	// Shortcut for FindElement(ByCSSSelector, sel)
	Q(sel string) (WebElement, error)
	// Shortcut for FindElements(ByCSSSelector, sel)
	QAll(sel string) ([]WebElement, error)

	// Cookies
	/* Get all cookies */
	GetCookies() ([]Cookie, error)
	/* Add a cookies */
	AddCookie(cookie *Cookie) error
	/* Delete all cookies */
	DeleteAllCookies() error
	/* Delete a cookie */
	DeleteCookie(name string) error

	// Mouse
	/* Click mouse button, button should be on of RightButton, MiddleButton or
	LeftButton.
	*/
	Click(button int) error
	/* Dobule click */
	DoubleClick() error
	/* Mouse button down */
	ButtonDown() error
	/* Mouse button up */
	ButtonUp() error

	// Misc
	/* Send modifier key to active element.
	modifier can be one of ShiftKey, ControlKey, AltKey, MetaKey.
	*/
	SendModifier(modifier string, isDown bool) error
	Screenshot() ([]byte, error)

	// Alerts
	/* Dismiss current alert. */
	DismissAlert() error
	/* Accept current alert. */
	AcceptAlert() error
	/* Current alert text. */
	AlertText() (string, error)
	/* Set current alert text. */
	SetAlertText(text string) error

	// Scripts
	/* Execute a script. */
	ExecuteScript(script string, args []interface{}) (interface{}, error)
	/* Execute a script async. */
	ExecuteScriptAsync(script string, args []interface{}) (interface{}, error)

	// Get a WebDriverT of this element that has methods that call t.Fatalf upon
	// encountering errors instead of using multiple returns to indicate errors.
	// The argument t is typically a *testing.T, but here it's a similar
	// interface to avoid needing to import "testing" (which registers global
	// command-line flags).
	T(t TestingT) WebDriverT
}

type WebElement interface {
	// Manipulation

	/* Click on element */
	Click() error
	/* Send keys (type) into element */
	SendKeys(keys string) error
	/* Submit */
	Submit() error
	/* Clear */
	Clear() error
	/* Move mouse to relative coordinates */
	MoveTo(xOffset, yOffset int) error

	// Finding

	/* Find children, return one element. */
	FindElement(by, value string) (WebElement, error)
	/* Find children, return list of elements. */
	FindElements(by, value string) ([]WebElement, error)

	// Shortcut for FindElement(ByCSSSelector, sel)
	Q(sel string) (WebElement, error)
	// Shortcut for FindElements(ByCSSSelector, sel)
	QAll(sel string) ([]WebElement, error)

	// Porperties

	/* Element name */
	TagName() (string, error)
	/* Text of element */
	Text() (string, error)
	/* Check if element is selected. */
	IsSelected() (bool, error)
	/* Check if element is enabled. */
	IsEnabled() (bool, error)
	/* Check if element is displayed. */
	IsDisplayed() (bool, error)
	/* Get element attribute. */
	GetAttribute(name string) (string, error)
	/* Element location. */
	Location() (*Point, error)
	/* Element location once it has been scrolled into view.
	   Note: This is considered an internal command and should only be used to determine an element's location for correctly generating native events.*/
	LocationInView() (*Point, error)
	/* Element size */
	Size() (*Size, error)
	/* Get element CSS property value. */
	CSSProperty(name string) (string, error)

	// Get a WebElementT of this element that has methods that call t.Fatalf
	// upon encountering errors instead of using multiple returns to indicate
	// errors. The argument t is typically a *testing.T, but here it's a similar
	// interface to avoid needing to import "testing" (which registers global
	// command-line flags).
	T(t TestingT) WebElementT
}

type TestingT interface {
	Fatalf(fmt string, v ...interface{})
}
<commit_msg>document TestingT<commit_after>package selenium

/* Element finding options */
const (
	ById              = "id"
	ByXPATH           = "xpath"
	ByLinkText        = "link text"
	ByPartialLinkText = "partial link text"
	ByName            = "name"
	ByTagName         = "tag name"
	ByClassName       = "class name"
	ByCSSSelector     = "css selector"
)

/* Mouse buttons */
const (
	LeftButton = iota
	MiddleButton
	RightButton
)

/* Keys */
const (
	NullKey       = string('\ue000')
	CancelKey     = string('\ue001')
	HelpKey       = string('\ue002')
	BackspaceKey  = string('\ue003')
	TabKey        = string('\ue004')
	ClearKey      = string('\ue005')
	ReturnKey     = string('\ue006')
	EnterKey      = string('\ue007')
	ShiftKey      = string('\ue008')
	ControlKey    = string('\ue009')
	AltKey        = string('\ue00a')
	PauseKey      = string('\ue00b')
	EscapeKey     = string('\ue00c')
	SpaceKey      = string('\ue00d')
	PageUpKey     = string('\ue00e')
	PageDownKey   = string('\ue00f')
	EndKey        = string('\ue010')
	HomeKey       = string('\ue011')
	LeftArrowKey  = string('\ue012')
	UpArrowKey    = string('\ue013')
	RightArrowKey = string('\ue014')
	DownArrowKey  = string('\ue015')
	InsertKey     = string('\ue016')
	DeleteKey     = string('\ue017')
	SemicolonKey  = string('\ue018')
	EqualsKey     = string('\ue019')
	Numpad0Key    = string('\ue01a')
	Numpad1Key    = string('\ue01b')
	Numpad2Key    = string('\ue01c')
	Numpad3Key    = string('\ue01d')
	Numpad4Key    = string('\ue01e')
	Numpad5Key    = string('\ue01f')
	Numpad6Key    = string('\ue020')
	Numpad7Key    = string('\ue021')
	Numpad8Key    = string('\ue022')
	Numpad9Key    = string('\ue023')
	MultiplyKey   = string('\ue024')
	AddKey        = string('\ue025')
	SeparatorKey  = string('\ue026')
	SubstractKey  = string('\ue027')
	DecimalKey    = string('\ue028')
	DivideKey     = string('\ue029')
	F1Key         = string('\ue031')
	F2Key         = string('\ue032')
	F3Key         = string('\ue033')
	F4Key         = string('\ue034')
	F5Key         = string('\ue035')
	F6Key         = string('\ue036')
	F7Key         = string('\ue037')
	F8Key         = string('\ue038')
	F9Key         = string('\ue039')
	F10Key        = string('\ue03a')
	F11Key        = string('\ue03b')
	F12Key        = string('\ue03c')
	MetaKey       = string('\ue03d')
)

/* Browser capabilities, see
http://code.google.com/p/selenium/wiki/JsonWireProtocol#Capabilities_JSON_Object
*/
type Capabilities map[string]interface{}

/* Build object, part of Status return. */
type Build struct {
	Version, Revision, Time string
}

/* OS object, part of Status return. */
type OS struct {
	Arch, Name, Version string
}

/* Information retured by Status method. */
type Status struct {
	Build `json:"build"`
	OS    `json:"os"`
}

/* Point */
type Point struct {
	X, Y float64
}

/* Size */
type Size struct {
	Width  float64 `json:"width"`
	Height float64 `json:"height"`
}

/* Cookie */
type Cookie struct {
	Name   string `json:"name"`
	Value  string `json:"value"`
	Path   string `json:"path"`
	Domain string `json:"domain"`
	Secure bool   `json:"secure"`
	Expiry uint   `json:"expiry"`
}

type WebDriver interface {
	/* Status (info) on server */
	Status() (*Status, error)

	/* Start a new session, return session id */
	NewSession() (string, error)

	/* Current session capabilities */
	Capabilities() (Capabilities, error)
	/* Set the amount of time, in milliseconds, that asynchronous scripts are permitted to run before they are aborted. */
	SetAsyncScriptTimeout(ms uint) error
	/* Set the amount of time, in milliseconds, the driver should wait when searching for elements. */
	SetImplicitWaitTimeout(ms uint) error

	// IME
	/* List all available engines on the machine. */
	AvailableEngines() ([]string, error)
	/* Get the name of the active IME engine. */
	ActiveEngine() (string, error)
	/* Indicates whether IME input is active at the moment. */
	IsEngineActivated() (bool, error)
	/* De-activates the currently-active IME engine. */
	DeactivateEngine() error
	/* Make an engines active */
	ActivateEngine(engine string) error

	/* Quit (end) current session */
	Quit() error

	// Page information and manipulation
	/* Return id of current window handle. */
	CurrentWindowHandle() (string, error)
	/* Return ids of current open windows. */
	WindowHandles() ([]string, error)
	/* Current url. */
	CurrentURL() (string, error)
	/* Page title. */
	Title() (string, error)
	/* Get page source. */
	PageSource() (string, error)
	/* Close current window. */
	Close() error
	/* Switch to frame, frame parameter can be name or id. */
	SwitchFrame(frame string) error
	/* Swtich to window. */
	SwitchWindow(name string) error
	/* Close window. */
	CloseWindow(name string) error
	/* Get window size */
	WindowSize(name string) (*Size, error)
	/* Get window position */
	WindowPosition(name string) (*Point, error)

	// ResizeWindow resizes the named window.
	ResizeWindow(name string, to Size) error

	// Navigation
	/* Open url. */
	Get(url string) error
	/* Move forward in history. */
	Forward() error
	/* Move backward in history. */
	Back() error
	/* Refresh page. */
	Refresh() error

	// Finding element(s)
	/* Find, return one element. */
	FindElement(by, value string) (WebElement, error)
	/* Find, return list of elements. */
	FindElements(by, value string) ([]WebElement, error)
	/* Current active element. */
	ActiveElement() (WebElement, error)

	// Shortcut for FindElement(ByCSSSelector, sel)
	Q(sel string) (WebElement, error)
	// Shortcut for FindElements(ByCSSSelector, sel)
	QAll(sel string) ([]WebElement, error)

	// Cookies
	/* Get all cookies */
	GetCookies() ([]Cookie, error)
	/* Add a cookies */
	AddCookie(cookie *Cookie) error
	/* Delete all cookies */
	DeleteAllCookies() error
	/* Delete a cookie */
	DeleteCookie(name string) error

	// Mouse
	/* Click mouse button, button should be on of RightButton, MiddleButton or
	LeftButton.
	*/
	Click(button int) error
	/* Dobule click */
	DoubleClick() error
	/* Mouse button down */
	ButtonDown() error
	/* Mouse button up */
	ButtonUp() error

	// Misc
	/* Send modifier key to active element.
	modifier can be one of ShiftKey, ControlKey, AltKey, MetaKey.
	*/
	SendModifier(modifier string, isDown bool) error
	Screenshot() ([]byte, error)

	// Alerts
	/* Dismiss current alert. */
	DismissAlert() error
	/* Accept current alert. */
	AcceptAlert() error
	/* Current alert text. */
	AlertText() (string, error)
	/* Set current alert text. */
	SetAlertText(text string) error

	// Scripts
	/* Execute a script. */
	ExecuteScript(script string, args []interface{}) (interface{}, error)
	/* Execute a script async. */
	ExecuteScriptAsync(script string, args []interface{}) (interface{}, error)

	// Get a WebDriverT of this element that has methods that call t.Fatalf upon
	// encountering errors instead of using multiple returns to indicate errors.
	// The argument t is typically a *testing.T, but here it's a similar
	// interface to avoid needing to import "testing" (which registers global
	// command-line flags).
	T(t TestingT) WebDriverT
}

type WebElement interface {
	// Manipulation

	/* Click on element */
	Click() error
	/* Send keys (type) into element */
	SendKeys(keys string) error
	/* Submit */
	Submit() error
	/* Clear */
	Clear() error
	/* Move mouse to relative coordinates */
	MoveTo(xOffset, yOffset int) error

	// Finding

	/* Find children, return one element. */
	FindElement(by, value string) (WebElement, error)
	/* Find children, return list of elements. */
	FindElements(by, value string) ([]WebElement, error)

	// Shortcut for FindElement(ByCSSSelector, sel)
	Q(sel string) (WebElement, error)
	// Shortcut for FindElements(ByCSSSelector, sel)
	QAll(sel string) ([]WebElement, error)

	// Porperties

	/* Element name */
	TagName() (string, error)
	/* Text of element */
	Text() (string, error)
	/* Check if element is selected. */
	IsSelected() (bool, error)
	/* Check if element is enabled. */
	IsEnabled() (bool, error)
	/* Check if element is displayed. */
	IsDisplayed() (bool, error)
	/* Get element attribute. */
	GetAttribute(name string) (string, error)
	/* Element location. */
	Location() (*Point, error)
	/* Element location once it has been scrolled into view.
	   Note: This is considered an internal command and should only be used to determine an element's location for correctly generating native events.*/
	LocationInView() (*Point, error)
	/* Element size */
	Size() (*Size, error)
	/* Get element CSS property value. */
	CSSProperty(name string) (string, error)

	// Get a WebElementT of this element that has methods that call t.Fatalf
	// upon encountering errors instead of using multiple returns to indicate
	// errors. The argument t is typically a *testing.T, but here it's a similar
	// interface to avoid needing to import "testing" (which registers global
	// command-line flags).
	T(t TestingT) WebElementT
}

// TestingT is a subset of the testing.T interface (to avoid needing
// to import "testing", which registers global command-line flags).
type TestingT interface {
	Fatalf(fmt string, v ...interface{})
}
<|endoftext|>


<commit_before>/*
	Package seo4ajax provides a library for accessing the SEO4Ajax prerender service.
	Before using, you need to set ServerIp to a valid IP address.
*/
package seo4ajax

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"

	"github.com/cenkalti/backoff"
	"github.com/go-kit/kit/log"
)

var (
	// ErrNoToken is returned when the client isn't provided a API token
	ErrNoToken = errors.New("no token given")
	// seo4ajax responded with a cache miss
	ErrCacheMiss     = errors.New("cache miss from seo4ajax")
	ErrUnknownStatus = errors.New("Unkonwn Status Code")
	errRedirect      = errors.New("SEO4AJAX: do not follow redirect")

	regexInvalidUserAgent = regexp.MustCompile(`(?i:google.*bot|bing|msnbot|yandexbot|pinterest.*ios|mail\.ru)`)
	regexValidUserAgent   = regexp.MustCompile(`(?i:bot|crawler|spider|archiver|pinterest|facebookexternalhit|flipboardproxy)`)
	regexPath             = regexp.MustCompile(`.*(\.[^?]{2,4}$|\.[^?]{2,4}?.*)`)
)

// Config is the Seo4Ajax Client config
type Config struct {
	Log       log.Logger
	Next      http.Handler
	Transport http.RoundTripper
	Server    string        // seo4ajax api server, defaults to http://api.seo4ajax.com
	Token     string        // seo4ajax token, must be set
	IP        string        // server IP, defaults to 127.0.0.1
	Timeout   time.Duration // retry timeout, defaults to 30s
	// s4a supports client side caching and returns an empty 304 if the content hasn't changed.
	// If UnconditionalFetch set to true the client side caching headers (If-Modified-Since and If-None-Match)
	// are removed
	UnconditionalFetch bool
}

// Client is the Seo4Ajax Client
type Client struct {
	log                log.Logger
	next               http.Handler
	server             string
	token              string
	ip                 string
	timeout            time.Duration
	http               *http.Client
	unconditionalFetch bool
}

// New creates a new Seo4Ajax client. Returns an error if no token is provided
func New(cfg Config) (*Client, error) {
	if cfg.Log == nil {
		cfg.Log = log.NewNopLogger()
	}
	if cfg.Server == "" {
		cfg.Server = "http://api.seo4ajax.com"
	}
	if cfg.Token == "" {
		return nil, ErrNoToken
	}
	if cfg.IP == "" {
		cfg.IP = "127.0.0.1"
	}
	if cfg.Timeout < time.Second {
		cfg.Timeout = 30 * time.Second
	}
	if cfg.Transport == nil {
		cfg.Transport = http.DefaultTransport
	}

	c := &Client{
		log:                cfg.Log,
		server:             cfg.Server,
		token:              cfg.Token,
		ip:                 cfg.IP,
		timeout:            cfg.Timeout,
		next:               cfg.Next,
		unconditionalFetch: cfg.UnconditionalFetch,
	}
	c.http = &http.Client{
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return errRedirect
		},
		Transport: cfg.Transport,
	}
	return c, nil
}

// IsPrerender returns true, when Seo4Ajax shall be used for the given http Request.
// The logic is taken from https://github.com/seo4ajax/connect-s4a/blob/master/lib/connect-s4a.js
func IsPrerender(r *http.Request) bool {
	if r.Method != "GET" && r.Method != "HEAD" {
		return false
	}

	if strings.Contains(r.URL.RawQuery, "_escaped_fragment_") {
		return true
	}

	if regexInvalidUserAgent.MatchString(r.Header.Get("User-Agent")) {
		return false
	}

	if regexPath.MatchString(r.URL.Path) {
		return false
	}

	return regexValidUserAgent.MatchString(r.Header.Get("User-Agent"))
}

// ServeHTTP will serve the prerendered page if this is a prerender request.
// If no upstream handler is set it will return an error. Otherwise it will
// just invoke the upstream handler. This way it can be either used as an
// HTTP middleware intercepting any prerender requests or an regular HTTP
// handler (if next is nil) to serve only prerender request
func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if IsPrerender(r) {
		c.GetPrerenderedPage(w, r)
		return
	}

	if c.next == nil {
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	c.next.ServeHTTP(w, r)
	return
}

// GetPrerenderedPage returns the prerendered html from the seo4ajax api
func (c *Client) GetPrerenderedPage(w http.ResponseWriter, r *http.Request) {
	var outputStarted bool
	opFunc := func() error {
		req, err := http.NewRequest("GET", fmt.Sprintf("%s/%s%s", c.server, c.token, cleanPath(r.URL)), nil)
		if err != nil {
			return err
		}

		req.Header = r.Header
		ips := []string{c.ip}
		if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
			ips = append(ips, xff)
		}
		req.Header.Set("X-Forwarded-For", strings.Join(ips, ", "))

		if c.unconditionalFetch {
			req.Header.Del("If-Modified-Since")
			req.Header.Del("If-None-Match")
		}

		resp, err := c.http.Do(req)
		if err != nil && !strings.HasSuffix(err.Error(), errRedirect.Error()) {
			return err
		}
		defer resp.Body.Close()

		if resp.StatusCode == http.StatusFound {
			http.Redirect(w, r, resp.Header.Get("Location"), http.StatusFound)
			return nil
		}

		for header, val := range resp.Header {
			w.Header()[header] = val
		}

		outputStarted = true
		// as soon as we start writing the body we must return nil, otherwise we'll
		// mess up the HTTP response by calling response.WriteHeader multiple times
		_, err = io.Copy(w, resp.Body)
		if err == nil {
			w.WriteHeader(resp.StatusCode)
		}
		return err
	}

	bo := backoff.NewExponentialBackOff()
	bo.InitialInterval = 50 * time.Millisecond
	bo.MaxInterval = 30 * time.Second
	bo.MaxElapsedTime = c.timeout
	err := backoff.RetryNotify(opFunc, bo, func(e error, duration time.Duration) {
		c.log.Log("level", "warn", "msg", "Upstream request failed, retrying ...", "err", e)
	})
	if err != nil {
		c.log.Log("level", "warn", "msg", "Upstream request finally failed", "err", err)
		if !outputStarted {
			http.Error(w, "Upstream error", http.StatusInternalServerError)
			return
		}
	}
	return
}

func cleanPath(u *url.URL) string {
	cpy := *u
	if len(cpy.Path) == 0 {
		cpy.Path = "/"
	} else if cpy.Path[0] != '/' {
		cpy.Path = "/" + cpy.Path
	}

	cpy.Scheme = ""
	cpy.Opaque = ""
	cpy.User = nil
	cpy.Host = ""

	return cpy.String()
}
<commit_msg>Revert "pass s4a request response status code to ServeHTTP response writer"<commit_after>/*
	Package seo4ajax provides a library for accessing the SEO4Ajax prerender service.
	Before using, you need to set ServerIp to a valid IP address.
*/
package seo4ajax

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"

	"github.com/cenkalti/backoff"
	"github.com/go-kit/kit/log"
)

var (
	// ErrNoToken is returned when the client isn't provided a API token
	ErrNoToken = errors.New("no token given")
	// seo4ajax responded with a cache miss
	ErrCacheMiss     = errors.New("cache miss from seo4ajax")
	ErrUnknownStatus = errors.New("Unkonwn Status Code")
	errRedirect      = errors.New("SEO4AJAX: do not follow redirect")

	regexInvalidUserAgent = regexp.MustCompile(`(?i:google.*bot|bing|msnbot|yandexbot|pinterest.*ios|mail\.ru)`)
	regexValidUserAgent   = regexp.MustCompile(`(?i:bot|crawler|spider|archiver|pinterest|facebookexternalhit|flipboardproxy)`)
	regexPath             = regexp.MustCompile(`.*(\.[^?]{2,4}$|\.[^?]{2,4}?.*)`)
)

// Config is the Seo4Ajax Client config
type Config struct {
	Log       log.Logger
	Next      http.Handler
	Transport http.RoundTripper
	Server    string        // seo4ajax api server, defaults to http://api.seo4ajax.com
	Token     string        // seo4ajax token, must be set
	IP        string        // server IP, defaults to 127.0.0.1
	Timeout   time.Duration // retry timeout, defaults to 30s
	// s4a supports client side caching and returns an empty 304 if the content hasn't changed.
	// If UnconditionalFetch set to true the client side caching headers (If-Modified-Since and If-None-Match)
	// are removed
	UnconditionalFetch bool
}

// Client is the Seo4Ajax Client
type Client struct {
	log                log.Logger
	next               http.Handler
	server             string
	token              string
	ip                 string
	timeout            time.Duration
	http               *http.Client
	unconditionalFetch bool
}

// New creates a new Seo4Ajax client. Returns an error if no token is provided
func New(cfg Config) (*Client, error) {
	if cfg.Log == nil {
		cfg.Log = log.NewNopLogger()
	}
	if cfg.Server == "" {
		cfg.Server = "http://api.seo4ajax.com"
	}
	if cfg.Token == "" {
		return nil, ErrNoToken
	}
	if cfg.IP == "" {
		cfg.IP = "127.0.0.1"
	}
	if cfg.Timeout < time.Second {
		cfg.Timeout = 30 * time.Second
	}
	if cfg.Transport == nil {
		cfg.Transport = http.DefaultTransport
	}

	c := &Client{
		log:                cfg.Log,
		server:             cfg.Server,
		token:              cfg.Token,
		ip:                 cfg.IP,
		timeout:            cfg.Timeout,
		next:               cfg.Next,
		unconditionalFetch: cfg.UnconditionalFetch,
	}
	c.http = &http.Client{
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return errRedirect
		},
		Transport: cfg.Transport,
	}
	return c, nil
}

// IsPrerender returns true, when Seo4Ajax shall be used for the given http Request.
// The logic is taken from https://github.com/seo4ajax/connect-s4a/blob/master/lib/connect-s4a.js
func IsPrerender(r *http.Request) bool {
	if r.Method != "GET" && r.Method != "HEAD" {
		return false
	}

	if strings.Contains(r.URL.RawQuery, "_escaped_fragment_") {
		return true
	}

	if regexInvalidUserAgent.MatchString(r.Header.Get("User-Agent")) {
		return false
	}

	if regexPath.MatchString(r.URL.Path) {
		return false
	}

	return regexValidUserAgent.MatchString(r.Header.Get("User-Agent"))
}

// ServeHTTP will serve the prerendered page if this is a prerender request.
// If no upstream handler is set it will return an error. Otherwise it will
// just invoke the upstream handler. This way it can be either used as an
// HTTP middleware intercepting any prerender requests or an regular HTTP
// handler (if next is nil) to serve only prerender request
func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if IsPrerender(r) {
		c.GetPrerenderedPage(w, r)
		return
	}

	if c.next == nil {
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	c.next.ServeHTTP(w, r)
	return
}

// GetPrerenderedPage returns the prerendered html from the seo4ajax api
func (c *Client) GetPrerenderedPage(w http.ResponseWriter, r *http.Request) {
	var outputStarted bool
	opFunc := func() error {
		req, err := http.NewRequest("GET", fmt.Sprintf("%s/%s%s", c.server, c.token, cleanPath(r.URL)), nil)
		if err != nil {
			return err
		}

		req.Header = r.Header
		ips := []string{c.ip}
		if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
			ips = append(ips, xff)
		}
		req.Header.Set("X-Forwarded-For", strings.Join(ips, ", "))

		if c.unconditionalFetch {
			req.Header.Del("If-Modified-Since")
			req.Header.Del("If-None-Match")
		}

		resp, err := c.http.Do(req)
		if err != nil && !strings.HasSuffix(err.Error(), errRedirect.Error()) {
			return err
		}
		defer resp.Body.Close()

		if resp.StatusCode == http.StatusFound {
			http.Redirect(w, r, resp.Header.Get("Location"), http.StatusFound)
			return nil
		}

		for header, val := range resp.Header {
			w.Header()[header] = val
		}

		outputStarted = true
		// as soon as we start writing the body we must return nil, otherwise we'll
		// mess up the HTTP response by calling response.WriteHeader multiple times
		_, err = io.Copy(w, resp.Body)
		return err
	}

	bo := backoff.NewExponentialBackOff()
	bo.InitialInterval = 50 * time.Millisecond
	bo.MaxInterval = 30 * time.Second
	bo.MaxElapsedTime = c.timeout
	err := backoff.Retry(opFunc, bo)
	if err != nil {
		c.log.Log("level", "warn", "msg", "Upstream request failed", "err", err)
		if !outputStarted {
			http.Error(w, "Upstream error", http.StatusInternalServerError)
			return
		}
	}
	return
}

func cleanPath(u *url.URL) string {
	cpy := *u
	if len(cpy.Path) == 0 {
		cpy.Path = "/"
	} else if cpy.Path[0] != '/' {
		cpy.Path = "/" + cpy.Path
	}

	cpy.Scheme = ""
	cpy.Opaque = ""
	cpy.User = nil
	cpy.Host = ""

	return cpy.String()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"

	"github.com/kataras/iris"

	"github.com/kataras/iris/mvc"
)

func main() {
	app := iris.New()
	app.Logger().SetLevel("debug")

	mvcApp := mvc.New(app)
	// To all controllers, it can optionally be overridden per-controller
	// if the controller contains the `HandleError(ctx iris.Context, err error)` function.
	//
	mvcApp.HandleError(func(ctx iris.Context, err error) {
		ctx.HTML(fmt.Sprintf("%s", err.Error()))
	})
	//
	mvcApp.Handle(new(myController))

	// http://localhost:8080
	app.Run(iris.Addr(":8080"))
}

func basicMVC(app *mvc.Application) {
	// GET: http://localhost:8080
	app.Handle(new(myController))
}

type myController struct {
}

// overriddes the mvcApp.HandleError function.
// func (c *myController) HandleError(ctx iris.Context, err error) {
// 	ctx.HTML(fmt.Sprintf("%s", err.Error()))
// }

func (c *myController) Get() error {
	return fmt.Errorf("error here")
}
<commit_msg>clean up the mvc error handler example<commit_after>package main

import (
	"fmt"

	"github.com/kataras/iris"

	"github.com/kataras/iris/mvc"
)

func main() {
	app := iris.New()
	app.Logger().SetLevel("debug")

	mvcApp := mvc.New(app)
	// To all controllers, it can optionally be overridden per-controller
	// if the controller contains the `HandleError(ctx iris.Context, err error)` function.
	//
	mvcApp.HandleError(func(ctx iris.Context, err error) {
		ctx.HTML(fmt.Sprintf("%s", err.Error()))
	})
	//
	mvcApp.Handle(new(myController))

	// http://localhost:8080
	app.Run(iris.Addr(":8080"))
}

type myController struct {
}

// overriddes the mvcApp.HandleError function.
func (c *myController) HandleError(ctx iris.Context, err error) {
	ctx.HTML(fmt.Sprintf("%s", err.Error()))
}

func (c *myController) Get() error {
	return fmt.Errorf("error here")
}
<|endoftext|>


<commit_before>package sessions

import (
	"github.com/plimble/utils/pool"
	"github.com/tinylib/msgp/msgp"

	"net/http"
)

type Sessions struct {
	req      *http.Request
	store    Store
	options  *Options
	sessions map[string]*Session
	bufPool  *pool.BufferPool
}

func (s *Sessions) Get(name string) (*Session, error) {
	s.sessions[name] = newSession(name, s.options)

	c, err := s.req.Cookie(name)
	if err != nil {
		s.sessions[name].Values = make(map[string]interface{})
		return s.sessions[name], err
	}

	buf := s.bufPool.Get()
	defer s.bufPool.Put(buf)

	if err := s.store.Get(c.Value, buf); err != nil {
		s.sessions[name].Values = make(map[string]interface{})
		return s.sessions[name], err
	}

	s.sessions[name].DecodeMsg(msgp.NewReader(buf))
	s.sessions[name].IsNew = false

	return s.sessions[name], nil
}

func (s *Sessions) Save(w http.ResponseWriter) error {
	var err error
	var b []byte
	buf := s.bufPool.Get()
	defer s.bufPool.Put(buf)
	for _, session := range s.sessions {
		if !session.isWriten {
			continue
		}

		session.ID = generateUUID()

		b, err = session.MarshalMsg(nil)
		if err != nil {
			return err
		}

		buf.Write(b)

		if err = s.store.Save(session, buf, w); err != nil {
			return err
		}

		buf.Reset()
	}

	return nil
}
<commit_msg>if exist use cache<commit_after>package sessions

import (
	"github.com/plimble/utils/pool"
	"github.com/tinylib/msgp/msgp"

	"net/http"
)

type Sessions struct {
	req      *http.Request
	store    Store
	options  *Options
	sessions map[string]*Session
	bufPool  *pool.BufferPool
}

func (s *Sessions) Get(name string) (*Session, error) {
	if session, ok := s.sessions[name]; ok {
		return session, nil
	}

	s.sessions[name] = newSession(name, s.options)

	c, err := s.req.Cookie(name)
	if err != nil {
		s.sessions[name].Values = make(map[string]interface{})
		return s.sessions[name], err
	}

	buf := s.bufPool.Get()
	defer s.bufPool.Put(buf)

	if err := s.store.Get(c.Value, buf); err != nil {
		s.sessions[name].Values = make(map[string]interface{})
		return s.sessions[name], err
	}

	s.sessions[name].DecodeMsg(msgp.NewReader(buf))
	s.sessions[name].IsNew = false

	return s.sessions[name], nil
}

func (s *Sessions) Save(w http.ResponseWriter) error {
	var err error
	var b []byte
	buf := s.bufPool.Get()
	defer s.bufPool.Put(buf)
	for _, session := range s.sessions {
		if !session.isWriten {
			continue
		}

		session.ID = generateUUID()

		b, err = session.MarshalMsg(nil)
		if err != nil {
			return err
		}

		buf.Write(b)

		if err = s.store.Save(session, buf, w); err != nil {
			return err
		}

		buf.Reset()
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net/http"
	"os"
	"runtime"

	"github.com/gorilla/websocket"
	sm "github.com/tchap/go-statemachine"
)

type ErrMsg struct {
	Err string `json:"error"`
}

type DeskMsg struct {
	Desk [][]string `json:"desk"`
}

type PlayerMsg struct {
	Cmd  sm.EventType `json:"command"`
	Card string       `json:"card"`
}

//

const (
	cmdStart sm.EventType = iota
	cmdMove

	cmdCount
)

const (
	stateCollection sm.State = iota
	stateDistribution
	stateGame

	stateCount
)

func stateToString(s sm.State) string {
	return [...]string{
		stateCollection:   "COLLECTION",
		stateDistribution: "DISTRIBUTION",
		stateGame:         "GAME",
	}[s]
}

func cmdToString(t sm.EventType) string {
	return [...]string{
		cmdStart: "START",
		cmdMove:  "MOVE",
	}[t]
}

//

type cmdArgs struct {
	conn *websocket.Conn
	card string
}

//

type gameState struct {
	// player that should make the move now
	p *websocket.Conn

	// card that should be beaten
	card string

	trump string

	sm *sm.StateMachine
}

func NewGameState() *gameState {
	gst := new(gameState)

	gst.sm = sm.New(stateGame, uint(stateCount), uint(cmdCount))

	gst.sm.On(cmdMove,
		[]sm.State{stateGame},
		gst.handleMove)

	return gst
}

var GSt *gameState = NewGameState()

// event handlers

func (self *gameState) handleMove(s sm.State, e *sm.Event) sm.State {
	conn := e.Data.(cmdArgs).conn
	card := e.Data.(cmdArgs).card

	if conn != self.p {
		log.Printf("it's %v's turn to make a move", self.p)
		return s
	}

	log.Println(card)

	return s
}

//

type durakSrv struct {
	conn *websocket.Conn
	gst  *gameState
}

func (self *durakSrv) read() {
	var m PlayerMsg

	for {
		err := self.conn.ReadJSON(&m)
		if err != nil {
			log.Println(err)
			return
		}

		switch m.Cmd {
		case cmdStart:
		case cmdMove:
			event := &sm.Event{cmdMove, cmdArgs{self.conn, m.Card}}

			err = self.gst.sm.Emit(event)
			if err != nil {
				log.Println(err)
				return
			}
		}
	}
}

//

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

func durakHandler(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}
	defer conn.Close() //dbg

	d := durakSrv{conn, GSt}

	d.read()
}

//

func main() {
	http.HandleFunc("/", durakHandler)

	log.Fatal(http.ListenAndServe(":"+os.Getenv("PORT"), nil))
}

func init() {
	runtime.GOMAXPROCS(runtime.NumCPU())
}
<commit_msg>added 'higher' function<commit_after>package main

import (
	"log"
	"net/http"
	"os"
	"regexp"
	"runtime"

	"github.com/gorilla/websocket"
	sm "github.com/tchap/go-statemachine"
)

type ErrMsg struct {
	Err string `json:"error"`
}

type DeskMsg struct {
	Desk [][]string `json:"desk"`
}

type PlayerMsg struct {
	Cmd  sm.EventType `json:"command"`
	Card string       `json:"card"`
}

//

var Order map[string]int = map[string]int{
	"6": 6, "7": 7, "8": 8, "9": 9, "10": 10,
	"J": 11,
	"Q": 12,
	"K": 13,
	"A": 14,
}

func higher(c0, c1, t string) int {
	// c0 and c1 have the same suit
	if c0[0] == c1[0] {
		if Order[c0[1:]] > Order[c1[1:]] {
			return 1
		}
		if Order[c0[1:]] < Order[c1[1:]] {
			return -1
		}
		return 0
	}
	// c0 is trump, c1 is not
	if c0[:1] == t {
		return 1
	}
	// c1 is trump, c0 is not
	if c1[:1] == t {
		return -1
	}
	// suits are different, both are not trump
	return -2
}

var CardRE *regexp.Regexp = regexp.MustCompile(`[SCHD]([6-9JQKA]|10)`)

func isValid(c string) bool {
	return CardRE.MatchString(c)
}

//

const (
	cmdStart sm.EventType = iota
	cmdMove

	cmdCount
)

const (
	stateCollection sm.State = iota
	stateDistribution
	stateGame

	stateCount
)

func stateToString(s sm.State) string {
	return [...]string{
		stateCollection:   "COLLECTION",
		stateDistribution: "DISTRIBUTION",
		stateGame:         "GAME",
	}[s]
}

func cmdToString(t sm.EventType) string {
	return [...]string{
		cmdStart: "START",
		cmdMove:  "MOVE",
	}[t]
}

//

type cmdArgs struct {
	conn *websocket.Conn
	card string
}

//

type gameState struct {
	// player that should make the move now
	p *websocket.Conn

	// card that should be beaten
	card string

	trump string

	sm *sm.StateMachine
}

func NewGameState() *gameState {
	gst := new(gameState)

	gst.sm = sm.New(stateGame, uint(stateCount), uint(cmdCount))

	gst.sm.On(cmdMove,
		[]sm.State{stateGame},
		gst.handleMove)

	return gst
}

var GSt *gameState = NewGameState()

// event handlers

func (self *gameState) handleMove(s sm.State, e *sm.Event) sm.State {
	conn := e.Data.(cmdArgs).conn
	card := e.Data.(cmdArgs).card

	if conn != self.p {
		log.Printf("it's %v's turn to make a move", self.p)
		return s
	}

	log.Println(card)

	return s
}

//

type durakSrv struct {
	conn *websocket.Conn
	gst  *gameState
}

func (self *durakSrv) read() {
	var m PlayerMsg

	for {
		err := self.conn.ReadJSON(&m)
		if err != nil {
			log.Println(err)
			return
		}

		switch m.Cmd {
		case cmdStart:
		case cmdMove:
			event := &sm.Event{cmdMove, cmdArgs{self.conn, m.Card}}

			err = self.gst.sm.Emit(event)
			if err != nil {
				log.Println(err)
				return
			}
		}
	}
}

//

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

func durakHandler(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}
	defer conn.Close() //dbg

	d := durakSrv{conn, GSt}

	d.read()
}

//

func main() {
	http.HandleFunc("/", durakHandler)

	log.Fatal(http.ListenAndServe(":"+os.Getenv("PORT"), nil))
}

func init() {
	runtime.GOMAXPROCS(runtime.NumCPU())
}
<|endoftext|>


<commit_before>// sftp帮助类
// create by gloomy 2017-05-09 20:19:24
package gutil

import (
	"errors"
	"github.com/pkg/sftp"
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/agent"
	"net"
	"os"
)

// sftp配置类
// create by gloomy 2017-05-09 20:21:07
type SftpConfigStruct struct {
	Account      string // 登录用户名
	Password     string // 登录密码
	Port         int    // 服务器端口
	ConntionSize int    // MaxPacket sets the maximum size of the payload
	Addr         string // 连接地址
}

// 建立sftp连接
// create by gloomy 2017-05-10 11:32:17
func sftpConntion(sftpModel SftpConfigStruct, sftpClient *sftp.Client, sshClient *ssh.Client) error {
	var (
		auths []ssh.AuthMethod
		err   error
	)
	if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
		auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
	}
	auths = append(auths, ssh.Password(sftpModel.Password))
	configModel := ssh.ClientConfig{
		User:            sftpModel.Account,
		Auth:            auths,
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}
	sshClient, err = ssh.Dial("tcp", sftpModel.Addr, &configModel)
	if err != nil {
		return err
	}
	sftpClient, err = sftp.NewClient(sshClient, sftp.MaxPacket(If(sftpModel.ConntionSize > 0, sftpModel.ConntionSize, 1<<15).(int)))

	return err
}

// sftp 关闭
// create by gloomy 2017-05-10 12:06:05
func SftpClose(sftpClient *sftp.Client, sshClient *ssh.Client) error {
	var (
		errs     string
		boNumber = 0
	)
	err := sftpClient.Close()
	if err != nil {
		errs += errors.New(err.Error())
		boNumber++
	}
	err = sshClient.Close()
	if err != nil {
		errs += errors.New(err.Error())
		return errors.New(errs)
	}
	if boNumber > 0 {
		return errors.New(errs)
	}
	return err
}

// sftp读取文件夹内容
// create by gloomy 2017-05-10 11:50:14
func SftpReadDir(sftpModel SftpConfigStruct, sftpClient *sftp.Client, sshClient *ssh.Client, pathStr string) (*[]os.FileInfo, error) {
	if sftpClient == nil || sshClient == nil {
		err := sftpConntion(sftpModel, sftpClient, sshClient)
		if err != nil {
			return nil, err
		}
	}
	files, err := sftpClient.ReadDir(pathStr)
	if err != nil {
		err := sftpConntion(sftpModel, sftpClient, sshClient)
		if err != nil {
			return nil, err
		}
		files, err = sftpClient.ReadDir(pathStr)
		if err != nil {
			return nil, err
		}
	}
	return &files, err
}
<commit_msg>修正连接地址,防止国内无法下载包<commit_after>// sftp帮助类
// create by gloomy 2017-05-09 20:19:24
package gutil

import (
	"errors"
	"github.com/pkg/sftp"
	"github.com/swgloomy/crypto/ssh"
	"github.com/swgloomy/crypto/ssh/agent"
	"net"
	"os"
)

// sftp配置类
// create by gloomy 2017-05-09 20:21:07
type SftpConfigStruct struct {
	Account      string // 登录用户名
	Password     string // 登录密码
	Port         int    // 服务器端口
	ConntionSize int    // MaxPacket sets the maximum size of the payload
	Addr         string // 连接地址
}

// 建立sftp连接
// create by gloomy 2017-05-10 11:32:17
func sftpConntion(sftpModel SftpConfigStruct, sftpClient *sftp.Client, sshClient *ssh.Client) error {
	var (
		auths []ssh.AuthMethod
		err   error
	)
	if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
		auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
	}
	auths = append(auths, ssh.Password(sftpModel.Password))
	configModel := ssh.ClientConfig{
		User:            sftpModel.Account,
		Auth:            auths,
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}
	sshClient, err = ssh.Dial("tcp", sftpModel.Addr, &configModel)
	if err != nil {
		return err
	}
	sftpClient, err = sftp.NewClient(sshClient, sftp.MaxPacket(If(sftpModel.ConntionSize > 0, sftpModel.ConntionSize, 1<<15).(int)))

	return err
}

// sftp 关闭
// create by gloomy 2017-05-10 12:06:05
func SftpClose(sftpClient *sftp.Client, sshClient *ssh.Client) error {
	var (
		errs     string
		boNumber = 0
	)
	err := sftpClient.Close()
	if err != nil {
		errs += errors.New(err.Error())
		boNumber++
	}
	err = sshClient.Close()
	if err != nil {
		errs += errors.New(err.Error())
		return errors.New(errs)
	}
	if boNumber > 0 {
		return errors.New(errs)
	}
	return err
}

// sftp读取文件夹内容
// create by gloomy 2017-05-10 11:50:14
func SftpReadDir(sftpModel SftpConfigStruct, sftpClient *sftp.Client, sshClient *ssh.Client, pathStr string) (*[]os.FileInfo, error) {
	if sftpClient == nil || sshClient == nil {
		err := sftpConntion(sftpModel, sftpClient, sshClient)
		if err != nil {
			return nil, err
		}
	}
	files, err := sftpClient.ReadDir(pathStr)
	if err != nil {
		err := sftpConntion(sftpModel, sftpClient, sshClient)
		if err != nil {
			return nil, err
		}
		files, err = sftpClient.ReadDir(pathStr)
		if err != nil {
			return nil, err
		}
	}
	return &files, err
}
<|endoftext|>


<commit_before>package redisconn

import (
	"errors"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"strconv"
)

func (r *R) GetInt(k string) (int, error) {
	i, err := r.GetInt64(k)
	return int(i), err
}

func (r *R) GetInt32(k string) (int32, error) {
	i, err := r.GetInt64(k)
	return int32(i), err
}

func (r *R) GetInt64(k string) (int64, error) {

	// return 0 if no record
	rData, err := r.Do("GET", k)
	if err != nil || rData == nil {
		return int64(0), err
	}

	// switch type
	switch intVal := rData.(type) {
	case int64:
		return intVal, nil
	case string:
		return strconv.ParseInt(intVal, 10, 64)
	case []byte:
		return strconv.ParseInt(fmt.Sprintf("%s", string(intVal)), 10, 64)
	case nil:
		return int64(0), nil
	case redis.Error:
		return int64(0), intVal
	default:
		return int64(0), errors.New("redis command return type not unknow")
	}
}

func (r *R) GetString(k string) (str string, err error) {

	// return "" if no record
	rData, err := r.Do("GET", k)
	if err != nil || rData == nil {
		return "", err
	}

	// switch type
	switch strVal := rData.(type) {
	case string:
		return strVal, nil
	case int64:
		return fmt.Sprintf("%d", strVal), nil
	case []byte:
		return string(strVal), nil
	case nil:
		return "", nil
	case redis.Error:
		return "", strVal
	default:
		return "", errors.New("redis command return type not unknow")
	}
}
<commit_msg>Add Get List shortcut<commit_after>package redisconn

import (
	"errors"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"strconv"
)

func (r *R) GetInt(k string) (int, error) {
	i, err := r.GetInt64(k)
	return int(i), err
}

func (r *R) GetInt32(k string) (int32, error) {
	i, err := r.GetInt64(k)
	return int32(i), err
}

func (r *R) GetInt64(k string) (int64, error) {

	// return 0 if no record
	rData, err := r.Do("GET", k)
	if err != nil || rData == nil {
		return int64(0), err
	}

	return convertToInt64(rData)
}

func (r *R) GetString(k string) (str string, err error) {

	// return "" if no record
	rData, err := r.Do("GET", k)
	if err != nil || rData == nil {
		return "", err
	}

	// switch type
	switch strVal := rData.(type) {
	case string:
		return strVal, nil
	case int64:
		return fmt.Sprintf("%d", strVal), nil
	case []byte:
		return string(strVal), nil
	case nil:
		return "", nil
	case redis.Error:
		return "", strVal
	default:
		return "", errors.New("redis command return type not unknow")
	}
}

func (r *R) GetInt64List(k string, start int, stop int) ([]int64, error) {

	ret := []int64{}
	rDatas, err := r.Do("LRANGE", k, start, stop)
	if err != nil || rDatas == nil {
		return ret, err
	}

	if datas, ok := rDatas.([]interface{}); ok {
		for _, data := range datas {
			i, err := convertToInt64(data)
			if err != nil {
				return ret, err
			}
			ret = append(ret, i)
		}
	} else {
		return ret, errors.New("redis command return type unknow")
	}

	return ret, nil
}

func (r *R) GetIntList(k string, start int, stop int) ([]int, error) {

	ret := []int{}
	retInt64, err := r.GetInt64List(k, start, stop)
	if err != nil {
		return ret, err
	}

	for _, i := range retInt64 {
		ret = append(ret, int(i))
	}

	return ret, nil
}

func convertToInt64(val interface{}) (int64, error) {

	// switch type
	switch intVal := val.(type) {
	case int64:
		return intVal, nil
	case string:
		return strconv.ParseInt(intVal, 10, 64)
	case []byte:
		return strconv.ParseInt(fmt.Sprintf("%s", string(intVal)), 10, 64)
	case nil:
		return 0, nil
	case redis.Error:
		return 0, intVal
	default:
		return 0, errors.New("redis command return type unknow")
	}
}
<|endoftext|>