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

package server

import (
	"fmt"
	"os"
	"time"

	"strings"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

type LoggingFormat int8

const (
	JSONFormat LoggingFormat = iota - 1
	StackdriverFormat
)

func SetupLogging(tmpLogger *zap.Logger, config Config) (*zap.Logger, *zap.Logger) {
	zapLevel := zapcore.InfoLevel
	switch strings.ToLower(config.GetLogger().Level) {
	case "debug":
		zapLevel = zapcore.DebugLevel
	case "info":
		zapLevel = zapcore.InfoLevel
	case "warn":
		zapLevel = zapcore.WarnLevel
	case "error":
		zapLevel = zapcore.ErrorLevel
	default:
		tmpLogger.Fatal("Logger level invalid, must be one of: DEBUG, INFO, WARN, or ERROR")
	}

	format := JSONFormat
	switch strings.ToLower(config.GetLogger().Format) {
	case "":
		fallthrough
	case "json":
		format = JSONFormat
	case "stackdriver":
		format = StackdriverFormat
	default:
		tmpLogger.Fatal("Logger mode invalid, must be one of: '', 'json', or 'stackdriver")
	}

	consoleLogger := NewJSONLogger(os.Stdout, zapLevel, format)
	var fileLogger *zap.Logger
	if config.GetLogger().Rotation {
		fileLogger = NewRotatingJSONFileLogger(consoleLogger, config, zapLevel, format)
	} else {
		fileLogger = NewJSONFileLogger(consoleLogger, config.GetLogger().File, zapLevel, format)
	}

	if fileLogger != nil {
		multiLogger := NewMultiLogger(consoleLogger, fileLogger)

		if config.GetLogger().Stdout {
			zap.RedirectStdLog(multiLogger)
			return multiLogger, multiLogger
		} else {
			zap.RedirectStdLog(fileLogger)
			return fileLogger, multiLogger
		}
	}

	zap.RedirectStdLog(consoleLogger)
	return consoleLogger, consoleLogger
}

func NewJSONFileLogger(consoleLogger *zap.Logger, fileName string, level zapcore.Level, format LoggingFormat) *zap.Logger {
	if len(fileName) == 0 {
		return nil
	}

	output, err := os.OpenFile(fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
	if err != nil {
		consoleLogger.Fatal("Could not create log file", zap.Error(err))
		return nil
	}

	return NewJSONLogger(output, level, format)
}

func NewRotatingJSONFileLogger(consoleLogger *zap.Logger, config Config, level zapcore.Level, format LoggingFormat) *zap.Logger {
	fileName := config.GetLogger().File
	if len(fileName) == 0 {
		consoleLogger.Fatal("Rotating log file is enabled but log file name is empty")
		return nil
	}
	if err := os.MkdirAll(fileName, 0755); err != nil {
		consoleLogger.Fatal("Could not create log directory", zap.Error(err))
		return nil
	}

	jsonEncoder := newJSONEncoder(format)

	// lumberjack.Logger is already safe for concurrent use, so we don't need to lock it.
	writeSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   fileName,
		MaxSize:    config.GetLogger().MaxSize,
		MaxAge:     config.GetLogger().MaxAge,
		MaxBackups: config.GetLogger().MaxBackups,
		LocalTime:  config.GetLogger().LocalTime,
		Compress:   config.GetLogger().Compress,
	})
	core := zapcore.NewCore(jsonEncoder, writeSyncer, level)
	options := []zap.Option{zap.AddStacktrace(zap.ErrorLevel)}
	return zap.New(core, options...)
}

func NewMultiLogger(loggers ...*zap.Logger) *zap.Logger {
	cores := make([]zapcore.Core, 0, len(loggers))
	for _, logger := range loggers {
		cores = append(cores, logger.Core())
	}

	teeCore := zapcore.NewTee(cores...)
	options := []zap.Option{zap.AddStacktrace(zap.ErrorLevel)}
	return zap.New(teeCore, options...)
}

func NewJSONLogger(output *os.File, level zapcore.Level, format LoggingFormat) *zap.Logger {
	jsonEncoder := newJSONEncoder(format)

	core := zapcore.NewCore(jsonEncoder, zapcore.Lock(output), level)
	options := []zap.Option{zap.AddStacktrace(zap.ErrorLevel)}
	return zap.New(core, options...)
}

// Create a new JSON log encoder with the correct settings.
func newJSONEncoder(format LoggingFormat) zapcore.Encoder {
	if format == StackdriverFormat {
		return zapcore.NewJSONEncoder(zapcore.EncoderConfig{
			TimeKey:        "time",
			LevelKey:       "severity",
			NameKey:        "logger",
			CallerKey:      "caller",
			MessageKey:     "msg",
			StacktraceKey:  "stacktrace",
			EncodeLevel:    StackdriverLevelEncoder,
			EncodeTime:     StackdriverTimeEncoder,
			EncodeDuration: zapcore.StringDurationEncoder,
			EncodeCaller:   zapcore.ShortCallerEncoder,
		})
	}

	return zapcore.NewJSONEncoder(zapcore.EncoderConfig{
		TimeKey:        "ts",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		MessageKey:     "msg",
		StacktraceKey:  "stacktrace",
		EncodeLevel:    zapcore.LowercaseLevelEncoder,
		EncodeTime:     zapcore.ISO8601TimeEncoder,
		EncodeDuration: zapcore.StringDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
	})
}

func StackdriverTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
	enc.AppendString(fmt.Sprintf("%d%s", t.Unix(), t.Format(".000000000")))
}

func StackdriverLevelEncoder(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
	switch l {
	case zapcore.DebugLevel:
		enc.AppendString("debug")
	case zapcore.InfoLevel:
		enc.AppendString("info")
	case zapcore.WarnLevel:
		enc.AppendString("warning")
	case zapcore.ErrorLevel:
		enc.AppendString("error")
	case zapcore.DPanicLevel:
		enc.AppendString("critical")
	case zapcore.PanicLevel:
		enc.AppendString("critical")
	case zapcore.FatalLevel:
		enc.AppendString("critical")
	default:
		enc.AppendString(fmt.Sprintf("Level(%d)", l))
	}
}
<commit_msg>Extract directory from logger filename in config. (#312)<commit_after>// Copyright 2018 The Nakama Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package server

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

	"strings"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

type LoggingFormat int8

const (
	JSONFormat LoggingFormat = iota - 1
	StackdriverFormat
)

func SetupLogging(tmpLogger *zap.Logger, config Config) (*zap.Logger, *zap.Logger) {
	zapLevel := zapcore.InfoLevel
	switch strings.ToLower(config.GetLogger().Level) {
	case "debug":
		zapLevel = zapcore.DebugLevel
	case "info":
		zapLevel = zapcore.InfoLevel
	case "warn":
		zapLevel = zapcore.WarnLevel
	case "error":
		zapLevel = zapcore.ErrorLevel
	default:
		tmpLogger.Fatal("Logger level invalid, must be one of: DEBUG, INFO, WARN, or ERROR")
	}

	format := JSONFormat
	switch strings.ToLower(config.GetLogger().Format) {
	case "":
		fallthrough
	case "json":
		format = JSONFormat
	case "stackdriver":
		format = StackdriverFormat
	default:
		tmpLogger.Fatal("Logger mode invalid, must be one of: '', 'json', or 'stackdriver")
	}

	consoleLogger := NewJSONLogger(os.Stdout, zapLevel, format)
	var fileLogger *zap.Logger
	if config.GetLogger().Rotation {
		fileLogger = NewRotatingJSONFileLogger(consoleLogger, config, zapLevel, format)
	} else {
		fileLogger = NewJSONFileLogger(consoleLogger, config.GetLogger().File, zapLevel, format)
	}

	if fileLogger != nil {
		multiLogger := NewMultiLogger(consoleLogger, fileLogger)

		if config.GetLogger().Stdout {
			zap.RedirectStdLog(multiLogger)
			return multiLogger, multiLogger
		} else {
			zap.RedirectStdLog(fileLogger)
			return fileLogger, multiLogger
		}
	}

	zap.RedirectStdLog(consoleLogger)
	return consoleLogger, consoleLogger
}

func NewJSONFileLogger(consoleLogger *zap.Logger, fileName string, level zapcore.Level, format LoggingFormat) *zap.Logger {
	if len(fileName) == 0 {
		return nil
	}

	output, err := os.OpenFile(fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
	if err != nil {
		consoleLogger.Fatal("Could not create log file", zap.Error(err))
		return nil
	}

	return NewJSONLogger(output, level, format)
}

func NewRotatingJSONFileLogger(consoleLogger *zap.Logger, config Config, level zapcore.Level, format LoggingFormat) *zap.Logger {
	fileName := config.GetLogger().File
	if len(fileName) == 0 {
		consoleLogger.Fatal("Rotating log file is enabled but log file name is empty")
		return nil
	}

	logDir := filepath.Dir(fileName)
	if _, err := os.Stat(logDir); os.IsNotExist(err) {
		if err := os.MkdirAll(logDir, 0755); err != nil {
			consoleLogger.Fatal("Could not create log directory", zap.Error(err))
			return nil
		}
	}

	jsonEncoder := newJSONEncoder(format)

	// lumberjack.Logger is already safe for concurrent use, so we don't need to lock it.
	writeSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   fileName,
		MaxSize:    config.GetLogger().MaxSize,
		MaxAge:     config.GetLogger().MaxAge,
		MaxBackups: config.GetLogger().MaxBackups,
		LocalTime:  config.GetLogger().LocalTime,
		Compress:   config.GetLogger().Compress,
	})
	core := zapcore.NewCore(jsonEncoder, writeSyncer, level)
	options := []zap.Option{zap.AddStacktrace(zap.ErrorLevel)}
	return zap.New(core, options...)
}

func NewMultiLogger(loggers ...*zap.Logger) *zap.Logger {
	cores := make([]zapcore.Core, 0, len(loggers))
	for _, logger := range loggers {
		cores = append(cores, logger.Core())
	}

	teeCore := zapcore.NewTee(cores...)
	options := []zap.Option{zap.AddStacktrace(zap.ErrorLevel)}
	return zap.New(teeCore, options...)
}

func NewJSONLogger(output *os.File, level zapcore.Level, format LoggingFormat) *zap.Logger {
	jsonEncoder := newJSONEncoder(format)

	core := zapcore.NewCore(jsonEncoder, zapcore.Lock(output), level)
	options := []zap.Option{zap.AddStacktrace(zap.ErrorLevel)}
	return zap.New(core, options...)
}

// Create a new JSON log encoder with the correct settings.
func newJSONEncoder(format LoggingFormat) zapcore.Encoder {
	if format == StackdriverFormat {
		return zapcore.NewJSONEncoder(zapcore.EncoderConfig{
			TimeKey:        "time",
			LevelKey:       "severity",
			NameKey:        "logger",
			CallerKey:      "caller",
			MessageKey:     "msg",
			StacktraceKey:  "stacktrace",
			EncodeLevel:    StackdriverLevelEncoder,
			EncodeTime:     StackdriverTimeEncoder,
			EncodeDuration: zapcore.StringDurationEncoder,
			EncodeCaller:   zapcore.ShortCallerEncoder,
		})
	}

	return zapcore.NewJSONEncoder(zapcore.EncoderConfig{
		TimeKey:        "ts",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		MessageKey:     "msg",
		StacktraceKey:  "stacktrace",
		EncodeLevel:    zapcore.LowercaseLevelEncoder,
		EncodeTime:     zapcore.ISO8601TimeEncoder,
		EncodeDuration: zapcore.StringDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
	})
}

func StackdriverTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
	enc.AppendString(fmt.Sprintf("%d%s", t.Unix(), t.Format(".000000000")))
}

func StackdriverLevelEncoder(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
	switch l {
	case zapcore.DebugLevel:
		enc.AppendString("debug")
	case zapcore.InfoLevel:
		enc.AppendString("info")
	case zapcore.WarnLevel:
		enc.AppendString("warning")
	case zapcore.ErrorLevel:
		enc.AppendString("error")
	case zapcore.DPanicLevel:
		enc.AppendString("critical")
	case zapcore.PanicLevel:
		enc.AppendString("critical")
	case zapcore.FatalLevel:
		enc.AppendString("critical")
	default:
		enc.AppendString(fmt.Sprintf("Level(%d)", l))
	}
}
<|endoftext|>


<commit_before>package http

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"net/http"

	"github.com/multimfi/bot/alert"
	"github.com/multimfi/bot/event"
)

const (
	EventIRCReady = iota
	EventIRCDown
	EventReset
	EventAlert
	EventResponder
)

const (
	StateActive = iota
	StateFailed
	StateUnknown
)

func (s *Server) ircstate() uint32 {
	if s.irc.IsReady() {
		return EventIRCReady
	} else {
		return EventIRCDown
	}
}

func (s *Server) broadcastIRC() {
	s.epool.Add(nil, s.ircstate())
}

