<commit_before>/* Copyright 2016 Snapshots LLC
 *
 * 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 (
	"fmt"
	"io"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
)

const re = `(?i)/?uploads/(?P<env>\w+)/(?P<username>\w+)?/?picture/attachment/(?P<id>\d+)/(?P<name>\w+)(?:/[a-zA-Z0-9]+)?$`
const watermarkPathFmt = "%s/uploads/%s/%s/%d/%s"
const photographerInfoPathPart = "photographer_info/picture"
const watermarkPathPart = "watermark/logo"
const picturePathFmt = "%s/uploads/%s/picture/attachment/%d/%s"

var pathMatcher *regexp.Regexp

type imagizerHandler struct {
	imagizerHost *url.URL
	config       *Config
	db           *DB
	logger       ILogger
}

func init() {
	pathMatcher = regexp.MustCompile(re)
}

// Start initializes and then starts the HTTP server
func Start(c *Config, logger ILogger) {
	db, err := NewDB(c)
	logger.HandleErr(err)

	imagizerHost, err := url.Parse(c.ImagizerHost)
	logger.HandleErr(err)

	handler := imagizerHandler{
		imagizerHost: imagizerHost,
		config:       c,
		db:           db,
		logger:       logger,
	}

	s := &http.Server{
		Addr:    c.BindAddr(),
		Handler: handler,
	}

	logger.Info("Listening on %s", s.Addr)
	err = s.ListenAndServe()
	logger.HandleErr(err)
}

func (h imagizerHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	notFound := func(msg string) {
		h.logger.Warn(msg)
		http.NotFound(w, req)
	}

	if req.Method != http.MethodGet {
		notFound("Only GET requests allowed")
		return
	}

	if !pathMatcher.MatchString(req.URL.Path) {
		notFound(fmt.Sprintf("Malformed Path: %s", req.URL.Path))
		return
	}

	parts := extractPathPartsToMap(req.URL.Path)
	h.logger.Debug("URL Parts: %v", parts)

	username, _ := parts["username"]
	isDev := (parts["env"] == "development")
	if isDev != (len(username) > 0) {
		notFound(fmt.Sprintf("Malformed Path: %s", req.URL.Path))
		return
	}

	version, ok := h.config.versionsByName[parts["name"]]
	if !ok {
		notFound(fmt.Sprintf("Version not found with name %s", parts["name"]))
		return
	}
	h.logger.Debug("Version found: %v", version)

	pictureID, err := strconv.Atoi(parts["id"])
	h.logger.HandleErr(err)

	pic := h.db.loadPicture(pictureID, h.logger)
	if pic.eventID == 0 {
		h.logger.Warn("Picture not found for ID %d", pictureID)
		http.NotFound(w, req)
		return
	}

	proxy := h.imagizerURL(version, parts)
	resp, err := http.Get(proxy.String())
	defer h.logger.CloseQuietly(resp.Body)
	h.logger.HandleErr(err)

	h.logger.Debug("Imagizer response: %v", resp)

	i, err := io.Copy(w, resp.Body)
	h.logger.HandleErr(err)
	h.logger.Debug("Copied %d bytes to output from Imagizer response", i)
}

func (h imagizerHandler) imagizerURL(version map[string]interface{}, parts map[string]string) url.URL {
	vals := url.Values{}
	pictureID, err := strconv.Atoi(parts["id"])
	h.logger.HandleErr(err)

	for key, val := range version {
		if key == "watermark" {
			if val == true && h.isPhotographerImage(pictureID) {
				wm := h.getWatermarkInfo(pictureID, parts["env"])
				vals.Add("mark", wm.logo.String)
				vals.Add("mark_scale", strconv.Itoa(wm.scale))
				vals.Add("mark_pos", wm.position)
				vals.Add("mark_offset", strconv.Itoa(wm.offset))
				vals.Add("mark_alpha", strconv.Itoa(wm.alpha))
			}

			continue
		}

		if key == "function_name" || key == "name" || key == "only_shrink_larger" {
			continue
		}

		switch val := val.(type) {
		default:
			h.logger.Fatal("Unexpected type %T for %v", val, val)
		case string:
			vals.Add(key, val)
		case int:
			vals.Add(key, strconv.Itoa(val))
		case float64:
			vals.Add(key, strconv.Itoa(int(val)))
		case bool:
			vals.Add(key, fmt.Sprintf("%t", val))
		}
	}

	retURL := url.URL{}
	retURL.Scheme = h.imagizerHost.Scheme
	retURL.Host = h.imagizerHost.Host
	retURL.Path = h.pathForImage(parts)
	retURL.RawQuery = vals.Encode()

	h.logger.Debug("Imagizer URL: %s", retURL.String())
	return retURL
}

func (h imagizerHandler) isPhotographerImage(id int) bool {
	pic := h.db.loadPicture(id, h.logger)
	ev := h.db.loadEvent(pic.eventID, h.logger)

	is := pic.userID == ev.ownerID
	h.logger.Debug("is photographer image? %v", is)
	return is
}

func (h imagizerHandler) getWatermarkInfo(id int, env string) watermark {
	pic := h.db.loadPicture(id, h.logger)
	pi := h.db.loadPhotographerInfo(pic.userID, h.logger)
	wm := h.db.loadWatermark(pi.id, h.logger)

	if wm.id == 0 {
		wm = watermark{
			logo:     newNullString("https://www.snapshots.com/images/icon.png"),
			disabled: false,
			alpha:    70,
			scale:    15,
			offset:   3,
			position: "bottom,right",
		}

		if pi.picture.Valid {
			wm.logo = newNullString(fmt.Sprintf(watermarkPathFmt,
				h.config.CDNHost, env, photographerInfoPathPart, pi.id, pi.picture.String))
		}
	} else {
		if wm.logo.Valid {
			wm.logo = newNullString(fmt.Sprintf(watermarkPathFmt,
				h.config.CDNHost, env, watermarkPathPart, wm.id, wm.logo.String))
		}
	}

	h.logger.Debug("Watermark: %v", wm)
	return wm
}

func (h imagizerHandler) pathForImage(parts map[string]string) string {
	pictureID, err := strconv.Atoi(parts["id"])
	h.logger.HandleErr(err)

	pic := h.db.loadPicture(pictureID, h.logger)
	env, _ := parts["env"]

	var envAndUsername string

	if env == "development" {
		envAndUsername = fmt.Sprintf("%s/%s", env, parts["username"])
	} else {
		envAndUsername = env
	}

	path := fmt.Sprintf(picturePathFmt,
		BucketNames[env], envAndUsername, pictureID, pic.attachment)
	h.logger.Debug("Path for image: %s", path)
	return path
}

func extractPathPartsToMap(path string) map[string]string {
	names := pathMatcher.SubexpNames()[1:]
	matches := pathMatcher.FindStringSubmatch(path)

	md := map[string]string{}

	for i, s := range matches[1:] {
		md[names[i]] = s
	}

	return md
}
<commit_msg>Add context for slow client protection<commit_after>/* Copyright 2016 Snapshots LLC
 *
 * 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 (
	"context"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"time"
)

const re = `(?i)/?uploads/(?P<env>\w+)/(?P<username>\w+)?/?picture/attachment/(?P<id>\d+)/(?P<name>\w+)(?:/[a-zA-Z0-9]+)?$`
const watermarkPathFmt = "%s/uploads/%s/%s/%d/%s"
const photographerInfoPathPart = "photographer_info/picture"
const watermarkPathPart = "watermark/logo"
const picturePathFmt = "%s/uploads/%s/picture/attachment/%d/%s"

var pathMatcher *regexp.Regexp

type imagizerHandler struct {
	imagizerHost *url.URL
	config       *Config
	db           *DB
	logger       ILogger
}

func init() {
	pathMatcher = regexp.MustCompile(re)
}

// Start initializes and then starts the HTTP server
func Start(c *Config, logger ILogger) {
	db, err := NewDB(c)
	logger.HandleErr(err)

	imagizerHost, err := url.Parse(c.ImagizerHost)
	logger.HandleErr(err)

	handler := imagizerHandler{
		imagizerHost: imagizerHost,
		config:       c,
		db:           db,
		logger:       logger,
	}

	s := &http.Server{
		Addr:         c.BindAddr(),
		Handler:      handler,
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}

	logger.Info("Listening on %s", s.Addr)
	err = s.ListenAndServe()
	logger.HandleErr(err)
}

func (h imagizerHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// 5 seconds max from start to finish
	// TODO: lower this based on real-world performance
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	timer := time.AfterFunc(5*time.Second, func() { cancel() })
	req.WithContext(ctx)

	notFound := func(msg string) {
		h.logger.Warn(msg)
		http.NotFound(w, req)
		cancel()
	}

	handleErr := func(err error) {
		if err != nil {
			cancel()
		}
		h.logger.HandleErr(err)
	}

	if req.Method != http.MethodGet {
		notFound("Only GET requests allowed")
		return
	}

	if !pathMatcher.MatchString(req.URL.Path) {
		notFound(fmt.Sprintf("Malformed Path: %s", req.URL.Path))
		return
	}

	parts := extractPathPartsToMap(req.URL.Path)
	h.logger.Debug("URL Parts: %v", parts)

	username, _ := parts["username"]
	isDev := (parts["env"] == "development")
	if isDev != (len(username) > 0) {
		notFound(fmt.Sprintf("Malformed Path: %s", req.URL.Path))
		return
	}

	version, ok := h.config.versionsByName[parts["name"]]
	if !ok {
		notFound(fmt.Sprintf("Version not found with name %s", parts["name"]))
		return
	}
	h.logger.Debug("Version found: %v", version)

	pictureID, err := strconv.Atoi(parts["id"])
	handleErr(err)

	pic := h.db.loadPicture(pictureID, h.logger)
	if pic.eventID == 0 {
		h.logger.Warn("Picture not found for ID %d", pictureID)
		http.NotFound(w, req)
		return
	}

	proxy := h.imagizerURL(version, parts)
	imagizerReq, err := http.NewRequest("GET", proxy.String(), nil)
	handleErr(err)
	reqCtx, reqCancel := context.WithTimeout(ctx, 1*time.Second)
	defer reqCancel()
	imagizerReq.WithContext(reqCtx)
	resp, err := http.DefaultClient.Do(imagizerReq)
	handleErr(err)
	defer h.logger.CloseQuietly(resp.Body)
	h.logger.Debug("Imagizer response: %v", resp)

	for {
		if ctx.Err() != nil {
			break
		}

		timer.Reset(150 * time.Millisecond)
		_, err := io.CopyN(w, resp.Body, 1024)
		if err == io.EOF {
			break
		} else {
			handleErr(err)
		}
	}
}

func (h imagizerHandler) imagizerURL(version map[string]interface{}, parts map[string]string) url.URL {
	vals := url.Values{}
	pictureID, err := strconv.Atoi(parts["id"])
	h.logger.HandleErr(err)

	for key, val := range version {
		if key == "watermark" {
			if val == true && h.isPhotographerImage(pictureID) {
				wm := h.getWatermarkInfo(pictureID, parts["env"])
				vals.Add("mark", wm.logo.String)
				vals.Add("mark_scale", strconv.Itoa(wm.scale))
				vals.Add("mark_pos", wm.position)
				vals.Add("mark_offset", strconv.Itoa(wm.offset))
				vals.Add("mark_alpha", strconv.Itoa(wm.alpha))
			}

			continue
		}

		if key == "function_name" || key == "name" || key == "only_shrink_larger" {
			continue
		}

		switch val := val.(type) {
		default:
			h.logger.Fatal("Unexpected type %T for %v", val, val)
		case string:
			vals.Add(key, val)
		case int:
			vals.Add(key, strconv.Itoa(val))
		case float64:
			vals.Add(key, strconv.Itoa(int(val)))
		case bool:
			vals.Add(key, fmt.Sprintf("%t", val))
		}
	}

	retURL := url.URL{}
	retURL.Scheme = h.imagizerHost.Scheme
	retURL.Host = h.imagizerHost.Host
	retURL.Path = h.pathForImage(parts)
	retURL.RawQuery = vals.Encode()

	h.logger.Debug("Imagizer URL: %s", retURL.String())
	return retURL
}

func (h imagizerHandler) isPhotographerImage(id int) bool {
	pic := h.db.loadPicture(id, h.logger)
	ev := h.db.loadEvent(pic.eventID, h.logger)

	is := pic.userID == ev.ownerID
	h.logger.Debug("is photographer image? %v", is)
	return is
}

func (h imagizerHandler) getWatermarkInfo(id int, env string) watermark {
	pic := h.db.loadPicture(id, h.logger)
	pi := h.db.loadPhotographerInfo(pic.userID, h.logger)
	wm := h.db.loadWatermark(pi.id, h.logger)

	if wm.id == 0 {
		wm = watermark{
			logo:     newNullString("https://www.snapshots.com/images/icon.png"),
			disabled: false,
			alpha:    70,
			scale:    15,
			offset:   3,
			position: "bottom,right",
		}

		if pi.picture.Valid {
			wm.logo = newNullString(fmt.Sprintf(watermarkPathFmt,
				h.config.CDNHost, env, photographerInfoPathPart, pi.id, pi.picture.String))
		}
	} else {
		if wm.logo.Valid {
			wm.logo = newNullString(fmt.Sprintf(watermarkPathFmt,
				h.config.CDNHost, env, watermarkPathPart, wm.id, wm.logo.String))
		}
	}

	h.logger.Debug("Watermark: %v", wm)
	return wm
}

func (h imagizerHandler) pathForImage(parts map[string]string) string {
	pictureID, err := strconv.Atoi(parts["id"])
	h.logger.HandleErr(err)

	pic := h.db.loadPicture(pictureID, h.logger)
	env, _ := parts["env"]

	var envAndUsername string

	if env == "development" {
		envAndUsername = fmt.Sprintf("%s/%s", env, parts["username"])
	} else {
		envAndUsername = env
	}

	path := fmt.Sprintf(picturePathFmt,
		BucketNames[env], envAndUsername, pictureID, pic.attachment)
	h.logger.Debug("Path for image: %s", path)
	return path
}

func extractPathPartsToMap(path string) map[string]string {
	names := pathMatcher.SubexpNames()[1:]
	matches := pathMatcher.FindStringSubmatch(path)

	md := map[string]string{}

	for i, s := range matches[1:] {
		md[names[i]] = s
	}

	return md
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/md5"
	"encoding/base64"
	"encoding/hex"
	"image/color"
	"image/png"
	"log"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"

	"github.com/cupcake/sigil/gen"
)

var config = gen.Sigil{
	Rows: 5,
	Foreground: []color.NRGBA{
		rgb(45, 79, 255),
		rgb(254, 180, 44),
		rgb(226, 121, 234),
		rgb(30, 179, 253),
		rgb(232, 77, 65),
		rgb(49, 203, 115),
		rgb(141, 69, 170),
	},
	Background: rgb(224, 224, 224),
}

func rgb(r, g, b uint8) color.NRGBA { return color.NRGBA{r, g, b, 255} }

func imageHandler(w http.ResponseWriter, r *http.Request) {
	ext := path.Ext(r.URL.Path)
	if ext != "" && ext != ".png" && ext != ".svg" {
		http.Error(w, "Unknown file extension", http.StatusNotFound)
		return
	}

	width := 240
	q := r.URL.Query()
	if ws := q.Get("w"); ws != "" {
		var err error
		width, err = strconv.Atoi(ws)
		if err != nil {
			http.Error(w, "Invalid w parameter, must be an integer", http.StatusBadRequest)
			return
		}
		if width > 600 {
			http.Error(w, "Invalid w parameter, must be less than 600", http.StatusBadRequest)
			return
		}
		div := (config.Rows + 1) * 2
		if width%div != 0 {
			http.Error(w, "Invalid w parameter, must be evenly divisible by "+strconv.Itoa(div), http.StatusBadRequest)
			return
		}
	}
	inverted := false
	if inv := q.Get("inverted"); inv != "" && inv != "false" && inv != "0" {
		inverted = true
	}

	base := path.Base(r.URL.Path)
	base = base[:len(base)-len(ext)]
	var data []byte
	if len(base) == 32 {
		// try to decode hex MD5
		data, _ = hex.DecodeString(base)
	}
	if data == nil {
		data = md5hash(base)
	}

	etag := `"` + base64.StdEncoding.EncodeToString(data) + `"`
	w.Header().Set("Etag", etag)
	if cond := r.Header.Get("If-None-Match"); cond != "" {
		if strings.Contains(cond, etag) {
			w.WriteHeader(http.StatusNotModified)
			return
		}
	}

	w.Header().Set("Cache-Control", "max-age=315360000")
	if ext == ".svg" {
		w.Header().Set("Content-Type", "image/svg+xml")
		config.MakeSVG(w, inverted, data)
		return
	}
	w.Header().Set("Content-Type", "image/png")
	png.Encode(w, config.Make(width, inverted, data))
}

func md5hash(s string) []byte {
	h := md5.New()
	h.Write([]byte(s))
	return h.Sum(nil)
}

func main() {
	http.HandleFunc("/", imageHandler)
	log.Fatal(http.ListenAndServe(":"+os.Getenv("PORT"), nil))
}
<commit_msg>Allow dots in data<commit_after>package main

import (
	"crypto/md5"
	"encoding/base64"
	"encoding/hex"
	"image/color"
	"image/png"
	"log"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"

	"github.com/cupcake/sigil/gen"
)

var config = gen.Sigil{
	Rows: 5,
	Foreground: []color.NRGBA{
		rgb(45, 79, 255),
		rgb(254, 180, 44),
		rgb(226, 121, 234),
		rgb(30, 179, 253),
		rgb(232, 77, 65),
		rgb(49, 203, 115),
		rgb(141, 69, 170),
	},
	Background: rgb(224, 224, 224),
}

func rgb(r, g, b uint8) color.NRGBA { return color.NRGBA{r, g, b, 255} }

func imageHandler(w http.ResponseWriter, r *http.Request) {
	ext := path.Ext(r.URL.Path)
	if ext != "" && ext != ".png" && ext != ".svg" {
		ext = ""
	}

	width := 240
	q := r.URL.Query()
	if ws := q.Get("w"); ws != "" {
		var err error
		width, err = strconv.Atoi(ws)
		if err != nil {
			http.Error(w, "Invalid w parameter, must be an integer", http.StatusBadRequest)
			return
		}
		if width > 600 {
			http.Error(w, "Invalid w parameter, must be less than 600", http.StatusBadRequest)
			return
		}
		div := (config.Rows + 1) * 2
		if width%div != 0 {
			http.Error(w, "Invalid w parameter, must be evenly divisible by "+strconv.Itoa(div), http.StatusBadRequest)
			return
		}
	}
	inverted := false
	if inv := q.Get("inverted"); inv != "" && inv != "false" && inv != "0" {
		inverted = true
	}

	base := path.Base(r.URL.Path)
	base = base[:len(base)-len(ext)]
	var data []byte
	if len(base) == 32 {
		// try to decode hex MD5
		data, _ = hex.DecodeString(base)
	}
	if data == nil {
		data = md5hash(base)
	}

	etag := `"` + base64.StdEncoding.EncodeToString(data) + `"`
	w.Header().Set("Etag", etag)
	if cond := r.Header.Get("If-None-Match"); cond != "" {
		if strings.Contains(cond, etag) {
			w.WriteHeader(http.StatusNotModified)
			return
		}
	}

	w.Header().Set("Cache-Control", "max-age=315360000")
	if ext == ".svg" {
		w.Header().Set("Content-Type", "image/svg+xml")
		config.MakeSVG(w, inverted, data)
		return
	}
	w.Header().Set("Content-Type", "image/png")
	png.Encode(w, config.Make(width, inverted, data))
}

func md5hash(s string) []byte {
	h := md5.New()
	h.Write([]byte(s))
	return h.Sum(nil)
}

func main() {
	http.HandleFunc("/", imageHandler)
	log.Fatal(http.ListenAndServe(":"+os.Getenv("PORT"), nil))
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"crypto/rand"
	"crypto/subtle"
	"encoding/binary"
	"io"
	"log"
	"net"
	"sync"
	"sync/atomic"
	"time"

	"golang.org/x/crypto/ed25519"
)

// ClientConnection - A client connection
type ClientConnection struct {
	conf          Conf
	conn          net.Conn
	reader        *bufio.Reader
	writer        *bufio.Writer
	clientVersion byte
}

// StoredContent - Paste buffer
type StoredContent struct {
	sync.RWMutex

	ts                                []byte
	signature                         []byte
	ciphertextWithEncryptSkIDAndNonce []byte
}

var storedContent StoredContent
var trustedClients TrustedClients
var clientsCount = uint64(0)

func (cnx *ClientConnection) getOperation(h1 []byte, isMove bool) {
	conf, reader, writer := cnx.conf, cnx.reader, cnx.writer
	rbuf := make([]byte, 32)
	if _, err := io.ReadFull(reader, rbuf); err != nil {
		log.Print(err)
		return
	}
	h2 := rbuf
	opcode := byte('G')
	if isMove {
		opcode = byte('M')
	}
	wh2 := auth2get(conf, cnx.clientVersion, h1, opcode)
	if subtle.ConstantTimeCompare(wh2, h2) != 1 {
		return
	}

	var ts, signature, ciphertextWithEncryptSkIDAndNonce []byte
	if isMove {
		storedContent.Lock()
		ts, signature, ciphertextWithEncryptSkIDAndNonce =
			storedContent.ts, storedContent.signature, storedContent.ciphertextWithEncryptSkIDAndNonce
		storedContent.ts, storedContent.signature,
			storedContent.ciphertextWithEncryptSkIDAndNonce = nil, nil, nil
		storedContent.Unlock()
	} else {
		storedContent.RLock()
		ts, signature, ciphertextWithEncryptSkIDAndNonce =
			storedContent.ts, storedContent.signature,
			storedContent.ciphertextWithEncryptSkIDAndNonce
		storedContent.RUnlock()
	}

	cnx.conn.SetDeadline(time.Now().Add(conf.DataTimeout))
	h3 := auth3get(conf, cnx.clientVersion, h2, ts, signature)
	writer.Write(h3)
	ciphertextWithEncryptSkIDAndNonceLen := uint64(len(ciphertextWithEncryptSkIDAndNonce))
	binary.Write(writer, binary.LittleEndian, ciphertextWithEncryptSkIDAndNonceLen)
	writer.Write(ts)
	writer.Write(signature)
	writer.Write(ciphertextWithEncryptSkIDAndNonce)
	if err := writer.Flush(); err != nil {
		log.Print(err)
		return
	}
}

func (cnx *ClientConnection) storeOperation(h1 []byte) {
	conf, reader, writer := cnx.conf, cnx.reader, cnx.writer
	rbuf := make([]byte, 112)
	if _, err := io.ReadFull(reader, rbuf); err != nil {
		log.Print(err)
		return
	}
	h2 := rbuf[0:32]
	ciphertextWithEncryptSkIDAndNonceLen := binary.LittleEndian.Uint64(rbuf[32:40])
	if ciphertextWithEncryptSkIDAndNonceLen < 8+24 {
		log.Printf("Short encrypted message (only %v bytes)\n", ciphertextWithEncryptSkIDAndNonceLen)
		return
	}
	if conf.MaxLen > 0 && ciphertextWithEncryptSkIDAndNonceLen > conf.MaxLen {
		log.Printf("%v bytes requested to be stored, but limit set to %v bytes (%v Mb)\n",
			ciphertextWithEncryptSkIDAndNonceLen, conf.MaxLen, conf.MaxLen/(1024*1024))
		return
	}
	var ts, signature []byte
	ts = rbuf[40:48]
	signature = rbuf[48:112]
	opcode := byte('S')

	wh2 := auth2store(conf, cnx.clientVersion, h1, opcode, ts, signature)
	if subtle.ConstantTimeCompare(wh2, h2) != 1 {
		return
	}
	ciphertextWithEncryptSkIDAndNonce := make([]byte, ciphertextWithEncryptSkIDAndNonceLen)

	cnx.conn.SetDeadline(time.Now().Add(conf.DataTimeout))
	if _, err := io.ReadFull(reader, ciphertextWithEncryptSkIDAndNonce); err != nil {
		log.Print(err)
		return
	}
	if ed25519.Verify(conf.SignPk, ciphertextWithEncryptSkIDAndNonce, signature) != true {
		return
	}
	h3 := auth3store(conf, h2)

	storedContent.Lock()
	storedContent.ts = ts
	storedContent.signature = signature
	storedContent.ciphertextWithEncryptSkIDAndNonce = ciphertextWithEncryptSkIDAndNonce
	storedContent.Unlock()

	writer.Write(h3)
	if err := writer.Flush(); err != nil {
		log.Print(err)
		return
	}
}

func handleClientConnection(conf Conf, conn net.Conn) {
	defer conn.Close()
	reader, writer := bufio.NewReader(conn), bufio.NewWriter(conn)
	cnx := ClientConnection{
		conf:   conf,
		conn:   conn,
		reader: reader,
		writer: writer,
	}
	rbuf := make([]byte, 65)
	if _, err := io.ReadFull(reader, rbuf); err != nil {
		return
	}
	cnx.clientVersion = rbuf[0]
	if cnx.clientVersion != 6 {
		log.Print("Unsupported client version - Please run the same version on the server and on the client")
		return
	}
	r := rbuf[1:33]
	h0 := rbuf[33:65]
	wh0 := auth0(conf, cnx.clientVersion, r)
	if subtle.ConstantTimeCompare(wh0, h0) != 1 {
		return
	}
	r2 := make([]byte, 32)
	if _, err := rand.Read(r); err != nil {
		log.Fatal(err)
	}
	h1 := auth1(conf, cnx.clientVersion, h0, r2)
	writer.Write([]byte{cnx.clientVersion})
	writer.Write(r2)
	writer.Write(h1)
	if err := writer.Flush(); err != nil {
		log.Print(err)
		return
	}
	remoteIP := cnx.conn.RemoteAddr().(*net.TCPAddr).IP
	addToTrustedIPs(conf, remoteIP)
	opcode, err := reader.ReadByte()
	if err != nil {
		return
	}
	switch opcode {
	case byte('G'):
		cnx.getOperation(h1, false)
	case byte('M'):
		cnx.getOperation(h1, true)
	case byte('S'):
		cnx.storeOperation(h1)
	}
}

// TrustedClients - Clients IPs having recently performed a successful handshake
type TrustedClients struct {
	sync.RWMutex

	ips []net.IP
}

func addToTrustedIPs(conf Conf, ip net.IP) {
	trustedClients.Lock()
	if uint64(len(trustedClients.ips)) >= conf.TrustedIPCount {
		trustedClients.ips = append(trustedClients.ips[1:], ip)
	} else {
		trustedClients.ips = append(trustedClients.ips, ip)
	}
	trustedClients.Unlock()
}

func isIPTrusted(conf Conf, ip net.IP) bool {
	trustedClients.RLock()
	defer trustedClients.RUnlock()
	if len(trustedClients.ips) == 0 {
		return true
	}
	for _, foundIP := range trustedClients.ips {
		if foundIP.Equal(ip) {
			return true
		}
	}
	return false
}

func acceptClient(conf Conf, conn net.Conn) {
	handleClientConnection(conf, conn)
	atomic.AddUint64(&clientsCount, ^uint64(0))
}

func maybeAcceptClient(conf Conf, conn net.Conn) {
	conn.SetDeadline(time.Now().Add(conf.Timeout))
	remoteIP := conn.RemoteAddr().(*net.TCPAddr).IP
	for {
		count := atomic.LoadUint64(&clientsCount)
		if count >= conf.MaxClients-conf.TrustedIPCount && isIPTrusted(conf, remoteIP) == false {
			conn.Close()
			return
		}
		if count >= conf.MaxClients {
			conn.Close()
			return
		} else if atomic.CompareAndSwapUint64(&clientsCount, count, count+1) {
			break
		}
	}
	go acceptClient(conf, conn)
}

// RunServer - run a server
func RunServer(conf Conf) {
	go handleSignals()
	listen, err := net.Listen("tcp", conf.Listen)
	if err != nil {
		log.Fatal(err)
	}
	defer listen.Close()
	for {
		conn, err := listen.Accept()
		if err != nil {
			log.Fatal(err)
		}
		maybeAcceptClient(conf, conn)
	}
}
<commit_msg>Fix empty server nonce (#30)<commit_after>package main

import (
	"bufio"
	"crypto/rand"
	"crypto/subtle"
	"encoding/binary"
	"io"
	"log"
	"net"
	"sync"
	"sync/atomic"
	"time"

	"golang.org/x/crypto/ed25519"
)

// ClientConnection - A client connection
type ClientConnection struct {
	conf          Conf
	conn          net.Conn
	reader        *bufio.Reader
	writer        *bufio.Writer
	clientVersion byte
}

// StoredContent - Paste buffer
type StoredContent struct {
	sync.RWMutex

	ts                                []byte
	signature                         []byte
	ciphertextWithEncryptSkIDAndNonce []byte
}

var storedContent StoredContent
var trustedClients TrustedClients
var clientsCount = uint64(0)

func (cnx *ClientConnection) getOperation(h1 []byte, isMove bool) {
	conf, reader, writer := cnx.conf, cnx.reader, cnx.writer
	rbuf := make([]byte, 32)
	if _, err := io.ReadFull(reader, rbuf); err != nil {
		log.Print(err)
		return
	}
	h2 := rbuf
	opcode := byte('G')
	if isMove {
		opcode = byte('M')
	}
	wh2 := auth2get(conf, cnx.clientVersion, h1, opcode)
	if subtle.ConstantTimeCompare(wh2, h2) != 1 {
		return
	}

	var ts, signature, ciphertextWithEncryptSkIDAndNonce []byte
	if isMove {
		storedContent.Lock()
		ts, signature, ciphertextWithEncryptSkIDAndNonce =
			storedContent.ts, storedContent.signature, storedContent.ciphertextWithEncryptSkIDAndNonce
		storedContent.ts, storedContent.signature,
			storedContent.ciphertextWithEncryptSkIDAndNonce = nil, nil, nil
		storedContent.Unlock()
	} else {
		storedContent.RLock()
		ts, signature, ciphertextWithEncryptSkIDAndNonce =
			storedContent.ts, storedContent.signature,
			storedContent.ciphertextWithEncryptSkIDAndNonce
		storedContent.RUnlock()
	}

	cnx.conn.SetDeadline(time.Now().Add(conf.DataTimeout))
	h3 := auth3get(conf, cnx.clientVersion, h2, ts, signature)
	writer.Write(h3)
	ciphertextWithEncryptSkIDAndNonceLen := uint64(len(ciphertextWithEncryptSkIDAndNonce))
	binary.Write(writer, binary.LittleEndian, ciphertextWithEncryptSkIDAndNonceLen)
	writer.Write(ts)
	writer.Write(signature)
	writer.Write(ciphertextWithEncryptSkIDAndNonce)
	if err := writer.Flush(); err != nil {
		log.Print(err)
		return
	}
}

func (cnx *ClientConnection) storeOperation(h1 []byte) {
	conf, reader, writer := cnx.conf, cnx.reader, cnx.writer
	rbuf := make([]byte, 112)
	if _, err := io.ReadFull(reader, rbuf); err != nil {
		log.Print(err)
		return
	}
	h2 := rbuf[0:32]
	ciphertextWithEncryptSkIDAndNonceLen := binary.LittleEndian.Uint64(rbuf[32:40])
	if ciphertextWithEncryptSkIDAndNonceLen < 8+24 {
		log.Printf("Short encrypted message (only %v bytes)\n", ciphertextWithEncryptSkIDAndNonceLen)
		return
	}
	if conf.MaxLen > 0 && ciphertextWithEncryptSkIDAndNonceLen > conf.MaxLen {
		log.Printf("%v bytes requested to be stored, but limit set to %v bytes (%v Mb)\n",
			ciphertextWithEncryptSkIDAndNonceLen, conf.MaxLen, conf.MaxLen/(1024*1024))
		return
	}
	var ts, signature []byte
	ts = rbuf[40:48]
	signature = rbuf[48:112]
	opcode := byte('S')

	wh2 := auth2store(conf, cnx.clientVersion, h1, opcode, ts, signature)
	if subtle.ConstantTimeCompare(wh2, h2) != 1 {
		return
	}
	ciphertextWithEncryptSkIDAndNonce := make([]byte, ciphertextWithEncryptSkIDAndNonceLen)

	cnx.conn.SetDeadline(time.Now().Add(conf.DataTimeout))
	if _, err := io.ReadFull(reader, ciphertextWithEncryptSkIDAndNonce); err != nil {
		log.Print(err)
		return
	}
	if ed25519.Verify(conf.SignPk, ciphertextWithEncryptSkIDAndNonce, signature) != true {
		return
	}
	h3 := auth3store(conf, h2)

	storedContent.Lock()
	storedContent.ts = ts
	storedContent.signature = signature
	storedContent.ciphertextWithEncryptSkIDAndNonce = ciphertextWithEncryptSkIDAndNonce
	storedContent.Unlock()

	writer.Write(h3)
	if err := writer.Flush(); err != nil {
		log.Print(err)
		return
	}
}

func handleClientConnection(conf Conf, conn net.Conn) {
	defer conn.Close()
	reader, writer := bufio.NewReader(conn), bufio.NewWriter(conn)
	cnx := ClientConnection{
		conf:   conf,
		conn:   conn,
		reader: reader,
		writer: writer,
	}
	rbuf := make([]byte, 65)
	if _, err := io.ReadFull(reader, rbuf); err != nil {
		return
	}
	cnx.clientVersion = rbuf[0]
	if cnx.clientVersion != 6 {
		log.Print("Unsupported client version - Please run the same version on the server and on the client")
		return
	}
	r := rbuf[1:33]
	h0 := rbuf[33:65]
	wh0 := auth0(conf, cnx.clientVersion, r)
	if subtle.ConstantTimeCompare(wh0, h0) != 1 {
		return
	}
	r2 := make([]byte, 32)
	if _, err := rand.Read(r2); err != nil {
		log.Fatal(err)
	}
	h1 := auth1(conf, cnx.clientVersion, h0, r2)
	writer.Write([]byte{cnx.clientVersion})
	writer.Write(r2)
	writer.Write(h1)
	if err := writer.Flush(); err != nil {
		log.Print(err)
		return
	}
	remoteIP := cnx.conn.RemoteAddr().(*net.TCPAddr).IP
	addToTrustedIPs(conf, remoteIP)
	opcode, err := reader.ReadByte()
	if err != nil {
		return
	}
	switch opcode {
	case byte('G'):
		cnx.getOperation(h1, false)
	case byte('M'):
		cnx.getOperation(h1, true)
	case byte('S'):
		cnx.storeOperation(h1)
	}
}

// TrustedClients - Clients IPs having recently performed a successful handshake
type TrustedClients struct {
	sync.RWMutex

	ips []net.IP
}

func addToTrustedIPs(conf Conf, ip net.IP) {
	trustedClients.Lock()
	if uint64(len(trustedClients.ips)) >= conf.TrustedIPCount {
		trustedClients.ips = append(trustedClients.ips[1:], ip)
	} else {
		trustedClients.ips = append(trustedClients.ips, ip)
	}
	trustedClients.Unlock()
}

func isIPTrusted(conf Conf, ip net.IP) bool {
	trustedClients.RLock()
	defer trustedClients.RUnlock()
	if len(trustedClients.ips) == 0 {
		return true
	}
	for _, foundIP := range trustedClients.ips {
		if foundIP.Equal(ip) {
			return true
		}
	}
	return false
}

func acceptClient(conf Conf, conn net.Conn) {
	handleClientConnection(conf, conn)
	atomic.AddUint64(&clientsCount, ^uint64(0))
}

func maybeAcceptClient(conf Conf, conn net.Conn) {
	conn.SetDeadline(time.Now().Add(conf.Timeout))
	remoteIP := conn.RemoteAddr().(*net.TCPAddr).IP
	for {
		count := atomic.LoadUint64(&clientsCount)
		if count >= conf.MaxClients-conf.TrustedIPCount && isIPTrusted(conf, remoteIP) == false {
			conn.Close()
			return
		}
		if count >= conf.MaxClients {
			conn.Close()
			return
		} else if atomic.CompareAndSwapUint64(&clientsCount, count, count+1) {
			break
		}
	}
	go acceptClient(conf, conn)
}

// RunServer - run a server
func RunServer(conf Conf) {
	go handleSignals()
	listen, err := net.Listen("tcp", conf.Listen)
	if err != nil {
		log.Fatal(err)
	}
	defer listen.Close()
	for {
		conn, err := listen.Accept()
		if err != nil {
			log.Fatal(err)
		}
		maybeAcceptClient(conf, conn)
	}
}
<|endoftext|>


<commit_before>package streamtunnel

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"os"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/hashicorp/yamux"
	"github.com/koding/logging"
)

type Server struct {
	// pending contains the channel that is associated with each new tunnel request
	pending   map[string]chan net.Conn
	pendingMu sync.Mutex // protects the pending map

	// sessions contains a session per virtual host. Sessions provides
	// multiplexing over one connection
	sessions   map[string]*yamux.Session
	sessionsMu sync.Mutex // protects the sessions map

	// controls contains the control connection from the client to the server
	controls *controls

	// virtualHosts is used to map public hosts to remote clients
	virtualHosts vhostStorage

	// yamuxConfig is passed to new yamux.Session's
	yamuxConfig *yamux.Config

	log logging.Logger
}

// ServerConfig defines the configuration for the Server
type ServerConfig struct {
	// Debug enables debug mode, enable only if you want to debug the server
	Debug bool

	// Log defines the logger. If nil a default logging.Logger is used.
	Log logging.Logger

	// YamuxConfig defines the config which passed to every new yamux.Session. If nil
	// yamux.DefaultConfig() is used.
	YamuxConfig *yamux.Config
}

func NewServer(cfg *ServerConfig) *Server {
	yamuxConfig := yamux.DefaultConfig()
	if cfg.YamuxConfig != nil {
		if err := yamux.VerifyConfig(cfg.YamuxConfig); err != nil {
			fmt.Fprintf(os.Stderr, err.Error())
			os.Exit(1)
		}

		yamuxConfig = cfg.YamuxConfig
	}

	log := newLogger("streamtunnel-server", cfg.Debug)
	if cfg.Log != nil {
		log = cfg.Log
	}

	s := &Server{
		pending:      make(map[string]chan net.Conn),
		sessions:     make(map[string]*yamux.Session),
		virtualHosts: newVirtualHosts(),
		controls:     newControls(),
		yamuxConfig:  yamuxConfig,
		log:          log,
	}

	http.Handle(ControlPath, s.checkConnect(s.ControlHandler))
	return s
}

// ServeHTTP is a tunnel that creates an http/websocket tunnel between a
// public connection and the client connection.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// if the user didn't add the control and tunnel handler manually, we'll
	// going to infer and call the respective path handlers.
	switch path.Clean(r.URL.Path) + "/" {
	case ControlPath:
		s.checkConnect(s.ControlHandler).ServeHTTP(w, r)
		return
	}

	if err := s.HandleHTTP(w, r); err != nil {
		http.Error(w, err.Error(), 502)
		return
	}
}

func (s *Server) HandleHTTP(w http.ResponseWriter, r *http.Request) error {
	s.log.Debug("HandleHTTP request:")
	s.log.Debug("%v", r)

	host := strings.ToLower(r.Host)
	if host == "" {
		return errors.New("request host is empty")
	}

	// get the identifier associated with this host
	identifier, ok := s.GetIdentifier(host)
	if !ok {
		return fmt.Errorf("no virtual host available for %s", host)
	}

	// then grab the control connection that is associated with this identifier
	control, ok := s.getControl(identifier)
	if !ok {
		return fmt.Errorf("no control available for %s", host)
	}

	session, err := s.getSession(identifier)
	if err != nil {
		return err
	}

	// if someoone hits foo.example.com:8080, this should be proxied to
	// localhost:8080, so send the port to the client so it knows how to proxy
	// correctly. If no port is available, it's up to client how to intepret it
	_, port, _ := net.SplitHostPort(r.Host)
	msg := ControlMsg{
		Action:    RequestClientSession,
		Protocol:  HTTPTransport,
		LocalPort: port,
	}

	// ask client to open a session to us, so we can accept it
	if err := control.send(msg); err != nil {
		return err
	}

	var stream net.Conn
	defer func() {
		if stream != nil {
			stream.Close()
		}
	}()

	acceptStream := func() error {
		stream, err = session.Accept()
		return err
	}

	// if we don't receive anything from the client, we'll timeout
	select {
	case err := <-async(acceptStream):
		if err != nil {
			return err
		}
	case <-time.After(time.Second * 10):
		return errors.New("timeout getting session")
	}

	if err := r.Write(stream); err != nil {
		return err
	}

	resp, err := http.ReadResponse(bufio.NewReader(stream), r)
	if err != nil {
		if resp.Body == nil {
			resp.Body.Close()
		}
		return fmt.Errorf("read from tunnel: %s", err.Error())
	}
	defer resp.Body.Close()

	copyHeader(w.Header(), resp.Header)
	w.WriteHeader(resp.StatusCode)
	if _, err := io.Copy(w, resp.Body); err != nil {
		return err
	}

	return nil
}

// ControlHandler is used to capture incoming tunnel connect requests into raw
// tunnel TCP connections.
func (s *Server) ControlHandler(w http.ResponseWriter, r *http.Request) (ctErr error) {
	identifier := r.Header.Get(XKTunnelIdentifier)
	_, ok := s.GetHost(identifier)
	if !ok {
		return fmt.Errorf("no host associated for identifier %s. please use server.AddHost()", identifier)
	}

	ct, ok := s.getControl(identifier)
	if ok {
		ct.Close()
		s.log.Warning("Control connection for '%s' already exists. This is a race condition and needs to be fixed on client implementation", identifier)
		return fmt.Errorf("control conn for %s already exist. \n", identifier)
	}

	s.log.Debug("tunnel with identifier %s", identifier)
	hj, ok := w.(http.Hijacker)
	if !ok {
		return errors.New("webserver doesn't support hijacking")
	}

	conn, _, err := hj.Hijack()
	if err != nil {
		return fmt.Errorf("hijack not possible %s", err)
	}

	io.WriteString(conn, "HTTP/1.1 "+Connected+"\n\n")

	conn.SetDeadline(time.Time{})

	session, err := yamux.Server(conn, s.yamuxConfig)
	if err != nil {
		return err
	}
	s.addSession(identifier, session)

	var stream net.Conn

	// close and delete the session/stream if something goes wrong
	defer func() {
		if ctErr != nil {
			if stream != nil {
				stream.Close()
			}
			s.deleteSession(identifier)
		}
	}()

	acceptStream := func() error {
		stream, err = session.Accept()
		return err
	}

	// if we don't receive anything from the client, we'll timeout
	select {
	case err := <-async(acceptStream):
		if err != nil {
			return err
		}
	case <-time.After(time.Second * 10):
		return errors.New("timeout getting session")
	}

	buf := make([]byte, len(ctHandshakeRequest))
	if _, err := stream.Read(buf); err != nil {
		return err
	}

	if string(buf) != ctHandshakeRequest {
		return fmt.Errorf("handshake aborted. got: %s", string(buf))
	}

	if _, err := stream.Write([]byte(ctHandshakeResponse)); err != nil {
		return err
	}

	// setup control stream and start to listen to messages
	ct = newControl(stream)
	s.addControl(identifier, ct)
	go s.listenControl(ct)

	return nil
}

// listenControl listens to messages coming from the client.
func (s *Server) listenControl(ct *control) {
	for {
		var msg map[string]interface{}
		err := ct.dec.Decode(&msg)
		if err != nil {
			ct.Close()
			s.deleteControl(ct.identifier)
			s.deleteSession(ct.identifier)
			s.log.Error("decode err: %s", err)
			return
		}

		// right now we don't do anything with the messages, but because the
		// underlying connection needs to establihsed, we know when we have
		// disconnection(above), so we can cleanup the connection.
		s.log.Debug("msg: %s", msg)
	}
}

func (s *Server) AddHost(host, identifier string) {
	s.virtualHosts.AddHost(host, identifier)
}

func (s *Server) DeleteHost(host string) {
	s.virtualHosts.DeleteHost(host)
}

func (s *Server) GetIdentifier(host string) (string, bool) {
	identifier, ok := s.virtualHosts.GetIdentifier(host)
	return identifier, ok
}

func (s *Server) GetHost(identifier string) (string, bool) {
	host, ok := s.virtualHosts.GetHost(identifier)
	return host, ok
}

func (s *Server) addControl(identifier string, conn *control) {
	s.controls.addControl(identifier, conn)
}

func (s *Server) getControl(identifier string) (*control, bool) {
	return s.controls.getControl(identifier)
}

func (s *Server) deleteControl(identifier string) {
	s.controls.deleteControl(identifier)
}

func (s *Server) getSession(identifier string) (*yamux.Session, error) {
	s.sessionsMu.Lock()
	session, ok := s.sessions[identifier]
	s.sessionsMu.Unlock()

	if !ok {
		return nil, fmt.Errorf("no session available for identifier: '%s'", identifier)
	}

	return session, nil
}

func (s *Server) addSession(identifier string, session *yamux.Session) {
	s.sessionsMu.Lock()
	s.sessions[identifier] = session
	s.sessionsMu.Unlock()
}

func (s *Server) deleteSession(identifier string) {
	s.sessionsMu.Lock()
	session, ok := s.sessions[identifier]
	s.sessionsMu.Unlock()

	if !ok {
		return // nothing to delete
	}

	if session != nil {
		session.GoAway() // don't accept any new connection
		session.Close()
	}

	delete(s.sessions, identifier)
}

func copyHeader(dst, src http.Header) {
	for k, vv := range src {
		for _, v := range vv {
			dst.Add(k, v)
		}
	}
}

// checkConnect checks wether the incoming request is HTTP CONNECT method. If
func (s *Server) checkConnect(fn func(w http.ResponseWriter, r *http.Request) error) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "CONNECT" {
			w.Header().Set("Content-Type", "text/plain; charset=utf-8")
			w.WriteHeader(http.StatusMethodNotAllowed)
			io.WriteString(w, "405 must CONNECT\n")
			return
		}

		err := fn(w, r)
		if err != nil {
			s.log.Error("Handler err: %v", err.Error())
			http.Error(w, err.Error(), 502)
		}
	})
}

func newLogger(name string, debug bool) logging.Logger {
	log := logging.NewLogger(name)
	logHandler := logging.NewWriterHandler(os.Stderr)
	logHandler.Colorize = true
	log.SetHandler(logHandler)

	if debug {
		log.SetLevel(logging.DEBUG)
		logHandler.SetLevel(logging.DEBUG)
	}

	return log
}
<commit_msg>tunnelserver: add OnDisconnect function feature<commit_after>package streamtunnel

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"os"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/hashicorp/yamux"
	"github.com/koding/logging"
)

type Server struct {
	// pending contains the channel that is associated with each new tunnel request
	pending   map[string]chan net.Conn
	pendingMu sync.Mutex // protects the pending map

	// sessions contains a session per virtual host. Sessions provides
	// multiplexing over one connection
	sessions   map[string]*yamux.Session
	sessionsMu sync.Mutex // protects the sessions map

	// controls contains the control connection from the client to the server
	controls *controls

	// virtualHosts is used to map public hosts to remote clients
	virtualHosts vhostStorage

	// onDisconnect contains the onDisconnect for each map
	onDisconnect   map[string]func() error
	onDisconnectMu sync.Mutex // protects onDisconnects

	// yamuxConfig is passed to new yamux.Session's
	yamuxConfig *yamux.Config

	log logging.Logger
}

// ServerConfig defines the configuration for the Server
type ServerConfig struct {
	// Debug enables debug mode, enable only if you want to debug the server
	Debug bool

	// Log defines the logger. If nil a default logging.Logger is used.
	Log logging.Logger

	// YamuxConfig defines the config which passed to every new yamux.Session. If nil
	// yamux.DefaultConfig() is used.
	YamuxConfig *yamux.Config
}

func NewServer(cfg *ServerConfig) *Server {
	yamuxConfig := yamux.DefaultConfig()
	if cfg.YamuxConfig != nil {
		if err := yamux.VerifyConfig(cfg.YamuxConfig); err != nil {
			fmt.Fprintf(os.Stderr, err.Error())
			os.Exit(1)
		}

		yamuxConfig = cfg.YamuxConfig
	}

	log := newLogger("streamtunnel-server", cfg.Debug)
	if cfg.Log != nil {
		log = cfg.Log
	}

	s := &Server{
		pending:      make(map[string]chan net.Conn),
		sessions:     make(map[string]*yamux.Session),
		onDisconnect: make(map[string]func() error),
		virtualHosts: newVirtualHosts(),
		controls:     newControls(),
		yamuxConfig:  yamuxConfig,
		log:          log,
	}

	http.Handle(ControlPath, s.checkConnect(s.ControlHandler))
	return s
}

// ServeHTTP is a tunnel that creates an http/websocket tunnel between a
// public connection and the client connection.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// if the user didn't add the control and tunnel handler manually, we'll
	// going to infer and call the respective path handlers.
	switch path.Clean(r.URL.Path) + "/" {
	case ControlPath:
		s.checkConnect(s.ControlHandler).ServeHTTP(w, r)
		return
	}

	if err := s.HandleHTTP(w, r); err != nil {
		http.Error(w, err.Error(), 502)
		return
	}
}

func (s *Server) HandleHTTP(w http.ResponseWriter, r *http.Request) error {
	s.log.Debug("HandleHTTP request:")
	s.log.Debug("%v", r)

	host := strings.ToLower(r.Host)
	if host == "" {
		return errors.New("request host is empty")
	}

	// get the identifier associated with this host
	identifier, ok := s.GetIdentifier(host)
	if !ok {
		return fmt.Errorf("no virtual host available for %s", host)
	}

	// then grab the control connection that is associated with this identifier
	control, ok := s.getControl(identifier)
	if !ok {
		return fmt.Errorf("no control available for %s", host)
	}

	session, err := s.getSession(identifier)
	if err != nil {
		return err
	}

	// if someoone hits foo.example.com:8080, this should be proxied to
	// localhost:8080, so send the port to the client so it knows how to proxy
	// correctly. If no port is available, it's up to client how to intepret it
	_, port, _ := net.SplitHostPort(r.Host)
	msg := ControlMsg{
		Action:    RequestClientSession,
		Protocol:  HTTPTransport,
		LocalPort: port,
	}

	// ask client to open a session to us, so we can accept it
	if err := control.send(msg); err != nil {
		return err
	}

	var stream net.Conn
	defer func() {
		if stream != nil {
			stream.Close()
		}
	}()

	acceptStream := func() error {
		stream, err = session.Accept()
		return err
	}

	// if we don't receive anything from the client, we'll timeout
	select {
	case err := <-async(acceptStream):
		if err != nil {
			return err
		}
	case <-time.After(time.Second * 10):
		return errors.New("timeout getting session")
	}

	if err := r.Write(stream); err != nil {
		return err
	}

	resp, err := http.ReadResponse(bufio.NewReader(stream), r)
	if err != nil {
		if resp.Body == nil {
			resp.Body.Close()
		}
		return fmt.Errorf("read from tunnel: %s", err.Error())
	}
	defer resp.Body.Close()

	copyHeader(w.Header(), resp.Header)
	w.WriteHeader(resp.StatusCode)
	if _, err := io.Copy(w, resp.Body); err != nil {
		return err
	}

	return nil
}

// ControlHandler is used to capture incoming tunnel connect requests into raw
// tunnel TCP connections.
func (s *Server) ControlHandler(w http.ResponseWriter, r *http.Request) (ctErr error) {
	identifier := r.Header.Get(XKTunnelIdentifier)
	_, ok := s.GetHost(identifier)
	if !ok {
		return fmt.Errorf("no host associated for identifier %s. please use server.AddHost()", identifier)
	}

	ct, ok := s.getControl(identifier)
	if ok {
		ct.Close()
		s.log.Warning("Control connection for '%s' already exists. This is a race condition and needs to be fixed on client implementation", identifier)
		return fmt.Errorf("control conn for %s already exist. \n", identifier)
	}

	s.log.Debug("tunnel with identifier %s", identifier)
	hj, ok := w.(http.Hijacker)
	if !ok {
		return errors.New("webserver doesn't support hijacking")
	}

	conn, _, err := hj.Hijack()
	if err != nil {
		return fmt.Errorf("hijack not possible %s", err)
	}

	io.WriteString(conn, "HTTP/1.1 "+Connected+"\n\n")

	conn.SetDeadline(time.Time{})

	session, err := yamux.Server(conn, s.yamuxConfig)
	if err != nil {
		return err
	}
	s.addSession(identifier, session)

	var stream net.Conn

	// close and delete the session/stream if something goes wrong
	defer func() {
		if ctErr != nil {
			if stream != nil {
				stream.Close()
			}
			s.deleteSession(identifier)
		}
	}()

	acceptStream := func() error {
		stream, err = session.Accept()
		return err
	}

	// if we don't receive anything from the client, we'll timeout
	select {
	case err := <-async(acceptStream):
		if err != nil {
			return err
		}
	case <-time.After(time.Second * 10):
		return errors.New("timeout getting session")
	}

	buf := make([]byte, len(ctHandshakeRequest))
	if _, err := stream.Read(buf); err != nil {
		return err
	}

	if string(buf) != ctHandshakeRequest {
		return fmt.Errorf("handshake aborted. got: %s", string(buf))
	}

	if _, err := stream.Write([]byte(ctHandshakeResponse)); err != nil {
		return err
	}

	// setup control stream and start to listen to messages
	ct = newControl(stream)
	s.addControl(identifier, ct)
	go s.listenControl(ct)

	return nil
}

// listenControl listens to messages coming from the client.
func (s *Server) listenControl(ct *control) {
	for {
		var msg map[string]interface{}
		err := ct.dec.Decode(&msg)
		if err != nil {
			ct.Close()
			s.deleteControl(ct.identifier)
			s.deleteSession(ct.identifier)
			if err := s.callOnDisconect(ct.identifier); err != nil {
				s.log.Error("onDisconnect (%s) err: %s", ct.identifier, err)
			}

			s.log.Error("decode err: %s", err)
			return
		}

		// right now we don't do anything with the messages, but because the
		// underlying connection needs to establihsed, we know when we have
		// disconnection(above), so we can cleanup the connection.
		s.log.Debug("msg: %s", msg)
	}
}

// OnDisconnect calls the function when the client connected with the
// associated identifier disconnects from the server.
func (s *Server) OnDisconnect(identifier string, fn func() error) {
	s.onDisconnectMu.Lock()
	s.onDisconnect[identifier] = fn
	s.onDisconnectMu.Unlock()
}

func (s *Server) callOnDisconect(identifier string) error {
	s.onDisconnectMu.Lock()
	fn, ok := s.onDisconnect[identifier]
	s.onDisconnectMu.Unlock()

	if !ok {
		return fmt.Errorf("no onDisconncet function available for '%s'", identifier)
	}

	return fn()
}

func (s *Server) AddHost(host, identifier string) {
	s.virtualHosts.AddHost(host, identifier)
}

func (s *Server) DeleteHost(host string) {
	s.virtualHosts.DeleteHost(host)
}

func (s *Server) GetIdentifier(host string) (string, bool) {
	identifier, ok := s.virtualHosts.GetIdentifier(host)
	return identifier, ok
}

func (s *Server) GetHost(identifier string) (string, bool) {
	host, ok := s.virtualHosts.GetHost(identifier)
	return host, ok
}

func (s *Server) addControl(identifier string, conn *control) {
	s.controls.addControl(identifier, conn)
}

func (s *Server) getControl(identifier string) (*control, bool) {
	return s.controls.getControl(identifier)
}

func (s *Server) deleteControl(identifier string) {
	s.controls.deleteControl(identifier)
}

func (s *Server) getSession(identifier string) (*yamux.Session, error) {
	s.sessionsMu.Lock()
	session, ok := s.sessions[identifier]
	s.sessionsMu.Unlock()

	if !ok {
		return nil, fmt.Errorf("no session available for identifier: '%s'", identifier)
	}

	return session, nil
}

func (s *Server) addSession(identifier string, session *yamux.Session) {
	s.sessionsMu.Lock()
	s.sessions[identifier] = session
	s.sessionsMu.Unlock()
}

func (s *Server) deleteSession(identifier string) {
	s.sessionsMu.Lock()
	session, ok := s.sessions[identifier]
	s.sessionsMu.Unlock()

	if !ok {
		return // nothing to delete
	}

	if session != nil {
		session.GoAway() // don't accept any new connection
		session.Close()
	}

	delete(s.sessions, identifier)
}

func copyHeader(dst, src http.Header) {
	for k, vv := range src {
		for _, v := range vv {
			dst.Add(k, v)
		}
	}
}

// checkConnect checks wether the incoming request is HTTP CONNECT method. If
func (s *Server) checkConnect(fn func(w http.ResponseWriter, r *http.Request) error) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "CONNECT" {
			w.Header().Set("Content-Type", "text/plain; charset=utf-8")
			w.WriteHeader(http.StatusMethodNotAllowed)
			io.WriteString(w, "405 must CONNECT\n")
			return
		}

		err := fn(w, r)
		if err != nil {
			s.log.Error("Handler err: %v", err.Error())
			http.Error(w, err.Error(), 502)
		}
	})
}

func newLogger(name string, debug bool) logging.Logger {
	log := logging.NewLogger(name)
	logHandler := logging.NewWriterHandler(os.Stderr)
	logHandler.Colorize = true
	log.SetHandler(logHandler)

	if debug {
		log.SetLevel(logging.DEBUG)
		logHandler.SetLevel(logging.DEBUG)
	}

	return log
}
<|endoftext|>


<commit_before>package devd

import (
	"crypto/tls"
	"fmt"
	"html/template"
	"net"
	"net/http"
	"regexp"
	"strings"
	"time"

	"golang.org/x/net/context"

	"github.com/GeertJohan/go.rice"
	"github.com/goji/httpauth"

	"github.com/cortesi/devd/httpctx"
	"github.com/cortesi/devd/inject"
	"github.com/cortesi/devd/livereload"
	"github.com/cortesi/devd/ricetemp"
	"github.com/cortesi/devd/slowdown"
	"github.com/cortesi/devd/timer"
	"github.com/cortesi/termlog"
)

const (
	// Version is the current version of devd
	Version  = "0.4"
	portLow  = 8000
	portHigh = 10000
)

func pickPort(addr string, low int, high int, tls bool) (net.Listener, error) {
	firstTry := 80
	if tls {
		firstTry = 443
	}
	hl, err := net.Listen("tcp", fmt.Sprintf("%v:%d", addr, firstTry))
	if err == nil {
		return hl, nil
	}
	for i := low; i < high; i++ {
		hl, err := net.Listen("tcp", fmt.Sprintf("%v:%d", addr, i))
		if err == nil {
			return hl, nil
		}
	}
	return nil, fmt.Errorf("Could not find open port.")
}

func getTLSConfig(path string) (t *tls.Config, err error) {
	config := &tls.Config{}
	if config.NextProtos == nil {
		config.NextProtos = []string{"http/1.1"}
	}
	config.Certificates = make([]tls.Certificate, 1)
	config.Certificates[0], err = tls.LoadX509KeyPair(path, path)
	if err != nil {
		return nil, err
	}
	return config, nil
}

// This filthy hack works in conjunction with hostPortStrip to restore the
// original request host after mux match.
func revertOriginalHost(r *http.Request) {
	original := r.Header.Get("_devd_original_host")
	if original != "" {
		r.Host = original
		r.Header.Del("_devd_original_host")
	}
}

// We can remove the mangling once this is fixed:
// 		https://github.com/golang/go/issues/10463
func hostPortStrip(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		host, _, err := net.SplitHostPort(r.Host)
		if err == nil {
			original := r.Host
			r.Host = host
			r.Header.Set("_devd_original_host", original)
		}
		next.ServeHTTP(w, r)
	})
}

func matchStringAny(regexps []*regexp.Regexp, s string) bool {
	for _, r := range regexps {
		if r.MatchString(s) {
			return true
		}
	}
	return false
}

func formatURL(tls bool, httpIP string, port int) string {
	proto := "http"
	if tls {
		proto = "https"
	}
	host := httpIP
	if httpIP == "0.0.0.0" || httpIP == "127.0.0.1" {
		host = "devd.io"
	}
	if port == 443 && tls {
		return fmt.Sprintf("https://%s", host)
	}
	if port == 80 && !tls {
		return fmt.Sprintf("http://%s", host)
	}
	return fmt.Sprintf("%s://%s:%d", proto, host, port)
}

// Credentials is a simple username/password pair
type Credentials struct {
	username string
	password string
}

// CredentialsFromSpec creates a set of credentials from a spec
func CredentialsFromSpec(spec string) (*Credentials, error) {
	parts := strings.SplitN(spec, ":", 2)
	if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
		return nil, fmt.Errorf("Invalid credential spec: %s", spec)
	}
	return &Credentials{parts[0], parts[1]}, nil
}

// Devd represents the devd server options
type Devd struct {
	Routes RouteCollection

	// Shaping
	Latency  int
	DownKbps uint
	UpKbps   uint

	// Livereload
	LivereloadRoutes bool
	WatchPaths       []string
	Excludes         []string

	// Logging
	IgnoreLogs []*regexp.Regexp

	// Password protection
	Credentials *Credentials
}

// WrapHandler wraps an httpctx.Handler in the paraphernalia needed by devd for
// logging, latency, and so forth.
func (dd *Devd) WrapHandler(log termlog.Logger, next httpctx.Handler) http.Handler {
	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		revertOriginalHost(r)
		timr := timer.Timer{}
		sublog := log.Group()
		defer func() {
			timing := termlog.DefaultPalette.Timestamp.SprintFunc()("timing: ")
			sublog.SayAs("timer", timing+timr.String())
			sublog.Done()
		}()
		if matchStringAny(dd.IgnoreLogs, fmt.Sprintf("%s%s", r.URL.Host, r.RequestURI)) {
			sublog.Quiet()
		}
		timr.RequestHeaders()
		time.Sleep(time.Millisecond * time.Duration(dd.Latency))
		sublog.Say("%s %s", r.Method, r.URL)
		LogHeader(sublog, r.Header)
		ctx := timr.NewContext(context.Background())
		ctx = termlog.NewContext(ctx, sublog)
		next.ServeHTTPContext(
			ctx,
			&ResponseLogWriter{Log: sublog, Resp: w, Timer: &timr},
			r,
		)
	})
	return h
}

// HasLivereload tells us if liverload is enabled
func (dd *Devd) HasLivereload() bool {
	if dd.LivereloadRoutes || len(dd.WatchPaths) > 0 {
		return true
	}
	return false
}

// AddRoutes adds route specifications to the server
func (dd *Devd) AddRoutes(specs []string) error {
	dd.Routes = make(RouteCollection)
	for _, s := range specs {
		err := dd.Routes.Add(s)
		if err != nil {
			return fmt.Errorf("Invalid route specification: %s", err)
		}
	}
	return nil
}

// AddIgnores adds log ignore patterns to the server
func (dd *Devd) AddIgnores(specs []string) error {
	dd.IgnoreLogs = make([]*regexp.Regexp, 0, 0)
	for _, expr := range specs {
		v, err := regexp.Compile(expr)
		if err != nil {
			return fmt.Errorf("%s", err)
		}
		dd.IgnoreLogs = append(dd.IgnoreLogs, v)
	}
	return nil
}

// HandleNotFound handles pages not found. In particular, this handler is used
// when we have no matching route for a request. This also means it's not
// useful to inject the livereload paraphernalia here.
func HandleNotFound(templates *template.Template) httpctx.Handler {
	return httpctx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusNotFound)
		err := templates.Lookup("404.html").Execute(w, nil)
		if err != nil {
			logger := termlog.FromContext(ctx)
			logger.Shout("Could not execute template: %s", err)
		}
	})
}

// Router constructs the main Devd router that serves all requests
func (dd *Devd) Router(logger termlog.Logger, templates *template.Template) (http.Handler, error) {
	mux := http.NewServeMux()
	hasGlobal := false

	ci := inject.CopyInject{}
	if dd.HasLivereload() {
		ci = livereload.Injector
	}

	for match, route := range dd.Routes {
		if match == "/" {
			hasGlobal = true
		}
		handler := dd.WrapHandler(
			logger,
			route.Endpoint.Handler(templates, ci),
		)
		handler = http.StripPrefix(route.Path, handler)
		mux.Handle(match, handler)
	}
	if dd.HasLivereload() {
		lr := livereload.NewServer("livereload", logger)
		mux.Handle(livereload.EndpointPath, lr)
		mux.Handle(livereload.ScriptPath, http.HandlerFunc(lr.ServeScript))
		if dd.LivereloadRoutes {
			err := WatchRoutes(dd.Routes, lr, dd.Excludes, logger)
			if err != nil {
				return nil, fmt.Errorf("Could not watch routes for livereload: %s", err)
			}
		}
		if len(dd.WatchPaths) > 0 {
			err := WatchPaths(dd.WatchPaths, dd.Excludes, lr, logger)
			if err != nil {
				return nil, fmt.Errorf("Could not watch path for livereload: %s", err)
			}
		}
	}
	if !hasGlobal {
		mux.Handle(
			"/",
			dd.WrapHandler(logger, HandleNotFound(templates)),
		)
	}
	var h = http.Handler(mux)
	if dd.Credentials != nil {
		h = httpauth.SimpleBasicAuth(
			dd.Credentials.username, dd.Credentials.password,
		)(h)
	}
	return hostPortStrip(h), nil
}

// Serve starts the devd server. The callback is called with the serving URL
// just before service starts.
func (dd *Devd) Serve(address string, port int, certFile string, logger termlog.Logger, callback func(string)) error {
	templates, err := ricetemp.MakeTemplates(rice.MustFindBox("templates"))
	if err != nil {
		return fmt.Errorf("Error loading templates: %s", err)
	}
	mux, err := dd.Router(logger, templates)
	if err != nil {
		return err
	}
	var tlsConfig *tls.Config
	var tlsEnabled bool
	if certFile != "" {
		tlsConfig, err = getTLSConfig(certFile)
		if err != nil {
			return fmt.Errorf("Could not load certs: %s", err)
		}
		tlsEnabled = true
	}

	var hl net.Listener
	if port > 0 {
		hl, err = net.Listen("tcp", fmt.Sprintf("%v:%d", address, port))
	} else {
		hl, err = pickPort(address, portLow, portHigh, tlsEnabled)
	}
	if err != nil {
		return fmt.Errorf("Could not bind to port: %s", err)
	}

	if tlsConfig != nil {
		hl = tls.NewListener(hl, tlsConfig)
	}

	hl = slowdown.NewSlowListener(hl, dd.UpKbps*1024, dd.DownKbps*1024)
	url := formatURL(tlsEnabled, address, hl.Addr().(*net.TCPAddr).Port)
	logger.Say("Listening on %s (%s)", url, hl.Addr().String())
	server := &http.Server{Addr: hl.Addr().String(), Handler: mux}
	callback(url)
	err = server.Serve(hl)
	logger.Shout("Server stopped: %v", err)
	return nil
}
<commit_msg>Small tweak to make error less stuttery<commit_after>package devd

import (
	"crypto/tls"
	"fmt"
	"html/template"
	"net"
	"net/http"
	"regexp"
	"strings"
	"time"

	"golang.org/x/net/context"

	"github.com/GeertJohan/go.rice"
	"github.com/goji/httpauth"

	"github.com/cortesi/devd/httpctx"
	"github.com/cortesi/devd/inject"
	"github.com/cortesi/devd/livereload"
	"github.com/cortesi/devd/ricetemp"
	"github.com/cortesi/devd/slowdown"
	"github.com/cortesi/devd/timer"
	"github.com/cortesi/termlog"
)

const (
	// Version is the current version of devd
	Version  = "0.4"
	portLow  = 8000
	portHigh = 10000
)

func pickPort(addr string, low int, high int, tls bool) (net.Listener, error) {
	firstTry := 80
	if tls {
		firstTry = 443
	}
	hl, err := net.Listen("tcp", fmt.Sprintf("%v:%d", addr, firstTry))
	if err == nil {
		return hl, nil
	}
	for i := low; i < high; i++ {
		hl, err := net.Listen("tcp", fmt.Sprintf("%v:%d", addr, i))
		if err == nil {
			return hl, nil
		}
	}
	return nil, fmt.Errorf("Could not find open port.")
}

func getTLSConfig(path string) (t *tls.Config, err error) {
	config := &tls.Config{}
	if config.NextProtos == nil {
		config.NextProtos = []string{"http/1.1"}
	}
	config.Certificates = make([]tls.Certificate, 1)
	config.Certificates[0], err = tls.LoadX509KeyPair(path, path)
	if err != nil {
		return nil, err
	}
	return config, nil
}

// This filthy hack works in conjunction with hostPortStrip to restore the
// original request host after mux match.
func revertOriginalHost(r *http.Request) {
	original := r.Header.Get("_devd_original_host")
	if original != "" {
		r.Host = original
		r.Header.Del("_devd_original_host")
	}
}

// We can remove the mangling once this is fixed:
// 		https://github.com/golang/go/issues/10463
func hostPortStrip(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		host, _, err := net.SplitHostPort(r.Host)
		if err == nil {
			original := r.Host
			r.Host = host
			r.Header.Set("_devd_original_host", original)
		}
		next.ServeHTTP(w, r)
	})
}

func matchStringAny(regexps []*regexp.Regexp, s string) bool {
	for _, r := range regexps {
		if r.MatchString(s) {
			return true
		}
	}
	return false
}

func formatURL(tls bool, httpIP string, port int) string {
	proto := "http"
	if tls {
		proto = "https"
	}
	host := httpIP
	if httpIP == "0.0.0.0" || httpIP == "127.0.0.1" {
		host = "devd.io"
	}
	if port == 443 && tls {
		return fmt.Sprintf("https://%s", host)
	}
	if port == 80 && !tls {
		return fmt.Sprintf("http://%s", host)
	}
	return fmt.Sprintf("%s://%s:%d", proto, host, port)
}

// Credentials is a simple username/password pair
type Credentials struct {
	username string
	password string
}

// CredentialsFromSpec creates a set of credentials from a spec
func CredentialsFromSpec(spec string) (*Credentials, error) {
	parts := strings.SplitN(spec, ":", 2)
	if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
		return nil, fmt.Errorf("Invalid credential spec: %s", spec)
	}
	return &Credentials{parts[0], parts[1]}, nil
}

// Devd represents the devd server options
type Devd struct {
	Routes RouteCollection

	// Shaping
	Latency  int
	DownKbps uint
	UpKbps   uint

	// Livereload
	LivereloadRoutes bool
	WatchPaths       []string
	Excludes         []string

	// Logging
	IgnoreLogs []*regexp.Regexp

	// Password protection
	Credentials *Credentials
}

// WrapHandler wraps an httpctx.Handler in the paraphernalia needed by devd for
// logging, latency, and so forth.
func (dd *Devd) WrapHandler(log termlog.Logger, next httpctx.Handler) http.Handler {
	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		revertOriginalHost(r)
		timr := timer.Timer{}
		sublog := log.Group()
		defer func() {
			timing := termlog.DefaultPalette.Timestamp.SprintFunc()("timing: ")
			sublog.SayAs("timer", timing+timr.String())
			sublog.Done()
		}()
		if matchStringAny(dd.IgnoreLogs, fmt.Sprintf("%s%s", r.URL.Host, r.RequestURI)) {
			sublog.Quiet()
		}
		timr.RequestHeaders()
		time.Sleep(time.Millisecond * time.Duration(dd.Latency))
		sublog.Say("%s %s", r.Method, r.URL)
		LogHeader(sublog, r.Header)
		ctx := timr.NewContext(context.Background())
		ctx = termlog.NewContext(ctx, sublog)
		next.ServeHTTPContext(
			ctx,
			&ResponseLogWriter{Log: sublog, Resp: w, Timer: &timr},
			r,
		)
	})
	return h
}

// HasLivereload tells us if liverload is enabled
func (dd *Devd) HasLivereload() bool {
	if dd.LivereloadRoutes || len(dd.WatchPaths) > 0 {
		return true
	}
	return false
}

// AddRoutes adds route specifications to the server
func (dd *Devd) AddRoutes(specs []string) error {
	dd.Routes = make(RouteCollection)
	for _, s := range specs {
		err := dd.Routes.Add(s)
		if err != nil {
			return fmt.Errorf("Invalid route specification: %s", err)
		}
	}
	return nil
}

// AddIgnores adds log ignore patterns to the server
func (dd *Devd) AddIgnores(specs []string) error {
	dd.IgnoreLogs = make([]*regexp.Regexp, 0, 0)
	for _, expr := range specs {
		v, err := regexp.Compile(expr)
		if err != nil {
			return fmt.Errorf("%s", err)
		}
		dd.IgnoreLogs = append(dd.IgnoreLogs, v)
	}
	return nil
}

// HandleNotFound handles pages not found. In particular, this handler is used
// when we have no matching route for a request. This also means it's not
// useful to inject the livereload paraphernalia here.
func HandleNotFound(templates *template.Template) httpctx.Handler {
	return httpctx.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusNotFound)
		err := templates.Lookup("404.html").Execute(w, nil)
		if err != nil {
			logger := termlog.FromContext(ctx)
			logger.Shout("Could not execute template: %s", err)
		}
	})
}

// Router constructs the main Devd router that serves all requests
func (dd *Devd) Router(logger termlog.Logger, templates *template.Template) (http.Handler, error) {
	mux := http.NewServeMux()
	hasGlobal := false

	ci := inject.CopyInject{}
	if dd.HasLivereload() {
		ci = livereload.Injector
	}

	for match, route := range dd.Routes {
		if match == "/" {
			hasGlobal = true
		}
		handler := dd.WrapHandler(
			logger,
			route.Endpoint.Handler(templates, ci),
		)
		handler = http.StripPrefix(route.Path, handler)
		mux.Handle(match, handler)
	}
	if dd.HasLivereload() {
		lr := livereload.NewServer("livereload", logger)
		mux.Handle(livereload.EndpointPath, lr)
		mux.Handle(livereload.ScriptPath, http.HandlerFunc(lr.ServeScript))
		if dd.LivereloadRoutes {
			err := WatchRoutes(dd.Routes, lr, dd.Excludes, logger)
			if err != nil {
				return nil, fmt.Errorf("Could not watch routes for livereload: %s", err)
			}
		}
		if len(dd.WatchPaths) > 0 {
			err := WatchPaths(dd.WatchPaths, dd.Excludes, lr, logger)
			if err != nil {
				return nil, fmt.Errorf("Could not watch path for livereload: %s", err)
			}
		}
	}
	if !hasGlobal {
		mux.Handle(
			"/",
			dd.WrapHandler(logger, HandleNotFound(templates)),
		)
	}
	var h = http.Handler(mux)
	if dd.Credentials != nil {
		h = httpauth.SimpleBasicAuth(
			dd.Credentials.username, dd.Credentials.password,
		)(h)
	}
	return hostPortStrip(h), nil
}

// Serve starts the devd server. The callback is called with the serving URL
// just before service starts.
func (dd *Devd) Serve(address string, port int, certFile string, logger termlog.Logger, callback func(string)) error {
	templates, err := ricetemp.MakeTemplates(rice.MustFindBox("templates"))
	if err != nil {
		return fmt.Errorf("Error loading templates: %s", err)
	}
	mux, err := dd.Router(logger, templates)
	if err != nil {
		return err
	}
	var tlsConfig *tls.Config
	var tlsEnabled bool
	if certFile != "" {
		tlsConfig, err = getTLSConfig(certFile)
		if err != nil {
			return fmt.Errorf("Could not load certs: %s", err)
		}
		tlsEnabled = true
	}

	var hl net.Listener
	if port > 0 {
		hl, err = net.Listen("tcp", fmt.Sprintf("%v:%d", address, port))
	} else {
		hl, err = pickPort(address, portLow, portHigh, tlsEnabled)
	}
	if err != nil {
		return err
	}

	if tlsConfig != nil {
		hl = tls.NewListener(hl, tlsConfig)
	}

	hl = slowdown.NewSlowListener(hl, dd.UpKbps*1024, dd.DownKbps*1024)
	url := formatURL(tlsEnabled, address, hl.Addr().(*net.TCPAddr).Port)
	logger.Say("Listening on %s (%s)", url, hl.Addr().String())
	server := &http.Server{Addr: hl.Addr().String(), Handler: mux}
	callback(url)
	err = server.Serve(hl)
	logger.Shout("Server stopped: %v", err)
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"

	"github.com/intervention-engine/fhir/server"
)

func main() {
	reqLog := flag.Bool("reqlog", false, "Enables request logging -- do NOT use in production")
	dbName := flag.String("dbname", "fhir", "Mongo database name")
	idxConfigPath := flag.String("idxconfig", "config/indexes.conf", "Path to the indexes config file")

	flag.Parse()
	s := server.NewServer("localhost")
	if *reqLog {
		s.Engine.Use(server.RequestLoggerHandler)
	}

	config := server.DefaultConfig

	if *dbName != "" {
		config.DatabaseName = *dbName
	}

	if *idxConfigPath != "" {
		config.IndexConfigPath = *idxConfigPath
	}

	s.Run(config)
}
<commit_msg>Remove server.go (again)<commit_after><|endoftext|>


<commit_before>package kraken

import (
	"errors"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/vincent-petithory/kraken/fileserver"
)

type MountMap struct {
	m   map[string]fileserver.Server
	mu  sync.RWMutex
	fsf fileserver.Factory
}

func (mm *MountMap) Targets() []string {
	mm.mu.RLock()
	defer mm.mu.RUnlock()
	mountTargets := make([]string, 0, len(mm.m))
	for mountTarget := range mm.m {
		mountTargets = append(mountTargets, mountTarget)
	}
	return mountTargets
}

// Get retrieves the source for the given mount target.
// It returns "" if the mount target doesn't exist.
func (mm *MountMap) GetSource(mountTarget string) string {
	mm.mu.RLock()
	defer mm.mu.RUnlock()
	fs, ok := mm.m[mountTarget]
	if !ok {
		return ""
	}
	return fs.Root()
}

var (
	// ErrInvalidMountTarget describes an invalid value for a mount target.
	ErrInvalidMountTarget = errors.New("invalid mount target value")
	// ErrInvalidMountSource describes an invalid value for a mount source.
	ErrInvalidMountSource = errors.New("invalid mount source value")
)

type MountSourcePermError struct {
	err error
}

func (e *MountSourcePermError) Error() string {
	return e.err.Error()
}

// Put registers a mount target for the given mount source.
// It returns true if the mount target already exists.
func (mm *MountMap) Put(mountTarget string, mountSource string, fsType string, fsParams fileserver.Params) (bool, error) {
	mm.mu.Lock()
	defer mm.mu.Unlock()

	// mountTarget must start with /
	if !strings.HasPrefix(mountTarget, "/") {
		return false, ErrInvalidMountTarget
	}
	// if mountTarget is not "/" and has a trailing /, reject it
	if mountTarget != "/" && strings.HasSuffix(mountTarget, "/") {
		return false, ErrInvalidMountTarget
	}

	if !path.IsAbs(mountSource) {
		return false, ErrInvalidMountSource
	}

	fi, err := os.Stat(mountSource)
	if err != nil {
		return false, &MountSourcePermError{err}
	}
	if !fi.IsDir() {
		return false, &MountSourcePermError{fmt.Errorf("%s: not a directory", mountSource)}
	}

	_, ok := mm.m[mountTarget]

	fs := mm.fsf.New(mountSource, fsType, fsParams)
	mm.m[mountTarget] = fs
	return ok, nil
}

// Delete removes an existing mount target.
// It returns true if the mount target existed.
func (mm *MountMap) DeleteTarget(mountTarget string) bool {
	mm.mu.RLock()
	defer mm.mu.RUnlock()
	_, ok := mm.m[mountTarget]
	delete(mm.m, mountTarget)
	return ok
}

func (mm *MountMap) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var (
		maxMountTargetLen int
		mountTarget       string
	)
	mm.mu.RLock()
	for t := range mm.m {
		if strings.HasPrefix(r.URL.Path, t) && len(t) >= maxMountTargetLen {
			maxMountTargetLen = len(t)
			mountTarget = t
		}
	}
	if maxMountTargetLen == 0 {
		http.Error(w, fmt.Sprintf("%s: mount target or file not found", r.URL.Path), http.StatusNotFound)
		mm.mu.RUnlock()
		return
	}

	if mountTarget != "/" {
		r.URL.Path = r.URL.Path[maxMountTargetLen:]
		if r.URL.Path == "" {
			http.Redirect(w, r, mountTarget+"/", http.StatusMovedPermanently)
			return
		}
	}
	fs, ok := mm.m[mountTarget]
	mm.mu.RUnlock()
	if !ok {
		http.Error(w, fmt.Sprintf("mount target %q not found", mountTarget), http.StatusNotFound)
		return
	}
	fs.ServeHTTP(w, r)
}

func NewMountMap(fsf fileserver.Factory) *MountMap {
	return &MountMap{
		m:   make(map[string]fileserver.Server),
		fsf: fsf,
	}
}

type Server struct {
	MountMap       *MountMap
	HandlerWrapper func(http.Handler) http.Handler
	Addr           string
	Port           uint16
	Started        chan struct{}
	Running        bool
	srv            *http.Server
	ln             net.Listener
}

func NewServer(addr string, fsf fileserver.Factory) *Server {
	return &Server{
		MountMap: NewMountMap(fsf),
		Addr:     addr,
		Started:  make(chan struct{}),
	}
}

func (s *Server) ListenAndServe() error {
	ln, err := net.Listen("tcp", s.Addr)
	if err != nil {
		return err
	}
	s.Addr = ln.Addr().String()
	_, sport, err := net.SplitHostPort(s.Addr)
	if err != nil {
		return err
	}
	port, err := strconv.Atoi(sport)
	if err != nil {
		return err
	}
	s.Port = uint16(port)

	var h http.Handler
	if s.HandlerWrapper != nil {
		h = s.HandlerWrapper(s.MountMap)
	} else {
		h = s.MountMap
	}
	s.srv = &http.Server{
		Handler: h,
	}
	s.ln = &connsCloserListener{
		Listener: tcpKeepAliveListener{ln.(*net.TCPListener)},
	}

	close(s.Started)
	s.Running = true
	if err := s.srv.Serve(s.ln); err != nil {
		return err
	}
	return nil
}

func (s *Server) Close() error {
	return s.ln.Close()
}

type connsCloserListener struct {
	net.Listener
	conns []net.Conn
}

func (ln *connsCloserListener) Accept() (c net.Conn, err error) {
	c, err = ln.Listener.Accept()
	if err != nil {
		return
	}
	ln.conns = append(ln.conns, c)
	return c, nil
}

func (ln *connsCloserListener) Close() error {
	for _, c := range ln.conns {
		if err := c.Close(); err != nil {
			log.Println(err)
		}
	}
	ln.conns = nil
	return ln.Listener.Close()
}

// borrowed from net/http
type tcpKeepAliveListener struct {
	*net.TCPListener
}

// borrowed from net/http
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
	tc, err := ln.AcceptTCP()
	if err != nil {
		return
	}
	tc.SetKeepAlive(true)
	tc.SetKeepAlivePeriod(3 * time.Minute)
	return tc, nil
}

type ServerPool struct {
	Srvs  []*Server
	Fsf   fileserver.Factory
	srvCh chan *Server
}

func NewServerPool(fsf fileserver.Factory) *ServerPool {
	return &ServerPool{
		Srvs:  make([]*Server, 0),
		Fsf:   fsf,
		srvCh: make(chan *Server),
	}
}

func (sp *ServerPool) Add(addr string) (*Server, error) {
	if err := checkAddr(addr); err != nil {
		return nil, err
	}
	s := NewServer(addr, sp.Fsf)
	sp.Srvs = append(sp.Srvs, s)
	return s, nil
}

func (sp *ServerPool) Get(port uint16) *Server {
	for _, srv := range sp.Srvs {
		if srv.Port == port {
			return srv
		}
	}
	return nil
}

func (sp *ServerPool) Remove(port uint16) (bool, error) {
	for i, srv := range sp.Srvs {
		if srv.Port != port {
			continue
		}
		if err := srv.Close(); err != nil {
			return false, err
		}
		copy(sp.Srvs[i:], sp.Srvs[i+1:])
		sp.Srvs[len(sp.Srvs)-1] = nil
		sp.Srvs = sp.Srvs[:len(sp.Srvs)-1]
		return true, nil
	}
	return false, nil
}

func (sp *ServerPool) StartSrv(s *Server) bool {
	// check the server is registered
	if s.Running {
		return false
	}
	for _, srv := range sp.Srvs {
		if srv == s {
			sp.srvCh <- s
			return true
		}
	}
	return false
}

func (sp *ServerPool) Listen() {
	for srv := range sp.srvCh {
		go func(s *Server) {
			// Ignore errClosing errors. See https://code.google.com/p/go/issues/detail?id=4373
			s.ListenAndServe()
		}(srv)
	}
}

func checkAddr(addr string) error {
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	return ln.Close()
}
<commit_msg>Refine and fix locks in MountMap:<commit_after>package kraken

import (
	"errors"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/vincent-petithory/kraken/fileserver"
)

type MountMap struct {
	m   map[string]fileserver.Server
	mu  sync.Mutex
	fsf fileserver.Factory
}

func (mm *MountMap) Targets() []string {
	mm.mu.Lock()
	defer mm.mu.Unlock()
	mountTargets := make([]string, 0, len(mm.m))
	for mountTarget := range mm.m {
		mountTargets = append(mountTargets, mountTarget)
	}
	return mountTargets
}

// GetSource retrieves the source for the given mount target.
// It returns "" if the mount target doesn't exist.
func (mm *MountMap) GetSource(mountTarget string) string {
	mm.mu.Lock()
	defer mm.mu.Unlock()
	fs, ok := mm.m[mountTarget]
	if !ok {
		return ""
	}
	return fs.Root()
}

var (
	// ErrInvalidMountTarget describes an invalid value for a mount target.
	ErrInvalidMountTarget = errors.New("invalid mount target value")
	// ErrInvalidMountSource describes an invalid value for a mount source.
	ErrInvalidMountSource = errors.New("invalid mount source value")
)

type MountSourcePermError struct {
	err error
}

func (e *MountSourcePermError) Error() string {
	return e.err.Error()
}

// Put registers a mount target for the given mount source.
// It returns true if the mount target already exists.
func (mm *MountMap) Put(mountTarget string, mountSource string, fsType string, fsParams fileserver.Params) (bool, error) {
	// mountTarget must start with /
	if !strings.HasPrefix(mountTarget, "/") {
		return false, ErrInvalidMountTarget
	}
	// if mountTarget is not "/" and has a trailing /, reject it
	if mountTarget != "/" && strings.HasSuffix(mountTarget, "/") {
		return false, ErrInvalidMountTarget
	}

	if !path.IsAbs(mountSource) {
		return false, ErrInvalidMountSource
	}

	fi, err := os.Stat(mountSource)
	if err != nil {
		return false, &MountSourcePermError{err}
	}
	if !fi.IsDir() {
		return false, &MountSourcePermError{fmt.Errorf("%s: not a directory", mountSource)}
	}

	mm.mu.Lock()
	_, ok := mm.m[mountTarget]
	fs := mm.fsf.New(mountSource, fsType, fsParams)
	mm.m[mountTarget] = fs
	mm.mu.Unlock()
	return ok, nil
}

// Delete removes an existing mount target.
// It returns true if the mount target existed.
func (mm *MountMap) DeleteTarget(mountTarget string) bool {
	mm.mu.Lock()
	_, ok := mm.m[mountTarget]
	delete(mm.m, mountTarget)
	mm.mu.Unlock()
	return ok
}

func (mm *MountMap) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var (
		maxMountTargetLen int
		mountTarget       string
	)
	mm.mu.Lock()
	defer mm.mu.Unlock()
	for t := range mm.m {
		if strings.HasPrefix(r.URL.Path, t) && len(t) >= maxMountTargetLen {
			maxMountTargetLen = len(t)
			mountTarget = t
		}
	}
	if maxMountTargetLen == 0 {
		http.Error(w, fmt.Sprintf("%s: mount target or file not found", r.URL.Path), http.StatusNotFound)
		return
	}

	if mountTarget != "/" {
		r.URL.Path = r.URL.Path[maxMountTargetLen:]
		if r.URL.Path == "" {
			http.Redirect(w, r, mountTarget+"/", http.StatusMovedPermanently)
			return
		}
	}
	fs, ok := mm.m[mountTarget]
	if !ok {
		http.Error(w, fmt.Sprintf("mount target %q not found", mountTarget), http.StatusNotFound)
		return
	}
	fs.ServeHTTP(w, r)
}

func NewMountMap(fsf fileserver.Factory) *MountMap {
	return &MountMap{
		m:   make(map[string]fileserver.Server),
		fsf: fsf,
	}
}

type Server struct {
	MountMap       *MountMap
	HandlerWrapper func(http.Handler) http.Handler
	Addr           string
	Port           uint16
	Started        chan struct{}
	Running        bool
	srv            *http.Server
	ln             net.Listener
}

func NewServer(addr string, fsf fileserver.Factory) *Server {
	return &Server{
		MountMap: NewMountMap(fsf),
		Addr:     addr,
		Started:  make(chan struct{}),
	}
}

func (s *Server) ListenAndServe() error {
	ln, err := net.Listen("tcp", s.Addr)
	if err != nil {
		return err
	}
	s.Addr = ln.Addr().String()
	_, sport, err := net.SplitHostPort(s.Addr)
	if err != nil {
		return err
	}
	port, err := strconv.Atoi(sport)
	if err != nil {
		return err
	}
	s.Port = uint16(port)

	var h http.Handler
	if s.HandlerWrapper != nil {
		h = s.HandlerWrapper(s.MountMap)
	} else {
		h = s.MountMap
	}
	s.srv = &http.Server{
		Handler: h,
	}
	s.ln = &connsCloserListener{
		Listener: tcpKeepAliveListener{ln.(*net.TCPListener)},
	}

	close(s.Started)
	s.Running = true
	if err := s.srv.Serve(s.ln); err != nil {
		return err
	}
	return nil
}

func (s *Server) Close() error {
	return s.ln.Close()
}

type connsCloserListener struct {
	net.Listener
	conns []net.Conn
}

func (ln *connsCloserListener) Accept() (c net.Conn, err error) {
	c, err = ln.Listener.Accept()
	if err != nil {
		return
	}
	ln.conns = append(ln.conns, c)
	return c, nil
}

func (ln *connsCloserListener) Close() error {
	for _, c := range ln.conns {
		if err := c.Close(); err != nil {
			log.Println(err)
		}
	}
	ln.conns = nil
	return ln.Listener.Close()
}

// borrowed from net/http
type tcpKeepAliveListener struct {
	*net.TCPListener
}

// borrowed from net/http
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
	tc, err := ln.AcceptTCP()
	if err != nil {
		return
	}
	tc.SetKeepAlive(true)
	tc.SetKeepAlivePeriod(3 * time.Minute)
	return tc, nil
}

type ServerPool struct {
	Srvs  []*Server
	Fsf   fileserver.Factory
	srvCh chan *Server
}

func NewServerPool(fsf fileserver.Factory) *ServerPool {
	return &ServerPool{
		Srvs:  make([]*Server, 0),
		Fsf:   fsf,
		srvCh: make(chan *Server),
	}
}

func (sp *ServerPool) Add(addr string) (*Server, error) {
	if err := checkAddr(addr); err != nil {
		return nil, err
	}
	s := NewServer(addr, sp.Fsf)
	sp.Srvs = append(sp.Srvs, s)
	return s, nil
}

func (sp *ServerPool) Get(port uint16) *Server {
	for _, srv := range sp.Srvs {
		if srv.Port == port {
			return srv
		}
	}
	return nil
}

func (sp *ServerPool) Remove(port uint16) (bool, error) {
	for i, srv := range sp.Srvs {
		if srv.Port != port {
			continue
		}
		if err := srv.Close(); err != nil {
			return false, err
		}
		copy(sp.Srvs[i:], sp.Srvs[i+1:])
		sp.Srvs[len(sp.Srvs)-1] = nil
		sp.Srvs = sp.Srvs[:len(sp.Srvs)-1]
		return true, nil
	}
	return false, nil
}

func (sp *ServerPool) StartSrv(s *Server) bool {
	// check the server is registered
	if s.Running {
		return false
	}
	for _, srv := range sp.Srvs {
		if srv == s {
			sp.srvCh <- s
			return true
		}
	}
	return false
}

func (sp *ServerPool) Listen() {
	for srv := range sp.srvCh {
		go func(s *Server) {
			// Ignore errClosing errors. See https://code.google.com/p/go/issues/detail?id=4373
			s.ListenAndServe()
		}(srv)
	}
}

func checkAddr(addr string) error {
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	return ln.Close()
}
<|endoftext|>


<commit_before>package main

import (
  _ "database/sql"
  "encoding/json"
  "fmt"
  "github.com/codegangsta/negroni"
  "github.com/gorilla/mux"
  "github.com/jinzhu/gorm"
  "github.com/joho/godotenv"
  _ "github.com/lib/pq"
  "github.com/unrolled/render"
  "log"
  "net/http"
  "os"
  "io/ioutil"
  "time"
)

type Api struct {
  DB gorm.DB
}

type Hacker struct {
  Id    int64
  Name  string `sql:"not null;unique"`
  Today bool
}

type GitHubEvent struct {
  Created_At string `json: "created_at"`
}

func main() {
  InitEnv()

  api := Api{}
  api.InitDB()
  api.InitSchema()

  // classic provides Recovery, Logging, Static default middleware
  n := negroni.Classic()
  // for easy template rendering
  r := render.New(render.Options{
    Directory:  "app/templates",
    Layout:     "layout",
    Extensions: []string{".tmpl", ".html"},
  })

  router := mux.NewRouter()

  router.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
    // Assumes you have a template in ./app/templates called "index.html"
    r.HTML(w, http.StatusOK, "index", nil)
  })

  // GET /hackers/chase
  router.HandleFunc("/hackers/{github_username}", api.HackerHandler).Methods("GET")

  // POST /hackers
  router.HandleFunc("/hackers", api.CreateHackerHandler).Methods("POST")

  // router goes last
  n.UseHandler(router)
  n.Run(":3000")
}

// learned from: http://www.alexedwards.net/blog/golang-response-snippets#json
func (api *Api) HackerHandler(w http.ResponseWriter, r *http.Request) {
  params := mux.Vars(r) // from the request
  // need to figure out how to recall a db record from params/Vars
  // return/display JSON dump of saved object
  // my_little_json := Hacker{1, params["github_username"], today("github_username")}
  hacker := Hacker{}
  if err := api.DB.Where("name = ?", params["github_username"]).First(&hacker).Error; err != nil {
    // This really should redirect to a Not Found route. Or return a not found JSON
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }

  js, err := json.Marshal(&hacker)
  if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }

  w.Header().Set("Content-Type", "application/json")
  w.Write(js)
}

func (api *Api) CreateHackerHandler(w http.ResponseWriter, r *http.Request) {
  err := r.ParseForm()
  if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }

  // save data
  hacker := Hacker{Name: r.Form.Get("name")}
  if err := api.DB.Where(Hacker{Name: r.Form.Get("name")}).FirstOrCreate(&hacker).Error; err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }

  // make JSON
  js, err := json.Marshal(&hacker)
  if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }

  // return JSON
  w.Header().Set("Content-Type", "application/json")
  w.Write(js)
}

func get_github_events(name string) []GitHubEvent {
  var gh_events []GitHubEvent
  personal_url := fmt.Sprintf("https://api.github.com/users/%s/events", name)
  body := read_github_events(personal_url)
  err := json.Unmarshal(body, &gh_events)

  if err != nil {
    log.Fatal(err.Error())
  }

  return gh_events
}

func today(name string) bool {

  gh_events := get_github_events(name)

  // get zulu time with go time library
  for _, item := range gh_events {
    item_time, _ := time.Parse(time.RFC3339, item.Created_At)
    if time.Now().Equal(item_time) {
      return true
    }
  }

  return false
}

func read_github_events(url string) []byte {
  response, err := http.Get(url)

  if err != nil {
    log.Fatal(err.Error())
  }

  body, err := ioutil.ReadAll(response.Body)

  if err != nil {
    log.Fatal(err.Error())
  }

  return body
}

func InitEnv() {
  env := godotenv.Load()
  if env != nil {
    log.Fatal("Error loading .env file")
  }
}

func (api *Api) InitDB() {
  var err error
  db_user := os.Getenv("DATABASE_USER")
  db_pass := os.Getenv("DATABASE_PASS")
  db_host := os.Getenv("DATABASE_HOST")
  db_name := os.Getenv("DATABASE_NAME")
  logger := log.New(os.Stdout, "", log.Ldate|log.Lmicroseconds|log.Lshortfile)
  logger.Println("got db_user: ", db_user)

  api.DB, err = gorm.Open("postgres", fmt.Sprintf("user=%s dbname=%s password=%s host=%s sslmode=disable", db_user, db_name, db_pass, db_host))
  if err != nil {
    log.Fatal("database connection error: ", err)
  }
  api.DB.LogMode(true)
}

func (api *Api) InitSchema() {
  api.DB.AutoMigrate(&Hacker{})
}
<commit_msg>comment for future reference<commit_after>package main

import (
  _ "database/sql"
  "encoding/json"
  "fmt"
  "github.com/codegangsta/negroni"
  "github.com/gorilla/mux"
  "github.com/jinzhu/gorm"
  "github.com/joho/godotenv"
  _ "github.com/lib/pq"
  "github.com/unrolled/render"
  "log"
  "net/http"
  "os"
  "io/ioutil"
  "time"
)

type Api struct {
  DB gorm.DB
}

type Hacker struct {
  Id    int64
  Name  string `sql:"not null;unique"`
  // Today may not be something that we should save in the database.
  // This is probably something that we want to be able to display though
  // the api, but not necessarily save it since it will change frequently.
  // It will be calculated upon the request.
  Today bool
}

type GitHubEvent struct {
  Created_At string `json: "created_at"`
}

func main() {
  InitEnv()

  api := Api{}
  api.InitDB()
  api.InitSchema()

  // classic provides Recovery, Logging, Static default middleware
  n := negroni.Classic()
  // for easy template rendering
  r := render.New(render.Options{
    Directory:  "app/templates",
    Layout:     "layout",
    Extensions: []string{".tmpl", ".html"},
  })

  router := mux.NewRouter()

  router.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
    // Assumes you have a template in ./app/templates called "index.html"
    r.HTML(w, http.StatusOK, "index", nil)
  })

  // GET /hackers/chase
  router.HandleFunc("/hackers/{github_username}", api.HackerHandler).Methods("GET")

  // POST /hackers
  router.HandleFunc("/hackers", api.CreateHackerHandler).Methods("POST")

  // router goes last
  n.UseHandler(router)
  n.Run(":3000")
}

// learned from: http://www.alexedwards.net/blog/golang-response-snippets#json
func (api *Api) HackerHandler(w http.ResponseWriter, r *http.Request) {
  params := mux.Vars(r) // from the request
  // need to figure out how to recall a db record from params/Vars
  // return/display JSON dump of saved object
  // my_little_json := Hacker{1, params["github_username"], today("github_username")}
  hacker := Hacker{}
  if err := api.DB.Where("name = ?", params["github_username"]).First(&hacker).Error; err != nil {
    // This really should redirect to a Not Found route. Or return a not found JSON
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }

  js, err := json.Marshal(&hacker)
  if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }

  w.Header().Set("Content-Type", "application/json")
  w.Write(js)
}

func (api *Api) CreateHackerHandler(w http.ResponseWriter, r *http.Request) {
  err := r.ParseForm()
  if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }

  // save data
  hacker := Hacker{Name: r.Form.Get("name")}
  if err := api.DB.Where(Hacker{Name: r.Form.Get("name")}).FirstOrCreate(&hacker).Error; err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }

  // make JSON
  js, err := json.Marshal(&hacker)
  if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }

  // return JSON
  w.Header().Set("Content-Type", "application/json")
  w.Write(js)
}

func get_github_events(name string) []GitHubEvent {
  var gh_events []GitHubEvent
  personal_url := fmt.Sprintf("https://api.github.com/users/%s/events", name)
  body := read_github_events(personal_url)
  err := json.Unmarshal(body, &gh_events)

  if err != nil {
    log.Fatal(err.Error())
  }

  return gh_events
}

func today(name string) bool {

  gh_events := get_github_events(name)

  // get zulu time with go time library
  for _, item := range gh_events {
    item_time, _ := time.Parse(time.RFC3339, item.Created_At)
    if time.Now().Equal(item_time) {
      return true
    }
  }

  return false
}

func read_github_events(url string) []byte {
  response, err := http.Get(url)

  if err != nil {
    log.Fatal(err.Error())
  }

  body, err := ioutil.ReadAll(response.Body)

  if err != nil {
    log.Fatal(err.Error())
  }

  return body
}

func InitEnv() {
  env := godotenv.Load()
  if env != nil {
    log.Fatal("Error loading .env file")
  }
}

func (api *Api) InitDB() {
  var err error
  db_user := os.Getenv("DATABASE_USER")
  db_pass := os.Getenv("DATABASE_PASS")
  db_host := os.Getenv("DATABASE_HOST")
  db_name := os.Getenv("DATABASE_NAME")
  logger := log.New(os.Stdout, "", log.Ldate|log.Lmicroseconds|log.Lshortfile)
  logger.Println("got db_user: ", db_user)

  api.DB, err = gorm.Open("postgres", fmt.Sprintf("user=%s dbname=%s password=%s host=%s sslmode=disable", db_user, db_name, db_pass, db_host))
  if err != nil {
    log.Fatal("database connection error: ", err)
  }
  api.DB.LogMode(true)
}

func (api *Api) InitSchema() {
  api.DB.AutoMigrate(&Hacker{})
}
<|endoftext|>


<commit_before>package main

import (
  "encoding/json"
  "fmt"
  "github.com/codegangsta/negroni"
  "github.com/gorilla/mux"
  "github.com/joho/godotenv"
  "log"
  "net/http"
  "os"
)

type Hacker struct {
  Name    string
  Hobbies []string
  Today   bool
}

func main() {
  env := godotenv.Load()
  if env != nil {
    log.Fatal("Error loading .env file")
  }

  db_user := os.Getenv("DATABASE_USER")
  logger := log.New(os.Stdout, "", log.Ldate|log.Lmicroseconds|log.Lshortfile)
  logger.Println("got db_user: ", db_user)

  // classic provides Recovery, Logging, Static default middleware
  n := negroni.Classic()

  router := mux.NewRouter()
  router.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, "Hello World!")
  })

  // GET /hackers/chase
  router.HandleFunc("/hackers/{hacker}", hacker_handler)

  // router goes last
  n.UseHandler(router)
  n.Run(":3000")
}

// learned from: http://www.alexedwards.net/blog/golang-response-snippets#json
func hacker_handler(w http.ResponseWriter, r *http.Request) {
  params := mux.Vars(r) // from the request
  my_little_json := Hacker{params["hacker"], []string{"music", "programming"}}

  js, err := json.Marshal(my_little_json)
  if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }

  w.Header().Set("Content-Type", "application/json")
  w.Write(js)
}
<commit_msg>start the function<commit_after>package main

import (
  "encoding/json"
  "fmt"
  "github.com/codegangsta/negroni"
  "github.com/gorilla/mux"
  "github.com/joho/godotenv"
  "log"
  "net/http"
  "os"
)

type Hacker struct {
  Name    string
  Hobbies []string
  Today   bool
}

func main() {
  env := godotenv.Load()
  if env != nil {
    log.Fatal("Error loading .env file")
  }

  db_user := os.Getenv("DATABASE_USER")
  logger := log.New(os.Stdout, "", log.Ldate|log.Lmicroseconds|log.Lshortfile)
  logger.Println("got db_user: ", db_user)

  // classic provides Recovery, Logging, Static default middleware
  n := negroni.Classic()

  router := mux.NewRouter()
  router.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, "Hello World!")
  })

  // GET /hackers/chase
  router.HandleFunc("/hackers/{hacker}", hacker_handler)

  // router goes last
  n.UseHandler(router)
  n.Run(":3000")
}

// learned from: http://www.alexedwards.net/blog/golang-response-snippets#json
func hacker_handler(w http.ResponseWriter, r *http.Request) {
  params := mux.Vars(r) // from the request
  my_little_json := Hacker{params["hacker"], []string{"music", "programming"},
                           today{params["hacker"]}}

  js, err := json.Marshal(my_little_json)
  if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }

  w.Header().Set("Content-Type", "application/json")
  w.Write(js)
}

func today(h string) bool{
  return false
}
<|endoftext|>