<commit_before>package main

import (
	"fmt"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/texttheater/golang-levenshtein/levenshtein"
)

func init() {
	CLITopics = append(CLITopics, &Topic{
		Name:   "help",
		Hidden: true,
		Commands: Commands{
			&Command{
				Hidden: true,
				Run: func(ctx *Context) {
					Args = []string{"heroku", "help"}
					help()
				},
			},
		},
	})
}

// HELP is "help"
const HELP = "help"

func help() {
	cmd := Args[1]
	switch Args[1] {
	case "help", "--help", "-h":
		if len(Args) >= 3 {
			cmd = Args[2]
		} else {
			cmd = ""
		}
	}
	topic := AllTopics().ByName(strings.SplitN(cmd, ":", 2)[0])
	command := AllCommands().Find(cmd)
	switch {
	case topic == nil:
		helpShowTopics()
	case command == nil:
		helpShowTopic(topic)
	default:
		helpShowCommand(topic, command)
	}
}

func helpShowTopics() {
	Printf("Usage: heroku COMMAND [--app APP] [command-specific-options]\n\n")
	Printf("Help topics, type \"heroku help TOPIC\" for more details:\n\n")
	topics := AllTopics().NonHidden().Sort()
	longestTopic := 0
	for _, topic := range topics {
		if len(topic.Name) > longestTopic {
			longestTopic = len(topic.Name)
		}
	}
	for _, topic := range topics {
		Printf("  heroku %-"+strconv.Itoa(longestTopic+1)+"s# %s\n", topic.Name, topic.Description)
	}
	Println()
	Exit(0)
}

func helpShowTopic(topic *Topic) {
	Printf("Usage: heroku %s:COMMAND [--app APP] [command-specific-options]\n\n", topic.Name)
	printTopicCommandsHelp(topic)
	Println()
	Exit(0)
}

func helpShowCommand(topic *Topic, command *Command) {
	Printf("Usage: heroku %s\n\n", CommandUsage(command))
	Println(command.buildFullHelp())
	if command.Command == "" {
		printTopicCommandsHelp(topic)
	}
	Println()
	Exit(0)
}

func printTopicCommandsHelp(topic *Topic) {
	topicCommands := Commands{}
	for _, cur := range AllCommands().NonHidden() {
		if topic != nil && cur.Topic == topic.Name {
			topicCommands = append(topicCommands, cur)
		}
	}
	topicCommands.loadUsages()
	if len(topicCommands) > 0 {
		Printf("\nCommands for %s, type \"heroku help %s:COMMAND\" for more details:\n\n", topic.Name, topic.Name)
		for _, command := range topicCommands.Sort() {
			Printf(" heroku %-30s # %s\n", command.Usage, command.Description)
		}
	}
}

func helpInvalidCommand() {
	checkIfKnownTopic(Args[1])
	var closest string
	currentAnalyticsCommand.Valid = false
	guess, distance := findClosestCommand(AllCommands(), Args[1])
	if len(Args[1]) > 2 || distance < 2 {
		newcmd := strings.TrimSpace(fmt.Sprintf("heroku %s %s", guess, strings.Join(Args[2:], " ")))
		WarnIfError(saveJSON(&Guess{guess.String(), Args[2:]}, guessPath()))
		closest = fmt.Sprintf("Perhaps you meant %s?\nRun %s to run %s.\n", yellow(guess.String()), cyan("heroku _"), cyan(newcmd))
	}
	ExitWithMessage(`%s is not a heroku command.
%sRun %s for a list of available commands.
`, yellow(Args[1]), closest, cyan("heroku help"))
}

func checkIfKnownTopic(cmd string) {
	knownTopics := map[string]string{
		"redis": "heroku-redis",
		"kafka": "heroku-kafka",
	}
	topic := strings.Split(cmd, ":")[0]
	plugin := knownTopics[topic]
	if plugin != "" {
		ExitWithMessage("Use %s commands by installing the %s plugin.\n%s", topic, yellow(plugin), cyan("heroku plugins:install "+plugin))
	}
}

func findClosestCommand(from Commands, a string) (*Command, int) {
	var top *Command
	var val int
	for _, b := range from {
		if cur := stringDistance(a, b.String()); cur < val || top == nil {
			top = b
			val = cur
		}
	}
	return top, val
}

func stringDistance(a, b string) int {
	return levenshtein.DistanceForStrings([]rune(a), []rune(b), levenshtein.DefaultOptions)
}

// Guess is used with `heroku _`
type Guess struct {
	Guess string   `json:"guess"`
	Args  []string `json:"args"`
}

func guessPath() string {
	return filepath.Join(CacheHome, "guess.json")
}

func loadLastCommandGuess() (guess *Guess) {
	err := readJSON(&guess, guessPath())
	LogIfError(err)
	return guess
}
<commit_msg>fix help for default commands<commit_after>package main

import (
	"fmt"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/texttheater/golang-levenshtein/levenshtein"
)

func init() {
	CLITopics = append(CLITopics, &Topic{
		Name:   "help",
		Hidden: true,
		Commands: Commands{
			&Command{
				Hidden: true,
				Run: func(ctx *Context) {
					Args = []string{"heroku", "help"}
					help()
				},
			},
		},
	})
}

// HELP is "help"
const HELP = "help"

func help() {
	cmd := Args[1]
	switch Args[1] {
	case "help", "--help", "-h":
		if len(Args) >= 3 {
			cmd = Args[2]
		} else {
			cmd = ""
		}
	}
	topic := AllTopics().ByName(strings.SplitN(cmd, ":", 2)[0])
	command := AllCommands().Find(cmd)
	switch {
	case topic == nil:
		helpShowTopics()
	case command == nil:
		helpShowTopic(topic)
	default:
		helpShowCommand(topic, command)
	}
}

func helpShowTopics() {
	Printf("Usage: heroku COMMAND [--app APP] [command-specific-options]\n\n")
	Printf("Help topics, type \"heroku help TOPIC\" for more details:\n\n")
	topics := AllTopics().NonHidden().Sort()
	longestTopic := 0
	for _, topic := range topics {
		if len(topic.Name) > longestTopic {
			longestTopic = len(topic.Name)
		}
	}
	for _, topic := range topics {
		Printf("  heroku %-"+strconv.Itoa(longestTopic+1)+"s# %s\n", topic.Name, topic.Description)
	}
	Println()
	Exit(0)
}

func helpShowTopic(topic *Topic) {
	Printf("Usage: heroku %s:COMMAND [--app APP] [command-specific-options]\n\n", topic.Name)
	printTopicCommandsHelp(topic)
	Println()
	Exit(0)
}

func helpShowCommand(topic *Topic, command *Command) {
	Printf("Usage: heroku %s\n\n", CommandUsage(command))
	Println(command.buildFullHelp())
	if command.Command == "" || command.Default {
		printTopicCommandsHelp(topic)
	}
	Println()
	Exit(0)
}

func printTopicCommandsHelp(topic *Topic) {
	topicCommands := Commands{}
	for _, cur := range AllCommands().NonHidden() {
		if topic != nil && cur.Topic == topic.Name {
			topicCommands = append(topicCommands, cur)
		}
	}
	topicCommands.loadUsages()
	if len(topicCommands) > 0 {
		Printf("\nCommands for %s, type \"heroku help %s:COMMAND\" for more details:\n\n", topic.Name, topic.Name)
		for _, command := range topicCommands.Sort() {
			Printf(" heroku %-30s # %s\n", command.Usage, command.Description)
		}
	}
}

func helpInvalidCommand() {
	checkIfKnownTopic(Args[1])
	var closest string
	currentAnalyticsCommand.Valid = false
	guess, distance := findClosestCommand(AllCommands(), Args[1])
	if len(Args[1]) > 2 || distance < 2 {
		newcmd := strings.TrimSpace(fmt.Sprintf("heroku %s %s", guess, strings.Join(Args[2:], " ")))
		WarnIfError(saveJSON(&Guess{guess.String(), Args[2:]}, guessPath()))
		closest = fmt.Sprintf("Perhaps you meant %s?\nRun %s to run %s.\n", yellow(guess.String()), cyan("heroku _"), cyan(newcmd))
	}
	ExitWithMessage(`%s is not a heroku command.
%sRun %s for a list of available commands.
`, yellow(Args[1]), closest, cyan("heroku help"))
}

func checkIfKnownTopic(cmd string) {
	knownTopics := map[string]string{
		"redis": "heroku-redis",
		"kafka": "heroku-kafka",
	}
	topic := strings.Split(cmd, ":")[0]
	plugin := knownTopics[topic]
	if plugin != "" {
		ExitWithMessage("Use %s commands by installing the %s plugin.\n%s", topic, yellow(plugin), cyan("heroku plugins:install "+plugin))
	}
}

func findClosestCommand(from Commands, a string) (*Command, int) {
	var top *Command
	var val int
	for _, b := range from {
		if cur := stringDistance(a, b.String()); cur < val || top == nil {
			top = b
			val = cur
		}
	}
	return top, val
}

func stringDistance(a, b string) int {
	return levenshtein.DistanceForStrings([]rune(a), []rune(b), levenshtein.DefaultOptions)
}

// Guess is used with `heroku _`
type Guess struct {
	Guess string   `json:"guess"`
	Args  []string `json:"args"`
}

func guessPath() string {
	return filepath.Join(CacheHome, "guess.json")
}

func loadLastCommandGuess() (guess *Guess) {
	err := readJSON(&guess, guessPath())
	LogIfError(err)
	return guess
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"strconv"
	"strings"
)

type Header struct {
	ContLen   int64
	Chunking  bool
	KeepAlive bool
}

type Request struct {
	Method string
	URL    *URL
	Proto  string

	Header
	isConnect bool

	raw bytes.Buffer
}

func (r *Request) String() (s string) {
	s = fmt.Sprintf("%s %s%s", r.Method,
		r.URL.Host, r.URL.Path)
	if verbose {
		s += fmt.Sprintf("\n%v", r.raw.String())
	}
	return
}

type Response struct {
	Status string
	Reason string

	Header

	raw bytes.Buffer
}

func (rp *Response) String() string {
	var r string
	if verbose {
		r = rp.raw.String()
	} else {
		r = fmt.Sprintf("%s %s", rp.Status, rp.Reason)
	}
	return r
}

type URL struct {
	Host string // must contain port
	Path string
}

func (url *URL) String() string {
	return fmt.Sprintf("%s%s", url.Host, url.Path)
}

// headers of interest to a proxy
// Define them as constant and use editor's completion to avoid typos.
// Note RFC2616 only says about "Connection", no "Proxy-Connection", but firefox
// send this header.
// See more at http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/web-proxy-connection-header.html
const (
	headerContentLength    = "content-length"
	headerTransferEncoding = "transfer-encoding"
	headerConnection       = "connection"
	headerProxyConnection  = "proxy-connection"
)

// For port, return empty string if no port specified.
func splitHostPort(s string) (host, port string) {
	// Common case should has no port, check the last char first
	if !IsDigit(s[len(s)-1]) {
		return s, ""
	}
	// Scan back, make sure we find ':'
	for i := len(s) - 2; i > 0; i-- {
		c := s[i]
		switch {
		case c == ':':
			return s[:i], s[i+1:]
		case !IsDigit(c):
			return s, ""
		}
	}
	return s, ""
}

