<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math"
	"net/http"
	"regexp"
	"strings"

	"github.com/golang/protobuf/proto"
	"github.com/golang/snappy"
	"github.com/prometheus/common/model"
	"github.com/prometheus/prometheus/storage/remote"
)

// Escaping for strings for Crate.io SQL.
var escaper = strings.NewReplacer("\\", "\\\\", "\"", "\\\"", "'", "\\'")

// Escape a labelname for use in SQL as a column name.
func escapeLabelName(s string) string {
	return "\"l" + escaper.Replace(s) + "\""
}

// Escape a labelvalue for use in SQL as a string value.
func escapeLabelValue(s string) string {
	return "'" + escaper.Replace(s) + "'"
}

type crateRequest struct {
	Stmt     string          `json:"stmt"`
	BulkArgs [][]interface{} `json:"bulk_args,omitempty"`
}

type crateResponse struct {
	Cols []string        `json:"cols,omitempty"`
	Rows [][]interface{} `json:"rows,omitempty"`
}

// Convert a read query into a Crate SQL query.
func queryToSQL(q *remote.Query) (string, error) {
	selectors := make([]string, 0, len(q.Matchers)+2)
	for _, m := range q.Matchers {
		switch m.Type {
		case remote.MatchType_EQUAL:
			if m.Value == "" {
				// Empty labels are recorded as NULL.
				// In PromQL, empty labels and missing labels are the same thing.
				selectors = append(selectors, fmt.Sprintf("(%s IS NULL)", escapeLabelName(m.Name)))
			} else {
				selectors = append(selectors, fmt.Sprintf("(%s = %s)", escapeLabelName(m.Name), escapeLabelValue(m.Value)))
			}
		case remote.MatchType_NOT_EQUAL:
			if m.Value == "" {
				selectors = append(selectors, fmt.Sprintf("(%s IS NOT NULL)", escapeLabelName(m.Name)))
			} else {
				selectors = append(selectors, fmt.Sprintf("(%s != %s)", escapeLabelName(m.Name), escapeLabelValue(m.Value)))
			}
		case remote.MatchType_REGEX_MATCH:
			re := "^(?:" + m.Value + ")$"
			matchesEmpty, err := regexp.MatchString(re, "")
			if err != nil {
				return "", err
			}
			// Crate regexes are not RE2, so there may be small semantic differences here.
			if matchesEmpty {
				selectors = append(selectors, fmt.Sprintf("(%s ~ %s OR %s IS NULL)", escapeLabelName(m.Name), escapeLabelValue(re), escapeLabelName(m.Name)))
			} else {
				selectors = append(selectors, fmt.Sprintf("(%s ~ %s)", escapeLabelName(m.Name), escapeLabelValue(re)))
			}
		case remote.MatchType_REGEX_NO_MATCH:
			re := "^(?:" + m.Value + ")$"
			matchesEmpty, err := regexp.MatchString(re, "")
			if err != nil {
				return "", err
			}
			if matchesEmpty {
				selectors = append(selectors, fmt.Sprintf("(%s !~ %s)", escapeLabelName(m.Name), escapeLabelValue(re)))
			} else {
				selectors = append(selectors, fmt.Sprintf("(%s !~ %s OR %s IS NULL)", escapeLabelName(m.Name), escapeLabelValue(re), escapeLabelName(m.Name)))
			}
		}
	}
	selectors = append(selectors, fmt.Sprintf("(timestamp <= %d)", q.EndTimestampMs))
	selectors = append(selectors, fmt.Sprintf("(timestamp >= %d)", q.StartTimestampMs))

	return fmt.Sprintf("SELECT * from metrics WHERE %s ORDER BY timestamp", strings.Join(selectors, " AND ")), nil
}

func runQuery(q *remote.Query) ([]*remote.TimeSeries, error) {
	query, err := queryToSQL(q)
	if err != nil {
		return nil, err
	}

	request := crateRequest{Stmt: query}
	jsonRequest, err := json.Marshal(request)
	if err != nil {
		return nil, err
	}

	result, err := http.Post("http://localhost:4200/_sql", "application/json", bytes.NewReader(jsonRequest))
	if err != nil {
		return nil, err
	}
	defer result.Body.Close()
	if result.StatusCode != http.StatusOK {
		return nil, err
	}
	var data crateResponse
	decoder := json.NewDecoder(result.Body)
	decoder.UseNumber()
	if err = decoder.Decode(&data); err != nil {
		return nil, err
	}

	timeseries := map[string]*remote.TimeSeries{}
	for _, row := range data.Rows {
		metric := model.Metric{}
		var v float64
		var t int64
		for i, value := range row {
			column := data.Cols[i]
			if column[0] == 'l' && value != nil {
				metric[model.LabelName(column[1:])] = model.LabelValue(value.(string))
			} else if column == "timestamp" {
				t, _ = value.(json.Number).Int64()
			} else if column == "valueRaw" {
				val, _ := value.(json.Number).Int64()
				v = math.Float64frombits(uint64(val))
			}
		}
		ts, ok := timeseries[metric.String()]
		if !ok {
			ts = &remote.TimeSeries{}
			for k, v := range metric {
				ts.Labels = append(ts.Labels, &remote.LabelPair{Name: string(k), Value: string(v)})
			}
			timeseries[metric.String()] = ts
		}
		ts.Samples = append(ts.Samples, &remote.Sample{Value: v, TimestampMs: t})
	}

	resp := make([]*remote.TimeSeries, 0, len(timeseries))
	for _, ts := range timeseries {
		resp = append(resp, ts)
	}
	return resp, nil
}

func main() {
	http.HandleFunc("/write", func(w http.ResponseWriter, r *http.Request) {
		compressed, err := ioutil.ReadAll(r.Body)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		reqBuf, err := snappy.Decode(nil, compressed)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		var req remote.WriteRequest
		if err := proto.Unmarshal(reqBuf, &req); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		// Build a list of every label name used.
		labelsUsed := map[string]struct{}{}
		for _, ts := range req.Timeseries {
			for _, l := range ts.Labels {
				labelsUsed[l.Name] = struct{}{}
			}
		}
		labels := make([]string, 0, len(labelsUsed))
		escapedLabels := make([]string, 0, len(labelsUsed))

		for l := range labelsUsed {
			labels = append(labels, l)
			escapedLabels = append(escapedLabels, escapeLabelName(l))
		}

		request := crateRequest{
			BulkArgs: make([][]interface{}, 0, len(req.Timeseries)),
		}
		placeholders := strings.Repeat("?, ", len(labels))
		columns := strings.Join(escapedLabels, ", ")
		request.Stmt = fmt.Sprintf("INSERT INTO metrics (%s, \"value\", \"valueRaw\", \"timestamp\") VALUES (%s ?, ?, ?)", columns, placeholders)

		for _, ts := range req.Timeseries {
			metric := make(map[string]string, len(ts.Labels))
			for _, l := range ts.Labels {
				metric[l.Name] = l.Value
			}

			for _, s := range ts.Samples {
				args := make([]interface{}, 0, len(labels)+2)
				for _, l := range labels {
					if metric[l] == "" {
						args = append(args, nil)
					} else {
						args = append(args, metric[l])
					}
				}
				// Convert to string to handle NaN/Inf/-Inf
				args = append(args, fmt.Sprintf("%f", s.Value))
				// Crate.io can't handle full NaN values as required by Prometheus 2.0,
				// so store the raw bits as an int64.
				args = append(args, int64(math.Float64bits(s.Value)))
				args = append(args, s.TimestampMs)

				request.BulkArgs = append(request.BulkArgs, args)
			}
		}

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

		resp, err := http.Post("http://localhost:4200/_sql", "application/json", bytes.NewReader(jsonRequest))
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		defer resp.Body.Close()
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		if resp.StatusCode != http.StatusOK {
			respBody, _ := ioutil.ReadAll(resp.Body)
			fmt.Println(string(respBody))
			http.Error(w, string(respBody), http.StatusInternalServerError)
		}
	})

	http.HandleFunc("/read", func(w http.ResponseWriter, r *http.Request) {
		compressed, err := ioutil.ReadAll(r.Body)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		reqBuf, err := snappy.Decode(nil, compressed)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		var req remote.ReadRequest
		if err := proto.Unmarshal(reqBuf, &req); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		if len(req.Queries) != 1 {
			http.Error(w, "Can only handle one query.", http.StatusBadRequest)
			return
		}

		result, err := runQuery(req.Queries[0])
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		resp := remote.ReadResponse{
			Results: []*remote.QueryResult{
				{Timeseries: result},
			},
		}
		data, err := proto.Marshal(&resp)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		w.Header().Set("Content-Type", "application/x-protobuf")
		if _, err := w.Write(snappy.Encode(nil, data)); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	})

	http.ListenAndServe(":1234", nil)
}
<commit_msg>Break read/write into standalone functions.<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math"
	"net/http"
	"regexp"
	"strings"

	"github.com/golang/protobuf/proto"
	"github.com/golang/snappy"
	"github.com/prometheus/common/model"
	"github.com/prometheus/prometheus/storage/remote"
)

// Escaping for strings for Crate.io SQL.
var escaper = strings.NewReplacer("\\", "\\\\", "\"", "\\\"", "'", "\\'")

// Escape a labelname for use in SQL as a column name.
func escapeLabelName(s string) string {
	return "\"l" + escaper.Replace(s) + "\""
}

// Escape a labelvalue for use in SQL as a string value.
func escapeLabelValue(s string) string {
	return "'" + escaper.Replace(s) + "'"
}

type crateRequest struct {
	Stmt     string          `json:"stmt"`
	BulkArgs [][]interface{} `json:"bulk_args,omitempty"`
}

type crateResponse struct {
	Cols []string        `json:"cols,omitempty"`
	Rows [][]interface{} `json:"rows,omitempty"`
}

// Convert a read query into a Crate SQL query.
func queryToSQL(q *remote.Query) (string, error) {
	selectors := make([]string, 0, len(q.Matchers)+2)
	for _, m := range q.Matchers {
		switch m.Type {
		case remote.MatchType_EQUAL:
			if m.Value == "" {
				// Empty labels are recorded as NULL.
				// In PromQL, empty labels and missing labels are the same thing.
				selectors = append(selectors, fmt.Sprintf("(%s IS NULL)", escapeLabelName(m.Name)))
			} else {
				selectors = append(selectors, fmt.Sprintf("(%s = %s)", escapeLabelName(m.Name), escapeLabelValue(m.Value)))
			}
		case remote.MatchType_NOT_EQUAL:
			if m.Value == "" {
				selectors = append(selectors, fmt.Sprintf("(%s IS NOT NULL)", escapeLabelName(m.Name)))
			} else {
				selectors = append(selectors, fmt.Sprintf("(%s != %s)", escapeLabelName(m.Name), escapeLabelValue(m.Value)))
			}
		case remote.MatchType_REGEX_MATCH:
			re := "^(?:" + m.Value + ")$"
			matchesEmpty, err := regexp.MatchString(re, "")
			if err != nil {
				return "", err
			}
			// Crate regexes are not RE2, so there may be small semantic differences here.
			if matchesEmpty {
				selectors = append(selectors, fmt.Sprintf("(%s ~ %s OR %s IS NULL)", escapeLabelName(m.Name), escapeLabelValue(re), escapeLabelName(m.Name)))
			} else {
				selectors = append(selectors, fmt.Sprintf("(%s ~ %s)", escapeLabelName(m.Name), escapeLabelValue(re)))
			}
		case remote.MatchType_REGEX_NO_MATCH:
			re := "^(?:" + m.Value + ")$"
			matchesEmpty, err := regexp.MatchString(re, "")
			if err != nil {
				return "", err
			}
			if matchesEmpty {
				selectors = append(selectors, fmt.Sprintf("(%s !~ %s)", escapeLabelName(m.Name), escapeLabelValue(re)))
			} else {
				selectors = append(selectors, fmt.Sprintf("(%s !~ %s OR %s IS NULL)", escapeLabelName(m.Name), escapeLabelValue(re), escapeLabelName(m.Name)))
			}
		}
	}
	selectors = append(selectors, fmt.Sprintf("(timestamp <= %d)", q.EndTimestampMs))
	selectors = append(selectors, fmt.Sprintf("(timestamp >= %d)", q.StartTimestampMs))

	return fmt.Sprintf("SELECT * from metrics WHERE %s ORDER BY timestamp", strings.Join(selectors, " AND ")), nil
}

type crateAdapter struct {
	client http.Client
	url    string
}

