<commit_before>package main

import (
	"log"
	"os"
	"runtime"
	"strconv"
)

// Version is the version number of the application.
const version = "Apollo v1.0.0"

// Create a configuration directory for Unis systems.
func makeUnixConfigDir() {
	err := os.Mkdir(os.Getenv("HOME")+"/.config/apollo", 0755)
	if err != nil {
		log.Print(err)
	}
}

// Returns the path of the database file.
func databasePath() string {
	if runtime.GOOS == "windows" {
		return "database.json"
	} else {
		makeUnixConfigDir()
		return os.Getenv("HOME") + "/.config/apollo/database.json"
	}
}

// Returns the path of the configuration file.
func configurationPath() string {
	if runtime.GOOS == "windows" {
		return "configuration.json"
	} else {
		makeUnixConfigDir()
		return os.Getenv("HOME") + "/.config/apollo/configuration.json"
	}
}

// PrintHelp prints out the help guide to the logs.
func (a *Apollo) printHelp() {
	s := []string{
		"{b}*───( Main Help Guide )───*",
		"{b}│ {d}List of key-bindings:",
		"{b}│ {d}Ctrl+C.........Close this software",
		"{b}│ {d}Alt+Num........Go to the [num]th tab",
		"{b}│ {d}Enter..........Send, or toggle between input bar and tab",
		"{b}│",
		"{b}│ {d}List of commands:",
		"{b}│ {d}(For more details, use /help <command name>)",
		"{b}│ {d}/help..........Show this help guide",
		"{b}│ {d}/quit..........Close this software",
		"{b}│ {d}/open..........Create a new tab",
		"{b}│ {d}/close.........Close the current tab",
		"{b}│ {d}/set...........Set a configuration option",
		"{b}│ {d}/config........Show the current configuration",
		"{b}│ {d}/stats.........Prints some stats about the entries",
		"{b}*───*",
	}

	for i := 0; i < len(s); i++ {
		a.log(s[i])
	}
}

// PrintDetailedHelp prints out the detailed help of a function to the logs.
func (a *Apollo) printDetailedHelp(subject string) {
	var s []string
	switch subject {
	case "help":
		s = []string{
			"{b}*───( Detailed Help Guide )───*",
			"{b}│ {d}/help",
			"{b}│ {d}/help <command name>",
			"{b}│",
			"{b}│ {d}Displays a help guide.",
			"{b}*───*",
		}
	case "quit":
		s = []string{
			"{b}*───( Detailed Help Guide )───*",
			"{b}│ {d}/quit",
			"{b}│",
			"{b}│ {d}Closes this software.",
			"{b}*───*",
		}
	case "open":
		s = []string{
			"{b}*───( Detailed Help Guide )───*",
			"{b}│ {d}/open <tab name>",
			"{b}│",
			"{b}│ {d}Opens a given tab or, if it already exists, selects it.",
			"{b}│ {d}Tabs available: anime, books, games, movies, series.",
			"{b}│ {d}For keybindings, use '/help tabs'",
			"{b}*───*",
		}
	case "close":
		s = []string{
			"{b}*───( Detailed Help Guide )───*",
			"{b}│ {d}/close",
			"{b}│",
			"{b}│ {d}Close the current tab.",
			"{b}*───*",
		}
	case "set":
		s = []string{
			"{b}*───( Detailed Help Guide )───*",
			"{b}│ {d}/set <option> <value>",
			"{b}│",
			"{b}│ {d}Sets a configuration option to a specific value.",
			"{b}*───*",
		}
	case "config":
		s = []string{
			"{b}*───( Detailed Help Guide )───*",
			"{b}│ {d}/config",
			"{b}│",
			"{b}│ {d}Shows the configuration options and their values.",
			"{b}*───*",
		}
	case "stats":
		s = []string{
			"{b}*───( Detailed Help Guide )───*",
			"{b}│ {d}/stats",
			"{b}│",
			"{b}│ {d}Prints statistics about the database entries.",
			"{b}*───*",
		}
	case "tabs":
		s = []string{
			"{b}*───( Detailed Help Guide )───*",
			"{b}│ {d}List of key-bindings:",
			"{b}│ {d}1..............Switch to passive view",
			"{b}│ {d}2..............Switch to active view",
			"{b}│ {d}3..............Switch to inactive view",
			"{b}│ {d}4..............Switch to all view",
			"{b}│ {d}s..............Sort the entries",
			"{b}│ {d}D..............Delete the current entry",
			"{b}│ {d}e..............Edit the current entry",
			"{b}│ {d}t..............Tag the current entry",
			"{b}│ {d}r..............Toggle ratings",
			"{b}│ {d}a..............Toggle the current entry's state",
			"{b}│ {d}z/x............Change the rating of the current entry",
			"{b}│ {d}c/v............Change the current episode of an entry",
			"{b}│ {d}p..............Print the current entries to a file",
			"{b}*───*",
		}
	default:
		s = []string{
			"{b}│ {d}Detailed help does not exist for this command.",
		}
	}

	for i := 0; i < len(s); i++ {
		a.log(s[i])
	}
}

// PrintWelcome prints out the welcome message to the logs.
func (a *Apollo) printWelcome() {
	a.log("{b}*───( " + version + " )───*")
	a.log("{b}│ {d}This software is licensed under the MIT License.")
	a.log("{b}│ {d}To get started, use /help.")
	a.log("{b}*───*")
}

// PrintConfig prints out the list of configuration options to the logs.
func (a *Apollo) printConfig() {
	a.log("{b}*───( Current Configuration )───*")
	for _, value := range a.c.config() {
		a.log("{b}│ {d}" + value)
	}
	a.log("{b}*───*")
}

// PrintStats prints out relevant statistics about the database.
func (a *Apollo) printStats() {
	totalMovies := 0
	watchedMovies := 0
	for i := range a.d.Movies {
		totalMovies++
		if a.d.Movies[i].State == "passive" {
			watchedMovies++
		}
	}

	totalAnime := 0
	watchedAnime := 0
	totalAnimeEp := 0
	watchedAnimeEp := 0
	for i := range a.d.Anime {
		totalAnime++
		if a.d.Anime[i].State == "passive" {
			watchedAnime++
		}
		totalAnimeEp += a.d.Anime[i].EpisodeTotal
		watchedAnimeEp += a.d.Anime[i].EpisodeDone
	}

	totalGames := 0
	playedGames := 0
	for i := range a.d.Games {
		totalGames++
		if a.d.Games[i].State == "passive" {
			playedGames++
		}
	}

	totalBooks := 0
	readBooks := 0
	for i := range a.d.Books {
		totalBooks++
		if a.d.Books[i].State == "passive" {
			readBooks++
		}
	}

	sTotalMovies := strconv.Itoa(totalMovies)
	sWatchedMovies := strconv.Itoa(watchedMovies)
	sTotalAnime := strconv.Itoa(totalAnime)
	sWatchedAnime := strconv.Itoa(watchedAnime)
	sTotalAnimeEp := strconv.Itoa(totalAnimeEp)
	sWatchedAnimeEp := strconv.Itoa(watchedAnimeEp)
	sTotalGames := strconv.Itoa(totalGames)
	sPlayedGames := strconv.Itoa(playedGames)
	sTotalBooks := strconv.Itoa(totalBooks)
	sReadBooks := strconv.Itoa(readBooks)

	a.log("{b}*───( Statistics )───*")
	a.log("{b}│ {d}Movies watched: " + sWatchedMovies + "/" + sTotalMovies)
	a.log("{b}│ {d}Anime seasons watched: " + sWatchedAnime + "/" + sTotalAnime)
	a.log("{b}│ {d} Episodes watched: " + sWatchedAnimeEp + "/" + sTotalAnimeEp)
	a.log("{b}│ {d}Games played: " + sPlayedGames + "/" + sTotalGames)
	a.log("{b}│ {d}Books read: " + sReadBooks + "/" + sTotalBooks)
	a.log("{b}*───*")
}
<commit_msg>Add a check for already existing directory path<commit_after>package main

import (
	"log"
	"os"
	"runtime"
	"strconv"
)

// Version is the version number of the application.
const version = "Apollo v1.0.0"

// Create a configuration directory for Unis systems.
func makeUnixConfigDir() {
	path := os.Getenv("HOME") + "/.config/apollo"
	_, err := os.Stat(path)
	if err != nil {
		err = os.Mkdir(path, 0755)
		if err != nil {
			log.Print(err)
		}
	}
}

// Returns the path of the database file.
func databasePath() string {
	if runtime.GOOS == "windows" {
		return "database.json"
	} else {
		makeUnixConfigDir()
		return os.Getenv("HOME") + "/.config/apollo/database.json"
	}
}

// Returns the path of the configuration file.
func configurationPath() string {
	if runtime.GOOS == "windows" {
		return "configuration.json"
	} else {
		makeUnixConfigDir()
		return os.Getenv("HOME") + "/.config/apollo/configuration.json"
	}
}

// PrintHelp prints out the help guide to the logs.
func (a *Apollo) printHelp() {
	s := []string{
		"{b}*───( Main Help Guide )───*",
		"{b}│ {d}List of key-bindings:",
		"{b}│ {d}Ctrl+C.........Close this software",
		"{b}│ {d}Alt+Num........Go to the [num]th tab",
		"{b}│ {d}Enter..........Send, or toggle between input bar and tab",
		"{b}│",
		"{b}│ {d}List of commands:",
		"{b}│ {d}(For more details, use /help <command name>)",
		"{b}│ {d}/help..........Show this help guide",
		"{b}│ {d}/quit..........Close this software",
		"{b}│ {d}/open..........Create a new tab",
		"{b}│ {d}/close.........Close the current tab",
		"{b}│ {d}/set...........Set a configuration option",
		"{b}│ {d}/config........Show the current configuration",
		"{b}│ {d}/stats.........Prints some stats about the entries",
		"{b}*───*",
	}

	for i := 0; i < len(s); i++ {
		a.log(s[i])
	}
}

// PrintDetailedHelp prints out the detailed help of a function to the logs.
func (a *Apollo) printDetailedHelp(subject string) {
	var s []string
	switch subject {
	case "help":
		s = []string{
			"{b}*───( Detailed Help Guide )───*",
			"{b}│ {d}/help",
			"{b}│ {d}/help <command name>",
			"{b}│",
			"{b}│ {d}Displays a help guide.",
			"{b}*───*",
		}
	case "quit":
		s = []string{
			"{b}*───( Detailed Help Guide )───*",
			"{b}│ {d}/quit",
			"{b}│",
			"{b}│ {d}Closes this software.",
			"{b}*───*",
		}
	case "open":
		s = []string{
			"{b}*───( Detailed Help Guide )───*",
			"{b}│ {d}/open <tab name>",
			"{b}│",
			"{b}│ {d}Opens a given tab or, if it already exists, selects it.",
			"{b}│ {d}Tabs available: anime, books, games, movies, series.",
			"{b}│ {d}For keybindings, use '/help tabs'",
			"{b}*───*",
		}
	case "close":
		s = []string{
			"{b}*───( Detailed Help Guide )───*",
			"{b}│ {d}/close",
			"{b}│",
			"{b}│ {d}Close the current tab.",
			"{b}*───*",
		}
	case "set":
		s = []string{
			"{b}*───( Detailed Help Guide )───*",
			"{b}│ {d}/set <option> <value>",
			"{b}│",
			"{b}│ {d}Sets a configuration option to a specific value.",
			"{b}*───*",
		}
	case "config":
		s = []string{
			"{b}*───( Detailed Help Guide )───*",
			"{b}│ {d}/config",
			"{b}│",
			"{b}│ {d}Shows the configuration options and their values.",
			"{b}*───*",
		}
	case "stats":
		s = []string{
			"{b}*───( Detailed Help Guide )───*",
			"{b}│ {d}/stats",
			"{b}│",
			"{b}│ {d}Prints statistics about the database entries.",
			"{b}*───*",
		}
	case "tabs":
		s = []string{
			"{b}*───( Detailed Help Guide )───*",
			"{b}│ {d}List of key-bindings:",
			"{b}│ {d}1..............Switch to passive view",
			"{b}│ {d}2..............Switch to active view",
			"{b}│ {d}3..............Switch to inactive view",
			"{b}│ {d}4..............Switch to all view",
			"{b}│ {d}s..............Sort the entries",
			"{b}│ {d}D..............Delete the current entry",
			"{b}│ {d}e..............Edit the current entry",
			"{b}│ {d}t..............Tag the current entry",
			"{b}│ {d}r..............Toggle ratings",
			"{b}│ {d}a..............Toggle the current entry's state",
			"{b}│ {d}z/x............Change the rating of the current entry",
			"{b}│ {d}c/v............Change the current episode of an entry",
			"{b}│ {d}p..............Print the current entries to a file",
			"{b}*───*",
		}
	default:
		s = []string{
			"{b}│ {d}Detailed help does not exist for this command.",
		}
	}

	for i := 0; i < len(s); i++ {
		a.log(s[i])
	}
}