func (s *Server) responderstate() []byte {
	lr := s.rpool.List()
	if len(lr) < 1 {
		return nil
	}

	d := make(map[string]uint8, len(lr))

	for _, v := range lr {
		if v.Active() {
			d[v.Name] = StateActive
		} else if v.Failed() {
			d[v.Name] = StateFailed
		} else {
			d[v.Name] = StateUnknown
		}
	}

	r, err := json.Marshal(d)
	if err != nil {
		panic(err)
	}

	return r
}

func (s *Server) broadcastResponders() {
	if r := s.responderstate(); r != nil {
		s.epool.Add(r, EventResponder)
	}
}

type alertmsg struct {
	*alert.Alert
	Hash    string `json:"h"`
	Current int    `json:"c"`
}

func alertjson(a *alert.Alert) ([]byte, error) {
	hash := a.Hash()
	return json.Marshal(&alertmsg{
		Alert:   a,
		Hash:    hex.EncodeToString(hash[:]),
		Current: a.Current(),
	})
}

func (s *Server) broadcastAlert(a *alert.Alert) {
	b, err := alertjson(a)
	if err != nil {
		panic(err)
	}
	s.epool.Add(b, EventAlert)
}

var msgReset = []byte("id:0\nevent:2\ndata:\n\n")

func sse(w http.ResponseWriter, id uint32, event uint32, data []byte) error {
	_, err := fmt.Fprintf(w, "id:%d\nevent:%d\ndata:%s\n\n", id, event, data)
	return err
}

func resetjs(w http.ResponseWriter) error {
	_, err := w.Write(msgReset)
	w.(http.Flusher).Flush()
	return err
}

func (s *Server) current(w http.ResponseWriter) (gen uint32, err error) {
	if err := resetjs(w); err != nil {
		return 0, err
	}

	if err := sse(w, 0, s.ircstate(), nil); err != nil {
		return 0, err
	}

	if r := s.responderstate(); r != nil {
		if err := sse(w, 0, EventResponder, r); err != nil {
			return 0, err
		}
	}

	gen = s.epool.Lock()
	defer s.epool.Unlock()

	for _, v := range s.pool.List() {
		r, err := alertjson(v)
		if err != nil {
			return 0, err
		}

		if err := sse(w, 0, EventAlert, r); err != nil {
			return 0, err
		}
	}

	w.(http.Flusher).Flush()
	return gen, nil
}

func (s *Server) sserange(w http.ResponseWriter, last, cur uint32) error {
	for {
		if last == cur {
			break
		}

		last++

		r := s.epool.Get(last)
		if err := sse(w, r.Gen, r.Type, r.Data); err != nil {
			return err
		}
	}

	w.(http.Flusher).Flush()
	return nil
}

func sub(a, b uint32) uint32 {
	const max = ^uint32(0)

	if a > b {
		return (a - max) + b
	}

	return b - a
}

func (s *Server) ssehandler(w http.ResponseWriter, r *http.Request) {
	r.Body.Close()

	if r.Method != http.MethodGet {
		http.NotFound(w, r)
		return
	}

	w.Header().Set("Content-Type", "text/event-stream")
	var (
		last, gen uint32
		err       error
		ok        bool
	)

	last, err = s.current(w)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	cc := r.Context().Done()
	go func() {
		<-cc
		s.epool.Broadcast()
	}()

	for {
		gen, ok = s.epool.Next(last, cc)
		if !ok {
			break
		}

		if sub(last, gen) > event.Len {
			last, err = s.current(w)
			break
		}

		s.sserange(w, last, gen)
		last = gen
	}

	if err != nil {
		log.Println("sse:", err)
	}
}
<commit_msg>sse: add headers<commit_after>package http

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"net/http"

	"github.com/multimfi/bot/alert"
	"github.com/multimfi/bot/event"
)

const (
	EventIRCReady = iota
	EventIRCDown
	EventReset
	EventAlert
	EventResponder
)

const (
	StateActive = iota
	StateFailed
	StateUnknown
)

func (s *Server) ircstate() uint32 {
	if s.irc.IsReady() {
		return EventIRCReady
	} else {
		return EventIRCDown
	}
}

func (s *Server) broadcastIRC() {
	s.epool.Add(nil, s.ircstate())
}

func (s *Server) responderstate() []byte {
	lr := s.rpool.List()
	if len(lr) < 1 {
		return nil
	}

	d := make(map[string]uint8, len(lr))

	for _, v := range lr {
		if v.Active() {
			d[v.Name] = StateActive
		} else if v.Failed() {
			d[v.Name] = StateFailed
		} else {
			d[v.Name] = StateUnknown
		}
	}

	r, err := json.Marshal(d)
	if err != nil {
		panic(err)
	}

	return r
}

func (s *Server) broadcastResponders() {
	if r := s.responderstate(); r != nil {
		s.epool.Add(r, EventResponder)
	}
}

type alertmsg struct {
	*alert.Alert
	Hash    string `json:"h"`
	Current int    `json:"c"`
}

func alertjson(a *alert.Alert) ([]byte, error) {
	hash := a.Hash()
	return json.Marshal(&alertmsg{
		Alert:   a,
		Hash:    hex.EncodeToString(hash[:]),
		Current: a.Current(),
	})
}

func (s *Server) broadcastAlert(a *alert.Alert) {
	b, err := alertjson(a)
	if err != nil {
		panic(err)
	}
	s.epool.Add(b, EventAlert)
}

var msgReset = []byte("id:0\nevent:2\ndata:\n\n")

func sse(w http.ResponseWriter, id uint32, event uint32, data []byte) error {
	_, err := fmt.Fprintf(w, "id:%d\nevent:%d\ndata:%s\n\n", id, event, data)
	return err
}

func resetjs(w http.ResponseWriter) error {
	_, err := w.Write(msgReset)
	w.(http.Flusher).Flush()
	return err
}

func (s *Server) current(w http.ResponseWriter) (gen uint32, err error) {
	if err := resetjs(w); err != nil {
		return 0, err
	}

	if err := sse(w, 0, s.ircstate(), nil); err != nil {
		return 0, err
	}

	if r := s.responderstate(); r != nil {
		if err := sse(w, 0, EventResponder, r); err != nil {
			return 0, err
		}
	}

	gen = s.epool.Lock()
	defer s.epool.Unlock()

	for _, v := range s.pool.List() {
		r, err := alertjson(v)
		if err != nil {
			return 0, err
		}

		if err := sse(w, 0, EventAlert, r); err != nil {
			return 0, err
		}
	}

	w.(http.Flusher).Flush()
	return gen, nil
}

func (s *Server) sserange(w http.ResponseWriter, last, cur uint32) error {
	for {
		if last == cur {
			break
		}

		last++

		r := s.epool.Get(last)
		if err := sse(w, r.Gen, r.Type, r.Data); err != nil {
			return err
		}
	}

	w.(http.Flusher).Flush()
	return nil
}

func sub(a, b uint32) uint32 {
	const max = ^uint32(0)

	if a > b {
		return (a - max) + b
	}

	return b - a
}

func (s *Server) ssehandler(w http.ResponseWriter, r *http.Request) {
	r.Body.Close()

	if r.Method != http.MethodGet {
		http.NotFound(w, r)
		return
	}

	hdr := w.Header()
	hdr.Set("Content-Type", "text/event-stream")
	hdr.Set("Cache-Control", "no-cache")
	hdr.Set("Connection", "keep-alive")

	var (
		last, gen uint32
		err       error
		ok        bool
	)

	last, err = s.current(w)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	cc := r.Context().Done()
	go func() {
		<-cc
		s.epool.Broadcast()
	}()

	for {
		gen, ok = s.epool.Next(last, cc)
		if !ok {
			break
		}

		if sub(last, gen) > event.Len {
			last, err = s.current(w)
			break
		}

		s.sserange(w, last, gen)
		last = gen
	}

	if err != nil {
		log.Println("sse:", err)
	}
}
<|endoftext|>


<commit_before>package worker

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/bitly/go-simplejson"
	"github.com/jtacoma/uritemplates"
	"github.com/pkg/errors"
	"github.com/travis-ci/worker/backend"
	"github.com/travis-ci/worker/metrics"
	gocontext "golang.org/x/net/context"
)

type httpJob struct {
	payload         *JobPayload
	rawPayload      *simplejson.Json
	startAttributes *backend.StartAttributes
	received        time.Time
	started         time.Time
}

func (j *httpJob) GoString() string {
	return fmt.Sprintf("&httpJob{payload: %#v, startAttributes: %#v}",
		j.payload, j.startAttributes)
}

func (j *httpJob) Payload() *JobPayload {
	return j.payload
}

func (j *httpJob) RawPayload() *simplejson.Json {
	return j.rawPayload
}

func (j *httpJob) StartAttributes() *backend.StartAttributes {
	return j.startAttributes
}

func (j *httpJob) Error(ctx gocontext.Context, errMessage string) error {
	log, err := j.LogWriter(ctx)
	if err != nil {
		return err
	}

	_, err = log.WriteAndClose([]byte(errMessage))
	if err != nil {
		return err
	}

	return j.Finish(FinishStateErrored)
}

func (j *httpJob) Requeue() error {
	metrics.Mark("worker.job.requeue")

	currentState := j.currentState()

	j.received = time.Time{}
	j.started = time.Time{}

	err := j.sendStateUpdate(currentState, "created")

	if err != nil {
		return err
	}

	return nil
}

func (j *httpJob) Received() error {
	j.received = time.Now()
	return j.sendStateUpdate("queued", "received")
}

func (j *httpJob) Started() error {
	j.started = time.Now()

	metrics.TimeSince("travis.worker.job.start_time", j.received)

	return j.sendStateUpdate("received", "started")
}

func (j *httpJob) currentState() string {

	currentState := "queued"

	if !j.received.IsZero() {
		currentState = "received"
	}

	if !j.started.IsZero() {
		currentState = "started"
	}

	return currentState
}

func (j *httpJob) Finish(state FinishState) error {
	currentState := j.currentState()

	finishedAt := time.Now()
	receivedAt := j.received
	if receivedAt.IsZero() {
		receivedAt = finishedAt
	}
	startedAt := j.started
	if startedAt.IsZero() {
		startedAt = finishedAt
	}

	err := j.sendStateUpdate(currentState, string(state))
	if err != nil {
		return err
	}

	return nil
}

func (j *httpJob) LogWriter(ctx gocontext.Context) (LogWriter, error) {
	return newHTTPLogWriter(ctx, j.payload.JobPartsURL, j.payload.JWT, j.payload.Job.ID)
}

func (j *httpJob) sendStateUpdate(currentState, newState string) error {
	payload := struct {
		CurrentState string    `json:"cur"`
		NewState     string    `json:"new"`
		ReceivedAt   time.Time `json:"received,omitempty"`
		StartedAt    time.Time `json:"started,omitempty"`
	}{
		CurrentState: currentState,
		NewState:     newState,
		ReceivedAt:   j.received,
		StartedAt:    j.started,
	}

	encodedPayload, err := json.Marshal(payload)
	if err != nil {
		return errors.Wrap(err, "error encoding json")
	}

	template, err := uritemplates.Parse(j.payload.JobStateURL)
	if err != nil {
		return errors.Wrap(err, "couldn't parse base URL template")
	}

	u, err := template.Expand(map[string]interface{}{
		"job_id": j.payload.Job.ID,
	})
	if err != nil {
		return errors.Wrap(err, "couldn't expand base URL template")
	}

	req, err := http.NewRequest("PATCH", u, bytes.NewReader(encodedPayload))
	if err != nil {
		return errors.Wrap(err, "couldn't create request")
	}

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", j.payload.JWT))

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return errors.Wrap(err, "error making state update request")
	}

	if resp.StatusCode != http.StatusOK {
		return errors.Errorf("expected %d, but got %d", http.StatusOK, resp.StatusCode)
	}

	return nil
}
<commit_msg>Extract an inline struct definition<commit_after>package worker

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/bitly/go-simplejson"
	"github.com/jtacoma/uritemplates"
	"github.com/pkg/errors"
	"github.com/travis-ci/worker/backend"
	"github.com/travis-ci/worker/metrics"
	gocontext "golang.org/x/net/context"
)

type httpJob struct {
	payload         *JobPayload
	rawPayload      *simplejson.Json
	startAttributes *backend.StartAttributes
	received        time.Time
	started         time.Time
}

type httpJobStateUpdate struct {
	CurrentState string    `json:"cur"`
	NewState     string    `json:"new"`
	ReceivedAt   time.Time `json:"received,omitempty"`
	StartedAt    time.Time `json:"started,omitempty"`
}

func (j *httpJob) GoString() string {
	return fmt.Sprintf("&httpJob{payload: %#v, startAttributes: %#v}",
		j.payload, j.startAttributes)
}