func (ca *crateAdapter) runQuery(q *remote.Query) ([]*remote.TimeSeries, error) {
	query, err := queryToSQL(q)
	if err != nil {
		return nil, err
	}

	request := crateRequest{Stmt: query}
	jsonRequest, err := json.Marshal(request)
	if err != nil {
		return nil, err
	}

	result, err := ca.client.Post(ca.url, "application/json", bytes.NewReader(jsonRequest))
	if err != nil {
		return nil, err
	}
	defer result.Body.Close()
	if result.StatusCode != http.StatusOK {
		return nil, err
	}
	var data crateResponse
	decoder := json.NewDecoder(result.Body)
	decoder.UseNumber()
	if err = decoder.Decode(&data); err != nil {
		return nil, err
	}

	timeseries := map[string]*remote.TimeSeries{}
	for _, row := range data.Rows {
		metric := model.Metric{}
		var v float64
		var t int64
		for i, value := range row {
			column := data.Cols[i]
			if column[0] == 'l' && value != nil {
				metric[model.LabelName(column[1:])] = model.LabelValue(value.(string))
			} else if column == "timestamp" {
				t, _ = value.(json.Number).Int64()
			} else if column == "valueRaw" {
				val, _ := value.(json.Number).Int64()
				v = math.Float64frombits(uint64(val))
			}
		}
		ts, ok := timeseries[metric.String()]
		if !ok {
			ts = &remote.TimeSeries{}
			for k, v := range metric {
				ts.Labels = append(ts.Labels, &remote.LabelPair{Name: string(k), Value: string(v)})
			}
			timeseries[metric.String()] = ts
		}
		ts.Samples = append(ts.Samples, &remote.Sample{Value: v, TimestampMs: t})
	}

	resp := make([]*remote.TimeSeries, 0, len(timeseries))
	for _, ts := range timeseries {
		resp = append(resp, ts)
	}
	return resp, nil
}

func (ca *crateAdapter) handleRead(w http.ResponseWriter, r *http.Request) {
	compressed, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	reqBuf, err := snappy.Decode(nil, compressed)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	var req remote.ReadRequest
	if err := proto.Unmarshal(reqBuf, &req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	if len(req.Queries) != 1 {
		http.Error(w, "Can only handle one query.", http.StatusBadRequest)
		return
	}

	result, err := ca.runQuery(req.Queries[0])
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	resp := remote.ReadResponse{
		Results: []*remote.QueryResult{
			{Timeseries: result},
		},
	}
	data, err := proto.Marshal(&resp)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/x-protobuf")
	if _, err := w.Write(snappy.Encode(nil, data)); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func (ca *crateAdapter) handleWrite(w http.ResponseWriter, r *http.Request) {
	compressed, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	reqBuf, err := snappy.Decode(nil, compressed)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	var req remote.WriteRequest
	if err := proto.Unmarshal(reqBuf, &req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	// Build a list of every label name used.
	labelsUsed := map[string]struct{}{}
	for _, ts := range req.Timeseries {
		for _, l := range ts.Labels {
			labelsUsed[l.Name] = struct{}{}
		}
	}
	labels := make([]string, 0, len(labelsUsed))
	escapedLabels := make([]string, 0, len(labelsUsed))

	for l := range labelsUsed {
		labels = append(labels, l)
		escapedLabels = append(escapedLabels, escapeLabelName(l))
	}

	request := crateRequest{
		BulkArgs: make([][]interface{}, 0, len(req.Timeseries)),
	}
	placeholders := strings.Repeat("?, ", len(labels))
	columns := strings.Join(escapedLabels, ", ")
	request.Stmt = fmt.Sprintf("INSERT INTO metrics (%s, \"value\", \"valueRaw\", \"timestamp\") VALUES (%s ?, ?, ?)", columns, placeholders)

	for _, ts := range req.Timeseries {
		metric := make(map[string]string, len(ts.Labels))
		for _, l := range ts.Labels {
			metric[l.Name] = l.Value
		}

		for _, s := range ts.Samples {
			args := make([]interface{}, 0, len(labels)+2)
			for _, l := range labels {
				if metric[l] == "" {
					args = append(args, nil)
				} else {
					args = append(args, metric[l])
				}
			}
			// Convert to string to handle NaN/Inf/-Inf
			args = append(args, fmt.Sprintf("%f", s.Value))
			// Crate.io can't handle full NaN values as required by Prometheus 2.0,
			// so store the raw bits as an int64.
			args = append(args, int64(math.Float64bits(s.Value)))
			args = append(args, s.TimestampMs)

			request.BulkArgs = append(request.BulkArgs, args)
		}
	}

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

	resp, err := ca.client.Post(ca.url, "application/json", bytes.NewReader(jsonRequest))
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer resp.Body.Close()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if resp.StatusCode != http.StatusOK {
		respBody, _ := ioutil.ReadAll(resp.Body)
		fmt.Println(string(respBody))
		http.Error(w, string(respBody), http.StatusInternalServerError)
	}
}

func main() {
	ca := crateAdapter{
		client: http.Client{},
		url:    "http://localhost:4200/_sql",
	}

	http.HandleFunc("/write", ca.handleWrite)
	http.HandleFunc("/read", ca.handleRead)
	http.ListenAndServe(":1234", nil)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"image"
	"image/jpeg"
	"image/png"
	"net/http"
	"os"

	"github.com/codegangsta/martini"
)

const (
	LOCAL_IMAGES_PATH = "local-images"
)

func main() {
	m := martini.Classic()
	m.Get("/image/**", func(params martini.Params) (int, string) {
		imagePath := params["_1"]

		if _, err := os.Stat(LOCAL_IMAGES_PATH + "/" + imagePath); os.IsNotExist(err) {
			return http.StatusNotFound, "Image not found: " + imagePath
		} else {
			reader, err := os.Open(LOCAL_IMAGES_PATH + "/" + imagePath)
			if err != nil {
				return http.StatusInternalServerError, "Cannot open image"
			}
			image, format, err := image.Decode(reader)
			if err != nil {
				return http.StatusInternalServerError, "Cannot decode image"
			}

			var buffer bytes.Buffer
			if format == "jpeg" {
				jpeg.Encode(&buffer, image, nil)
			} else {
				png.Encode(&buffer, image)
			}

			return http.StatusOK, buffer.String()
		}
	})
	m.Run()
}

func log(message string) {
	fmt.Println("[pixlserv] " + message)
}
<commit_msg>Parsing parameters<commit_after>package main

import (
	"bytes"
	"image"
	"image/jpeg"
	"image/png"
	"log"
	"net/http"
	"os"
	"strings"
	"strconv"

	"github.com/codegangsta/martini"
)

const (
	LOCAL_IMAGES_PATH = "local-images"
)

func main() {
	// Set up logging
	log.SetPrefix("[pixlserv] ")
	log.SetFlags(0)  // Removed the timestamp
	log.Println("Test")

	// Run the server
	m := martini.Classic()
	m.Get("/image/:parameters/**", func(params martini.Params) (int, string) {
		parameters, err := parseParameters(params["parameters"])
		if err != "" {
			return http.StatusBadRequest, err
		}
		log.Println(parameters)
		imagePath := params["_1"]

		if _, err := os.Stat(LOCAL_IMAGES_PATH + "/" + imagePath); os.IsNotExist(err) {
			return http.StatusNotFound, "Image not found: " + imagePath
		} else {
			reader, err := os.Open(LOCAL_IMAGES_PATH + "/" + imagePath)
			if err != nil {
				return http.StatusInternalServerError, "Cannot open image"
			}
			image, format, err := image.Decode(reader)
			if err != nil {
				return http.StatusInternalServerError, "Cannot decode image"
			}

			// TODO - magic

			var buffer bytes.Buffer
			if format == "jpeg" {
				jpeg.Encode(&buffer, image, nil)
			} else {
				png.Encode(&buffer, image)
			}

			return http.StatusOK, buffer.String()
		}
	})
	m.Run()
}

// Turns a string like "w_400,h_300" into a map[w:400 h:300]
// The second return value is an error message
// Also validates the parameters to make sure they have valid values
// w = width, h = height
func parseParameters(parametersStr string) (map[string]string, string) {
	parameters := make(map[string]string)
	parts := strings.Split(parametersStr, ",")
	for _, part := range parts {
		// TODO - validation
		keyAndValue := strings.SplitN(part, "_", 2)
		key := keyAndValue[0]
		value := keyAndValue[1]

		switch key {
		case "w", "h":
			value, err := strconv.Atoi(value)
			if err != nil {
				return nil, "Could not parse value for parameter: " + key
			}
			if value <= 0 {
				return nil, "Value must be > 0: " + key
			}
		}

		parameters[key] = value
	}
	return parameters, ""
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"os"
	"strconv"

	"github.com/golang/gddo/httputil"
	"github.com/gorilla/mux"
	_ "gopkg.in/cq.v1"
)

const neo4jURL = "http://localhost:7474"

var (
	db     *Database
	Logger = log.New(os.Stdout, "  ", log.LstdFlags|log.Lshortfile)
)
var (
	ErrNotFound     = errors.New("Not Found")
	ErrBadRequest   = errors.New("Bad Request")
	ErrUnauthorized = errors.New("Unauthorized")
)

func handler(f func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		Logger.Printf("%v: %v\n", r.Method, r.URL.Path)

		// TODO: Implement optional authentication param. Transparently handle the auth before calling the handler.
		// TODO: Panic recover defer function
		var wb httputil.ResponseBuffer
		err := f(&wb, r)
		if err == nil {
			wb.WriteTo(w)
			return
		}

		Logger.Println("Error: ", err)
		switch err {
		case ErrNotFound:
			writeAPIError(w, http.StatusNotFound)
		case ErrBadRequest:
			writeAPIError(w, http.StatusBadRequest)
		case ErrUnauthorized:
			writeAPIError(w, http.StatusUnauthorized)
		default:
			writeAPIError(w, http.StatusInternalServerError)
		}

	}
}

func userHandler(w http.ResponseWriter, r *http.Request) error {
	switch {
	case r.Method == "POST":
		user := &User{}
		if err := json.NewDecoder(r.Body).Decode(user); err != nil {
			Logger.Println(err)
			return ErrBadRequest
		}

		err := db.UpsertUser(user)
		if err != nil {
			return err
		}

		return writeJSON(w, user, http.StatusCreated)
	case r.Method == "GET":
		id := mux.Vars(r)["id"]
		if id == "" {
			return ErrBadRequest
		}

		user := db.GetUser(id)
		if user == nil {
			return ErrNotFound
		}
		return writeJSON(w, user, http.StatusOK)
	}
	return ErrBadRequest
}

func itemHandler(w http.ResponseWriter, r *http.Request) error {
	switch {
	case r.Method == "POST":
		item := &Item{}
		if err := json.NewDecoder(r.Body).Decode(item); err != nil {
			Logger.Println(err)
			return ErrBadRequest
		}

		if err := db.UpsertItem(item); err != nil {
			return err
		}
		return writeJSON(w, item, http.StatusCreated)
	case r.Method == "GET":
		id := mux.Vars(r)["id"]
		if id == "" {
			return ErrBadRequest
		}

		item := db.GetItem(id)
		if item == nil {
			return ErrNotFound
		}

		return writeJSON(w, item, http.StatusOK)
	}
	return ErrBadRequest
}

func linkHandler(w http.ResponseWriter, r *http.Request) error {
	switch {
	case r.Method == "POST":
		// For more complex JSON decodings: http://talks.golang.org/2015/json.slide#21
		link := &Link{}
		if err := json.NewDecoder(r.Body).Decode(link); err != nil {
			return err
		}
		if link.UserId == "" || link.ItemId == "" {
			return ErrBadRequest
		}

		/*
			// This query needs to match *all* nodes, therefore is not valid
			cypher := `MERGE (u:User {id:{0}, name:{1}})
						MERGE (i:Item {id:{2}, name:{3}})
						MERGE (u)-[r:LINKED {type:{4}}]->(i)` // FIXME: No return values?
			rows, err := ctx.db.Query(cypher, link.User.Id, link.User.Name, link.Item.Id, link.Item.Name, link.Type)
		*/
		err := db.UpsertLink(link)
		if err != nil {
			return err
		}

		return writeJSON(w, link, http.StatusCreated)
	}
	return ErrBadRequest
}

// recommendHandler will manage item recommendations. It currently reads the user id and
// categories for the recommended items. Matching items must fulfil *any* category
func recommendHandler(w http.ResponseWriter, r *http.Request) error {
	uid := mux.Vars(r)["uid"]
	if uid == "" {
		return ErrBadRequest
	}

	r.ParseForm()
	categories := r.Form["category"]

	// Store binding vars in a slice
	args := []interface{}{}
	argPos := func() string {
		return strconv.Itoa(len(args) - 1) // Current var position (string)
	}

	args = append(args, uid)
	where := "WHERE u.id = {" + argPos() + "}"
	if len(categories) > 0 {
		//where = where + " AND ALL (x IN {1} WHERE x in item2.categories)"
		args = append(args, categories)
		where = where + " AND ANY (x IN {" + argPos() + "} WHERE x in item2.categories)"
	}

	cypher := `MATCH (u:User)-[:LINKED]->(item1:Item)<-[:LINKED]-(u2:User),
		(u2)-[l:LINKED]->(item2:Item)` +
		where +
		`AND NOT (u)-[:LINKED]->(item2)
		RETURN item2.id, item2.name, count(distinct l) as frequency
		ORDER BY frequency DESC`

	rows, err := db.Query(cypher, args...)
	if err != nil {
		return err
	}
	defer rows.Close()

	resp := []Recommendation{}
	for rows.Next() {
		var rec Recommendation
		err = rows.Scan(&rec.Item.Id, &rec.Item.Name, &rec.Strength)
		if err != nil {
			return err
		}
		resp = append(resp, rec)
	}

	return writeJSON(w, resp, http.StatusOK)
}

func writeJSON(w http.ResponseWriter, value interface{}, status int) error {
	w.WriteHeader(status)
	w.Header().Set("Content-Type", "application/json; charset=UTF8")
	return json.NewEncoder(w).Encode(value)
}

func writeAPIError(w http.ResponseWriter, status int) {
	var data struct {
		Error struct {
			Message string `json:"message"`
		} `json:"error"`
	}
	data.Error.Message = http.StatusText(status)
	w.WriteHeader(status)
	w.Header().Set("Content-Type", "application/json; charset=UTF8")
	json.NewEncoder(w).Encode(&data)
}

func main() {
	// TODO: Create uniqueness constraints in Graph (user id, item id)
	db = NewDatabase()

	r := mux.NewRouter()
	r.HandleFunc("/user", handler(userHandler)).Methods("POST")
	r.HandleFunc("/user/{id}", handler(userHandler)).Methods("GET")
	r.HandleFunc("/item", handler(itemHandler)).Methods("POST")
	r.HandleFunc("/item/{id}", handler(itemHandler)).Methods("GET")
	r.HandleFunc("/link", handler(linkHandler)).Methods("POST")

	r.HandleFunc("/recommend/{uid}", handler(recommendHandler)).Methods("GET")

	http.Handle("/", r)
	http.ListenAndServe(":8080", nil)
}

func Int(v int) *int {
	p := new(int)
	*p = v
	return p
}

func String(s string) *string {
	p := new(string)
	*p = s
	return p
}
<commit_msg>Recover from unforeseen panic<commit_after>package main

import (
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"os"
	"strconv"

	"github.com/golang/gddo/httputil"
	"github.com/gorilla/mux"
	_ "gopkg.in/cq.v1"
)

const neo4jURL = "http://localhost:7474"

var (
	db     *Database
	Logger = log.New(os.Stdout, "  ", log.LstdFlags|log.Lshortfile)
)
var (
	ErrNotFound     = errors.New("Not Found")
	ErrBadRequest   = errors.New("Bad Request")
	ErrUnauthorized = errors.New("Unauthorized")
)

func handler(f func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if rv := recover(); rv != nil {
				Logger.Println("Error: handler panic!")
				writeAPIError(w, http.StatusInternalServerError)
			}
		}()
		Logger.Printf("%v: %v\n", r.Method, r.URL.Path)

		// TODO: Implement optional authentication param. Transparently handle the auth before calling the handler.
		var wb httputil.ResponseBuffer
		err := f(&wb, r)
		if err == nil {
			wb.WriteTo(w)
			return
		}

		Logger.Println("Error: ", err)
		switch err {
		case ErrNotFound:
			writeAPIError(w, http.StatusNotFound)
		case ErrBadRequest:
			writeAPIError(w, http.StatusBadRequest)
		case ErrUnauthorized:
			writeAPIError(w, http.StatusUnauthorized)
		default:
			writeAPIError(w, http.StatusInternalServerError)
		}

	}
}