// PrintWelcome prints out the welcome message to the logs.
func (a *Apollo) printWelcome() {
	a.log("{b}*───( " + version + " )───*")
	a.log("{b}│ {d}This software is licensed under the MIT License.")
	a.log("{b}│ {d}To get started, use /help.")
	a.log("{b}*───*")
}

// PrintConfig prints out the list of configuration options to the logs.
func (a *Apollo) printConfig() {
	a.log("{b}*───( Current Configuration )───*")
	for _, value := range a.c.config() {
		a.log("{b}│ {d}" + value)
	}
	a.log("{b}*───*")
}

// PrintStats prints out relevant statistics about the database.
func (a *Apollo) printStats() {
	totalMovies := 0
	watchedMovies := 0
	for i := range a.d.Movies {
		totalMovies++
		if a.d.Movies[i].State == "passive" {
			watchedMovies++
		}
	}

	totalAnime := 0
	watchedAnime := 0
	totalAnimeEp := 0
	watchedAnimeEp := 0
	for i := range a.d.Anime {
		totalAnime++
		if a.d.Anime[i].State == "passive" {
			watchedAnime++
		}
		totalAnimeEp += a.d.Anime[i].EpisodeTotal
		watchedAnimeEp += a.d.Anime[i].EpisodeDone
	}

	totalGames := 0
	playedGames := 0
	for i := range a.d.Games {
		totalGames++
		if a.d.Games[i].State == "passive" {
			playedGames++
		}
	}

	totalBooks := 0
	readBooks := 0
	for i := range a.d.Books {
		totalBooks++
		if a.d.Books[i].State == "passive" {
			readBooks++
		}
	}

	sTotalMovies := strconv.Itoa(totalMovies)
	sWatchedMovies := strconv.Itoa(watchedMovies)
	sTotalAnime := strconv.Itoa(totalAnime)
	sWatchedAnime := strconv.Itoa(watchedAnime)
	sTotalAnimeEp := strconv.Itoa(totalAnimeEp)
	sWatchedAnimeEp := strconv.Itoa(watchedAnimeEp)
	sTotalGames := strconv.Itoa(totalGames)
	sPlayedGames := strconv.Itoa(playedGames)
	sTotalBooks := strconv.Itoa(totalBooks)
	sReadBooks := strconv.Itoa(readBooks)

	a.log("{b}*───( Statistics )───*")
	a.log("{b}│ {d}Movies watched: " + sWatchedMovies + "/" + sTotalMovies)
	a.log("{b}│ {d}Anime seasons watched: " + sWatchedAnime + "/" + sTotalAnime)
	a.log("{b}│ {d} Episodes watched: " + sWatchedAnimeEp + "/" + sTotalAnimeEp)
	a.log("{b}│ {d}Games played: " + sPlayedGames + "/" + sTotalGames)
	a.log("{b}│ {d}Books read: " + sReadBooks + "/" + sTotalBooks)
	a.log("{b}*───*")
}
<|endoftext|>


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

package main

import (
	"bytes"
	"fmt"
	"os"
	"text/tabwriter"

	"github.com/sdboyer/gps"
)

var statusCmd = &command{
	fn:   runStatus,
	name: "status",
	short: `[flags] [packages]
	Report the status of the current project's dependencies.
	`,
	long: `
	If no packages are specified, for each dependency:
	- root import path
	- (if present in lock) the currently selected version
	- (else) that it's missing from the lock
	- whether it's present in the vendor directory (or if it's in
	  workspace, if that's a thing?)
	- the current aggregate constraints on that project (as specified by
	  the Manifest)
	- if -u is specified, whether there are newer versions of this
	  dependency

	If packages are specified, or if -a is specified,
	for each of those dependencies:
	- (if present in lock) the currently selected version
	- (else) that it's missing from the lock
	- whether it's present in the vendor directory
	- The set of possible versions for that project
	- The upstream source URL(s) from which the project may be retrieved
	- The type of upstream source (git, hg, bzr, svn, registry)
	- Other versions that might work, given the current constraints
	- The list of all projects that import the project within the current
	  depgraph
	- The current constraint. If more than one project constrains it, both
	  the aggregate and the individual components (and which project provides
	  that constraint) are printed
	- License information
	- Package source location, if fetched from an alternate location

	Flags:
	-json		Output in json format
	-f [template]	Output in text/template format

	-old		Only show out of date packages and the current version
	-missing	Only show missing packages.
	-unused		Only show unused packages.
	-modified	Only show modified packages.

	-dot		Export dependency graph in GraphViz format

	The exit code of status is zero if all repositories are in a "good state".
	`,
}

// BasicStatus contains all the information reported about a single dependency
// in the summary/list status output mode.
type BasicStatus struct {
	ProjectRoot  string
	Constraint   gps.Constraint
	Version      gps.UnpairedVersion
	Revision     gps.Revision
	Latest       gps.Version
	PackageCount int
}

type MissingStatus struct {
	ProjectRoot     string
	MissingPackages string
}

func runStatus(args []string) error {
	p, err := loadProject("")
	if err != nil {
		return err
	}

	sm, err := getSourceManager()
	if err != nil {
		return err
	}
	defer sm.Release()

	if len(args) == 0 {
		return runStatusAll(p, sm)
	}
	return runStatusDetailed(p, sm, args)
}

func runStatusAll(p *project, sm *gps.SourceMgr) error {
	if p.l == nil {
		// TODO if we have no lock file, do...other stuff
		return nil
	}

	// In the background, warm caches of version lists for all the projects in
	// the lock. The SourceMgr coordinates access to this information - if the
	// main goroutine asks for the version list while the background goroutine's
	// request is in flight (or vice versa), both calls are folded together and
	// are fulfilled from the same network response, and the same on-disk
	// repository cache.
	for _, proj := range p.l.Projects() {
		id := proj.Ident()
		go sm.ListVersions(id)
	}

	// While the network churns on ListVersions() requests, statically analyze
	// code from the current project.
	ptree, err := gps.ListPackages(p.absroot, string(p.importroot))
	if err != nil {
		return fmt.Errorf("analysis of local packages failed: %v", err)
	}

	// Set up a solver in order to check the InputHash.
	params := gps.SolveParameters{
		RootDir:         p.absroot,
		RootPackageTree: ptree,
		Manifest:        p.m,
		// Locks aren't a part of the input hash check, so we can omit it.
	}

	s, err := gps.Prepare(params, sm)
	if err != nil {
		return fmt.Errorf("could not set up solver for input hashing: %s", err)
	}

	cm := collectConstraints(ptree, p, sm)

	if bytes.Equal(s.HashInputs(), p.l.Memo) {
		tw := tabwriter.NewWriter(os.Stdout, 0, 4, 1, ' ', 0)
		// Print header row
		fmt.Fprintf(tw, "Project\tConstraint\tVersion\tRevision\tLatest\tPkgs Used\t\n")

		// If these are equal, we're guaranteed that the lock is a transitively
		// complete picture of all deps. That eliminates the need for at least
		// some checks.
		for _, proj := range p.l.Projects() {
			bs := BasicStatus{
				ProjectRoot:  string(proj.Ident().ProjectRoot),
				PackageCount: len(proj.Packages()),
			}

			// Split apart the version from the lock into its constituent parts
			switch tv := proj.Version().(type) {
			case gps.UnpairedVersion:
				bs.Version = tv
			case gps.Revision:
				bs.Revision = tv
			case gps.PairedVersion:
				bs.Version = tv.Unpair()
				bs.Revision = tv.Underlying()
			}

			// Check if the manifest has an override for this project. If so,
			// set that as the constraint.
			if pp, has := p.m.Ovr[proj.Ident().ProjectRoot]; has && pp.Constraint != nil {
				// TODO note somehow that it's overridden
				bs.Constraint = pp.Constraint
			} else {
				bs.Constraint = gps.Any()
				for _, c := range cm[bs.ProjectRoot] {
					bs.Constraint = c.Intersect(bs.Constraint)
				}
			}

			// Only if we have a non-rev and non-plain version do/can we display
			// anything wrt the version's updateability.
			if bs.Version != nil && bs.Version.Type() != "version" {
				c, has := p.m.Dependencies[proj.Ident().ProjectRoot]
				if !has {
					c.Constraint = gps.Any()
				}

				vl, err := sm.ListVersions(proj.Ident())
				if err != nil {
					gps.SortForUpgrade(vl)

					for _, v := range vl {
						// Because we've sorted the version list for upgrade, the
						// first version we encounter that matches our constraint
						// will be what we want
						if c.Constraint.Matches(v) {
							bs.Latest = v
							break
						}
					}
				}
			}

			fmt.Fprintf(tw,
				"%s\t%s\t%s\t%s\t%s\t%s\t\n",
				bs.ProjectRoot,
				bs.Constraint,
				bs.Version,
				string(bs.Revision)[:7],
				bs.Latest,
				bs.PackageCount,
			)
		}
		tw.Flush()
	} else {
		tw := tabwriter.NewWriter(os.Stdout, 0, 4, 1, ' ', 0)
		// Print header row
		fmt.Fprintf(tw, "Project\tMissing Packages\t\n")
		// Not equal - the lock may or may not be a complete picture, and even
		// if it does have all the deps, it may not be a valid set of
		// constraints.

		external := ptree.ExternalReach(true, false, nil).ListExternalImports()
		roots := make(map[gps.ProjectRoot][]string)
		var errs []string
		for _, e := range external {
			root, err := sm.DeduceProjectRoot(e)
			if err != nil {
				errs = append(errs, string(root))
				continue
			}

			roots[root] = append(roots[root], e)
		}

	outer:
		for root, pkgs := range roots {
			// TODO also handle the case where the project is present, but there
			// are items missing from just the package list
			for _, lp := range p.l.Projects() {
				if lp.Ident().ProjectRoot == root {
					continue outer
				}
			}

			fmt.Fprintf(tw,
				"%s\t%s\t\n",
				root,
				pkgs,
			)
		}
		tw.Flush()
	}

	return nil
}

func runStatusDetailed(p *project, sm *gps.SourceMgr, args []string) error {
	// TODO
	return fmt.Errorf("not implemented")
}

func collectConstraints(ptree gps.PackageTree, p *project, sm *gps.SourceMgr) map[string][]gps.Constraint {
	// TODO
	return map[string][]gps.Constraint{}
}
<commit_msg>dep: alpha sort by root project in status output<commit_after>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"fmt"
	"os"
	"sort"
	"text/tabwriter"

	"github.com/sdboyer/gps"
)

var statusCmd = &command{
	fn:   runStatus,
	name: "status",
	short: `[flags] [packages]
	Report the status of the current project's dependencies.
	`,
	long: `
	If no packages are specified, for each dependency:
	- root import path
	- (if present in lock) the currently selected version
	- (else) that it's missing from the lock
	- whether it's present in the vendor directory (or if it's in
	  workspace, if that's a thing?)
	- the current aggregate constraints on that project (as specified by
	  the Manifest)
	- if -u is specified, whether there are newer versions of this
	  dependency

	If packages are specified, or if -a is specified,
	for each of those dependencies:
	- (if present in lock) the currently selected version
	- (else) that it's missing from the lock
	- whether it's present in the vendor directory
	- The set of possible versions for that project
	- The upstream source URL(s) from which the project may be retrieved
	- The type of upstream source (git, hg, bzr, svn, registry)
	- Other versions that might work, given the current constraints
	- The list of all projects that import the project within the current
	  depgraph
	- The current constraint. If more than one project constrains it, both
	  the aggregate and the individual components (and which project provides
	  that constraint) are printed
	- License information
	- Package source location, if fetched from an alternate location

	Flags:
	-json		Output in json format
	-f [template]	Output in text/template format

	-old		Only show out of date packages and the current version
	-missing	Only show missing packages.
	-unused		Only show unused packages.
	-modified	Only show modified packages.

	-dot		Export dependency graph in GraphViz format

	The exit code of status is zero if all repositories are in a "good state".
	`,
}