// net.ParseRequestURI will unescape encoded path, but the proxy don't need
// Assumes the input rawurl valid. Even if rawurl is not valid, net.Dial
// will check the correctness of the host.
func ParseRequestURI(rawurl string) (*URL, error) {
	if rawurl[0] == '/' {
		// OS X seems to send only path to the server if the url is 127.0.0.1
		return &URL{Host: "", Path: rawurl}, nil
		// return nil, errors.New("Invalid proxy request URI: " + rawurl)
	}

	var f []string
	var rest string
	f = strings.SplitN(rawurl, "://", 2)
	if len(f) == 1 {
		rest = f[0]
	} else {
		scheme := strings.ToLower(f[0])
		if scheme != "http" && scheme != "https" {
			return nil, errors.New(scheme + " protocol not supported")
		}
		rest = f[1]
	}

	var host, path string
	f = strings.SplitN(rest, "/", 2)
	host = f[0]
	if len(f) == 1 || f[1] == "" {
		path = "/"
	} else {
		path = "/" + f[1]
	}
	_, port := splitHostPort(host)
	if port == "" {
		host += ":80"
	}

	return &URL{Host: host, Path: path}, nil
}

func splitHeader(s string) (name, val string, err error) {
	var f []string
	if f = strings.SplitN(strings.ToLower(s), ":", 2); len(f) != 2 {
		// TODO Fix this when encounter such web servers
		return "", "", errors.New("Multi-line header not supported")
	}
	return f[0], f[1], nil
}

func (h *Header) parseContentLength(s string) (err error) {
	h.ContLen, err = strconv.ParseInt(strings.TrimSpace(s), 10, 64)
	return err
}

func (h *Header) parseConnection(s string) error {
	h.KeepAlive = strings.Contains(s, "keep-alive")
	return nil
}

func (h *Header) parseTransferEncoding(s string) error {
	h.Chunking = strings.Contains(s, "chunked")
	return nil
}

type HeaderParserFunc func(*Header, string) error

// Using Go's method expression
var headerParser = map[string]HeaderParserFunc{
	headerConnection:       (*Header).parseConnection,
	headerProxyConnection:  (*Header).parseConnection,
	headerContentLength:    (*Header).parseContentLength,
	headerTransferEncoding: (*Header).parseTransferEncoding,
}

// Only add headers that are of interest for a proxy into request's header map.
func (h *Header) parseHeader(reader *bufio.Reader, raw *bytes.Buffer, addHeader string) (err error) {
	// Read request header and body
	var s, name, val string
	for {
		if s, err = ReadLine(reader); err != nil {
			return
		}
		if s == "" {
			// Connection close, no content length specification
			// Use chunked encoding to pass content back to client
			if !h.KeepAlive && !h.Chunking && h.ContLen == -1 {
				raw.WriteString("Transfer-Encoding: chunked\r\n")
			}

			raw.WriteString(addHeader)
			raw.WriteString("\r\n")
			return
		}
		if name, val, err = splitHeader(s); err != nil {
			return
		}
		if parseFunc, ok := headerParser[name]; ok {
			parseFunc(h, val)
			if name == headerConnection || name == headerProxyConnection {
				// Don't pass connection header to server or client
				continue
			}
		}
		raw.WriteString(s)
		raw.WriteString("\r\n")
		// debug.Printf("len %d %s", len(s), s)
	}
	return
}

// Consume all http header. Used for CONNECT method.
func drainHeader(reader *bufio.Reader) (err error) {
	// Read request header and body
	var s string
	for {
		s, err = ReadLine(reader)
		if s == "" || err != nil {
			return
		}
	}
	return
}

// Parse the initial line and header, does not touch body
func parseRequest(reader *bufio.Reader) (r *Request, err error) {
	r = new(Request)
	r.KeepAlive = true
	r.ContLen = -1
	var s string

	// parse initial request line
	if s, err = ReadLine(reader); err != nil {
		return nil, err
	}
	// debug.Printf("Request initial line %s", s)

	var f []string
	if f = strings.SplitN(s, " ", 3); len(f) < 3 {
		return nil, errors.New(fmt.Sprintf("malformed HTTP request: %s", s))
	}
	var requestURI string
	r.Method, requestURI, r.Proto = f[0], f[1], f[2]

	// Parse URI into host and path
	r.URL, err = ParseRequestURI(requestURI)
	if err != nil {
		return
	}
	if r.Method == "CONNECT" {
		// Consume remaining header and just return. Headers are not used for
		// CONNECT method.
		r.isConnect = true
		r.KeepAlive = false
		err = drainHeader(reader)
		return
	}

	r.genRequestLine()

	// Read request header
	if err = r.parseHeader(reader, &r.raw, ""); err != nil {
		errl.Printf("Parsing request header: %v\n", err)
		return nil, err
	}
	return
}

func (r *Request) genRequestLine() {
	r.raw.WriteString(r.Method)
	r.raw.WriteString(" ")
	r.raw.WriteString(r.URL.Path)
	r.raw.WriteString(" ")
	r.raw.WriteString("HTTP/1.1\r\n")
	r.raw.WriteString("Connection: Keep-Alive\r\n")
}

var crlfBuf = make([]byte, 2)

func readCheckCRLF(reader *bufio.Reader) error {
	if _, err := io.ReadFull(reader, crlfBuf); err != nil {
		return err
	}
	if crlfBuf[0] != '\r' || crlfBuf[1] != '\n' {
		return errors.New("Not CRLF")
	}
	return nil
}

// If an http response may have message body
func (rp *Response) hasBody(method string) bool {
	// when we have tenary search tree, can optimize this a little
	return !(method == "HEAD" ||
		rp.Status == "304" ||
		rp.Status == "204" ||
		strings.HasPrefix(rp.Status, "1"))
}

var malformedHTTPResponseErr = errors.New("malformed HTTP response")

// Parse response status and headers.
func parseResponse(reader *bufio.Reader) (rp *Response, err error) {
	rp = new(Response)
	rp.ContLen = -1

	var s string
START:
	if s, err = ReadLine(reader); err != nil {
		if err != io.EOF {
			// err maybe timeout caused by explicity setting deadline
			debug.Printf("Reading Response status line: %v\n", err)
		}
		return nil, err
	}
	var f []string
	if f = strings.SplitN(s, " ", 3); len(f) < 3 {
		errl.Println("malformed HTTP response status line:", s)
		return nil, malformedHTTPResponseErr
	}
	// Handle 1xx response
	if f[1] == "100" {
		if err = readCheckCRLF(reader); err != nil {
			errl.Printf("Reading CRLF after 1xx response: %v\n", err)
			return nil, err
		}
		goto START
	}
	rp.Status = f[1]
	rp.Reason = f[2]

	rp.raw.WriteString(s)
	rp.raw.WriteString("\r\n")

	if err = rp.parseHeader(reader, &rp.raw, "Connection: Keep-Alive\r\n"); err != nil {
		errl.Println("Reading response header:", err)
		return nil, err
	}

	return rp, nil
}
<commit_msg>Handle http response status line which has only 2 fields.<commit_after>package main

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"strconv"
	"strings"
)

type Header struct {
	ContLen   int64
	Chunking  bool
	KeepAlive bool
}

type Request struct {
	Method string
	URL    *URL
	Proto  string

	Header
	isConnect bool

	raw bytes.Buffer
}

func (r *Request) String() (s string) {
	s = fmt.Sprintf("%s %s%s", r.Method,
		r.URL.Host, r.URL.Path)
	if verbose {
		s += fmt.Sprintf("\n%v", r.raw.String())
	}
	return
}

type Response struct {
	Status string
	Reason string

	Header

	raw bytes.Buffer
}

func (rp *Response) String() string {
	var r string
	if verbose {
		r = rp.raw.String()
	} else {
		r = fmt.Sprintf("%s %s", rp.Status, rp.Reason)
	}
	return r
}

type URL struct {
	Host string // must contain port
	Path string
}

func (url *URL) String() string {
	return fmt.Sprintf("%s%s", url.Host, url.Path)
}

// headers of interest to a proxy
// Define them as constant and use editor's completion to avoid typos.
// Note RFC2616 only says about "Connection", no "Proxy-Connection", but firefox
// send this header.
// See more at http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/web-proxy-connection-header.html
const (
	headerContentLength    = "content-length"
	headerTransferEncoding = "transfer-encoding"
	headerConnection       = "connection"
	headerProxyConnection  = "proxy-connection"
)

// For port, return empty string if no port specified.
func splitHostPort(s string) (host, port string) {
	// Common case should has no port, check the last char first
	if !IsDigit(s[len(s)-1]) {
		return s, ""
	}
	// Scan back, make sure we find ':'
	for i := len(s) - 2; i > 0; i-- {
		c := s[i]
		switch {
		case c == ':':
			return s[:i], s[i+1:]
		case !IsDigit(c):
			return s, ""
		}
	}
	return s, ""
}

// net.ParseRequestURI will unescape encoded path, but the proxy don't need
// Assumes the input rawurl valid. Even if rawurl is not valid, net.Dial
// will check the correctness of the host.
func ParseRequestURI(rawurl string) (*URL, error) {
	if rawurl[0] == '/' {
		// OS X seems to send only path to the server if the url is 127.0.0.1
		return &URL{Host: "", Path: rawurl}, nil
		// return nil, errors.New("Invalid proxy request URI: " + rawurl)
	}

	var f []string
	var rest string
	f = strings.SplitN(rawurl, "://", 2)
	if len(f) == 1 {
		rest = f[0]
	} else {
		scheme := strings.ToLower(f[0])
		if scheme != "http" && scheme != "https" {
			return nil, errors.New(scheme + " protocol not supported")
		}
		rest = f[1]
	}

	var host, path string
	f = strings.SplitN(rest, "/", 2)
	host = f[0]
	if len(f) == 1 || f[1] == "" {
		path = "/"
	} else {
		path = "/" + f[1]
	}
	_, port := splitHostPort(host)
	if port == "" {
		host += ":80"
	}

	return &URL{Host: host, Path: path}, nil
}

func splitHeader(s string) (name, val string, err error) {
	var f []string
	if f = strings.SplitN(strings.ToLower(s), ":", 2); len(f) != 2 {
		// TODO Fix this when encounter such web servers
		return "", "", errors.New("Multi-line header not supported")
	}
	return f[0], f[1], nil
}

func (h *Header) parseContentLength(s string) (err error) {
	h.ContLen, err = strconv.ParseInt(strings.TrimSpace(s), 10, 64)
	return err
}

func (h *Header) parseConnection(s string) error {
	h.KeepAlive = strings.Contains(s, "keep-alive")
	return nil
}

func (h *Header) parseTransferEncoding(s string) error {
	h.Chunking = strings.Contains(s, "chunked")
	return nil
}

type HeaderParserFunc func(*Header, string) error

// Using Go's method expression
var headerParser = map[string]HeaderParserFunc{
	headerConnection:       (*Header).parseConnection,
	headerProxyConnection:  (*Header).parseConnection,
	headerContentLength:    (*Header).parseContentLength,
	headerTransferEncoding: (*Header).parseTransferEncoding,
}

// Only add headers that are of interest for a proxy into request's header map.
func (h *Header) parseHeader(reader *bufio.Reader, raw *bytes.Buffer, addHeader string) (err error) {
	// Read request header and body
	var s, name, val string
	for {
		if s, err = ReadLine(reader); err != nil {
			return
		}
		if s == "" {
			// Connection close, no content length specification
			// Use chunked encoding to pass content back to client
			if !h.KeepAlive && !h.Chunking && h.ContLen == -1 {
				raw.WriteString("Transfer-Encoding: chunked\r\n")
			}

			raw.WriteString(addHeader)
			raw.WriteString("\r\n")
			return
		}
		if name, val, err = splitHeader(s); err != nil {
			return
		}
		if parseFunc, ok := headerParser[name]; ok {
			parseFunc(h, val)
			if name == headerConnection || name == headerProxyConnection {
				// Don't pass connection header to server or client
				continue
			}
		}
		raw.WriteString(s)
		raw.WriteString("\r\n")
		// debug.Printf("len %d %s", len(s), s)
	}
	return
}