func userHandler(w http.ResponseWriter, r *http.Request) error {
	switch {
	case r.Method == "POST":
		user := &User{}
		if err := json.NewDecoder(r.Body).Decode(user); err != nil {
			Logger.Println(err)
			return ErrBadRequest
		}

		err := db.UpsertUser(user)
		if err != nil {
			return err
		}

		return writeJSON(w, user, http.StatusCreated)
	case r.Method == "GET":
		id := mux.Vars(r)["id"]
		if id == "" {
			return ErrBadRequest
		}

		user := db.GetUser(id)
		if user == nil {
			return ErrNotFound
		}
		return writeJSON(w, user, http.StatusOK)
	}
	return ErrBadRequest
}

func itemHandler(w http.ResponseWriter, r *http.Request) error {
	switch {
	case r.Method == "POST":
		item := &Item{}
		if err := json.NewDecoder(r.Body).Decode(item); err != nil {
			Logger.Println(err)
			return ErrBadRequest
		}

		if err := db.UpsertItem(item); err != nil {
			return err
		}
		return writeJSON(w, item, http.StatusCreated)
	case r.Method == "GET":
		id := mux.Vars(r)["id"]
		if id == "" {
			return ErrBadRequest
		}

		item := db.GetItem(id)
		if item == nil {
			return ErrNotFound
		}

		return writeJSON(w, item, http.StatusOK)
	}
	return ErrBadRequest
}

func linkHandler(w http.ResponseWriter, r *http.Request) error {
	switch {
	case r.Method == "POST":
		// For more complex JSON decodings: http://talks.golang.org/2015/json.slide#21
		link := &Link{}
		if err := json.NewDecoder(r.Body).Decode(link); err != nil {
			return err
		}
		if link.UserId == "" || link.ItemId == "" {
			return ErrBadRequest
		}

		/*
			// This query needs to match *all* nodes, therefore is not valid
			cypher := `MERGE (u:User {id:{0}, name:{1}})
						MERGE (i:Item {id:{2}, name:{3}})
						MERGE (u)-[r:LINKED {type:{4}}]->(i)` // FIXME: No return values?
			rows, err := ctx.db.Query(cypher, link.User.Id, link.User.Name, link.Item.Id, link.Item.Name, link.Type)
		*/
		err := db.UpsertLink(link)
		if err != nil {
			return err
		}

		return writeJSON(w, link, http.StatusCreated)
	}
	return ErrBadRequest
}

// recommendHandler will manage item recommendations. It currently reads the user id and
// categories for the recommended items. Matching items must fulfil *any* category
func recommendHandler(w http.ResponseWriter, r *http.Request) error {
	uid := mux.Vars(r)["uid"]
	if uid == "" {
		return ErrBadRequest
	}

	r.ParseForm()
	categories := r.Form["category"]

	// Store binding vars in a slice
	args := []interface{}{}
	argPos := func() string {
		return strconv.Itoa(len(args) - 1) // Current var position (string)
	}

	args = append(args, uid)
	where := "WHERE u.id = {" + argPos() + "}"
	if len(categories) > 0 {
		//where = where + " AND ALL (x IN {1} WHERE x in item2.categories)"
		args = append(args, categories)
		where = where + " AND ANY (x IN {" + argPos() + "} WHERE x in item2.categories)"
	}

	cypher := `MATCH (u:User)-[:LINKED]->(item1:Item)<-[:LINKED]-(u2:User),
		(u2)-[l:LINKED]->(item2:Item)` +
		where +
		`AND NOT (u)-[:LINKED]->(item2)
		RETURN item2.id, item2.name, count(distinct l) as frequency
		ORDER BY frequency DESC`

	rows, err := db.Query(cypher, args...)
	if err != nil {
		return err
	}
	defer rows.Close()

	resp := []Recommendation{}
	for rows.Next() {
		var rec Recommendation
		err = rows.Scan(&rec.Item.Id, &rec.Item.Name, &rec.Strength)
		if err != nil {
			return err
		}
		resp = append(resp, rec)
	}

	return writeJSON(w, resp, http.StatusOK)
}

func writeJSON(w http.ResponseWriter, value interface{}, status int) error {
	w.WriteHeader(status)
	w.Header().Set("Content-Type", "application/json; charset=UTF8")
	return json.NewEncoder(w).Encode(value)
}

func writeAPIError(w http.ResponseWriter, status int) {
	var data struct {
		Error struct {
			Message string `json:"message"`
		} `json:"error"`
	}
	data.Error.Message = http.StatusText(status)
	w.WriteHeader(status)
	w.Header().Set("Content-Type", "application/json; charset=UTF8")
	json.NewEncoder(w).Encode(&data)
}

func main() {
	// TODO: Create uniqueness constraints in Graph (user id, item id)
	db = NewDatabase()

	r := mux.NewRouter()
	r.HandleFunc("/user", handler(userHandler)).Methods("POST")
	r.HandleFunc("/user/{id}", handler(userHandler)).Methods("GET")
	r.HandleFunc("/item", handler(itemHandler)).Methods("POST")
	r.HandleFunc("/item/{id}", handler(itemHandler)).Methods("GET")
	r.HandleFunc("/link", handler(linkHandler)).Methods("POST")

	r.HandleFunc("/recommend/{uid}", handler(recommendHandler)).Methods("GET")

	http.Handle("/", r)
	http.ListenAndServe(":8080", nil)
}

func Int(v int) *int {
	p := new(int)
	*p = v
	return p
}

func String(s string) *string {
	p := new(string)
	*p = s
	return p
}
<|endoftext|>


<commit_before>package irckit

import (
	"errors"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/sorcix/irc"
)

var ErrHandshakeFailed = errors.New("handshake failed")

var defaultVersion = "go-irckit"

const handshakeMsgTolerance = 20

// ID will normalize a name to be used as a unique identifier for comparison.
func ID(s string) string {
	return strings.ToLower(s)
}

type Prefixer interface {
	// Prefix returns a prefix configuration for the origin of the message.
	Prefix() *irc.Prefix
}

type Server interface {
	Prefixer

	// Name of the server (usually hostname).
	Name() string

	// Motd is the Message of the Day for the server.
	Motd() []string

	// Connect starts the handshake for a new user, blocks until it's completed or failed with an error.
	Connect(*User) error

	// Quit removes the user from all the channels and disconnects.
	Quit(*User, string)

	// HasUser returns an existing User with a given Nick.
	HasUser(string) (*User, bool)

	// RenameUser changes the Nick of a User if the new name is available.
	// Returns whether the rename was was successful.
	RenameUser(*User, string) bool

	// Channel gets or creates a new channel with the given name and Id.
	Channel(string) Channel

	// HasChannel returns an existing Channel with a given name.
	HasChannel(string) (Channel, bool)

	// UnlinkChannel removes the channel from the server's storage if it
	// exists. Once removed, the server is free to create a fresh channel with
	// the same ID. The server is not responsible for evicting members of an
	// unlinked channel.
	UnlinkChannel(Channel)

	Add(u *User) bool
	Handle(u *User)
	Logout(u *User)
	ChannelCount() int
	UserCount() int
	EncodeMessage(u *User, cmd string, params []string, trailing string) error
}

// ServerConfig produces a Server setup with configuration options.
type ServerConfig struct {
	// Name is used as the prefix for the server.
	Name string
	// Version string of the server (default: go-irckit).
	Version string
	// Motd is the message of the day for the server, list of message lines where each line should be max 80 chars.
	Motd []string
	// InviteOnly prevents regular users from joining and making new channels.
	InviteOnly bool
	// MaxNickLen is the maximum length for a NICK value (default: 32)
	MaxNickLen int

	// DiscardEmpty setting will start a goroutine to discard empty channels.
	DiscardEmpty bool
	// NewChannel overrides the constructor for a new Channel in a given Server and Name.
	NewChannel func(s Server, channelId string, name string) Channel
	// Commands is the handler registry to use (default: DefaultCommands())
	Commands Commands
}

func (c ServerConfig) Server() Server {
	if c.NewChannel == nil {
		c.NewChannel = NewChannel
	}
	if c.Commands == nil {
		c.Commands = DefaultCommands()
	}
	if c.Version == "" {
		c.Version = defaultVersion
	}
	if c.Name == "" {
		c.Name = "go-irckit"
	}
	if c.MaxNickLen == 0 {
		c.MaxNickLen = 32
	}

	srv := &server{
		config:   c,
		users:    map[string]*User{},
		channels: map[string]Channel{},
		created:  time.Now(),
		commands: c.Commands,
	}

	return srv
}

// NewServer creates a server.
func NewServer(name string) Server {
	return ServerConfig{Name: name}.Server()
}

type server struct {
	config   ServerConfig
	created  time.Time
	commands Commands

	u *User
	sync.RWMutex
	count    int
	users    map[string]*User
	channels map[string]Channel
}

func (s *server) Name() string {
	return s.config.Name
}

func (s *server) Motd() []string {
	return s.config.Motd
}

func (s *server) Close() error {
	// TODO: Send notice or something?
	// TODO: Clear channels?
	s.Lock()
	for _, u := range s.users {
		u.Close()
	}
	s.Unlock()
	return nil
}

// Prefix returns the server's command prefix string.
func (s *server) Prefix() *irc.Prefix {
	return &irc.Prefix{Name: s.config.Name}
}

// HasUser returns whether a given user is in the server.
func (s *server) HasUser(nick string) (*User, bool) {
	s.RLock()
	u, exists := s.users[ID(nick)]
	s.RUnlock()
	return u, exists
}

// Rename will attempt to rename the given user's Nick if it's available.
func (s *server) RenameUser(u *User, newNick string) bool {
	if len(newNick) > s.config.MaxNickLen {
		newNick = newNick[:s.config.MaxNickLen]
	}

	s.Lock()
	if _, exists := s.users[ID(newNick)]; exists {
		s.Unlock()
		s.EncodeMessage(u, irc.ERR_NICKNAMEINUSE, []string{newNick}, "Nickname is already in use")
		return false
	}

	delete(s.users, u.ID())
	oldPrefix := u.Prefix()
	u.Nick = newNick
	s.users[u.ID()] = u
	s.Unlock()

	changeMsg := &irc.Message{
		Prefix:  oldPrefix,
		Command: irc.NICK,
		Params:  []string{newNick},
	}
	u.Encode(changeMsg)
	for _, other := range u.VisibleTo() {
		other.Encode(changeMsg)
	}
	return true
}

// HasChannel returns whether a given channel already exists.
func (s *server) HasChannel(channelId string) (Channel, bool) {
	s.RLock()
	ch, exists := s.channels[channelId]
	s.RUnlock()
	return ch, exists
}