// BasicStatus contains all the information reported about a single dependency
// in the summary/list status output mode.
type BasicStatus struct {
	ProjectRoot  string
	Constraint   gps.Constraint
	Version      gps.UnpairedVersion
	Revision     gps.Revision
	Latest       gps.Version
	PackageCount int
}

type MissingStatus struct {
	ProjectRoot     string
	MissingPackages string
}

func runStatus(args []string) error {
	p, err := loadProject("")
	if err != nil {
		return err
	}

	sm, err := getSourceManager()
	if err != nil {
		return err
	}
	defer sm.Release()

	if len(args) == 0 {
		return runStatusAll(p, sm)
	}
	return runStatusDetailed(p, sm, args)
}

func runStatusAll(p *project, sm *gps.SourceMgr) error {
	if p.l == nil {
		// TODO if we have no lock file, do...other stuff
		return nil
	}

	// In the background, warm caches of version lists for all the projects in
	// the lock. The SourceMgr coordinates access to this information - if the
	// main goroutine asks for the version list while the background goroutine's
	// request is in flight (or vice versa), both calls are folded together and
	// are fulfilled from the same network response, and the same on-disk
	// repository cache.
	for _, proj := range p.l.Projects() {
		id := proj.Ident()
		go sm.ListVersions(id)
	}

	// While the network churns on ListVersions() requests, statically analyze
	// code from the current project.
	ptree, err := gps.ListPackages(p.absroot, string(p.importroot))
	if err != nil {
		return fmt.Errorf("analysis of local packages failed: %v", err)
	}

	// Set up a solver in order to check the InputHash.
	params := gps.SolveParameters{
		RootDir:         p.absroot,
		RootPackageTree: ptree,
		Manifest:        p.m,
		// Locks aren't a part of the input hash check, so we can omit it.
	}

	s, err := gps.Prepare(params, sm)
	if err != nil {
		return fmt.Errorf("could not set up solver for input hashing: %s", err)
	}

	cm := collectConstraints(ptree, p, sm)

	// Get the project list and sort it so that the printed output users see is
	// deterministically ordered. (This may be superfluous if the lock is always
	// written in alpha order, but it doesn't hurt to double down.)
	slp := p.l.Projects()
	sort.Sort(sortedLockedProjects(slp))

	if bytes.Equal(s.HashInputs(), p.l.Memo) {
		// If these are equal, we're guaranteed that the lock is a transitively
		// complete picture of all deps. That eliminates the need for at least
		// some checks.

		tw := tabwriter.NewWriter(os.Stdout, 0, 4, 1, ' ', 0)
		fmt.Fprintf(tw, "Project\tConstraint\tVersion\tRevision\tLatest\tPkgs Used\t\n")

		for _, proj := range slp {
			bs := BasicStatus{
				ProjectRoot:  string(proj.Ident().ProjectRoot),
				PackageCount: len(proj.Packages()),
			}

			// Split apart the version from the lock into its constituent parts
			switch tv := proj.Version().(type) {
			case gps.UnpairedVersion:
				bs.Version = tv
			case gps.Revision:
				bs.Revision = tv
			case gps.PairedVersion:
				bs.Version = tv.Unpair()
				bs.Revision = tv.Underlying()
			}

			// Check if the manifest has an override for this project. If so,
			// set that as the constraint.
			if pp, has := p.m.Ovr[proj.Ident().ProjectRoot]; has && pp.Constraint != nil {
				// TODO note somehow that it's overridden
				bs.Constraint = pp.Constraint
			} else {
				bs.Constraint = gps.Any()
				for _, c := range cm[bs.ProjectRoot] {
					bs.Constraint = c.Intersect(bs.Constraint)
				}
			}

			// Only if we have a non-rev and non-plain version do/can we display
			// anything wrt the version's updateability.
			if bs.Version != nil && bs.Version.Type() != "version" {
				c, has := p.m.Dependencies[proj.Ident().ProjectRoot]
				if !has {
					c.Constraint = gps.Any()
				}

				vl, err := sm.ListVersions(proj.Ident())
				if err != nil {
					gps.SortForUpgrade(vl)

					for _, v := range vl {
						// Because we've sorted the version list for upgrade, the
						// first version we encounter that matches our constraint
						// will be what we want
						if c.Constraint.Matches(v) {
							bs.Latest = v
							break
						}
					}
				}
			}

			fmt.Fprintf(tw,
				"%s\t%s\t%s\t%s\t%s\t%s\t\n",
				bs.ProjectRoot,
				bs.Constraint,
				bs.Version,
				string(bs.Revision)[:7],
				bs.Latest,
				bs.PackageCount,
			)
		}
		tw.Flush()
	} else {
		tw := tabwriter.NewWriter(os.Stdout, 0, 4, 1, ' ', 0)
		// Print header row
		fmt.Fprintf(tw, "Project\tMissing Packages\t\n")
		// Not equal - the lock may or may not be a complete picture, and even
		// if it does have all the deps, it may not be a valid set of
		// constraints.

		external := ptree.ExternalReach(true, false, nil).ListExternalImports()
		roots := make(map[gps.ProjectRoot][]string)
		var errs []string
		for _, e := range external {
			root, err := sm.DeduceProjectRoot(e)
			if err != nil {
				errs = append(errs, string(root))
				continue
			}

			roots[root] = append(roots[root], e)
		}

	outer:
		for root, pkgs := range roots {
			// TODO also handle the case where the project is present, but there
			// are items missing from just the package list
			for _, lp := range slp {
				if lp.Ident().ProjectRoot == root {
					continue outer
				}
			}

			fmt.Fprintf(tw,
				"%s\t%s\t\n",
				root,
				pkgs,
			)
		}
		tw.Flush()
	}

	return nil
}

type sortedLockedProjects []gps.LockedProject

func (s sortedLockedProjects) Len() int      { return len(s) }
func (s sortedLockedProjects) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s sortedLockedProjects) Less(i, j int) bool {
	l, r := s[i].Ident(), s[j].Ident()

	if l.ProjectRoot < r.ProjectRoot {
		return true
	}
	if r.ProjectRoot < l.ProjectRoot {
		return false
	}

	return l.NetworkName < r.NetworkName
}

func runStatusDetailed(p *project, sm *gps.SourceMgr, args []string) error {
	// TODO
	return fmt.Errorf("not implemented")
}

func collectConstraints(ptree gps.PackageTree, p *project, sm *gps.SourceMgr) map[string][]gps.Constraint {
	// TODO
	return map[string][]gps.Constraint{}
}
<|endoftext|>


<commit_before>package multitemplate

import (
	"text/template"
	"text/template/parse"

	"github.com/acsellers/multitemplate"
)

type defaultParser struct{}

func (ms *multiStruct) ParseTemplate(name, src string, funcs template.FuncMap) (map[string]*parse.Tree, error) {
	t, e := template.New(name).Funcs(funcs).Parse(src)
	if e != nil {
		return nil, e
	}
	ret := make(map[string]*parse.Tree)
	for _, t := range t.Templates() {
		ret[t.Name()] = t.Tree
	}
	return ret, nil
}

func (ms *multiStruct) String() string {
	return "html/template: Standard Library Template"
}

func init() {
	ms := multiStruct{}
	multitemplate.Parsers["default"] = &ms
	multitemplate.Parsers["tmpl"] = &ms
}
<commit_msg>Clean up default parser, expose Delims<commit_after>package multitemplate

import (
	"text/template"
	"text/template/parse"
)

var GoLeftDelim, GoRightDelim string

type defaultParser struct {
	left, right string
}

func (ms *defaultParser) ParseTemplate(name, src string, funcs template.FuncMap) (map[string]*parse.Tree, error) {
	var t *template.Template
	var e error
	if GoRightDelim != "" || GoLeftDelim != "" {
		t, e = template.New(name).Funcs(funcs).Delims(GoLeftDelim, GoRightDelim).Parse(src)
	} else {
		t, e = template.New(name).Funcs(funcs).Parse(src)
	}
	if e != nil {
		return nil, e
	}

	ret := make(map[string]*parse.Tree)
	for _, t := range t.Templates() {
		ret[t.Name()] = t.Tree
	}
	return ret, nil
}

func (ms *defaultParser) String() string {
	return "html/template: Standard Library Template"
}

func init() {
	Parsers["tmpl"] = &defaultParser{}
}
<|endoftext|>


<commit_before>// Package stream implements stream algorithms.
package stream

// Counter represents a counted item in a Summary.
type Counter struct {
	Item      string
	Count     uint64
	ErrorRate uint64
}

func newCounter(item string) *Counter {
	return &Counter{Item: item, Count: 1, ErrorRate: 0}
}

// Summary represents a Stream-Summary data structure as described in "Efficient
// Computation of Frequent and Top-k Elements in Data Streams".
type Summary struct {
	capacity int
	counters []*Counter
	index    map[string]int
}

// NewSummary returns a new Summary with the given max capacity.
func NewSummary(capacity int) *Summary {
	return &Summary{capacity: capacity, counters: []*Counter{}, index: make(map[string]int)}
}

// Top returns the top n Counters in the Summary.  If the Summary contains less
// than n Counters, all Counters in the Summary are returned.
func (s *Summary) Top(n int) []*Counter {
	if n > len(s.counters) {
		return s.counters[0:len(s.counters)]
	}

	return s.counters[0:n]
}

// Observe adds an observation of an item to the Summary.
func (s *Summary) Observe(item string) {
	i, exists := s.index[item]
	if exists {
		s.counters[i].Count++
		for i > 0 && s.counters[i].Count > s.counters[i-1].Count {
			s.promote(i)
			i--
		}
	} else {
		if len(s.counters) < s.capacity {
			s.counters = append(s.counters, newCounter(item))
			s.index[item] = len(s.counters) - 1
		} else {
			minCounter := s.counters[len(s.counters)-1]
			delete(s.index, minCounter.Item)

			counter := newCounter(item)
			counter.Count = minCounter.Count + 1
			counter.ErrorRate = minCounter.Count
			s.counters[len(s.counters)-1] = counter
			s.index[item] = len(s.counters) - 1
		}
	}
}

func (s *Summary) promote(i int) {
	toPromote := s.counters[i]
	toDemote := s.counters[i-1]

	s.counters[i-1] = toPromote
	s.index[toPromote.Item] = i - 1

	s.counters[i] = toDemote
	s.index[toDemote.Item] = i
}
<commit_msg>Sync reads/writes with an RWMutex<commit_after>// Package stream implements stream algorithms.
package stream

import "sync"

// Counter represents a counted item in a Summary.
type Counter struct {
	Item      string
	Count     uint64
	ErrorRate uint64
}

func newCounter(item string) *Counter {
	return &Counter{Item: item, Count: 1, ErrorRate: 0}
}

// Summary represents a Stream-Summary data structure as described in "Efficient
// Computation of Frequent and Top-k Elements in Data Streams".
type Summary struct {
	capacity int
	counters []*Counter
	index    map[string]int
	rw       sync.RWMutex
}

// NewSummary returns a new Summary with the given max capacity.
func NewSummary(capacity int) *Summary {
	return &Summary{capacity: capacity, counters: []*Counter{}, index: make(map[string]int)}
}

// Top returns the top n Counters in the Summary.  If the Summary contains less
// than n Counters, all Counters in the Summary are returned.
func (s *Summary) Top(n int) []*Counter {
	s.rw.RLock()
	defer s.rw.RUnlock()

	if n > len(s.counters) {
		return s.counters[0:len(s.counters)]
	}

	return s.counters[0:n]
}

// Observe adds an observation of an item to the Summary.
func (s *Summary) Observe(item string) {
	s.rw.Lock()
	defer s.rw.Unlock()

	i, exists := s.index[item]
	if exists {
		s.counters[i].Count++
		for i > 0 && s.counters[i].Count > s.counters[i-1].Count {
			s.promote(i)
			i--
		}
	} else {
		if len(s.counters) < s.capacity {
			s.counters = append(s.counters, newCounter(item))
			s.index[item] = len(s.counters) - 1
		} else {
			minCounter := s.counters[len(s.counters)-1]
			delete(s.index, minCounter.Item)

			counter := newCounter(item)
			counter.Count = minCounter.Count + 1
			counter.ErrorRate = minCounter.Count
			s.counters[len(s.counters)-1] = counter
			s.index[item] = len(s.counters) - 1
		}
	}
}