// Consume all http header. Used for CONNECT method.
func drainHeader(reader *bufio.Reader) (err error) {
	// Read request header and body
	var s string
	for {
		s, err = ReadLine(reader)
		if s == "" || err != nil {
			return
		}
	}
	return
}

// Parse the initial line and header, does not touch body
func parseRequest(reader *bufio.Reader) (r *Request, err error) {
	r = new(Request)
	r.KeepAlive = true
	r.ContLen = -1
	var s string

	// parse initial request line
	if s, err = ReadLine(reader); err != nil {
		return nil, err
	}
	// debug.Printf("Request initial line %s", s)

	var f []string
	if f = strings.SplitN(s, " ", 3); len(f) < 3 {
		return nil, errors.New(fmt.Sprintf("malformed HTTP request: %s", s))
	}
	var requestURI string
	r.Method, requestURI, r.Proto = f[0], f[1], f[2]

	// Parse URI into host and path
	r.URL, err = ParseRequestURI(requestURI)
	if err != nil {
		return
	}
	if r.Method == "CONNECT" {
		// Consume remaining header and just return. Headers are not used for
		// CONNECT method.
		r.isConnect = true
		r.KeepAlive = false
		err = drainHeader(reader)
		return
	}

	r.genRequestLine()

	// Read request header
	if err = r.parseHeader(reader, &r.raw, ""); err != nil {
		errl.Printf("Parsing request header: %v\n", err)
		return nil, err
	}
	return
}

func (r *Request) genRequestLine() {
	r.raw.WriteString(r.Method)
	r.raw.WriteString(" ")
	r.raw.WriteString(r.URL.Path)
	r.raw.WriteString(" ")
	r.raw.WriteString("HTTP/1.1\r\n")
	r.raw.WriteString("Connection: Keep-Alive\r\n")
}

var crlfBuf = make([]byte, 2)

func readCheckCRLF(reader *bufio.Reader) error {
	if _, err := io.ReadFull(reader, crlfBuf); err != nil {
		return err
	}
	if crlfBuf[0] != '\r' || crlfBuf[1] != '\n' {
		return errors.New("Not CRLF")
	}
	return nil
}

// If an http response may have message body
func (rp *Response) hasBody(method string) bool {
	// when we have tenary search tree, can optimize this a little
	return !(method == "HEAD" ||
		rp.Status == "304" ||
		rp.Status == "204" ||
		strings.HasPrefix(rp.Status, "1"))
}

var malformedHTTPResponseErr = errors.New("malformed HTTP response")

// Parse response status and headers.
func parseResponse(reader *bufio.Reader) (rp *Response, err error) {
	rp = new(Response)
	rp.ContLen = -1

	var s string
START:
	if s, err = ReadLine(reader); err != nil {
		if err != io.EOF {
			// err maybe timeout caused by explicity setting deadline
			debug.Printf("Reading Response status line: %v\n", err)
		}
		return nil, err
	}
	var f []string
	if f = strings.SplitN(s, " ", 3); len(f) < 2 {
		errl.Println("malformed HTTP response status line:", s)
		return nil, malformedHTTPResponseErr
	}
	// Handle 1xx response
	if f[1] == "100" {
		if err = readCheckCRLF(reader); err != nil {
			errl.Printf("Reading CRLF after 1xx response: %v\n", err)
			return nil, err
		}
		goto START
	}
	rp.Status = f[1]
	if len(f) == 3 {
		rp.Reason = f[2]
	}

	rp.raw.WriteString(s)
	rp.raw.WriteString("\r\n")

	if err = rp.parseHeader(reader, &rp.raw, "Connection: Keep-Alive\r\n"); err != nil {
		errl.Println("Reading response header:", err)
		return nil, err
	}

	return rp, nil
}
<|endoftext|>


<commit_before>package word2vec

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

type cosQuery struct {
	A Expr `json:"a,omitempty"`
	B Expr `json:"b,omitempty"`
}

type cosResponse struct {
	Value float32 `json:"value"`
}

func (q cosQuery) Eval(m *Model) (*cosResponse, error) {
	v, err := m.Cos(q.A, q.B)
	if err != nil {
		return nil, err
	}

	return &cosResponse{
		Value: v,
	}, nil
}

type cosesQuery struct {
	Queries []cosQuery `json:"queries"`
}

type cosesResponse struct {
	Values []cosResponse `json:"values"`
}

func (qs cosesQuery) Eval(m *Model) (*cosesResponse, error) {
	values := make([]cosResponse, len(qs.Queries))
	for i, q := range qs.Queries {
		r, err := q.Eval(m)
		if err != nil {
			return nil, err
		}
		values[i] = *r
	}
	return &cosesResponse{
		Values: values,
	}, nil
}

type cosNQuery struct {
	Expr Expr `json:"expr"`
	N    int  `json:"n"`
}

type cosNResponse struct {
	Matches []Match `json:"matches"`
}

func (q cosNQuery) Eval(m *Model) (*cosNResponse, error) {
	r, err := m.CosN(q.Expr, q.N)
	if err != nil {
		return nil, err
	}

	return &cosNResponse{
		Matches: r,
	}, nil
}

// server is a type which implements http.Handler and exports endpoints
// for performing similarity queries on a word2vec model.
type server struct {
	*Model
	*http.ServeMux
}

// NewServer creates a new word2vec server which exports endpoints for performing
// similarity queries on a word2vec Model.
func NewServer(m *Model) http.Handler {
	ms := &server{
		Model: m,
	}

	mux := http.NewServeMux()
	mux.HandleFunc("/cos-n", ms.handleCosNQuery)
	mux.HandleFunc("/cos", ms.handleCosQuery)
	mux.HandleFunc("/coses", ms.handleCosesQuery)

	ms.ServeMux = mux
	return ms
}

func handleError(w http.ResponseWriter, r *http.Request, status int, msg string) {
	log.Printf(msg)
	w.WriteHeader(status)
	w.Write([]byte(msg))
	return
}

func (m *server) handleCosQuery(w http.ResponseWriter, r *http.Request) {
	dec := json.NewDecoder(r.Body)
	defer r.Body.Close()

	var q cosQuery
	err := dec.Decode(&q)
	if err != nil {
		msg := fmt.Sprintf("error decoding query: %v", err)
		handleError(w, r, http.StatusInternalServerError, msg)
		return
	}

	resp, err := q.Eval(m.Model)
	if err != nil {
		msg := fmt.Sprintf("error evaluating query: %v", err)
		handleError(w, r, http.StatusBadRequest, msg)
		return
	}

	b, err := json.Marshal(resp)
	if err != nil {
		msg := fmt.Sprintf("error encoding response %#v to JSON: %v", resp, err)
		handleError(w, r, http.StatusInternalServerError, msg)
		return
	}

	_, err = w.Write(b)
	if err != nil {
		log.Printf("error writing response: %v", err)
	}
}

func (m *server) handleCosesQuery(w http.ResponseWriter, r *http.Request) {
	dec := json.NewDecoder(r.Body)
	defer r.Body.Close()

	var q cosesQuery
	err := dec.Decode(&q)
	if err != nil {
		msg := fmt.Sprintf("error decoding query: %v", err)
		handleError(w, r, http.StatusInternalServerError, msg)
		return
	}

	resp, err := q.Eval(m.Model)
	if err != nil {
		msg := fmt.Sprintf("error evaluating query: %v", err)
		handleError(w, r, http.StatusBadRequest, msg)
		return
	}

	b, err := json.Marshal(resp)
	if err != nil {
		msg := fmt.Sprintf("error encoding response %#v to JSON: %v", resp, err)
		handleError(w, r, http.StatusInternalServerError, msg)
		return
	}

	_, err = w.Write(b)
	if err != nil {
		log.Printf("error writing response: %v", err)
	}
}

func (m *server) handleCosNQuery(w http.ResponseWriter, r *http.Request) {
	dec := json.NewDecoder(r.Body)
	defer r.Body.Close()

	var q cosNQuery
	err := dec.Decode(&q)
	if err != nil {
		msg := fmt.Sprintf("error decoding query: %v", err)
		handleError(w, r, http.StatusInternalServerError, msg)
		return
	}

	resp, err := q.Eval(m.Model)
	if err != nil {
		msg := fmt.Sprintf("error evaluating query: %v", err)
		handleError(w, r, http.StatusBadRequest, msg)
		return
	}

	b, err := json.Marshal(resp)
	if err != nil {
		msg := fmt.Sprintf("error encoding response %#v to JSON: %v", resp, err)
		handleError(w, r, http.StatusInternalServerError, msg)
		return
	}

	_, err = w.Write(b)
	if err != nil {
		log.Printf("error writing response: %v", err)
	}
}

// Client is type which implements Coser and evaluates Expr similarity queries
// using a word2vec Server (see above).
type Client struct {
	Addr string
}

// Cos implements Coser.
func (c Client) Cos(x, y Expr) (float32, error) {
	req := cosQuery{A: x, B: y}

	b, err := json.Marshal(req)
	if err != nil {
		return 0.0, err
	}

	r, err := http.NewRequest("GET", "http://"+c.Addr+"/cos", bytes.NewReader(b))
	if err != nil {
		return 0.0, err
	}

	resp, err := http.DefaultClient.Do(r)
	if err != nil {
		return 0.0, err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0.0, fmt.Errorf("error reading response: %v", err)
	}

	if resp.StatusCode == http.StatusBadRequest {
		return 0.0, fmt.Errorf("error: %v", string(b))
	}

	if resp.StatusCode != http.StatusOK {
		return 0.0, fmt.Errorf("non-%v status code: %v msg: %v", http.StatusOK, resp.Status, string(b))
	}

	var data cosResponse
	err = json.Unmarshal(body, &data)
	if err != nil {
		return 0.0, fmt.Errorf("error unmarshalling result: %v", err)
	}

	return data.Value, nil
}

// Coses implements Coser.
func (c Client) Coses(pairs [][2]Expr) ([]float32, error) {
	req := cosesQuery{
		Queries: make([]cosQuery, len(pairs)),
	}
	for _, pair := range pairs {
		req.Queries = append(req.Queries, cosQuery{
			A: pair[0],
			B: pair[1],
		})
	}

	b, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}

	r, err := http.NewRequest("GET", "http://"+c.Addr+"/coses", bytes.NewReader(b))
	if err != nil {
		return nil, err
	}

	resp, err := http.DefaultClient.Do(r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("non-%v status code: %v", http.StatusOK, resp.Status)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("error reading response: %v", err)
	}

	var data cosesResponse
	err = json.Unmarshal(body, &data)
	if err != nil {
		return nil, fmt.Errorf("error unmarshalling result: %v", err)
	}

	result := make([]float32, len(data.Values))
	for i, v := range data.Values {
		result[i] = v.Value
	}
	return result, nil
}

// CosN implements Coser.
func (c Client) CosN(e Expr, n int) ([]Match, error) {
	req := cosNQuery{Expr: e, N: n}

	b, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}

	r, err := http.NewRequest("GET", "http://"+c.Addr+"/cos-n", bytes.NewReader(b))
	if err != nil {
		return nil, err
	}

	resp, err := http.DefaultClient.Do(r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("error reading response: %v", err)
	}

	if resp.StatusCode == http.StatusBadRequest {
		return nil, fmt.Errorf("error: %v", string(body))
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("non-%v status code: %v msg: %v", http.StatusOK, resp.Status, string(body))
	}

	var data cosNResponse
	err = json.Unmarshal(body, &data)
	if err != nil {
		return nil, fmt.Errorf("error unmarshalling result: %v", err)
	}

	return data.Matches, nil
}
<commit_msg>Simplify Server using new evaler interface.<commit_after>package word2vec

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