// Channel returns an existing or new channel with the give name.
func (s *server) Channel(channelId string) Channel {
	s.Lock()
	ch, ok := s.channels[channelId]
	if !ok {
		name := s.u.mc.GetChannelName(channelId)
		teamId := s.u.mc.GetTeamFromChannel(channelId)
		teamName := s.u.mc.GetTeamName(teamId)

		if teamName != "" && teamId != s.u.mc.Team.Id {
			name = "#" + teamName + "/" + name
		}
		if teamId == s.u.mc.Team.Id {
			name = "#" + name
		}
		if name == "" {
			name = channelId
		}
		newFn := s.config.NewChannel
		ch = newFn(s, channelId, name)
		s.channels[channelId] = ch
		s.channels[name] = ch
		s.Unlock()
	} else {
		s.Unlock()
	}
	return ch
}

// UnlinkChannel unlinks the channel from the server's storage, returns whether it existed.
func (s *server) UnlinkChannel(ch Channel) {
	s.Lock()
	chStored := s.channels[ch.String()]
	r := chStored == ch
	if r {
		delete(s.channels, ch.String())
		delete(s.channels, ch.ID())
	}
	s.Unlock()
}

// Connect starts the handshake for a new User and returns when complete or failed.
func (s *server) Connect(u *User) error {
	err := s.handshake(u)
	if err != nil {
		u.Close()
		return err
	}
	go s.handle(u)
	return nil
}

// Quit will remove the user from all channels and disconnect.
func (s *server) Quit(u *User, message string) {
	go u.Close()
	s.Lock()
	delete(s.users, u.ID())
	s.Unlock()
	if u.mc != nil {
		u.mc.Logout()
	}
}

func (s *server) guestNick() string {
	s.Lock()
	defer s.Unlock()

	s.count++
	return fmt.Sprintf("Guest%d", s.count)
}

// Len returns the number of users connected to the server.
func (s *server) Len() int {
	s.RLock()
	defer s.RUnlock()
	return len(s.users)
}

func (s *server) welcome(u *User) error {
	err := u.Encode(
		&irc.Message{
			Prefix:   s.Prefix(),
			Command:  irc.RPL_WELCOME,
			Params:   []string{u.Nick},
			Trailing: fmt.Sprintf("Welcome! %s", u.Prefix()),
		},
		&irc.Message{
			Prefix:   s.Prefix(),
			Command:  irc.RPL_YOURHOST,
			Params:   []string{u.Nick},
			Trailing: fmt.Sprintf("Your host is %s, running version %s", s.config.Name, s.config.Version),
		},
		&irc.Message{
			Prefix:   s.Prefix(),
			Command:  irc.RPL_CREATED,
			Params:   []string{u.Nick},
			Trailing: fmt.Sprintf("This server was created %s", s.created.Format(time.UnixDate)),
		},
		&irc.Message{
			Prefix:   s.Prefix(),
			Command:  irc.RPL_MYINFO,
			Params:   []string{u.Nick},
			Trailing: fmt.Sprintf("%s %s o o", s.config.Name, s.config.Version),
		},
		&irc.Message{
			Prefix:   s.Prefix(),
			Command:  irc.RPL_LUSERCLIENT,
			Params:   []string{u.Nick},
			Trailing: fmt.Sprintf("There are %d users and 0 services on 1 servers", s.Len()),
		},
	)
	if err != nil {
		return err
	}
	// Always include motd, even if it's empty? Seems some clients expect it (libpurple?).
	return CmdMotd(s, u, nil)
}

func (s *server) EncodeMessage(u *User, cmd string, params []string, trailing string) error {
	return u.Encode(&irc.Message{
		Prefix:   s.Prefix(),
		Command:  cmd,
		Params:   params,
		Trailing: trailing,
	})
}

func (s *server) Handle(u *User) {
	s.handle(u)
}

func (s *server) handle(u *User) {
	var partMsg string
	defer s.Quit(u, partMsg)

	for {
		msg, err := u.Decode()
		if err != nil {
			logger.Errorf("handle decode error for %s: %s", u.ID(), err.Error())
			return
		}
		if msg == nil {
			// Ignore empty messages
			continue
		}

		go func() {
			err = s.commands.Run(s, u, msg)
			logger.Debug("Executed", msg, err)
			if err == ErrUnknownCommand {
				// TODO: Emit event?
			} else if err != nil {
				logger.Errorf("handler error for %s: %s", u.ID(), err.Error())
			}
		}()
	}
}

func (s *server) Add(u *User) (ok bool) {
	return s.add(u)
}

func (s *server) add(u *User) (ok bool) {
	s.Lock()
	defer s.Unlock()

	id := u.ID()
	if _, exists := s.users[id]; exists {
		return false
	}

	s.users[id] = u
	return true
}

func (s *server) handshake(u *User) error {
	// Assign host
	u.Host = u.ResolveHost()

	// Read messages until we filled in USER details.
	for i := handshakeMsgTolerance; i > 0; i-- {
		// Consume N messages then give up.
		msg, err := u.Decode()
		if err != nil {
			return err
		}
		if msg == nil {
			// Empty message, ignore.
			continue
		}

		// apparently NICK message can have a : prefix on connection
		// https://github.com/42wim/matterircd/issues/32
		if msg.Command == irc.NICK && msg.Trailing != "" {
			msg.Params = append(msg.Params, msg.Trailing)
		}
		if len(msg.Params) < 1 {
			continue
		}

		switch msg.Command {
		case irc.NICK:
			u.Nick = msg.Params[0]
		case irc.USER:
			u.User = msg.Params[0]
			u.Real = msg.Trailing
		}

		if u.Nick == "" || u.User == "" {
			// Wait for both to be set before proceeding
			continue
		}
		if len(u.Nick) > s.config.MaxNickLen {
			u.Nick = u.Nick[:s.config.MaxNickLen]
		}

		ok := s.add(u)
		if !ok {
			s.EncodeMessage(u, irc.ERR_NICKNAMEINUSE, []string{u.Nick}, "Nickname is already in use")
			continue
		}
		s.u = u
		return s.welcome(u)
	}
	return ErrHandshakeFailed
}
<commit_msg>Cleanup message<commit_after>package irckit

import (
	"errors"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/sorcix/irc"
)

var ErrHandshakeFailed = errors.New("handshake failed")

var defaultVersion = "go-irckit"

const handshakeMsgTolerance = 20

// ID will normalize a name to be used as a unique identifier for comparison.
func ID(s string) string {
	return strings.ToLower(s)
}

type Prefixer interface {
	// Prefix returns a prefix configuration for the origin of the message.
	Prefix() *irc.Prefix
}

type Server interface {
	Prefixer

	// Name of the server (usually hostname).
	Name() string

	// Motd is the Message of the Day for the server.
	Motd() []string

	// Connect starts the handshake for a new user, blocks until it's completed or failed with an error.
	Connect(*User) error

	// Quit removes the user from all the channels and disconnects.
	Quit(*User, string)

	// HasUser returns an existing User with a given Nick.
	HasUser(string) (*User, bool)

	// RenameUser changes the Nick of a User if the new name is available.
	// Returns whether the rename was was successful.
	RenameUser(*User, string) bool

	// Channel gets or creates a new channel with the given name and Id.
	Channel(string) Channel

	// HasChannel returns an existing Channel with a given name.
	HasChannel(string) (Channel, bool)

	// UnlinkChannel removes the channel from the server's storage if it
	// exists. Once removed, the server is free to create a fresh channel with
	// the same ID. The server is not responsible for evicting members of an
	// unlinked channel.
	UnlinkChannel(Channel)

	Add(u *User) bool
	Handle(u *User)
	Logout(u *User)
	ChannelCount() int
	UserCount() int
	EncodeMessage(u *User, cmd string, params []string, trailing string) error
}

// ServerConfig produces a Server setup with configuration options.
type ServerConfig struct {
	// Name is used as the prefix for the server.
	Name string
	// Version string of the server (default: go-irckit).
	Version string
	// Motd is the message of the day for the server, list of message lines where each line should be max 80 chars.
	Motd []string
	// InviteOnly prevents regular users from joining and making new channels.
	InviteOnly bool
	// MaxNickLen is the maximum length for a NICK value (default: 32)
	MaxNickLen int

	// DiscardEmpty setting will start a goroutine to discard empty channels.
	DiscardEmpty bool
	// NewChannel overrides the constructor for a new Channel in a given Server and Name.
	NewChannel func(s Server, channelId string, name string) Channel
	// Commands is the handler registry to use (default: DefaultCommands())
	Commands Commands
}

func (c ServerConfig) Server() Server {
	if c.NewChannel == nil {
		c.NewChannel = NewChannel
	}
	if c.Commands == nil {
		c.Commands = DefaultCommands()
	}
	if c.Version == "" {
		c.Version = defaultVersion
	}
	if c.Name == "" {
		c.Name = "go-irckit"
	}
	if c.MaxNickLen == 0 {
		c.MaxNickLen = 32
	}

	srv := &server{
		config:   c,
		users:    map[string]*User{},
		channels: map[string]Channel{},
		created:  time.Now(),
		commands: c.Commands,
	}

	return srv
}

// NewServer creates a server.
func NewServer(name string) Server {
	return ServerConfig{Name: name}.Server()
}

type server struct {
	config   ServerConfig
	created  time.Time
	commands Commands

	u *User
	sync.RWMutex
	count    int
	users    map[string]*User
	channels map[string]Channel
}

func (s *server) Name() string {
	return s.config.Name
}

func (s *server) Motd() []string {
	return s.config.Motd
}

func (s *server) Close() error {
	// TODO: Send notice or something?
	// TODO: Clear channels?
	s.Lock()
	for _, u := range s.users {
		u.Close()
	}
	s.Unlock()
	return nil
}

// Prefix returns the server's command prefix string.
func (s *server) Prefix() *irc.Prefix {
	return &irc.Prefix{Name: s.config.Name}
}

// HasUser returns whether a given user is in the server.
func (s *server) HasUser(nick string) (*User, bool) {
	s.RLock()
	u, exists := s.users[ID(nick)]
	s.RUnlock()
	return u, exists
}

// Rename will attempt to rename the given user's Nick if it's available.
func (s *server) RenameUser(u *User, newNick string) bool {
	if len(newNick) > s.config.MaxNickLen {
		newNick = newNick[:s.config.MaxNickLen]
	}

	s.Lock()
	if _, exists := s.users[ID(newNick)]; exists {
		s.Unlock()
		s.EncodeMessage(u, irc.ERR_NICKNAMEINUSE, []string{newNick}, "Nickname is already in use")
		return false
	}

	delete(s.users, u.ID())
	oldPrefix := u.Prefix()
	u.Nick = newNick
	s.users[u.ID()] = u
	s.Unlock()

	changeMsg := &irc.Message{
		Prefix:  oldPrefix,
		Command: irc.NICK,
		Params:  []string{newNick},
	}
	u.Encode(changeMsg)
	for _, other := range u.VisibleTo() {
		other.Encode(changeMsg)
	}
	return true
}

// HasChannel returns whether a given channel already exists.
func (s *server) HasChannel(channelId string) (Channel, bool) {
	s.RLock()
	ch, exists := s.channels[channelId]
	s.RUnlock()
	return ch, exists
}

// Channel returns an existing or new channel with the give name.
func (s *server) Channel(channelId string) Channel {
	s.Lock()
	ch, ok := s.channels[channelId]
	if !ok {
		name := s.u.mc.GetChannelName(channelId)
		teamId := s.u.mc.GetTeamFromChannel(channelId)
		teamName := s.u.mc.GetTeamName(teamId)

		if teamName != "" && teamId != s.u.mc.Team.Id {
			name = "#" + teamName + "/" + name
		}
		if teamId == s.u.mc.Team.Id {
			name = "#" + name
		}
		if name == "" {
			name = channelId
		}
		newFn := s.config.NewChannel
		ch = newFn(s, channelId, name)
		s.channels[channelId] = ch
		s.channels[name] = ch
		s.Unlock()
	} else {
		s.Unlock()
	}
	return ch
}

// UnlinkChannel unlinks the channel from the server's storage, returns whether it existed.
func (s *server) UnlinkChannel(ch Channel) {
	s.Lock()
	chStored := s.channels[ch.String()]
	r := chStored == ch
	if r {
		delete(s.channels, ch.String())
		delete(s.channels, ch.ID())
	}
	s.Unlock()
}

// Connect starts the handshake for a new User and returns when complete or failed.
func (s *server) Connect(u *User) error {
	err := s.handshake(u)
	if err != nil {
		u.Close()
		return err
	}
	go s.handle(u)
	return nil
}

// Quit will remove the user from all channels and disconnect.
func (s *server) Quit(u *User, message string) {
	go u.Close()
	s.Lock()
	delete(s.users, u.ID())
	s.Unlock()
	if u.mc != nil {
		u.mc.Logout()
	}
}

func (s *server) guestNick() string {
	s.Lock()
	defer s.Unlock()

	s.count++
	return fmt.Sprintf("Guest%d", s.count)
}

// Len returns the number of users connected to the server.
func (s *server) Len() int {
	s.RLock()
	defer s.RUnlock()
	return len(s.users)
}