func (j *httpJob) Payload() *JobPayload {
	return j.payload
}

func (j *httpJob) RawPayload() *simplejson.Json {
	return j.rawPayload
}

func (j *httpJob) StartAttributes() *backend.StartAttributes {
	return j.startAttributes
}

func (j *httpJob) Error(ctx gocontext.Context, errMessage string) error {
	log, err := j.LogWriter(ctx)
	if err != nil {
		return err
	}

	_, err = log.WriteAndClose([]byte(errMessage))
	if err != nil {
		return err
	}

	return j.Finish(FinishStateErrored)
}

func (j *httpJob) Requeue() error {
	metrics.Mark("worker.job.requeue")

	currentState := j.currentState()

	j.received = time.Time{}
	j.started = time.Time{}

	err := j.sendStateUpdate(currentState, "created")

	if err != nil {
		return err
	}

	return nil
}

func (j *httpJob) Received() error {
	j.received = time.Now()
	return j.sendStateUpdate("queued", "received")
}

func (j *httpJob) Started() error {
	j.started = time.Now()

	metrics.TimeSince("travis.worker.job.start_time", j.received)

	return j.sendStateUpdate("received", "started")
}

func (j *httpJob) currentState() string {

	currentState := "queued"

	if !j.received.IsZero() {
		currentState = "received"
	}

	if !j.started.IsZero() {
		currentState = "started"
	}

	return currentState
}

func (j *httpJob) Finish(state FinishState) error {
	currentState := j.currentState()

	finishedAt := time.Now()
	receivedAt := j.received
	if receivedAt.IsZero() {
		receivedAt = finishedAt
	}
	startedAt := j.started
	if startedAt.IsZero() {
		startedAt = finishedAt
	}

	err := j.sendStateUpdate(currentState, string(state))
	if err != nil {
		return err
	}

	return nil
}

func (j *httpJob) LogWriter(ctx gocontext.Context) (LogWriter, error) {
	return newHTTPLogWriter(ctx, j.payload.JobPartsURL, j.payload.JWT, j.payload.Job.ID)
}

func (j *httpJob) sendStateUpdate(currentState, newState string) error {
	payload := &httpJobStateUpdate{
		CurrentState: currentState,
		NewState:     newState,
		ReceivedAt:   j.received,
		StartedAt:    j.started,
	}

	encodedPayload, err := json.Marshal(payload)
	if err != nil {
		return errors.Wrap(err, "error encoding json")
	}

	template, err := uritemplates.Parse(j.payload.JobStateURL)
	if err != nil {
		return errors.Wrap(err, "couldn't parse base URL template")
	}

	u, err := template.Expand(map[string]interface{}{
		"job_id": j.payload.Job.ID,
	})
	if err != nil {
		return errors.Wrap(err, "couldn't expand base URL template")
	}

	req, err := http.NewRequest("PATCH", u, bytes.NewReader(encodedPayload))
	if err != nil {
		return errors.Wrap(err, "couldn't create request")
	}

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", j.payload.JWT))

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return errors.Wrap(err, "error making state update request")
	}

	if resp.StatusCode != http.StatusOK {
		return errors.Errorf("expected %d, but got %d", http.StatusOK, resp.StatusCode)
	}

	return nil
}
<|endoftext|>


<commit_before>package httputil

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"runtime/debug"
	"time"
)

var (
	CommonLogFmt = `%s - - [%s] "%s %s %s" %d %d "%s" "%s" %dms`
	LogFmt       = CommonLogFmt
	notify       = []chan *Access{}
)

func Notify(ch chan *Access) {
	notify = append(notify, ch)
}

type Access struct {
	RemoteAddr    string
	Time          time.Time
	Method        string
	RequestURI    string
	Proto         string
	StatusCode    int
	ContentLength int64
	Referer       string
	UserAgent     string
	Duration      time.Duration
}

func (a *Access) String() string {
	return fmt.Sprintf(LogFmt,
		a.RemoteAddr,
		a.Time.Format("02/Jan/2006:15:04:05 -0700"),
		a.Method,
		a.RequestURI,
		a.Proto,
		a.StatusCode,
		a.ContentLength,
		a.Referer,
		a.UserAgent,
		a.Duration/time.Millisecond)
}

type baseHandler struct {
	inner http.Handler
}

type responseWriter struct {
	StatusCode int
	inner      http.ResponseWriter
}

func (rw *responseWriter) HasStatus() bool {
	return rw.StatusCode != 0
}

func (rw *responseWriter) WriteHeader(status int) {
	rw.StatusCode = status
	rw.inner.WriteHeader(status)
}

func (rw *responseWriter) Write(b []byte) (int, error) {
	return rw.inner.Write(b)
}

func (rw *responseWriter) Header() http.Header {
	return rw.inner.Header()
}

func logRequest(r *http.Request, statusCode int, delta time.Duration) {
	var referer, remoteAddr, userAgent string

	if h, ok := r.Header["Referer"]; ok {
		referer = h[0]
	} else {
		referer = "-"
	}

	if h, ok := r.Header["X-Forwarded-For"]; ok {
		remoteAddr = h[0]
	} else {
		if host, _, err := net.SplitHostPort(r.RemoteAddr); err != nil {
			remoteAddr = "?"
		} else {
			remoteAddr = host
		}
	}

	if h, ok := r.Header["User-Agent"]; ok {
		userAgent = h[0]
	} else {
		userAgent = "-"
	}

	for _, ch := range notify {
		ch <- &Access{
			remoteAddr,
			time.Now(),
			r.Method,
			r.RequestURI,
			r.Proto,
			statusCode,
			r.ContentLength,
			referer,
			userAgent,
			delta,
		}
	}
}

func (h *baseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var delta time.Duration

	rw := &responseWriter{inner: w}

	defer func() {
		if e := recover(); e != nil {
			if !rw.HasStatus() {
				rw.WriteHeader(http.StatusInternalServerError)
			}
			log.Printf("panic: %v", e)
			log.Println(debug.Stack())
		}
		if rw.HasStatus() {
			logRequest(r, rw.StatusCode, delta)
		} else {
			logRequest(r, http.StatusOK, delta)
		}
	}()

	t := time.Now()
	h.inner.ServeHTTP(rw, r)
	delta = time.Since(t)
}

func init() {
	ch := make(chan *Access, 1)

	Notify(ch)

	go func() {
		for {
			log.Println((<-ch).String())
		}
	}()
}
<commit_msg>Various improvements<commit_after>package httputil

import (
	"fmt"
	"html"
	"log"
	"net"
	"net/http"
	"runtime/debug"
	"strconv"
	"time"
)

const CommonLogFmt = `%s - - [%s] "%s %s %s" %d %d "%s" "%s" %d`

var (
	LogFmt = CommonLogFmt
	notify = []chan *Access{}
)

func Notify(ch chan *Access) {
	notify = append(notify, ch)
}

type Access struct {
	RemoteAddr    string
	Time          time.Time
	Method        string
	RequestURI    string
	Proto         string
	StatusCode    int
	ContentLength int64
	Referer       string
	UserAgent     string
	Duration      time.Duration
}

func (a *Access) String() string {
	return fmt.Sprintf(LogFmt,
		a.RemoteAddr,
		a.Time.Format("02/Jan/2006:15:04:05 -0700"),
		a.Method,
		a.RequestURI,
		a.Proto,
		a.StatusCode,
		a.ContentLength,
		a.Referer,
		a.UserAgent,
		a.Duration/time.Millisecond)
}

type baseHandler struct {
	inner       http.Handler
	contentType string
}

func NewHandler(inner http.Handler, ctype string) http.Handler {
	return &baseHandler{inner, ctype}
}

type ResponseWriter struct {
	StatusCode  int
	ContentType string
	inner       http.ResponseWriter
}

func (rw *ResponseWriter) HasStatus() bool {
	return rw.StatusCode != 0
}

func (rw *ResponseWriter) WriteHeader(status int) {
	rw.StatusCode = status
	rw.inner.WriteHeader(status)
}

func (rw *ResponseWriter) Write(b []byte) (int, error) {
	return rw.inner.Write(b)
}

func (rw *ResponseWriter) Header() http.Header {
	return rw.inner.Header()
}

func logRequest(r *http.Request, statusCode int, delta time.Duration) {
	var referer, remoteAddr, userAgent string

	if h, ok := r.Header["Referer"]; ok {
		referer = h[0]
	} else {
		referer = "-"
	}

	if h, ok := r.Header["X-Forwarded-For"]; ok {
		remoteAddr = h[0]
	} else {
		if host, _, err := net.SplitHostPort(r.RemoteAddr); err != nil {
			remoteAddr = "?"
		} else {
			remoteAddr = host
		}
	}

	if h, ok := r.Header["User-Agent"]; ok {
		userAgent = h[0]
	} else {
		userAgent = "-"
	}

	for _, ch := range notify {
		ch <- &Access{
			remoteAddr,
			time.Now(),
			r.Method,
			r.RequestURI,
			r.Proto,
			statusCode,
			r.ContentLength,
			referer,
			userAgent,
			delta,
		}
	}
}

func (h *baseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var delta time.Duration

	rw := &ResponseWriter{inner: w, ContentType: h.contentType}
	rw.Header().Set("Content-Type", h.contentType)

	defer func() {
		if e := recover(); e != nil {
			if !rw.HasStatus() {
				rw.WriteHeader(http.StatusInternalServerError)
			}
			log.Printf("panic: %v", e)
			debug.PrintStack()
		}
		if rw.HasStatus() {
			logRequest(r, rw.StatusCode, delta)
		} else {
			logRequest(r, http.StatusOK, delta)
		}
	}()

	t := time.Now()
	h.inner.ServeHTTP(rw, r)
	delta = time.Since(t)
}

func Error(w http.ResponseWriter, err string, code int) {
	if rw, ok := w.(*ResponseWriter); ok {
		switch rw.ContentType {
		case "application/json":
			err = fmt.Sprintf(`{"error":%s}`, strconv.QuoteToASCII(err))
		case "text/html":
			err = html.EscapeString(err)
		case "text/plain":
			fallthrough
		default:
			err = "error: " + err
		}
	}
	http.Error(w, err, code)
}

func init() {
	ch := make(chan *Access, 1)

	Notify(ch)

	go func() {
		for {
			log.Println((<-ch).String())
		}
	}()
}
<|endoftext|>


<commit_before>// SPDX-License-Identifier: MIT

package server

import (
	"io/fs"
	"os"

	"github.com/issue9/cache"
	"github.com/issue9/sliceutil"
)

type Module struct {
	srv *Server
	id  string
	fs  []fs.FS
}

// NewModule 声明新的模块
//
// id 模块的 ID，需要全局唯一，且要符合 fs.ValidPath 的要求。
func (srv *Server) NewModule(id string) *Module {
	if !fs.ValidPath(id) {
		panic("无效的 id 格式。")
	}

	if sliceutil.Exists(srv.modules, func(e string) bool { return e == id }) {
		panic("存在同名模块：" + id)
	}

	f := make([]fs.FS, 0, 2)
	if fsys, err := fs.Sub(srv, id); err != nil {
		srv.Logs().Error(err) // 不退出
	} else {
		f = append(f, fsys)
	}

	srv.modules = append(srv.modules, id)
	return &Module{
		srv: srv,
		fs:  f,
		id:  id,
	}
}

// NewModule 声明 id 值为 m.ID + "/" + id 的新模块
func (m *Module) NewModule(id string) *Module {
	return m.Server().NewModule(m.ID() + "/" + id)
}

// ID 模块的唯一 ID
func (m *Module) ID() string { return m.id }

func (m *Module) Server() *Server { return m.srv }

// AddFS 添加文件系统
//
// Module 默认以 id 为名称相对于 Server 创建了一个文件系统，
// 此操作会将 fsys 作为 Module 另一个文件系统与 Module 相关联，
// 当执行 Open 等操作时，会依然以关联顺序查找相应的文件系统，直到找到。
//
// 需要注意的是，fs.Glob 不是搜索所有的 fsys 然后返回集合。
func (m *Module) AddFS(fsys ...fs.FS) { m.fs = append(m.fs, fsys...) }

func (m *Module) Open(name string) (fs.File, error) {
	for _, fsys := range m.fs {
		if existsFS(fsys, name) {
			return fsys.Open(name)
		}
	}
	return nil, fs.ErrNotExist
}

func (m *Module) Glob(pattern string) ([]string, error) {
	for _, fsys := range m.fs {
		if matches, err := fs.Glob(fsys, pattern); len(matches) > 0 {
			return matches, err
		}
	}
	return nil, nil
}

// Cache 获取缓存对象
//
// 该缓存对象的 key 会自动添加 Module.ID 作为其前缀。
func (m *Module) Cache() cache.Access {
	return cache.Prefix(m.ID(), m.Server().Cache())
}