type cosQuery struct {
	A Expr `json:"a,omitempty"`
	B Expr `json:"b,omitempty"`
}

type cosResponse struct {
	Value float32 `json:"value"`
}

func (q cosQuery) Eval(m *Model) (interface{}, error) {
	v, err := m.Cos(q.A, q.B)
	if err != nil {
		return nil, err
	}

	return &cosResponse{
		Value: v,
	}, nil
}

type cosesQuery struct {
	Queries []cosQuery `json:"queries"`
}

type cosesResponse struct {
	Values []cosResponse `json:"values"`
}

func (qs cosesQuery) Eval(m *Model) (interface{}, error) {
	values := make([]interface{}, len(qs.Queries))
	for i, q := range qs.Queries {
		r, err := q.Eval(m)
		if err != nil {
			return nil, err
		}
		values[i] = r
	}
	return struct {
		Values interface{}
	}{
		Values: values,
	}, nil
}

type cosNQuery struct {
	Expr Expr `json:"expr"`
	N    int  `json:"n"`
}

type cosNResponse struct {
	Matches []Match `json:"matches"`
}

func (q cosNQuery) Eval(m *Model) (interface{}, error) {
	r, err := m.CosN(q.Expr, q.N)
	if err != nil {
		return nil, err
	}

	return &cosNResponse{
		Matches: r,
	}, nil
}

// server is a type which implements http.Handler and exports endpoints
// for performing similarity queries on a word2vec model.
type server struct {
	*Model
	*http.ServeMux
}

// NewServer creates a new word2vec server which exports endpoints for performing
// similarity queries on a word2vec Model.
func NewServer(m *Model) http.Handler {
	ms := &server{
		Model: m,
	}

	mux := http.NewServeMux()
	mux.HandleFunc("/cos-n", ms.handleCosNQuery)
	mux.HandleFunc("/cos", ms.handleCosQuery)
	mux.HandleFunc("/coses", ms.handleCosesQuery)

	ms.ServeMux = mux
	return ms
}

func handleError(w http.ResponseWriter, r *http.Request, status int, msg string) {
	log.Printf(msg)
	w.WriteHeader(status)
	w.Write([]byte(msg))
	return
}

type evaler interface {
	Eval(*Model) (interface{}, error)
}

func (m *server) handleEval(e evaler, w http.ResponseWriter, r *http.Request) {
	resp, err := e.Eval(m.Model)
	if err != nil {
		msg := fmt.Sprintf("error evaluating query: %v", err)
		handleError(w, r, http.StatusBadRequest, msg)
		return
	}

	b, err := json.Marshal(resp)
	if err != nil {
		msg := fmt.Sprintf("error encoding response %#v to JSON: %v", resp, err)
		handleError(w, r, http.StatusInternalServerError, msg)
		return
	}

	_, err = w.Write(b)
	if err != nil {
		log.Printf("error writing response: %v", err)
	}
}

func (m *server) handleCosQuery(w http.ResponseWriter, r *http.Request) {
	dec := json.NewDecoder(r.Body)
	defer r.Body.Close()

	var q cosQuery
	err := dec.Decode(&q)
	if err != nil {
		msg := fmt.Sprintf("error decoding query: %v", err)
		handleError(w, r, http.StatusInternalServerError, msg)
		return
	}
	m.handleEval(q, w, r)
}

func (m *server) handleCosesQuery(w http.ResponseWriter, r *http.Request) {
	dec := json.NewDecoder(r.Body)
	defer r.Body.Close()

	var q cosesQuery
	err := dec.Decode(&q)
	if err != nil {
		msg := fmt.Sprintf("error decoding query: %v", err)
		handleError(w, r, http.StatusInternalServerError, msg)
		return
	}
	m.handleEval(q, w, r)
}

func (m *server) handleCosNQuery(w http.ResponseWriter, r *http.Request) {
	dec := json.NewDecoder(r.Body)
	defer r.Body.Close()

	var q cosNQuery
	err := dec.Decode(&q)
	if err != nil {
		msg := fmt.Sprintf("error decoding query: %v", err)
		handleError(w, r, http.StatusInternalServerError, msg)
		return
	}
	m.handleEval(q, w, r)
}

// Client is type which implements Coser and evaluates Expr similarity queries
// using a word2vec Server (see above).
type Client struct {
	Addr string
}

// Cos implements Coser.
func (c Client) Cos(x, y Expr) (float32, error) {
	req := cosQuery{A: x, B: y}

	b, err := json.Marshal(req)
	if err != nil {
		return 0.0, err
	}

	r, err := http.NewRequest("GET", "http://"+c.Addr+"/cos", bytes.NewReader(b))
	if err != nil {
		return 0.0, err
	}

	resp, err := http.DefaultClient.Do(r)
	if err != nil {
		return 0.0, err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0.0, fmt.Errorf("error reading response: %v", err)
	}

	if resp.StatusCode == http.StatusBadRequest {
		return 0.0, fmt.Errorf("error: %v", string(b))
	}

	if resp.StatusCode != http.StatusOK {
		return 0.0, fmt.Errorf("non-%v status code: %v msg: %v", http.StatusOK, resp.Status, string(b))
	}

	var data cosResponse
	err = json.Unmarshal(body, &data)
	if err != nil {
		return 0.0, fmt.Errorf("error unmarshalling result: %v", err)
	}

	return data.Value, nil
}

// Coses implements Coser.
func (c Client) Coses(pairs [][2]Expr) ([]float32, error) {
	req := cosesQuery{
		Queries: make([]cosQuery, len(pairs)),
	}
	for _, pair := range pairs {
		req.Queries = append(req.Queries, cosQuery{
			A: pair[0],
			B: pair[1],
		})
	}

	b, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}

	r, err := http.NewRequest("GET", "http://"+c.Addr+"/coses", bytes.NewReader(b))
	if err != nil {
		return nil, err
	}

	resp, err := http.DefaultClient.Do(r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("non-%v status code: %v", http.StatusOK, resp.Status)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("error reading response: %v", err)
	}

	var data cosesResponse
	err = json.Unmarshal(body, &data)
	if err != nil {
		return nil, fmt.Errorf("error unmarshalling result: %v", err)
	}

	result := make([]float32, len(data.Values))
	for i, v := range data.Values {
		result[i] = v.Value
	}
	return result, nil
}

// CosN implements Coser.
func (c Client) CosN(e Expr, n int) ([]Match, error) {
	req := cosNQuery{Expr: e, N: n}

	b, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}

	r, err := http.NewRequest("GET", "http://"+c.Addr+"/cos-n", bytes.NewReader(b))
	if err != nil {
		return nil, err
	}

	resp, err := http.DefaultClient.Do(r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("error reading response: %v", err)
	}

	if resp.StatusCode == http.StatusBadRequest {
		return nil, fmt.Errorf("error: %v", string(body))
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("non-%v status code: %v msg: %v", http.StatusOK, resp.Status, string(body))
	}

	var data cosNResponse
	err = json.Unmarshal(body, &data)
	if err != nil {
		return nil, fmt.Errorf("error unmarshalling result: %v", err)
	}

	return data.Matches, nil
}
<|endoftext|>


<commit_before>/* http.go
 *
 * Provides the HTTP interface for Scarlet.
 */
package main

import (
	"fmt"
	"net/http"
	"regexp"
	"strconv"
	"strings"
)

var (
	urlRegex         = regexp.MustCompile("^/([0-9]{1,2})(/(.+))?(/(ttl|type))?")
	querystringRegex = regexp.MustCompile(`(\?.*)$`)
)

func startHttp(listenAddr string) {
	// URL-to-handler func mappings
	//
	http.HandleFunc("/info", GetInformation)
	http.HandleFunc("/favicon.ico", Favicon)
	http.HandleFunc("/", DispatchRequest)

	// Start listening for requests
	//
	println("Scarlet HTTP listening on", listenAddr)
	panic(http.ListenAndServe(listenAddr, nil))
	return
}

func GetInformation(rw http.ResponseWriter, req *http.Request) {
	rw.Header().Set("Content-Type", "application/json")
	var response R
	config, _ := LoadConfig(*configPath)
	if config.Redis.InfoDisabled() {
		e := "Retrieving node information has been disabled."
		response = R{"result": nil, "error": e}
		fmt.Fprint(rw, response)
		return
	}
	println("INFO")
	info, err := GetHostInfo(redisClient)
	response = R{"result": info, "error": err}
	fmt.Fprint(rw, response)
	return
}

// Dispatches the incoming request to the proper action handler, depending on
// the HTTP method that was used.
//
func DispatchRequest(rw http.ResponseWriter, req *http.Request) {
	var response R
	switch req.Method {
	case "GET":
		response = HandleReadOperation(req)

	case "POST":
		response = HandleCreateOperation(req)

	case "PUT":
		response = HandleUpdateOperation(req)
	}
	rw.Header().Set("Content-Type", "application/json")
	fmt.Fprint(rw, response)
	return
}

// Handles HTTP GET requests, which are intended for retrieving data.
//
func HandleReadOperation(req *http.Request) (response R) {
	url := querystringRegex.ReplaceAllString(req.URL.String(), "")
	matches := urlRegex.FindStringSubmatch(url)
	if matches == nil {
		response = R{"result": nil, "error": "No database number specified."}
		return
	}

	// Parse out the database number
	//
	db := matches[1]
	dbnum, err := strconv.Atoi(strings.TrimLeft(db, "/"))
	if err != nil {
		response = R{"result": nil, "error": err}
		return
	}
	if *debug {
		println("debug:", "DB #", dbnum)
	}

	// Get a Redis client for the specified database number.
	//
	client := Database.DB(dbnum)

	// Parse out the key name
	//
	key := matches[3]
	if len(key) == 0 {
		// The length of the key name is zero, so just list all
		// of the keys in the database.
		//
		fmt.Println("KEYS", "*")
		keys, err := client.Keys("*")
		if err != nil {
			response = R{"result": nil, "error": fmt.Sprintf("%s", err)}
		} else {
			response = R{"result": keys, "error": nil}
		}
		return
	}

	// Get the key type, so that we know how to properly format the
	// response.
	//
	keyType, err := client.Type(key)
	if err != nil {
		response = R{"result": nil, "error": err}
		return
	}

	// Format the response according to the type the key holds.
	//
	switch keyType {
	case "string":
		println("GET", key)
		v, _ := client.Get(key)
		response = R{"result": v, "error": nil}

	case "set":
		println("SMEMBERS", key)
		v, _ := client.Smembers(key)
		response = R{"result": v.StringArray(), "error": nil}

	case "zset":
		println("ZRANGE", key, 0, -1)
		v, _ := client.Zrange(key, 0, -1)
		response = R{"result": v.StringArray(), "error": nil}

	case "list":
		println("LRANGE", key, 0, -1)
		v, _ := client.Lrange(key, 0, -1)
		response = R{"result": v.StringArray(), "error": nil}

	case "hash":
		if field := req.FormValue("field"); field != "" {
			println("HGET", key, field)
			v, _ := client.Hget(key, field)
			response = R{"result": v.String(), "error": nil}
		} else {
			println("HGETALL", key)
			reply, _ := client.Hgetall(key)
			response = R{"result": reply.StringMap(), "error": nil}
		}

	default:
		e := fmt.Sprintf("Unknown type for key %s: %s", key, keyType)
		response = R{"result": nil, "error": e}
	}
	return
}

// Handles HTTP POST requests, intended for creating new keys.
//
func HandleCreateOperation(req *http.Request) (response R) {
	e := "Create operations have not yet been implemented."
	response = R{"result": nil, "error": e}
	return
}

