<commit_before>package resolver

import (
	"container/ring"
	"errors"
	"fmt"
	"koding/db/models"
	"koding/db/mongodb/modelhelper"
	"koding/kontrol/kontrolproxy/utils"
	"log"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"sync"
	"time"
)

var (
	ErrGone   = errors.New("target is gone")
	ErrNoHost = errors.New("no healthy hostname available")

	// cache lookup tables
	targets   = make(map[string]*Target)
	targetsMu sync.Mutex // protects targets map

	// our load balancer roundrobin maps. Key is created for each index key.
	rings   = make(map[string]*roundRing)
	ringsMu sync.Mutex
)

const (
	CookieBuildName  = "kdproxy-preferred-build"
	CookieDomainName = "kdproxy-preferred-domain"

	ModeMaintenance = "maintenance"
	ModeInternal    = "internal"
	ModeVM          = "vm"
	ModeVMOff       = "vmOff"
	ModeRedirect    = "redirect"

	HealthCheckInterval = time.Second * 10
)

// Target is returned for every incoming request host.
type Target struct {
	// URL contains the final target
	URL *url.URL

	// A list of hosts, mainly used for vm mode
	HostnameAlias []string

	// Information used to resolve to the final target.
	Proxy *models.ProxyTable

	// FetchedAt contains the time the target was fetched and stored in our
	// internal map. Useful for caching.
	FetchedAt time.Time

	// FetchedSource contains the source information from which the target is
	// obtained.
	FetchedSource string

	// CacheTimeout is used to invalidate the target. Zero duration disables the cache.
	CacheTimeout time.Duration

	// HostCacheDisabled is used to disable caching host based targets.
	HostCacheDisabled bool
}

// GetTarget is used to get the target according to the incoming request it
// looks if a request has set cookies to change the target. If not it
// fallbacks to request host data.
func GetTarget(req *http.Request) (*Target, error) {
	cookieBuild, err := req.Cookie(CookieBuildName)
	if err == nil {
		service := service{
			username:    "koding",
			servicename: "server",
			build:       cookieBuild.Value,
		}

		t, err := service.target()
		if err == nil {
			log.Printf("proxy target is overridden by cookie build: '%s'\n", cookieBuild.Value)
			return t, nil
		}
	}

	cookieDomain, err := req.Cookie(CookieDomainName)
	if err == nil {
		t, err := TargetByHost(cookieDomain.Value)
		if err == nil {
			log.Printf("proxy target is overrriden by cookie domain: '%s'\n", cookieDomain.Value)
			return t, nil
		}
	}

	// if cookie is not set, use request host data, this is also a fallback
	return TargetByHost(req.Host)
}

// TargetByHost is used to resolve any hostname to their final target
// destination together with the mode of the domain for the given host string.
// Any incoming domain can have multiple different target destinations.
// TargetByHost returns the ultimate target destinations. Some examples:
//
// koding.com -> "http://webserver-build-koding-813a.in.koding.com:3000", mode:internal
// arslan.kd.io -> "http://10.128.2.25:80", mode:vm
// y.koding.com -> "http://localhost/maintenance", mode:maintenance
func TargetByHost(host string) (*Target, error) {
	target, err := getFromCache(host)
	if err == nil {
		return target, nil
	}

	return getFromDB(host)
}

func getFromCache(host string) (*Target, error) {
	targetsMu.Lock()
	defer targetsMu.Unlock()

	target, ok := targets[host]
	if !ok {
		return nil, fmt.Errorf("target not found for %s", host)
	}

	if target.HostCacheDisabled {
		target.FetchedSource = "InternalCache"
		return target, nil
	}

	if time.Now().Before(target.FetchedAt.Add(target.CacheTimeout)) {
		target.FetchedSource = "Cache"
		return target, nil
	}

	return nil, fmt.Errorf("target cache invalidaiton for host %s", host)
}

func getFromDB(host string) (*Target, error) {
	domain, err := getDomain(host)
	if err != nil {
		return nil, err
	}

	target := &Target{
		Proxy:         domain.Proxy,
		HostnameAlias: domain.HostnameAlias,
		CacheTimeout:  time.Second * 20,
		FetchedAt:     time.Now(),
		FetchedSource: "MongoDB",
	}

	err = target.Resolve(host)
	if err != nil {
		return nil, err
	}

	targetsMu.Lock()
	targets[host] = target
	targetsMu.Unlock()

	return target, nil
}