func (s *Summary) promote(i int) {
	toPromote := s.counters[i]
	toDemote := s.counters[i-1]

	s.counters[i-1] = toPromote
	s.index[toPromote.Item] = i - 1

	s.counters[i] = toDemote
	s.index[toDemote.Item] = i
}
<|endoftext|>


<commit_before>/*
Copyright 2021 The Knative Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"flag"
	"log"
	"net/http"
	"os"
	"path/filepath"

	"knative.dev/serving/test"
)

func testfilePath() string {
	base := os.Getenv("DATA_PATH")
	if base == "" {
		base = "/data"
	}
	return filepath.Join(base, "testfile")
}

func handler(w http.ResponseWriter, r *http.Request) {
	content, err := os.ReadFile(testfilePath())
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Write(content)
}

func main() {
	flag.Parse()

	log.Print("Writing test content.")
	if err := os.WriteFile(testfilePath(), []byte(test.EmptyDirText), 0644); err != nil {
		panic(err)
	}

	log.Print("Empty dir volume app started.")
	test.ListenAndServeGracefully(":8080", handler)
}
<commit_msg>Drop unused flag.Parse and simplify emptyDir image even more (#11855)<commit_after>/*
Copyright 2021 The Knative Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

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

	"knative.dev/serving/test"
)

func main() {
	base := os.Getenv("DATA_PATH")
	if base == "" {
		base = "/data"
	}

	testfilePath := filepath.Join(base, "testfile")
	log.Printf("Writing test content to %s.", testfilePath)

	if err := os.WriteFile(testfilePath, []byte(test.EmptyDirText), 0644); err != nil {
		panic(err)
	}

	log.Print("Empty dir volume app started.")
	test.ListenAndServeGracefully(":8080", func(w http.ResponseWriter, _ *http.Request) {
		content, err := os.ReadFile(testfilePath)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		w.Write(content)
	})
}
<|endoftext|>


<commit_before>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"code.google.com/p/go.crypto/pbkdf2"
	crypto_rand "crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"flag"
	"fmt"
	"github.com/jacobsa/aws/s3"
	"github.com/jacobsa/aws/sdb"
	"github.com/jacobsa/comeback/backup"
	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/config"
	"github.com/jacobsa/comeback/crypto"
	"github.com/jacobsa/comeback/fs"
	s3_kv "github.com/jacobsa/comeback/kv/s3"
	"github.com/jacobsa/comeback/sys"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"os"
	"time"
)

// Keep this consistent with the item name used in the backup package.
const markerItemName = "comeback_marker"
const saltAttributeName = "password_salt"

var g_configFile = flag.String("config", "", "Path to config file.")
var g_jobName = flag.String("job", "", "Job name within the config file.")

func randUint64(randSrc *rand.Rand) uint64

// Return the existing salt used by the domain, or nil if there is none.
func getExistingSalt(domain sdb.Domain) (salt []byte, err error) {
	// Call the domain.
	attrs, err := domain.GetAttributes(
		markerItemName,
		false, // No need to ask for a consistent read
		[]string{saltAttributeName},
	)

	if err != nil {
		err = fmt.Errorf("GetAttributes: %v", err)
		return
	}

	if len(attrs) == 0 {
		return
	}

	if attrs[0].Name != saltAttributeName {
		panic(fmt.Errorf("Unexpected attribute: %v", attrs[0]))
	}

	// Base64-decode the salt.
	salt, err = base64.StdEncoding.DecodeString(attrs[0].Value)
	if err != nil {
		err = fmt.Errorf("base64.DecodeString(%s): %v", attrs[0].Value, err)
		return
	}

	return
}

func generateAndSetSalt(domain sdb.Domain) (salt []byte, err error) {
	const saltLen = 8

	// Generate a salt.
	salt = make([]byte, saltLen)
	if _, err = io.ReadAtLeast(crypto_rand.Reader, salt, len(salt)); err != nil {
		err = fmt.Errorf("Reading random bytes: %v", err)
		return
	}

	// Base64-encode it.
	encoded := base64.StdEncoding.EncodeToString(salt)

	// Write it out, making sure nobody has beaten us to the punch.
	err = domain.PutAttributes(
		markerItemName,
		[]sdb.PutUpdate{
			sdb.PutUpdate{Name: saltAttributeName, Value: encoded},
		},
		&sdb.Precondition{Name: saltAttributeName, Value: nil},
	)

	if err != nil {
		err = fmt.Errorf("PutAttributes: %v", err)
		return
	}

	return
}

func main() {
	var err error
	flag.Parse()

	// Attempt to read the user's config data.
	if *g_configFile == "" {
		fmt.Println("You must set -config.")
		os.Exit(1)
	}

	configData, err := ioutil.ReadFile(*g_configFile)
	if err != nil {
		fmt.Println("Error reading config file:", err)
		os.Exit(1)
	}

	// Parse the config file.
	cfg, err := config.Parse(configData)
	if err != nil {
		fmt.Println("Parsing config file:", err)
		os.Exit(1)
	}

	// Validate the config file.
	if err := config.Validate(cfg); err != nil {
		fmt.Printf("Config file invalid: %v", err)
		os.Exit(1)
	}

	// Look for the specified job.
	if *g_jobName == "" {
		fmt.Println("You must set -job.")
		os.Exit(1)
	}

	job, ok := cfg.Jobs[*g_jobName]
	if !ok {
		fmt.Println("Unknown job:", *g_jobName)
		os.Exit(1)
	}

	// Create a user registry.
	userRegistry, err := sys.NewUserRegistry()
	if err != nil {
		log.Fatalf("Creating user registry: %v", err)
	}

	// Create a group registry.
	groupRegistry, err := sys.NewGroupRegistry()
	if err != nil {
		log.Fatalf("Creating group registry: %v", err)
	}

	// Create a file system.
	fileSystem, err := fs.NewFileSystem(userRegistry, groupRegistry)
	if err != nil {
		log.Fatalf("Creating file system: %v", err)
	}

	// Open a connection to SimpleDB.
	db, err := sdb.NewSimpleDB(cfg.SdbRegion, cfg.AccessKey)
	if err != nil {
		log.Fatalf("Creating SimpleDB: %v", err)
	}

	// Open the appropriate domain.
	domain, err := db.OpenDomain(cfg.SdbDomain)
	if err != nil {
		log.Fatalf("OpenDomain: %v", err)
	}

	// Open a connection to S3.
	bucket, err := s3.OpenBucket(cfg.S3Bucket, cfg.S3Region, cfg.AccessKey)
	if err != nil {
		log.Fatalf("Creating bucket: %v", err)
	}

	// Read in a password.
	password := []byte(readPassword("Enter crypto password: "))
	if len(password) == 0 {
		log.Fatalf("You must enter a password.")
	}

	// Look for a salt that has already been used.
	salt, err := getExistingSalt(domain)
	if err != nil {
		log.Fatalf("%v", err)
	}

	// If there is no existing salt, set one up.
	if salt == nil {
		salt, err = generateAndSetSalt(domain)
		if err != nil {
			log.Fatalf("%v", err)
		}
	}

	// Derive a crypto key from the password using PBKDF2, recommended for use by
	// NIST Special Publication 800-132. The latter says that PBKDF2 is approved
	// for use with HMAC and any approved hash function. Special Publication
	// 800-107 lists SHA-256 as an approved hash function.
	const pbkdf2Iters = 4096
	const keyLen = 32  // Minimum key length for AES-SIV
	cryptoKey := pbkdf2.Key(password, salt, pbkdf2Iters, keyLen, sha256.New)

	// Create the crypter.
	crypter, err := crypto.NewCrypter(cryptoKey)
	if err != nil {
		log.Fatalf("Creating crypter: %v", err)
	}

	// Create the backup registry.
	randSrc := rand.New(rand.NewSource(time.Now().UnixNano()))
	registry, err := backup.NewRegistry(domain, crypter, randSrc)
	if err != nil {
		log.Fatalf("Creating registry: %v", err)
	}

	// Create the kv store.
	kvStore, err := s3_kv.NewS3KvStore(bucket)
	if err != nil {
		log.Fatalf("Creating kv store: %v", err)
	}

	// Create the blob store.
	blobStore := blob.NewKvBasedBlobStore(kvStore)
	blobStore = blob.NewCheckingStore(blobStore)
	blobStore = blob.NewEncryptingStore(crypter, blobStore)

	// Create the file saver.
	fileSaver, err := backup.NewFileSaver(blobStore, 1<<24)
	if err != nil {
		log.Fatalf("Creating file saver: %v", err)
	}

	// Create a directory saver.
	dirSaver, err := backup.NewDirectorySaver(
		blobStore,
		fileSystem,
		fileSaver)

	if err != nil {
		log.Fatalf("Creating directory saver: %v", err)
	}

	// Choose a start time for the job.
	startTime := time.Now()

	// Run the job.
	score, err := dirSaver.Save(job.BasePath, "", job.Excludes)
	if err != nil {
		log.Fatalf("Saving: %v", err)
	}

	// Register the successful backup.
	completedJob := backup.CompletedJob{
		Id:        randUint64(randSrc),
		Name:      *g_jobName,
		StartTime: startTime,
		Score:     score,
	}

	if err = registry.RecordBackup(completedJob); err != nil {
		log.Fatalf("Recoding to registry: %v", err)
	}

	fmt.Printf("Successfully backed up. ID: %16x\n", completedJob.Id)
}
<commit_msg>Implemented randUint64.<commit_after>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"code.google.com/p/go.crypto/pbkdf2"
	crypto_rand "crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"flag"
	"fmt"
	"github.com/jacobsa/aws/s3"
	"github.com/jacobsa/aws/sdb"
	"github.com/jacobsa/comeback/backup"
	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/config"
	"github.com/jacobsa/comeback/crypto"
	"github.com/jacobsa/comeback/fs"
	s3_kv "github.com/jacobsa/comeback/kv/s3"
	"github.com/jacobsa/comeback/sys"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"os"
	"time"
)

// Keep this consistent with the item name used in the backup package.
const markerItemName = "comeback_marker"
const saltAttributeName = "password_salt"

var g_configFile = flag.String("config", "", "Path to config file.")
var g_jobName = flag.String("job", "", "Job name within the config file.")

func randUint64(randSrc *rand.Rand) uint64 {
	return (uint64(randSrc.Uint32()) << 32) | uint64(randSrc.Uint32())
}

// Return the existing salt used by the domain, or nil if there is none.
func getExistingSalt(domain sdb.Domain) (salt []byte, err error) {
	// Call the domain.
	attrs, err := domain.GetAttributes(
		markerItemName,
		false, // No need to ask for a consistent read
		[]string{saltAttributeName},
	)

	if err != nil {
		err = fmt.Errorf("GetAttributes: %v", err)
		return
	}

	if len(attrs) == 0 {
		return
	}

	if attrs[0].Name != saltAttributeName {
		panic(fmt.Errorf("Unexpected attribute: %v", attrs[0]))
	}

	// Base64-decode the salt.
	salt, err = base64.StdEncoding.DecodeString(attrs[0].Value)
	if err != nil {
		err = fmt.Errorf("base64.DecodeString(%s): %v", attrs[0].Value, err)
		return
	}

	return
}

func generateAndSetSalt(domain sdb.Domain) (salt []byte, err error) {
	const saltLen = 8

	// Generate a salt.
	salt = make([]byte, saltLen)
	if _, err = io.ReadAtLeast(crypto_rand.Reader, salt, len(salt)); err != nil {
		err = fmt.Errorf("Reading random bytes: %v", err)
		return
	}

	// Base64-encode it.
	encoded := base64.StdEncoding.EncodeToString(salt)

	// Write it out, making sure nobody has beaten us to the punch.
	err = domain.PutAttributes(
		markerItemName,
		[]sdb.PutUpdate{
			sdb.PutUpdate{Name: saltAttributeName, Value: encoded},
		},
		&sdb.Precondition{Name: saltAttributeName, Value: nil},
	)

	if err != nil {
		err = fmt.Errorf("PutAttributes: %v", err)
		return
	}

	return
}

func main() {
	var err error
	flag.Parse()

	// Attempt to read the user's config data.
	if *g_configFile == "" {
		fmt.Println("You must set -config.")
		os.Exit(1)
	}

	configData, err := ioutil.ReadFile(*g_configFile)
	if err != nil {
		fmt.Println("Error reading config file:", err)
		os.Exit(1)
	}

	// Parse the config file.
	cfg, err := config.Parse(configData)
	if err != nil {
		fmt.Println("Parsing config file:", err)
		os.Exit(1)
	}

	// Validate the config file.
	if err := config.Validate(cfg); err != nil {
		fmt.Printf("Config file invalid: %v", err)
		os.Exit(1)
	}

	// Look for the specified job.
	if *g_jobName == "" {
		fmt.Println("You must set -job.")
		os.Exit(1)
	}

	job, ok := cfg.Jobs[*g_jobName]
	if !ok {
		fmt.Println("Unknown job:", *g_jobName)
		os.Exit(1)
	}

	// Create a user registry.
	userRegistry, err := sys.NewUserRegistry()
	if err != nil {
		log.Fatalf("Creating user registry: %v", err)
	}

	// Create a group registry.
	groupRegistry, err := sys.NewGroupRegistry()
	if err != nil {
		log.Fatalf("Creating group registry: %v", err)
	}

	// Create a file system.
	fileSystem, err := fs.NewFileSystem(userRegistry, groupRegistry)
	if err != nil {
		log.Fatalf("Creating file system: %v", err)
	}

	// Open a connection to SimpleDB.
	db, err := sdb.NewSimpleDB(cfg.SdbRegion, cfg.AccessKey)
	if err != nil {
		log.Fatalf("Creating SimpleDB: %v", err)
	}

	// Open the appropriate domain.
	domain, err := db.OpenDomain(cfg.SdbDomain)
	if err != nil {
		log.Fatalf("OpenDomain: %v", err)
	}

	// Open a connection to S3.
	bucket, err := s3.OpenBucket(cfg.S3Bucket, cfg.S3Region, cfg.AccessKey)
	if err != nil {
		log.Fatalf("Creating bucket: %v", err)
	}

	// Read in a password.
	password := []byte(readPassword("Enter crypto password: "))
	if len(password) == 0 {
		log.Fatalf("You must enter a password.")
	}

	// Look for a salt that has already been used.
	salt, err := getExistingSalt(domain)
	if err != nil {
		log.Fatalf("%v", err)
	}

	// If there is no existing salt, set one up.
	if salt == nil {
		salt, err = generateAndSetSalt(domain)
		if err != nil {
			log.Fatalf("%v", err)
		}
	}

	// Derive a crypto key from the password using PBKDF2, recommended for use by
	// NIST Special Publication 800-132. The latter says that PBKDF2 is approved
	// for use with HMAC and any approved hash function. Special Publication
	// 800-107 lists SHA-256 as an approved hash function.
	const pbkdf2Iters = 4096
	const keyLen = 32  // Minimum key length for AES-SIV
	cryptoKey := pbkdf2.Key(password, salt, pbkdf2Iters, keyLen, sha256.New)

	// Create the crypter.
	crypter, err := crypto.NewCrypter(cryptoKey)
	if err != nil {
		log.Fatalf("Creating crypter: %v", err)
	}

	// Create the backup registry.
	randSrc := rand.New(rand.NewSource(time.Now().UnixNano()))
	registry, err := backup.NewRegistry(domain, crypter, randSrc)
	if err != nil {
		log.Fatalf("Creating registry: %v", err)
	}

	// Create the kv store.
	kvStore, err := s3_kv.NewS3KvStore(bucket)
	if err != nil {
		log.Fatalf("Creating kv store: %v", err)
	}

	// Create the blob store.
	blobStore := blob.NewKvBasedBlobStore(kvStore)
	blobStore = blob.NewCheckingStore(blobStore)
	blobStore = blob.NewEncryptingStore(crypter, blobStore)

	// Create the file saver.
	fileSaver, err := backup.NewFileSaver(blobStore, 1<<24)
	if err != nil {
		log.Fatalf("Creating file saver: %v", err)
	}

	// Create a directory saver.
	dirSaver, err := backup.NewDirectorySaver(
		blobStore,
		fileSystem,
		fileSaver)

	if err != nil {
		log.Fatalf("Creating directory saver: %v", err)
	}

	// Choose a start time for the job.
	startTime := time.Now()

	// Run the job.
	score, err := dirSaver.Save(job.BasePath, "", job.Excludes)
	if err != nil {
		log.Fatalf("Saving: %v", err)
	}

	// Register the successful backup.
	completedJob := backup.CompletedJob{
		Id:        randUint64(randSrc),
		Name:      *g_jobName,
		StartTime: startTime,
		Score:     score,
	}

	if err = registry.RecordBackup(completedJob); err != nil {
		log.Fatalf("Recoding to registry: %v", err)
	}

	fmt.Printf("Successfully backed up. ID: %16x\n", completedJob.Id)
}
<|endoftext|>


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

package reverseproxy

import (
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/braintree/manners"
	"github.com/nu7hatch/gouuid"
	"github.com/tsuru/planb/log"
)

var (
	emptyResponseBody   = &fixedReadCloser{}
	noRouteResponseBody = &fixedReadCloser{value: noRouteResponseContent}
	noopDirector        = func(*http.Request) {}

	_ ReverseProxy = &NativeReverseProxy{}
)

type NativeReverseProxy struct {
	http.Transport
	ReverseProxyConfig
	servers []*manners.GracefulServer
	rp      *httputil.ReverseProxy
	dialer  *net.Dialer
}

type fixedReadCloser struct {
	value []byte
}

func (r *fixedReadCloser) Read(p []byte) (n int, err error) {
	return copy(p, r.value), io.EOF
}

func (r *fixedReadCloser) Close() error {
	return nil
}

type bufferPool struct {
	syncPool sync.Pool
}

func (p *bufferPool) Get() []byte {
	b := p.syncPool.Get()
	if b == nil {
		return make([]byte, 32*1024)
	}
	return b.([]byte)
}

func (p *bufferPool) Put(b []byte) {
	p.syncPool.Put(b)
}

func (rp *NativeReverseProxy) Initialize(rpConfig ReverseProxyConfig) error {
	rp.ReverseProxyConfig = rpConfig
	rp.servers = make([]*manners.GracefulServer, 0)

	rp.dialer = &net.Dialer{
		Timeout:   rp.DialTimeout,
		KeepAlive: 30 * time.Second,
	}
	rp.Transport = http.Transport{
		Dial:                rp.dialer.Dial,
		TLSHandshakeTimeout: rp.DialTimeout,
		MaxIdleConnsPerHost: 100,
	}
	rp.rp = &httputil.ReverseProxy{
		Director:      noopDirector,
		Transport:     rp,
		FlushInterval: rp.FlushInterval,
		BufferPool:    &bufferPool{},
	}
	return nil
}

func (rp *NativeReverseProxy) Listen(listener net.Listener) {
	server := manners.NewWithServer(&http.Server{Handler: rp})
	rp.servers = append(rp.servers, server)
	server.Serve(listener)
}

func (rp *NativeReverseProxy) Stop() {
	for _, server := range rp.servers {
		server.Close()
	}
}

func (rp *NativeReverseProxy) ridString(req *http.Request) string {
	return rp.RequestIDHeader + ":" + req.Header.Get(rp.RequestIDHeader)
}

func (rp *NativeReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	if req.Host == "__ping__" && req.URL.Path == "/" {
		rw.WriteHeader(http.StatusOK)
		rw.Write(okResponse)
		return
	}
	if rp.RequestIDHeader != "" && req.Header.Get(rp.RequestIDHeader) == "" {
		unparsedID, err := uuid.NewV4()
		if err == nil {
			req.Header.Set(rp.RequestIDHeader, unparsedID.String())
		}
	}
	upgrade := req.Header.Get("Upgrade")
	if upgrade != "" && strings.ToLower(upgrade) == "websocket" {
		reqData, err := rp.serveWebsocket(rw, req)
		if err != nil {
			reqData.logError(req.URL.Path, rp.ridString(req), err)
			http.Error(rw, "", http.StatusBadGateway)
		}
		return
	}
	rp.rp.ServeHTTP(rw, req)
}

func (rp *NativeReverseProxy) serveWebsocket(rw http.ResponseWriter, req *http.Request) (*RequestData, error) {
	reqData, err := rp.Router.ChooseBackend(req.Host)
	if err != nil {
		return reqData, err
	}
	url, err := url.Parse(reqData.Backend)
	if err != nil {
		return reqData, err
	}
	req.Host = url.Host
	dstConn, err := rp.dialer.Dial("tcp", url.Host)
	if err != nil {
		return reqData, err
	}
	defer dstConn.Close()
	hj, ok := rw.(http.Hijacker)
	if !ok {
		return reqData, errors.New("not a hijacker")
	}
	conn, _, err := hj.Hijack()
	if err != nil {
		return reqData, err
	}
	defer conn.Close()
	var clientIP string
	if clientIP, _, err = net.SplitHostPort(req.RemoteAddr); err == nil {
		if prior, ok := req.Header["X-Forwarded-For"]; ok {
			clientIP = strings.Join(prior, ", ") + ", " + clientIP
		}
		req.Header.Set("X-Forwarded-For", clientIP)
	}
	err = req.Write(dstConn)
	if err != nil {
		return reqData, err
	}
	errc := make(chan error, 2)
	cp := func(dst io.Writer, src io.Reader) {
		_, err := io.Copy(dst, src)
		errc <- err
	}
	go cp(dstConn, conn)
	go cp(conn, dstConn)
	<-errc
	return reqData, nil
}

func (rp *NativeReverseProxy) RoundTrip(req *http.Request) (*http.Response, error) {
	req.URL.Scheme = ""
	req.URL.Host = ""
	reqData, err := rp.Router.ChooseBackend(req.Host)
	if err != nil {
		reqData.logError(req.URL.Path, rp.ridString(req), err)
		return rp.roundTripWithData(req, reqData, err), nil
	}
	u, err := url.Parse(reqData.Backend)
	if err == nil {
		req.URL.Host = u.Host
		req.URL.Scheme = u.Scheme
	}
	if req.URL.Host == "" {
		req.URL.Scheme = "http"
		req.URL.Host = reqData.Backend
	}
	return rp.roundTripWithData(req, reqData, nil), nil
}

func (rp *NativeReverseProxy) doResponse(req *http.Request, reqData *RequestData, rsp *http.Response, isDebug bool, isDead bool, backendDuration time.Duration) *http.Response {
	logEntry := func() *log.LogEntry {
		return &log.LogEntry{
			Now:             time.Now(),
			BackendDuration: backendDuration,
			TotalDuration:   time.Since(reqData.StartTime),
			BackendKey:      reqData.BackendKey,
			RemoteAddr:      req.RemoteAddr,
			Method:          req.Method,
			Path:            req.URL.Path,
			Proto:           req.Proto,
			Referer:         req.Referer(),
			UserAgent:       req.UserAgent(),
			RequestIDHeader: rp.RequestIDHeader,
			RequestID:       req.Header.Get(rp.RequestIDHeader),
			StatusCode:      rsp.StatusCode,
			ContentLength:   rsp.ContentLength,
		}
	}
	rsp.Request = req
	rsp.ProtoMajor = req.ProtoMajor
	rsp.ProtoMinor = req.ProtoMinor
	if rsp.Header == nil {
		rsp.Header = http.Header{}
	}
	if isDebug {
		rsp.Header.Set("X-Debug-Backend-Url", reqData.Backend)
		rsp.Header.Set("X-Debug-Backend-Id", strconv.FormatUint(uint64(reqData.BackendIdx), 10))
		rsp.Header.Set("X-Debug-Frontend-Key", reqData.Host)
	}
	err := rp.Router.EndRequest(reqData, isDead, logEntry)
	if err != nil {
		reqData.logError(req.URL.Path, rp.ridString(req), err)
	}
	return rsp
}

func (rp *NativeReverseProxy) roundTripWithData(req *http.Request, reqData *RequestData, err error) (rsp *http.Response) {
	isDebug := req.Header.Get("X-Debug-Router") != ""
	req.Header.Del("X-Debug-Router")
	if err != nil || req.URL.Scheme == "" || req.URL.Host == "" {
		switch err {
		case nil, ErrNoRegisteredBackends:
			rsp = &http.Response{
				StatusCode:    http.StatusBadRequest,
				ContentLength: int64(len(noRouteResponseBody.value)),
				Body:          noRouteResponseBody,
			}
		default:
			rsp = &http.Response{
				StatusCode: http.StatusServiceUnavailable,
				Body:       emptyResponseBody,
			}
		}
		return rp.doResponse(req, reqData, rsp, isDebug, false, 0)
	}
	var timedout int32
	if rp.RequestTimeout > 0 {
		timer := time.AfterFunc(rp.RequestTimeout, func() {
			atomic.AddInt32(&timedout, 1)
			rp.Transport.CancelRequest(req)
		})
		defer timer.Stop()
	}
	host, _, _ := net.SplitHostPort(req.URL.Host)
	if host == "" {
		host = req.URL.Host
	}
	isIP := net.ParseIP(host) != nil
	if !isIP {
		req.Header.Set("X-Host", req.Host)
		req.Header.Set("X-Forwarded-Host", req.Host)
		req.Host = host
	}
	t0 := time.Now().UTC()
	rsp, err = rp.Transport.RoundTrip(req)
	backendDuration := time.Since(t0)
	markAsDead := false
	if err != nil {
		var dialTimeout, requestTimeout bool
		if netErr, ok := err.(net.Error); ok {
			markAsDead = !netErr.Temporary()
			dialTimeout = netErr.Timeout()
		}
		requestTimeout = atomic.LoadInt32(&timedout) == int32(1)
		if requestTimeout {
			markAsDead = false
			err = fmt.Errorf("request timeout after %v: %s", time.Since(reqData.StartTime), err)
		} else if dialTimeout {
			markAsDead = true
			err = fmt.Errorf("dial timeout after %v: %s", time.Since(reqData.StartTime), err)
		} else {
			err = fmt.Errorf("error in backend request: %s", err)
		}
		if markAsDead {
			err = fmt.Errorf("%s *DEAD*", err)
		}
		reqData.logError(req.URL.Path, rp.ridString(req), err)
		rsp = &http.Response{
			StatusCode: http.StatusServiceUnavailable,
			Body:       emptyResponseBody,
		}
	}
	return rp.doResponse(req, reqData, rsp, isDebug, markAsDead, backendDuration)
}
<commit_msg>reverseproxy: adds custom prometheus metrics<commit_after>// Copyright 2016 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package reverseproxy

import (
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/braintree/manners"
	"github.com/nu7hatch/gouuid"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/tsuru/planb/log"
)

var (
	emptyResponseBody   = &fixedReadCloser{}
	noRouteResponseBody = &fixedReadCloser{value: noRouteResponseContent}
	noopDirector        = func(*http.Request) {}

	_ ReverseProxy = &NativeReverseProxy{}

	openConnections = prometheus.NewGauge(prometheus.GaugeOpts{
		Namespace: "planb",
		Subsystem: "reverseproxy",
		Name:      "connections_open",
		Help:      "The current number of open connections.",
	})

	requestDurations = prometheus.NewHistogram(prometheus.HistogramOpts{
		Namespace: "planb",
		Subsystem: "reverseproxy",
		Name:      "request_duration_seconds",
		Help:      "The total request latencies in seconds.",
	})

	backendDurations = prometheus.NewHistogram(prometheus.HistogramOpts{
		Namespace: "planb",
		Subsystem: "reverseproxy",
		Name:      "backend_request_duration_seconds",
		Help:      "The HTTP request latencies in seconds.",
	})

	backendResponse = prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: "planb",
		Subsystem: "reverseproxy",
		Name:      "backends_responses",
		Help:      "The total backends responses by status code.",
	}, []string{"status_code"})
)

func init() {
	prometheus.MustRegister(openConnections)
	prometheus.MustRegister(requestDurations)
	prometheus.MustRegister(backendResponse)
	prometheus.MustRegister(backendDurations)
}

type NativeReverseProxy struct {
	http.Transport
	ReverseProxyConfig
	servers []*manners.GracefulServer
	rp      *httputil.ReverseProxy
	dialer  *net.Dialer
}

type fixedReadCloser struct {
	value []byte
}

func (r *fixedReadCloser) Read(p []byte) (n int, err error) {
	return copy(p, r.value), io.EOF
}

func (r *fixedReadCloser) Close() error {
	return nil
}

type bufferPool struct {
	syncPool sync.Pool
}

func (p *bufferPool) Get() []byte {
	b := p.syncPool.Get()
	if b == nil {
		return make([]byte, 32*1024)
	}
	return b.([]byte)
}

func (p *bufferPool) Put(b []byte) {
	p.syncPool.Put(b)
}

func (rp *NativeReverseProxy) Initialize(rpConfig ReverseProxyConfig) error {
	rp.ReverseProxyConfig = rpConfig
	rp.servers = make([]*manners.GracefulServer, 0)

	rp.dialer = &net.Dialer{
		Timeout:   rp.DialTimeout,
		KeepAlive: 30 * time.Second,
	}
	rp.Transport = http.Transport{
		Dial:                rp.dialer.Dial,
		TLSHandshakeTimeout: rp.DialTimeout,
		MaxIdleConnsPerHost: 100,
	}
	rp.rp = &httputil.ReverseProxy{
		Director:      noopDirector,
		Transport:     rp,
		FlushInterval: rp.FlushInterval,
		BufferPool:    &bufferPool{},
	}
	return nil
}

func (rp *NativeReverseProxy) Listen(listener net.Listener) {
	server := manners.NewWithServer(&http.Server{
		Handler: rp,
		ConnState: func(c net.Conn, s http.ConnState) {
			switch s {
			case http.StateNew:
				openConnections.Inc()
			case http.StateClosed:
				openConnections.Dec()
			}
		},
	})
	rp.servers = append(rp.servers, server)
	server.Serve(listener)
}

func (rp *NativeReverseProxy) Stop() {
	for _, server := range rp.servers {
		server.Close()
	}
}

func (rp *NativeReverseProxy) ridString(req *http.Request) string {
	return rp.RequestIDHeader + ":" + req.Header.Get(rp.RequestIDHeader)
}

func (rp *NativeReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	if req.Host == "__ping__" && req.URL.Path == "/" {
		rw.WriteHeader(http.StatusOK)
		rw.Write(okResponse)
		return
	}
	if rp.RequestIDHeader != "" && req.Header.Get(rp.RequestIDHeader) == "" {
		unparsedID, err := uuid.NewV4()
		if err == nil {
			req.Header.Set(rp.RequestIDHeader, unparsedID.String())
		}
	}
	upgrade := req.Header.Get("Upgrade")
	if upgrade != "" && strings.ToLower(upgrade) == "websocket" {
		reqData, err := rp.serveWebsocket(rw, req)
		if err != nil {
			reqData.logError(req.URL.Path, rp.ridString(req), err)
			http.Error(rw, "", http.StatusBadGateway)
		}
		return
	}
	rp.rp.ServeHTTP(rw, req)
}

func (rp *NativeReverseProxy) serveWebsocket(rw http.ResponseWriter, req *http.Request) (*RequestData, error) {
	reqData, err := rp.Router.ChooseBackend(req.Host)
	if err != nil {
		return reqData, err
	}
	url, err := url.Parse(reqData.Backend)
	if err != nil {
		return reqData, err
	}
	req.Host = url.Host
	dstConn, err := rp.dialer.Dial("tcp", url.Host)
	if err != nil {
		return reqData, err
	}
	defer dstConn.Close()
	hj, ok := rw.(http.Hijacker)
	if !ok {
		return reqData, errors.New("not a hijacker")
	}
	conn, _, err := hj.Hijack()
	if err != nil {
		return reqData, err
	}
	defer conn.Close()
	var clientIP string
	if clientIP, _, err = net.SplitHostPort(req.RemoteAddr); err == nil {
		if prior, ok := req.Header["X-Forwarded-For"]; ok {
			clientIP = strings.Join(prior, ", ") + ", " + clientIP
		}
		req.Header.Set("X-Forwarded-For", clientIP)
	}
	err = req.Write(dstConn)
	if err != nil {
		return reqData, err
	}
	errc := make(chan error, 2)
	cp := func(dst io.Writer, src io.Reader) {
		_, err := io.Copy(dst, src)
		errc <- err
	}
	go cp(dstConn, conn)
	go cp(conn, dstConn)
	<-errc
	return reqData, nil
}

func (rp *NativeReverseProxy) RoundTrip(req *http.Request) (*http.Response, error) {
	req.URL.Scheme = ""
	req.URL.Host = ""
	reqData, err := rp.Router.ChooseBackend(req.Host)
	if err != nil {
		reqData.logError(req.URL.Path, rp.ridString(req), err)
		return rp.roundTripWithData(req, reqData, err), nil
	}
	u, err := url.Parse(reqData.Backend)
	if err == nil {
		req.URL.Host = u.Host
		req.URL.Scheme = u.Scheme
	}
	if req.URL.Host == "" {
		req.URL.Scheme = "http"
		req.URL.Host = reqData.Backend
	}
	return rp.roundTripWithData(req, reqData, nil), nil
}

func (rp *NativeReverseProxy) doResponse(req *http.Request, reqData *RequestData, rsp *http.Response, isDebug bool, isDead bool, backendDuration time.Duration) *http.Response {
	totalDuration := time.Since(reqData.StartTime)
	logEntry := func() *log.LogEntry {
		return &log.LogEntry{
			Now:             time.Now(),
			BackendDuration: backendDuration,
			TotalDuration:   totalDuration,
			BackendKey:      reqData.BackendKey,
			RemoteAddr:      req.RemoteAddr,
			Method:          req.Method,
			Path:            req.URL.Path,
			Proto:           req.Proto,
			Referer:         req.Referer(),
			UserAgent:       req.UserAgent(),
			RequestIDHeader: rp.RequestIDHeader,
			RequestID:       req.Header.Get(rp.RequestIDHeader),
			StatusCode:      rsp.StatusCode,
			ContentLength:   rsp.ContentLength,
		}
	}
	rsp.Request = req
	rsp.ProtoMajor = req.ProtoMajor
	rsp.ProtoMinor = req.ProtoMinor
	if rsp.Header == nil {
		rsp.Header = http.Header{}
	}
	if isDebug {
		rsp.Header.Set("X-Debug-Backend-Url", reqData.Backend)
		rsp.Header.Set("X-Debug-Backend-Id", strconv.FormatUint(uint64(reqData.BackendIdx), 10))
		rsp.Header.Set("X-Debug-Frontend-Key", reqData.Host)
	}
	err := rp.Router.EndRequest(reqData, isDead, logEntry)
	if err != nil {
		reqData.logError(req.URL.Path, rp.ridString(req), err)
	}
	backendResponse.WithLabelValues(strconv.Itoa(rsp.StatusCode)).Inc()
	backendDurations.Observe(backendDuration.Seconds())
	requestDurations.Observe(totalDuration.Seconds())
	return rsp
}

func (rp *NativeReverseProxy) roundTripWithData(req *http.Request, reqData *RequestData, err error) (rsp *http.Response) {
	isDebug := req.Header.Get("X-Debug-Router") != ""
	req.Header.Del("X-Debug-Router")
	if err != nil || req.URL.Scheme == "" || req.URL.Host == "" {
		switch err {
		case nil, ErrNoRegisteredBackends:
			rsp = &http.Response{
				StatusCode:    http.StatusBadRequest,
				ContentLength: int64(len(noRouteResponseBody.value)),
				Body:          noRouteResponseBody,
			}
		default:
			rsp = &http.Response{
				StatusCode: http.StatusServiceUnavailable,
				Body:       emptyResponseBody,
			}
		}
		return rp.doResponse(req, reqData, rsp, isDebug, false, 0)
	}
	var timedout int32
	if rp.RequestTimeout > 0 {
		timer := time.AfterFunc(rp.RequestTimeout, func() {
			atomic.AddInt32(&timedout, 1)
			rp.Transport.CancelRequest(req)
		})
		defer timer.Stop()
	}
	host, _, _ := net.SplitHostPort(req.URL.Host)
	if host == "" {
		host = req.URL.Host
	}
	isIP := net.ParseIP(host) != nil
	if !isIP {
		req.Header.Set("X-Host", req.Host)
		req.Header.Set("X-Forwarded-Host", req.Host)
		req.Host = host
	}
	t0 := time.Now().UTC()
	rsp, err = rp.Transport.RoundTrip(req)
	backendDuration := time.Since(t0)
	markAsDead := false
	if err != nil {
		var dialTimeout, requestTimeout bool
		if netErr, ok := err.(net.Error); ok {
			markAsDead = !netErr.Temporary()
			dialTimeout = netErr.Timeout()
		}
		requestTimeout = atomic.LoadInt32(&timedout) == int32(1)
		if requestTimeout {
			markAsDead = false
			err = fmt.Errorf("request timeout after %v: %s", time.Since(reqData.StartTime), err)
		} else if dialTimeout {
			markAsDead = true
			err = fmt.Errorf("dial timeout after %v: %s", time.Since(reqData.StartTime), err)
		} else {
			err = fmt.Errorf("error in backend request: %s", err)
		}
		if markAsDead {
			err = fmt.Errorf("%s *DEAD*", err)
		}
		reqData.logError(req.URL.Path, rp.ridString(req), err)
		rsp = &http.Response{
			StatusCode: http.StatusServiceUnavailable,
			Body:       emptyResponseBody,
		}
	}
	return rp.doResponse(req, reqData, rsp, isDebug, markAsDead, backendDuration)
}
<|endoftext|>


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

package reverseproxy

import (
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/braintree/manners"
	"github.com/nu7hatch/gouuid"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/tsuru/planb/log"
)

var (
	emptyResponseBody           = &fixedReadCloser{}
	noRouteResponseBody         = &fixedReadCloser{value: noRouteResponseContent}
	allBackendsDeadResponseBody = &fixedReadCloser{value: allBackendsDeadContent}
	noopDirector                = func(*http.Request) {}

	_ ReverseProxy = &NativeReverseProxy{}

	openConnections = prometheus.NewGauge(prometheus.GaugeOpts{
		Namespace: "planb",
		Subsystem: "reverseproxy",
		Name:      "connections_current_open",
		Help:      "The current number of open connections excluding hijacked ones.",
	})

	requestDurations = prometheus.NewHistogram(prometheus.HistogramOpts{
		Namespace: "planb",
		Subsystem: "reverseproxy",
		Name:      "http_request_duration_seconds",
		Help:      "The total HTTP request latencies in seconds.",
	})

	backendDurations = prometheus.NewHistogram(prometheus.HistogramOpts{
		Namespace: "planb",
		Subsystem: "reverseproxy",
		Name:      "backend_request_duration_seconds",
		Help:      "The backends HTTP request latencies in seconds.",
	})

	backendResponse = prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: "planb",
		Subsystem: "reverseproxy",
		Name:      "backends_responses_total",
		Help:      "The total backends responses by status code.",
	}, []string{"status_code"})
)

func init() {
	prometheus.MustRegister(openConnections)
	prometheus.MustRegister(requestDurations)
	prometheus.MustRegister(backendResponse)
	prometheus.MustRegister(backendDurations)
}

type NativeReverseProxy struct {
	http.Transport
	ReverseProxyConfig
	servers []*manners.GracefulServer
	rp      *httputil.ReverseProxy
	dialer  *net.Dialer
}

type fixedReadCloser struct {
	value []byte
}

func (r *fixedReadCloser) Read(p []byte) (n int, err error) {
	return copy(p, r.value), io.EOF
}

func (r *fixedReadCloser) Close() error {
	return nil
}

type bufferPool struct {
	syncPool sync.Pool
}

func (p *bufferPool) Get() []byte {
	b := p.syncPool.Get()
	if b == nil {
		return make([]byte, 32*1024)
	}
	return b.([]byte)
}

func (p *bufferPool) Put(b []byte) {
	p.syncPool.Put(b)
}

func (rp *NativeReverseProxy) Initialize(rpConfig ReverseProxyConfig) error {
	rp.ReverseProxyConfig = rpConfig
	rp.servers = make([]*manners.GracefulServer, 0)

	rp.dialer = &net.Dialer{
		Timeout:   rp.DialTimeout,
		KeepAlive: 30 * time.Second,
	}
	rp.Transport = http.Transport{
		Dial:                rp.dialer.Dial,
		TLSHandshakeTimeout: rp.DialTimeout,
		MaxIdleConnsPerHost: 100,
	}
	rp.rp = &httputil.ReverseProxy{
		Director:      noopDirector,
		Transport:     rp,
		FlushInterval: rp.FlushInterval,
		BufferPool:    &bufferPool{},
	}
	return nil
}

func (rp *NativeReverseProxy) Listen(listener net.Listener) {
	server := manners.NewWithServer(&http.Server{
		ReadTimeout:       rp.ReadTimeout,
		ReadHeaderTimeout: rp.ReadHeaderTimeout,
		WriteTimeout:      rp.WriteTimeout,
		IdleTimeout:       rp.IdleTimeout,
		Handler:           rp,
		ConnState: func(c net.Conn, s http.ConnState) {
			switch s {
			case http.StateNew:
				openConnections.Inc()
			case http.StateHijacked:
				openConnections.Dec()
			case http.StateClosed:
				openConnections.Dec()
			}
		},
	})
	rp.servers = append(rp.servers, server)
	server.Serve(listener)
}

func (rp *NativeReverseProxy) Stop() {
	for _, server := range rp.servers {
		server.Close()
	}
}

func (rp *NativeReverseProxy) ridString(req *http.Request) string {
	return rp.RequestIDHeader + ":" + req.Header.Get(rp.RequestIDHeader)
}

func (rp *NativeReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	if req.Host == "__ping__" && req.URL.Path == "/" {
		rw.WriteHeader(http.StatusOK)
		rw.Write(okResponse)
		return
	}
	if rp.RequestIDHeader != "" && req.Header.Get(rp.RequestIDHeader) == "" {
		unparsedID, err := uuid.NewV4()
		if err == nil {
			req.Header.Set(rp.RequestIDHeader, unparsedID.String())
		}
	}
	upgrade := req.Header.Get("Upgrade")
	if upgrade != "" && strings.ToLower(upgrade) == "websocket" {
		reqData, err := rp.serveWebsocket(rw, req)
		if err != nil {
			reqData.logError(req.URL.Path, rp.ridString(req), err)
			http.Error(rw, "", http.StatusBadGateway)
		}
		return
	}
	rp.rp.ServeHTTP(rw, req)
}

func (rp *NativeReverseProxy) serveWebsocket(rw http.ResponseWriter, req *http.Request) (*RequestData, error) {
	reqData, err := rp.Router.ChooseBackend(req.Host)
	if err != nil {
		return reqData, err
	}
	url, err := url.Parse(reqData.Backend)
	if err != nil {
		return reqData, err
	}
	req.Host = url.Host
	dstConn, err := rp.dialer.Dial("tcp", url.Host)
	if err != nil {
		return reqData, err
	}
	defer dstConn.Close()
	hj, ok := rw.(http.Hijacker)
	if !ok {
		return reqData, errors.New("not a hijacker")
	}
	conn, _, err := hj.Hijack()
	if err != nil {
		return reqData, err
	}
	defer conn.Close()
	var clientIP string
	if clientIP, _, err = net.SplitHostPort(req.RemoteAddr); err == nil {
		if prior, ok := req.Header["X-Forwarded-For"]; ok {
			clientIP = strings.Join(prior, ", ") + ", " + clientIP
		}
		req.Header.Set("X-Forwarded-For", clientIP)
	}
	err = req.Write(dstConn)
	if err != nil {
		return reqData, err
	}
	errc := make(chan error, 2)
	cp := func(dst io.Writer, src io.Reader) {
		_, err := io.Copy(dst, src)
		errc <- err
	}
	go cp(dstConn, conn)
	go cp(conn, dstConn)
	<-errc
	return reqData, nil
}

func (rp *NativeReverseProxy) RoundTrip(req *http.Request) (*http.Response, error) {
	req.URL.Scheme = ""
	req.URL.Host = ""
	reqData, err := rp.Router.ChooseBackend(req.Host)
	if err != nil {
		reqData.logError(req.URL.Path, rp.ridString(req), err)
		return rp.roundTripWithData(req, reqData, err), nil
	}
	u, err := url.Parse(reqData.Backend)
	if err == nil {
		req.URL.Host = u.Host
		req.URL.Scheme = u.Scheme
	}
	if req.URL.Host == "" {
		req.URL.Scheme = "http"
		req.URL.Host = reqData.Backend
	}
	return rp.roundTripWithData(req, reqData, nil), nil
}

func (rp *NativeReverseProxy) doResponse(req *http.Request, reqData *RequestData, rsp *http.Response, isDebug bool, isDead bool, backendDuration time.Duration) *http.Response {
	totalDuration := time.Since(reqData.StartTime)
	logEntry := func() *log.LogEntry {
		return &log.LogEntry{
			Now:             time.Now(),
			BackendDuration: backendDuration,
			TotalDuration:   totalDuration,
			BackendKey:      reqData.BackendKey,
			RemoteAddr:      req.RemoteAddr,
			Method:          req.Method,
			Path:            req.URL.Path,
			Proto:           req.Proto,
			Referer:         req.Referer(),
			UserAgent:       req.UserAgent(),
			RequestIDHeader: rp.RequestIDHeader,
			RequestID:       req.Header.Get(rp.RequestIDHeader),
			StatusCode:      rsp.StatusCode,
			ContentLength:   rsp.ContentLength,
		}
	}
	rsp.Request = req
	rsp.ProtoMajor = req.ProtoMajor
	rsp.ProtoMinor = req.ProtoMinor
	if rsp.Header == nil {
		rsp.Header = http.Header{}
	}
	if isDebug {
		rsp.Header.Set("X-Debug-Backend-Url", reqData.Backend)
		rsp.Header.Set("X-Debug-Backend-Id", strconv.FormatUint(uint64(reqData.BackendIdx), 10))
		rsp.Header.Set("X-Debug-Frontend-Key", reqData.Host)
	}
	err := rp.Router.EndRequest(reqData, isDead, logEntry)
	if err != nil {
		reqData.logError(req.URL.Path, rp.ridString(req), err)
	}
	backendResponse.WithLabelValues(strconv.Itoa(rsp.StatusCode)).Inc()
	backendDurations.Observe(backendDuration.Seconds())
	requestDurations.Observe(totalDuration.Seconds())
	return rsp
}

func (rp *NativeReverseProxy) roundTripWithData(req *http.Request, reqData *RequestData, err error) (rsp *http.Response) {
	isDebug := req.Header.Get("X-Debug-Router") != ""
	req.Header.Del("X-Debug-Router")
	if err != nil || req.URL.Scheme == "" || req.URL.Host == "" {
		switch err {
		case ErrAllBackendsDead:
			rsp = &http.Response{
				StatusCode:    http.StatusServiceUnavailable,
				ContentLength: int64(len(allBackendsDeadResponseBody.value)),
				Body:          allBackendsDeadResponseBody,
			}
		case nil, ErrNoRegisteredBackends:
			rsp = &http.Response{
				StatusCode:    http.StatusBadRequest,
				ContentLength: int64(len(noRouteResponseBody.value)),
				Body:          noRouteResponseBody,
			}
		default:
			rsp = &http.Response{
				StatusCode: http.StatusServiceUnavailable,
				Body:       emptyResponseBody,
			}
		}
		return rp.doResponse(req, reqData, rsp, isDebug, false, 0)
	}
	var timedout int32
	if rp.RequestTimeout > 0 {
		timer := time.AfterFunc(rp.RequestTimeout, func() {
			atomic.AddInt32(&timedout, 1)
			rp.Transport.CancelRequest(req)
		})
		defer timer.Stop()
	}
	host, _, _ := net.SplitHostPort(req.URL.Host)
	if host == "" {
		host = req.URL.Host
	}
	isIP := net.ParseIP(host) != nil
	if !isIP {
		req.Header.Set("X-Host", req.Host)
		req.Header.Set("X-Forwarded-Host", req.Host)
		req.Host = host
	}
	t0 := time.Now().UTC()
	rsp, err = rp.Transport.RoundTrip(req)
	backendDuration := time.Since(t0)
	markAsDead := false
	if err != nil {
		var dialTimeout, requestTimeout bool
		if netErr, ok := err.(net.Error); ok {
			markAsDead = !netErr.Temporary()
			dialTimeout = netErr.Timeout()
		}
		requestTimeout = atomic.LoadInt32(&timedout) == int32(1)
		if requestTimeout {
			markAsDead = false
			err = fmt.Errorf("request timeout after %v: %s", time.Since(reqData.StartTime), err)
		} else if dialTimeout {
			markAsDead = true
			err = fmt.Errorf("dial timeout after %v: %s", time.Since(reqData.StartTime), err)
		} else {
			err = fmt.Errorf("error in backend request: %s", err)
		}
		if markAsDead {
			err = fmt.Errorf("%s *DEAD*", err)
		}
		reqData.logError(req.URL.Path, rp.ridString(req), err)
		rsp = &http.Response{
			StatusCode: http.StatusServiceUnavailable,
			Body:       emptyResponseBody,
		}
	}
	return rp.doResponse(req, reqData, rsp, isDebug, markAsDead, backendDuration)
}
<commit_msg>reverseproxy: disable automatic gzip compression<commit_after>// Copyright 2016 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package reverseproxy

import (
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/braintree/manners"
	"github.com/nu7hatch/gouuid"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/tsuru/planb/log"
)

var (
	emptyResponseBody           = &fixedReadCloser{}
	noRouteResponseBody         = &fixedReadCloser{value: noRouteResponseContent}
	allBackendsDeadResponseBody = &fixedReadCloser{value: allBackendsDeadContent}
	noopDirector                = func(*http.Request) {}

	_ ReverseProxy = &NativeReverseProxy{}

	openConnections = prometheus.NewGauge(prometheus.GaugeOpts{
		Namespace: "planb",
		Subsystem: "reverseproxy",
		Name:      "connections_current_open",
		Help:      "The current number of open connections excluding hijacked ones.",
	})

	requestDurations = prometheus.NewHistogram(prometheus.HistogramOpts{
		Namespace: "planb",
		Subsystem: "reverseproxy",
		Name:      "http_request_duration_seconds",
		Help:      "The total HTTP request latencies in seconds.",
	})

	backendDurations = prometheus.NewHistogram(prometheus.HistogramOpts{
		Namespace: "planb",
		Subsystem: "reverseproxy",
		Name:      "backend_request_duration_seconds",
		Help:      "The backends HTTP request latencies in seconds.",
	})

	backendResponse = prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: "planb",
		Subsystem: "reverseproxy",
		Name:      "backends_responses_total",
		Help:      "The total backends responses by status code.",
	}, []string{"status_code"})
)

func init() {
	prometheus.MustRegister(openConnections)
	prometheus.MustRegister(requestDurations)
	prometheus.MustRegister(backendResponse)
	prometheus.MustRegister(backendDurations)
}

type NativeReverseProxy struct {
	http.Transport
	ReverseProxyConfig
	servers []*manners.GracefulServer
	rp      *httputil.ReverseProxy
	dialer  *net.Dialer
}

type fixedReadCloser struct {
	value []byte
}

func (r *fixedReadCloser) Read(p []byte) (n int, err error) {
	return copy(p, r.value), io.EOF
}

func (r *fixedReadCloser) Close() error {
	return nil
}

type bufferPool struct {
	syncPool sync.Pool
}

func (p *bufferPool) Get() []byte {
	b := p.syncPool.Get()
	if b == nil {
		return make([]byte, 32*1024)
	}
	return b.([]byte)
}

func (p *bufferPool) Put(b []byte) {
	p.syncPool.Put(b)
}

func (rp *NativeReverseProxy) Initialize(rpConfig ReverseProxyConfig) error {
	rp.ReverseProxyConfig = rpConfig
	rp.servers = make([]*manners.GracefulServer, 0)

	rp.dialer = &net.Dialer{
		Timeout:   rp.DialTimeout,
		KeepAlive: 30 * time.Second,
	}
	rp.Transport = http.Transport{
		Dial:                rp.dialer.Dial,
		TLSHandshakeTimeout: rp.DialTimeout,
		MaxIdleConnsPerHost: 100,
		DisableCompression:  true,
	}
	rp.rp = &httputil.ReverseProxy{
		Director:      noopDirector,
		Transport:     rp,
		FlushInterval: rp.FlushInterval,
		BufferPool:    &bufferPool{},
	}
	return nil
}

func (rp *NativeReverseProxy) Listen(listener net.Listener) {
	server := manners.NewWithServer(&http.Server{
		ReadTimeout:       rp.ReadTimeout,
		ReadHeaderTimeout: rp.ReadHeaderTimeout,
		WriteTimeout:      rp.WriteTimeout,
		IdleTimeout:       rp.IdleTimeout,
		Handler:           rp,
		ConnState: func(c net.Conn, s http.ConnState) {
			switch s {
			case http.StateNew:
				openConnections.Inc()
			case http.StateHijacked:
				openConnections.Dec()
			case http.StateClosed:
				openConnections.Dec()
			}
		},
	})
	rp.servers = append(rp.servers, server)
	server.Serve(listener)
}

func (rp *NativeReverseProxy) Stop() {
	for _, server := range rp.servers {
		server.Close()
	}
}

func (rp *NativeReverseProxy) ridString(req *http.Request) string {
	return rp.RequestIDHeader + ":" + req.Header.Get(rp.RequestIDHeader)
}

func (rp *NativeReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	if req.Host == "__ping__" && req.URL.Path == "/" {
		rw.WriteHeader(http.StatusOK)
		rw.Write(okResponse)
		return
	}
	if rp.RequestIDHeader != "" && req.Header.Get(rp.RequestIDHeader) == "" {
		unparsedID, err := uuid.NewV4()
		if err == nil {
			req.Header.Set(rp.RequestIDHeader, unparsedID.String())
		}
	}
	upgrade := req.Header.Get("Upgrade")
	if upgrade != "" && strings.ToLower(upgrade) == "websocket" {
		reqData, err := rp.serveWebsocket(rw, req)
		if err != nil {
			reqData.logError(req.URL.Path, rp.ridString(req), err)
			http.Error(rw, "", http.StatusBadGateway)
		}
		return
	}
	rp.rp.ServeHTTP(rw, req)
}

func (rp *NativeReverseProxy) serveWebsocket(rw http.ResponseWriter, req *http.Request) (*RequestData, error) {
	reqData, err := rp.Router.ChooseBackend(req.Host)
	if err != nil {
		return reqData, err
	}
	url, err := url.Parse(reqData.Backend)
	if err != nil {
		return reqData, err
	}
	req.Host = url.Host
	dstConn, err := rp.dialer.Dial("tcp", url.Host)
	if err != nil {
		return reqData, err
	}
	defer dstConn.Close()
	hj, ok := rw.(http.Hijacker)
	if !ok {
		return reqData, errors.New("not a hijacker")
	}
	conn, _, err := hj.Hijack()
	if err != nil {
		return reqData, err
	}
	defer conn.Close()
	var clientIP string
	if clientIP, _, err = net.SplitHostPort(req.RemoteAddr); err == nil {
		if prior, ok := req.Header["X-Forwarded-For"]; ok {
			clientIP = strings.Join(prior, ", ") + ", " + clientIP
		}
		req.Header.Set("X-Forwarded-For", clientIP)
	}
	err = req.Write(dstConn)
	if err != nil {
		return reqData, err
	}
	errc := make(chan error, 2)
	cp := func(dst io.Writer, src io.Reader) {
		_, err := io.Copy(dst, src)
		errc <- err
	}
	go cp(dstConn, conn)
	go cp(conn, dstConn)
	<-errc
	return reqData, nil
}

func (rp *NativeReverseProxy) RoundTrip(req *http.Request) (*http.Response, error) {
	req.URL.Scheme = ""
	req.URL.Host = ""
	reqData, err := rp.Router.ChooseBackend(req.Host)
	if err != nil {
		reqData.logError(req.URL.Path, rp.ridString(req), err)
		return rp.roundTripWithData(req, reqData, err), nil
	}
	u, err := url.Parse(reqData.Backend)
	if err == nil {
		req.URL.Host = u.Host
		req.URL.Scheme = u.Scheme
	}
	if req.URL.Host == "" {
		req.URL.Scheme = "http"
		req.URL.Host = reqData.Backend
	}
	return rp.roundTripWithData(req, reqData, nil), nil
}

func (rp *NativeReverseProxy) doResponse(req *http.Request, reqData *RequestData, rsp *http.Response, isDebug bool, isDead bool, backendDuration time.Duration) *http.Response {
	totalDuration := time.Since(reqData.StartTime)
	logEntry := func() *log.LogEntry {
		return &log.LogEntry{
			Now:             time.Now(),
			BackendDuration: backendDuration,
			TotalDuration:   totalDuration,
			BackendKey:      reqData.BackendKey,
			RemoteAddr:      req.RemoteAddr,
			Method:          req.Method,
			Path:            req.URL.Path,
			Proto:           req.Proto,
			Referer:         req.Referer(),
			UserAgent:       req.UserAgent(),
			RequestIDHeader: rp.RequestIDHeader,
			RequestID:       req.Header.Get(rp.RequestIDHeader),
			StatusCode:      rsp.StatusCode,
			ContentLength:   rsp.ContentLength,
		}
	}
	rsp.Request = req
	rsp.ProtoMajor = req.ProtoMajor
	rsp.ProtoMinor = req.ProtoMinor
	if rsp.Header == nil {
		rsp.Header = http.Header{}
	}
	if isDebug {
		rsp.Header.Set("X-Debug-Backend-Url", reqData.Backend)
		rsp.Header.Set("X-Debug-Backend-Id", strconv.FormatUint(uint64(reqData.BackendIdx), 10))
		rsp.Header.Set("X-Debug-Frontend-Key", reqData.Host)
	}
	err := rp.Router.EndRequest(reqData, isDead, logEntry)
	if err != nil {
		reqData.logError(req.URL.Path, rp.ridString(req), err)
	}
	backendResponse.WithLabelValues(strconv.Itoa(rsp.StatusCode)).Inc()
	backendDurations.Observe(backendDuration.Seconds())
	requestDurations.Observe(totalDuration.Seconds())
	return rsp
}

func (rp *NativeReverseProxy) roundTripWithData(req *http.Request, reqData *RequestData, err error) (rsp *http.Response) {
	isDebug := req.Header.Get("X-Debug-Router") != ""
	req.Header.Del("X-Debug-Router")
	if err != nil || req.URL.Scheme == "" || req.URL.Host == "" {
		switch err {
		case ErrAllBackendsDead:
			rsp = &http.Response{
				StatusCode:    http.StatusServiceUnavailable,
				ContentLength: int64(len(allBackendsDeadResponseBody.value)),
				Body:          allBackendsDeadResponseBody,
			}
		case nil, ErrNoRegisteredBackends:
			rsp = &http.Response{
				StatusCode:    http.StatusBadRequest,
				ContentLength: int64(len(noRouteResponseBody.value)),
				Body:          noRouteResponseBody,
			}
		default:
			rsp = &http.Response{
				StatusCode: http.StatusServiceUnavailable,
				Body:       emptyResponseBody,
			}
		}
		return rp.doResponse(req, reqData, rsp, isDebug, false, 0)
	}
	var timedout int32
	if rp.RequestTimeout > 0 {
		timer := time.AfterFunc(rp.RequestTimeout, func() {
			atomic.AddInt32(&timedout, 1)
			rp.Transport.CancelRequest(req)
		})
		defer timer.Stop()
	}
	host, _, _ := net.SplitHostPort(req.URL.Host)
	if host == "" {
		host = req.URL.Host
	}
	isIP := net.ParseIP(host) != nil
	if !isIP {
		req.Header.Set("X-Host", req.Host)
		req.Header.Set("X-Forwarded-Host", req.Host)
		req.Host = host
	}
	t0 := time.Now().UTC()
	rsp, err = rp.Transport.RoundTrip(req)
	backendDuration := time.Since(t0)
	markAsDead := false
	if err != nil {
		var dialTimeout, requestTimeout bool
		if netErr, ok := err.(net.Error); ok {
			markAsDead = !netErr.Temporary()
			dialTimeout = netErr.Timeout()
		}
		requestTimeout = atomic.LoadInt32(&timedout) == int32(1)
		if requestTimeout {
			markAsDead = false
			err = fmt.Errorf("request timeout after %v: %s", time.Since(reqData.StartTime), err)
		} else if dialTimeout {
			markAsDead = true
			err = fmt.Errorf("dial timeout after %v: %s", time.Since(reqData.StartTime), err)
		} else {
			err = fmt.Errorf("error in backend request: %s", err)
		}
		if markAsDead {
			err = fmt.Errorf("%s *DEAD*", err)
		}
		reqData.logError(req.URL.Path, rp.ridString(req), err)
		rsp = &http.Response{
			StatusCode: http.StatusServiceUnavailable,
			Body:       emptyResponseBody,
		}
	}
	return rp.doResponse(req, reqData, rsp, isDebug, markAsDead, backendDuration)
}
<|endoftext|>