// Handles HTTP PUT requests, inteded for updating keys.
//
func HandleUpdateOperation(req *http.Request) (response R) {
	e := "Update operations have not yet been implemented."
	response = R{"result": nil, "error": e}
	return
}

func Favicon(rw http.ResponseWriter, req *http.Request) {
	rw.WriteHeader(http.StatusOK)
	return
}
<commit_msg>Started working on the PUT handlers.<commit_after>/* http.go
 *
 * Provides the HTTP interface for Scarlet.
 */
package main

import (
	"fmt"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"errors"
)

var (
	urlRegex         = regexp.MustCompile("^/([0-9]{1,2})(/(.+))?(/(ttl|type))?")
	querystringRegex = regexp.MustCompile(`(\?.*)$`)
)

func startHttp(listenAddr string) {
	// URL-to-handler func mappings
	//
	http.HandleFunc("/info", GetInformation)
	http.HandleFunc("/favicon.ico", Favicon)
	http.HandleFunc("/", DispatchRequest)

	// Start listening for requests
	//
	println("Scarlet HTTP listening on", listenAddr)
	panic(http.ListenAndServe(listenAddr, nil))
	return
}

func GetInformation(rw http.ResponseWriter, req *http.Request) {
	rw.Header().Set("Content-Type", "application/json")
	var response R
	config, _ := LoadConfig(*configPath)
	if config.Redis.InfoDisabled() {
		e := "Retrieving node information has been disabled."
		response = R{"result": nil, "error": e}
		fmt.Fprint(rw, response)
		return
	}
	println("INFO")
	info, err := GetHostInfo(redisClient)
	response = R{"result": info, "error": err}
	fmt.Fprint(rw, response)
	return
}

type RequestInfo struct {
	DbNum int
	Key string
}

func GetRequestInfo(r *http.Request) (ri *RequestInfo, err error) {
	url := querystringRegex.ReplaceAllString(r.URL.String(), "")
	m := urlRegex.FindStringSubmatch(url)
	if m == nil {
		err = errors.New("Malformed URL")
		return
	}
	dbnum, e := strconv.Atoi(strings.TrimLeft(m[1], "/"))
	if err != nil {
		err = e
		return
	}
	ri = &RequestInfo{DbNum: dbnum, Key: m[3]}
	return
}

// Dispatches the incoming request to the proper action handler, depending on
// the HTTP method that was used.
//
func DispatchRequest(rw http.ResponseWriter, req *http.Request) {
	var response R
	if info, err := GetRequestInfo(req); err == nil {
		switch req.Method {
		case "GET":
			response = HandleReadOperation(req, info)

		case "POST":
			response = HandleCreateOperation(req, info)

		case "PUT":
			response = HandleUpdateOperation(req, info)
		}
	} else {
		response = R{"result": nil, "error": err}
	}
	rw.Header().Set("Content-Type", "application/json")
	fmt.Fprint(rw, response)
	return
}

// Handles HTTP GET requests, which are intended for retrieving data.
//
func HandleReadOperation(req *http.Request, info *RequestInfo) (response R) {
	// Get a Redis client for the specified database number.
	//
	client := Database.DB(info.DbNum)

	// Parse out the key name
	//
	key := info.Key
	if len(key) == 0 {
		// The length of the key name is zero, so just list all
		// of the keys in the database.
		//
		fmt.Println("KEYS", "*")
		keys, err := client.Keys("*")
		if err != nil {
			response = R{"result": nil, "error": fmt.Sprintf("%s", err)}
		} else {
			response = R{"result": keys, "error": nil}
		}
		return
	}

	// Get the key type, so that we know how to properly format the
	// response.
	//
	keyType, err := client.Type(key)
	if err != nil {
		response = R{"result": nil, "error": err}
		return
	}

	// Format the response according to the type the key holds.
	//
	switch keyType {
	case "string":
		println("GET", key)
		v, _ := client.Get(key)
		response = R{"result": v, "error": nil}

	case "set":
		println("SMEMBERS", key)
		v, _ := client.Smembers(key)
		response = R{"result": v.StringArray(), "error": nil}

	case "zset":
		println("ZRANGE", key, 0, -1)
		v, _ := client.Zrange(key, 0, -1)
		response = R{"result": v.StringArray(), "error": nil}

	case "list":
		println("LRANGE", key, 0, -1)
		v, _ := client.Lrange(key, 0, -1)
		response = R{"result": v.StringArray(), "error": nil}

	case "hash":
		if field := req.FormValue("field"); field != "" {
			println("HGET", key, field)
			v, _ := client.Hget(key, field)
			response = R{"result": v.String(), "error": nil}
		} else {
			println("HGETALL", key)
			reply, _ := client.Hgetall(key)
			response = R{"result": reply.StringMap(), "error": nil}
		}

	default:
		e := fmt.Sprintf("Unknown type for key %s: %s", key, keyType)
		response = R{"result": nil, "error": e}
	}
	return
}

// Handles HTTP POST requests, intended for creating new keys.
//
func HandleCreateOperation(req *http.Request, info *RequestInfo) (response R) {
	e := "Create operations have not yet been implemented."
	response = R{"result": nil, "error": e}
	return
}

// Handles HTTP PUT requests, inteded for updating keys.
//
func HandleUpdateOperation(req *http.Request, info *RequestInfo) (response R) {
	client := Database.DB(info.DbNum)
	existsp, err := client.Exists(info.Key)
	if  err != nil {
		response = R{"result": nil, "error": err}
		return
	}
	if existsp {
		var errors []string

		// Check if the user specfieid an expiry time for the key.
		//
		if ttl := req.FormValue("ttl"); len(ttl) > 0 {
			ittl, err := strconv.Atoi(ttl)
			if err != nil {
				errors = append(errors, fmt.Sprintf("%s", err))
			}

			setp, err := client.Expire(info.Key, int64(ittl))
			if err != nil {
				errors = append(errors, fmt.Sprintf("%s", err))
			}

			if setp {
				fmt.Println("EXPIRE", info.Key, ittl)
			}
		}

		// Get the value the user would like to set.
		//
		val := req.FormValue("value")
		fmt.Println("DEBUG", "Value =", val)

		// Now we need to branch, depending on the type of key we are setting
		// to.
		//
		keytype, err := client.Type(info.Key)
		if err != nil {
			errors = append(errors, fmt.Sprintf("%s", err))
		}

		switch keytype {
		case "string":
			if offset := req.FormValue("offset"); len(offset) > 0 {
				i, err := strconv.Atoi(offset)
				if err != nil {
					errors = append(errors, fmt.Sprintf("%s", err))
				} else {
					err = client.Setrange(info.Key, offset, val)
					fmt.Println("SETRANGE", info.Key, offset, val)
				}
			}
			err = client.Set(info.Key, val)
			fmt.Println("SET", info.Key, val)

		case "set":
			err = client.Sadd(info.Key, val)
			fmt.Println("SADD", info.Key, val)

		case "zset":
			err = client.Zadd(info.Key, val)
			fmt.Println("ZADD", info.Key, val)

		case "hash":
			field := req.FormValue("field")
			if len(field) == 0 {
				e := "Missing required parameter: field."
				response = R{"result": nil, "error": e}
				return
			}
			err = client.Hset(info.Key, field, val)
			fmt.Println("HSET", info.Key, field, val)

		case "list":
			side := "right"
			if req.FormValue("side") == "left" {
				side := "left"
			}

			if side == "left" {
				err = client.Lpush(info.Key, val)
				fmt.Println("LPUSH", info.Key, val)
			} else {
				err = client.Rpush(info.Key, val)
				fmt.Println("RPUSH", info.Key, val)
			}
		}

		if err != nil {
			errors = append(errors, fmt.Sprintf("%s", err))
		}

		if len(errors) > 0 {
			response = R{"result": nil, "error": strings.Join(errors, " ")}
		} else {
			response = R{"result": true, "error": nil}
		}
	} else {
		e := "Key does not exist, cannot update."
		response = R{"result": nil, "error": e}
		return
	}
	return
}

func Favicon(rw http.ResponseWriter, req *http.Request) {
	rw.WriteHeader(http.StatusOK)
	return
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/rand"
	"crypto/tls"
	"encoding/base32"
	"encoding/json"
	"errors"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/devel/dnsmapper/storeapi"
	"github.com/gorilla/handlers"
)

type ipResponse struct {
	DNS  string
	EDNS string
	HTTP string
}

var (
	uuidCh    chan string
	localNets []*net.IPNet
)

func init() {
	go uuidFactory()

	pn := []string{"10.0.0.0/8", "192.168.0.0/16"}
	for _, p := range pn {
		_, ipnet, err := net.ParseCIDR(p)
		if err != nil {
			panic(err)
		}
		localNets = append(localNets, ipnet)
	}
}

func uuidFactory() {
	uuidCh = make(chan string, 10)

	enc := base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")

	length := 20

	buf := make([]byte, length)
	uuid := make([]byte, enc.EncodedLen(length))

	for {
		rand.Read(buf)
		enc.Encode(uuid, buf)
		uuidCh <- string(uuid)
	}
}

func uuid() string {
	return <-uuidCh
}

func jsonData(req *http.Request) (string, error) {

	ip := remoteIP(req.Header)
	if len(ip) == 0 {
		ip, _, _ = net.SplitHostPort(req.RemoteAddr)
	}

	resp := &ipResponse{HTTP: ip, DNS: ""}

	uuid := getUUIDFromDomain(req.Host)

	dns, edns, ok := getCache(uuid)

	if !ok {
		return "", errors.New("UUID not found")
	}

	resp.DNS = dns
	resp.EDNS = edns

	js, err := json.Marshal(resp)
	if err != nil {
		log.Print("JSON ERROR:", err)
		return "", err
	}

	data := storeapi.RequestData{
		TestIP:   *flagip,
		ServerIP: resp.DNS,
		ClientIP: resp.HTTP,
		EdnsNet:  resp.EDNS,
	}
	select {
	case ch <- &data:
	default:
		log.Println("dropped log data, queue full")
	}

	return string(js), nil
}

func redirectUUID(w http.ResponseWriter, req *http.Request) {
	uuid := uuid()
	host := uuid + "." + *flagdomain

	proto := "http"

	if req.TLS != nil {
		proto = "https"
	}

	http.Redirect(w, req, proto+"://"+host+req.RequestURI, 302)
	return
}

func mainServer(w http.ResponseWriter, req *http.Request) {

	if req.URL.Path == "/jsonp" || req.URL.Path == "/json" || req.URL.Path == "/none" {

		w.Header().Set("Cache-Control", "private, no-cache, no-store, must-revalidate")

		uuid := getUUIDFromDomain(req.Host)
		if uuid == "www" {
			redirectUUID(w, req)
			return
		}

		js, err := jsonData(req)
		if err != nil {
			redirectUUID(w, req)
			return
		}

		if req.URL.Path == "/none" {
			w.WriteHeader(204)
			return
		}

		if jsonp := req.FormValue("jsonp"); len(jsonp) > 0 {
			w.Header().Set("Content-Type", "text/javascript")
			io.WriteString(w, jsonp+"("+js+");\n")
			return
		}

		// not jsonp
		w.Header().Set("Content-Type", "application/json")
		io.WriteString(w, js+"\n")
		return

	}

	if req.URL.Path == "/mapper.js" {
		w.Header().Set("Cache-Control", "public, max-age=86400")
		w.Header().Set("Content-Type", "text/javascript; charset=utf-8")
		w.WriteHeader(200)
		io.WriteString(w, `
(function(global){"use strict";var id=function(){var chars="0123456789abcdefghijklmnopqrstuvxyz".split("");
var uuid=[],rnd=Math.random,r;for(var i=0;i<17;i++){if(!uuid[i]){r=0|rnd()*16;uuid[i]=chars[i==19?r&3|8:r&15]}}
return uuid.join("")};
setTimeout(function(){(new Image).src="http://"+id()+".`+
			*flagdomain+
			`/none"},3200)})(this);
`)
		return
	}

	if req.URL.Path == "/" {
		w.Header().Set("Cache-Control", "public, max-age=900")
		w.WriteHeader(200)
		io.WriteString(w, HOMEPAGE)
		return
	}

	if req.URL.Path == "/robots.txt" {
		w.Header().Set("Cache-Control", "public, max-age=604800")
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.WriteHeader(200)
		io.WriteString(w, "# Hi Robot!\n")
		return
	}

	if req.URL.Path == "/version" {
		io.WriteString(w, `<html><head><title>DNS Mapper `+
			VERSION+`</title><body>`+
			`Hello`+
			`</body></html>`)
		return
	}

	http.NotFound(w, req)
	return
}