func existsFS(fsys fs.FS, p string) bool {
	_, err := fs.Stat(fsys, p)
	return err == nil || os.IsExist(err)
}
<commit_msg>feat(server): 添加 Module.LoadLocale<commit_after>// SPDX-License-Identifier: MIT

package server

import (
	"io/fs"
	"os"

	"github.com/issue9/cache"
	"github.com/issue9/sliceutil"
)

type Module struct {
	srv *Server
	id  string
	fs  []fs.FS
}

// NewModule 声明新的模块
//
// id 模块的 ID，需要全局唯一，且要符合 fs.ValidPath 的要求。
func (srv *Server) NewModule(id string) *Module {
	if !fs.ValidPath(id) {
		panic("无效的 id 格式。")
	}

	if sliceutil.Exists(srv.modules, func(e string) bool { return e == id }) {
		panic("存在同名模块：" + id)
	}

	f := make([]fs.FS, 0, 2)
	if fsys, err := fs.Sub(srv, id); err != nil {
		srv.Logs().Error(err) // 不退出
	} else {
		f = append(f, fsys)
	}

	srv.modules = append(srv.modules, id)
	return &Module{
		srv: srv,
		fs:  f,
		id:  id,
	}
}

// NewModule 声明 id 值为 m.ID + "/" + id 的新模块
func (m *Module) NewModule(id string) *Module {
	return m.Server().NewModule(m.ID() + "/" + id)
}

// ID 模块的唯一 ID
func (m *Module) ID() string { return m.id }

func (m *Module) Server() *Server { return m.srv }

// AddFS 添加文件系统
//
// Module 默认以 id 为名称相对于 Server 创建了一个文件系统，
// 此操作会将 fsys 作为 Module 另一个文件系统与 Module 相关联，
// 当执行 Open 等操作时，会依然以关联顺序查找相应的文件系统，直到找到。
//
// 需要注意的是，fs.Glob 不是搜索所有的 fsys 然后返回集合。
func (m *Module) AddFS(fsys ...fs.FS) { m.fs = append(m.fs, fsys...) }

func (m *Module) Open(name string) (fs.File, error) {
	for _, fsys := range m.fs {
		if existsFS(fsys, name) {
			return fsys.Open(name)
		}
	}
	return nil, fs.ErrNotExist
}

func (m *Module) Glob(pattern string) ([]string, error) {
	for _, fsys := range m.fs {
		if matches, err := fs.Glob(fsys, pattern); len(matches) > 0 {
			return matches, err
		}
	}
	return nil, nil
}

// Cache 获取缓存对象
//
// 该缓存对象的 key 会自动添加 Module.ID 作为其前缀。
func (m *Module) Cache() cache.Access {
	return cache.Prefix(m.ID(), m.Server().Cache())
}

func existsFS(fsys fs.FS, p string) bool {
	_, err := fs.Stat(fsys, p)
	return err == nil || os.IsExist(err)
}

// LoadLocale 加载当前模块文件系统下的本地化文件
func (m *Module) LoadLocale(glob string) error {
	return m.Server().Locale().LoadFileFS(m, glob)
}
<|endoftext|>


<commit_before>package main

import (
	"code.google.com/p/go.net/websocket"
	"log"
)

type player struct {
	ws          *websocket.Conn
	Robot       robot
	send        chan *[]robot
	Instruction instruction
}

type instruction struct {
	MoveTo position `json:"move_to"`
	FireAt position `json:"fire_at"`
}

func (p *player) sender() {
	for robots := range p.send {
		log.Printf("%s sending %d robots", p.Robot.Id, len(*robots))
		err := websocket.JSON.Send(p.ws, *robots)
		if err != nil {
			break
		}
		log.Printf("%s: state sent", p.Robot.Id)
	}
	p.ws.Close()
}

func (p *player) recv() {
	for {
		var msg instruction
		log.Printf("recv: %s waiting for a recv", p.Robot.Id)
		err := websocket.JSON.Receive(p.ws, &msg)
		log.Printf("recv: %s: %+v\n", p.Robot.Id, msg)
		if err != nil {
			log.Print(err)
			break
		}
		p.Instruction = msg
	}
	p.ws.Close()
}
<commit_msg>fixed jitter<commit_after>package main

import (
	"code.google.com/p/go.net/websocket"
	"log"
)

type player struct {
	ws          *websocket.Conn
	Robot       robot
	send        chan *[]robot
	Instruction instruction
}

type instruction struct {
	MoveTo position `json:"move_to"`
	FireAt position `json:"fire_at"`
}

func (p *player) sender() {
	for robots := range p.send {
		log.Printf("%s sending %d robots", p.Robot.Id, len(*robots))
		err := websocket.JSON.Send(p.ws, *robots)
		if err != nil {
			break
		}
		log.Printf("%s: state sent", p.Robot.Id)
	}
	p.ws.Close()
}

func (p *player) recv() {
	for {
		var msg instruction
		err := websocket.JSON.Receive(p.ws, &msg)
		if err != nil {
			log.Print(err)
			break
		}
		p.Robot.MoveTo = msg.MoveTo
	}
	p.ws.Close()
}

func (p *player) nudge() {
	switch {
	case p.Robot.Position.X < p.Robot.MoveTo.X:
		p.Robot.Position.X += 1
	case p.Robot.Position.X > p.Robot.MoveTo.X:
		p.Robot.Position.X -= 1
	}
	switch {
	case p.Robot.Position.Y < p.Robot.MoveTo.Y:
		p.Robot.Position.Y += 1
	case p.Robot.Position.Y > p.Robot.MoveTo.Y:
		p.Robot.Position.Y -= 1
	}
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	"net/http"
	"os"

	"github.com/cardigann/cardigann/config"
	"github.com/cardigann/cardigann/logger"
)

// Server is an http server which wraps the Handler
type Server struct {
	Bind, Port, Passphrase string
	version                string
	config                 config.Config
}

func New(conf config.Config, version string) (*Server, error) {
	bind, err := config.GetGlobalConfig("bind", "0.0.0.0", conf)
	if err != nil {
		return nil, err
	}

	port, err := config.GetGlobalConfig("port", "5060", conf)
	if err != nil {
		return nil, err
	}

	passphrase, err := config.GetGlobalConfig("passphrase", "", conf)
	if err != nil {
		return nil, err
	}

	if envPort := os.Getenv("PORT"); envPort != "" {
		port = envPort
	}

	if version == "" {
		version = "dev"
	}

	return &Server{
		Bind:       bind,
		Port:       port,
		Passphrase: passphrase,
		config:     conf,
		version:    version,
	}, nil
}

func (s *Server) Listen() error {
	logger.Logger.Infof("Cardigann %s", s.version)

	listenOn := fmt.Sprintf("%s:%s", s.Bind, s.Port)
	logger.Logger.Infof("Listening on %s", listenOn)

	h, err := NewHandler(Params{
		Passphrase: s.Passphrase,
		Config:     s.config,
		Version:    s.version,
	})
	if err != nil {
		return err
	}

	return http.ListenAndServe(listenOn, h)
}
<commit_msg>Add some path debugging to server startup<commit_after>package server

import (
	"fmt"
	"net/http"
	"os"

	"github.com/cardigann/cardigann/config"
	"github.com/cardigann/cardigann/logger"
)

// Server is an http server which wraps the Handler
type Server struct {
	Bind, Port, Passphrase string
	version                string
	config                 config.Config
}

func New(conf config.Config, version string) (*Server, error) {
	bind, err := config.GetGlobalConfig("bind", "0.0.0.0", conf)
	if err != nil {
		return nil, err
	}

	port, err := config.GetGlobalConfig("port", "5060", conf)
	if err != nil {
		return nil, err
	}

	passphrase, err := config.GetGlobalConfig("passphrase", "", conf)
	if err != nil {
		return nil, err
	}

	if envPort := os.Getenv("PORT"); envPort != "" {
		port = envPort
	}

	if version == "" {
		version = "dev"
	}

	return &Server{
		Bind:       bind,
		Port:       port,
		Passphrase: passphrase,
		config:     conf,
		version:    version,
	}, nil
}

func (s *Server) Listen() error {
	logger.Logger.Infof("Cardigann %s", s.version)

	path, err := config.GetConfigPath()
	if err != nil {
		return err
	}

	logger.Logger.Debugf("Config path is %s", path)
	logger.Logger.Debugf("Cache dir is %s", config.GetCachePath("/"))

	for _, dir := range config.GetDefinitionDirs() {
		logger.Logger.Debugf("Definition load path is %s", dir)
	}

	listenOn := fmt.Sprintf("%s:%s", s.Bind, s.Port)
	logger.Logger.Infof("Listening on %s", listenOn)

	h, err := NewHandler(Params{
		Passphrase: s.Passphrase,
		Config:     s.config,
		Version:    s.version,
	})
	if err != nil {
		return err
	}

	return http.ListenAndServe(listenOn, h)
}
<|endoftext|>


<commit_before>package main

/*
This file initializes the
*/

import (
	// General
	"flag"
	"net/http"
	"os"
	"reflect"
	"strconv"

	"github.com/golang/glog"
	_ "github.com/gorilla/mux"
	"github.com/iambc/xerrors"

	//DB
	"database/sql"

	_ "github.com/lib/pq"
)

var api abcAPI

func main() {
	flag.Parse()
	var (
		dbConnString       = os.Getenv("ABC_DB_CONN_STRING")
		filesServerAddr    = os.Getenv("ABC_FILES_SERVER_URL")
		filesDir           = os.Getenv("ABC_FILES_DIR")
		serverEndpointAddr = os.Getenv("ABC_SERVER_ENDPOINT_URL")
	)

	db, err := sql.Open("postgres", dbConnString)
	if err != nil {
		glog.Fatal("Connection to the database has failed")
	}

	api.wr = &writerrdb{db}

	go http.ListenAndServe(":"+filesServerAddr, http.FileServer(http.Dir(filesDir)))
	http.HandleFunc("/api", QueryStringHandler)
	http.ListenAndServe(`:`+serverEndpointAddr, nil)
}

func QueryStringHandler(res http.ResponseWriter, req *http.Request) {
	res.Header().Set("Access-Control-Allow-Origin", "*")
	values := req.URL.Query()
	command, isPassed := values[`command`]
	if !isPassed {
		res.Write([]byte(`{"Status":"error","Msg":"Parameter 'command' is undefined.","Payload":null}`))
		return
	}

	var resp []byte
	var err error
	if command[0] == `getBoards` {
		apiKey, isPassed := values[`api_key`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'api_key' is undefined.","Payload":null}`))
			return
		}
		resp, err = api.getBoards(apiKey[0])

	} else if command[0] == `getActiveThreadsForBoard` {
		apiKey, isPassed := values[`api_key`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'api_key' is undefined.","Payload":null}`))
			return
		}
		boardID, isPassed := values[`board_Id`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'board_Id' is undefined.","Payload":null}`))
			return
		}
		boardIDInt, err := strconv.Atoi(boardID[0])
		if err != nil {
			res.Write([]byte(`{"Status":"error","Msg":"Wrong value for parameter board_Id","Payload":null}`))
			return
		}
		resp, err = api.getActiveThreadsForBoard(apiKey[0], boardIDInt)

	} else if command[0] == `getPostsForThread` {
		apiKey, isPassed := values[`api_key`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'api_key' is undefined.","Payload":null}`))
			return
		}
		threadID, isPassed := values[`thread_Id`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'thread_Id' is undefined.","Payload":null}`))
			return
		}
		threadIDInt, err := strconv.Atoi(threadID[0])
		if err != nil {
			res.Write([]byte(`{"Status":"error","Msg":"Wrong value for parameter board_Id","Payload":null}`))
			return
		}
		resp, err = api.getPostsForThread(apiKey[0], threadIDInt)

	} else if command[0] == `addPostToThread` {
		threadID, isPassed := values[`thread_Id`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'board_Id' is undefined.","Payload":null}`))
			return
		}
		threadIDInt, err := strconv.Atoi(threadID[0])
		if err != nil {
			res.Write([]byte(`{"Status":"error","Msg":"Wrong value for parameter board_Id","Payload":null}`))
			return
		}
		threadBodyPost, isPassed := values[`thread_body_post`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'thread_body_post' is undefined.","Payload":null}`))
			return
		}
		attachmentURL, isPassed := values[`attachment_Id`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'attachment_Id' is undefined.","Payload":null}`))
			return
		}
		clientRemoteAddr, isPassed := values[`clientRemoteAddr`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'clientRemoteAddr' is undefined.","Payload":null}`))
			return
		}
		resp, err = api.addPostToThread(threadIDInt, threadBodyPost[0], &attachmentURL[0], clientRemoteAddr[0])

	} else if command[0] == `addThread` {
		boardID, isPassed := values[`board_Id`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'board_Id' is undefined.","Payload":null}`))
			return
		}
		boardIDInt, err := strconv.Atoi(boardID[0])
		if err != nil {
			res.Write([]byte(`{"Status":"error","Msg":"Wrong value for parameter board_Id","Payload":null}`))
			return
		}
		threadName, isPassed := values[`thread_name`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'thread_name' is undefined.","Payload":null}`))
			return
		}
		resp, err = api.addThread(boardIDInt, threadName[0])

	} else {
		res.Write([]byte(`{"Status":"error","Msg":"No such command exists.","Payload":null}`))
		glog.Error("command: ", command[0])
	}

	if err != nil {
		if string(reflect.TypeOf(err).Name()) == `XError` {
			res.Write([]byte(`{"Status":"` + err.(xerrors.XError).Code + `","Msg":"` + err.Error() + `","Payload":null}`))
		} else {
			res.Write([]byte(`{"Status":"000","Msg":"Application Error!","Payload":null}`))
		}
		glog.Error(err)
		return
	}
	res.Write(resp)

}
<commit_msg>feat: fixed param name board_Id to board_id<commit_after>package main