func (s *server) welcome(u *User) error {
	err := u.Encode(
		&irc.Message{
			Prefix:   s.Prefix(),
			Command:  irc.RPL_WELCOME,
			Params:   []string{u.Nick},
			Trailing: fmt.Sprintf("Welcome! %s", u.Prefix()),
		},
		&irc.Message{
			Prefix:   s.Prefix(),
			Command:  irc.RPL_YOURHOST,
			Params:   []string{u.Nick},
			Trailing: fmt.Sprintf("Your host is %s, running version %s", s.config.Name, s.config.Version),
		},
		&irc.Message{
			Prefix:   s.Prefix(),
			Command:  irc.RPL_CREATED,
			Params:   []string{u.Nick},
			Trailing: fmt.Sprintf("This server was created %s", s.created.Format(time.UnixDate)),
		},
		&irc.Message{
			Prefix:   s.Prefix(),
			Command:  irc.RPL_MYINFO,
			Params:   []string{u.Nick},
			Trailing: fmt.Sprintf("%s %s o o", s.config.Name, s.config.Version),
		},
		&irc.Message{
			Prefix:   s.Prefix(),
			Command:  irc.RPL_LUSERCLIENT,
			Params:   []string{u.Nick},
			Trailing: fmt.Sprintf("There are %d users and 0 services on 1 servers", s.Len()),
		},
	)
	if err != nil {
		return err
	}
	// Always include motd, even if it's empty? Seems some clients expect it (libpurple?).
	return CmdMotd(s, u, nil)
}

func (s *server) EncodeMessage(u *User, cmd string, params []string, trailing string) error {
	return u.Encode(&irc.Message{
		Prefix:   s.Prefix(),
		Command:  cmd,
		Params:   params,
		Trailing: trailing,
	})
}

func (s *server) Handle(u *User) {
	s.handle(u)
}

func (s *server) handle(u *User) {
	var partMsg string
	defer s.Quit(u, partMsg)

	for {
		msg, err := u.Decode()
		if err != nil {
			logger.Errorf("handle decode error for %s: %s", u.ID(), err.Error())
			return
		}
		if msg == nil {
			// Ignore empty messages
			continue
		}

		go func() {
			err = s.commands.Run(s, u, msg)
			logger.Debugf("Executed %#v %#v", msg, err)
			if err == ErrUnknownCommand {
				// TODO: Emit event?
			} else if err != nil {
				logger.Errorf("handler error for %s: %s", u.ID(), err.Error())
			}
		}()
	}
}

func (s *server) Add(u *User) (ok bool) {
	return s.add(u)
}

func (s *server) add(u *User) (ok bool) {
	s.Lock()
	defer s.Unlock()

	id := u.ID()
	if _, exists := s.users[id]; exists {
		return false
	}

	s.users[id] = u
	return true
}

func (s *server) handshake(u *User) error {
	// Assign host
	u.Host = u.ResolveHost()

	// Read messages until we filled in USER details.
	for i := handshakeMsgTolerance; i > 0; i-- {
		// Consume N messages then give up.
		msg, err := u.Decode()
		if err != nil {
			return err
		}
		if msg == nil {
			// Empty message, ignore.
			continue
		}

		// apparently NICK message can have a : prefix on connection
		// https://github.com/42wim/matterircd/issues/32
		if msg.Command == irc.NICK && msg.Trailing != "" {
			msg.Params = append(msg.Params, msg.Trailing)
		}
		if len(msg.Params) < 1 {
			continue
		}

		switch msg.Command {
		case irc.NICK:
			u.Nick = msg.Params[0]
		case irc.USER:
			u.User = msg.Params[0]
			u.Real = msg.Trailing
		}

		if u.Nick == "" || u.User == "" {
			// Wait for both to be set before proceeding
			continue
		}
		if len(u.Nick) > s.config.MaxNickLen {
			u.Nick = u.Nick[:s.config.MaxNickLen]
		}

		ok := s.add(u)
		if !ok {
			s.EncodeMessage(u, irc.ERR_NICKNAMEINUSE, []string{u.Nick}, "Nickname is already in use")
			continue
		}
		s.u = u
		return s.welcome(u)
	}
	return ErrHandshakeFailed
}
<|endoftext|>


<commit_before>package httpow

import (
	"errors"
	"net"
	"net/http"
	"sync"
	"time"
)

// Server traps http.Server, exposes additional fuctionality
type Server struct {
	// IdleTimeout tells how long connection can be idle between requests.
	// Value of http.Server.ReadTimeout taken from the passed server instance
	// will be used as a single request read timeout.
	IdleTimeout time.Duration

	// NoNewIdle controls whether new connection can be idle before issuing
	// a request. By default new connections will have IdleTimeout allowing for
	// idle period before issuing a request. However, if this flag is set, new
	// connections will be given ReadTimeout as the request was already initiated.
	NoNewIdle bool

	server   *http.Server // wrapped http server
	listener net.Listener

	lock sync.Mutex

	// conns is a map of connections which indicates whether connection is active.
	// Connection is active when is processeing a request (after headers parsing).
	conns map[net.Conn]bool
}

// NewServer wraps http.Server, which should be already configured.
func NewServer(server *http.Server) *Server {
	return &Server{server: server, conns: make(map[net.Conn]bool)}
}

// Serve behaves as http.Server.Serve on the wrapped server instance
func (s *Server) Serve(l net.Listener) error {
	if s.IdleTimeout != 0 {
		reqTimeout := s.server.ReadTimeout

		// Disable read timeout managment by http.Server
		s.server.ReadTimeout = 0

		// Wrap with custom listener
		l = &rtListener{
			Listener:    l,
			reqTimeout:  reqTimeout,
			idleTimeout: s.IdleTimeout,
			newAsReq:    s.NoNewIdle,
		}
	}

	// Add ConnState callback, but make sure the one provided by user is called as well
	oldConnState := s.server.ConnState
	s.server.ConnState = func(c net.Conn, state http.ConnState) {
		s.updateConnMap(c, state)

		if state == http.StateIdle {
			if c, ok := c.(*rtConn); ok {
				c.setIdle()
			}
		}

		// Pass to original handler
		if oldConnState != nil {
			oldConnState(c, state)
		}
	}

	err := s.server.Serve(l)
	if err == errListenerClosed {
		err = nil
	}
	return err
}

// Close server
func (s *Server) Close() {
	s.listener.Close()
}

func (s *Server) updateConnMap(c net.Conn, state http.ConnState) {
	s.lock.Lock()
	defer s.lock.Unlock()

	switch state {
	case http.StateNew, http.StateIdle:
		s.conns[c] = false
	case http.StateClosed, http.StateHijacked:
		delete(s.conns, c)
	case http.StateActive:
		s.conns[c] = true
	}
}

type rtListener struct {
	net.Listener

	reqTimeout  time.Duration // timeout for processing a single request
	idleTimeout time.Duration // idle timeout
	newAsReq    bool          // set new connections to reqTimeout

	mx     sync.Mutex
	closed bool
}

// This error will be proagated to Serve when we delibaretely close the listener
var errListenerClosed = errors.New("listener closed")

func (l *rtListener) Accept() (c net.Conn, err error) {
	c, err = l.Listener.Accept()
	if c != nil {
		c = newRtConn(c, l.reqTimeout, l.idleTimeout, l.newAsReq)
	}
	if err != nil {
		l.mx.Lock()
		if l.closed {
			err = errListenerClosed
		}
		l.mx.Unlock()
	}
	return
}

func (l *rtListener) Close() (err error) {
	l.mx.Lock()
	l.closed = true
	l.mx.Unlock()
	return l.Listener.Close()
}

// rtConn is a net.Conn that sets read deadlines for idle and active state.
// It automatically detects requests as first bytes are read after idle state.
type rtConn struct {
	net.Conn

	reqTimeout  time.Duration // timeout for processing a single request
	idleTimeout time.Duration // idle timeout
	active      bool          // are we currently processing a request?
}

func newRtConn(c net.Conn, rto time.Duration, ito time.Duration, active bool) *rtConn {
	rc := &rtConn{c, rto, ito, active}
	if active {
		rc.setActive()
	} else {
		rc.setIdle()
	}
	return rc
}

func (c *rtConn) Read(b []byte) (n int, err error) {
	n, err = c.Conn.Read(b)
	if n > 0 && !c.active {
		c.setActive()
	}
	return
}

func (c *rtConn) setActive() {
	c.active = true
	_ = c.Conn.SetReadDeadline(time.Now().Add(c.reqTimeout))
}

func (c *rtConn) setIdle() {
	c.active = false
	_ = c.Conn.SetReadDeadline(time.Now().Add(c.idleTimeout))
}
<commit_msg>Set deadline for all inactive connections<commit_after>package httpow

import (
	"errors"
	"net"
	"net/http"
	"sync"
	"time"
)

// Server traps http.Server, exposes additional fuctionality
type Server struct {
	// IdleTimeout tells how long connection can be idle between requests.
	// Value of http.Server.ReadTimeout taken from the passed server instance
	// will be used as a single request read timeout.
	IdleTimeout time.Duration

	// NoNewIdle controls whether new connection can be idle before issuing
	// a request. By default new connections will have IdleTimeout allowing for
	// idle period before issuing a request. However, if this flag is set, new
	// connections will be given ReadTimeout as the request was already initiated.
	NoNewIdle bool

	server   *http.Server // wrapped http server
	listener net.Listener

	lock sync.Mutex

	// conns is a map of connections which indicates whether connection is active.
	// Connection is active when is processeing a request (after headers parsing).
	conns map[net.Conn]bool
}

// NewServer wraps http.Server, which should be already configured.
func NewServer(server *http.Server) *Server {
	return &Server{server: server, conns: make(map[net.Conn]bool)}
}

// Serve behaves as http.Server.Serve on the wrapped server instance
func (s *Server) Serve(l net.Listener) error {
	if s.IdleTimeout != 0 {
		reqTimeout := s.server.ReadTimeout

		// Disable read timeout managment by http.Server
		s.server.ReadTimeout = 0

		// Wrap with custom listener
		l = &rtListener{
			Listener:    l,
			reqTimeout:  reqTimeout,
			idleTimeout: s.IdleTimeout,
			newAsReq:    s.NoNewIdle,
		}
	}

	// Add ConnState callback, but make sure the one provided by user is called as well
	oldConnState := s.server.ConnState
	s.server.ConnState = func(c net.Conn, state http.ConnState) {
		s.updateConnMap(c, state)

		if state == http.StateIdle {
			if c, ok := c.(*rtConn); ok {
				c.setIdle()
			}
		}

		// Pass to original handler
		if oldConnState != nil {
			oldConnState(c, state)
		}
	}

	err := s.server.Serve(l)
	if err == errListenerClosed {
		err = nil
	}
	return err
}

// Close server
func (s *Server) Close() {
	s.lock.Lock()
	defer s.lock.Unlock()

	s.listener.Close()
	s.server.SetKeepAlivesEnabled(false)

	// Set a 100ms deadline for all incactive connections.
	// We could normally just close the connection, but some of them might be
	// processing headers, so we want to give some time to handle new request.
	deadline := time.Now().Add(100 * time.Millisecond)
	for c, active := range s.conns {
		if !active {
			c.SetReadDeadline(deadline)
		}
	}
}

func (s *Server) updateConnMap(c net.Conn, state http.ConnState) {
	s.lock.Lock()
	defer s.lock.Unlock()

	switch state {
	case http.StateNew, http.StateIdle:
		s.conns[c] = false
	case http.StateClosed, http.StateHijacked:
		delete(s.conns, c)
	case http.StateActive:
		s.conns[c] = true
	}
}

type rtListener struct {
	net.Listener

	reqTimeout  time.Duration // timeout for processing a single request
	idleTimeout time.Duration // idle timeout
	newAsReq    bool          // set new connections to reqTimeout

	mx     sync.Mutex
	closed bool
}

// This error will be proagated to Serve when we delibaretely close the listener
var errListenerClosed = errors.New("listener closed")

func (l *rtListener) Accept() (c net.Conn, err error) {
	c, err = l.Listener.Accept()
	if c != nil {
		c = newRtConn(c, l.reqTimeout, l.idleTimeout, l.newAsReq)
	}
	if err != nil {
		l.mx.Lock()
		if l.closed {
			err = errListenerClosed
		}
		l.mx.Unlock()
	}
	return
}

func (l *rtListener) Close() (err error) {
	l.mx.Lock()
	l.closed = true
	l.mx.Unlock()
	return l.Listener.Close()
}

// rtConn is a net.Conn that sets read deadlines for idle and active state.
// It automatically detects requests as first bytes are read after idle state.
type rtConn struct {
	net.Conn

	reqTimeout  time.Duration // timeout for processing a single request
	idleTimeout time.Duration // idle timeout
	active      bool          // are we currently processing a request?
}

func newRtConn(c net.Conn, rto time.Duration, ito time.Duration, active bool) *rtConn {
	rc := &rtConn{c, rto, ito, active}
	if active {
		rc.setActive()
	} else {
		rc.setIdle()
	}
	return rc
}

func (c *rtConn) Read(b []byte) (n int, err error) {
	n, err = c.Conn.Read(b)
	if n > 0 && !c.active {
		c.setActive()
	}
	return
}

func (c *rtConn) setActive() {
	c.active = true
	_ = c.Conn.SetReadDeadline(time.Now().Add(c.reqTimeout))
}

func (c *rtConn) setIdle() {
	c.active = false
	_ = c.Conn.SetReadDeadline(time.Now().Add(c.idleTimeout))
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"sync"
	"time"
)

var (
	green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
	red   = string([]byte{27, 91, 57, 55, 59, 52, 49, 109})
	reset = string([]byte{27, 91, 48, 109})
	white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109})
)