func httpHandler() {
	http.HandleFunc("/", mainServer)

	if len(*flagtlskeyfile) > 0 {

		log.Printf("Starting TLS with key='%s' and cert='%s'",
			*flagtlskeyfile,
			*flagtlscrtfile,
		)

		go func() {

			tlsconfig := &tls.Config{
				ClientSessionCache: tls.NewLRUClientSessionCache(100),
				MinVersion:         tls.VersionTLS10,
				CipherSuites: []uint16{
					tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
					tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
					tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
					tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
					tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
					tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
					tls.TLS_RSA_WITH_AES_128_CBC_SHA,
					tls.TLS_RSA_WITH_AES_256_CBC_SHA,
					tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
					tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA},
			}

			tlslisten := *flagip + ":" + *flaghttpsport
			srv := &http.Server{
				Addr:         tlslisten,
				WriteTimeout: 5 * time.Second,
				ReadTimeout:  10 * time.Second,
				TLSConfig:    tlsconfig,
			}
			log.Println("Going to listen for TLS requests on port", tlslisten)
			log.Fatal(srv.ListenAndServeTLS(
				*flagtlscrtfile,
				*flagtlskeyfile,
			))

		}()
	}

	listen := *flagip + ":" + *flaghttpport
	srv := &http.Server{
		Handler:      handlers.CombinedLoggingHandler(os.Stdout, http.DefaultServeMux),
		Addr:         listen,
		WriteTimeout: 5 * time.Second,
		ReadTimeout:  5 * time.Second,
	}
	log.Println("HTTP listen on", listen)
	log.Fatal(srv.ListenAndServe())

}

func localNet(ip net.IP) bool {
	for _, n := range localNets {
		if n.Contains(ip) {
			return true
		}
	}
	return false
}

func remoteIP(h http.Header) string {
	xff := h.Get("X-Forwarded-For")
	if len(xff) > 0 {
		ips := strings.Split(xff, ",")
		for i := len(ips) - 1; i >= 0; i-- {
			ip := strings.TrimSpace(ips[i])
			nip := net.ParseIP(ip)
			if nip != nil {
				if localNet(nip) {
					continue
				}
				return nip.String()
			}
		}
	}

	return ""
}
<commit_msg>CORS headers for json requests<commit_after>package main

import (
	"crypto/rand"
	"crypto/tls"
	"encoding/base32"
	"encoding/json"
	"errors"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/devel/dnsmapper/storeapi"
	"github.com/gorilla/handlers"
)

type ipResponse struct {
	DNS  string
	EDNS string
	HTTP string
}

var (
	uuidCh    chan string
	localNets []*net.IPNet
)

func init() {
	go uuidFactory()

	pn := []string{"10.0.0.0/8", "192.168.0.0/16"}
	for _, p := range pn {
		_, ipnet, err := net.ParseCIDR(p)
		if err != nil {
			panic(err)
		}
		localNets = append(localNets, ipnet)
	}
}

func uuidFactory() {
	uuidCh = make(chan string, 10)

	enc := base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")

	length := 20

	buf := make([]byte, length)
	uuid := make([]byte, enc.EncodedLen(length))

	for {
		rand.Read(buf)
		enc.Encode(uuid, buf)
		uuidCh <- string(uuid)
	}
}

func uuid() string {
	return <-uuidCh
}

func jsonData(req *http.Request) (string, error) {

	ip := remoteIP(req.Header)
	if len(ip) == 0 {
		ip, _, _ = net.SplitHostPort(req.RemoteAddr)
	}

	resp := &ipResponse{HTTP: ip, DNS: ""}

	uuid := getUUIDFromDomain(req.Host)

	dns, edns, ok := getCache(uuid)

	if !ok {
		return "", errors.New("UUID not found")
	}

	resp.DNS = dns
	resp.EDNS = edns

	js, err := json.Marshal(resp)
	if err != nil {
		log.Print("JSON ERROR:", err)
		return "", err
	}

	data := storeapi.RequestData{
		TestIP:   *flagip,
		ServerIP: resp.DNS,
		ClientIP: resp.HTTP,
		EdnsNet:  resp.EDNS,
	}
	select {
	case ch <- &data:
	default:
		log.Println("dropped log data, queue full")
	}

	return string(js), nil
}

func redirectUUID(w http.ResponseWriter, req *http.Request) {
	uuid := uuid()
	host := uuid + "." + *flagdomain

	proto := "http"

	if req.TLS != nil {
		proto = "https"
	}

	http.Redirect(w, req, proto+"://"+host+req.RequestURI, 302)
	return
}

func mainServer(w http.ResponseWriter, req *http.Request) {

	if req.URL.Path == "/jsonp" || req.URL.Path == "/json" || req.URL.Path == "/none" {

		w.Header().Set("Cache-Control", "private, no-cache, no-store, must-revalidate")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET")

		uuid := getUUIDFromDomain(req.Host)
		if uuid == "www" {
			redirectUUID(w, req)
			return
		}

		js, err := jsonData(req)
		if err != nil {
			redirectUUID(w, req)
			return
		}

		if req.URL.Path == "/none" {
			w.WriteHeader(204)
			return
		}

		if jsonp := req.FormValue("jsonp"); len(jsonp) > 0 {
			w.Header().Set("Content-Type", "text/javascript")
			io.WriteString(w, jsonp+"("+js+");\n")
			return
		}

		// not jsonp
		w.Header().Set("Content-Type", "application/json")
		io.WriteString(w, js+"\n")
		return

	}

	if req.URL.Path == "/mapper.js" {
		w.Header().Set("Cache-Control", "public, max-age=86400")
		w.Header().Set("Content-Type", "text/javascript; charset=utf-8")
		w.WriteHeader(200)
		io.WriteString(w, `
(function(global){"use strict";var id=function(){var chars="0123456789abcdefghijklmnopqrstuvxyz".split("");
var uuid=[],rnd=Math.random,r;for(var i=0;i<17;i++){if(!uuid[i]){r=0|rnd()*16;uuid[i]=chars[i==19?r&3|8:r&15]}}
return uuid.join("")};
setTimeout(function(){(new Image).src="http://"+id()+".`+
			*flagdomain+
			`/none"},3200)})(this);
`)
		return
	}

	if req.URL.Path == "/" {
		w.Header().Set("Cache-Control", "public, max-age=900")
		w.WriteHeader(200)
		io.WriteString(w, HOMEPAGE)
		return
	}

	if req.URL.Path == "/robots.txt" {
		w.Header().Set("Cache-Control", "public, max-age=604800")
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.WriteHeader(200)
		io.WriteString(w, "# Hi Robot!\n")
		return
	}

	if req.URL.Path == "/version" {
		io.WriteString(w, `<html><head><title>DNS Mapper `+
			VERSION+`</title><body>`+
			`Hello`+
			`</body></html>`)
		return
	}

	http.NotFound(w, req)
	return
}

func httpHandler() {
	http.HandleFunc("/", mainServer)

	if len(*flagtlskeyfile) > 0 {

		log.Printf("Starting TLS with key='%s' and cert='%s'",
			*flagtlskeyfile,
			*flagtlscrtfile,
		)

		go func() {

			tlsconfig := &tls.Config{
				ClientSessionCache: tls.NewLRUClientSessionCache(100),
				MinVersion:         tls.VersionTLS10,
				CipherSuites: []uint16{
					tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
					tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
					tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
					tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
					tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
					tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
					tls.TLS_RSA_WITH_AES_128_CBC_SHA,
					tls.TLS_RSA_WITH_AES_256_CBC_SHA,
					tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
					tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA},
			}

			tlslisten := *flagip + ":" + *flaghttpsport
			srv := &http.Server{
				Addr:         tlslisten,
				WriteTimeout: 5 * time.Second,
				ReadTimeout:  10 * time.Second,
				TLSConfig:    tlsconfig,
			}
			log.Println("Going to listen for TLS requests on port", tlslisten)
			log.Fatal(srv.ListenAndServeTLS(
				*flagtlscrtfile,
				*flagtlskeyfile,
			))

		}()
	}

	listen := *flagip + ":" + *flaghttpport
	srv := &http.Server{
		Handler:      handlers.CombinedLoggingHandler(os.Stdout, http.DefaultServeMux),
		Addr:         listen,
		WriteTimeout: 5 * time.Second,
		ReadTimeout:  5 * time.Second,
	}
	log.Println("HTTP listen on", listen)
	log.Fatal(srv.ListenAndServe())

}

func localNet(ip net.IP) bool {
	for _, n := range localNets {
		if n.Contains(ip) {
			return true
		}
	}
	return false
}

func remoteIP(h http.Header) string {
	xff := h.Get("X-Forwarded-For")
	if len(xff) > 0 {
		ips := strings.Split(xff, ",")
		for i := len(ips) - 1; i >= 0; i-- {
			ip := strings.TrimSpace(ips[i])
			nip := net.ParseIP(ip)
			if nip != nil {
				if localNet(nip) {
					continue
				}
				return nip.String()
			}
		}
	}

	return ""
}
<|endoftext|>


<commit_before>// Copyright 2012, Bryan Matsuo. 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

/*  Filename:    http.go
 *  Author:      Bryan Matsuo <bmatsuo@soe.ucsc.edu>
 *  Created:     2012-03-11 16:24:28.171009 -0700 PDT
 *  Description:
 */

import (
	"code.google.com/p/gorilla/mux"
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	"strings"
)

type HTTPFormat uint

const (
	Hhtml HTTPFormat = iota
	Hjson
	Hinvalid
)

var httpLogger Logger

func _initHTTP() {
	if loggerMux == nil {
		panic("nil mux")
	}
	httpLogger = loggerMux.NewSource("http")
}

func HTTPRequestFormat(r *http.Request) HTTPFormat {
	if format := mux.Vars(r)["format"]; format != "" {
		switch strings.ToLower(format) {
		case ".html":
			return Hhtml
		case ".json":
			return Hjson
		}
		return Hinvalid
	}
	f := r.URL.Query().Get("format")
	if f == "" {
		return Hhtml
	}
	switch strings.ToLower(f) {
	case "html":
		return Hhtml
	case "json":
		return Hjson
	}
	return Hinvalid
}