/*
This file initializes the
*/

import (
	// General
	"flag"
	"net/http"
	"os"
	"reflect"
	"strconv"

	"github.com/golang/glog"
	_ "github.com/gorilla/mux"
	"github.com/iambc/xerrors"

	//DB
	"database/sql"

	_ "github.com/lib/pq"
)

var api abcAPI

func main() {
	flag.Parse()
	var (
		dbConnString       = os.Getenv("ABC_DB_CONN_STRING")
		filesServerAddr    = os.Getenv("ABC_FILES_SERVER_URL")
		filesDir           = os.Getenv("ABC_FILES_DIR")
		serverEndpointAddr = os.Getenv("ABC_SERVER_ENDPOINT_URL")
	)

	db, err := sql.Open("postgres", dbConnString)
	if err != nil {
		glog.Fatal("Connection to the database has failed")
	}

	api.wr = &writerrdb{db}

	go http.ListenAndServe(":"+filesServerAddr, http.FileServer(http.Dir(filesDir)))
	http.HandleFunc("/api", QueryStringHandler)
	http.ListenAndServe(`:`+serverEndpointAddr, nil)
}

func QueryStringHandler(res http.ResponseWriter, req *http.Request) {
	res.Header().Set("Access-Control-Allow-Origin", "*")
	values := req.URL.Query()
	command, isPassed := values[`command`]
	if !isPassed {
		res.Write([]byte(`{"Status":"error","Msg":"Parameter 'command' is undefined.","Payload":null}`))
		return
	}

	var resp []byte
	var err error
	if command[0] == `getBoards` {
		apiKey, isPassed := values[`api_key`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'api_key' is undefined.","Payload":null}`))
			return
		}
		resp, err = api.getBoards(apiKey[0])

	} else if command[0] == `getActiveThreadsForBoard` {
		apiKey, isPassed := values[`api_key`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'api_key' is undefined.","Payload":null}`))
			return
		}
		boardID, isPassed := values[`board_id`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'board_Id' is undefined.","Payload":null}`))
			return
		}
		boardIDInt, err := strconv.Atoi(boardID[0])
		if err != nil {
			res.Write([]byte(`{"Status":"error","Msg":"Wrong value for parameter board_Id","Payload":null}`))
			return
		}
		resp, err = api.getActiveThreadsForBoard(apiKey[0], boardIDInt)

	} else if command[0] == `getPostsForThread` {
		apiKey, isPassed := values[`api_key`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'api_key' is undefined.","Payload":null}`))
			return
		}
		threadID, isPassed := values[`thread_Id`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'thread_Id' is undefined.","Payload":null}`))
			return
		}
		threadIDInt, err := strconv.Atoi(threadID[0])
		if err != nil {
			res.Write([]byte(`{"Status":"error","Msg":"Wrong value for parameter board_Id","Payload":null}`))
			return
		}
		resp, err = api.getPostsForThread(apiKey[0], threadIDInt)

	} else if command[0] == `addPostToThread` {
		threadID, isPassed := values[`thread_Id`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'board_Id' is undefined.","Payload":null}`))
			return
		}
		threadIDInt, err := strconv.Atoi(threadID[0])
		if err != nil {
			res.Write([]byte(`{"Status":"error","Msg":"Wrong value for parameter board_Id","Payload":null}`))
			return
		}
		threadBodyPost, isPassed := values[`thread_body_post`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'thread_body_post' is undefined.","Payload":null}`))
			return
		}
		attachmentURL, isPassed := values[`attachment_Id`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'attachment_Id' is undefined.","Payload":null}`))
			return
		}
		clientRemoteAddr, isPassed := values[`clientRemoteAddr`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'clientRemoteAddr' is undefined.","Payload":null}`))
			return
		}
		resp, err = api.addPostToThread(threadIDInt, threadBodyPost[0], &attachmentURL[0], clientRemoteAddr[0])

	} else if command[0] == `addThread` {
		boardID, isPassed := values[`board_Id`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'board_Id' is undefined.","Payload":null}`))
			return
		}
		boardIDInt, err := strconv.Atoi(boardID[0])
		if err != nil {
			res.Write([]byte(`{"Status":"error","Msg":"Wrong value for parameter board_Id","Payload":null}`))
			return
		}
		threadName, isPassed := values[`thread_name`]
		if !isPassed {
			res.Write([]byte(`{"Status":"error","Msg":"Parameter 'thread_name' is undefined.","Payload":null}`))
			return
		}
		resp, err = api.addThread(boardIDInt, threadName[0])

	} else {
		res.Write([]byte(`{"Status":"error","Msg":"No such command exists.","Payload":null}`))
		glog.Error("command: ", command[0])
	}

	if err != nil {
		if string(reflect.TypeOf(err).Name()) == `XError` {
			res.Write([]byte(`{"Status":"` + err.(xerrors.XError).Code + `","Msg":"` + err.Error() + `","Payload":null}`))
		} else {
			res.Write([]byte(`{"Status":"000","Msg":"Application Error!","Payload":null}`))
		}
		glog.Error(err)
		return
	}
	res.Write(resp)

}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"net"
	"os"
	"regexp"
	"runtime"
	"strings"
	"sync"

	"github.com/dustywilson/lmifeedr"
	"github.com/scjalliance/logmein"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"gopkg.in/gcfg.v1"
)

type config struct {
	RSS struct {
		ProfileID uint64 `gcfg:"profile"`
		Key       string `gcfg:"key"`
	} `gcfg:"rss"`
}

var configFile = flag.String("c", "config.conf", "Config file")

// ErrNoMatch is an error that means that the requested query returned zero results
var ErrNoMatch = errors.New("No match for query")

type lmiServer struct {
	logmein              *logmein.LMI
	regexpSet            map[string]*regexp.Regexp
	hostIDSubscribers    map[uint64][]chan<- *logmein.Computer
	nameMatchSubscribers map[*regexp.Regexp][]chan<- *logmein.Computer
	ipMatchSubscribers   map[string][]chan<- *logmein.Computer
	sync.RWMutex
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	flag.Parse()

	cfg := new(config)
	err := gcfg.ReadFileInto(cfg, *configFile)
	if err != nil {
		fmt.Println("Config Error: ", err)
		os.Exit(1)
	}

	l, err := net.Listen("tcp", "127.0.0.1:23232")
	if err != nil {
		panic(err)
	}

	lmi := logmein.NewLMI(cfg.RSS.ProfileID, cfg.RSS.Key)

	svr := &lmiServer{
		logmein:              lmi,
		regexpSet:            make(map[string]*regexp.Regexp),
		hostIDSubscribers:    make(map[uint64][]chan<- *logmein.Computer),
		nameMatchSubscribers: make(map[*regexp.Regexp][]chan<- *logmein.Computer),
		ipMatchSubscribers:   make(map[string][]chan<- *logmein.Computer),
	}

	grpcServer := grpc.NewServer()
	lmifeedr.RegisterLMIFeedrServer(grpcServer, svr)
	go func() { panic(grpcServer.Serve(l)) }()

	recv := make(chan *logmein.Computer)
	stop := make(chan struct{})
	go lmi.Watch(recv, stop, false)

	for {
		svr.processComputer(<-recv)
	}
}

func (svr *lmiServer) processComputer(computer *logmein.Computer) {
	svr.RLock()
	defer svr.RUnlock()

	fmt.Printf("PC:[%+v]\n", computer)

	// receivedThisComputer map should help prevent duplicate computers from crossing the wire
	receivedThisComputer := make(map[chan<- *logmein.Computer]bool)

	if subs, ok := svr.hostIDSubscribers[computer.HostID()]; ok {
		for _, sub := range subs {
			if _, ok := receivedThisComputer[sub]; !ok {
				sub <- computer
				receivedThisComputer[sub] = true
			}
		}
	} else if subs, ok := svr.hostIDSubscribers[computer.OldHostID()]; ok {
		// presently I know of no situation where we ever see an "OldHostID != HostID" except at creation where OldHostID == 0
		for _, sub := range subs {
			if _, ok := receivedThisComputer[sub]; !ok {
				sub <- computer
				receivedThisComputer[sub] = true
			}
		}
	}

	for _, re := range svr.regexpSet {
		if subs, ok := svr.nameMatchSubscribers[re]; ok {
			if re.MatchString(computer.Name()) || re.MatchString(computer.OldName()) {
				for _, sub := range subs {
					if _, ok := receivedThisComputer[sub]; !ok {
						sub <- computer
						receivedThisComputer[sub] = true
					}
				}
			}
		}
	}

	for ipNetStr, subs := range svr.ipMatchSubscribers {
		_, ipNet, err := net.ParseCIDR(ipNetStr)
		if err == nil {
			if ipNet.Contains(computer.IPAddress()) || ipNet.Contains(computer.OldIPAddress()) {
				for _, sub := range subs {
					sub <- computer
				}
			}
		}
	}
}

func convertComputerToGRPC(c *logmein.Computer) *lmifeedr.Computer {
	v := &lmifeedr.Computer{
		HostID:    c.HostID(),
		OldHostID: c.OldHostID(),
		Name:      c.Name(),
		OldName:   c.OldName(),
		Status:    int32(c.Status()),
		OldStatus: int32(c.OldStatus()),
		ChangeSet: uint32(c.GetChangeSet()),
	}
	if c.IPAddress() != nil {
		v.Ip = c.IPAddress().String()
	}
	if c.OldIPAddress() != nil {
		v.OldIp = c.OldIPAddress().String()
	}
	return v
}

func (svr *lmiServer) registerWatchChan(req *lmifeedr.WatchRequest, firehose chan<- *logmein.Computer) error {
	// update this in sync with `unregisterWatchChan`

	svr.Lock()
	defer svr.Unlock()

	for _, id := range req.HostID {
		if _, ok := svr.hostIDSubscribers[id]; !ok {
			svr.hostIDSubscribers[id] = []chan<- *logmein.Computer{firehose}
		} else {
			svr.hostIDSubscribers[id] = append(svr.hostIDSubscribers[id], firehose)
		}
	}

	for _, nameMatch := range req.NameMatch {
		var re *regexp.Regexp
		if reCache, ok := svr.regexpSet[nameMatch]; ok {
			re = reCache
		} else {
			var err error
			re, err = regexp.Compile(nameMatch)
			if err != nil {
				return err
			}
			svr.regexpSet[nameMatch] = re
		}
		if _, ok := svr.nameMatchSubscribers[re]; !ok {
			svr.nameMatchSubscribers[re] = []chan<- *logmein.Computer{firehose}
		} else {
			svr.nameMatchSubscribers[re] = append(svr.nameMatchSubscribers[re], firehose)
		}
	}

	for _, ip := range req.IpMatch {
		ip = ensureIPIsCIDR(ip)
		_, _, err := net.ParseCIDR(ip)
		if err != nil {
			return err
		}
		if _, ok := svr.ipMatchSubscribers[ip]; !ok {
			svr.ipMatchSubscribers[ip] = []chan<- *logmein.Computer{firehose}
		} else {
			svr.ipMatchSubscribers[ip] = append(svr.ipMatchSubscribers[ip], firehose)
		}
	}

	return nil
}

func (svr *lmiServer) unregisterWatchChan(req *lmifeedr.WatchRequest, firehose chan<- *logmein.Computer) error {
	// update this in sync with `registerWatchChan`

	svr.Lock()
	defer svr.Unlock()

	for _, id := range req.HostID {
		if subs, ok := svr.hostIDSubscribers[id]; ok {
		UnsubHostIDLoop:
			for {
				for i, ch := range subs {
					if ch == firehose {
						subs, subs[len(subs)-1] = append(subs[:i], subs[i+1:]...), nil
						continue UnsubHostIDLoop
					}
				}
				break
			}
		}
	}

	for _, nameMatch := range req.NameMatch {
		if re, ok := svr.regexpSet[nameMatch]; ok {
			if subs, ok := svr.nameMatchSubscribers[re]; ok {
			UnsubNameMatchLoop:
				for {
					for i, ch := range subs {
						if ch == firehose {
							subs, subs[len(subs)-1] = append(subs[:i], subs[i+1:]...), nil
							continue UnsubNameMatchLoop
						}
					}
					break
				}
			}
		}
	}

	for _, ip := range req.IpMatch {
		ip = ensureIPIsCIDR(ip)
		if subs, ok := svr.ipMatchSubscribers[ip]; ok {
		UnsubIPMatchLoop:
			for {
				for i, ch := range subs {
					if ch == firehose {
						subs, subs[len(subs)-1] = append(subs[:i], subs[i+1:]...), nil
						continue UnsubIPMatchLoop
					}
				}
				break
			}
		}
	}

	close(firehose)

	return nil
}