type Server struct {
	Name      string
	Address   string
	Interval  int
	alerts    []Alert
	log       *log.Logger
	service   *Service
	failCount int
	LastEvent *Event
	wg        sync.WaitGroup
}

func (s *Service) AddServer(name string, address string, interval int, alertNames []string) {

	alerts := []Alert{}
	for _, alertName := range alertNames {
		alerts = append(alerts, s.GetAlert(alertName))
	}

	var wg sync.WaitGroup
	s.servers = append(s.servers, &Server{
		Name:     name,
		Address:  address,
		Interval: interval,
		alerts:   alerts,
		log:      log.New(os.Stdout, name+" ", log.Ldate|log.Ltime),
		service:  s,
		wg:       wg,
	})

}

func (s *Server) Ping() (time.Duration, error) {

	startTime := time.Now()
	s.log.Println("Pinging: ", s.Name)

	req, err := http.NewRequest("GET", s.Address, nil)
	if err != nil {
		return 0, errors.New("redalert ping: failed parsing url in http.NewRequest " + err.Error())
	}

	req.Header.Add("User-Agent", "Redalert/1.0")
	client := &http.Client{}
	resp, err := client.Do(req)

	endTime := time.Now()
	latency := endTime.Sub(startTime)
	s.log.Println(white, "Analytics: ", latency, reset)

	if err != nil {
		return latency, errors.New("redalert ping: failed client.Do " + err.Error())
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return latency, errors.New("redalert ping: non-200 status code. status code was " + strconv.Itoa(resp.StatusCode))
	}
	s.log.Println(green, "OK", reset, s.Name)

	return latency, nil
}

func (s *Server) SchedulePing(stopChan chan bool) {

	go func() {

		var err error
		var event *Event
		var latency time.Duration

		originalDelay := time.Second * time.Duration(s.Interval)
		delay := time.Second * time.Duration(s.Interval)

		for {

			latency, err = s.Ping()

			if err != nil {

				s.log.Println(red, "ERROR: ", err, reset, s.Name)
				event = NewRedAlert(s, latency)
				s.LastEvent = event

				s.TriggerAlerts(event)

				s.IncrFailCount()
				if s.failCount > 0 {
					delay = time.Second * time.Duration(s.failCount*s.Interval)
				}

			} else {

				event = NewGreenAlert(s, latency)
				isRedalertRecovery := s.LastEvent != nil && s.LastEvent.isRedAlert()
				s.LastEvent = event
				if isRedalertRecovery {
					s.log.Println(green, "RECOVERY: ", reset, s.Name)
					s.TriggerAlerts(event)
				}

				delay = originalDelay
				s.failCount = 0

			}

			select {
			case <-time.After(delay):
			case <-stopChan:
				return
			}
		}
	}()

}

func (s *Server) Monitor() {

	s.service.wg.Add(1)
	s.wg.Add(1)

	stopScheduler := make(chan bool)
	s.SchedulePing(stopScheduler)

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, os.Interrupt)
	go func() {
		for _ = range sigChan {
			stopScheduler <- true
			s.wg.Done()
		}
	}()

	s.wg.Wait()

	s.service.wg.Done()

}

func (s *Server) TriggerAlerts(event *Event) {

	go func() {

		var currentAlert Alert

		defer func() {
			if r := recover(); r != nil {
				s.log.Println(red, "CRITICAL: Failure triggering alert ["+currentAlert.Name()+"] due to : ", r, reset)
			}
		}()

		var err error
		for _, alert := range s.alerts {
			currentAlert = alert
			err = alert.Trigger(event)
			if err != nil {
				s.log.Fatalf("Error triggering alert %v", err)
			}
		}

	}()
}

func (s *Server) IncrFailCount() {
	s.failCount++
}
<commit_msg>Remove duplicate server name in log.<commit_after>package main

import (
	"errors"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"sync"
	"time"
)

var (
	green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
	red   = string([]byte{27, 91, 57, 55, 59, 52, 49, 109})
	reset = string([]byte{27, 91, 48, 109})
	white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109})
)

type Server struct {
	Name      string
	Address   string
	Interval  int
	alerts    []Alert
	log       *log.Logger
	service   *Service
	failCount int
	LastEvent *Event
	wg        sync.WaitGroup
}

func (s *Service) AddServer(name string, address string, interval int, alertNames []string) {

	alerts := []Alert{}
	for _, alertName := range alertNames {
		alerts = append(alerts, s.GetAlert(alertName))
	}

	var wg sync.WaitGroup
	s.servers = append(s.servers, &Server{
		Name:     name,
		Address:  address,
		Interval: interval,
		alerts:   alerts,
		log:      log.New(os.Stdout, name+" ", log.Ldate|log.Ltime),
		service:  s,
		wg:       wg,
	})

}

func (s *Server) Ping() (time.Duration, error) {

	startTime := time.Now()
	s.log.Println("Pinging: ", s.Name)

	req, err := http.NewRequest("GET", s.Address, nil)
	if err != nil {
		return 0, errors.New("redalert ping: failed parsing url in http.NewRequest " + err.Error())
	}

	req.Header.Add("User-Agent", "Redalert/1.0")
	client := &http.Client{}
	resp, err := client.Do(req)

	endTime := time.Now()
	latency := endTime.Sub(startTime)
	s.log.Println(white, "Analytics: ", latency, reset)

	if err != nil {
		return latency, errors.New("redalert ping: failed client.Do " + err.Error())
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return latency, errors.New("redalert ping: non-200 status code. status code was " + strconv.Itoa(resp.StatusCode))
	}
	s.log.Println(green, "OK", reset, s.Name)

	return latency, nil
}

func (s *Server) SchedulePing(stopChan chan bool) {

	go func() {

		var err error
		var event *Event
		var latency time.Duration

		originalDelay := time.Second * time.Duration(s.Interval)
		delay := time.Second * time.Duration(s.Interval)

		for {

			latency, err = s.Ping()

			if err != nil {

				s.log.Println(red, "ERROR: ", err, reset)
				event = NewRedAlert(s, latency)
				s.LastEvent = event

				s.TriggerAlerts(event)

				s.IncrFailCount()
				if s.failCount > 0 {
					delay = time.Second * time.Duration(s.failCount*s.Interval)
				}

			} else {

				event = NewGreenAlert(s, latency)
				isRedalertRecovery := s.LastEvent != nil && s.LastEvent.isRedAlert()
				s.LastEvent = event
				if isRedalertRecovery {
					s.log.Println(green, "RECOVERY: ", reset, s.Name)
					s.TriggerAlerts(event)
				}

				delay = originalDelay
				s.failCount = 0

			}

			select {
			case <-time.After(delay):
			case <-stopChan:
				return
			}
		}
	}()

}

func (s *Server) Monitor() {

	s.service.wg.Add(1)
	s.wg.Add(1)

	stopScheduler := make(chan bool)
	s.SchedulePing(stopScheduler)

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, os.Interrupt)
	go func() {
		for _ = range sigChan {
			stopScheduler <- true
			s.wg.Done()
		}
	}()

	s.wg.Wait()

	s.service.wg.Done()

}

func (s *Server) TriggerAlerts(event *Event) {

	go func() {

		var currentAlert Alert

		defer func() {
			if r := recover(); r != nil {
				s.log.Println(red, "CRITICAL: Failure triggering alert ["+currentAlert.Name()+"] due to : ", r, reset)
			}
		}()

		var err error
		for _, alert := range s.alerts {
			currentAlert = alert
			err = alert.Trigger(event)
			if err != nil {
				s.log.Fatalf("Error triggering alert %v", err)
			}
		}

	}()
}

func (s *Server) IncrFailCount() {
	s.failCount++
}
<|endoftext|>


<commit_before>package httpteleport

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"github.com/valyala/fasthttp"
	"github.com/valyala/tcplisten"
	"io"
	"log"
	"net"
	"os"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

// Server is a server accepting requests from httpteleport Client.
type Server struct {
	// Handler must process incoming http requests.
	Handler fasthttp.RequestHandler

	// CompressType is the compression type used for responses.
	//
	// CompressFlate is used by default.
	CompressType CompressType

	// Concurrency is the maximum number of concurrent goroutines
	// with Server.Handler the server may run.
	//
	// DefaultConcurrency is used by default.
	Concurrency int

	// TLSConfig is TLS (aka SSL) config used for accepting encrypted
	// client connections.
	//
	// Encrypted connections may be used for transferring sensitive
	// information over untrusted networks.
	//
	// By default server accepts only unencrypted connections.
	TLSConfig *tls.Config

	// MaxBatchDelay is the maximum duration before ready responses
	// are sent to the client.
	//
	// Responses' batching may reduce network bandwidth usage and CPU usage.
	//
	// By default responses are sent immediately to the client.
	MaxBatchDelay time.Duration

	// Maximum duration for reading the full request (including body).
	//
	// This also limits the maximum lifetime for idle connections.
	//
	// By default request read timeout is unlimited.
	ReadTimeout time.Duration

	// Maximum duration for writing the full response (including body).
	//
	// By default response write timeout is unlimited.
	WriteTimeout time.Duration

	// ReduceMemoryUsage leads to reduced memory usage at the cost
	// of higher CPU usage if set to true.
	//
	// Memory usage reduction is disabled by default.
	ReduceMemoryUsage bool

	// ReadBufferSize is the size for read buffer.
	//
	// DefaultReadBufferSize is used by default.
	ReadBufferSize int

	// WriteBufferSize is the size for write buffer.
	//
	// DefaultWriteBufferSize is used by default.
	WriteBufferSize int

	// Logger, which is used by RequestCtx.Logger().
	//
	// Standard logger from log package is used by default.
	Logger fasthttp.Logger

	workItemPool sync.Pool

	concurrencyCount uint32
}

func (s *Server) concurrency() int {
	concurrency := s.Concurrency
	if concurrency <= 0 {
		concurrency = DefaultConcurrency
	}
	return concurrency
}

// ListenAndServe serves httpteleport requests accepted from the given
// TCP address.
func (s *Server) ListenAndServe(addr string) error {
	var cfg tcplisten.Config
	ln, err := cfg.NewListener("tcp4", addr)
	if err != nil {
		return err
	}
	return s.Serve(ln)
}

// Serve serves httpteleport requests accepted from the given listener.
func (s *Server) Serve(ln net.Listener) error {
	for {
		conn, err := ln.Accept()
		if err != nil {
			if conn != nil {
				panic("BUG: net.Listener returned non-nil conn and non-nil error")
			}
			if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
				s.logger().Printf("httpteleport.Server: temporary error when accepting new connections: %s", netErr)
				time.Sleep(time.Second)
				continue
			}
			if err != io.EOF && !strings.Contains(err.Error(), "use of closed network connection") {
				s.logger().Printf("httpteleport.Server: permanent error when accepting new connections: %s", err)
				return err
			}
			return nil
		}
		if conn == nil {
			panic("BUG: net.Listener returned (nil, nil)")
		}

		go func() {
			laddr := conn.LocalAddr().String()
			raddr := conn.RemoteAddr().String()
			if err := s.serveConn(conn); err != nil {
				s.logger().Printf("httpteleport.Server: error on connection %q<->%q: %s", laddr, raddr, err)
			}
		}()
	}
}

func (s *Server) serveConn(conn net.Conn) error {
	br, bw, err := newBufioConn(conn, s.ReadBufferSize, s.WriteBufferSize, s.CompressType, s.TLSConfig, true)
	if err != nil {
		conn.Close()
		return err
	}

	stopCh := make(chan struct{})

	pendingResponses := make(chan *serverWorkItem, s.concurrency())
	readerDone := make(chan error, 1)
	go func() {
		readerDone <- s.connReader(br, conn, pendingResponses, stopCh)
	}()

	writerDone := make(chan error, 1)
	go func() {
		writerDone <- s.connWriter(bw, conn, pendingResponses, stopCh)
	}()

	select {
	case err = <-readerDone:
		conn.Close()
		close(stopCh)
		<-writerDone
	case err = <-writerDone:
		conn.Close()
		close(stopCh)
		<-readerDone
	}
	return err
}

func (s *Server) connReader(br *bufio.Reader, conn net.Conn, pendingResponses chan<- *serverWorkItem, stopCh <-chan struct{}) error {
	handler := s.Handler
	if handler == nil {
		panic("BUG: Server.Handler must be set")
	}
	logger := s.logger()
	concurrency := s.concurrency()
	reduceMemoryUsage := s.ReduceMemoryUsage
	readTimeout := s.ReadTimeout
	var lastReadDeadline time.Time
	for {
		wi := s.acquireWorkItem()

		if readTimeout > 0 {
			// Optimization: update read deadline only if more than 25%
			// of the last read deadline exceeded.
			// See https://github.com/golang/go/issues/15133 for details.
			t := time.Now()
			if t.Sub(lastReadDeadline) > (readTimeout >> 2) {
				if err := conn.SetReadDeadline(t.Add(readTimeout)); err != nil {
					return fmt.Errorf("cannot update read deadline: %s", err)
				}
				lastReadDeadline = t
			}
		}

		if n, err := io.ReadFull(br, wi.reqID[:]); err != nil {
			if n == 0 {
				// Ignore error if no bytes are read, since
				// the client may just close the connection.
				return nil
			}
			return fmt.Errorf("cannot read request ID: %s", err)
		}

		wi.ctx.Init2(conn, logger, reduceMemoryUsage)

		if err := wi.ctx.Request.Read(br); err != nil {
			return fmt.Errorf("cannot read request: %s", err)
		}

		n := int(atomic.AddUint32(&s.concurrencyCount, 1))
		if n > concurrency {
			atomic.AddUint32(&s.concurrencyCount, ^uint32(0))

			fmt.Fprintf(&wi.ctx, "concurrency limit exceeded: %d. Increase Server.Concurrency or decrease load on the server", concurrency)
			wi.ctx.SetStatusCode(fasthttp.StatusTooManyRequests)

			select {
			case pendingResponses <- wi:
			default:
				select {
				case pendingResponses <- wi:
				case <-stopCh:
					return nil
				}
			}
			continue
		}

		go func() {
			handler(&wi.ctx)
			if wi.ctx.IsBodyStream() {
				panic("chunked responses aren't supported")
			}

			// Request is no longer needed, so reset it in order
			// to free up resources occupied by the request.
			wi.ctx.Request.Reset()

			select {
			case pendingResponses <- wi:
			default:
				select {
				case pendingResponses <- wi:
				case <-stopCh:
				}
			}

			atomic.AddUint32(&s.concurrencyCount, ^uint32(0))
		}()
	}
}

