<commit_before>package main

import (
	"encoding/gob"
	"fmt"
	"os"

	"github.com/Symantec/Dominator/lib/json"
	"github.com/Symantec/Dominator/lib/log"
	"github.com/Symantec/Dominator/lib/srpc"
	fm_proto "github.com/Symantec/Dominator/proto/fleetmanager"
	hyper_proto "github.com/Symantec/Dominator/proto/hypervisor"
)

func getUpdatesSubcommand(args []string, logger log.DebugLogger) {
	if err := getUpdates(logger); err != nil {
		fmt.Fprintf(os.Stderr, "Error getting updates: %s\n", err)
		os.Exit(1)
	}
	os.Exit(0)
}

func getUpdates(logger log.DebugLogger) error {
	if *hypervisorHostname != "" {
		return getUpdatesOnHypervisor(
			fmt.Sprintf("%s:%d", *hypervisorHostname, *hypervisorPortNum),
			logger)
	} else if *fleetManagerHostname != "" {
		return getUpdatesOnFleetManager(
			fmt.Sprintf("%s:%d", *fleetManagerHostname, *fleetManagerPortNum),
			logger)
	} else {
		return getUpdatesOnHypervisor(fmt.Sprintf(":%d", *hypervisorPortNum),
			logger)
	}

}

func getUpdatesOnFleetManager(fleetManager string,
	logger log.DebugLogger) error {
	client, err := srpc.DialHTTP("tcp", fleetManager, 0)
	if err != nil {
		return err
	}
	defer client.Close()
	conn, err := client.Call("FleetManager.GetUpdates")
	if err != nil {
		return err
	}
	defer conn.Close()
	encoder := gob.NewEncoder(conn)
	request := fm_proto.GetUpdatesRequest{Location: *location}
	if err := encoder.Encode(request); err != nil {
		return err
	}
	if err := conn.Flush(); err != nil {
		return err
	}
	decoder := gob.NewDecoder(conn)
	for {
		var update fm_proto.Update
		if err := decoder.Decode(&update); err != nil {
			return err
		}
		if err := json.WriteWithIndent(os.Stdout, "    ", update); err != nil {
			return err
		}
	}
	return nil
}