// GetComputer returns the first match.  It's best to be used for looking up computers by ID or name where you expect exactly one result.
func (svr *lmiServer) GetComputer(c context.Context, req *lmifeedr.ComputerRequest) (*lmifeedr.Computer, error) {
	svr.RLock()
	defer svr.RUnlock()

	computers := svr.logmein.Computers()
	if req.HostID > 0 {
		if c, ok := computers[req.HostID]; ok {
			return convertComputerToGRPC(c), nil
		}
	}
	if len(req.NameMatch) > 0 {
		re, err := regexp.Compile(req.NameMatch)
		if err != nil {
			return nil, err
		}
		for _, c := range computers {
			if re.MatchString(c.Name()) {
				return convertComputerToGRPC(c), nil
			}
		}
	}
	if len(req.IpMatch) > 0 {
		_, ipNet, err := net.ParseCIDR(ensureIPIsCIDR(req.IpMatch))
		if err != nil {
			return nil, err
		}
		for _, c := range computers {
			if ipNet.Contains(c.IPAddress()) {
				return convertComputerToGRPC(c), nil
			}
		}
	}
	return nil, ErrNoMatch
}

// GetComputers returns a stream of all matching computers.
func (svr *lmiServer) GetComputers(req *lmifeedr.ComputerRequest, stream lmifeedr.LMIFeedr_GetComputersServer) error {
	svr.RLock()
	defer svr.RUnlock()

	returned := make(map[uint64]bool) // which computers have we already sent over the wire this time around?

	computers := svr.logmein.Computers()
	if req.HostID > 0 {
		if c, ok := computers[req.HostID]; ok {
			if !returned[c.HostID()] {
				if err := stream.Send(convertComputerToGRPC(c)); err != nil {
					return err
				}
				returned[c.HostID()] = true
			}
		}
	}
	if len(req.NameMatch) > 0 {
		re, err := regexp.Compile(req.NameMatch)
		if err != nil {
			return err
		}
		for _, c := range computers {
			if re.MatchString(c.Name()) {
				if !returned[c.HostID()] {
					if err := stream.Send(convertComputerToGRPC(c)); err != nil {
						return err
					}
					returned[c.HostID()] = true
				}
			}
		}
	}
	if len(req.IpMatch) > 0 {
		_, ipNet, err := net.ParseCIDR(ensureIPIsCIDR(req.IpMatch))
		if err != nil {
			return err
		}
		for _, c := range computers {
			if ipNet.Contains(c.IPAddress()) {
				if !returned[c.HostID()] {
					if err := stream.Send(convertComputerToGRPC(c)); err != nil {
						return err
					}
					returned[c.HostID()] = true
				}
			}
		}
	}
	if len(returned) > 0 {
		return nil
	}
	return ErrNoMatch
}

// Watch returns a neverending stream of state changes, until the client disconnects.
func (svr *lmiServer) Watch(req *lmifeedr.WatchRequest, stream lmifeedr.LMIFeedr_WatchServer) error {
	firehose := make(chan *logmein.Computer)
	err := svr.registerWatchChan(req, firehose)
	if err != nil {
		return err
	}
	defer svr.unregisterWatchChan(req, firehose)
	for {
		water := <-firehose
		err = stream.Send(convertComputerToGRPC(water))
		if err != nil {
			return err
		}
	}
}

func ensureIPIsCIDR(ip string) string {
	if !strings.Contains(ip, "/") {
		ip = ip + "/32"
	}
	return ip
}
<commit_msg>Server is less noisy on console<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"net"
	"os"
	"regexp"
	"runtime"
	"strings"
	"sync"

	"github.com/dustywilson/lmifeedr"
	"github.com/scjalliance/logmein"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"gopkg.in/gcfg.v1"
)

type config struct {
	RSS struct {
		ProfileID uint64 `gcfg:"profile"`
		Key       string `gcfg:"key"`
	} `gcfg:"rss"`
}

var configFile = flag.String("c", "config.conf", "Config file")

// ErrNoMatch is an error that means that the requested query returned zero results
var ErrNoMatch = errors.New("No match for query")

type lmiServer struct {
	logmein              *logmein.LMI
	regexpSet            map[string]*regexp.Regexp
	hostIDSubscribers    map[uint64][]chan<- *logmein.Computer
	nameMatchSubscribers map[*regexp.Regexp][]chan<- *logmein.Computer
	ipMatchSubscribers   map[string][]chan<- *logmein.Computer
	sync.RWMutex
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	flag.Parse()

	cfg := new(config)
	err := gcfg.ReadFileInto(cfg, *configFile)
	if err != nil {
		fmt.Println("Config Error: ", err)
		os.Exit(1)
	}

	l, err := net.Listen("tcp", "127.0.0.1:23232")
	if err != nil {
		panic(err)
	}

	lmi := logmein.NewLMI(cfg.RSS.ProfileID, cfg.RSS.Key)

	svr := &lmiServer{
		logmein:              lmi,
		regexpSet:            make(map[string]*regexp.Regexp),
		hostIDSubscribers:    make(map[uint64][]chan<- *logmein.Computer),
		nameMatchSubscribers: make(map[*regexp.Regexp][]chan<- *logmein.Computer),
		ipMatchSubscribers:   make(map[string][]chan<- *logmein.Computer),
	}

	grpcServer := grpc.NewServer()
	lmifeedr.RegisterLMIFeedrServer(grpcServer, svr)
	go func() { panic(grpcServer.Serve(l)) }()

	recv := make(chan *logmein.Computer)
	stop := make(chan struct{})
	go lmi.Watch(recv, stop, false)

	for {
		svr.processComputer(<-recv)
	}
}

func (svr *lmiServer) processComputer(computer *logmein.Computer) {
	svr.RLock()
	defer svr.RUnlock()

	// receivedThisComputer map should help prevent duplicate computers from crossing the wire
	receivedThisComputer := make(map[chan<- *logmein.Computer]bool)

	if subs, ok := svr.hostIDSubscribers[computer.HostID()]; ok {
		for _, sub := range subs {
			if _, ok := receivedThisComputer[sub]; !ok {
				sub <- computer
				receivedThisComputer[sub] = true
			}
		}
	} else if subs, ok := svr.hostIDSubscribers[computer.OldHostID()]; ok {
		// presently I know of no situation where we ever see an "OldHostID != HostID" except at creation where OldHostID == 0
		for _, sub := range subs {
			if _, ok := receivedThisComputer[sub]; !ok {
				sub <- computer
				receivedThisComputer[sub] = true
			}
		}
	}

	for _, re := range svr.regexpSet {
		if subs, ok := svr.nameMatchSubscribers[re]; ok {
			if re.MatchString(computer.Name()) || re.MatchString(computer.OldName()) {
				for _, sub := range subs {
					if _, ok := receivedThisComputer[sub]; !ok {
						sub <- computer
						receivedThisComputer[sub] = true
					}
				}
			}
		}
	}

	for ipNetStr, subs := range svr.ipMatchSubscribers {
		_, ipNet, err := net.ParseCIDR(ipNetStr)
		if err == nil {
			if ipNet.Contains(computer.IPAddress()) || ipNet.Contains(computer.OldIPAddress()) {
				for _, sub := range subs {
					sub <- computer
				}
			}
		}
	}
}

func convertComputerToGRPC(c *logmein.Computer) *lmifeedr.Computer {
	v := &lmifeedr.Computer{
		HostID:    c.HostID(),
		OldHostID: c.OldHostID(),
		Name:      c.Name(),
		OldName:   c.OldName(),
		Status:    int32(c.Status()),
		OldStatus: int32(c.OldStatus()),
		ChangeSet: uint32(c.GetChangeSet()),
	}
	if c.IPAddress() != nil {
		v.Ip = c.IPAddress().String()
	}
	if c.OldIPAddress() != nil {
		v.OldIp = c.OldIPAddress().String()
	}
	return v
}

func (svr *lmiServer) registerWatchChan(req *lmifeedr.WatchRequest, firehose chan<- *logmein.Computer) error {
	// update this in sync with `unregisterWatchChan`

	svr.Lock()
	defer svr.Unlock()

	for _, id := range req.HostID {
		if _, ok := svr.hostIDSubscribers[id]; !ok {
			svr.hostIDSubscribers[id] = []chan<- *logmein.Computer{firehose}
		} else {
			svr.hostIDSubscribers[id] = append(svr.hostIDSubscribers[id], firehose)
		}
	}

	for _, nameMatch := range req.NameMatch {
		var re *regexp.Regexp
		if reCache, ok := svr.regexpSet[nameMatch]; ok {
			re = reCache
		} else {
			var err error
			re, err = regexp.Compile(nameMatch)
			if err != nil {
				return err
			}
			svr.regexpSet[nameMatch] = re
		}
		if _, ok := svr.nameMatchSubscribers[re]; !ok {
			svr.nameMatchSubscribers[re] = []chan<- *logmein.Computer{firehose}
		} else {
			svr.nameMatchSubscribers[re] = append(svr.nameMatchSubscribers[re], firehose)
		}
	}

	for _, ip := range req.IpMatch {
		ip = ensureIPIsCIDR(ip)
		_, _, err := net.ParseCIDR(ip)
		if err != nil {
			return err
		}
		if _, ok := svr.ipMatchSubscribers[ip]; !ok {
			svr.ipMatchSubscribers[ip] = []chan<- *logmein.Computer{firehose}
		} else {
			svr.ipMatchSubscribers[ip] = append(svr.ipMatchSubscribers[ip], firehose)
		}
	}

	return nil
}

func (svr *lmiServer) unregisterWatchChan(req *lmifeedr.WatchRequest, firehose chan<- *logmein.Computer) error {
	// update this in sync with `registerWatchChan`

	svr.Lock()
	defer svr.Unlock()

	for _, id := range req.HostID {
		if subs, ok := svr.hostIDSubscribers[id]; ok {
		UnsubHostIDLoop:
			for {
				for i, ch := range subs {
					if ch == firehose {
						subs, subs[len(subs)-1] = append(subs[:i], subs[i+1:]...), nil
						continue UnsubHostIDLoop
					}
				}
				break
			}
		}
	}

	for _, nameMatch := range req.NameMatch {
		if re, ok := svr.regexpSet[nameMatch]; ok {
			if subs, ok := svr.nameMatchSubscribers[re]; ok {
			UnsubNameMatchLoop:
				for {
					for i, ch := range subs {
						if ch == firehose {
							subs, subs[len(subs)-1] = append(subs[:i], subs[i+1:]...), nil
							continue UnsubNameMatchLoop
						}
					}
					break
				}
			}
		}
	}

	for _, ip := range req.IpMatch {
		ip = ensureIPIsCIDR(ip)
		if subs, ok := svr.ipMatchSubscribers[ip]; ok {
		UnsubIPMatchLoop:
			for {
				for i, ch := range subs {
					if ch == firehose {
						subs, subs[len(subs)-1] = append(subs[:i], subs[i+1:]...), nil
						continue UnsubIPMatchLoop
					}
				}
				break
			}
		}
	}

	close(firehose)

	return nil
}

// GetComputer returns the first match.  It's best to be used for looking up computers by ID or name where you expect exactly one result.
func (svr *lmiServer) GetComputer(c context.Context, req *lmifeedr.ComputerRequest) (*lmifeedr.Computer, error) {
	svr.RLock()
	defer svr.RUnlock()

	computers := svr.logmein.Computers()
	if req.HostID > 0 {
		if c, ok := computers[req.HostID]; ok {
			return convertComputerToGRPC(c), nil
		}
	}
	if len(req.NameMatch) > 0 {
		re, err := regexp.Compile(req.NameMatch)
		if err != nil {
			return nil, err
		}
		for _, c := range computers {
			if re.MatchString(c.Name()) {
				return convertComputerToGRPC(c), nil
			}
		}
	}
	if len(req.IpMatch) > 0 {
		_, ipNet, err := net.ParseCIDR(ensureIPIsCIDR(req.IpMatch))
		if err != nil {
			return nil, err
		}
		for _, c := range computers {
			if ipNet.Contains(c.IPAddress()) {
				return convertComputerToGRPC(c), nil
			}
		}
	}
	return nil, ErrNoMatch
}