func (s *Server) connWriter(bw *bufio.Writer, conn net.Conn, pendingResponses <-chan *serverWorkItem, stopCh <-chan struct{}) error {
	var wi *serverWorkItem

	var (
		flushTimer    = time.NewTimer(time.Hour * 24 * 30)
		flushCh       <-chan time.Time
		flushAlwaysCh = make(chan time.Time)
	)
	close(flushAlwaysCh)
	maxBatchDelay := s.MaxBatchDelay
	if maxBatchDelay < 0 {
		maxBatchDelay = 0
	}

	writeTimeout := s.WriteTimeout
	var lastWriteDeadline time.Time
	for {
		select {
		case wi = <-pendingResponses:
		default:
			select {
			case wi = <-pendingResponses:
			case <-stopCh:
				return nil
			case <-flushCh:
				if err := bw.Flush(); err != nil {
					return fmt.Errorf("cannot flush response data to client: %s", err)
				}
				flushCh = nil
				continue
			}
		}

		if writeTimeout > 0 {
			// Optimization: update write deadline only if more than 25%
			// of the last write deadline exceeded.
			// See https://github.com/golang/go/issues/15133 for details.
			t := time.Now()
			if t.Sub(lastWriteDeadline) > (writeTimeout >> 2) {
				if err := conn.SetReadDeadline(t.Add(writeTimeout)); err != nil {
					return fmt.Errorf("cannot update write deadline: %s", err)
				}
				lastWriteDeadline = t
			}
		}

		if _, err := bw.Write(wi.reqID[:]); err != nil {
			return fmt.Errorf("cannot write response ID: %s", err)
		}
		if err := wi.ctx.Response.Write(bw); err != nil {
			return fmt.Errorf("cannot write response: %s", err)
		}

		// Response is no longer needed, so reset it in order to release
		// resources occupied by the response.
		wi.ctx.Response.Reset()

		s.releaseWorkItem(wi)

		// re-arm flush channel
		if len(pendingResponses) == 0 {
			if maxBatchDelay > 0 {
				if !flushTimer.Stop() {
					select {
					case <-flushTimer.C:
					default:
					}
				}
				flushTimer.Reset(maxBatchDelay)
				flushCh = flushTimer.C
			} else {
				flushCh = flushAlwaysCh
			}
		}
	}
}

type serverWorkItem struct {
	ctx   fasthttp.RequestCtx
	reqID [4]byte
}

func (s *Server) acquireWorkItem() *serverWorkItem {
	v := s.workItemPool.Get()
	if v == nil {
		return &serverWorkItem{}
	}
	return v.(*serverWorkItem)
}

func (s *Server) releaseWorkItem(wi *serverWorkItem) {
	s.workItemPool.Put(wi)
}

var defaultLogger = log.New(os.Stderr, "", log.LstdFlags)

func (s *Server) logger() fasthttp.Logger {
	if s.Logger != nil {
		return s.Logger
	}
	return defaultLogger
}
<commit_msg>Server: panic on a connection hijack attempt<commit_after>package httpteleport

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"github.com/valyala/fasthttp"
	"github.com/valyala/tcplisten"
	"io"
	"log"
	"net"
	"os"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

// Server is a server accepting requests from httpteleport Client.
type Server struct {
	// Handler must process incoming http requests.
	Handler fasthttp.RequestHandler

	// CompressType is the compression type used for responses.
	//
	// CompressFlate is used by default.
	CompressType CompressType

	// Concurrency is the maximum number of concurrent goroutines
	// with Server.Handler the server may run.
	//
	// DefaultConcurrency is used by default.
	Concurrency int

	// TLSConfig is TLS (aka SSL) config used for accepting encrypted
	// client connections.
	//
	// Encrypted connections may be used for transferring sensitive
	// information over untrusted networks.
	//
	// By default server accepts only unencrypted connections.
	TLSConfig *tls.Config

	// MaxBatchDelay is the maximum duration before ready responses
	// are sent to the client.
	//
	// Responses' batching may reduce network bandwidth usage and CPU usage.
	//
	// By default responses are sent immediately to the client.
	MaxBatchDelay time.Duration

	// Maximum duration for reading the full request (including body).
	//
	// This also limits the maximum lifetime for idle connections.
	//
	// By default request read timeout is unlimited.
	ReadTimeout time.Duration

	// Maximum duration for writing the full response (including body).
	//
	// By default response write timeout is unlimited.
	WriteTimeout time.Duration

	// ReduceMemoryUsage leads to reduced memory usage at the cost
	// of higher CPU usage if set to true.
	//
	// Memory usage reduction is disabled by default.
	ReduceMemoryUsage bool

	// ReadBufferSize is the size for read buffer.
	//
	// DefaultReadBufferSize is used by default.
	ReadBufferSize int

	// WriteBufferSize is the size for write buffer.
	//
	// DefaultWriteBufferSize is used by default.
	WriteBufferSize int

	// Logger, which is used by RequestCtx.Logger().
	//
	// Standard logger from log package is used by default.
	Logger fasthttp.Logger

	workItemPool sync.Pool

	concurrencyCount uint32
}

func (s *Server) concurrency() int {
	concurrency := s.Concurrency
	if concurrency <= 0 {
		concurrency = DefaultConcurrency
	}
	return concurrency
}

// ListenAndServe serves httpteleport requests accepted from the given
// TCP address.
func (s *Server) ListenAndServe(addr string) error {
	var cfg tcplisten.Config
	ln, err := cfg.NewListener("tcp4", addr)
	if err != nil {
		return err
	}
	return s.Serve(ln)
}

// Serve serves httpteleport requests accepted from the given listener.
func (s *Server) Serve(ln net.Listener) error {
	for {
		conn, err := ln.Accept()
		if err != nil {
			if conn != nil {
				panic("BUG: net.Listener returned non-nil conn and non-nil error")
			}
			if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
				s.logger().Printf("httpteleport.Server: temporary error when accepting new connections: %s", netErr)
				time.Sleep(time.Second)
				continue
			}
			if err != io.EOF && !strings.Contains(err.Error(), "use of closed network connection") {
				s.logger().Printf("httpteleport.Server: permanent error when accepting new connections: %s", err)
				return err
			}
			return nil
		}
		if conn == nil {
			panic("BUG: net.Listener returned (nil, nil)")
		}

		go func() {
			laddr := conn.LocalAddr().String()
			raddr := conn.RemoteAddr().String()
			if err := s.serveConn(conn); err != nil {
				s.logger().Printf("httpteleport.Server: error on connection %q<->%q: %s", laddr, raddr, err)
			}
		}()
	}
}

func (s *Server) serveConn(conn net.Conn) error {
	br, bw, err := newBufioConn(conn, s.ReadBufferSize, s.WriteBufferSize, s.CompressType, s.TLSConfig, true)
	if err != nil {
		conn.Close()
		return err
	}

	stopCh := make(chan struct{})

	pendingResponses := make(chan *serverWorkItem, s.concurrency())
	readerDone := make(chan error, 1)
	go func() {
		readerDone <- s.connReader(br, conn, pendingResponses, stopCh)
	}()

	writerDone := make(chan error, 1)
	go func() {
		writerDone <- s.connWriter(bw, conn, pendingResponses, stopCh)
	}()

	select {
	case err = <-readerDone:
		conn.Close()
		close(stopCh)
		<-writerDone
	case err = <-writerDone:
		conn.Close()
		close(stopCh)
		<-readerDone
	}
	return err
}

func (s *Server) connReader(br *bufio.Reader, conn net.Conn, pendingResponses chan<- *serverWorkItem, stopCh <-chan struct{}) error {
	handler := s.Handler
	if handler == nil {
		panic("BUG: Server.Handler must be set")
	}
	logger := s.logger()
	concurrency := s.concurrency()
	reduceMemoryUsage := s.ReduceMemoryUsage
	readTimeout := s.ReadTimeout
	var lastReadDeadline time.Time
	for {
		wi := s.acquireWorkItem()

		if readTimeout > 0 {
			// Optimization: update read deadline only if more than 25%
			// of the last read deadline exceeded.
			// See https://github.com/golang/go/issues/15133 for details.
			t := time.Now()
			if t.Sub(lastReadDeadline) > (readTimeout >> 2) {
				if err := conn.SetReadDeadline(t.Add(readTimeout)); err != nil {
					return fmt.Errorf("cannot update read deadline: %s", err)
				}
				lastReadDeadline = t
			}
		}

		if n, err := io.ReadFull(br, wi.reqID[:]); err != nil {
			if n == 0 {
				// Ignore error if no bytes are read, since
				// the client may just close the connection.
				return nil
			}
			return fmt.Errorf("cannot read request ID: %s", err)
		}

		wi.ctx.Init2(conn, logger, reduceMemoryUsage)

		if err := wi.ctx.Request.Read(br); err != nil {
			return fmt.Errorf("cannot read request: %s", err)
		}

		n := int(atomic.AddUint32(&s.concurrencyCount, 1))
		if n > concurrency {
			atomic.AddUint32(&s.concurrencyCount, ^uint32(0))

			fmt.Fprintf(&wi.ctx, "concurrency limit exceeded: %d. Increase Server.Concurrency or decrease load on the server", concurrency)
			wi.ctx.SetStatusCode(fasthttp.StatusTooManyRequests)

			select {
			case pendingResponses <- wi:
			default:
				select {
				case pendingResponses <- wi:
				case <-stopCh:
					return nil
				}
			}
			continue
		}

		go func() {
			handler(&wi.ctx)
			if wi.ctx.IsBodyStream() {
				panic("chunked responses aren't supported")
			}
			if wi.ctx.Hijacked() {
				panic("hijacking isn't supported")
			}

			// Request is no longer needed, so reset it in order
			// to free up resources occupied by the request.
			wi.ctx.Request.Reset()

			select {
			case pendingResponses <- wi:
			default:
				select {
				case pendingResponses <- wi:
				case <-stopCh:
				}
			}

			atomic.AddUint32(&s.concurrencyCount, ^uint32(0))
		}()
	}
}

func (s *Server) connWriter(bw *bufio.Writer, conn net.Conn, pendingResponses <-chan *serverWorkItem, stopCh <-chan struct{}) error {
	var wi *serverWorkItem

	var (
		flushTimer    = time.NewTimer(time.Hour * 24 * 30)
		flushCh       <-chan time.Time
		flushAlwaysCh = make(chan time.Time)
	)
	close(flushAlwaysCh)
	maxBatchDelay := s.MaxBatchDelay
	if maxBatchDelay < 0 {
		maxBatchDelay = 0
	}

	writeTimeout := s.WriteTimeout
	var lastWriteDeadline time.Time
	for {
		select {
		case wi = <-pendingResponses:
		default:
			select {
			case wi = <-pendingResponses:
			case <-stopCh:
				return nil
			case <-flushCh:
				if err := bw.Flush(); err != nil {
					return fmt.Errorf("cannot flush response data to client: %s", err)
				}
				flushCh = nil
				continue
			}
		}

		if writeTimeout > 0 {
			// Optimization: update write deadline only if more than 25%
			// of the last write deadline exceeded.
			// See https://github.com/golang/go/issues/15133 for details.
			t := time.Now()
			if t.Sub(lastWriteDeadline) > (writeTimeout >> 2) {
				if err := conn.SetReadDeadline(t.Add(writeTimeout)); err != nil {
					return fmt.Errorf("cannot update write deadline: %s", err)
				}
				lastWriteDeadline = t
			}
		}

		if _, err := bw.Write(wi.reqID[:]); err != nil {
			return fmt.Errorf("cannot write response ID: %s", err)
		}
		if err := wi.ctx.Response.Write(bw); err != nil {
			return fmt.Errorf("cannot write response: %s", err)
		}

		// Response is no longer needed, so reset it in order to release
		// resources occupied by the response.
		wi.ctx.Response.Reset()

		s.releaseWorkItem(wi)

		// re-arm flush channel
		if len(pendingResponses) == 0 {
			if maxBatchDelay > 0 {
				if !flushTimer.Stop() {
					select {
					case <-flushTimer.C:
					default:
					}
				}
				flushTimer.Reset(maxBatchDelay)
				flushCh = flushTimer.C
			} else {
				flushCh = flushAlwaysCh
			}
		}
	}
}