func getDomain(host string) (*models.Domain, error) {
	domain := new(models.Domain)
	var err error

	domain, err = modelhelper.GetDomain(host)<commit_msg>kontrolproxy: remove port when doing db lookup<commit_after>package resolver

import (
	"container/ring"
	"errors"
	"fmt"
	"koding/db/models"
	"koding/db/mongodb/modelhelper"
	"koding/kontrol/kontrolproxy/utils"
	"log"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"sync"
	"time"
)

var (
	ErrGone   = errors.New("target is gone")
	ErrNoHost = errors.New("no healthy hostname available")

	// cache lookup tables
	targets   = make(map[string]*Target)
	targetsMu sync.Mutex // protects targets map

	// our load balancer roundrobin maps. Key is created for each index key.
	rings   = make(map[string]*roundRing)
	ringsMu sync.Mutex
)

const (
	CookieBuildName  = "kdproxy-preferred-build"
	CookieDomainName = "kdproxy-preferred-domain"

	ModeMaintenance = "maintenance"
	ModeInternal    = "internal"
	ModeVM          = "vm"
	ModeVMOff       = "vmOff"
	ModeRedirect    = "redirect"

	HealthCheckInterval = time.Second * 10
)

// Target is returned for every incoming request host.
type Target struct {
	// URL contains the final target
	URL *url.URL

	// A list of hosts, mainly used for vm mode
	HostnameAlias []string

	// Information used to resolve to the final target.
	Proxy *models.ProxyTable

	// FetchedAt contains the time the target was fetched and stored in our
	// internal map. Useful for caching.
	FetchedAt time.Time

	// FetchedSource contains the source information from which the target is
	// obtained.
	FetchedSource string

	// CacheTimeout is used to invalidate the target. Zero duration disables the cache.
	CacheTimeout time.Duration

	// HostCacheDisabled is used to disable caching host based targets.
	HostCacheDisabled bool
}

// GetTarget is used to get the target according to the incoming request it
// looks if a request has set cookies to change the target. If not it
// fallbacks to request host data.
func GetTarget(req *http.Request) (*Target, error) {
	cookieBuild, err := req.Cookie(CookieBuildName)
	if err == nil {
		service := service{
			username:    "koding",
			servicename: "server",
			build:       cookieBuild.Value,
		}

		t, err := service.target()
		if err == nil {
			log.Printf("proxy target is overridden by cookie build: '%s'\n", cookieBuild.Value)
			return t, nil
		}
	}

	cookieDomain, err := req.Cookie(CookieDomainName)
	if err == nil {
		t, err := TargetByHost(cookieDomain.Value)
		if err == nil {
			log.Printf("proxy target is overrriden by cookie domain: '%s'\n", cookieDomain.Value)
			return t, nil
		}
	}

	// if cookie is not set, use request host data, this is also a fallback
	return TargetByHost(req.Host)
}

// TargetByHost is used to resolve any hostname to their final target
// destination together with the mode of the domain for the given host string.
// Any incoming domain can have multiple different target destinations.
// TargetByHost returns the ultimate target destinations. Some examples:
//
// koding.com -> "http://webserver-build-koding-813a.in.koding.com:3000", mode:internal
// arslan.kd.io -> "http://10.128.2.25:80", mode:vm
// y.koding.com -> "http://localhost/maintenance", mode:maintenance
func TargetByHost(host string) (*Target, error) {
	target, err := getFromCache(host)
	if err == nil {
		return target, nil
	}

	return getFromDB(host)
}

func getFromCache(host string) (*Target, error) {
	targetsMu.Lock()
	defer targetsMu.Unlock()

	target, ok := targets[host]
	if !ok {
		return nil, fmt.Errorf("target not found for %s", host)
	}

	if target.HostCacheDisabled {
		target.FetchedSource = "InternalCache"
		return target, nil
	}

	if time.Now().Before(target.FetchedAt.Add(target.CacheTimeout)) {
		target.FetchedSource = "Cache"
		return target, nil
	}

	return nil, fmt.Errorf("target cache invalidaiton for host %s", host)
}

func getFromDB(host string) (*Target, error) {
	domain, err := getDomain(host)
	if err != nil {
		return nil, err
	}

	target := &Target{
		Proxy:         domain.Proxy,
		HostnameAlias: domain.HostnameAlias,
		CacheTimeout:  time.Second * 20,
		FetchedAt:     time.Now(),
		FetchedSource: "MongoDB",
	}

	err = target.Resolve(host)
	if err != nil {
		return nil, err
	}

	targetsMu.Lock()
	targets[host] = target
	targetsMu.Unlock()

	return target, nil
}

func getDomain(host string) (*models.Domain, error) {
	// remove port if host as a port
	if utils.HasPort(host) {
		host, _, _ = net.SplitHostPort(host)
	}

	domain := new(models.Domain)
	var err error

	domain, err = modelhelper.GetDomain(host)
{'fraction_non_alphanumeric': 0.11169724770642202, 'fraction_numerical': 0.00481651376146789, 'mean_word_length': 3.668629550321199, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5523283', 'n_tokens_mistral': 2970, 'n_tokens_neox': 2802, 'n_words': 896}