// GetComputers returns a stream of all matching computers.
func (svr *lmiServer) GetComputers(req *lmifeedr.ComputerRequest, stream lmifeedr.LMIFeedr_GetComputersServer) error {
	svr.RLock()
	defer svr.RUnlock()

	returned := make(map[uint64]bool) // which computers have we already sent over the wire this time around?

	computers := svr.logmein.Computers()
	if req.HostID > 0 {
		if c, ok := computers[req.HostID]; ok {
			if !returned[c.HostID()] {
				if err := stream.Send(convertComputerToGRPC(c)); err != nil {
					return err
				}
				returned[c.HostID()] = true
			}
		}
	}
	if len(req.NameMatch) > 0 {
		re, err := regexp.Compile(req.NameMatch)
		if err != nil {
			return err
		}
		for _, c := range computers {
			if re.MatchString(c.Name()) {
				if !returned[c.HostID()] {
					if err := stream.Send(convertComputerToGRPC(c)); err != nil {
						return err
					}
					returned[c.HostID()] = true
				}
			}
		}
	}
	if len(req.IpMatch) > 0 {
		_, ipNet, err := net.ParseCIDR(ensureIPIsCIDR(req.IpMatch))
		if err != nil {
			return err
		}
		for _, c := range computers {
			if ipNet.Contains(c.IPAddress()) {
				if !returned[c.HostID()] {
					if err := stream.Send(convertComputerToGRPC(c)); err != nil {
						return err
					}
					returned[c.HostID()] = true
				}
			}
		}
	}
	if len(returned) > 0 {
		return nil
	}
	return ErrNoMatch
}

// Watch returns a neverending stream of state changes, until the client disconnects.
func (svr *lmiServer) Watch(req *lmifeedr.WatchRequest, stream lmifeedr.LMIFeedr_WatchServer) error {
	firehose := make(chan *logmein.Computer)
	err := svr.registerWatchChan(req, firehose)
	if err != nil {
		return err
	}
	defer svr.unregisterWatchChan(req, firehose)
	for {
		water := <-firehose
		err = stream.Send(convertComputerToGRPC(water))
		if err != nil {
			return err
		}
	}
}

func ensureIPIsCIDR(ip string) string {
	if !strings.Contains(ip, "/") {
		ip = ip + "/32"
	}
	return ip
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/md5"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/blackjack/syslog"
	"github.com/boltdb/bolt"
	"github.com/satori/go.uuid"
	"golang.org/x/crypto/ssh"
)

var (
	AUTHKEYSFILE string = os.Getenv("HOME") + "/.ssh/authorized_keys"
	CLIENT       string = strings.Split(os.Getenv("SSH_CLIENT"), " ")[0]
)

func Debug(format string, a ...interface{}) {
	syslog.Syslogf(syslog.LOG_NOTICE, format, a...)
}

func Log(format string, a ...interface{}) {
	syslog.Syslogf(syslog.LOG_NOTICE, format, a...)
}

func Warn(format string, a ...interface{}) {
	syslog.Syslogf(syslog.LOG_WARNING, format, a...)
}

func syntaxError() {
	fmt.Println("ERROR")
	os.Exit(1)
}

func dbFileLine(filename string) {
	file, err := os.Stat(filename)
	if err != nil {
		Warn("Error Stat %v: %v", filename, err)
		return
	}

	trimmed := strings.TrimPrefix(filename, "./")

	hash, _ := fingerprint(filename)

	mode := 0600
	if file.Mode()&0111 != 0 {
		mode = 0700
	}

	fmt.Printf("%s\t%o\t*\t%d\t%x\n", trimmed, mode, file.Size(), hash)
}

func dbDirLine(directory string) {
	trimmed := strings.TrimPrefix(directory, "./")
	fmt.Printf("%s/\t%d\t*\n", trimmed, 700)
}

func listDir(dirname string) {
	files, err := ioutil.ReadDir(dirname)
	if err != nil {
		Warn("Error reading directory %v", dirname)
		return
	}

	for _, file := range files {
		if file.Name() == ".git" {
			continue
		}

		fullname := dirname + "/" + file.Name()

		switch mode := file.Mode(); {
		case mode.IsDir():
			dbDirLine(fullname)
			listDir(fullname)
		case mode.IsRegular():
			dbFileLine(fullname)
		}
	}
}

func netskelDB(cuuid string) {
	servername, _ := os.Hostname()
	now := time.Now().Format("Mon, 2 Jan 2006 15:04:05 UTC")

	fmt.Printf("#\n# .netskeldb for %s at %v\n#\n# Generated %v by %v\n#\n", cuuid, CLIENT, now, servername)

	// Force-inject the client itself
	dbDirLine("bin")
	dbFileLine("bin/netskel")

	os.Chdir("db")
	listDir(".")

	os.Exit(0)
}

func fingerprint(filename string) ([]byte, error) {
	var result []byte

	file, err := os.Open(filename)
	if err != nil {
		return result, err
	}
	defer file.Close()

	hash := md5.New()
	if _, err := io.Copy(hash, file); err != nil {
		return result, err
	}

	return hash.Sum(result), nil
}

func hexdump(filename string) {
	linelength := 30
	count := 0

	file, err := ioutil.ReadFile(filename)
	if err != nil {
		Warn("Error trying to hexdump %v: %v", filename, err)
		os.Exit(1)
	}

	for _, c := range file {
		fmt.Printf("%02x", c)
		count += 1
		if count >= linelength {
			count = 0
			fmt.Printf("\n")
		}
	}
	fmt.Printf("\n")
}

func addKey(hostname string) {
	servername, err := os.Hostname()
	now := time.Now()
	nowFmt := now.Format("Mon Jan _2 15:04:05 2006")
	uuid := uuid.NewV4()
	cuuid := uuid.String()

	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		Warn("Error generating private key: %v", err)
		os.Exit(1)
	}
	privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}
	pemdata := pem.EncodeToMemory(privateKeyPEM)

	pub, err := ssh.NewPublicKey(&privateKey.PublicKey)
	if err != nil {
		Warn("Error constructing public key: %v", err)
		os.Exit(1)
	}
	pubdata := strings.TrimSpace(string(ssh.MarshalAuthorizedKey(pub)))

	Debug("Appending %d byte public key to %s for %s (%v) uuid %s", len(pubdata), AUTHKEYSFILE, hostname, CLIENT, uuid)

	f, err := os.OpenFile(AUTHKEYSFILE, os.O_APPEND|os.O_WRONLY, 0600)
	if err != nil {
		Warn("Error writing public key: %v", err)
		os.Exit(1)
	}

	defer f.Close()

	if _, err = f.WriteString("restrict " + pubdata + " " + hostname + " " + cuuid + " " + nowFmt + "\n"); err != nil {
		panic(err)
	}

	fmt.Printf("#\n# Netskel private key generated by %v for %v (%v)\n#\n", servername, hostname, CLIENT)
	fmt.Printf("# CLIENT_UUID %s\n#\n", uuid)
	fmt.Println(string(pemdata))
	//fmt.Println("-- ")
	//fmt.Println(string(pubdata))

	secs := strconv.Itoa(int(now.Unix()))

	clientPut(cuuid, "hostname", hostname)
	clientPut(cuuid, "originalHostname", hostname)
	clientPut(cuuid, "created", secs)

	os.Exit(0)
}

func clientHeartbeat(uuid, inet string) {
	now := time.Now()
	secs := strconv.Itoa(int(now.Unix()))

	clientPut(uuid, "inet", inet)
	clientPut(uuid, "lastSeen", secs)
	Log("Stored heartbeat for %v", uuid)
}

func clientPut(uuid, key, value string) {
	db, err := bolt.Open("clients.db", 0660, &bolt.Options{Timeout: 2 * time.Second})
	if err != nil {
		Warn("Unable to open client database: %v", err)
		return
	}
	defer db.Close()

	berr := db.Update(func(tx *bolt.Tx) error {
		b, err := tx.CreateBucketIfNotExists([]byte(uuid))
		if err != nil {
			return fmt.Errorf("Can't create bucket for %s: %v", uuid, err)
		}

		perr := b.Put([]byte(key), []byte(value))
		return perr
	})

	if berr != nil {
		Warn("clientHeartbeat error %v", berr)
	}

}

func main() {
	syslog.Openlog("netskel-server", syslog.LOG_PID, syslog.LOG_USER)

	if os.Args[0] != "server" {
		syntaxError()
	}

	nsCommand := strings.Split(os.Args[2], " ")
	command := nsCommand[0]

	Log("netskel-server launched for %v with %v", CLIENT, nsCommand)

	//for index, arg := range nsCommand {
	//	fmt.Printf("%2d: %v\n", index, arg)
	//}

	switch command {
	case "netskeldb":
		cuuid := "nouuid"

		if len(nsCommand) > 1 {
			c, err := uuid.FromString(nsCommand[1])
			if err != nil {
				Warn("Unable to parse client-supplied UUID %v: %v", nsCommand[1], err)
			} else {
				cuuid = c.String()
			}
		}

		clientHeartbeat(cuuid, CLIENT)
		netskelDB(cuuid)

	case "sha1":
		filename := nsCommand[1]
		hash, _ := fingerprint(filename)
		fmt.Println(hash)

	case "sendfile":
		filename := nsCommand[1]

		if filename == "db/bin/netskel" {
			filename = "bin/netskel"
		}

		hexdump(filename)

	case "addkey":
		key := nsCommand[1]
		addKey(key)

	default:
		syntaxError()
	}

	os.Exit(0)
}
<commit_msg>Refine the logging for clarity and brevity<commit_after>package main

import (
	"crypto/md5"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/blackjack/syslog"
	"github.com/boltdb/bolt"
	"github.com/satori/go.uuid"
	"golang.org/x/crypto/ssh"
)

var (
	AUTHKEYSFILE string = os.Getenv("HOME") + "/.ssh/authorized_keys"
	CLIENT       string = strings.Split(os.Getenv("SSH_CLIENT"), " ")[0]
)

func Debug(format string, a ...interface{}) {
	syslog.Syslogf(syslog.LOG_DEBUG, format, a...)
}

func Log(format string, a ...interface{}) {
	syslog.Syslogf(syslog.LOG_NOTICE, format, a...)
}

func Warn(format string, a ...interface{}) {
	syslog.Syslogf(syslog.LOG_WARNING, format, a...)
}

func syntaxError() {
	fmt.Println("ERROR")
	os.Exit(1)
}

func dbFileLine(filename string) {
	file, err := os.Stat(filename)
	if err != nil {
		Warn("Error Stat %v: %v", filename, err)
		return
	}

	trimmed := strings.TrimPrefix(filename, "./")

	hash, _ := fingerprint(filename)

	mode := 0600
	if file.Mode()&0111 != 0 {
		mode = 0700
	}

	fmt.Printf("%s\t%o\t*\t%d\t%x\n", trimmed, mode, file.Size(), hash)
}

func dbDirLine(directory string) {
	trimmed := strings.TrimPrefix(directory, "./")
	fmt.Printf("%s/\t%d\t*\n", trimmed, 700)
}

func listDir(dirname string) {
	files, err := ioutil.ReadDir(dirname)
	if err != nil {
		Warn("Error reading directory %v", dirname)
		return
	}

	for _, file := range files {
		if file.Name() == ".git" {
			continue
		}

		fullname := dirname + "/" + file.Name()

		switch mode := file.Mode(); {
		case mode.IsDir():
			dbDirLine(fullname)
			listDir(fullname)
		case mode.IsRegular():
			dbFileLine(fullname)
		}
	}
}

func netskelDB(cuuid string) {
	servername, _ := os.Hostname()
	now := time.Now().Format("Mon, 2 Jan 2006 15:04:05 UTC")

	fmt.Printf("#\n# .netskeldb for %s at %v\n#\n# Generated %v by %v\n#\n", cuuid, CLIENT, now, servername)

	// Force-inject the client itself
	dbDirLine("bin")
	dbFileLine("bin/netskel")

	os.Chdir("db")
	listDir(".")

	Log("Sent netskeldb to %s (%s)", CLIENT, cuuid)
}

func fingerprint(filename string) ([]byte, error) {
	var result []byte

	file, err := os.Open(filename)
	if err != nil {
		return result, err
	}
	defer file.Close()

	hash := md5.New()
	if _, err := io.Copy(hash, file); err != nil {
		return result, err
	}

	return hash.Sum(result), nil
}

func hexdump(filename string) {
	linelength := 30
	count := 0

	file, err := ioutil.ReadFile(filename)
	if err != nil {
		Warn("Error trying to hexdump %v: %v", filename, err)
		os.Exit(1)
	}

	for _, c := range file {
		fmt.Printf("%02x", c)
		count += 1
		if count >= linelength {
			count = 0
			fmt.Printf("\n")
		}
	}
	fmt.Printf("\n")
	Log("Sent %s (%d bytes) to %s", filename, len(file), CLIENT)
}