var configHTMLTemplateString = `

		<title>config | gutterd</title>
		<meta http-equiv="content-type" content="text/html;charset=UTF-8" />

Configuration

Web server:

				{{.HTTP}}

Logs:

						Name
						Accepts

					{{range .Logs}}

						{{.Path}}

							{{range .Accepts}}
							{{.}},
							{{end}}

					{{end}}

Poll frequency:

						{{.PollFrequency}}s

							<form name="upPoll" action="/config/pollFrequency" method="post">
								<input type="submit" value="⬆" />
								</form>
							<form name="downPoll" action="/config/pollFrequency" method="post">
								<input type="submit" value="⬇" />
								</form>

Watch directories:

					{{range $i, $w := .Watch}}

						{{$w}}

							<form name="delWatch{{$i}}" action="/config/watch" method="post">
								<input type="submit" value="×" />
								</form>

					{{end}}

						<form name="newWatch" action="/config/watch" method="post">
							<input type="text" name="watch" />
							<input type="submit" value="+" />
							</form>

Handlers:

						Name
						Watch
						Match

						Tracker
						Basename
						Extension

					{{range $i, $h := .Handlers}}

							<form name="delHandler{{$i}}" action="/config/handler/{{$h.Name}}/delete" method="post">
								<input type="submit" value="×" />
								</form>
							<form name="upHandler{{$i}}" action="/config/handler/{{$h.Name}}/up" method="post">
								<input type="submit" value="⬆" />
								</form>
							<form name="downHandler{{$i}}" action="/config/handler/{{$h.Name}}/down" method="post">
								<input type="submit" value="⬇" />
								</form>

						{{$h.Name}}
						{{$h.Watch}}
						{{$h.Match.Tracker}}
						{{$h.Match.Basename}}
						{{$h.Match.Ext}}

					{{end}}

`
var configHTMLTemplate = template.Must(template.New("config").Parse(configHTMLTemplateString))

func ConfigControllerShow(w http.ResponseWriter, r *http.Request) {
	format := HTTPRequestFormat(r)
	switch format {
	case Hhtml:
		err := configHTMLTemplate.Execute(w, config)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	case Hjson:
		var p []byte
		var err error
		w.Header().Set("Content-type", "application/json")
		if p, err = json.Marshal(config); err != nil {
			http.Error(w, `{"error": "couldn't marshal configuration"}`,
				http.StatusInternalServerError)
			return
		}
		fmt.Fprintf(w, "%s", p)
	default:
		http.Error(w, `{"error": "Invalid parameter: format"}`,
			http.StatusBadRequest)
	}
}

func ConfigControllerPollUpdate(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/config", http.StatusFound)
}

func ConfigControllerWatchUpdate(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/config", http.StatusFound)
}

func ConfigControllerSave(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/config", http.StatusFound)
}

func HandlerControllerNew(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "New handler form.")
}

func HandlerControllerPrepend(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/config", http.StatusFound)
}

func HandlerControllerAppend(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/config", http.StatusFound)
}

func HandlerControllerInsert(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/config", http.StatusFound)
}

func HandlerControllerDelete(w http.ResponseWriter, r *http.Request) {
	h := mux.Vars(r)["handler"]
	hIndex := -1
	for i := range config.Handlers {
		if h == config.Handlers[i].Name {
			hIndex = i
			break
		}
	}

	if hIndex < 0 {
		http.Redirect(w, r, "/config", http.StatusNotFound)
	}
	if hIndex >= len(config.Handlers)+1 {
		http.Redirect(w, r, "/config", http.StatusNotFound)
	}

	handlers = append(handlers[:hIndex], handlers[hIndex+1:]...)
	config.Handlers = append(config.Handlers[:hIndex], config.Handlers[hIndex+1:]...)

	Info("Deleted handler: ", h)

	http.Redirect(w, r, "/config", http.StatusFound)
}

func HandlerControllerUp(w http.ResponseWriter, r *http.Request) {
	h := mux.Vars(r)["handler"]
	hIndex := -1
	for i := range config.Handlers {
		if h == config.Handlers[i].Name {
			hIndex = i
			break
		}
	}

	if hIndex < 1 {
		http.Redirect(w, r, "/config", http.StatusNotFound)
	}

	handlers[hIndex-1], handlers[hIndex] = handlers[hIndex], handlers[hIndex-1]
	config.Handlers[hIndex-1], config.Handlers[hIndex] = config.Handlers[hIndex], config.Handlers[hIndex-1]

	handlerNames := make([]string, len(config.Handlers))
	for i := range config.Handlers {
		handlerNames[i] = config.Handlers[i].Name
	}
	Info("New handler order: ", handlerNames)

	http.Redirect(w, r, "/config", http.StatusFound)
}

func HandlerControllerDown(w http.ResponseWriter, r *http.Request) {
	h := mux.Vars(r)["handler"]
	hIndex := -1
	for i := range config.Handlers {
		if h == config.Handlers[i].Name {
			hIndex = i
			break
		}
	}

	if hIndex >= len(config.Handlers)-1 {
		http.Redirect(w, r, "/config", http.StatusNotFound)
	}

	handlers[hIndex], handlers[hIndex+1] = handlers[hIndex+1], handlers[hIndex]
	config.Handlers[hIndex], config.Handlers[hIndex+1] = config.Handlers[hIndex+1], config.Handlers[hIndex]

	handlerNames := make([]string, len(config.Handlers))
	for i := range config.Handlers {
		handlerNames[i] = config.Handlers[i].Name
	}
	Info("New handler order: ", handlerNames)
	http.Redirect(w, r, "/config", http.StatusFound)
}

func HandlerControllerNameUpdate(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/config", http.StatusFound)
}

func HandlerControllerWatchUpdate(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/config", http.StatusFound)
}

func ListenAndServe() {
	router := mux.NewRouter()
	router.HandleFunc("/config/handlers/prepend", HandlerControllerPrepend).
		Methods("POST")
	router.HandleFunc("/config/handlers/append", HandlerControllerAppend).
		Methods("POST")
	router.HandleFunc("/config/handlers/insert", HandlerControllerInsert).
		Methods("POST")
	router.HandleFunc("/config/handlers/new", HandlerControllerNew).
		Methods("GET")
	router.HandleFunc("/config/handler/{handler}/delete", HandlerControllerDelete).
		Methods("POST")
	router.HandleFunc("/config/handler/{handler}/up", HandlerControllerUp).
		Methods("POST")
	router.HandleFunc("/config/handler/{handler}/down", HandlerControllerDown).
		Methods("POST")
	router.HandleFunc("/config/handler/{handler}/name", HandlerControllerNameUpdate).
		Methods("POST")
	router.HandleFunc("/config/handler/{handler}/watch", HandlerControllerWatchUpdate).
		Methods("POST")
	router.HandleFunc("/config/pollFrequency", ConfigControllerPollUpdate).
		Methods("POST")
	router.HandleFunc("/config/watch", ConfigControllerWatchUpdate).
		Methods("POST")
	router.HandleFunc("/config/save", ConfigControllerSave).
		Methods("POST")
	router.HandleFunc(`/config{format:(\.(json|html))?}`, ConfigControllerShow).
		Methods("GET")
	http.ListenAndServe(config.HTTP, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		httpLogger.Infof("Request\t%v\t%v", r.Method, r.URL.Path)
		router.ServeHTTP(w, r)
	}))
}
<commit_msg>Update poll frequency and edit watchlist in web interface.<commit_after>// Copyright 2012, Bryan Matsuo. 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

/*  Filename:    http.go
 *  Author:      Bryan Matsuo <bmatsuo@soe.ucsc.edu>
 *  Created:     2012-03-11 16:24:28.171009 -0700 PDT
 *  Description:
 */

import (
	"code.google.com/p/gorilla/mux"
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"unicode"
)

type HTTPFormat uint

const (
	Hhtml HTTPFormat = iota
	Hjson
	Hinvalid
)

var httpLogger Logger

func _initHTTP() {
	if loggerMux == nil {
		panic("nil mux")
	}
	httpLogger = loggerMux.NewSource("http")
}

func HTTPRequestFormat(r *http.Request) HTTPFormat {
	if format := mux.Vars(r)["format"]; format != "" {
		switch strings.ToLower(format) {
		case ".html":
			return Hhtml
		case ".json":
			return Hjson
		}
		return Hinvalid
	}
	f := r.URL.Query().Get("format")
	if f == "" {
		return Hhtml
	}
	switch strings.ToLower(f) {
	case "html":
		return Hhtml
	case "json":
		return Hjson
	}
	return Hinvalid
}

var configHTMLTemplateString = `

		<title>config | gutterd</title>
		<meta http-equiv="content-type" content="text/html;charset=UTF-8" />

Configuration

Web server:

				{{.HTTP}}

Logs:

						Name
						Accepts

					{{range .Logs}}

						{{.Path}}

							{{range .Accepts}}
							{{.}},
							{{end}}

					{{end}}

Poll frequency:

						{{.PollFrequency}}s

							<form name="upPoll" action="/config/pollFrequency" method="post">
								<input type="hidden" name="delta" value="5" />
								<input type="submit" value="⬆" />
								</form>
							<form name="downPoll" action="/config/pollFrequency" method="post">
								<input type="hidden" name="delta" value="-5" />
								<input type="submit" value="⬇" />
								</form>

Watch directories:

					{{range $i, $w := .Watch}}

						{{$w}}

							<form name="delWatch{{$i}}" action="/config/watch/{{$i}}/delete" method="post">
								<input type="submit" value="×" />
								</form>

					{{end}}

						<form name="newWatch" action="/config/watch/add" method="post">
							<input type="text" name="watch" />
							<input type="submit" value="+" />
							</form>

Handlers:

						Name
						Watch
						Match

						Tracker
						Basename
						Extension

					{{range $i, $h := .Handlers}}

							<form name="delHandler{{$i}}" action="/config/handler/{{$h.Name}}/delete" method="post">
								<input type="submit" value="×" />
								</form>
							<form name="upHandler{{$i}}" action="/config/handler/{{$h.Name}}/up" method="post">
								<input type="submit" value="⬆" />
								</form>
							<form name="downHandler{{$i}}" action="/config/handler/{{$h.Name}}/down" method="post">
								<input type="submit" value="⬇" />
								</form>

						{{$h.Name}}
						{{$h.Watch}}
						{{$h.Match.Tracker}}
						{{$h.Match.Basename}}
						{{$h.Match.Ext}}

					{{end}}

`
var configHTMLTemplate = template.Must(template.New("config").Parse(configHTMLTemplateString))

func ConfigControllerShow(w http.ResponseWriter, r *http.Request) {
	format := HTTPRequestFormat(r)
	switch format {
	case Hhtml:
		err := configHTMLTemplate.Execute(w, config)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	case Hjson:
		var p []byte
		var err error
		w.Header().Set("Content-type", "application/json")
		if p, err = json.Marshal(config); err != nil {
			http.Error(w, `{"error": "couldn't marshal configuration"}`,
				http.StatusInternalServerError)
			return
		}
		fmt.Fprintf(w, "%s", p)
	default:
		http.Error(w, `{"error": "Invalid parameter: format"}`,
			http.StatusBadRequest)
	}
}

func ConfigControllerPollUpdate(w http.ResponseWriter, r *http.Request) {
	_delta := r.FormValue("delta")
	if _delta == "" {
		httpLogger.Info("No pollFrequency delta")
		http.Redirect(w, r, "/config", http.StatusFound)
		return
	}
	delta, err := strconv.ParseInt(_delta, 10, 64)
	if err != nil {
		httpLogger.Info("Error parsing delta: ", err)
		http.Redirect(w, r, "/config", http.StatusFound)
		return
	}
	freq := config.PollFrequency + delta
	if freq < 0 {
		httpLogger.Info("Negative frequency: ", freq)
		http.Redirect(w, r, "/config", http.StatusFound)
		return
	}
	Noticef("Updating poll frequency : %d", freq)
	config.PollFrequency = freq
	http.Redirect(w, r, "/config", http.StatusFound)
}