func getUpdatesOnHypervisor(hypervisor string, logger log.DebugLogger) error {
	client, err := srpc.DialHTTP("tcp", hypervisor, 0)
	if err != nil {
		return err
	}
	defer client.Close()
	conn, err := client.Call("Hypervisor.GetUpdates")
	if err != nil {
		return err
	}
	defer conn.Close()
	decoder := gob.NewDecoder(conn)
	for {
		var update hyper_proto.Update
		if err := decoder.Decode(&update); err != nil {
			return err
		}
		if err := json.WriteWithIndent(os.Stdout, "    ", update); err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>Check Error field in message in hyper-control get-updates subcommand.<commit_after>package main

import (
	"encoding/gob"
	"fmt"
	"os"

	"github.com/Symantec/Dominator/lib/errors"
	"github.com/Symantec/Dominator/lib/json"
	"github.com/Symantec/Dominator/lib/log"
	"github.com/Symantec/Dominator/lib/srpc"
	fm_proto "github.com/Symantec/Dominator/proto/fleetmanager"
	hyper_proto "github.com/Symantec/Dominator/proto/hypervisor"
)

func getUpdatesSubcommand(args []string, logger log.DebugLogger) {
	if err := getUpdates(logger); err != nil {
		fmt.Fprintf(os.Stderr, "Error getting updates: %s\n", err)
		os.Exit(1)
	}
	os.Exit(0)
}

func getUpdates(logger log.DebugLogger) error {
	if *hypervisorHostname != "" {
		return getUpdatesOnHypervisor(
			fmt.Sprintf("%s:%d", *hypervisorHostname, *hypervisorPortNum),
			logger)
	} else if *fleetManagerHostname != "" {
		return getUpdatesOnFleetManager(
			fmt.Sprintf("%s:%d", *fleetManagerHostname, *fleetManagerPortNum),
			logger)
	} else {
		return getUpdatesOnHypervisor(fmt.Sprintf(":%d", *hypervisorPortNum),
			logger)
	}

}

func getUpdatesOnFleetManager(fleetManager string,
	logger log.DebugLogger) error {
	client, err := srpc.DialHTTP("tcp", fleetManager, 0)
	if err != nil {
		return err
	}
	defer client.Close()
	conn, err := client.Call("FleetManager.GetUpdates")
	if err != nil {
		return err
	}
	defer conn.Close()
	encoder := gob.NewEncoder(conn)
	request := fm_proto.GetUpdatesRequest{Location: *location}
	if err := encoder.Encode(request); err != nil {
		return err
	}
	if err := conn.Flush(); err != nil {
		return err
	}
	decoder := gob.NewDecoder(conn)
	for {
		var update fm_proto.Update
		if err := decoder.Decode(&update); err != nil {
			return err
		}
		if err := errors.New(update.Error); err != nil {
			return err
		}
		if err := json.WriteWithIndent(os.Stdout, "    ", update); err != nil {
			return err
		}
	}
	return nil
}

func getUpdatesOnHypervisor(hypervisor string, logger log.DebugLogger) error {
	client, err := srpc.DialHTTP("tcp", hypervisor, 0)
	if err != nil {
		return err
	}
	defer client.Close()
	conn, err := client.Call("Hypervisor.GetUpdates")
	if err != nil {
		return err
	}
	defer conn.Close()
	decoder := gob.NewDecoder(conn)
	for {
		var update hyper_proto.Update
		if err := decoder.Decode(&update); err != nil {
			return err
		}
		if err := json.WriteWithIndent(os.Stdout, "    ", update); err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package app

import (
	"net"
	"os"
	"strings"
	"time"

	"github.com/coredns/coredns/coremain"
	clog "github.com/coredns/coredns/plugin/pkg/log"

	"k8s.io/dns/cmd/kube-dns/app/options"
	"k8s.io/dns/pkg/dns/config"
	"k8s.io/dns/pkg/netif"
	"k8s.io/kubernetes/pkg/util/dbus"
	utiliptables "k8s.io/kubernetes/pkg/util/iptables"
	utilexec "k8s.io/utils/exec"
	utilnet "k8s.io/utils/net"
	utilebtables "k8s.io/utils/net/ebtables"
)

// ConfigParams lists the configuration options that can be provided to node-cache
type ConfigParams struct {
	LocalIPStr           string        // comma separated listen ips for the local cache agent
	LocalIPs             []net.IP      // parsed ip addresses for the local cache agent to listen for dns requests
	LocalPort            string        // port to listen for dns requests
	MetricsListenAddress string        // address to serve metrics on
	SetupInterface       bool          // Indicates whether to setup network interface
	InterfaceName        string        // Name of the interface to be created
	Interval             time.Duration // specifies how often to run iptables rules check
	BaseCoreFile         string        // Path to the template config file for node-cache
	CoreFile             string        // Path to config file used by node-cache
	KubednsCMPath        string        // Directory where kube-dns configmap will be mounted
	UpstreamSvcName      string        // Name of the service whose clusterIP is the upstream for node-cache for cluster domain
	HealthPort           string        // port for the healthcheck
	SetupIptables        bool
	SetupEbtables        bool
	SkipTeardown         bool // Indicates whether the iptables rules and interface should be torn down
}

type iptablesRule struct {
	table utiliptables.Table
	chain utiliptables.Chain
	args  []string
}

type ebtablesRule struct {
	table utilebtables.Table
	chain utilebtables.Chain
	args  []string
}

// CacheApp contains all the config required to run node-cache.
type CacheApp struct {
	iptables      utiliptables.Interface
	iptablesRules []iptablesRule
	ebtables      utilebtables.Interface
	ebtablesRules []ebtablesRule
	params        *ConfigParams
	netifHandle   *netif.NetifManager
	kubednsConfig *options.KubeDNSConfig
	exitChan      chan struct{} // Channel to terminate background goroutines
	clusterDNSIP  net.IP
}

func isLockedErr(err error) bool {
	return strings.Contains(err.Error(), "holding the xtables lock")
}

// Init initializes the parameters and networking setup necessary to run node-cache
func (c *CacheApp) Init() {
	if c.params.SetupInterface {
		c.netifHandle = netif.NewNetifManager(c.params.LocalIPs)
	}
	if c.params.SetupIptables {
		c.initIptables()
	}
	if c.params.SetupEbtables {
		c.initEbtables()
	}
	initMetrics(c.params.MetricsListenAddress)
	// Write the config file from template.
	// this is required in case there is no or erroneous kube-dns configpath specified.
	c.updateCorefile(&config.Config{})
	// Initialize periodic sync for node-local-dns, kube-dns configmap.
	c.initDNSConfigSync()
	// Setup only the network interface first.
	// This is to ensure that iptables rules don't get setup if the cache(coreDNS) is unable to startup due to config
	// error, port conflicts or other reasons.
	c.setupInterface()
	// Initial setup of iptables/ebtables
	c.setupNetworking()
}

// isIPv6 return if the node-cache is working in IPv6 mode
// LocalIPs are guaranteed to have the same family
func (c *CacheApp) isIPv6() bool {
	if len(c.params.LocalIPs) > 0 {
		return utilnet.IsIPv6(c.params.LocalIPs[0])
	}
	return false
}

func (c *CacheApp) initIptables() {
	// using the localIPStr param since we need ip strings here
	for _, localIP := range strings.Split(c.params.LocalIPStr, ",") {
		c.iptablesRules = append(c.iptablesRules, []iptablesRule{
			// Match traffic destined for localIp:localPort and set the flows to be NOTRACKED, this skips connection tracking
			{utiliptables.Table("raw"), utiliptables.ChainPrerouting, []string{"-p", "tcp", "-d", localIP,
				"--dport", c.params.LocalPort, "-j", "NOTRACK"}},
			{utiliptables.Table("raw"), utiliptables.ChainPrerouting, []string{"-p", "udp", "-d", localIP,
				"--dport", c.params.LocalPort, "-j", "NOTRACK"}},
			// There are rules in filter table to allow tracked connections to be accepted. Since we skipped connection tracking,
			// need these additional filter table rules.
			{utiliptables.TableFilter, utiliptables.ChainInput, []string{"-p", "tcp", "-d", localIP,
				"--dport", c.params.LocalPort, "-j", "ACCEPT"}},
			{utiliptables.TableFilter, utiliptables.ChainInput, []string{"-p", "udp", "-d", localIP,
				"--dport", c.params.LocalPort, "-j", "ACCEPT"}},
			// Match traffic from localIp:localPort and set the flows to be NOTRACKED, this skips connection tracking
			{utiliptables.Table("raw"), utiliptables.ChainOutput, []string{"-p", "tcp", "-s", localIP,
				"--sport", c.params.LocalPort, "-j", "NOTRACK"}},
			{utiliptables.Table("raw"), utiliptables.ChainOutput, []string{"-p", "udp", "-s", localIP,
				"--sport", c.params.LocalPort, "-j", "NOTRACK"}},
			// Additional filter table rules for traffic frpm localIp:localPort
			{utiliptables.TableFilter, utiliptables.ChainOutput, []string{"-p", "tcp", "-s", localIP,
				"--sport", c.params.LocalPort, "-j", "ACCEPT"}},
			{utiliptables.TableFilter, utiliptables.ChainOutput, []string{"-p", "udp", "-s", localIP,
				"--sport", c.params.LocalPort, "-j", "ACCEPT"}},
			// Skip connection tracking for requests to nodelocalDNS that are locally generated, example - by hostNetwork pods
			{utiliptables.Table("raw"), utiliptables.ChainOutput, []string{"-p", "tcp", "-d", localIP,
				"--dport", c.params.LocalPort, "-j", "NOTRACK"}},
			{utiliptables.Table("raw"), utiliptables.ChainOutput, []string{"-p", "udp", "-d", localIP,
				"--dport", c.params.LocalPort, "-j", "NOTRACK"}},
			// skip connection tracking for healthcheck requests generated by liveness probe to health plugin
			{utiliptables.Table("raw"), utiliptables.ChainOutput, []string{"-p", "tcp", "-d", localIP,
				"--dport", c.params.HealthPort, "-j", "NOTRACK"}},
			{utiliptables.Table("raw"), utiliptables.ChainOutput, []string{"-p", "tcp", "-s", localIP,
				"--sport", c.params.HealthPort, "-j", "NOTRACK"}},
		}...)
	}
	c.iptables = newIPTables(c.isIPv6())
}

func newIPTables(isIPv6 bool) utiliptables.Interface {
	execer := utilexec.New()
	dbus := dbus.New()
	protocol := utiliptables.ProtocolIpv4
	if isIPv6 {
		protocol = utiliptables.ProtocolIpv6
	}
	return utiliptables.New(execer, dbus, protocol)
}

func (c *CacheApp) initEbtables() {
	protocol := "IPv4"
	if c.isIPv6() {
		protocol = "IPv6"
	}
	// using the localIPStr param since we need ip strings here
	for _, localIP := range strings.Split(c.params.LocalIPStr, ",") {
		c.ebtablesRules = append(c.ebtablesRules, []ebtablesRule{
			// Match traffic destined for localIp and use the MAC address of the bridge port as destination address
			{utilebtables.TableBroute, utilebtables.ChainBrouting, []string{"-p", protocol, "--ip-dst", localIP,
				"-j", "redirect"}},
		}...)
	}
	c.ebtables = newEBTables()
}

func newEBTables() utilebtables.Interface {
	execer := utilexec.New()
	return utilebtables.New(execer)
}

// TeardownNetworking removes all custom iptables rules and network interface added by node-cache
func (c *CacheApp) TeardownNetworking() error {
	clog.Infof("Tearing down")
	if c.exitChan != nil {
		// Stop the goroutine that periodically checks for iptables rules/dummy interface
		// exitChan is a buffered channel of size 1, so this will not block
		c.exitChan <- struct{}{}
	}
	var err error
	if c.params.SetupInterface {
		err = c.netifHandle.RemoveDummyDevice(c.params.InterfaceName)
	}
	if c.params.SetupIptables {
		for _, rule := range c.iptablesRules {
			exists := true
			for exists == true {
				c.iptables.DeleteRule(rule.table, rule.chain, rule.args...)
				exists, _ = c.iptables.EnsureRule(utiliptables.Prepend, rule.table, rule.chain, rule.args...)
			}
			// Delete the rule one last time since EnsureRule creates the rule if it doesn't exist
			c.iptables.DeleteRule(rule.table, rule.chain, rule.args...)
		}
		if c.params.SetupEbtables {
			for _, rule := range c.ebtablesRules {
				exists := true
				for exists == true {
					c.ebtables.DeleteRule(rule.table, rule.chain, rule.args...)
					exists, _ = c.ebtables.EnsureRule(utilebtables.Append, rule.table, rule.chain, rule.args...)
				}
				// Delete the rule one last time since EnsureRule creates the rule if it doesn't exist
				c.ebtables.DeleteRule(rule.table, rule.chain, rule.args...)
			}
		}
	}
	return err
}

func (c *CacheApp) setupNetworking() {
	if c.params.SetupIptables {
		for _, rule := range c.iptablesRules {
			exists, err := c.iptables.EnsureRule(utiliptables.Prepend, rule.table, rule.chain, rule.args...)
			switch {
			case exists:
				// debug messages can be printed by including "debug" plugin in coreFile.
				clog.Debugf("iptables rule %v for nodelocaldns already exists", rule)
				continue
			case err == nil:
				clog.Infof("Added back nodelocaldns rule - %v", rule)
				continue
				// if we got here, either iptables check failed or adding rule back failed.
			case isLockedErr(err):
				clog.Infof("Error checking/adding iptables rule %v, due to xtables lock in use, retrying in %v", rule, c.params.Interval)
				setupErrCount.WithLabelValues("iptables_lock").Inc()
			default:
				clog.Errorf("Error adding iptables rule %v - %s", rule, err)
				setupErrCount.WithLabelValues("iptables").Inc()
			}
		}
	}

	if c.params.SetupEbtables {
		for _, rule := range c.ebtablesRules {
			exists, err := c.ebtables.EnsureRule(utilebtables.Append, rule.table, rule.chain, rule.args...)
			switch {
			case exists:
				// debug messages can be printed by including "debug" plugin in coreFile.
				clog.Debugf("ebtables rule %v for nodelocaldns already exists", rule)
				continue
			case err == nil:
				clog.Infof("Added back ebtables rule - %v", rule)
				continue
				// if we got here, either ebtables check failed or adding rule back failed.
			default:
				clog.Errorf("Error adding ebtables rule %v - %s", rule, err)
				setupErrCount.WithLabelValues("ebtables").Inc()
			}
		}
	}

	c.setupInterface()
}

func (c *CacheApp) setupInterface() {
	if c.params.SetupInterface {
		exists, err := c.netifHandle.EnsureDummyDevice(c.params.InterfaceName)
		if !exists {
			if err != nil {
				clog.Errorf("Failed to add non-existent interface %s: %s", c.params.InterfaceName, err)
				setupErrCount.WithLabelValues("interface_add").Inc()
			}
			clog.Infof("Added interface - %s", c.params.InterfaceName)
		}
		if err != nil {
			clog.Errorf("Error checking dummy device %s - %s", c.params.InterfaceName, err)
			setupErrCount.WithLabelValues("interface_check").Inc()
		}
	}
}

func (c *CacheApp) runPeriodic() {
	c.exitChan = make(chan struct{}, 1)
	tick := time.NewTicker(c.params.Interval * time.Second)
	for {
		select {
		case <-tick.C:
			c.setupNetworking()
		case <-c.exitChan:
			clog.Warningf("Exiting iptables/interface check goroutine")
			return
		}
	}
}

// RunApp invokes the background checks and runs coreDNS as a cache
func (c *CacheApp) RunApp() {
	go c.runPeriodic()
	coremain.Run()
	// Unlikely to reach here, if we did it is because coremain exited and the signal was not trapped.
	clog.Errorf("Untrapped signal, tearing down")
	c.TeardownNetworking()
}

// NewCacheApp returns a new instance of CacheApp by applying the specified config params.
func NewCacheApp(params *ConfigParams) (*CacheApp, error) {
	c := &CacheApp{params: params, kubednsConfig: options.NewKubeDNSConfig()}
	c.clusterDNSIP = net.ParseIP(os.ExpandEnv(toSvcEnv(params.UpstreamSvcName)))
	if c.clusterDNSIP == nil {
		clog.Warningf("Unable to lookup IP address of Upstream service %s, env %s `%s`", params.UpstreamSvcName, toSvcEnv(params.UpstreamSvcName), os.ExpandEnv(toSvcEnv(params.UpstreamSvcName)))
	}
	return c, nil
}

// toSvcEnv converts service name to the corresponding ENV variable. This is exposed in every pod and its value is the clusterIP.
// https://kubernetes.io/docs/concepts/services-networking/service/#environment-variables
func toSvcEnv(svcName string) string {
	envName := strings.Replace(svcName, "-", "_", -1)
	return "$" + strings.ToUpper(envName) + "_SERVICE_HOST"
}
<commit_msg>call setupNetworking before loop<commit_after>package app

import (
	"net"
	"os"
	"strings"
	"time"

	"github.com/coredns/coredns/coremain"
	clog "github.com/coredns/coredns/plugin/pkg/log"

	"k8s.io/dns/cmd/kube-dns/app/options"
	"k8s.io/dns/pkg/dns/config"
	"k8s.io/dns/pkg/netif"
	"k8s.io/kubernetes/pkg/util/dbus"
	utiliptables "k8s.io/kubernetes/pkg/util/iptables"
	utilexec "k8s.io/utils/exec"
	utilnet "k8s.io/utils/net"
	utilebtables "k8s.io/utils/net/ebtables"
)

// ConfigParams lists the configuration options that can be provided to node-cache
type ConfigParams struct {
	LocalIPStr           string        // comma separated listen ips for the local cache agent
	LocalIPs             []net.IP      // parsed ip addresses for the local cache agent to listen for dns requests
	LocalPort            string        // port to listen for dns requests
	MetricsListenAddress string        // address to serve metrics on
	SetupInterface       bool          // Indicates whether to setup network interface
	InterfaceName        string        // Name of the interface to be created
	Interval             time.Duration // specifies how often to run iptables rules check
	BaseCoreFile         string        // Path to the template config file for node-cache
	CoreFile             string        // Path to config file used by node-cache
	KubednsCMPath        string        // Directory where kube-dns configmap will be mounted
	UpstreamSvcName      string        // Name of the service whose clusterIP is the upstream for node-cache for cluster domain
	HealthPort           string        // port for the healthcheck
	SetupIptables        bool
	SetupEbtables        bool
	SkipTeardown         bool // Indicates whether the iptables rules and interface should be torn down
}

type iptablesRule struct {
	table utiliptables.Table
	chain utiliptables.Chain
	args  []string
}

type ebtablesRule struct {
	table utilebtables.Table
	chain utilebtables.Chain
	args  []string
}

// CacheApp contains all the config required to run node-cache.
type CacheApp struct {
	iptables      utiliptables.Interface
	iptablesRules []iptablesRule
	ebtables      utilebtables.Interface
	ebtablesRules []ebtablesRule
	params        *ConfigParams
	netifHandle   *netif.NetifManager
	kubednsConfig *options.KubeDNSConfig
	exitChan      chan struct{} // Channel to terminate background goroutines
	clusterDNSIP  net.IP
}

func isLockedErr(err error) bool {
	return strings.Contains(err.Error(), "holding the xtables lock")
}

// Init initializes the parameters and networking setup necessary to run node-cache
func (c *CacheApp) Init() {
	if c.params.SetupInterface {
		c.netifHandle = netif.NewNetifManager(c.params.LocalIPs)
	}
	if c.params.SetupIptables {
		c.initIptables()
	}
	if c.params.SetupEbtables {
		c.initEbtables()
	}
	initMetrics(c.params.MetricsListenAddress)
	// Write the config file from template.
	// this is required in case there is no or erroneous kube-dns configpath specified.
	c.updateCorefile(&config.Config{})
	// Initialize periodic sync for node-local-dns, kube-dns configmap.
	c.initDNSConfigSync()
	// Setup only the network interface during this init. IPTables will be setup via runPeriodic.
	// This is to ensure that iptables rules don't get setup if the cache(coreDNS) is unable to startup due to config
	// error, port conflicts or other reasons.
	setupIptables := c.params.SetupIptables
	c.params.SetupIptables = false
	c.setupNetworking()
	c.params.SetupIptables = setupIptables
}

// isIPv6 return if the node-cache is working in IPv6 mode
// LocalIPs are guaranteed to have the same family
func (c *CacheApp) isIPv6() bool {
	if len(c.params.LocalIPs) > 0 {
		return utilnet.IsIPv6(c.params.LocalIPs[0])
	}
	return false
}

func (c *CacheApp) initIptables() {
	// using the localIPStr param since we need ip strings here
	for _, localIP := range strings.Split(c.params.LocalIPStr, ",") {
		c.iptablesRules = append(c.iptablesRules, []iptablesRule{
			// Match traffic destined for localIp:localPort and set the flows to be NOTRACKED, this skips connection tracking
			{utiliptables.Table("raw"), utiliptables.ChainPrerouting, []string{"-p", "tcp", "-d", localIP,
				"--dport", c.params.LocalPort, "-j", "NOTRACK"}},
			{utiliptables.Table("raw"), utiliptables.ChainPrerouting, []string{"-p", "udp", "-d", localIP,
				"--dport", c.params.LocalPort, "-j", "NOTRACK"}},
			// There are rules in filter table to allow tracked connections to be accepted. Since we skipped connection tracking,
			// need these additional filter table rules.
			{utiliptables.TableFilter, utiliptables.ChainInput, []string{"-p", "tcp", "-d", localIP,
				"--dport", c.params.LocalPort, "-j", "ACCEPT"}},
			{utiliptables.TableFilter, utiliptables.ChainInput, []string{"-p", "udp", "-d", localIP,
				"--dport", c.params.LocalPort, "-j", "ACCEPT"}},
			// Match traffic from localIp:localPort and set the flows to be NOTRACKED, this skips connection tracking
			{utiliptables.Table("raw"), utiliptables.ChainOutput, []string{"-p", "tcp", "-s", localIP,
				"--sport", c.params.LocalPort, "-j", "NOTRACK"}},
			{utiliptables.Table("raw"), utiliptables.ChainOutput, []string{"-p", "udp", "-s", localIP,
				"--sport", c.params.LocalPort, "-j", "NOTRACK"}},
			// Additional filter table rules for traffic frpm localIp:localPort
			{utiliptables.TableFilter, utiliptables.ChainOutput, []string{"-p", "tcp", "-s", localIP,
				"--sport", c.params.LocalPort, "-j", "ACCEPT"}},
			{utiliptables.TableFilter, utiliptables.ChainOutput, []string{"-p", "udp", "-s", localIP,
				"--sport", c.params.LocalPort, "-j", "ACCEPT"}},
			// Skip connection tracking for requests to nodelocalDNS that are locally generated, example - by hostNetwork pods
			{utiliptables.Table("raw"), utiliptables.ChainOutput, []string{"-p", "tcp", "-d", localIP,
				"--dport", c.params.LocalPort, "-j", "NOTRACK"}},
			{utiliptables.Table("raw"), utiliptables.ChainOutput, []string{"-p", "udp", "-d", localIP,
				"--dport", c.params.LocalPort, "-j", "NOTRACK"}},
			// skip connection tracking for healthcheck requests generated by liveness probe to health plugin
			{utiliptables.Table("raw"), utiliptables.ChainOutput, []string{"-p", "tcp", "-d", localIP,
				"--dport", c.params.HealthPort, "-j", "NOTRACK"}},
			{utiliptables.Table("raw"), utiliptables.ChainOutput, []string{"-p", "tcp", "-s", localIP,
				"--sport", c.params.HealthPort, "-j", "NOTRACK"}},
		}...)
	}
	c.iptables = newIPTables(c.isIPv6())
}

func newIPTables(isIPv6 bool) utiliptables.Interface {
	execer := utilexec.New()
	dbus := dbus.New()
	protocol := utiliptables.ProtocolIpv4
	if isIPv6 {
		protocol = utiliptables.ProtocolIpv6
	}
	return utiliptables.New(execer, dbus, protocol)
}

func (c *CacheApp) initEbtables() {
	protocol := "IPv4"
	if c.isIPv6() {
		protocol = "IPv6"
	}
	// using the localIPStr param since we need ip strings here
	for _, localIP := range strings.Split(c.params.LocalIPStr, ",") {
		c.ebtablesRules = append(c.ebtablesRules, []ebtablesRule{
			// Match traffic destined for localIp and use the MAC address of the bridge port as destination address
			{utilebtables.TableBroute, utilebtables.ChainBrouting, []string{"-p", protocol, "--ip-dst", localIP,
				"-j", "redirect"}},
		}...)
	}
	c.ebtables = newEBTables()
}

func newEBTables() utilebtables.Interface {
	execer := utilexec.New()
	return utilebtables.New(execer)
}

// TeardownNetworking removes all custom iptables rules and network interface added by node-cache
func (c *CacheApp) TeardownNetworking() error {
	clog.Infof("Tearing down")
	if c.exitChan != nil {
		// Stop the goroutine that periodically checks for iptables rules/dummy interface
		// exitChan is a buffered channel of size 1, so this will not block
		c.exitChan <- struct{}{}
	}
	var err error
	if c.params.SetupInterface {
		err = c.netifHandle.RemoveDummyDevice(c.params.InterfaceName)
	}
	if c.params.SetupIptables {
		for _, rule := range c.iptablesRules {
			exists := true
			for exists == true {
				c.iptables.DeleteRule(rule.table, rule.chain, rule.args...)
				exists, _ = c.iptables.EnsureRule(utiliptables.Prepend, rule.table, rule.chain, rule.args...)
			}
			// Delete the rule one last time since EnsureRule creates the rule if it doesn't exist
			c.iptables.DeleteRule(rule.table, rule.chain, rule.args...)
		}
		if c.params.SetupEbtables {
			for _, rule := range c.ebtablesRules {
				exists := true
				for exists == true {
					c.ebtables.DeleteRule(rule.table, rule.chain, rule.args...)
					exists, _ = c.ebtables.EnsureRule(utilebtables.Append, rule.table, rule.chain, rule.args...)
				}
				// Delete the rule one last time since EnsureRule creates the rule if it doesn't exist
				c.ebtables.DeleteRule(rule.table, rule.chain, rule.args...)
			}
		}
	}
	return err
}

func (c *CacheApp) setupNetworking() {
	if c.params.SetupIptables {
		for _, rule := range c.iptablesRules {
			exists, err := c.iptables.EnsureRule(utiliptables.Prepend, rule.table, rule.chain, rule.args...)
			switch {
			case exists:
				// debug messages can be printed by including "debug" plugin in coreFile.
				clog.Debugf("iptables rule %v for nodelocaldns already exists", rule)
				continue
			case err == nil:
				clog.Infof("Added back nodelocaldns rule - %v", rule)
				continue
				// if we got here, either iptables check failed or adding rule back failed.
			case isLockedErr(err):
				clog.Infof("Error checking/adding iptables rule %v, due to xtables lock in use, retrying in %v", rule, c.params.Interval)
				setupErrCount.WithLabelValues("iptables_lock").Inc()
			default:
				clog.Errorf("Error adding iptables rule %v - %s", rule, err)
				setupErrCount.WithLabelValues("iptables").Inc()
			}
		}
	}

	if c.params.SetupEbtables {
		for _, rule := range c.ebtablesRules {
			exists, err := c.ebtables.EnsureRule(utilebtables.Append, rule.table, rule.chain, rule.args...)
			switch {
			case exists:
				// debug messages can be printed by including "debug" plugin in coreFile.
				clog.Debugf("ebtables rule %v for nodelocaldns already exists", rule)
				continue
			case err == nil:
				clog.Infof("Added back ebtables rule - %v", rule)
				continue
				// if we got here, either ebtables check failed or adding rule back failed.
			default:
				clog.Errorf("Error adding ebtables rule %v - %s", rule, err)
				setupErrCount.WithLabelValues("ebtables").Inc()
			}
		}
	}

	if c.params.SetupInterface {
		exists, err := c.netifHandle.EnsureDummyDevice(c.params.InterfaceName)
		if !exists {
			if err != nil {
				clog.Errorf("Failed to add non-existent interface %s: %s", c.params.InterfaceName, err)
				setupErrCount.WithLabelValues("interface_add").Inc()
			}
			clog.Infof("Added interface - %s", c.params.InterfaceName)
		}
		if err != nil {
			clog.Errorf("Error checking dummy device %s - %s", c.params.InterfaceName, err)
			setupErrCount.WithLabelValues("interface_check").Inc()
		}
	}
}

func (c *CacheApp) runPeriodic() {
	c.exitChan = make(chan struct{}, 1)
	tick := time.NewTicker(c.params.Interval * time.Second)
	c.setupNetworking()
	for {
		select {
		case <-tick.C:
			c.setupNetworking()
		case <-c.exitChan:
			clog.Warningf("Exiting iptables/interface check goroutine")
			return
		}
	}
}

// RunApp invokes the background checks and runs coreDNS as a cache
func (c *CacheApp) RunApp() {
	go c.runPeriodic()
	coremain.Run()
	// Unlikely to reach here, if we did it is because coremain exited and the signal was not trapped.
	clog.Errorf("Untrapped signal, tearing down")
	c.TeardownNetworking()
}

// NewCacheApp returns a new instance of CacheApp by applying the specified config params.
func NewCacheApp(params *ConfigParams) (*CacheApp, error) {
	c := &CacheApp{params: params, kubednsConfig: options.NewKubeDNSConfig()}
	c.clusterDNSIP = net.ParseIP(os.ExpandEnv(toSvcEnv(params.UpstreamSvcName)))
	if c.clusterDNSIP == nil {
		clog.Warningf("Unable to lookup IP address of Upstream service %s, env %s `%s`", params.UpstreamSvcName, toSvcEnv(params.UpstreamSvcName), os.ExpandEnv(toSvcEnv(params.UpstreamSvcName)))
	}
	return c, nil
}

// toSvcEnv converts service name to the corresponding ENV variable. This is exposed in every pod and its value is the clusterIP.
// https://kubernetes.io/docs/concepts/services-networking/service/#environment-variables
func toSvcEnv(svcName string) string {
	envName := strings.Replace(svcName, "-", "_", -1)
	return "$" + strings.ToUpper(envName) + "_SERVICE_HOST"
}
<|endoftext|>


<commit_before>package gopkg

import (
	"bytes"
	"fmt"
	"go/importer"
	"go/types"
	"io/ioutil"
	"os"
	"testing"
)

// -----------------------------------------------------------------------------

type goFunc struct {
	Name string
	This interface{}
}

const expected = `// Package strings provide Go+ "strings" package, as "strings" package in Go.
package strings

import (
	gop "github.com/qiniu/goplus/gop"
	strings "strings"
)

func execNewReplacer(arity int, p *gop.Context) {
	args := p.GetArgs(arity)
	ret0 := strings.NewReplacer(gop.ToStrings(args)...)
	p.Ret(arity, ret0)
}

func execmReplacerReplace(_ int, p *gop.Context) {
	args := p.GetArgs(2)
	ret0 := args[0].(*strings.Replacer).Replace(args[1].(string))
	p.Ret(2, ret0)
}

// I is a Go package instance.
var I = gop.NewGoPackage("strings")

func init() {
	I.RegisterFuncs(
		I.Func("(*Replacer).Replace", (*strings.Replacer).Replace, execmReplacerReplace),
	)
	I.RegisterFuncvs(
		I.Funcv("NewReplacer", strings.NewReplacer, execNewReplacer),
	)
}
`

func getMethod(o *types.Named, name string) *types.Func {
	n := o.NumMethods()
	for i := 0; i < n; i++ {
		m := o.Method(i)
		if m.Name() == name {
			return m
		}
	}
	return nil
}

func TestBasic(t *testing.T) {
	pkg, err := importer.Default().Import("strings")
	if err != nil {
		t.Fatal("Import failed:", err)
	}
	gbl := pkg.Scope()
	newReplacer := gbl.Lookup("NewReplacer").(*types.Func)
	replacer := gbl.Lookup("Replacer").(*types.TypeName).Type().(*types.Named)
	b := bytes.NewBuffer(nil)
	e := NewExporter(b, pkg)
	e.ExportFunc(newReplacer)
	e.ExportFunc(getMethod(replacer, "Replace"))
	e.Close()
	if real := b.String(); real != expected {
		fmt.Println(real)
		t.Fatal("Test failed")
	}
}

func TestFixPkgString(t *testing.T) {
	pkg, _ := importer.Default().Import("go/types")
	b := bytes.NewBuffer(nil)
	e := NewExporter(b, pkg)
	e.imports["io"] = "io1"
	e.imports["go/types"] = "types1"
	if v := e.fixPkgString("*go/types.Interface"); v != "*types1.Interface" {
		t.Fatal(v)
	}
	if v := e.fixPkgString("[]*go/types.Package"); v != "[]*types1.Package" {
		t.Fatal(v)
	}
	if v := e.fixPkgString("io.Writer"); v != "io1.Writer" {
		t.Fatal(v)
	}
}

// -----------------------------------------------------------------------------

func TestExportStrings(t *testing.T) {
	err := Export("strings", ioutil.Discard)
	if err != nil {
		t.Fatal("TestExport failed:", err)
	}
}

func TestExportStrconv(t *testing.T) {
	err := Export("strconv", ioutil.Discard)
	if err != nil {
		t.Fatal("TestExport failed:", err)
	}
}

func TestExportReflect(t *testing.T) {
	err := Export("reflect", ioutil.Discard)
	if err != nil {
		t.Fatal("TestExport failed:", err)
	}
}

func TestExportIo(t *testing.T) {
	err := Export("io", os.Stdout)
	if err != nil {
		t.Fatal("TestExport failed:", err)
	}
}

func TestExportGoTypes(t *testing.T) {
	err := Export("go/types", ioutil.Discard)
	if err != nil {
		t.Fatal("TestExport failed:", err)
	}
}

func TestPackage(t *testing.T) {
	pkg, err := Import("go/build")
	if err != nil {
		t.Fatal("TestPackage Import failed:", err)
	}
	err = ExportPackage(pkg, ioutil.Discard)
	if err != nil {
		t.Fatal("TestPackage ExportPackage failed:", err)
	}
}

// -----------------------------------------------------------------------------
<commit_msg>cmd/qexp/gopkg: update test<commit_after>package gopkg

import (
	"bytes"
	"fmt"
	"go/importer"
	"go/types"
	"io/ioutil"
	"os"
	"testing"
)

// -----------------------------------------------------------------------------

type goFunc struct {
	Name string
	This interface{}
}

const expected = `// Package strings provide Go+ "strings" package, as "strings" package in Go.
package strings

import (
	gop "github.com/qiniu/goplus/gop"
	strings "strings"
)

func execNewReplacer(arity int, p *gop.Context) {
	args := p.GetArgs(arity)
	ret0 := strings.NewReplacer(gop.ToStrings(args)...)
	p.Ret(arity, ret0)
}

func execmReplacerReplace(_ int, p *gop.Context) {
	args := p.GetArgs(2)
	ret0 := args[0].(*strings.Replacer).Replace(args[1].(string))
	p.Ret(2, ret0)
}

// I is a Go package instance.
var I = gop.NewGoPackage("strings")

func init() {
	I.RegisterFuncs(
		I.Func("(*Replacer).Replace", (*strings.Replacer).Replace, execmReplacerReplace),
	)
	I.RegisterFuncvs(
		I.Funcv("NewReplacer", strings.NewReplacer, execNewReplacer),
	)
}
`

func getMethod(o *types.Named, name string) *types.Func {
	n := o.NumMethods()
	for i := 0; i < n; i++ {
		m := o.Method(i)
		if m.Name() == name {
			return m
		}
	}
	return nil
}

func TestBasic(t *testing.T) {
	pkg, err := importer.Default().Import("strings")
	if err != nil {
		t.Fatal("Import failed:", err)
	}
	gbl := pkg.Scope()
	newReplacer := gbl.Lookup("NewReplacer").(*types.Func)
	replacer := gbl.Lookup("Replacer").(*types.TypeName).Type().(*types.Named)
	b := bytes.NewBuffer(nil)
	e := NewExporter(b, pkg)
	e.ExportFunc(newReplacer)
	e.ExportFunc(getMethod(replacer, "Replace"))
	e.Close()
	if real := b.String(); real != expected {
		fmt.Println(real)
		t.Fatal("Test failed")
	}
}

func TestFixPkgString(t *testing.T) {
	pkg, _ := importer.Default().Import("go/types")
	b := bytes.NewBuffer(nil)
	e := NewExporter(b, pkg)
	e.imports["io"] = "io1"
	e.imports["go/types"] = "types1"
	if v := e.fixPkgString("*go/types.Interface"); v != "*types1.Interface" {
		t.Fatal(v)
	}
	if v := e.fixPkgString("[]*go/types.Package"); v != "[]*types1.Package" {
		t.Fatal(v)
	}
	if v := e.fixPkgString("io.Writer"); v != "io1.Writer" {
		t.Fatal(v)
	}
}

// -----------------------------------------------------------------------------

func TestExportStrings(t *testing.T) {
	err := Export("strings", ioutil.Discard)
	if err != nil {
		t.Fatal("TestExport failed:", err)
	}
}

func TestExportStrconv(t *testing.T) {
	err := Export("strconv", ioutil.Discard)
	if err != nil {
		t.Fatal("TestExport failed:", err)
	}
}

func TestExportReflect(t *testing.T) {
	err := Export("reflect", ioutil.Discard)
	if err != nil {
		t.Fatal("TestExport failed:", err)
	}
}

func TestExportIo(t *testing.T) {
	err := Export("io", os.Stdout)
	if err != nil {
		t.Fatal("TestExport failed:", err)
	}
}

func TestExportGoTypes(t *testing.T) {
	err := Export("go/types", ioutil.Discard)
	if err != nil {
		t.Fatal("TestExport failed:", err)
	}
}

func TestExportMath(t *testing.T) {
	err := Export("math", ioutil.Discard)
	if err != nil {
		t.Fatal("TestExport failed:", err)
	}
}

func TestImport(t *testing.T) {
	pkg, err := Import("go/types")
	if err != nil {
		t.Fatal("TestImport failed:", err)
	}
	if pkg.Path() != "go/types" {
		t.Fatal(pkg.Path())
	}
}

func TestBadImport(t *testing.T) {
	_, err := Import("go/badtypes")
	if err == nil {
		t.Fatal("TestBadImport failed")
	}
}

func TestBadExport(t *testing.T) {
	err := Export("go/badtypes", ioutil.Discard)
	if err == nil {
		t.Fatal("TestBadExport failed")
	}
}

func TestExportPackage(t *testing.T) {
	pkg, err := Import("go/build")
	if err != nil {
		t.Fatal("TestPackage Import failed:", err)
	}
	err = ExportPackage(pkg, ioutil.Discard)
	if err != nil {
		t.Fatal("TestPackage ExportPackage failed:", err)
	}
}

// -----------------------------------------------------------------------------
<|endoftext|>


<commit_before>// Package up is used to run a function for some duration. If a new function is added while a previous run is
// still ongoing, nothing new will be executed.
package up

import (
	"sync"
	"time"
)

// Probe is used to run a single Func until it returns true (indicating a target is healthy). If an Func
// is already in progress no new one will be added, i.e. there is always a maximum of 1 checks in flight.
type Probe struct {
	do   chan Func
	stop chan bool

	target string

	sync.Mutex
	inprogress int
}

// Func is used to determine if a target is alive. If so this function must return nil.
type Func func() error

// New returns a pointer to an intialized Probe.
func New() *Probe {
	return &Probe{stop: make(chan bool), do: make(chan Func)}
}

// Do will probe target, if a probe is already in progress this is a noop.
func (p *Probe) Do(f Func) { p.do <- f }

// Stop stops the probing.
func (p *Probe) Stop() { p.stop <- true }

// Start will start the probe manager, after which probes can be initialized with Do.
func (p *Probe) Start(interval time.Duration) { go p.start(interval) }

func (p *Probe) start(interval time.Duration) {
	for {
		select {
		case <-p.stop:
			p.Lock()
			p.inprogress = stop
			p.Unlock()
			return
		case f := <-p.do:
			p.Lock()
			if p.inprogress == active || p.inprogress == stop {
				p.Unlock()
				continue
			}
			p.inprogress = active
			p.Unlock()

			// Passed the lock. Now run f for as long it returns false. If a true is returned
			// we return from the goroutine and we can accept another Func to run.
			go func() {
				for {
					if err := f(); err == nil {
						break
					}
					time.Sleep(interval)
					p.Lock()
					if p.inprogress == stop {
						p.Unlock()
						return
					}
					p.Unlock()
				}

				p.Lock()
				p.inprogress = idle
				p.Unlock()
			}()
		}
	}
}

const (
	idle = iota
	active
	stop
)
<commit_msg>Probe simplification (#1784)<commit_after>// Package up is used to run a function for some duration. If a new function is added while a previous run is
// still ongoing, nothing new will be executed.
package up

import (
	"sync"
	"time"
)

// Probe is used to run a single Func until it returns true (indicating a target is healthy). If an Func
// is already in progress no new one will be added, i.e. there is always a maximum of 1 checks in flight.
type Probe struct {
	sync.Mutex
	inprogress int
	interval   time.Duration
}

// Func is used to determine if a target is alive. If so this function must return nil.
type Func func() error

// New returns a pointer to an intialized Probe.
func New() *Probe { return &Probe{} }

// Do will probe target, if a probe is already in progress this is a noop.
func (p *Probe) Do(f Func) {
	p.Lock()
	if p.inprogress != idle {
		p.Unlock()
		return
	}
	p.inprogress = active
	interval := p.interval
	p.Unlock()
	// Passed the lock. Now run f for as long it returns false. If a true is returned
	// we return from the goroutine and we can accept another Func to run.
	go func() {
		for {
			if err := f(); err == nil {
				break
			}
			time.Sleep(interval)
			p.Lock()
			if p.inprogress == stop {
				p.Unlock()
				return
			}
			p.Unlock()
		}

		p.Lock()
		p.inprogress = idle
		p.Unlock()
	}()
}

// Stop stops the probing.
func (p *Probe) Stop() {
	p.Lock()
	p.inprogress = stop
	p.Unlock()
}

// Start will initialize the probe manager, after which probes can be initiated with Do.
func (p *Probe) Start(interval time.Duration) { p.SetInterval(interval) }

// SetInterval sets the probing interval to be used by upcoming probes initiated with Do.
func (p *Probe) SetInterval(interval time.Duration) {
	p.Lock()
	p.interval = interval
	p.Unlock()
}

const (
	idle = iota
	active
	stop
)
<|endoftext|>


<commit_before>package main

import (
	"net/http"
	"strings"
)

var (
	// Whitelist crawlers here
	crawlerPatterns = [...]string{
		"Google (+https://developers.google.com/+/web/snippet/)",
		"Googlebot",
		"bingbot",
		"MSNbot",
		"facebookexternalhit",
		"PlurkBot",
		"Twitterbot",
		"CloudFlare-AlwaysOnline",
	}
)

func isCrawlerUserAgent(r *http.Request) bool {
	ua := r.UserAgent()

	for _, pattern := range crawlerPatterns {
		if strings.Contains(ua, pattern) {
			return true
		}
	}

	return false
}
<commit_msg>Add Telegram preview to crawler list<commit_after>package main

import (
	"net/http"
	"strings"
)

var (
	// Whitelist crawlers here
	crawlerPatterns = [...]string{
		"Google (+https://developers.google.com/+/web/snippet/)",
		"Googlebot",
		"bingbot",
		"MSNbot",
		"facebookexternalhit",
		"PlurkBot",
		"Twitterbot",
		"TelegramBot",
		"CloudFlare-AlwaysOnline",
	}
)

func isCrawlerUserAgent(r *http.Request) bool {
	ua := r.UserAgent()

	for _, pattern := range crawlerPatterns {
		if strings.Contains(ua, pattern) {
			return true
		}
	}

	return false
}
<|endoftext|>


<commit_before>// Copyright (c) 2013 Mathieu Turcotte
// Licensed under the MIT license.

// Package browserchannel provides a server-side browser channel
// implementation. See http://goo.gl/F287G for the client-side API.
package browserchannel

import (
	crand "crypto/rand"
	"encoding/json"
	"io"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
)

// The browser channel protocol version implemented by this library.
const SupportedProcolVersion = 8

const (
	// The path for the channel connection.
	DefaultBindPath = "bind"
	// The path for the test connection.
	DefaultTestPath = "test"
)

type queryType int

// Possible values for the query TYPE parameter.
const (
	queryUnset = iota
	queryTerminate
	queryXmlHttp
	queryHtml
	queryTest
)

func parseQueryType(s string) (qtype queryType) {
	switch s {
	case "html":
		qtype = queryHtml
	case "xmlhttp":
		qtype = queryXmlHttp
	case "terminate":
		qtype = queryTerminate
	case "test":
		qtype = queryTest
	}
	return
}

func (qtype queryType) setContentType(rw http.ResponseWriter) {
	if qtype == queryHtml {
		rw.Header().Set("Content-Type", "text/html")
	} else {
		rw.Header().Set("Content-Type", "text/plain")
	}
}

func parseAid(s string) (aid int, err error) {
	aid = -1
	if len(s) == 0 {
		return
	}
	aid, err = strconv.Atoi(s)
	return
}

func parseProtoVersion(s string) (version int) {
	version, err := strconv.Atoi(s)
	if err != nil {
		version = -1
	}
	return
}

type bindParams struct {
	cver    string
	sid     SessionId
	qtype   queryType
	domain  string
	rid     string
	aid     int
	chunked bool
	values  url.Values
	method  string
}

func parseBindParams(req *http.Request, values url.Values) (params *bindParams, err error) {
	cver := req.Form.Get("VER")
	qtype := parseQueryType(req.Form.Get("TYPE"))
	domain := req.Form.Get("DOMAIN")
	rid := req.Form.Get("zx")
	chunked := req.Form.Get("CI") == "0"
	sid, err := parseSessionId(req.Form.Get("SID"))
	if err != nil {
		return
	}
	aid, err := parseAid(req.Form.Get("AID"))
	if err != nil {
		return
	}
	params = &bindParams{cver, sid, qtype, domain, rid, aid, chunked, values, req.Method}
	return
}

type testParams struct {
	ver    int
	init   bool
	qtype  queryType
	domain string
}

func parseTestParams(req *http.Request) *testParams {
	version := parseProtoVersion(req.Form.Get("VER"))
	qtype := parseQueryType(req.Form.Get("TYPE"))
	domain := req.Form.Get("DOMAIN")
	init := req.Form.Get("MODE") == "init"
	return &testParams{version, init, qtype, domain}
}

var headers = map[string]string{
	"Cache-Control":          "no-cache, no-store, max-age=0, must-revalidate",
	"Expires":                "Fri, 01 Jan 1990 00:00:00 GMT",
	"X-Content-Type-Options": "nosniff",
	"Transfer-Encoding":      "chunked",
	"Pragma":                 "no-cache",
}

type channelMap struct {
	sync.RWMutex
	m map[SessionId]*Channel
}

func (m *channelMap) get(sid SessionId) *Channel {
	m.RLock()
	defer m.RUnlock()
	return m.m[sid]
}

func (m *channelMap) set(sid SessionId, channel *Channel) {
	m.Lock()
	defer m.Unlock()
	m.m[sid] = channel
}

func (m *channelMap) del(sid SessionId) (deleted bool) {
	m.Lock()
	defer m.Unlock()
	_, deleted = m.m[sid]
	delete(m.m, sid)
	return
}

// Contains the browser channel cross domain info for a single domain.
type crossDomainInfo struct {
	hostMatcher *regexp.Regexp
	domain      string
	prefixes    []string
}

func getHostPrefix(info *crossDomainInfo) string {
	if info != nil {
		return info.prefixes[rand.Intn(len(info.prefixes))]
	}
	return ""
}

// The browser channel HTTP handler will invoke its ChannelHandler in a
// goroutine for each new browser channel connection established.
type ChannelHandler func(*Channel)

// The browser channel http.Handler.
type Handler struct {
	corsInfo    *crossDomainInfo
	prefix      string
	channels    *channelMap
	bindPath    string
	testPath    string
	gcChan      chan SessionId
	chanHandler ChannelHandler
}

// Creates a new browser channel HTTP handler. The last path segment of the
// URL is used to distinguish bind and test connections.
func NewHandler(chanHandler ChannelHandler) (h *Handler) {
	h = new(Handler)
	h.channels = &channelMap{m: make(map[SessionId]*Channel)}
	h.bindPath = DefaultBindPath
	h.testPath = DefaultTestPath
	h.gcChan = make(chan SessionId, 10)
	h.chanHandler = chanHandler
	go h.removeClosedSession()
	return
}

// Sets the cross domain information for this browser channel. The origin is
// used as the Access-Control-Allow-Origin header value and should respect the
// format specified by http://www.w3.org/TR/cors/. The prefixes are used to set
// the hostPrefix parameter on the client side. The prefix assigned to each
// browser channel session is chosen randomly from the array of prefixes.
func (h *Handler) SetCrossDomainPrefix(domain string, prefixes []string) {
	h.corsInfo = &crossDomainInfo{makeOriginMatcher(domain), domain, prefixes}
}

// Removes closed channels from the handler's channel map.
func (h *Handler) removeClosedSession() {
	for {
		sid, ok := <-h.gcChan
		if !ok {
			break
		}

		log.Printf("removing %s from session map\n", sid)

		if !h.channels.del(sid) {
			log.Printf("missing channel for %s in session map\n", sid)
		}
	}
}

func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	// The CORS  spec only supports *, null or the exact domain.
	// http://www.w3.org/TR/cors/#access-control-allow-origin-response-header
	// http://tools.ietf.org/html/rfc6454#section-7.1
	origin := req.Header.Get("origin")
	if len(origin) > 0 && h.corsInfo != nil &&
		h.corsInfo.hostMatcher.MatchString(origin) {
		rw.Header().Set("Access-Control-Allow-Origin", origin)
		rw.Header().Set("Access-Control-Allow-Credentials", "true")
	}

	// The body is parsed before calling ParseForm so the values don't get
	// collapsed into a single collection.
	values, err := parseBody(req.Body)
	if err != nil {
		rw.WriteHeader(400)
		return
	}

	req.ParseForm()

	path := req.URL.Path
	if strings.HasSuffix(path, h.testPath) {
		h.handleTestRequest(rw, parseTestParams(req))
	} else if strings.HasSuffix(path, h.bindPath) {
		params, err := parseBindParams(req, values)
		if err != nil {
			rw.WriteHeader(400)
			return
		}
		h.handleBindRequest(rw, params)
	} else {
		rw.WriteHeader(404)
	}
}

func (h *Handler) handleTestRequest(rw http.ResponseWriter, params *testParams) {
	if params.ver != SupportedProcolVersion {
		rw.WriteHeader(400)
		io.WriteString(rw, "Unsupported protocol version.")
	} else if params.init {
		setHeaders(rw, &headers)
		rw.WriteHeader(200)
		io.WriteString(rw, "[\""+getHostPrefix(h.corsInfo)+"\",\"\"]")
	} else {
		params.qtype.setContentType(rw)
		setHeaders(rw, &headers)
		rw.WriteHeader(200)

		if params.qtype == queryHtml {
			writeHtmlHead(rw)
			writeHtmlDomain(rw, params.domain)
			writeHtmlRpc(rw, "11111")
			writeHtmlPadding(rw)
		} else {
			io.WriteString(rw, "11111")
		}

		// It is important to flush the response at this point otherwise the
		// client won't receive the intermediate result and will disable the
		// chunking support by setting the CI parameter to 1 which tells the
		// server to close bind requests immediately. For reference, see
		// goog.net.BrowserTestChannel#onRequestComplete.
		rw.(http.Flusher).Flush()

		time.Sleep(2 * time.Second)

		if params.qtype == queryHtml {
			writeHtmlRpc(rw, "2")
			writeHtmlDone(rw)
		} else {
			io.WriteString(rw, "2")
		}
	}
}

func (h *Handler) handleBindRequest(rw http.ResponseWriter, params *bindParams) {
	var channel *Channel
	sid := params.sid

	// If the client has specified a session id, lookup the session object in
	// the sessions map. Lookup failure should be signaled to the client using
	// a 400 status code and a message containing 'Unknown SID'. See
	// goog/net/channelrequest.js for more context on how this error is
	// handled.
	if sid != nullSessionId {
		channel = h.channels.get(sid)
		if channel == nil {
			log.Printf("failed to lookup session %s\n", sid)
			setHeaders(rw, &headers)
			rw.WriteHeader(400)
			io.WriteString(rw, "Unknown SID")
			return
		}
	}

	if channel == nil {
		sid, _ = generateSesionId(crand.Reader)
		log.Printf("creating session %s\n", sid)
		channel = newChannel(params.cver, sid, h.gcChan, h.corsInfo)
		h.channels.set(sid, channel)
		channel.armChannelTimeout()
		go h.chanHandler(channel)
	}

	if params.aid != -1 {
		channel.acknowledgeArrays(params.aid)
	}

	switch params.method {
	case "POST":
		h.handleBindPost(rw, params, channel)
	case "GET":
		h.handleBindGet(rw, params, channel)
	default:
		rw.WriteHeader(400)
	}
}

func (h *Handler) handleBindPost(rw http.ResponseWriter, params *bindParams, channel *Channel) {
	offset, maps, err := parseIncomingMaps(params.values)
	if err != nil {
		rw.WriteHeader(400)
		return
	}

	if err := channel.receiveMaps(offset, maps); err != nil {
		log.Printf("%s: %s\n", channel.Sid, err)
		rw.WriteHeader(500)
		return
	}

	if channel.state == channelInit {
		setHeaders(rw, &headers)
		rw.WriteHeader(200)
		rw.(http.Flusher).Flush()

		// The initial forward request is used as a back channel to send the
		// server configuration: ['c', id, host, version]. This payload has to
		// be sent immediately, so the streaming is disabled on the back
		// channel. Note that the first bind request made by IE<10 does not
		// contain a TYPE=html query parameter and therefore receives the same
		// length prefixed array reply as is sent to the XHR streaming clients.
		backChannel := newBackChannel(channel.Sid, rw, false, "", params.rid)
		channel.setBackChannel(backChannel)
		backChannel.wait()
	} else {
		// On normal forward channel request, the session status is returned
		// to the client. The session status contains 3 pieces of information:
		// does this session has a back channel, the last array id sent to the
		// client and the number of outstanding bytes in the back channel.
		b, _ := json.Marshal(channel.getState())
		setHeaders(rw, &headers)
		rw.WriteHeader(200)
		io.WriteString(rw, strconv.FormatInt(int64(len(b)), 10)+"\n")
		rw.Write(b)
	}
}

func (h *Handler) handleBindGet(rw http.ResponseWriter, params *bindParams, channel *Channel) {
	if params.qtype == queryTerminate {
		channel.terminate()
	} else {
		params.qtype.setContentType(rw)
		setHeaders(rw, &headers)
		rw.WriteHeader(200)
		rw.(http.Flusher).Flush()

		isHtml := params.qtype == queryHtml
		bc := newBackChannel(channel.Sid, rw, isHtml, params.domain, params.rid)
		bc.setChunked(params.chunked)
		channel.setBackChannel(bc)
		bc.wait()
	}
}
<commit_msg>Don't set chunked headers on init.<commit_after>// Copyright (c) 2013 Mathieu Turcotte
// Licensed under the MIT license.

// Package browserchannel provides a server-side browser channel
// implementation. See http://goo.gl/F287G for the client-side API.
package browserchannel

import (
	crand "crypto/rand"
	"encoding/json"
	"io"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
)

// The browser channel protocol version implemented by this library.
const SupportedProcolVersion = 8

const (
	// The path for the channel connection.
	DefaultBindPath = "bind"
	// The path for the test connection.
	DefaultTestPath = "test"
)

type queryType int

// Possible values for the query TYPE parameter.
const (
	queryUnset = iota
	queryTerminate
	queryXmlHttp
	queryHtml
	queryTest
)

func parseQueryType(s string) (qtype queryType) {
	switch s {
	case "html":
		qtype = queryHtml
	case "xmlhttp":
		qtype = queryXmlHttp
	case "terminate":
		qtype = queryTerminate
	case "test":
		qtype = queryTest
	}
	return
}

func (qtype queryType) setContentType(rw http.ResponseWriter) {
	if qtype == queryHtml {
		rw.Header().Set("Content-Type", "text/html")
	} else {
		rw.Header().Set("Content-Type", "text/plain")
	}
}

func parseAid(s string) (aid int, err error) {
	aid = -1
	if len(s) == 0 {
		return
	}
	aid, err = strconv.Atoi(s)
	return
}

func parseProtoVersion(s string) (version int) {
	version, err := strconv.Atoi(s)
	if err != nil {
		version = -1
	}
	return
}

type bindParams struct {
	cver    string
	sid     SessionId
	qtype   queryType
	domain  string
	rid     string
	aid     int
	chunked bool
	values  url.Values
	method  string
}

func parseBindParams(req *http.Request, values url.Values) (params *bindParams, err error) {
	cver := req.Form.Get("VER")
	qtype := parseQueryType(req.Form.Get("TYPE"))
	domain := req.Form.Get("DOMAIN")
	rid := req.Form.Get("zx")
	chunked := req.Form.Get("CI") == "0"
	sid, err := parseSessionId(req.Form.Get("SID"))
	if err != nil {
		return
	}
	aid, err := parseAid(req.Form.Get("AID"))
	if err != nil {
		return
	}
	params = &bindParams{cver, sid, qtype, domain, rid, aid, chunked, values, req.Method}
	return
}

type testParams struct {
	ver    int
	init   bool
	qtype  queryType
	domain string
}

func parseTestParams(req *http.Request) *testParams {
	version := parseProtoVersion(req.Form.Get("VER"))
	qtype := parseQueryType(req.Form.Get("TYPE"))
	domain := req.Form.Get("DOMAIN")
	init := req.Form.Get("MODE") == "init"
	return &testParams{version, init, qtype, domain}
}

var headers = map[string]string{
	"Cache-Control":          "no-cache, no-store, max-age=0, must-revalidate",
	"Expires":                "Fri, 01 Jan 1990 00:00:00 GMT",
	"X-Content-Type-Options": "nosniff",
	"Transfer-Encoding":      "chunked",
	"Pragma":                 "no-cache",
}

type channelMap struct {
	sync.RWMutex
	m map[SessionId]*Channel
}

func (m *channelMap) get(sid SessionId) *Channel {
	m.RLock()
	defer m.RUnlock()
	return m.m[sid]
}

func (m *channelMap) set(sid SessionId, channel *Channel) {
	m.Lock()
	defer m.Unlock()
	m.m[sid] = channel
}

func (m *channelMap) del(sid SessionId) (deleted bool) {
	m.Lock()
	defer m.Unlock()
	_, deleted = m.m[sid]
	delete(m.m, sid)
	return
}

// Contains the browser channel cross domain info for a single domain.
type crossDomainInfo struct {
	hostMatcher *regexp.Regexp
	domain      string
	prefixes    []string
}

func getHostPrefix(info *crossDomainInfo) string {
	if info != nil {
		return info.prefixes[rand.Intn(len(info.prefixes))]
	}
	return ""
}

// The browser channel HTTP handler will invoke its ChannelHandler in a
// goroutine for each new browser channel connection established.
type ChannelHandler func(*Channel)

// The browser channel http.Handler.
type Handler struct {
	corsInfo    *crossDomainInfo
	prefix      string
	channels    *channelMap
	bindPath    string
	testPath    string
	gcChan      chan SessionId
	chanHandler ChannelHandler
}

// Creates a new browser channel HTTP handler. The last path segment of the
// URL is used to distinguish bind and test connections.
func NewHandler(chanHandler ChannelHandler) (h *Handler) {
	h = new(Handler)
	h.channels = &channelMap{m: make(map[SessionId]*Channel)}
	h.bindPath = DefaultBindPath
	h.testPath = DefaultTestPath
	h.gcChan = make(chan SessionId, 10)
	h.chanHandler = chanHandler
	go h.removeClosedSession()
	return
}

// Sets the cross domain information for this browser channel. The origin is
// used as the Access-Control-Allow-Origin header value and should respect the
// format specified by http://www.w3.org/TR/cors/. The prefixes are used to set
// the hostPrefix parameter on the client side. The prefix assigned to each
// browser channel session is chosen randomly from the array of prefixes.
func (h *Handler) SetCrossDomainPrefix(domain string, prefixes []string) {
	h.corsInfo = &crossDomainInfo{makeOriginMatcher(domain), domain, prefixes}
}

// Removes closed channels from the handler's channel map.
func (h *Handler) removeClosedSession() {
	for {
		sid, ok := <-h.gcChan
		if !ok {
			break
		}

		log.Printf("removing %s from session map\n", sid)

		if !h.channels.del(sid) {
			log.Printf("missing channel for %s in session map\n", sid)
		}
	}
}

func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	// The CORS  spec only supports *, null or the exact domain.
	// http://www.w3.org/TR/cors/#access-control-allow-origin-response-header
	// http://tools.ietf.org/html/rfc6454#section-7.1
	origin := req.Header.Get("origin")
	if len(origin) > 0 && h.corsInfo != nil &&
		h.corsInfo.hostMatcher.MatchString(origin) {
		rw.Header().Set("Access-Control-Allow-Origin", origin)
		rw.Header().Set("Access-Control-Allow-Credentials", "true")
	}

	// The body is parsed before calling ParseForm so the values don't get
	// collapsed into a single collection.
	values, err := parseBody(req.Body)
	if err != nil {
		rw.WriteHeader(400)
		return
	}

	req.ParseForm()

	path := req.URL.Path
	if strings.HasSuffix(path, h.testPath) {
		h.handleTestRequest(rw, parseTestParams(req))
	} else if strings.HasSuffix(path, h.bindPath) {
		params, err := parseBindParams(req, values)
		if err != nil {
			rw.WriteHeader(400)
			return
		}
		h.handleBindRequest(rw, params)
	} else {
		rw.WriteHeader(404)
	}
}

func (h *Handler) handleTestRequest(rw http.ResponseWriter, params *testParams) {
	if params.ver != SupportedProcolVersion {
		rw.WriteHeader(400)
		io.WriteString(rw, "Unsupported protocol version.")
	} else if params.init {
		rw.WriteHeader(200)
		io.WriteString(rw, "[\""+getHostPrefix(h.corsInfo)+"\",\"\"]")
	} else {
		params.qtype.setContentType(rw)
		setHeaders(rw, &headers)
		rw.WriteHeader(200)

		if params.qtype == queryHtml {
			writeHtmlHead(rw)
			writeHtmlDomain(rw, params.domain)
			writeHtmlRpc(rw, "11111")
			writeHtmlPadding(rw)
		} else {
			io.WriteString(rw, "11111")
		}

		// It is important to flush the response at this point otherwise the
		// client won't receive the intermediate result and will disable the
		// chunking support by setting the CI parameter to 1 which tells the
		// server to close bind requests immediately. For reference, see
		// goog.net.BrowserTestChannel#onRequestComplete.
		rw.(http.Flusher).Flush()

		time.Sleep(2 * time.Second)

		if params.qtype == queryHtml {
			writeHtmlRpc(rw, "2")
			writeHtmlDone(rw)
		} else {
			io.WriteString(rw, "2")
		}
	}
}

func (h *Handler) handleBindRequest(rw http.ResponseWriter, params *bindParams) {
	var channel *Channel
	sid := params.sid

	// If the client has specified a session id, lookup the session object in
	// the sessions map. Lookup failure should be signaled to the client using
	// a 400 status code and a message containing 'Unknown SID'. See
	// goog/net/channelrequest.js for more context on how this error is
	// handled.
	if sid != nullSessionId {
		channel = h.channels.get(sid)
		if channel == nil {
			log.Printf("failed to lookup session %s\n", sid)
			setHeaders(rw, &headers)
			rw.WriteHeader(400)
			io.WriteString(rw, "Unknown SID")
			return
		}
	}

	if channel == nil {
		sid, _ = generateSesionId(crand.Reader)
		log.Printf("creating session %s\n", sid)
		channel = newChannel(params.cver, sid, h.gcChan, h.corsInfo)
		h.channels.set(sid, channel)
		channel.armChannelTimeout()
		go h.chanHandler(channel)
	}

	if params.aid != -1 {
		channel.acknowledgeArrays(params.aid)
	}

	switch params.method {
	case "POST":
		h.handleBindPost(rw, params, channel)
	case "GET":
		h.handleBindGet(rw, params, channel)
	default:
		rw.WriteHeader(400)
	}
}

func (h *Handler) handleBindPost(rw http.ResponseWriter, params *bindParams, channel *Channel) {
	offset, maps, err := parseIncomingMaps(params.values)
	if err != nil {
		rw.WriteHeader(400)
		return
	}

	if err := channel.receiveMaps(offset, maps); err != nil {
		log.Printf("%s: %s\n", channel.Sid, err)
		rw.WriteHeader(500)
		return
	}

	if channel.state == channelInit {
		setHeaders(rw, &headers)
		rw.WriteHeader(200)
		rw.(http.Flusher).Flush()

		// The initial forward request is used as a back channel to send the
		// server configuration: ['c', id, host, version]. This payload has to
		// be sent immediately, so the streaming is disabled on the back
		// channel. Note that the first bind request made by IE<10 does not
		// contain a TYPE=html query parameter and therefore receives the same
		// length prefixed array reply as is sent to the XHR streaming clients.
		backChannel := newBackChannel(channel.Sid, rw, false, "", params.rid)
		channel.setBackChannel(backChannel)
		backChannel.wait()
	} else {
		// On normal forward channel request, the session status is returned
		// to the client. The session status contains 3 pieces of information:
		// does this session has a back channel, the last array id sent to the
		// client and the number of outstanding bytes in the back channel.
		b, _ := json.Marshal(channel.getState())
		setHeaders(rw, &headers)
		rw.WriteHeader(200)
		io.WriteString(rw, strconv.FormatInt(int64(len(b)), 10)+"\n")
		rw.Write(b)
	}
}

func (h *Handler) handleBindGet(rw http.ResponseWriter, params *bindParams, channel *Channel) {
	if params.qtype == queryTerminate {
		channel.terminate()
	} else {
		params.qtype.setContentType(rw)
		setHeaders(rw, &headers)
		rw.WriteHeader(200)
		rw.(http.Flusher).Flush()

		isHtml := params.qtype == queryHtml
		bc := newBackChannel(channel.Sid, rw, isHtml, params.domain, params.rid)
		bc.setChunked(params.chunked)
		channel.setBackChannel(bc)
		bc.wait()
	}
}
<|endoftext|>


<commit_before>package object

import (
	"encoding/json"

	"gopkg.in/mgo.v2/bson"
)

// NOTE(rjeczalik): Inliner is not suitable to inline large values (>1MB)

type (
	InlineFirst  interface{}
	InlineSecond interface{}
)

// TODO(rjeczalik): inlining YAML may not work correctly
// due to:
//
//   https://github.com/go-yaml/yaml/issues/55
//
// Either add workaround, like the one for BSON, or fix YAML package.
type Inliner struct {
	InlineFirst  `bson:",inline" yaml:",inline"`
	InlineSecond `bson:",inline" yaml:",inline"`
}

func Inline(v1, v2 interface{}) *Inliner {
	return &Inliner{
		InlineFirst:  v1,
		InlineSecond: v2,
	}
}

var (
	_ json.Marshaler   = (*Inliner)(nil)
	_ json.Unmarshaler = (*Inliner)(nil)
	_ bson.Getter      = (*Inliner)(nil)
	_ bson.Setter      = (*Inliner)(nil)
)

func (in *Inliner) MarshalJSON() ([]byte, error) {
	obj, err := in.Inline()
	if err != nil {
		return nil, err
	}

	return json.Marshal(obj)
}

func (in *Inliner) UnmarshalJSON(p []byte) error {
	if err := json.Unmarshal(p, in.FirstAddr()); err != nil {
		return err
	}

	return json.Unmarshal(p, in.SecondAddr())
}

func (in *Inliner) Inline() (Object, error) {
	first, err := ToJSON(in.InlineFirst)
	if err != nil {
		return nil, err
	}

	second, err := ToJSON(in.InlineSecond)
	if err != nil {
		return nil, err
	}

	for k, v := range second {
		if _, ok := first[k]; !ok {
			first[k] = v
		}
	}

	return Object(first), nil
}

// GetBSON is a workaround for bson.Marshal not allowing
// for pointers to structs with ,inline option.
// It fails with:
//
//   Option ,inline needs a struct value or map field
//
// It should eventually be fixed and workaround dropped.
func (in *Inliner) GetBSON() (interface{}, error) {
	first, err := ToBSON(in.InlineFirst)
	if err != nil {
		return nil, err
	}

	second, err := ToBSON(in.InlineSecond)
	if err != nil {
		return nil, err
	}

	for k, v := range second {
		if _, ok := first[k]; !ok {
			first[k] = v
		}
	}

	return Object(first), nil
}

// SetBSON is a workaround for bson.Marshal not allowing
// for pointers to structs with ,inline option.
// It fails with:
//
//   Option ,inline needs a struct value or map field
//
// It should eventually be fixed and workaround dropped.
func (in *Inliner) SetBSON(raw bson.Raw) error {
	if err := raw.Unmarshal(in.SecondAddr()); err != nil {
		return err
	}

	return raw.Unmarshal(in.FirstAddr())
}

func (in *Inliner) FirstAddr() interface{} {
	return addr(in.InlineFirst)
}

func (in *Inliner) SecondAddr() interface{} {
	return addr(in.InlineSecond)
}

func addr(v interface{}) interface{} {
	switch v := v.(type) {
	case map[string]interface{}:
		return &v
	case []interface{}:
		return &v
	case []map[string]interface{}:
		return &v
	default:
		return v
	}
}

func ToJSON(v interface{}) (obj Object, err error) {
	p, err := json.Marshal(v)
	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(p, &obj); err != nil {
		return nil, err
	}

	return obj, nil
}

func ToBSON(v interface{}) (obj Object, err error) {
	p, err := bson.Marshal(v)
	if err != nil {
		return nil, err
	}

	if err := bson.Unmarshal(p, &obj); err != nil {
		return nil, err
	}

	return obj, nil
}
<commit_msg>object/inliner: export ToAddr (addr) function<commit_after>package object

import (
	"encoding/json"

	"gopkg.in/mgo.v2/bson"
)

// NOTE(rjeczalik): Inliner is not suitable to inline large values (>1MB)

type (
	InlineFirst  interface{}
	InlineSecond interface{}
)

// TODO(rjeczalik): inlining YAML may not work correctly
// due to:
//
//   https://github.com/go-yaml/yaml/issues/55
//
// Either add workaround, like the one for BSON, or fix YAML package.
type Inliner struct {
	InlineFirst  `bson:",inline" yaml:",inline"`
	InlineSecond `bson:",inline" yaml:",inline"`
}

func Inline(v1, v2 interface{}) *Inliner {
	return &Inliner{
		InlineFirst:  v1,
		InlineSecond: v2,
	}
}

var (
	_ json.Marshaler   = (*Inliner)(nil)
	_ json.Unmarshaler = (*Inliner)(nil)
	_ bson.Getter      = (*Inliner)(nil)
	_ bson.Setter      = (*Inliner)(nil)
)

func (in *Inliner) MarshalJSON() ([]byte, error) {
	obj, err := in.Inline()
	if err != nil {
		return nil, err
	}

	return json.Marshal(obj)
}

func (in *Inliner) UnmarshalJSON(p []byte) error {
	if err := json.Unmarshal(p, in.FirstAddr()); err != nil {
		return err
	}

	return json.Unmarshal(p, in.SecondAddr())
}

func (in *Inliner) Inline() (Object, error) {
	first, err := ToJSON(in.InlineFirst)
	if err != nil {
		return nil, err
	}

	second, err := ToJSON(in.InlineSecond)
	if err != nil {
		return nil, err
	}

	for k, v := range second {
		if _, ok := first[k]; !ok {
			first[k] = v
		}
	}

	return Object(first), nil
}

// GetBSON is a workaround for bson.Marshal not allowing
// for pointers to structs with ,inline option.
// It fails with:
//
//   Option ,inline needs a struct value or map field
//
// It should eventually be fixed and workaround dropped.
func (in *Inliner) GetBSON() (interface{}, error) {
	first, err := ToBSON(in.InlineFirst)
	if err != nil {
		return nil, err
	}

	second, err := ToBSON(in.InlineSecond)
	if err != nil {
		return nil, err
	}

	for k, v := range second {
		if _, ok := first[k]; !ok {
			first[k] = v
		}
	}

	return Object(first), nil
}

// SetBSON is a workaround for bson.Marshal not allowing
// for pointers to structs with ,inline option.
// It fails with:
//
//   Option ,inline needs a struct value or map field
//
// It should eventually be fixed and workaround dropped.
func (in *Inliner) SetBSON(raw bson.Raw) error {
	if err := raw.Unmarshal(in.SecondAddr()); err != nil {
		return err
	}

	return raw.Unmarshal(in.FirstAddr())
}

func (in *Inliner) FirstAddr() interface{} {
	return ToAddr(in.InlineFirst)
}

func (in *Inliner) SecondAddr() interface{} {
	return ToAddr(in.InlineSecond)
}

func ToAddr(v interface{}) interface{} {
	switch v := v.(type) {
	case map[string]interface{}:
		return &v
	case []interface{}:
		return &v
	case []map[string]interface{}:
		return &v
	default:
		return v
	}
}

func ToJSON(v interface{}) (obj Object, err error) {
	p, err := json.Marshal(v)
	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(p, &obj); err != nil {
		return nil, err
	}

	return obj, nil
}

func ToBSON(v interface{}) (obj Object, err error) {
	p, err := bson.Marshal(v)
	if err != nil {
		return nil, err
	}

	if err := bson.Unmarshal(p, &obj); err != nil {
		return nil, err
	}

	return obj, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"koding/db/models"
	"koding/db/mongodb"
	"koding/db/mongodb/modelhelper"
	"koding/tools/config"
	"koding/tools/logger"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"

	"github.com/op/go-logging"
)

var (
	log                 *logging.Logger
	MAX_ITERATION_COUNT = 50
)

func init() {
	log = logger.CreateLogger("Profile Meta Counts Migrator", "debug")
}

func main() {

	err := mongodb.Run("jAccounts", updateFunc())
	if err != nil {
		log.Info("error on accouint query.", err)
	}
}
func updateFunc() func(coll *mgo.Collection) error {
	return func(coll *mgo.Collection) error {
		query := coll.Find(modelhelper.Selector{})

		totalCount, err := query.Count()
		if err != nil {
			log.Info("Err while getting count, exiting", err)
			return err
		}

		log.Info("totalCount", totalCount)

		fmt.Println(config.Skip, config.Count)
		skip := config.Skip
		// this is a starting point
		index := skip
		// this is the item count to be processed
		limit := config.Count
		// this will be the ending point
		count := index + limit

		var account models.Account

		iteration := 0
		for {
			// if we reach to the end of the all collection, exit
			if index >= totalCount {
				log.Info("All items are processed, exiting")
				break
			}

			// this is the max re-iterating count
			if iteration == MAX_ITERATION_COUNT {
				break
			}

			// if we processed all items then exit
			if index == count {
				break
			}

			iter := query.Skip(index).Limit(count - index).Iter()
			for iter.Next(&account) {
				err := updateCounts(account.Id)
				if err != nil {
					log.Info("Err while updating account", err)
				}
				// break
				index++
				fmt.Println(index)
			}

			if err := iter.Close(); err != nil {
				log.Info("error on iter", err)
			}

			if iter.Timeout() {
				continue
			}

			log.Info("iter existed, starting over from %v  -- %v  item(s) are processsed on this iter", index+1, index-skip)
			iteration++
		}
		if iteration == MAX_ITERATION_COUNT {
			log.Debug("Max iteration count %v reached, exiting", iteration)
		}
		log.Debug("Synced %v entries on this process", index-skip)

		return nil
	}
}

func updateCounts(id bson.ObjectId) error {
	toBeUpdatedFields := modelhelper.Selector{}

	toBeUpdatedFields["counts.likes"] = getLikeCount(id)
	toBeUpdatedFields["counts.following"] = getFollowingCount(id)
	toBeUpdatedFields["counts.followers"] = getFollowerCount(id)
	toBeUpdatedFields["counts.topics"] = getTopicCount(id)
	toBeUpdatedFields["counts.statusUpdates"] = getAuthorCount(id)
	toBeUpdatedFields["counts.comments"] = getCommentCount(id)
	toBeUpdated := modelhelper.Selector{"$set": toBeUpdatedFields}
	return modelhelper.UpdateAccount(modelhelper.Selector{"_id": id}, toBeUpdated)

}
func getLikeCount(id bson.ObjectId) int {
	selector := modelhelper.Selector{
		"targetId":   id,
		"targetName": "JAccount",
		"as":         "like",
	}
	count, err := modelhelper.RelationshipCount(selector)
	if err != nil {
		log.Info("like err", err)
		count = 0
	}
	return count
}

func getFollowingCount(id bson.ObjectId) int {
	selector := modelhelper.Selector{
		"targetId":   id,
		"targetName": "JAccount",
		"sourceName": "JAccount",
		"as":         "follower",
	}
	count, err := modelhelper.RelationshipCount(selector)
	if err != nil {
		log.Info("following err", err)
		count = 0
	}
	return count
}

func getFollowerCount(id bson.ObjectId) int {
	selector := modelhelper.Selector{
		"sourceId":   id,
		"targetName": "JAccount",
		"sourceName": "JAccount",
		"as":         "follower",
	}
	count, err := modelhelper.RelationshipCount(selector)
	if err != nil {
		log.Info("follower", err)
		count = 0
	}
	return count
}

func getTopicCount(id bson.ObjectId) int {
	selector := modelhelper.Selector{
		"targetId":   id,
		"targetName": "JAccount",
		"sourceName": "JTag",
		"as":         "follower",
	}
	count, err := modelhelper.RelationshipCount(selector)
	if err != nil {
		log.Info("topic", err)
		count = 0
	}
	return count
}

func getAuthorCount(id bson.ObjectId) int {
	selector := modelhelper.Selector{
		"targetId":   id,
		"targetName": "JAccount",
		"sourceName": "JNewStatusUpdate",
		"as":         "author",
	}
	count, err := modelhelper.RelationshipCount(selector)
	if err != nil {
		log.Info("statu update count", err)
		count = 0
	}
	return count
}

func getCommentCount(id bson.ObjectId) int {
	selector := modelhelper.Selector{
		"targetId":   id,
		"targetName": "JAccount",
		"sourceName": "JNewStatusUpdate",
		"as":         "commenter",
	}
	count, err := modelhelper.RelationshipCount(selector)
	if err != nil {
		log.Info("commet count", err)
		count = 0
	}
	return count
}
<commit_msg>ProfileCountUpdater: add account id into logs<commit_after>package main

import (
	"fmt"
	"koding/db/models"
	"koding/db/mongodb"
	"koding/db/mongodb/modelhelper"
	"koding/tools/config"
	"koding/tools/logger"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"

	"github.com/op/go-logging"
)

var (
	log                 *logging.Logger
	MAX_ITERATION_COUNT = 50
)

func init() {
	log = logger.CreateLogger("Profile Meta Counts Migrator", "debug")
}

func main() {

	err := mongodb.Run("jAccounts", updateFunc())
	if err != nil {
		log.Info("error on accouint query.", err)
	}
}
func updateFunc() func(coll *mgo.Collection) error {
	return func(coll *mgo.Collection) error {
		query := coll.Find(modelhelper.Selector{})

		totalCount, err := query.Count()
		if err != nil {
			log.Info("Err while getting count, exiting", err)
			return err
		}

		log.Info("totalCount", totalCount)

		fmt.Println(config.Skip, config.Count)
		skip := config.Skip
		// this is a starting point
		index := skip
		// this is the item count to be processed
		limit := config.Count
		// this will be the ending point
		count := index + limit

		var account models.Account

		iteration := 0
		for {
			// if we reach to the end of the all collection, exit
			if index >= totalCount {
				log.Info("All items are processed, exiting")
				break
			}

			// this is the max re-iterating count
			if iteration == MAX_ITERATION_COUNT {
				break
			}

			// if we processed all items then exit
			if index == count {
				break
			}

			iter := query.Skip(index).Limit(count - index).Iter()
			for iter.Next(&account) {
				log.Debug("Account id", account.Id)
				err := updateCounts(account.Id)
				if err != nil {
					log.Info("Err while updating account", err)
				}
				// break
				index++
				fmt.Println(index)
			}

			if err := iter.Close(); err != nil {
				log.Info("error on iter", err)
			}

			if iter.Timeout() {
				continue
			}

			log.Info("iter existed, starting over from %v  -- %v  item(s) are processsed on this iter", index+1, index-skip)
			iteration++
		}
		if iteration == MAX_ITERATION_COUNT {
			log.Debug("Max iteration count %v reached, exiting", iteration)
		}
		log.Debug("Synced %v entries on this process", index-skip)

		return nil
	}
}

func updateCounts(id bson.ObjectId) error {
	toBeUpdatedFields := modelhelper.Selector{}

	toBeUpdatedFields["counts.likes"] = getLikeCount(id)
	toBeUpdatedFields["counts.following"] = getFollowingCount(id)
	toBeUpdatedFields["counts.followers"] = getFollowerCount(id)
	toBeUpdatedFields["counts.topics"] = getTopicCount(id)
	toBeUpdatedFields["counts.statusUpdates"] = getAuthorCount(id)
	toBeUpdatedFields["counts.comments"] = getCommentCount(id)
	toBeUpdated := modelhelper.Selector{"$set": toBeUpdatedFields}
	return modelhelper.UpdateAccount(modelhelper.Selector{"_id": id}, toBeUpdated)

}
func getLikeCount(id bson.ObjectId) int {
	selector := modelhelper.Selector{
		"targetId":   id,
		"targetName": "JAccount",
		"as":         "like",
	}
	count, err := modelhelper.RelationshipCount(selector)
	if err != nil {
		log.Info("like err", err)
		count = 0
	}
	return count
}

func getFollowingCount(id bson.ObjectId) int {
	selector := modelhelper.Selector{
		"targetId":   id,
		"targetName": "JAccount",
		"sourceName": "JAccount",
		"as":         "follower",
	}
	count, err := modelhelper.RelationshipCount(selector)
	if err != nil {
		log.Info("following err", err)
		count = 0
	}
	return count
}

func getFollowerCount(id bson.ObjectId) int {
	selector := modelhelper.Selector{
		"sourceId":   id,
		"targetName": "JAccount",
		"sourceName": "JAccount",
		"as":         "follower",
	}
	count, err := modelhelper.RelationshipCount(selector)
	if err != nil {
		log.Info("follower", err)
		count = 0
	}
	return count
}

func getTopicCount(id bson.ObjectId) int {
	selector := modelhelper.Selector{
		"targetId":   id,
		"targetName": "JAccount",
		"sourceName": "JTag",
		"as":         "follower",
	}
	count, err := modelhelper.RelationshipCount(selector)
	if err != nil {
		log.Info("topic", err)
		count = 0
	}
	return count
}

func getAuthorCount(id bson.ObjectId) int {
	selector := modelhelper.Selector{
		"targetId":   id,
		"targetName": "JAccount",
		"sourceName": "JNewStatusUpdate",
		"as":         "author",
	}
	count, err := modelhelper.RelationshipCount(selector)
	if err != nil {
		log.Info("statu update count", err)
		count = 0
	}
	return count
}

func getCommentCount(id bson.ObjectId) int {
	selector := modelhelper.Selector{
		"targetId":   id,
		"targetName": "JAccount",
		"sourceName": "JNewStatusUpdate",
		"as":         "commenter",
	}
	count, err := modelhelper.RelationshipCount(selector)
	if err != nil {
		log.Info("commet count", err)
		count = 0
	}
	return count
}
<|endoftext|>


<commit_before>package handlers

import (
	"net/http"
	"socialapi/workers/api/modules/account"
	"socialapi/workers/api/modules/activity"
	"socialapi/workers/api/modules/channel"
	"socialapi/workers/api/modules/interaction"
	"socialapi/workers/api/modules/message"
	"socialapi/workers/api/modules/messagelist"
	"socialapi/workers/api/modules/notification"
	"socialapi/workers/api/modules/participant"
	"socialapi/workers/api/modules/popular"
	"socialapi/workers/api/modules/privatemessage"
	"socialapi/workers/api/modules/reply"

	"github.com/rcrowley/go-tigertonic"
)

var (
	cors = tigertonic.NewCORSBuilder().AddAllowedOrigins("*")
)

func handlerWrapper(handler interface{}, logName string) http.Handler {
	return cors.Build(
		tigertonic.Timed(
			tigertonic.Marshaled(
				handler,
			),
			logName,
			nil,
		))
}

// todo implement context support here for requests
func Inject(mux *tigertonic.TrieServeMux) *tigertonic.TrieServeMux {

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Message Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// tested
	mux.Handle("POST", "/message/{id}", handlerWrapper(message.Update, "message-update"))
	// tested
	mux.Handle("DELETE", "/message/{id}", handlerWrapper(message.Delete, "message-delete"))
	// tested
	mux.Handle("GET", "/message/{id}", handlerWrapper(message.Get, "message-get"))
	mux.Handle("GET", "/message/{id}/related", handlerWrapper(message.GetWithRelated, "message-get"))

	////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////// Message Reply Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// tested
	mux.Handle("POST", "/message/{id}/reply", handlerWrapper(reply.Create, "reply-create"))

	// tested
	mux.Handle("GET", "/message/{id}/reply", handlerWrapper(reply.List, "reply-list"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////// Message Interaction Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// add new like
	// tested
	mux.Handle("POST", "/message/{id}/interaction/{type}/add", handlerWrapper(interaction.Add, "interactions-add"))
	// delete like - unlike
	// tested
	mux.Handle("POST", "/message/{id}/interaction/{type}/delete", handlerWrapper(interaction.Delete, "interactions-delete"))
	// get all the interactions for message
	// mux.Handle("GET", "/message/{id}/interaction", handlerWrapper(interaction.List, "interactions-list"))
	// get typed interactions
	// tested
	mux.Handle("GET", "/message/{id}/interaction/{type}", handlerWrapper(interaction.List, "interactions-list-typed"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Channel Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// tested
	mux.Handle("POST", "/channel", handlerWrapper(channel.Create, "channel-create"))

	mux.Handle("GET", "/channel", handlerWrapper(channel.List, "channel-list"))
	// tested
	// deprecated, here for socialworker
	mux.Handle("POST", "/channel/{id}", handlerWrapper(channel.Update, "channel-update"))
	//
	mux.Handle("POST", "/channel/{id}/update", handlerWrapper(channel.Update, "channel-update"))
	// tested
	mux.Handle("POST", "/channel/{id}/delete", handlerWrapper(channel.Delete, "channel-delete"))
	// tested
	mux.Handle("GET", "/channel/{id}", handlerWrapper(channel.Get, "channel-get"))

	// add a new messages to the channel
	// tested
	mux.Handle("POST", "/channel/{id}/message", handlerWrapper(message.Create, "channel-message-create"))

	// list participants of the channel
	// tested
	mux.Handle("GET", "/channel/{id}/participant", handlerWrapper(participant.List, "participant-list"))

	// add participant to the channel
	//tested
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/add", handlerWrapper(participant.Add, "participant-list"))

	// remove participant from the channel
	// tested
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/delete", handlerWrapper(participant.Delete, "participant-list"))

	// list messages of the channel
	mux.Handle("GET", "/channel/{id}/history", handlerWrapper(messagelist.List, "channel-history-list"))

	// register an account
	mux.Handle("POST", "/account", handlerWrapper(account.Register, "account-create"))

	// list channels of the account
	mux.Handle("GET", "/account/{id}/channels", handlerWrapper(account.ListChannels, "account-channel-list"))

	// follow the account
	mux.Handle("POST", "/account/{id}/follow", handlerWrapper(account.Follow, "account-follow"))

	// un-follow the account
	mux.Handle("POST", "/account/{id}/unfollow", handlerWrapper(account.Unfollow, "account-unfollow"))

	// fetch profile feed
	// mux.Handle("GET", "/account/{id}/profile/feed", handlerWrapper(account.ListProfileFeed, "list-profile-feed"))

	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/channel", handlerWrapper(activity.GetPinnedActivityChannel, "activity-pin-get-channel"))

	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/list", handlerWrapper(activity.List, "activity-pin-list-message"))

	// pin a new status update
	mux.Handle("POST", "/activity/pin/add", handlerWrapper(activity.PinMessage, "activity-add-pinned-message"))

	// unpin a status update
	mux.Handle("POST", "/activity/pin/remove", handlerWrapper(activity.UnpinMessage, "activity-remove-pinned-message"))

	// get popular topics
	mux.Handle("GET", "/popular/topics/{statisticName}", handlerWrapper(popular.ListTopics, "list-popular-topics"))

	// list notifications
	mux.Handle("GET", "/notification/{accountId}", handlerWrapper(notification.List, "notification-list"))

	// mux.Handle("POST", "/follow/{id}", handlerWrapper(post, "follow-id"))
	// mux.Handle("POST", "/unfollow/{id}", handlerWrapper(post, "follow-id"))

	mux.Handle("POST", "/privatemessage/send", handlerWrapper(privatemessage.Send, "privatemessage-send"))

	mux.Handle("GET", "/privatemessage/list", handlerWrapper(privatemessage.List, "privatemessage-list"))

	return mux
}

// to-do list
// get current account from context for future
// like client.connection.delegate
<commit_msg>Social: notification glance handler is registered as handler<commit_after>package handlers

import (
	"net/http"
	"socialapi/workers/api/modules/account"
	"socialapi/workers/api/modules/activity"
	"socialapi/workers/api/modules/channel"
	"socialapi/workers/api/modules/interaction"
	"socialapi/workers/api/modules/message"
	"socialapi/workers/api/modules/messagelist"
	"socialapi/workers/api/modules/notification"
	"socialapi/workers/api/modules/participant"
	"socialapi/workers/api/modules/popular"
	"socialapi/workers/api/modules/privatemessage"
	"socialapi/workers/api/modules/reply"

	"github.com/rcrowley/go-tigertonic"
)

var (
	cors = tigertonic.NewCORSBuilder().AddAllowedOrigins("*")
)

func handlerWrapper(handler interface{}, logName string) http.Handler {
	return cors.Build(
		tigertonic.Timed(
			tigertonic.Marshaled(
				handler,
			),
			logName,
			nil,
		))
}

// todo implement context support here for requests
func Inject(mux *tigertonic.TrieServeMux) *tigertonic.TrieServeMux {

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Message Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// tested
	mux.Handle("POST", "/message/{id}", handlerWrapper(message.Update, "message-update"))
	// tested
	mux.Handle("DELETE", "/message/{id}", handlerWrapper(message.Delete, "message-delete"))
	// tested
	mux.Handle("GET", "/message/{id}", handlerWrapper(message.Get, "message-get"))
	mux.Handle("GET", "/message/{id}/related", handlerWrapper(message.GetWithRelated, "message-get"))

	////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////// Message Reply Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// tested
	mux.Handle("POST", "/message/{id}/reply", handlerWrapper(reply.Create, "reply-create"))

	// tested
	mux.Handle("GET", "/message/{id}/reply", handlerWrapper(reply.List, "reply-list"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////// Message Interaction Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// add new like
	// tested
	mux.Handle("POST", "/message/{id}/interaction/{type}/add", handlerWrapper(interaction.Add, "interactions-add"))
	// delete like - unlike
	// tested
	mux.Handle("POST", "/message/{id}/interaction/{type}/delete", handlerWrapper(interaction.Delete, "interactions-delete"))
	// get all the interactions for message
	// mux.Handle("GET", "/message/{id}/interaction", handlerWrapper(interaction.List, "interactions-list"))
	// get typed interactions
	// tested
	mux.Handle("GET", "/message/{id}/interaction/{type}", handlerWrapper(interaction.List, "interactions-list-typed"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Channel Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// tested
	mux.Handle("POST", "/channel", handlerWrapper(channel.Create, "channel-create"))

	mux.Handle("GET", "/channel", handlerWrapper(channel.List, "channel-list"))
	// tested
	// deprecated, here for socialworker
	mux.Handle("POST", "/channel/{id}", handlerWrapper(channel.Update, "channel-update"))
	//
	mux.Handle("POST", "/channel/{id}/update", handlerWrapper(channel.Update, "channel-update"))
	// tested
	mux.Handle("POST", "/channel/{id}/delete", handlerWrapper(channel.Delete, "channel-delete"))
	// tested
	mux.Handle("GET", "/channel/{id}", handlerWrapper(channel.Get, "channel-get"))

	// add a new messages to the channel
	// tested
	mux.Handle("POST", "/channel/{id}/message", handlerWrapper(message.Create, "channel-message-create"))

	// list participants of the channel
	// tested
	mux.Handle("GET", "/channel/{id}/participant", handlerWrapper(participant.List, "participant-list"))

	// add participant to the channel
	//tested
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/add", handlerWrapper(participant.Add, "participant-list"))

	// remove participant from the channel
	// tested
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/delete", handlerWrapper(participant.Delete, "participant-list"))

	// list messages of the channel
	mux.Handle("GET", "/channel/{id}/history", handlerWrapper(messagelist.List, "channel-history-list"))

	// register an account
	mux.Handle("POST", "/account", handlerWrapper(account.Register, "account-create"))

	// list channels of the account
	mux.Handle("GET", "/account/{id}/channels", handlerWrapper(account.ListChannels, "account-channel-list"))

	// follow the account
	mux.Handle("POST", "/account/{id}/follow", handlerWrapper(account.Follow, "account-follow"))

	// un-follow the account
	mux.Handle("POST", "/account/{id}/unfollow", handlerWrapper(account.Unfollow, "account-unfollow"))

	// fetch profile feed
	// mux.Handle("GET", "/account/{id}/profile/feed", handlerWrapper(account.ListProfileFeed, "list-profile-feed"))

	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/channel", handlerWrapper(activity.GetPinnedActivityChannel, "activity-pin-get-channel"))

	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/list", handlerWrapper(activity.List, "activity-pin-list-message"))

	// pin a new status update
	mux.Handle("POST", "/activity/pin/add", handlerWrapper(activity.PinMessage, "activity-add-pinned-message"))

	// unpin a status update
	mux.Handle("POST", "/activity/pin/remove", handlerWrapper(activity.UnpinMessage, "activity-remove-pinned-message"))

	// get popular topics
	mux.Handle("GET", "/popular/topics/{statisticName}", handlerWrapper(popular.ListTopics, "list-popular-topics"))

	// list notifications
	mux.Handle("GET", "/notification/{accountId}", handlerWrapper(notification.List, "notification-list"))

	// glance notifications
	mux.Handle("POST", "/notification/{accountId}/glance", handlerWrapper(notification.Glance, "notification-glance"))
	// mux.Handle("POST", "/follow/{id}", handlerWrapper(post, "follow-id"))
	// mux.Handle("POST", "/unfollow/{id}", handlerWrapper(post, "follow-id"))

	mux.Handle("POST", "/privatemessage/send", handlerWrapper(privatemessage.Send, "privatemessage-send"))

	mux.Handle("GET", "/privatemessage/list", handlerWrapper(privatemessage.List, "privatemessage-list"))

	return mux
}

// to-do list
// get current account from context for future
// like client.connection.delegate
<|endoftext|>