func addKey(hostname string) {
	servername, err := os.Hostname()
	now := time.Now()
	nowFmt := now.Format("Mon Jan _2 15:04:05 2006")
	uuid := uuid.NewV4()
	cuuid := uuid.String()

	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		Warn("Error generating private key: %v", err)
		os.Exit(1)
	}
	privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}
	pemdata := pem.EncodeToMemory(privateKeyPEM)

	pub, err := ssh.NewPublicKey(&privateKey.PublicKey)
	if err != nil {
		Warn("Error constructing public key: %v", err)
		os.Exit(1)
	}
	pubdata := strings.TrimSpace(string(ssh.MarshalAuthorizedKey(pub)))

	f, err := os.OpenFile(AUTHKEYSFILE, os.O_APPEND|os.O_WRONLY, 0600)
	if err != nil {
		Warn("Error opening %s for writing: %v", AUTHKEYSFILE, err)
		os.Exit(1)
	}

	defer f.Close()

	if _, err = f.WriteString("restrict " + pubdata + " " + hostname + " " + cuuid + " " + nowFmt + "\n"); err != nil {
		panic(err)
	}

	fmt.Printf("#\n# Netskel private key generated by %v for %v (%v)\n#\n", servername, hostname, CLIENT)
	fmt.Printf("# CLIENT_UUID %s\n#\n", uuid)
	fmt.Println(string(pemdata))

	secs := strconv.Itoa(int(now.Unix()))

	clientPut(cuuid, "hostname", hostname)
	clientPut(cuuid, "originalHostname", hostname)
	clientPut(cuuid, "created", secs)

	Log("Added %d byte public key to %s for %s (%v) uuid %s", len(pubdata), AUTHKEYSFILE, hostname, CLIENT, uuid)
}

func clientHeartbeat(uuid, inet string) {
	now := time.Now()
	secs := strconv.Itoa(int(now.Unix()))

	clientPut(uuid, "inet", inet)
	clientPut(uuid, "lastSeen", secs)
	Debug("Stored heartbeat for %v", uuid)
}

func clientPut(uuid, key, value string) {
	db, err := bolt.Open("clients.db", 0660, &bolt.Options{Timeout: 2 * time.Second})
	if err != nil {
		Warn("Unable to open client database: %v", err)
		return
	}
	defer db.Close()

	berr := db.Update(func(tx *bolt.Tx) error {
		b, err := tx.CreateBucketIfNotExists([]byte(uuid))
		if err != nil {
			return fmt.Errorf("Can't create bucket for %s: %v", uuid, err)
		}

		perr := b.Put([]byte(key), []byte(value))
		return perr
	})

	if berr != nil {
		Warn("clientHeartbeat error %v", berr)
	}
}

func main() {
	syslog.Openlog("netskel-server", syslog.LOG_PID, syslog.LOG_USER)

	if os.Args[0] != "server" {
		syntaxError()
	}

	nsCommand := strings.Split(os.Args[2], " ")
	command := nsCommand[0]

	Debug("Launched from %v with %v", CLIENT, nsCommand)

	switch command {
	case "netskeldb":
		cuuid := "nouuid"

		if len(nsCommand) > 1 {
			c, err := uuid.FromString(nsCommand[1])
			if err != nil {
				Warn("Unable to parse client-supplied UUID %v: %v", nsCommand[1], err)
			} else {
				cuuid = c.String()
			}
		}

		clientHeartbeat(cuuid, CLIENT)
		netskelDB(cuuid)

	case "sha1":
		filename := nsCommand[1]
		hash, _ := fingerprint(filename)
		fmt.Println(hash)

	case "sendfile":
		filename := nsCommand[1]

		if filename == "db/bin/netskel" {
			filename = "bin/netskel"
		}

		hexdump(filename)

	case "addkey":
		key := nsCommand[1]
		addKey(key)

	default:
		syntaxError()
	}

	os.Exit(0)
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	"log"
	"net"
	"net/rpc"

	"github.com/pocke/go-iprange"
)

var connCh = make(chan net.Conn, 1)

func Serve(port int, allowIP string) error {
	ra, err := iprange.New(allowIP)
	if err != nil {
		return err
	}

	uri := &URI{}
	rpc.Register(uri)
	clipboard := &Clipboard{}
	rpc.Register(clipboard)

	addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		return err
	}
	l, err := net.ListenTCP("tcp", addr)
	if err != nil {
		return err
	}

	for {
		conn, err := l.Accept()
		if err != nil {
			log.Println(err)
		}
		log.Printf("Request from %s", conn.RemoteAddr())
		if !ra.InlucdeConn(conn) {
			continue
		}
		connCh <- conn
		rpc.ServeConn(conn)
	}
	return nil
}
<commit_msg>Remove unreachable code.<commit_after>package server

import (
	"fmt"
	"log"
	"net"
	"net/rpc"

	"github.com/pocke/go-iprange"
)

var connCh = make(chan net.Conn, 1)

func Serve(port int, allowIP string) error {
	ra, err := iprange.New(allowIP)
	if err != nil {
		return err
	}

	uri := &URI{}
	rpc.Register(uri)
	clipboard := &Clipboard{}
	rpc.Register(clipboard)

	addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		return err
	}
	l, err := net.ListenTCP("tcp", addr)
	if err != nil {
		return err
	}

	for {
		conn, err := l.Accept()
		if err != nil {
			log.Println(err)
		}
		log.Printf("Request from %s", conn.RemoteAddr())
		if !ra.InlucdeConn(conn) {
			continue
		}
		connCh <- conn
		rpc.ServeConn(conn)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"
	"sync"

	"github.com/dgrijalva/jwt-go"
)

// Todo is a representation of each item that
// we are going to maintain in our service
type Todo struct {
	ID        uint64
	Task      string
	Completed bool
}

// In-memory collection of Todos
var todos []*Todo

// If we store the Todos in a database, this will be the LastInsertID
var todoCounter uint64

// The single lock is used to synchronize access to
// the above array as well as the counter variables.
var lock sync.RWMutex

var hmacSampleSecret = []byte("Top secret signing key")

// A middleware for validating JWT tokens
func authCheck(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		tokenString := r.Header.Get("Authorization")
		if tokenString == "" {
			http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
			return
		}

		tokenString = strings.TrimPrefix(tokenString, "Bearer ")
		token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
			// Don't forget to validate the alg is what you expect:
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
			}
			return hmacSampleSecret, nil
		})

		if err != nil {
			http.Error(w, err.Error(), http.StatusUnauthorized)
			return
		}

		if claims, ok := token.Claims.(jwt.StandardClaims); ok && token.Valid {
			log.Println(claims)
		} else {
			http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
			return
		}
		next.ServeHTTP(w, r)
	})
}

func main() {
	http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
		// Authenticate and return JWT
	})

	http.Handle("/todos", authCheck(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == http.MethodGet {
			// Return all the Todo items
			lock.RLock()
			defer lock.RUnlock()

			j, err := json.Marshal(todos)
			if err != nil {
				log.Println(err)
				http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
				return
			}
			w.Header().Set("Content-Type", "application/json")
			w.Write(j)

		} else if r.Method == http.MethodPost {
			// Create a new Todo item
			decoder := json.NewDecoder(r.Body)
			var t Todo
			err := decoder.Decode(&t)
			if err != nil {
				log.Println(err)
				http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
				return
			}
			defer r.Body.Close()

			if len(t.Task) < 1 {
				http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
				return
			}

			t.Completed = false

			lock.Lock()
			t.ID = todoCounter
			todoCounter++
			todos = append(todos, &t)
			lock.Unlock()
			w.WriteHeader(http.StatusCreated)
		} else {
			http.NotFound(w, r)
		}
	})))

	http.Handle("/todo/", authCheck(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		s := strings.TrimPrefix(r.URL.String(), "/todo/")
		id, err := strconv.ParseUint(s, 10, 64)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		if r.Method == http.MethodPut {

			// Parse task from the request body
			// Copied from the /todos POST handler
			decoder := json.NewDecoder(r.Body)
			var t Todo
			err := decoder.Decode(&t)
			if err != nil {
				log.Println(err)
				http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
				return
			}
			defer r.Body.Close()

			if len(t.Task) < 1 {
				http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
				return
			}

			// Update the particular Todo item
			lock.Lock()
			defer lock.Unlock()

			for _, i := range todos {
				if i.ID == id {
					i.Task = t.Task
					i.Completed = t.Completed
					w.WriteHeader(http.StatusOK)
					return
				}
			}

			http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
			return

		} else if r.Method == http.MethodDelete {
			// Delete the particular Todo item
			// Update the particular Todo item
			lock.Lock()
			defer lock.Unlock()

			for k, v := range todos {
				if v.ID == id {
					todos = append(todos[:k], todos[k+1:]...)
					w.WriteHeader(http.StatusOK)
					return
				}
			}

			http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
			return
		} else {
			http.NotFound(w, r)
		}
	})))

	log.Fatal(http.ListenAndServe(":8080", nil))
}
<commit_msg>JWT generation and checking<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/dgrijalva/jwt-go"
)

// Todo is a representation of each item that
// we are going to maintain in our service
type Todo struct {
	ID        uint64
	Task      string
	Completed bool
}

// In-memory collection of Todos
var todos []*Todo

// If we store the Todos in a database, this will be the LastInsertID
var todoCounter uint64

// The single lock is used to synchronize access to
// the above array as well as the counter variables.
var lock sync.RWMutex

var hmacSampleSecret = []byte("Top secret signing key")

// A middleware for validating JWT tokens
func authCheck(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		tokenString := r.Header.Get("Authorization")
		if tokenString == "" {
			http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
			return
		}

		tokenString = strings.TrimPrefix(tokenString, "Bearer ")
		token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
			// Don't forget to validate the alg is what you expect:
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
			}
			return hmacSampleSecret, nil
		})

		if err != nil {
			http.Error(w, err.Error(), http.StatusUnauthorized)
			return
		}

		if !token.Valid {
			http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
			return
		}
		next.ServeHTTP(w, r)
	})
}

func main() {
	http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
		// Authenticate and return JWT
		if u, p, ok := r.BasicAuth(); ok {
			if u == p {
				token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
					ExpiresAt: time.Now().Add(time.Minute * 3).Unix(),
					Issuer:    "example.com",
				})

				// Sign and get the complete encoded token as a string using the secret
				tokenString, err := token.SignedString(hmacSampleSecret)
				if err != nil {
					log.Println(err)
					http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
					return
				}

				fmt.Fprintln(w, tokenString)
				return
			}
		}
		http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
		return
	})

	http.Handle("/todos", authCheck(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == http.MethodGet {
			// Return all the Todo items
			lock.RLock()
			defer lock.RUnlock()

			j, err := json.Marshal(todos)
			if err != nil {
				log.Println(err)
				http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
				return
			}
			w.Header().Set("Content-Type", "application/json")
			w.Write(j)

		} else if r.Method == http.MethodPost {
			// Create a new Todo item
			decoder := json.NewDecoder(r.Body)
			var t Todo
			err := decoder.Decode(&t)
			if err != nil {
				log.Println(err)
				http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
				return
			}
			defer r.Body.Close()

			if len(t.Task) < 1 {
				http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
				return
			}

			t.Completed = false

			lock.Lock()
			t.ID = todoCounter
			todoCounter++
			todos = append(todos, &t)
			lock.Unlock()
			w.WriteHeader(http.StatusCreated)
		} else {
			http.NotFound(w, r)
		}
	})))

	http.Handle("/todo/", authCheck(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		s := strings.TrimPrefix(r.URL.String(), "/todo/")
		id, err := strconv.ParseUint(s, 10, 64)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		if r.Method == http.MethodPut {

			// Parse task from the request body
			// Copied from the /todos POST handler
			decoder := json.NewDecoder(r.Body)
			var t Todo
			err := decoder.Decode(&t)
			if err != nil {
				log.Println(err)
				http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
				return
			}
			defer r.Body.Close()

			if len(t.Task) < 1 {
				http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
				return
			}

			// Update the particular Todo item
			lock.Lock()
			defer lock.Unlock()

			for _, i := range todos {
				if i.ID == id {
					i.Task = t.Task
					i.Completed = t.Completed
					w.WriteHeader(http.StatusOK)
					return
				}
			}

			http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
			return

		} else if r.Method == http.MethodDelete {
			// Delete the particular Todo item
			// Update the particular Todo item
			lock.Lock()
			defer lock.Unlock()

			for k, v := range todos {
				if v.ID == id {
					todos = append(todos[:k], todos[k+1:]...)
					w.WriteHeader(http.StatusOK)
					return
				}
			}

			http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
			return
		} else {
			http.NotFound(w, r)
		}
	})))

	log.Fatal(http.ListenAndServe(":8080", nil))
}
<|endoftext|>