type serverWorkItem struct {
	ctx   fasthttp.RequestCtx
	reqID [4]byte
}

func (s *Server) acquireWorkItem() *serverWorkItem {
	v := s.workItemPool.Get()
	if v == nil {
		return &serverWorkItem{}
	}
	return v.(*serverWorkItem)
}

func (s *Server) releaseWorkItem(wi *serverWorkItem) {
	s.workItemPool.Put(wi)
}

var defaultLogger = log.New(os.Stderr, "", log.LstdFlags)

func (s *Server) logger() fasthttp.Logger {
	if s.Logger != nil {
		return s.Logger
	}
	return defaultLogger
}
<|endoftext|>


<commit_before>package main

import (
    "flag"
    "github.com/orc/db"
    "github.com/orc/resources"
    "github.com/orc/router"
    "github.com/orc/mvc/controllers"
    "log"
    "net/http"
    "os"
)

func main() {
    log.Println("Server started.")

    testData := flag.Bool("test-data", false, "to load test data")
    flag.Parse()

    db.Init()
    controllers.CreateRegistrationEvent()
    resources.LoadAdmin()

    if *testData == true {
        resources.Load()
    }

    base := new(controllers.BaseController)
    base.Index().LoadContestsFromCats()

    http.Handle("/", new(router.FastCGIServer))
    http.Handle("/js/", http.StripPrefix("/js/", http.FileServer(http.Dir("./static/js"))))
    http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("./static/css"))))
    http.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("./static/img"))))

    port := os.Getenv("PORT")
    if port == "" {
        port = "5000"
    }

    if err := http.ListenAndServe(":" + port, nil); err != nil {
        log.Println("Error listening: ", err.Error())
        os.Exit(1)
    }
}
<commit_msg>hide loading contests from cats<commit_after>package main

import (
    "flag"
    "github.com/orc/db"
    "github.com/orc/resources"
    "github.com/orc/router"
    "github.com/orc/mvc/controllers"
    "log"
    "net/http"
    "os"
)

func main() {
    log.Println("Server started.")

    testData := flag.Bool("test-data", false, "to load test data")
    flag.Parse()

    db.Init()
    controllers.CreateRegistrationEvent()
    resources.LoadAdmin()

    if *testData == true {
        resources.Load()
    }

    // base := new(controllers.BaseController)
    // base.Index().LoadContestsFromCats()

    http.Handle("/", new(router.FastCGIServer))
    http.Handle("/js/", http.StripPrefix("/js/", http.FileServer(http.Dir("./static/js"))))
    http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("./static/css"))))
    http.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("./static/img"))))

    port := os.Getenv("PORT")
    if port == "" {
        port = "5000"
    }

    if err := http.ListenAndServe(":" + port, nil); err != nil {
        log.Println("Error listening: ", err.Error())
        os.Exit(1)
    }
}
<|endoftext|>


<commit_before>package util

import (
	"errors"
	"github.com/realglobe-Inc/go-lib-rg/erro"
	"math/rand"
	"net"
	"net/http"
	"net/http/fcgi"
	"os"
	"os/signal"
	"runtime"
	"strconv"
	"syscall"
	"time"
)

type HandlerFunc func(http.ResponseWriter, *http.Request) error

var invalidProtocol = errors.New("invalid protocol.")

func Serve(socType, socPath string, socPort int, protType string, routes map[string]HandlerFunc) error {

	shutCh := make(chan struct{}, 1)

	// SIGINT、SIGKILL、SIGTERM を受け取ったら終了。
	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, os.Interrupt, os.Kill, syscall.SIGTERM)
	go func() {
		sig := <-sigCh
		log.Info("Signal ", sig, " was caught.")
		shutCh <- struct{}{}
	}()

	var sleepTime time.Duration = 0
	resetInterval := time.Minute
	for {
		if brk, err := func() (brk bool, err error) {

			var lis net.Listener
			defer func() {
				if lis != nil {
					lis.Close()
				}
			}()

			switch socType {
			case "unix":
				lis, err = net.Listen("unix", socPath)
				if err != nil {
					return false, erro.Wrap(err)
				}
				if err := os.Chmod(socPath, 0777); err != nil {
					return false, erro.Wrap(err)
				}
				log.Info("Wait on UNIX socket " + socPath + ".")
			case "tcp":
				lis, err = net.Listen("tcp", ":"+strconv.Itoa(socPort))
				if err != nil {
					return false, erro.Wrap(err)
				}
				log.Info("Wait on TCP socket ", socPort, ".")
			default:
				return true, erro.New("invalid socket type " + socType + ".")
			}

			stopCh := make(chan struct{}, 1)
			subShutCh := make(chan bool, 1)
			go func() {
				select {
				case <-shutCh:
					shutCh <- struct{}{}
					subShutCh <- true
					lis.Close()
				case <-stopCh:
					subShutCh <- false
				}
			}()
			defer func() { stopCh <- struct{}{} }()

			start := time.Now()
			if err := serveCore(protType, routes, lis); err != nil {
				err := erro.Wrap(err)

				// 正常な終了処理としてソケットが閉じられたかもしれないので調べる。
				select {
				case <-subShutCh:
					return true, nil
				default:
				}

				stopCh <- struct{}{}
				brk = <-subShutCh

				if brk || erro.Unwrap(err) == invalidProtocol {
					// どうしようもない。
					return true, err
				}

				end := time.Now()
				if end.Sub(start) > resetInterval {
					sleepTime = 0
				}

				return false, err
			}

			stopCh <- struct{}{}
			return <-subShutCh, nil
		}(); brk {
			return erro.Wrap(err)
		} else {
			if err != nil {
				log.Err(erro.Unwrap(err))
				log.Debug(err)
			}

			sleepTime = serverNextSleepTime(sleepTime, resetInterval)
			log.Info("Retry after ", sleepTime)

			timeCh := time.After(sleepTime)
			select {
			case <-shutCh:
				return nil
			case <-timeCh:
			}
		}
	}

	return nil
}

func serverNextSleepTime(cur, max time.Duration) time.Duration {
	next := 2*cur + time.Duration(rand.Int63n(int64(time.Second)))
	if next >= max {
		next = time.Minute
	}
	return next
}

func serveCore(protType string, routes map[string]HandlerFunc, lis net.Listener) error {
	var serv func(net.Listener, http.Handler) error
	switch protType {
	case "http":
		serv = http.Serve
	case "fcgi":
		serv = fcgi.Serve
	default:
		return erro.Wrap(invalidProtocol)
	}

	log.Debug("Server starts.")
	defer log.Debug("Server exits.")

	mux := http.NewServeMux()

	for path, handler := range routes {
		mux.HandleFunc(path, serverPanicErrorWrapper(handler))
	}

	if err := serv(lis, mux); err != nil {
		return erro.Wrap(err)
	}

	return nil
}

// パニックとエラーの処理をまとめる。
func serverPanicErrorWrapper(handler HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// panic時にプロセス終了しないようにrecoverする
		defer func() {
			if rcv := recover(); rcv != nil {
				buff := make([]byte, 8192)
				stackLen := runtime.Stack(buff, false)
				stack := string(buff[:stackLen])
				err := erro.Wrap(NewPanicWrapper(rcv, stack))

				log.Err(erro.Unwrap(err))
				log.Debug(err)

				w.Header().Set("Content-Type", ContentTypeJson)
				http.Error(w, string(ErrorToResponseJson(err)), http.StatusInternalServerError)
				return
			}
		}()

		//////////////////////////////
		LogRequest(r, true)
		//////////////////////////////

		if err := handler(w, r); err != nil {
			err = erro.Wrap(err)
			log.Err(erro.Unwrap(err))
			log.Debug(err)

			var status int
			switch e := erro.Unwrap(err).(type) {
			case *HttpStatusError:
				status = e.Status()
			default:
				status = http.StatusInternalServerError
			}
			w.Header().Set("Content-Type", ContentTypeJson)
			http.Error(w, string(ErrorToResponseJson(err)), status)
			return
		}
	}
}
<commit_msg>コメントを追加<commit_after>package util

import (
	"errors"
	"github.com/realglobe-Inc/go-lib-rg/erro"
	"math/rand"
	"net"
	"net/http"
	"net/http/fcgi"
	"os"
	"os/signal"
	"runtime"
	"strconv"
	"syscall"
	"time"
)

type HandlerFunc func(http.ResponseWriter, *http.Request) error

var invalidProtocol = errors.New("invalid protocol.")

func Serve(socType, socPath string, socPort int, protType string, routes map[string]HandlerFunc) error {

	shutCh := make(chan struct{}, 1)

	// SIGINT、SIGKILL、SIGTERM を受け取ったら終了。
	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, os.Interrupt, os.Kill, syscall.SIGTERM)
	go func() {
		sig := <-sigCh
		log.Info("Signal ", sig, " was caught.")
		shutCh <- struct{}{}
	}()

	// エラー発生時の冷却期間はだんだん延ばす。
	var sleepTime time.Duration = 0
	// 一定期間以上エラー無しで動作したら冷却期間をリセットする。
	resetInterval := time.Minute
	for {
		if brk, err := func() (brk bool, err error) {

			var lis net.Listener
			defer func() {
				if lis != nil {
					lis.Close()
				}
			}()

			switch socType {
			case "unix":
				lis, err = net.Listen("unix", socPath)
				if err != nil {
					return false, erro.Wrap(err)
				}
				if err := os.Chmod(socPath, 0777); err != nil {
					return false, erro.Wrap(err)
				}
				log.Info("Wait on UNIX socket " + socPath + ".")
			case "tcp":
				lis, err = net.Listen("tcp", ":"+strconv.Itoa(socPort))
				if err != nil {
					return false, erro.Wrap(err)
				}
				log.Info("Wait on TCP socket ", socPort, ".")
			default:
				return true, erro.New("invalid socket type " + socType + ".")
			}

			stopCh := make(chan struct{}, 1)
			subShutCh := make(chan bool, 1)
			go func() {
				select {
				case <-shutCh:
					shutCh <- struct{}{}
					subShutCh <- true
					lis.Close()
				case <-stopCh:
					subShutCh <- false
				}
			}()
			defer func() { stopCh <- struct{}{} }()

			start := time.Now()
			if err := serveCore(protType, routes, lis); err != nil {
				err := erro.Wrap(err)

				// 正常な終了処理としてソケットが閉じられたかもしれないので調べる。
				select {
				case <-subShutCh:
					return true, nil
				default:
				}

				stopCh <- struct{}{}
				brk = <-subShutCh

				if brk || erro.Unwrap(err) == invalidProtocol {
					// どうしようもない。
					return true, err
				}

				end := time.Now()
				if end.Sub(start) > resetInterval {
					sleepTime = 0
				}

				return false, err
			}

			stopCh <- struct{}{}
			return <-subShutCh, nil
		}(); brk {
			return erro.Wrap(err)
		} else {
			if err != nil {
				log.Err(erro.Unwrap(err))
				log.Debug(err)
			}

			sleepTime = serverNextSleepTime(sleepTime, resetInterval)
			log.Info("Retry after ", sleepTime)

			timeCh := time.After(sleepTime)
			select {
			case <-shutCh:
				return nil
			case <-timeCh:
			}
		}
	}

	return nil
}

func serverNextSleepTime(cur, max time.Duration) time.Duration {
	next := 2*cur + time.Duration(rand.Int63n(int64(time.Second)))
	if next >= max {
		next = time.Minute
	}
	return next
}

func serveCore(protType string, routes map[string]HandlerFunc, lis net.Listener) error {
	var serv func(net.Listener, http.Handler) error
	switch protType {
	case "http":
		serv = http.Serve
	case "fcgi":
		serv = fcgi.Serve
	default:
		return erro.Wrap(invalidProtocol)
	}

	log.Debug("Server starts.")
	defer log.Debug("Server exits.")

	mux := http.NewServeMux()

	for path, handler := range routes {
		mux.HandleFunc(path, serverPanicErrorWrapper(handler))
	}

	if err := serv(lis, mux); err != nil {
		return erro.Wrap(err)
	}

	return nil
}

// パニックとエラーの処理をまとめる。
func serverPanicErrorWrapper(handler HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// panic時にプロセス終了しないようにrecoverする
		defer func() {
			if rcv := recover(); rcv != nil {
				buff := make([]byte, 8192)
				stackLen := runtime.Stack(buff, false)
				stack := string(buff[:stackLen])
				err := erro.Wrap(NewPanicWrapper(rcv, stack))

				log.Err(erro.Unwrap(err))
				log.Debug(err)

				w.Header().Set("Content-Type", ContentTypeJson)
				http.Error(w, string(ErrorToResponseJson(err)), http.StatusInternalServerError)
				return
			}
		}()

		//////////////////////////////
		LogRequest(r, true)
		//////////////////////////////

		if err := handler(w, r); err != nil {
			err = erro.Wrap(err)
			log.Err(erro.Unwrap(err))
			log.Debug(err)

			var status int
			switch e := erro.Unwrap(err).(type) {
			case *HttpStatusError:
				status = e.Status()
			default:
				status = http.StatusInternalServerError
			}
			w.Header().Set("Content-Type", ContentTypeJson)
			http.Error(w, string(ErrorToResponseJson(err)), status)
			return
		}
	}
}
<|endoftext|>