func ConfigControllerWatchAdd(w http.ResponseWriter, r *http.Request) {
	_path := strings.TrimFunc(r.FormValue("watch"), unicode.IsSpace)
	if _path == "" {
		httpLogger.Error("No 'watch' specified.")
		http.Redirect(w, r, "/config", http.StatusFound)
		return
	}
	if !strings.HasPrefix(_path, "/") {
		httpLogger.Error("Non-absolute path specified.")
		http.Redirect(w, r, "/config", http.StatusFound)
		return
	}
	path, err := filepath.EvalSymlinks(_path)
	if err != nil {
		httpLogger.Error("Error evaluating symlinks: ", err)
		http.Redirect(w, r, "/config", http.StatusNotFound)
		return
	}
	stat, err := os.Stat(path)
	if err != nil {
		httpLogger.Error("Cannot stat: ", err)
		http.Redirect(w, r, "/config", http.StatusNotFound)
		return
	}
	if !stat.IsDir() {
		httpLogger.Error("Not a directory: ", _path)
		http.Redirect(w, r, "/config", http.StatusNotFound)
		return
	}
	for _, _watch := range config.Watch {
		watch, err := filepath.EvalSymlinks(_watch)
		if err != nil {
			httpLogger.Error("Error evaluating symlinks: ", err)
			http.Redirect(w, r, "/config", http.StatusNotFound)
			return
		}
		if watch == path {
			httpLogger.Error("Already watching: ", _path)
			http.Redirect(w, r, "/config", http.StatusNotFound)
			return
		}
	}
	Notice("Watching directory: ", _path)
	config.Watch = append(config.Watch, _path)
	http.Redirect(w, r, "/config", http.StatusFound)
}

func ConfigControllerWatchDelete(w http.ResponseWriter, r *http.Request) {
	_i, err := strconv.ParseInt(mux.Vars(r)["index"], 10, 0)
	if err != nil {
		http.Redirect(w, r, "/config", http.StatusInternalServerError)
	}
	i := int(_i)
	if i >= len(config.Watch) {
		http.Redirect(w, r, "/config", http.StatusNotFound)
	}
	Notice("No longer watching directory: ", config.Watch[i])
	config.Watch = append(config.Watch[:i], config.Watch[i+1:]...)
	http.Redirect(w, r, "/config", http.StatusFound)
}

func ConfigControllerSave(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/config", http.StatusFound)
}

func HandlerControllerNew(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "New handler form.")
}

func HandlerControllerPrepend(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/config", http.StatusFound)
}

func HandlerControllerAppend(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/config", http.StatusFound)
}

func HandlerControllerInsert(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/config", http.StatusFound)
}

func HandlerControllerDelete(w http.ResponseWriter, r *http.Request) {
	h := mux.Vars(r)["handler"]
	hIndex := -1
	for i := range config.Handlers {
		if h == config.Handlers[i].Name {
			hIndex = i
			break
		}
	}

	if hIndex < 0 {
		http.Redirect(w, r, "/config", http.StatusNotFound)
	}
	if hIndex >= len(config.Handlers)+1 {
		http.Redirect(w, r, "/config", http.StatusNotFound)
	}

	handlers = append(handlers[:hIndex], handlers[hIndex+1:]...)
	config.Handlers = append(config.Handlers[:hIndex], config.Handlers[hIndex+1:]...)

	Info("Deleted handler: ", h)

	http.Redirect(w, r, "/config", http.StatusFound)
}

func HandlerControllerUp(w http.ResponseWriter, r *http.Request) {
	h := mux.Vars(r)["handler"]
	hIndex := -1
	for i := range config.Handlers {
		if h == config.Handlers[i].Name {
			hIndex = i
			break
		}
	}

	if hIndex < 1 {
		http.Redirect(w, r, "/config", http.StatusNotFound)
	}

	handlers[hIndex-1], handlers[hIndex] = handlers[hIndex], handlers[hIndex-1]
	config.Handlers[hIndex-1], config.Handlers[hIndex] = config.Handlers[hIndex], config.Handlers[hIndex-1]

	handlerNames := make([]string, len(config.Handlers))
	for i := range config.Handlers {
		handlerNames[i] = config.Handlers[i].Name
	}
	Notice("New handler order: ", handlerNames)

	http.Redirect(w, r, "/config", http.StatusFound)
}

func HandlerControllerDown(w http.ResponseWriter, r *http.Request) {
	h := mux.Vars(r)["handler"]
	hIndex := -1
	for i := range config.Handlers {
		if h == config.Handlers[i].Name {
			hIndex = i
			break
		}
	}

	if hIndex >= len(config.Handlers)-1 {
		http.Redirect(w, r, "/config", http.StatusNotFound)
	}

	handlers[hIndex], handlers[hIndex+1] = handlers[hIndex+1], handlers[hIndex]
	config.Handlers[hIndex], config.Handlers[hIndex+1] = config.Handlers[hIndex+1], config.Handlers[hIndex]

	handlerNames := make([]string, len(config.Handlers))
	for i := range config.Handlers {
		handlerNames[i] = config.Handlers[i].Name
	}
	Info("New handler order: ", handlerNames)
	http.Redirect(w, r, "/config", http.StatusFound)
}

func HandlerControllerNameUpdate(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/config", http.StatusFound)
}

func HandlerControllerWatchUpdate(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/config", http.StatusFound)
}

func ListenAndServe() {
	router := mux.NewRouter()
	router.HandleFunc("/config/handlers/prepend", HandlerControllerPrepend).
		Methods("POST")
	router.HandleFunc("/config/handlers/append", HandlerControllerAppend).
		Methods("POST")
	router.HandleFunc("/config/handlers/insert", HandlerControllerInsert).
		Methods("POST")
	router.HandleFunc("/config/handlers/new", HandlerControllerNew).
		Methods("GET")
	router.HandleFunc("/config/handler/{handler}/delete", HandlerControllerDelete).
		Methods("POST")
	router.HandleFunc("/config/handler/{handler}/up", HandlerControllerUp).
		Methods("POST")
	router.HandleFunc("/config/handler/{handler}/down", HandlerControllerDown).
		Methods("POST")
	router.HandleFunc("/config/handler/{handler}/name", HandlerControllerNameUpdate).
		Methods("POST")
	router.HandleFunc("/config/handler/{handler}/watch", HandlerControllerWatchUpdate).
		Methods("POST")
	router.HandleFunc("/config/pollFrequency", ConfigControllerPollUpdate).
		Methods("POST")
	router.HandleFunc("/config/watch/{index:[0-9]+}/delete", ConfigControllerWatchDelete).
		Methods("POST")
	router.HandleFunc("/config/watch/add", ConfigControllerWatchAdd).
		Methods("POST")
	router.HandleFunc("/config/save", ConfigControllerSave).
		Methods("POST")
	router.HandleFunc(`/config{format:(\.(json|html))?}`, ConfigControllerShow).
		Methods("GET")
	http.ListenAndServe(config.HTTP, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		httpLogger.Infof("Request\t%v\t%v", r.Method, r.URL.Path)
		router.ServeHTTP(w, r)
	}))
}
<|endoftext|>


<commit_before>package hush // import "github.com/mndrix/hush"

import (
	"fmt"
	"os"
	"path"
	"path/filepath"

	"github.com/pkg/errors"
)

// Main implements the main() function of the hush command line tool.
func Main() {
	tree, err := LoadTree()
	if err != nil {
		die("%s\n", err.Error())
	}

	switch os.Args[1] {
	case "export": // hush export
		err := SetPassphrase(tree)
		if err != nil {
			die("%s\n", err.Error())
		}
		CmdExport(os.Stdout, tree)
	case "import": // hush import
		err := SetPassphrase(tree)
		if err != nil {
			die("%s\n", err.Error())
		}
		warnings, err := CmdImport(os.Stdout, os.Stdin, tree)
		if err != nil {
			die("%s\n", err.Error())
		}
		for _, warning := range warnings {
			warn(warning)
		}
	case "ls": // hush ls foo/bar
		err := SetPassphrase(tree)
		if err != nil {
			die("%s\n", err.Error())
		}
		if len(os.Args) < 3 {
			tree.Print(os.Stdout)
			return
		}
		CmdLs(os.Stdout, tree, os.Args[2])
	case "set": // hush set paypal.com/personal/user john@example.com
		err := SetPassphrase(tree)
		if err != nil {
			die("%s\n", err.Error())
		}
		p := NewPath(os.Args[2])
		v, err := captureValue(os.Args[3])
		if err != nil {
			die("%s\n", err.Error())
		}
		err = CmdSet(os.Stdout, tree, p, v)
		if err != nil {
			die("%s\n", err.Error())
		}
	default:
		die("Usage: hum ...\n")
	}
}

func SetPassphrase(t *Tree) error {
	password := ""
	t.SetPassphrase(password)
	return nil
}

func die(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, format, args...)
	os.Exit(1)
}

func warn(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, format+"\n", args...)
}

func home() (string, error) {
	home := os.Getenv("HOME")
	if home == "" {
		return "", errors.New("Point $HOME at your home directory")
	}
	return home, nil
}

func hushPath() (string, error) {
	home, err := home()
	if err != nil {
		return "", err
	}
	filename := path.Join(home, ".hush")
	return filepath.EvalSymlinks(filename)
}

var editorVarNames = []string{
	"HUSH_EDITOR",
	"VISUAL",
	"EDITOR",
}

func editor() string {
	for _, varName := range editorVarNames {
		ed := os.Getenv(varName)
		if ed != "" {
			return ed
		}
	}

	ed := "vi"
	warn("environment configures no editor. defaulting to %s", ed)
	return ed
}
<commit_msg>Factor out SetPassphrase invocations<commit_after>package hush // import "github.com/mndrix/hush"

import (
	"fmt"
	"os"
	"path"
	"path/filepath"

	"github.com/pkg/errors"
)

// Main implements the main() function of the hush command line tool.
func Main() {
	tree, err := LoadTree()
	if err != nil {
		die("%s\n", err.Error())
	}

	// prepare for encryption and decryption
	err = SetPassphrase(tree)
	if err != nil {
		die("%s\n", err.Error())
	}

	switch os.Args[1] {
	case "export": // hush export
		CmdExport(os.Stdout, tree)
	case "import": // hush import
		warnings, err := CmdImport(os.Stdout, os.Stdin, tree)
		if err != nil {
			die("%s\n", err.Error())
		}
		for _, warning := range warnings {
			warn(warning)
		}
	case "ls": // hush ls foo/bar
		if len(os.Args) < 3 {
			tree.Print(os.Stdout)
			return
		}
		CmdLs(os.Stdout, tree, os.Args[2])
	case "set": // hush set paypal.com/personal/user john@example.com
		p := NewPath(os.Args[2])
		v, err := captureValue(os.Args[3])
		if err != nil {
			die("%s\n", err.Error())
		}
		err = CmdSet(os.Stdout, tree, p, v)
		if err != nil {
			die("%s\n", err.Error())
		}
	default:
		die("Usage: hum ...\n")
	}
}

func SetPassphrase(t *Tree) error {
	password := ""
	t.SetPassphrase(password)
	return nil
}

func die(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, format, args...)
	os.Exit(1)
}

func warn(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, format+"\n", args...)
}

func home() (string, error) {
	home := os.Getenv("HOME")
	if home == "" {
		return "", errors.New("Point $HOME at your home directory")
	}
	return home, nil
}

func hushPath() (string, error) {
	home, err := home()
	if err != nil {
		return "", err
	}
	filename := path.Join(home, ".hush")
	return filepath.EvalSymlinks(filename)
}

var editorVarNames = []string{
	"HUSH_EDITOR",
	"VISUAL",
	"EDITOR",
}

func editor() string {
	for _, varName := range editorVarNames {
		ed := os.Getenv(varName)
		if ed != "" {
			return ed
		}
	}

	ed := "vi"
	warn("environment configures no editor. defaulting to %s", ed)
	return ed
}
<|endoftext|>