<commit_before>package topicfeed

import (
	"encoding/json"
	"errors"
	"fmt"
	"socialapi/models"
	"github.com/koding/logging"

	verbalexpressions "github.com/VerbalExpressions/GoVerbalExpressions"
	"github.com/jinzhu/gorm"
)

// s := "naber #foo hede #bar dede gel # baz #123 #-`3sdf"
// will find [foo, bar, 123]
// will not find [' baz', '-`3sdf']
// here is the regex -> (?m)(?:#)(\w+)(?: )
var topicRegex = verbalexpressions.New().
	Find("#").
	BeginCapture().
	Word().
	EndCapture().
	Find(" ").
	Regex()

type Action func(*TopicFeedController, *models.ChannelMessage) error

type TopicFeedController struct {
	routes map[string]Action
	log    logging.Logger
}

var HandlerNotFoundErr = errors.New("Handler Not Found")

func NewTopicFeedController(log logging.Logger) *TopicFeedController {
	ffc := &TopicFeedController{
		log: log,
	}

	routes := map[string]Action{
		"channel_message_created": (*TopicFeedController).MessageSaved,
		"channel_message_update":  (*TopicFeedController).MessageUpdated,
		"channel_message_deleted": (*TopicFeedController).MessageDeleted,
	}

	ffc.routes = routes

	return ffc
}

func (f *TopicFeedController) HandleEvent(event string, data []byte) error {
	f.log.Debug("New Event Recieved %s", event)
	handler, ok := f.routes[event]
	if !ok {
		return HandlerNotFoundErr
	}

	cm, err := mapMessage(data)
	if err != nil {
		return err
	}

	res, err := isEligible(cm)
	if err != nil {
		return err
	}

	if !res {
		return nil
	}

	return handler(f, cm)
}

func (f *TopicFeedController) MessageSaved(data *models.ChannelMessage) error {

	topics := extractTopics(data.Body)
	if len(topics) == 0 {
		return nil
	}

	c, err := fetchChannel(data.InitialChannelId)
	if err != nil {
		f.log.Error("Error on fetchChannel %d, %s", data.InitialChannelId, err)
		return err
	}

	return ensureChannelMessages(c, data, topics)
}

func ensureChannelMessages(parentChannel *models.Channel, data *models.ChannelMessage, topics []string) error {
	for _, topic := range topics {
		tc, err := fetchTopicChannel(parentChannel.Group, topic)
		if err != nil && err != gorm.RecordNotFound {
			return err
		}

		if err == gorm.RecordNotFound {
			tc, err = createTopicChannel(data.AccountId, parentChannel.Group, topic, parentChannel.Privacy)
			if err != nil {
				return err
			}
		}

		_, err = tc.AddMessage(data.Id)
		if err != nil {
			return err
		}
	}

	return nil
}

func extractTopics(body string) []string {
	flattened := make([]string, 0)

	res := topicRegex.FindAllStringSubmatch(body, -1)
	if len(res) == 0 {
		return flattened
	}

	topics := map[string]struct{}{}
	// remove duplicate tag usages
	for _, ele := range res {
		topics[ele[1]] = struct{}{}
	}

	for topic := range topics {
		flattened = append(flattened, topic)
	}

	return flattened
}

func (f *TopicFeedController) MessageUpdated(data *models.ChannelMessage) error {
	f.log.Debug("udpate message %s", data.Id)
	// fetch message's current topics from the db
	channels, err := fetchMessageChannels(data.Id)
	if err != nil {
		return err
	}

	// get current topics from
	topics := extractTopics(data.Body)
	if topics == nil {
		return nil
	}

	// if message and the topics dont have any item, we can safely return
	if len(channels) == 0 && len(topics) == 0 {
		return nil
	}

	res := getTopicDiff(channels, topics)

	// add messages
	if len(res["added"]) > 0 {
		initialChannel, err := fetchChannel(data.InitialChannelId)
		if err != nil {
			return err
		}

		if err := ensureChannelMessages(initialChannel, data, res["added"]); err != nil {
			return err
		}
	}

	// delete messages
	if len(res["deleted"]) > 0 {
		if err := deleteChannelMessages(channels, data, res["deleted"]); err != nil {
			return err
		}
	}

	return nil
}

func deleteChannelMessages(channels []models.Channel, data *models.ChannelMessage, toBeDeletedTopics []string) error {
	for _, channel := range channels {
		for _, topic := range toBeDeletedTopics {
			if channel.Name != topic {
				continue
			}

			cml := models.NewChannelMessageList()
			selector := map[string]interface{}{
				"message_id": data.Id,
				"channel_id": channel.Id,
			}

			if err := cml.DeleteMessagesBySelector(selector); err != nil {
				return err
			}
		}
	}
	return nil
}

func fetchMessageChannels(messageId int64) ([]models.Channel, error) {
	cml := models.NewChannelMessageList()
	return cml.FetchMessageChannels(messageId)
}

func getTopicDiff(channels []models.Channel, topics []string) map[string][]string {
	res := make(map[string][]string)

	// aggregate all channel names into map
	channelNames := map[string]struct{}{}
	for _, channel := range channels {
		channelNames[channel.Name] = struct{}{}
	}

	// range over new topics, bacause we are gonna remove
	// unused channels
	for _, topic := range topics {
		found := false
		for channelName := range channelNames {
			if channelName == topic {
				found = true
			}
		}
		if !found {
			res["added"] = append(res["added"], topic)
		} else {
			// if we have topic in channels
			// do remove it because at the end we are gonna mark
			// channels as deleted which are still in channelNames
			delete(channelNames, topic)
		}
	}
	// flatten the deleted channel names
	for channelName := range channelNames {
		res["deleted"] = append(res["deleted"], channelName)
	}

	return res
}

func (f *TopicFeedController) MessageDeleted(data *models.ChannelMessage) error {
	cml := models.NewChannelMessageList()
	selector := map[string]interface{}{
		"message_id": data.Id,
	}

	if err := cml.DeleteMessagesBySelector(selector); err != nil {
		return err
	}
	return nil
}

func mapMessage(data []byte) (*models.ChannelMessage, error) {
	cm := models.NewChannelMessage()
	if err := json.Unmarshal(data, cm); err != nil {
		return nil, err
	}

	return cm, nil
}

func isEligible(cm *models.ChannelMessage) (bool, error) {
	if cm.InitialChannelId == 0 {
		return false, nil
	}

	if cm.Type != models.ChannelMessage_TYPE_POST {
		return false, nil
	}

	return true, nil
}

// todo add caching here
func fetchChannel(channelId int64) (*models.Channel, error) {
	c := models.NewChannel()
	c.Id = channelId
	// todo - fetch only name here
	if err := c.Fetch(); err != nil {
		return nil, err
	}

	return c, nil
}

// todo add caching here
func fetchTopicChannel(groupName, channelName string) (*models.Channel, error) {
	c := models.NewChannel()

	selector := map[string]interface{}{
		"group": groupName,
		"name":  channelName,
		"type":  models.Channel_TYPE_TOPIC,
	}

	err := c.One(selector)
	if err != nil {
		return nil, err
	}

	return c, nil
}

func createTopicChannel(creatorId int64, groupName, channelName, privacy string) (*models.Channel, error) {
	c := models.NewChannel()
	c.Name = channelName
	c.CreatorId = creatorId
	c.Group = groupName
	c.Purpose = fmt.Sprintf("Channel for %s topic", channelName)
	c.Type = models.Channel_TYPE_TOPIC
	c.Privacy = privacy
	if err := c.Create(); err != nil {
		return nil, err
	}

	return c, nil
}
<commit_msg>social: fix regex for tags<commit_after>package topicfeed

import (
	"encoding/json"
	"errors"
	"fmt"
	"socialapi/models"
	"github.com/koding/logging"

	verbalexpressions "github.com/VerbalExpressions/GoVerbalExpressions"
	"github.com/jinzhu/gorm"
)

// s := "naber #foo hede #bar dede gel # baz #123 #-`3sdf"
// will find [foo, bar, 123]
// will not find [' baz', '-`3sdf']
// here is the regex -> (?m)(?:#)(\w+)
var topicRegex = verbalexpressions.New().
	Find("#").
	BeginCapture().
	Word().
	EndCapture().
	Regex()

// extend this regex with https://github.com/twitter/twitter-text-rb/blob/eacf388136891eb316f1c110da8898efb8b54a38/lib/twitter-text/regex.rb
// to support all languages

type Action func(*TopicFeedController, *models.ChannelMessage) error

type TopicFeedController struct {
	routes map[string]Action
	log    logging.Logger
}

var HandlerNotFoundErr = errors.New("Handler Not Found")

func NewTopicFeedController(log logging.Logger) *TopicFeedController {
	ffc := &TopicFeedController{
		log: log,
	}

	routes := map[string]Action{
		"channel_message_created": (*TopicFeedController).MessageSaved,
		"channel_message_update":  (*TopicFeedController).MessageUpdated,
		"channel_message_deleted": (*TopicFeedController).MessageDeleted,
	}

	ffc.routes = routes

	return ffc
}

func (f *TopicFeedController) HandleEvent(event string, data []byte) error {
	f.log.Debug("New Event Recieved %s", event)
	handler, ok := f.routes[event]
	if !ok {
		return HandlerNotFoundErr
	}

	cm, err := mapMessage(data)
	if err != nil {
		return err
	}

	res, err := isEligible(cm)
	if err != nil {
		return err
	}

	if !res {
		return nil
	}

	return handler(f, cm)
}

func (f *TopicFeedController) MessageSaved(data *models.ChannelMessage) error {

	topics := extractTopics(data.Body)
	if len(topics) == 0 {
		return nil
	}

	c, err := fetchChannel(data.InitialChannelId)
	if err != nil {
		f.log.Error("Error on fetchChannel %d, %s", data.InitialChannelId, err)
		return err
	}

	return ensureChannelMessages(c, data, topics)
}

func ensureChannelMessages(parentChannel *models.Channel, data *models.ChannelMessage, topics []string) error {
	for _, topic := range topics {
		tc, err := fetchTopicChannel(parentChannel.Group, topic)
		if err != nil && err != gorm.RecordNotFound {
			return err
		}

		if err == gorm.RecordNotFound {
			tc, err = createTopicChannel(data.AccountId, parentChannel.Group, topic, parentChannel.Privacy)
			if err != nil {
				return err
			}
		}

		_, err = tc.AddMessage(data.Id)
		if err != nil {
			return err
		}
	}

	return nil
}

func extractTopics(body string) []string {
	flattened := make([]string, 0)

	res := topicRegex.FindAllStringSubmatch(body, -1)
	if len(res) == 0 {
		return flattened
	}

	topics := map[string]struct{}{}
	// remove duplicate tag usages
	for _, ele := range res {
		topics[ele[1]] = struct{}{}
	}

	for topic := range topics {
		flattened = append(flattened, topic)
	}

	return flattened
}

func (f *TopicFeedController) MessageUpdated(data *models.ChannelMessage) error {
	f.log.Debug("udpate message %s", data.Id)
	// fetch message's current topics from the db
	channels, err := fetchMessageChannels(data.Id)
	if err != nil {
		return err
	}

	// get current topics from
	topics := extractTopics(data.Body)
	if topics == nil {
		return nil
	}

	// if message and the topics dont have any item, we can safely return
	if len(channels) == 0 && len(topics) == 0 {
		return nil
	}

	res := getTopicDiff(channels, topics)

	// add messages
	if len(res["added"]) > 0 {
		initialChannel, err := fetchChannel(data.InitialChannelId)
		if err != nil {
			return err
		}

		if err := ensureChannelMessages(initialChannel, data, res["added"]); err != nil {
			return err
		}
	}

	// delete messages
	if len(res["deleted"]) > 0 {
		if err := deleteChannelMessages(channels, data, res["deleted"]); err != nil {
			return err
		}
	}

	return nil
}

func deleteChannelMessages(channels []models.Channel, data *models.ChannelMessage, toBeDeletedTopics []string) error {
	for _, channel := range channels {
		for _, topic := range toBeDeletedTopics {
			if channel.Name != topic {
				continue
			}

			cml := models.NewChannelMessageList()
			selector := map[string]interface{}{
				"message_id": data.Id,
				"channel_id": channel.Id,
			}

			if err := cml.DeleteMessagesBySelector(selector); err != nil {
				return err
			}
		}
	}
	return nil
}

func fetchMessageChannels(messageId int64) ([]models.Channel, error) {
	cml := models.NewChannelMessageList()
	return cml.FetchMessageChannels(messageId)
}

func getTopicDiff(channels []models.Channel, topics []string) map[string][]string {
	res := make(map[string][]string)

	// aggregate all channel names into map
	channelNames := map[string]struct{}{}
	for _, channel := range channels {
		channelNames[channel.Name] = struct{}{}
	}

	// range over new topics, bacause we are gonna remove
	// unused channels
	for _, topic := range topics {
		found := false
		for channelName := range channelNames {
			if channelName == topic {
				found = true
			}
		}
		if !found {
			res["added"] = append(res["added"], topic)
		} else {
			// if we have topic in channels
			// do remove it because at the end we are gonna mark
			// channels as deleted which are still in channelNames
			delete(channelNames, topic)
		}
	}
	// flatten the deleted channel names
	for channelName := range channelNames {
		res["deleted"] = append(res["deleted"], channelName)
	}

	return res
}

func (f *TopicFeedController) MessageDeleted(data *models.ChannelMessage) error {
	cml := models.NewChannelMessageList()
	selector := map[string]interface{}{
		"message_id": data.Id,
	}

	if err := cml.DeleteMessagesBySelector(selector); err != nil {
		return err
	}
	return nil
}

func mapMessage(data []byte) (*models.ChannelMessage, error) {
	cm := models.NewChannelMessage()
	if err := json.Unmarshal(data, cm); err != nil {
		return nil, err
	}

	return cm, nil
}

func isEligible(cm *models.ChannelMessage) (bool, error) {
	if cm.InitialChannelId == 0 {
		return false, nil
	}

	if cm.Type != models.ChannelMessage_TYPE_POST {
		return false, nil
	}

	return true, nil
}

// todo add caching here
func fetchChannel(channelId int64) (*models.Channel, error) {
	c := models.NewChannel()
	c.Id = channelId
	// todo - fetch only name here
	if err := c.Fetch(); err != nil {
		return nil, err
	}

	return c, nil
}

// todo add caching here
func fetchTopicChannel(groupName, channelName string) (*models.Channel, error) {
	c := models.NewChannel()

	selector := map[string]interface{}{
		"group": groupName,
		"name":  channelName,
		"type":  models.Channel_TYPE_TOPIC,
	}

	err := c.One(selector)
	if err != nil {
		return nil, err
	}

	return c, nil
}

func createTopicChannel(creatorId int64, groupName, channelName, privacy string) (*models.Channel, error) {
	c := models.NewChannel()
	c.Name = channelName
	c.CreatorId = creatorId
	c.Group = groupName
	c.Purpose = fmt.Sprintf("Channel for %s topic", channelName)
	c.Type = models.Channel_TYPE_TOPIC
	c.Privacy = privacy
	if err := c.Create(); err != nil {
		return nil, err
	}

	return c, nil
}
<|endoftext|>


<commit_before>package libvirt

/*
#cgo LDFLAGS: -lvirt
#include <libvirt/libvirt.h>
#include <libvirt/virterror.h>
#include <stdlib.h>
*/
import "C"

import (
	"unsafe"
)

type VirSecretUsageType int

const (
	VIR_SECRET_USAGE_TYPE_NONE   = VirSecretUsageType(C.VIR_SECRET_USAGE_TYPE_NONE)
	VIR_SECRET_USAGE_TYPE_VOLUME = VirSecretUsageType(C.VIR_SECRET_USAGE_TYPE_VOLUME)
	VIR_SECRET_USAGE_TYPE_CEPH   = VirSecretUsageType(C.VIR_SECRET_USAGE_TYPE_CEPH)
	VIR_SECRET_USAGE_TYPE_ISCSI  = VirSecretUsageType(C.VIR_SECRET_USAGE_TYPE_ISCSI)
)

type VirSecret struct {
	ptr C.virSecretPtr
}

func (s *VirSecret) Free() error {
	if result := C.virSecretFree(s.ptr); result != 0 {
		return GetLastError()
	}
	return nil
}

func (s *VirSecret) Undefine() error {
	result := C.virSecretUndefine(s.ptr)
	if result == -1 {
		return GetLastError()
	}
	return nil
}

func (s *VirSecret) GetUUID() ([]byte, error) {
	var cUuid [C.VIR_UUID_BUFLEN](byte)
	cuidPtr := unsafe.Pointer(&cUuid)
	result := C.virSecretGetUUID(s.ptr, (*C.uchar)(cuidPtr))
	if result != 0 {
		return []byte{}, GetLastError()
	}
	return C.GoBytes(cuidPtr, C.VIR_UUID_BUFLEN), nil
}

func (s *VirSecret) GetUUIDString() (string, error) {
	var cUuid [C.VIR_UUID_STRING_BUFLEN](C.char)
	cuidPtr := unsafe.Pointer(&cUuid)
	result := C.virSecretGetUUIDString(s.ptr, (*C.char)(cuidPtr))
	if result != 0 {
		return "", GetLastError()
	}
	return C.GoString((*C.char)(cuidPtr)), nil
}

func (s *VirSecret) GetUsageID() (string, error) {
	result := C.virSecretGetUsageID(s.ptr)
	if result == nil {
		return "", GetLastError()
	}
	return C.GoString(result), nil
}

func (s *VirSecret) GetUsageType() (VirSecretUsageType, error) {
	result := VirSecretUsageType(C.virSecretGetUsageType(s.ptr))
	if result == -1 {
		return 0, GetLastError()
	}
	return result, nil
}

func (s *VirSecret) GetXMLDesc(flags uint32) (string, error) {
	result := C.virSecretGetXMLDesc(s.ptr, C.uint(flags))
	if result == nil {
		return "", GetLastError()
	}
	xml := C.GoString(result)
	C.free(unsafe.Pointer(result))
	return xml, nil
}
<commit_msg>Add remaining enum constants for secret APIs<commit_after>package libvirt

/*
#cgo LDFLAGS: -lvirt
#include <libvirt/libvirt.h>
#include <libvirt/virterror.h>
#include <stdlib.h>
*/
import "C"

import (
	"unsafe"
)

type VirSecretUsageType int

const (
	VIR_SECRET_USAGE_TYPE_NONE   = VirSecretUsageType(C.VIR_SECRET_USAGE_TYPE_NONE)
	VIR_SECRET_USAGE_TYPE_VOLUME = VirSecretUsageType(C.VIR_SECRET_USAGE_TYPE_VOLUME)
	VIR_SECRET_USAGE_TYPE_CEPH   = VirSecretUsageType(C.VIR_SECRET_USAGE_TYPE_CEPH)
	VIR_SECRET_USAGE_TYPE_ISCSI  = VirSecretUsageType(C.VIR_SECRET_USAGE_TYPE_ISCSI)
	VIR_SECRET_USAGE_TYPE_TLS    = VirSecretUsageType(C.VIR_SECRET_USAGE_TYPE_TLS)
)

type VirSecret struct {
	ptr C.virSecretPtr
}

func (s *VirSecret) Free() error {
	if result := C.virSecretFree(s.ptr); result != 0 {
		return GetLastError()
	}
	return nil
}

func (s *VirSecret) Undefine() error {
	result := C.virSecretUndefine(s.ptr)
	if result == -1 {
		return GetLastError()
	}
	return nil
}

func (s *VirSecret) GetUUID() ([]byte, error) {
	var cUuid [C.VIR_UUID_BUFLEN](byte)
	cuidPtr := unsafe.Pointer(&cUuid)
	result := C.virSecretGetUUID(s.ptr, (*C.uchar)(cuidPtr))
	if result != 0 {
		return []byte{}, GetLastError()
	}
	return C.GoBytes(cuidPtr, C.VIR_UUID_BUFLEN), nil
}

func (s *VirSecret) GetUUIDString() (string, error) {
	var cUuid [C.VIR_UUID_STRING_BUFLEN](C.char)
	cuidPtr := unsafe.Pointer(&cUuid)
	result := C.virSecretGetUUIDString(s.ptr, (*C.char)(cuidPtr))
	if result != 0 {
		return "", GetLastError()
	}
	return C.GoString((*C.char)(cuidPtr)), nil
}

func (s *VirSecret) GetUsageID() (string, error) {
	result := C.virSecretGetUsageID(s.ptr)
	if result == nil {
		return "", GetLastError()
	}
	return C.GoString(result), nil
}

func (s *VirSecret) GetUsageType() (VirSecretUsageType, error) {
	result := VirSecretUsageType(C.virSecretGetUsageType(s.ptr))
	if result == -1 {
		return 0, GetLastError()
	}
	return result, nil
}

func (s *VirSecret) GetXMLDesc(flags uint32) (string, error) {
	result := C.virSecretGetXMLDesc(s.ptr, C.uint(flags))
	if result == nil {
		return "", GetLastError()
	}
	xml := C.GoString(result)
	C.free(unsafe.Pointer(result))
	return xml, nil
}
<|endoftext|>


<commit_before>package logrus_sentry

import (
	"encoding/json"
	"fmt"
	"runtime"
	"sync"
	"time"

	"github.com/getsentry/raven-go"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

var (
	severityMap = map[logrus.Level]raven.Severity{
		logrus.TraceLevel: raven.DEBUG,
		logrus.DebugLevel: raven.DEBUG,
		logrus.InfoLevel:  raven.INFO,
		logrus.WarnLevel:  raven.WARNING,
		logrus.ErrorLevel: raven.ERROR,
		logrus.FatalLevel: raven.FATAL,
		logrus.PanicLevel: raven.FATAL,
	}
)

// SentryHook delivers logs to a sentry server.
type SentryHook struct {
	// Timeout sets the time to wait for a delivery error from the sentry server.
	// If this is set to zero the server will not wait for any response and will
	// consider the message correctly sent.
	//
	// This is ignored for asynchronous hooks. If you want to set a timeout when
	// using an async hook (to bound the length of time that hook.Flush can take),
	// you probably want to create your own raven.Client and set
	// ravenClient.Transport.(*raven.HTTPTransport).Client.Timeout to set a
	// timeout on the underlying HTTP request instead.
	Timeout                 time.Duration
	StacktraceConfiguration StackTraceConfiguration

	client *raven.Client
	levels []logrus.Level

	serverName    string
	ignoreFields  map[string]struct{}
	extraFilters  map[string]func(interface{}) interface{}
	errorHandlers []func(entry *logrus.Entry, err error)

	asynchronous bool

	mu sync.RWMutex
	wg sync.WaitGroup
}

// The Stacktracer interface allows an error type to return a raven.Stacktrace.
type Stacktracer interface {
	GetStacktrace() *raven.Stacktrace
}

type causer interface {
	Cause() error
}

type pkgErrorStackTracer interface {
	StackTrace() errors.StackTrace
}

// StackTraceConfiguration allows for configuring stacktraces
type StackTraceConfiguration struct {
	// whether stacktraces should be enabled
	Enable bool
	// the level at which to start capturing stacktraces
	Level logrus.Level
	// how many stack frames to skip before stacktrace starts recording
	Skip int
	// the number of lines to include around a stack frame for context
	Context int
	// the prefixes that will be matched against the stack frame.
	// if the stack frame's package matches one of these prefixes
	// sentry will identify the stack frame as "in_app"
	InAppPrefixes []string
	// whether sending exception type should be enabled.
	SendExceptionType bool
	// whether the exception type and message should be switched.
	SwitchExceptionTypeAndMessage bool
}

// NewSentryHook creates a hook to be added to an instance of logger
// and initializes the raven client.
// This method sets the timeout to 100 milliseconds.
func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
	client, err := raven.New(DSN)
	if err != nil {
		return nil, err
	}
	return NewWithClientSentryHook(client, levels)
}

// NewWithTagsSentryHook creates a hook with tags to be added to an instance
// of logger and initializes the raven client. This method sets the timeout to
// 100 milliseconds.
func NewWithTagsSentryHook(DSN string, tags map[string]string, levels []logrus.Level) (*SentryHook, error) {
	client, err := raven.NewWithTags(DSN, tags)
	if err != nil {
		return nil, err
	}
	return NewWithClientSentryHook(client, levels)
}

// NewWithClientSentryHook creates a hook using an initialized raven client.
// This method sets the timeout to 100 milliseconds.
func NewWithClientSentryHook(client *raven.Client, levels []logrus.Level) (*SentryHook, error) {
	return &SentryHook{
		Timeout: 100 * time.Millisecond,
		StacktraceConfiguration: StackTraceConfiguration{
			Enable:            false,
			Level:             logrus.ErrorLevel,
			Skip:              6,
			Context:           0,
			InAppPrefixes:     nil,
			SendExceptionType: true,
		},
		client:       client,
		levels:       levels,
		ignoreFields: make(map[string]struct{}),
		extraFilters: make(map[string]func(interface{}) interface{}),
	}, nil
}

// NewAsyncSentryHook creates a hook same as NewSentryHook, but in asynchronous
// mode.
func NewAsyncSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
	hook, err := NewSentryHook(DSN, levels)
	return setAsync(hook), err
}

// NewAsyncWithTagsSentryHook creates a hook same as NewWithTagsSentryHook, but
// in asynchronous mode.
func NewAsyncWithTagsSentryHook(DSN string, tags map[string]string, levels []logrus.Level) (*SentryHook, error) {
	hook, err := NewWithTagsSentryHook(DSN, tags, levels)
	return setAsync(hook), err
}

// NewAsyncWithClientSentryHook creates a hook same as NewWithClientSentryHook,
// but in asynchronous mode.
func NewAsyncWithClientSentryHook(client *raven.Client, levels []logrus.Level) (*SentryHook, error) {
	hook, err := NewWithClientSentryHook(client, levels)
	return setAsync(hook), err
}

func setAsync(hook *SentryHook) *SentryHook {
	if hook == nil {
		return nil
	}
	hook.asynchronous = true
	return hook
}

// Fire is called when an event should be sent to sentry
// Special fields that sentry uses to give more information to the server
// are extracted from entry.Data (if they are found)
// These fields are: error, logger, server_name, http_request, tags
func (hook *SentryHook) Fire(entry *logrus.Entry) error {
	hook.mu.RLock() // Allow multiple go routines to log simultaneously
	defer hook.mu.RUnlock()
	packet := raven.NewPacket(entry.Message)
	packet.Timestamp = raven.Timestamp(entry.Time)
	packet.Level = severityMap[entry.Level]
	packet.Platform = "go"

	df := newDataField(entry.Data)

	// set special fields
	if hook.serverName != "" {
		packet.ServerName = hook.serverName
	}
	if logger, ok := df.getLogger(); ok {
		packet.Logger = logger
	}
	if serverName, ok := df.getServerName(); ok {
		packet.ServerName = serverName
	}
	if eventID, ok := df.getEventID(); ok {
		packet.EventID = eventID
	}
	if tags, ok := df.getTags(); ok {
		packet.Tags = tags
	}
	if fingerprint, ok := df.getFingerprint(); ok {
		packet.Fingerprint = fingerprint
	}
	if req, ok := df.getHTTPRequest(); ok {
		packet.Interfaces = append(packet.Interfaces, req)
	}
	if user, ok := df.getUser(); ok {
		packet.Interfaces = append(packet.Interfaces, user)
	}

	// set stacktrace data
	stConfig := &hook.StacktraceConfiguration
	if stConfig.Enable && entry.Level <= stConfig.Level {
		if err, ok := df.getError(); ok {
			var currentStacktrace *raven.Stacktrace
			currentStacktrace = hook.findStacktrace(err)
			if currentStacktrace == nil {
				currentStacktrace = raven.NewStacktrace(stConfig.Skip, stConfig.Context, stConfig.InAppPrefixes)
			}
			cause := errors.Cause(err)
			if cause == nil {
				cause = err
			}
			exc := raven.NewException(cause, currentStacktrace)
			if !stConfig.SendExceptionType {
				exc.Type = ""
			}
			if stConfig.SwitchExceptionTypeAndMessage {
				packet.Interfaces = append(packet.Interfaces, currentStacktrace)
				packet.Culprit = exc.Type + ": " + currentStacktrace.Culprit()
			} else {
				packet.Interfaces = append(packet.Interfaces, exc)
				packet.Culprit = err.Error()
			}
		} else {
			currentStacktrace := raven.NewStacktrace(stConfig.Skip, stConfig.Context, stConfig.InAppPrefixes)
			if currentStacktrace != nil {
				packet.Interfaces = append(packet.Interfaces, currentStacktrace)
			}
		}
	} else {
		// set the culprit even when the stack trace is disabled, as long as we have an error
		if err, ok := df.getError(); ok {
			packet.Culprit = err.Error()
		}
	}

	// set other fields
	dataExtra := hook.formatExtraData(df)
	if packet.Extra == nil {
		packet.Extra = dataExtra
	} else {
		for k, v := range dataExtra {
			packet.Extra[k] = v
		}
	}

	_, errCh := hook.client.Capture(packet, nil)

	switch {
	case hook.asynchronous:
		// Our use of hook.mu guarantees that we are following the WaitGroup rule of
		// not calling Add in parallel with Wait.
		hook.wg.Add(1)
		go func() {
			if err := <-errCh; err != nil {
				for _, handlerFn := range hook.errorHandlers {
					handlerFn(entry, err)
				}
				hook.wg.Done()
			}
		}()
		return nil
	case hook.Timeout == 0:
		return nil
	default:
		timeout := hook.Timeout
		timeoutCh := time.After(timeout)
		select {
		case err := <-errCh:
			for _, handlerFn := range hook.errorHandlers {
				handlerFn(entry, err)
			}
			return err
		case <-timeoutCh:
			return fmt.Errorf("no response from sentry server in %s", timeout)
		}
	}
}

// Flush waits for the log queue to empty. This function only does anything in
// asynchronous mode.
func (hook *SentryHook) Flush() {
	if !hook.asynchronous {
		return
	}
	hook.mu.Lock() // Claim exclusive access; any logging goroutines will block until the flush completes
	defer hook.mu.Unlock()

	hook.wg.Wait()
}

func (hook *SentryHook) findStacktrace(err error) *raven.Stacktrace {
	var stacktrace *raven.Stacktrace
	var stackErr errors.StackTrace
	for err != nil {
		// Find the earliest *raven.Stacktrace, or error.StackTrace
		if tracer, ok := err.(Stacktracer); ok {
			stacktrace = tracer.GetStacktrace()
			stackErr = nil
		} else if tracer, ok := err.(pkgErrorStackTracer); ok {
			stacktrace = nil
			stackErr = tracer.StackTrace()
		}
		if cause, ok := err.(causer); ok {
			err = cause.Cause()
		} else {
			break
		}
	}
	if stackErr != nil {
		stacktrace = hook.convertStackTrace(stackErr)
	}
	return stacktrace
}

// convertStackTrace converts an errors.StackTrace into a natively consumable
// *raven.Stacktrace
func (hook *SentryHook) convertStackTrace(st errors.StackTrace) *raven.Stacktrace {
	stConfig := &hook.StacktraceConfiguration
	stFrames := []errors.Frame(st)
	frames := make([]*raven.StacktraceFrame, 0, len(stFrames))
	for i := range stFrames {
		pc := uintptr(stFrames[i])
		fn := runtime.FuncForPC(pc)
		file, line := fn.FileLine(pc)
		frame := raven.NewStacktraceFrame(pc, fn.Name(), file, line, stConfig.Context, stConfig.InAppPrefixes)
		if frame != nil {
			frames = append(frames, frame)
		}
	}

	// Sentry wants the frames with the oldest first, so reverse them
	for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 {
		frames[i], frames[j] = frames[j], frames[i]
	}
	return &raven.Stacktrace{Frames: frames}
}

// Levels returns the available logging levels.
func (hook *SentryHook) Levels() []logrus.Level {
	return hook.levels
}

// AddIgnore adds field name to ignore.
func (hook *SentryHook) AddIgnore(name string) {
	hook.ignoreFields[name] = struct{}{}
}

// AddExtraFilter adds a custom filter function.
func (hook *SentryHook) AddExtraFilter(name string, fn func(interface{}) interface{}) {
	hook.extraFilters[name] = fn
}

// AddErrorHandler adds a error handler function used when Sentry returns error.
func (hook *SentryHook) AddErrorHandler(fn func(entry *logrus.Entry, err error)) {
	hook.errorHandlers = append(hook.errorHandlers, fn)
}

func (hook *SentryHook) formatExtraData(df *dataField) (result map[string]interface{}) {
	// create a map for passing to Sentry's extra data
	result = make(map[string]interface{}, df.len())
	for k, v := range df.data {
		if df.isOmit(k) {
			continue // skip already used special fields
		}
		if _, ok := hook.ignoreFields[k]; ok {
			continue
		}

		if fn, ok := hook.extraFilters[k]; ok {
			v = fn(v) // apply custom filter
		} else {
			v = formatData(v) // use default formatter
		}
		result[k] = v
	}
	return result
}

// formatData returns value as a suitable format.
func formatData(value interface{}) (formatted interface{}) {
	switch value := value.(type) {
	case json.Marshaler:
		return value
	case error:
		return value.Error()
	case fmt.Stringer:
		return value.String()
	default:
		return value
	}
}
<commit_msg>bugfix: wg wait block exit when shutdown w/out error (#82)<commit_after>package logrus_sentry

import (
	"encoding/json"
	"fmt"
	"runtime"
	"sync"
	"time"

	"github.com/getsentry/raven-go"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

var (
	severityMap = map[logrus.Level]raven.Severity{
		logrus.TraceLevel: raven.DEBUG,
		logrus.DebugLevel: raven.DEBUG,
		logrus.InfoLevel:  raven.INFO,
		logrus.WarnLevel:  raven.WARNING,
		logrus.ErrorLevel: raven.ERROR,
		logrus.FatalLevel: raven.FATAL,
		logrus.PanicLevel: raven.FATAL,
	}
)

// SentryHook delivers logs to a sentry server.
type SentryHook struct {
	// Timeout sets the time to wait for a delivery error from the sentry server.
	// If this is set to zero the server will not wait for any response and will
	// consider the message correctly sent.
	//
	// This is ignored for asynchronous hooks. If you want to set a timeout when
	// using an async hook (to bound the length of time that hook.Flush can take),
	// you probably want to create your own raven.Client and set
	// ravenClient.Transport.(*raven.HTTPTransport).Client.Timeout to set a
	// timeout on the underlying HTTP request instead.
	Timeout                 time.Duration
	StacktraceConfiguration StackTraceConfiguration

	client *raven.Client
	levels []logrus.Level

	serverName    string
	ignoreFields  map[string]struct{}
	extraFilters  map[string]func(interface{}) interface{}
	errorHandlers []func(entry *logrus.Entry, err error)

	asynchronous bool

	mu sync.RWMutex
	wg sync.WaitGroup
}

// The Stacktracer interface allows an error type to return a raven.Stacktrace.
type Stacktracer interface {
	GetStacktrace() *raven.Stacktrace
}

type causer interface {
	Cause() error
}

type pkgErrorStackTracer interface {
	StackTrace() errors.StackTrace
}

// StackTraceConfiguration allows for configuring stacktraces
type StackTraceConfiguration struct {
	// whether stacktraces should be enabled
	Enable bool
	// the level at which to start capturing stacktraces
	Level logrus.Level
	// how many stack frames to skip before stacktrace starts recording
	Skip int
	// the number of lines to include around a stack frame for context
	Context int
	// the prefixes that will be matched against the stack frame.
	// if the stack frame's package matches one of these prefixes
	// sentry will identify the stack frame as "in_app"
	InAppPrefixes []string
	// whether sending exception type should be enabled.
	SendExceptionType bool
	// whether the exception type and message should be switched.
	SwitchExceptionTypeAndMessage bool
}

// NewSentryHook creates a hook to be added to an instance of logger
// and initializes the raven client.
// This method sets the timeout to 100 milliseconds.
func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
	client, err := raven.New(DSN)
	if err != nil {
		return nil, err
	}
	return NewWithClientSentryHook(client, levels)
}

// NewWithTagsSentryHook creates a hook with tags to be added to an instance
// of logger and initializes the raven client. This method sets the timeout to
// 100 milliseconds.
func NewWithTagsSentryHook(DSN string, tags map[string]string, levels []logrus.Level) (*SentryHook, error) {
	client, err := raven.NewWithTags(DSN, tags)
	if err != nil {
		return nil, err
	}
	return NewWithClientSentryHook(client, levels)
}

// NewWithClientSentryHook creates a hook using an initialized raven client.
// This method sets the timeout to 100 milliseconds.
func NewWithClientSentryHook(client *raven.Client, levels []logrus.Level) (*SentryHook, error) {
	return &SentryHook{
		Timeout: 100 * time.Millisecond,
		StacktraceConfiguration: StackTraceConfiguration{
			Enable:            false,
			Level:             logrus.ErrorLevel,
			Skip:              6,
			Context:           0,
			InAppPrefixes:     nil,
			SendExceptionType: true,
		},
		client:       client,
		levels:       levels,
		ignoreFields: make(map[string]struct{}),
		extraFilters: make(map[string]func(interface{}) interface{}),
	}, nil
}

// NewAsyncSentryHook creates a hook same as NewSentryHook, but in asynchronous
// mode.
func NewAsyncSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
	hook, err := NewSentryHook(DSN, levels)
	return setAsync(hook), err
}

// NewAsyncWithTagsSentryHook creates a hook same as NewWithTagsSentryHook, but
// in asynchronous mode.
func NewAsyncWithTagsSentryHook(DSN string, tags map[string]string, levels []logrus.Level) (*SentryHook, error) {
	hook, err := NewWithTagsSentryHook(DSN, tags, levels)
	return setAsync(hook), err
}

// NewAsyncWithClientSentryHook creates a hook same as NewWithClientSentryHook,
// but in asynchronous mode.
func NewAsyncWithClientSentryHook(client *raven.Client, levels []logrus.Level) (*SentryHook, error) {
	hook, err := NewWithClientSentryHook(client, levels)
	return setAsync(hook), err
}

func setAsync(hook *SentryHook) *SentryHook {
	if hook == nil {
		return nil
	}
	hook.asynchronous = true
	return hook
}

// Fire is called when an event should be sent to sentry
// Special fields that sentry uses to give more information to the server
// are extracted from entry.Data (if they are found)
// These fields are: error, logger, server_name, http_request, tags
func (hook *SentryHook) Fire(entry *logrus.Entry) error {
	hook.mu.RLock() // Allow multiple go routines to log simultaneously
	defer hook.mu.RUnlock()
	packet := raven.NewPacket(entry.Message)
	packet.Timestamp = raven.Timestamp(entry.Time)
	packet.Level = severityMap[entry.Level]
	packet.Platform = "go"

	df := newDataField(entry.Data)

	// set special fields
	if hook.serverName != "" {
		packet.ServerName = hook.serverName
	}
	if logger, ok := df.getLogger(); ok {
		packet.Logger = logger
	}
	if serverName, ok := df.getServerName(); ok {
		packet.ServerName = serverName
	}
	if eventID, ok := df.getEventID(); ok {
		packet.EventID = eventID
	}
	if tags, ok := df.getTags(); ok {
		packet.Tags = tags
	}
	if fingerprint, ok := df.getFingerprint(); ok {
		packet.Fingerprint = fingerprint
	}
	if req, ok := df.getHTTPRequest(); ok {
		packet.Interfaces = append(packet.Interfaces, req)
	}
	if user, ok := df.getUser(); ok {
		packet.Interfaces = append(packet.Interfaces, user)
	}

	// set stacktrace data
	stConfig := &hook.StacktraceConfiguration
	if stConfig.Enable && entry.Level <= stConfig.Level {
		if err, ok := df.getError(); ok {
			var currentStacktrace *raven.Stacktrace
			currentStacktrace = hook.findStacktrace(err)
			if currentStacktrace == nil {
				currentStacktrace = raven.NewStacktrace(stConfig.Skip, stConfig.Context, stConfig.InAppPrefixes)
			}
			cause := errors.Cause(err)
			if cause == nil {
				cause = err
			}
			exc := raven.NewException(cause, currentStacktrace)
			if !stConfig.SendExceptionType {
				exc.Type = ""
			}
			if stConfig.SwitchExceptionTypeAndMessage {
				packet.Interfaces = append(packet.Interfaces, currentStacktrace)
				packet.Culprit = exc.Type + ": " + currentStacktrace.Culprit()
			} else {
				packet.Interfaces = append(packet.Interfaces, exc)
				packet.Culprit = err.Error()
			}
		} else {
			currentStacktrace := raven.NewStacktrace(stConfig.Skip, stConfig.Context, stConfig.InAppPrefixes)
			if currentStacktrace != nil {
				packet.Interfaces = append(packet.Interfaces, currentStacktrace)
			}
		}
	} else {
		// set the culprit even when the stack trace is disabled, as long as we have an error
		if err, ok := df.getError(); ok {
			packet.Culprit = err.Error()
		}
	}

	// set other fields
	dataExtra := hook.formatExtraData(df)
	if packet.Extra == nil {
		packet.Extra = dataExtra
	} else {
		for k, v := range dataExtra {
			packet.Extra[k] = v
		}
	}

	_, errCh := hook.client.Capture(packet, nil)

	switch {
	case hook.asynchronous:
		// Our use of hook.mu guarantees that we are following the WaitGroup rule of
		// not calling Add in parallel with Wait.
		hook.wg.Add(1)
		go func() {
			if err := <-errCh; err != nil {
				for _, handlerFn := range hook.errorHandlers {
					handlerFn(entry, err)
				}
			}
			hook.wg.Done()
		}()
		return nil
	case hook.Timeout == 0:
		return nil
	default:
		timeout := hook.Timeout
		timeoutCh := time.After(timeout)
		select {
		case err := <-errCh:
			for _, handlerFn := range hook.errorHandlers {
				handlerFn(entry, err)
			}
			return err
		case <-timeoutCh:
			return fmt.Errorf("no response from sentry server in %s", timeout)
		}
	}
}

// Flush waits for the log queue to empty. This function only does anything in
// asynchronous mode.
func (hook *SentryHook) Flush() {
	if !hook.asynchronous {
		return
	}
	hook.mu.Lock() // Claim exclusive access; any logging goroutines will block until the flush completes
	defer hook.mu.Unlock()

	hook.wg.Wait()
}

func (hook *SentryHook) findStacktrace(err error) *raven.Stacktrace {
	var stacktrace *raven.Stacktrace
	var stackErr errors.StackTrace
	for err != nil {
		// Find the earliest *raven.Stacktrace, or error.StackTrace
		if tracer, ok := err.(Stacktracer); ok {
			stacktrace = tracer.GetStacktrace()
			stackErr = nil
		} else if tracer, ok := err.(pkgErrorStackTracer); ok {
			stacktrace = nil
			stackErr = tracer.StackTrace()
		}
		if cause, ok := err.(causer); ok {
			err = cause.Cause()
		} else {
			break
		}
	}
	if stackErr != nil {
		stacktrace = hook.convertStackTrace(stackErr)
	}
	return stacktrace
}

// convertStackTrace converts an errors.StackTrace into a natively consumable
// *raven.Stacktrace
func (hook *SentryHook) convertStackTrace(st errors.StackTrace) *raven.Stacktrace {
	stConfig := &hook.StacktraceConfiguration
	stFrames := []errors.Frame(st)
	frames := make([]*raven.StacktraceFrame, 0, len(stFrames))
	for i := range stFrames {
		pc := uintptr(stFrames[i])
		fn := runtime.FuncForPC(pc)
		file, line := fn.FileLine(pc)
		frame := raven.NewStacktraceFrame(pc, fn.Name(), file, line, stConfig.Context, stConfig.InAppPrefixes)
		if frame != nil {
			frames = append(frames, frame)
		}
	}

	// Sentry wants the frames with the oldest first, so reverse them
	for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 {
		frames[i], frames[j] = frames[j], frames[i]
	}
	return &raven.Stacktrace{Frames: frames}
}

// Levels returns the available logging levels.
func (hook *SentryHook) Levels() []logrus.Level {
	return hook.levels
}

// AddIgnore adds field name to ignore.
func (hook *SentryHook) AddIgnore(name string) {
	hook.ignoreFields[name] = struct{}{}
}

// AddExtraFilter adds a custom filter function.
func (hook *SentryHook) AddExtraFilter(name string, fn func(interface{}) interface{}) {
	hook.extraFilters[name] = fn
}

// AddErrorHandler adds a error handler function used when Sentry returns error.
func (hook *SentryHook) AddErrorHandler(fn func(entry *logrus.Entry, err error)) {
	hook.errorHandlers = append(hook.errorHandlers, fn)
}

func (hook *SentryHook) formatExtraData(df *dataField) (result map[string]interface{}) {
	// create a map for passing to Sentry's extra data
	result = make(map[string]interface{}, df.len())
	for k, v := range df.data {
		if df.isOmit(k) {
			continue // skip already used special fields
		}
		if _, ok := hook.ignoreFields[k]; ok {
			continue
		}

		if fn, ok := hook.extraFilters[k]; ok {
			v = fn(v) // apply custom filter
		} else {
			v = formatData(v) // use default formatter
		}
		result[k] = v
	}
	return result
}

// formatData returns value as a suitable format.
func formatData(value interface{}) (formatted interface{}) {
	switch value := value.(type) {
	case json.Marshaler:
		return value
	case error:
		return value.Error()
	case fmt.Stringer:
		return value.String()
	default:
		return value
	}
}
<|endoftext|>


<commit_before>package incus

import (
	"crypto/md5"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/alexjlockwood/gcm"
	apns "github.com/anachronistic/apns"
	"github.com/gorilla/websocket"
	"github.com/spf13/viper"
)

const (
	writeWait                         = 5 * time.Second
	pongWait                          = 1 * time.Second
	abnormalCloseControlWriteDeadline = 1 * time.Second
	websocketReadBufferSize           = 1024
	websocketWriteBufferSize          = 1024

	// RFC 6455 Section 7
	closeCodeNormal          = 1000
	closeCodeGoingAway       = 1001
	closeCodeUnexpectedError = 1011
)

type GCMClient interface {
	Send(*gcm.Message, int) (*gcm.Response, error)
}

type Server struct {
	ID    string
	Store *Storage
	Stats RuntimeStats

	timeout      time.Duration
	apnsProvider func(string) apns.APNSClient
	gcmProvider  func() GCMClient
}

func NewServer(store *Storage, stats RuntimeStats) *Server {
	hash := md5.New()
	io.WriteString(hash, time.Now().String())
	id := string(hash.Sum(nil))

	timeout := time.Duration(viper.GetInt("connection_timeout"))

	if timeout <= 0 {
		panic(fmt.Errorf("connection_timeout <= 0: %+v", timeout))
	}

	apnsProvider := func(build string) apns.APNSClient {
		return apns.NewClient(viper.GetString("apns_"+build+"_url"), viper.GetString("apns_"+build+"_cert"), viper.GetString("apns_"+build+"_private_key"))
	}

	gcmProvider := func() GCMClient {
		return &gcm.Sender{ApiKey: viper.GetString("gcm_api_key")}
	}

	return &Server{
		ID:           id,
		Store:        store,
		timeout:      timeout,
		Stats:        stats,
		apnsProvider: apnsProvider,
		gcmProvider:  gcmProvider,
	}
}

func (this *Server) ListenFromSockets() {
	Connect := func(w http.ResponseWriter, r *http.Request) {
		writtenCloseMessage := false

		exitSignals := make(chan os.Signal)
		signal.Notify(exitSignals, os.Interrupt, syscall.SIGTERM)
		defer signal.Stop(exitSignals)

		if r.Method != "GET" {
			http.Error(w, "Method not allowed", 405)
			return
		}
		//if r.Header.Get("Origin") != "http://"+r.Host {
		//        http.Error(w, "Origin not allowed", 403)
		//        return
		// }

		ws, err := websocket.Upgrade(w, r, nil, websocketReadBufferSize, websocketWriteBufferSize)
		if _, ok := err.(websocket.HandshakeError); ok {
			http.Error(w, "Not a websocket handshake", 400)
			return
		} else if err != nil {
			log.Println(err)
			return
		}

		defer func() {
			if !writtenCloseMessage {
				closeMessage := websocket.FormatCloseMessage(closeCodeUnexpectedError, "")
				ws.WriteControl(websocket.CloseMessage, closeMessage, time.Now().Add(abnormalCloseControlWriteDeadline))
			}

			ws.Close()
			this.Stats.LogWebsocketDisconnection()
			if DEBUG {
				log.Println("Socket Closed")
			}
		}()

		sock := newSocket(ws, nil, this, "")

		this.Stats.LogWebsocketConnection()
		if DEBUG {
			log.Printf("Socket connected via %s\n", ws.RemoteAddr())
		}
		if err := sock.Authenticate(""); err != nil {
			if DEBUG {
				log.Printf("Error: %s\n", err.Error())
			}
			return
		}

		go sock.listenForMessages()
		go sock.listenForWrites()

		select {
		case <-time.After(this.timeout * time.Second):
			sock.Close()
			writtenCloseMessage = closeWebsocket(closeCodeNormal, ws)
			return
		case <-sock.done:
			writtenCloseMessage = closeWebsocket(closeCodeNormal, ws)
			return
		case <-exitSignals:
			writtenCloseMessage = closeWebsocket(closeCodeGoingAway, ws)
			return
		}
	}

	http.HandleFunc("/socket", Connect)
}

func closeWebsocket(closeCode int, ws *websocket.Conn) bool {
	closeMessage := websocket.FormatCloseMessage(closeCode, "")
	err := ws.WriteControl(websocket.CloseMessage, closeMessage, time.Now().Add(1*time.Second))
	return err == nil
}

func (this *Server) ListenFromLongpoll() {
	LpConnect := func(w http.ResponseWriter, r *http.Request) {
		exitSignals := make(chan os.Signal)
		signal.Notify(exitSignals, os.Interrupt, syscall.SIGTERM)
		defer signal.Stop(exitSignals)

		defer func() {
			r.Body.Close()
			if DEBUG {
				log.Println("Socket Closed")
			}
		}()

		sock := newSocket(nil, w, this, "")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Content-Type", "application/json")
		w.Header().Set("Cache-Control", "private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0")
		w.Header().Set("Connection", "keep-alive")
		//w.Header().Set("Content-Encoding", "gzip")

		if DEBUG {
			log.Printf("Long poll connected via \n")
		}

		if err := sock.Authenticate(r.FormValue("user")); err != nil {
			if DEBUG {
				log.Printf("Error: %s\n", err.Error())
			}
			return
		}

		page := r.FormValue("page")
		if page != "" {
			if sock.Page != "" {
				this.Store.UnsetPage(sock) //remove old page if it exists
			}

			sock.Page = page
			this.Store.SetPage(sock)
		}

		command := r.FormValue("command")
		if command != "" {
			var cmd = new(CommandMsg)
			json.Unmarshal([]byte(command), cmd)

			go cmd.FromSocket(sock)
		}

		go sock.listenForWrites()

		select {
		case <-exitSignals:
			sock.Close()
			w.WriteHeader(503)
			return
		case <-time.After(this.timeout * time.Second):
			sock.Close()
			w.WriteHeader(204)
			return
		case <-sock.done:
			// No need to write 200, as it's already written implicitly.
			return
		}

	}

	http.HandleFunc("/lp", LpConnect)
}

func (this *Server) ListenFromRedis() {
	if !viper.GetBool("redis_enabled") {
		return
	}

	subReciever := make(chan []byte, 10000)
	queueReciever := make(chan []byte, 10000)

	_, err := this.Store.redis.Subscribe(subReciever, viper.GetString("redis_message_channel"))
	if err != nil {
		log.Fatal("Couldn't subscribe to redis channel")
	}

	err = this.Store.redis.Poll(queueReciever, viper.GetString("redis_message_queue"))
	if err != nil {
		log.Fatal("Couldn't start polling of redis queue")
	}

	if DEBUG {
		log.Println("LISTENING FOR REDIS MESSAGE")
	}

	var subMessage []byte
	var pollMessage []byte
	for {
		var cmd = new(CommandMsg)

		select {
		case subMessage = <-subReciever:
			err = json.Unmarshal(subMessage, cmd)
		case pollMessage = <-queueReciever:
			err = json.Unmarshal(pollMessage, cmd)
		}

		if err != nil {
			log.Printf("Error decoding JSON: %s", err.Error())
			this.Stats.LogInvalidJSON()
		} else {
			go cmd.FromRedis(this)
		}
	}
}

func (this *Server) ListenForHTTPPings() {
	pingHandler := func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "OK")
	}

	http.HandleFunc("/ping", pingHandler)
}

func (this *Server) SendHeartbeatsPeriodically(period time.Duration) {

	for {
		time.Sleep(period)

		clients := this.Store.Clients()

		for _, user := range clients {
			for _, sock := range user {

				if sock.isWebsocket() {
					if !sock.isClosed() {
						sock.ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(pongWait))
					}
				}

			}
		}
	}
}

func (this *Server) RecordStats(period time.Duration) {
	for {
		this.Stats.LogClientCount(this.Store.memory.clientCount)
		time.Sleep(period)
	}
}

func (this *Server) LogConnectedClientsPeriodically(period time.Duration) {
	for {
		log.Printf("There are %d connected clients\n", this.Store.memory.clientCount)
		time.Sleep(period)
	}
}

func (this *Server) GetAPNSClient(build string) apns.APNSClient {
	return this.apnsProvider(build)
}

func (this *Server) GetGCMClient() GCMClient {
	return this.gcmProvider()
}
<commit_msg>fix: Websockets shouldn't timeout<commit_after>package incus

import (
	"crypto/md5"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/alexjlockwood/gcm"
	apns "github.com/anachronistic/apns"
	"github.com/gorilla/websocket"
	"github.com/spf13/viper"
)

const (
	writeWait                         = 5 * time.Second
	pongWait                          = 1 * time.Second
	abnormalCloseControlWriteDeadline = 1 * time.Second
	websocketReadBufferSize           = 1024
	websocketWriteBufferSize          = 1024

	// RFC 6455 Section 7
	closeCodeNormal          = 1000
	closeCodeGoingAway       = 1001
	closeCodeUnexpectedError = 1011
)

type GCMClient interface {
	Send(*gcm.Message, int) (*gcm.Response, error)
}

type Server struct {
	ID    string
	Store *Storage
	Stats RuntimeStats

	timeout      time.Duration
	apnsProvider func(string) apns.APNSClient
	gcmProvider  func() GCMClient
}

func NewServer(store *Storage, stats RuntimeStats) *Server {
	hash := md5.New()
	io.WriteString(hash, time.Now().String())
	id := string(hash.Sum(nil))

	timeout := time.Duration(viper.GetInt("connection_timeout"))

	if timeout <= 0 {
		panic(fmt.Errorf("connection_timeout <= 0: %+v", timeout))
	}

	apnsProvider := func(build string) apns.APNSClient {
		return apns.NewClient(viper.GetString("apns_"+build+"_url"), viper.GetString("apns_"+build+"_cert"), viper.GetString("apns_"+build+"_private_key"))
	}

	gcmProvider := func() GCMClient {
		return &gcm.Sender{ApiKey: viper.GetString("gcm_api_key")}
	}

	return &Server{
		ID:           id,
		Store:        store,
		timeout:      timeout,
		Stats:        stats,
		apnsProvider: apnsProvider,
		gcmProvider:  gcmProvider,
	}
}

func (this *Server) ListenFromSockets() {
	Connect := func(w http.ResponseWriter, r *http.Request) {
		writtenCloseMessage := false

		exitSignals := make(chan os.Signal)
		signal.Notify(exitSignals, os.Interrupt, syscall.SIGTERM)
		defer signal.Stop(exitSignals)

		if r.Method != "GET" {
			http.Error(w, "Method not allowed", 405)
			return
		}
		//if r.Header.Get("Origin") != "http://"+r.Host {
		//        http.Error(w, "Origin not allowed", 403)
		//        return
		// }

		ws, err := websocket.Upgrade(w, r, nil, websocketReadBufferSize, websocketWriteBufferSize)
		if _, ok := err.(websocket.HandshakeError); ok {
			http.Error(w, "Not a websocket handshake", 400)
			return
		} else if err != nil {
			log.Println(err)
			return
		}

		defer func() {
			if !writtenCloseMessage {
				closeMessage := websocket.FormatCloseMessage(closeCodeUnexpectedError, "")
				ws.WriteControl(websocket.CloseMessage, closeMessage, time.Now().Add(abnormalCloseControlWriteDeadline))
			}

			ws.Close()
			this.Stats.LogWebsocketDisconnection()
			if DEBUG {
				log.Println("Socket Closed")
			}
		}()

		sock := newSocket(ws, nil, this, "")

		this.Stats.LogWebsocketConnection()
		if DEBUG {
			log.Printf("Socket connected via %s\n", ws.RemoteAddr())
		}
		if err := sock.Authenticate(""); err != nil {
			if DEBUG {
				log.Printf("Error: %s\n", err.Error())
			}
			return
		}

		go sock.listenForMessages()
		go sock.listenForWrites()

		select {
		case <-sock.done:
			writtenCloseMessage = closeWebsocket(closeCodeNormal, ws)
			return
		case <-exitSignals:
			writtenCloseMessage = closeWebsocket(closeCodeGoingAway, ws)
			return
		}
	}

	http.HandleFunc("/socket", Connect)
}

func closeWebsocket(closeCode int, ws *websocket.Conn) bool {
	closeMessage := websocket.FormatCloseMessage(closeCode, "")
	err := ws.WriteControl(websocket.CloseMessage, closeMessage, time.Now().Add(1*time.Second))
	return err == nil
}

func (this *Server) ListenFromLongpoll() {
	LpConnect := func(w http.ResponseWriter, r *http.Request) {
		exitSignals := make(chan os.Signal)
		signal.Notify(exitSignals, os.Interrupt, syscall.SIGTERM)
		defer signal.Stop(exitSignals)

		defer func() {
			r.Body.Close()
			if DEBUG {
				log.Println("Socket Closed")
			}
		}()

		sock := newSocket(nil, w, this, "")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Content-Type", "application/json")
		w.Header().Set("Cache-Control", "private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0")
		w.Header().Set("Connection", "keep-alive")
		//w.Header().Set("Content-Encoding", "gzip")

		if DEBUG {
			log.Printf("Long poll connected via \n")
		}

		if err := sock.Authenticate(r.FormValue("user")); err != nil {
			if DEBUG {
				log.Printf("Error: %s\n", err.Error())
			}
			return
		}

		page := r.FormValue("page")
		if page != "" {
			if sock.Page != "" {
				this.Store.UnsetPage(sock) //remove old page if it exists
			}

			sock.Page = page
			this.Store.SetPage(sock)
		}

		command := r.FormValue("command")
		if command != "" {
			var cmd = new(CommandMsg)
			json.Unmarshal([]byte(command), cmd)

			go cmd.FromSocket(sock)
		}

		go sock.listenForWrites()

		select {
		case <-exitSignals:
			sock.Close()
			w.WriteHeader(503)
			return
		case <-time.After(this.timeout * time.Second):
			sock.Close()
			w.WriteHeader(204)
			return
		case <-sock.done:
			// No need to write 200, as it's already written implicitly.
			return
		}

	}

	http.HandleFunc("/lp", LpConnect)
}

func (this *Server) ListenFromRedis() {
	if !viper.GetBool("redis_enabled") {
		return
	}

	subReciever := make(chan []byte, 10000)
	queueReciever := make(chan []byte, 10000)

	_, err := this.Store.redis.Subscribe(subReciever, viper.GetString("redis_message_channel"))
	if err != nil {
		log.Fatal("Couldn't subscribe to redis channel")
	}

	err = this.Store.redis.Poll(queueReciever, viper.GetString("redis_message_queue"))
	if err != nil {
		log.Fatal("Couldn't start polling of redis queue")
	}

	if DEBUG {
		log.Println("LISTENING FOR REDIS MESSAGE")
	}

	var subMessage []byte
	var pollMessage []byte
	for {
		var cmd = new(CommandMsg)

		select {
		case subMessage = <-subReciever:
			err = json.Unmarshal(subMessage, cmd)
		case pollMessage = <-queueReciever:
			err = json.Unmarshal(pollMessage, cmd)
		}

		if err != nil {
			log.Printf("Error decoding JSON: %s", err.Error())
			this.Stats.LogInvalidJSON()
		} else {
			go cmd.FromRedis(this)
		}
	}
}

func (this *Server) ListenForHTTPPings() {
	pingHandler := func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "OK")
	}

	http.HandleFunc("/ping", pingHandler)
}

func (this *Server) SendHeartbeatsPeriodically(period time.Duration) {

	for {
		time.Sleep(period)

		clients := this.Store.Clients()

		for _, user := range clients {
			for _, sock := range user {

				if sock.isWebsocket() {
					if !sock.isClosed() {
						sock.ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(pongWait))
					}
				}

			}
		}
	}
}

func (this *Server) RecordStats(period time.Duration) {
	for {
		this.Stats.LogClientCount(this.Store.memory.clientCount)
		time.Sleep(period)
	}
}

func (this *Server) LogConnectedClientsPeriodically(period time.Duration) {
	for {
		log.Printf("There are %d connected clients\n", this.Store.memory.clientCount)
		time.Sleep(period)
	}
}

func (this *Server) GetAPNSClient(build string) apns.APNSClient {
	return this.apnsProvider(build)
}

func (this *Server) GetGCMClient() GCMClient {
	return this.gcmProvider()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
)

const (
	CompletePrefix = "/authproxy/complete"
	AuthPrefix     = "/authproxy/auth"

	AuthIdKey      = "apid"
	LoginUrlKey    = "loginurl"
	RedirectUrlKey = "redirecturlkey"

	ClosePage = `<html><script>window.close();</script></html>`
)

func main() {
	s := &Server{Cache: NewCache()}
	(&http.Server{
		Addr:    ":8426",
		Handler: s.Handler(),
	}).ListenAndServe()
	for {
	}
}

type Server struct {
	Cache *Cache
}

func (s *Server) NewAuth(wr http.ResponseWriter, req *http.Request) {
	c := make(chan AuthId)
	s.Cache.NewAuthRequests <- &NewAuthRequest{c}
	id := <-c
	loginUrl, err := url.ParseRequestURI(req.URL.Query().Get(LoginUrlKey))
	if err != nil {
		http.Error(wr, err.Error(), http.StatusBadRequest)
		return
	}
	redirectParamName := req.URL.Query().Get(RedirectUrlKey)
	redirectUrl := fmt.Sprintf("%v%v?%v=%v", req.Host, CompletePrefix, AuthIdKey, id)

	cookie := &http.Cookie{Name: AuthIdKey, Value: id.String(), HttpOnly: true}
	http.SetCookie(wr, cookie)
	q := loginUrl.Query()
	q.Set(redirectParamName, redirectUrl)
	loginUrl.RawQuery = q.Encode()
	wr.Write([]byte(loginUrl.String()))
}

func (s *Server) Poll(wr http.ResponseWriter, req *http.Request) {
	cookie, err := req.Cookie(AuthIdKey)
	if err != nil {
		http.Error(wr, err.Error(), http.StatusBadRequest)
		return
	}
	id, err := ParseId(cookie.Value)
	if err != nil {
		http.Error(wr, err.Error(), http.StatusBadRequest)
		return
	}
	c := make(chan *PollResponse)
	s.Cache.PollRequests <- &PollRequest{Id: id, Response: c}
	resp := <-c
	if !resp.Found {
		http.Error(wr, "nope", http.StatusUnauthorized)
		return
	}
	resp.Content.WriteTo(wr)
}

func (s *Server) Auth(wr http.ResponseWriter, req *http.Request) {
	switch req.Method {
	case "POST", "PUT":
		s.NewAuth(wr, req)
	case "GET", "":
		s.Poll(wr, req)
	default:
		http.Error(wr, "huh", http.StatusBadRequest)
	}
}

func (s *Server) Complete(wr http.ResponseWriter, req *http.Request) {
	values := req.URL.Query()
	id, err := ParseId(values.Get(AuthIdKey))
	if err != nil {
		http.Error(wr, err.Error(), http.StatusBadRequest)
		return
	}
	values.Del(AuthIdKey)
	s.Cache.AuthResponses <- &AuthSuccess{
		Id:      id,
		Content: AuthContent(values),
	}
	io.WriteString(wr, ClosePage)
}

func (s *Server) Handler() http.Handler {
	mux := http.NewServeMux()
	mux.HandleFunc(AuthPrefix, s.Auth)
	mux.HandleFunc(CompletePrefix, s.Complete)
	return mux
}
<commit_msg>parse post body<commit_after>package main

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
)

const (
	CompletePrefix = "/authproxy/complete"
	AuthPrefix     = "/authproxy/auth"

	AuthIdKey      = "apid"
	LoginUrlKey    = "loginurl"
	RedirectUrlKey = "redirecturlkey"

	ClosePage = `<html><script>window.close();</script></html>`
)

func main() {
	s := &Server{Cache: NewCache()}
	(&http.Server{
		Addr:    ":8426",
		Handler: s.Handler(),
	}).ListenAndServe()
	for {
	}
}

type Server struct {
	Cache *Cache
}

func (s *Server) NewAuth(wr http.ResponseWriter, req *http.Request) {
	loginUrl, err := url.ParseRequestURI(req.FormValue(LoginUrlKey))
	if err != nil {
		http.Error(wr, err.Error(), http.StatusBadRequest)
		return
	}
	c := make(chan AuthId)
	s.Cache.NewAuthRequests <- &NewAuthRequest{c}
	id := <-c
	redirectParamName := req.URL.Query().Get(RedirectUrlKey)
	redirectUrl := fmt.Sprintf("%v%v?%v=%v", req.Host, CompletePrefix, AuthIdKey, id)

	cookie := &http.Cookie{Name: AuthIdKey, Value: id.String(), HttpOnly: true}
	http.SetCookie(wr, cookie)
	q := loginUrl.Query()
	q.Set(redirectParamName, redirectUrl)
	loginUrl.RawQuery = q.Encode()
	wr.Write([]byte(loginUrl.String()))
}

func (s *Server) Poll(wr http.ResponseWriter, req *http.Request) {
	cookie, err := req.Cookie(AuthIdKey)
	if err != nil {
		http.Error(wr, err.Error(), http.StatusBadRequest)
		return
	}
	id, err := ParseId(cookie.Value)
	if err != nil {
		http.Error(wr, err.Error(), http.StatusBadRequest)
		return
	}
	c := make(chan *PollResponse)
	s.Cache.PollRequests <- &PollRequest{Id: id, Response: c}
	resp := <-c
	if !resp.Found {
		http.Error(wr, "nope", http.StatusUnauthorized)
		return
	}
	resp.Content.WriteTo(wr)
}

func (s *Server) Auth(wr http.ResponseWriter, req *http.Request) {
	switch req.Method {
	case "POST", "PUT":
		s.NewAuth(wr, req)
	case "GET", "":
		s.Poll(wr, req)
	default:
		http.Error(wr, "huh", http.StatusBadRequest)
	}
}

func (s *Server) Complete(wr http.ResponseWriter, req *http.Request) {
	values := req.URL.Query()
	id, err := ParseId(values.Get(AuthIdKey))
	if err != nil {
		http.Error(wr, err.Error(), http.StatusBadRequest)
		return
	}
	values.Del(AuthIdKey)
	s.Cache.AuthResponses <- &AuthSuccess{
		Id:      id,
		Content: AuthContent(values),
	}
	io.WriteString(wr, ClosePage)
}

func (s *Server) Handler() http.Handler {
	mux := http.NewServeMux()
	mux.HandleFunc(AuthPrefix, s.Auth)
	mux.HandleFunc(CompletePrefix, s.Complete)
	return mux
}
<|endoftext|>


<commit_before>package dotweb

import (
	"fmt"
	"github.com/devfeel/dotweb/framework/convert"
	"github.com/devfeel/dotweb/framework/exception"
	"github.com/devfeel/dotweb/framework/json"
	"github.com/devfeel/dotweb/framework/log"
	"github.com/devfeel/dotweb/session"
	"net/http"
	"strings"
	"sync"
	"time"

	"compress/gzip"
	"github.com/devfeel/dotweb/router"
	"golang.org/x/net/websocket"
	"io"
	"net/url"
)

const (
	RouteMethod_GET       = "GET"
	RouteMethod_HEAD      = "HEAD"
	RouteMethod_OPTIONS   = "OPTIONS"
	RouteMethod_POST      = "POST"
	RouteMethod_PUT       = "PUT"
	RouteMethod_PATCH     = "PATCH"
	RouteMethod_DELETE    = "DELETE"
	RouteMethod_HiJack    = "HiJack"
	RouteMethod_WebSocket = "WebSocket"

	DefaultGzipLevel = 9
	gzipScheme       = "gzip"
)

type (
	//HttpModule定义
	HttpModule struct {
		//响应请求时作为 HTTP 执行管线链中的第一个事件发生
		OnBeginRequest func(*HttpContext)
		//响应请求时作为 HTTP 执行管线链中的最后一个事件发生。
		OnEndRequest func(*HttpContext)
	}

	//HttpServer定义
	HttpServer struct {
		router         *router.Router
		dotweb         *Dotweb
		sessionManager *session.SessionManager
		lock_session   *sync.RWMutex
		pool           *pool
		ServerConfig   *ServerConfig
		binder         Binder
	}

	//server global config
	ServerConfig struct {
		EnabledDebug   bool //is enabled debug mode
		EnabledSession bool //is enabled seesion
		EnabledGzip    bool //is enabled gzip
	}

	//pool定义
	pool struct {
		response sync.Pool
		context  sync.Pool
	}
)

func NewServerConfig() *ServerConfig {
	config := &ServerConfig{
		EnabledDebug:   false,
		EnabledSession: false,
	}
	return config
}

// Handle is a function that can be registered to a route to handle HTTP
// requests. Like http.HandlerFunc, but has a third parameter for the values of
// wildcards (variables).
type HttpHandle func(*HttpContext)

func NewHttpServer() *HttpServer {
	server := &HttpServer{
		router: router.New(),
		pool: &pool{
			response: sync.Pool{
				New: func() interface{} {
					return &Response{}
				},
			},
			context: sync.Pool{
				New: func() interface{} {
					return &HttpContext{}
				},
			},
		},
		ServerConfig: NewServerConfig(),
		lock_session: new(sync.RWMutex),
		binder:       &binder{},
	}

	return server
}

//ServeHTTP makes the httprouter implement the http.Handler interface.
func (server *HttpServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	//针对websocket与调试信息特殊处理
	if checkIsWebSocketRequest(req) {
		http.DefaultServeMux.ServeHTTP(w, req)
	} else {
		//设置header信息
		w.Header().Set(HeaderServer, DefaultServerName)
		server.router.ServeHTTP(w, req)
	}
}

//init session manager
func (server *HttpServer) InitSessionManager(config *session.StoreConfig) {
	if server.sessionManager == nil {
		//设置Session
		server.lock_session.Lock()
		if manager, err := session.NewDefaultSessionManager(config); err != nil {
			//panic error with create session manager
			panic(err.Error())
		} else {
			server.sessionManager = manager
		}
		server.lock_session.Unlock()
	}
}

/*
* 关联当前HttpServer实例对应的DotServer实例
 */
func (server *HttpServer) setDotweb(dotweb *Dotweb) {
	server.dotweb = dotweb
}

//get session manager in current httpserver
func (server *HttpServer) GetSessionManager() *session.SessionManager {
	if !server.ServerConfig.EnabledSession {
		return nil
	}
	return server.sessionManager
}

// GET is a shortcut for router.Handle("GET", path, handle)
func (server *HttpServer) GET(path string, handle HttpHandle) {
	server.RegisterRoute(RouteMethod_GET, path, handle)
}

// HEAD is a shortcut for router.Handle("HEAD", path, handle)
func (server *HttpServer) HEAD(path string, handle HttpHandle) {
	server.RegisterRoute(RouteMethod_HEAD, path, handle)
}

// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
func (server *HttpServer) OPTIONS(path string, handle HttpHandle) {
	server.RegisterRoute(RouteMethod_OPTIONS, path, handle)
}

// POST is a shortcut for router.Handle("POST", path, handle)
func (server *HttpServer) POST(path string, handle HttpHandle) {
	server.RegisterRoute(RouteMethod_POST, path, handle)
}

// PUT is a shortcut for router.Handle("PUT", path, handle)
func (server *HttpServer) PUT(path string, handle HttpHandle) {
	server.RegisterRoute(RouteMethod_PUT, path, handle)
}

// PATCH is a shortcut for router.Handle("PATCH", path, handle)
func (server *HttpServer) PATCH(path string, handle HttpHandle) {
	server.RegisterRoute(RouteMethod_PATCH, path, handle)
}

// DELETE is a shortcut for router.Handle("DELETE", path, handle)
func (server *HttpServer) DELETE(path string, handle HttpHandle) {
	server.RegisterRoute(RouteMethod_DELETE, path, handle)
}

// DELETE is a shortcut for router.Handle("DELETE", path, handle)
func (server *HttpServer) HiJack(path string, handle HttpHandle) {
	server.RegisterRoute(RouteMethod_GET, path, handle)
}

// shortcut for router.Handle(httpmethod, path, handle)
// support GET\POST\DELETE\PUT\HEAD\PATCH\OPTIONS\HiJack\WebSocket
func (server *HttpServer) RegisterRoute(routeMethod string, path string, handle HttpHandle) {

	logger.Log("Dotweb:RegisterRoute ["+routeMethod+"] ["+path+"] ["+fmt.Sprintln(handle)+"]", LogTarget_HttpServer, LogLevel_Debug)

	//hijack mode,use get and isHijack = true
	if routeMethod == RouteMethod_HiJack {
		server.router.Handle(RouteMethod_GET, path, server.wrapRouterHandle(handle, true))
		return
	}
	//websocket mode,use default httpserver
	if routeMethod == RouteMethod_WebSocket {
		http.Handle(path, websocket.Handler(server.wrapWebSocketHandle(handle)))
		return
	}

	//GET\POST\DELETE\PUT\HEAD\PATCH\OPTIONS mode
	server.router.Handle(routeMethod, path, server.wrapRouterHandle(handle, false))
	return
}

// ServerFile is a shortcut for router.ServeFiles(path, filepath)
// simple demo:server.ServerFile("/src/*filepath", "/var/www")
func (server *HttpServer) ServerFile(urlpath string, filepath string) {
	server.router.ServeFiles(urlpath, http.Dir(filepath))
}

// WebSocket is a shortcut for websocket.Handler
func (server *HttpServer) WebSocket(path string, handle HttpHandle) {
	server.RegisterRoute(RouteMethod_WebSocket, path, handle)
}

func (server *HttpServer) Binder() Binder {
	return server.binder
}

type LogJson struct {
	RequestUrl string
	HttpHeader string
	HttpBody   string
}

//wrap HttpHandle to httprouter.Handle
func (server *HttpServer) wrapRouterHandle(handle HttpHandle, isHijack bool) router.Handle {
	return func(w http.ResponseWriter, r *http.Request, params router.Params) {
		//get from pool
		res := server.pool.response.Get().(*Response)
		res.Reset(w)
		httpCtx := server.pool.context.Get().(*HttpContext)
		httpCtx.Reset(res, r, server, params)

		//gzip
		if server.ServerConfig.EnabledGzip {
			gw, err := gzip.NewWriterLevel(w, DefaultGzipLevel)
			if err != nil {
				panic("use gzip error -> " + err.Error())
			}
			grw := &gzipResponseWriter{Writer: gw, ResponseWriter: w}
			res.Reset(grw)
			httpCtx.SetHeader(HeaderContentEncoding, gzipScheme)
		}
		//增加状态计数
		GlobalState.AddRequestCount(1)

		//session
		//if exists client-sessionid, use it
		//if not exists client-sessionid, new one
		if server.ServerConfig.EnabledSession {
			sessionId, err := server.GetSessionManager().GetClientSessionID(r)
			if err == nil && sessionId != "" {
				httpCtx.SessionID = sessionId
			} else {
				httpCtx.SessionID = server.GetSessionManager().NewSessionID()
				cookie := http.Cookie{
					Name:  server.sessionManager.CookieName,
					Value: url.QueryEscape(httpCtx.SessionID),
					Path:  "/",
				}
				httpCtx.WriteCookieObj(cookie)
			}
		}

		//hijack处理
		if isHijack {
			_, hijack_err := httpCtx.Hijack()
			if hijack_err != nil {
				//输出内容
				httpCtx.Response.WriteHeader(http.StatusInternalServerError)
				httpCtx.Response.Header().Set(HeaderContentType, CharsetUTF8)
				httpCtx.WriteString(hijack_err.Error())
			}
		}

		startTime := time.Now()
		defer func() {
			var errmsg string
			if err := recover(); err != nil {
				errmsg = exception.CatchError("httpserver::RouterHandle", LogTarget_HttpServer, err)

				//默认异常处理
				if server.dotweb.ExceptionHandler != nil {
					server.dotweb.ExceptionHandler(httpCtx, err)
				}

				//记录访问日志
				headinfo := fmt.Sprintln(httpCtx.Response.Header)
				logJson := LogJson{
					RequestUrl: httpCtx.Request.RequestURI,
					HttpHeader: headinfo,
					HttpBody:   errmsg,
				}
				logString := jsonutil.GetJsonString(logJson)
				logger.Log(logString, LogTarget_HttpServer, LogLevel_Error)

				//增加错误计数
				GlobalState.AddErrorCount(1)
			}
			timetaken := int64(time.Now().Sub(startTime) / time.Millisecond)
			//HttpServer Logging
			logger.Log(httpCtx.Url()+" "+logString(httpCtx, timetaken), LogTarget_HttpRequest, LogLevel_Debug)

			if server.ServerConfig.EnabledGzip {
				var w io.Writer
				w = res.Writer().(*gzipResponseWriter).Writer
				w.(*gzip.Writer).Close()
			}
			// Return to pool
			server.pool.response.Put(res)
			server.pool.context.Put(httpCtx)
		}()

		//处理前置Module集合
		for _, module := range server.dotweb.Modules {
			if module.OnBeginRequest != nil {
				module.OnBeginRequest(httpCtx)
				//if current module set HttpContext.End,stop this request
				if httpCtx.isEnd {
					return
				}
			}
		}

		//处理用户handle
		handle(httpCtx)
		if httpCtx.isEnd {
			//if current module set HttpContext.End,stop this request
			return
		}

		//处理后置Module集合
		for _, module := range server.dotweb.Modules {
			if module.OnEndRequest != nil {
				module.OnEndRequest(httpCtx)
				if httpCtx.isEnd {
					//if current module set HttpContext.End,stop this request
					return
				}
			}
		}

	}
}

//wrap HttpHandle to websocket.Handle
func (server *HttpServer) wrapWebSocketHandle(handle HttpHandle) websocket.Handler {
	return func(ws *websocket.Conn) {
		//get from pool
		httpCtx := server.pool.context.Get().(*HttpContext)
		httpCtx.Reset(nil, ws.Request(), server, nil)
		httpCtx.WebSocket = &WebSocket{
			Conn: ws,
		}
		httpCtx.IsWebSocket = true

		startTime := time.Now()
		defer func() {
			var errmsg string
			if err := recover(); err != nil {
				errmsg = exception.CatchError("httpserver::WebsocketHandle", LogTarget_HttpServer, err)

				//记录访问日志
				headinfo := fmt.Sprintln(httpCtx.WebSocket.Conn.Request().Header)
				logJson := LogJson{
					RequestUrl: httpCtx.WebSocket.Conn.Request().RequestURI,
					HttpHeader: headinfo,
					HttpBody:   errmsg,
				}
				logString := jsonutil.GetJsonString(logJson)
				logger.Log(logString, LogTarget_HttpServer, LogLevel_Error)

				//增加错误计数
				GlobalState.AddErrorCount(1)
			}
			timetaken := int64(time.Now().Sub(startTime) / time.Millisecond)
			//HttpServer Logging
			logger.Log(httpCtx.Url()+" "+logString(httpCtx, timetaken), LogTarget_HttpRequest, LogLevel_Debug)

			// Return to pool
			server.pool.context.Put(httpCtx)
		}()

		handle(httpCtx)

		//增加状态计数
		GlobalState.AddRequestCount(1)
	}
}

//get default log string
func logString(ctx *HttpContext, timetaken int64) string {

	var reqbytelen, resbytelen, method, proto, status, userip string
	if !ctx.IsWebSocket {
		reqbytelen = convert.Int642String(ctx.Request.ContentLength)
		resbytelen = convert.Int642String(ctx.Response.Size)
		method = ctx.Request.Method
		proto = ctx.Request.Proto
		status = convert.Int2String(ctx.Response.Status)
		userip = ctx.RemoteIP()
	} else {
		reqbytelen = convert.Int642String(ctx.Request.ContentLength)
		resbytelen = "0"
		method = ctx.Request.Method
		proto = ctx.Request.Proto
		status = "0"
		userip = ctx.RemoteIP()
	}

	log := method + " "
	log += userip + " "
	log += proto + " "
	log += status + " "
	log += reqbytelen + " "
	log += resbytelen + " "
	log += convert.Int642String(timetaken)

	return log
}

//check request is the websocket request
func checkIsWebSocketRequest(req *http.Request) bool {
	if req.Header.Get("Connection") == "Upgrade" {
		return true
	}
	return false
}

//check request is startwith /debug/
func checkIsDebugRequest(req *http.Request) bool {
	if strings.Index(req.RequestURI, "/debug/") == 0 {
		return true
	}
	return false
}
<commit_msg>router log remove handler addr field<commit_after>package dotweb

import (
	"fmt"
	"github.com/devfeel/dotweb/framework/convert"
	"github.com/devfeel/dotweb/framework/exception"
	"github.com/devfeel/dotweb/framework/json"
	"github.com/devfeel/dotweb/framework/log"
	"github.com/devfeel/dotweb/session"
	"net/http"
	"strings"
	"sync"
	"time"

	"compress/gzip"
	"github.com/devfeel/dotweb/router"
	"golang.org/x/net/websocket"
	"io"
	"net/url"
)

const (
	RouteMethod_GET       = "GET"
	RouteMethod_HEAD      = "HEAD"
	RouteMethod_OPTIONS   = "OPTIONS"
	RouteMethod_POST      = "POST"
	RouteMethod_PUT       = "PUT"
	RouteMethod_PATCH     = "PATCH"
	RouteMethod_DELETE    = "DELETE"
	RouteMethod_HiJack    = "HiJack"
	RouteMethod_WebSocket = "WebSocket"

	DefaultGzipLevel = 9
	gzipScheme       = "gzip"
)

type (
	//HttpModule定义
	HttpModule struct {
		//响应请求时作为 HTTP 执行管线链中的第一个事件发生
		OnBeginRequest func(*HttpContext)
		//响应请求时作为 HTTP 执行管线链中的最后一个事件发生。
		OnEndRequest func(*HttpContext)
	}

	//HttpServer定义
	HttpServer struct {
		router         *router.Router
		dotweb         *Dotweb
		sessionManager *session.SessionManager
		lock_session   *sync.RWMutex
		pool           *pool
		ServerConfig   *ServerConfig
		binder         Binder
	}

	//server global config
	ServerConfig struct {
		EnabledDebug   bool //is enabled debug mode
		EnabledSession bool //is enabled seesion
		EnabledGzip    bool //is enabled gzip
	}

	//pool定义
	pool struct {
		response sync.Pool
		context  sync.Pool
	}
)

func NewServerConfig() *ServerConfig {
	config := &ServerConfig{
		EnabledDebug:   false,
		EnabledSession: false,
	}
	return config
}

// Handle is a function that can be registered to a route to handle HTTP
// requests. Like http.HandlerFunc, but has a third parameter for the values of
// wildcards (variables).
type HttpHandle func(*HttpContext)

func NewHttpServer() *HttpServer {
	server := &HttpServer{
		router: router.New(),
		pool: &pool{
			response: sync.Pool{
				New: func() interface{} {
					return &Response{}
				},
			},
			context: sync.Pool{
				New: func() interface{} {
					return &HttpContext{}
				},
			},
		},
		ServerConfig: NewServerConfig(),
		lock_session: new(sync.RWMutex),
		binder:       &binder{},
	}

	return server
}

//ServeHTTP makes the httprouter implement the http.Handler interface.
func (server *HttpServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	//针对websocket与调试信息特殊处理
	if checkIsWebSocketRequest(req) {
		http.DefaultServeMux.ServeHTTP(w, req)
	} else {
		//设置header信息
		w.Header().Set(HeaderServer, DefaultServerName)
		server.router.ServeHTTP(w, req)
	}
}

//init session manager
func (server *HttpServer) InitSessionManager(config *session.StoreConfig) {
	if server.sessionManager == nil {
		//设置Session
		server.lock_session.Lock()
		if manager, err := session.NewDefaultSessionManager(config); err != nil {
			//panic error with create session manager
			panic(err.Error())
		} else {
			server.sessionManager = manager
		}
		server.lock_session.Unlock()
	}
}

/*
* 关联当前HttpServer实例对应的DotServer实例
 */
func (server *HttpServer) setDotweb(dotweb *Dotweb) {
	server.dotweb = dotweb
}

//get session manager in current httpserver
func (server *HttpServer) GetSessionManager() *session.SessionManager {
	if !server.ServerConfig.EnabledSession {
		return nil
	}
	return server.sessionManager
}

// GET is a shortcut for router.Handle("GET", path, handle)
func (server *HttpServer) GET(path string, handle HttpHandle) {
	server.RegisterRoute(RouteMethod_GET, path, handle)
}

// HEAD is a shortcut for router.Handle("HEAD", path, handle)
func (server *HttpServer) HEAD(path string, handle HttpHandle) {
	server.RegisterRoute(RouteMethod_HEAD, path, handle)
}

// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
func (server *HttpServer) OPTIONS(path string, handle HttpHandle) {
	server.RegisterRoute(RouteMethod_OPTIONS, path, handle)
}

// POST is a shortcut for router.Handle("POST", path, handle)
func (server *HttpServer) POST(path string, handle HttpHandle) {
	server.RegisterRoute(RouteMethod_POST, path, handle)
}

// PUT is a shortcut for router.Handle("PUT", path, handle)
func (server *HttpServer) PUT(path string, handle HttpHandle) {
	server.RegisterRoute(RouteMethod_PUT, path, handle)
}

// PATCH is a shortcut for router.Handle("PATCH", path, handle)
func (server *HttpServer) PATCH(path string, handle HttpHandle) {
	server.RegisterRoute(RouteMethod_PATCH, path, handle)
}

// DELETE is a shortcut for router.Handle("DELETE", path, handle)
func (server *HttpServer) DELETE(path string, handle HttpHandle) {
	server.RegisterRoute(RouteMethod_DELETE, path, handle)
}

// DELETE is a shortcut for router.Handle("DELETE", path, handle)
func (server *HttpServer) HiJack(path string, handle HttpHandle) {
	server.RegisterRoute(RouteMethod_GET, path, handle)
}

// shortcut for router.Handle(httpmethod, path, handle)
// support GET\POST\DELETE\PUT\HEAD\PATCH\OPTIONS\HiJack\WebSocket
func (server *HttpServer) RegisterRoute(routeMethod string, path string, handle HttpHandle) {

	logger.Log("Dotweb:RegisterRoute ["+routeMethod+"] ["+path+"]", LogTarget_HttpServer, LogLevel_Debug)

	//hijack mode,use get and isHijack = true
	if routeMethod == RouteMethod_HiJack {
		server.router.Handle(RouteMethod_GET, path, server.wrapRouterHandle(handle, true))
		return
	}
	//websocket mode,use default httpserver
	if routeMethod == RouteMethod_WebSocket {
		http.Handle(path, websocket.Handler(server.wrapWebSocketHandle(handle)))
		return
	}

	//GET\POST\DELETE\PUT\HEAD\PATCH\OPTIONS mode
	server.router.Handle(routeMethod, path, server.wrapRouterHandle(handle, false))
	return
}

// ServerFile is a shortcut for router.ServeFiles(path, filepath)
// simple demo:server.ServerFile("/src/*filepath", "/var/www")
func (server *HttpServer) ServerFile(urlpath string, filepath string) {
	server.router.ServeFiles(urlpath, http.Dir(filepath))
}

// WebSocket is a shortcut for websocket.Handler
func (server *HttpServer) WebSocket(path string, handle HttpHandle) {
	server.RegisterRoute(RouteMethod_WebSocket, path, handle)
}

func (server *HttpServer) Binder() Binder {
	return server.binder
}

type LogJson struct {
	RequestUrl string
	HttpHeader string
	HttpBody   string
}

//wrap HttpHandle to httprouter.Handle
func (server *HttpServer) wrapRouterHandle(handle HttpHandle, isHijack bool) router.Handle {
	return func(w http.ResponseWriter, r *http.Request, params router.Params) {
		//get from pool
		res := server.pool.response.Get().(*Response)
		res.Reset(w)
		httpCtx := server.pool.context.Get().(*HttpContext)
		httpCtx.Reset(res, r, server, params)

		//gzip
		if server.ServerConfig.EnabledGzip {
			gw, err := gzip.NewWriterLevel(w, DefaultGzipLevel)
			if err != nil {
				panic("use gzip error -> " + err.Error())
			}
			grw := &gzipResponseWriter{Writer: gw, ResponseWriter: w}
			res.Reset(grw)
			httpCtx.SetHeader(HeaderContentEncoding, gzipScheme)
		}
		//增加状态计数
		GlobalState.AddRequestCount(1)

		//session
		//if exists client-sessionid, use it
		//if not exists client-sessionid, new one
		if server.ServerConfig.EnabledSession {
			sessionId, err := server.GetSessionManager().GetClientSessionID(r)
			if err == nil && sessionId != "" {
				httpCtx.SessionID = sessionId
			} else {
				httpCtx.SessionID = server.GetSessionManager().NewSessionID()
				cookie := http.Cookie{
					Name:  server.sessionManager.CookieName,
					Value: url.QueryEscape(httpCtx.SessionID),
					Path:  "/",
				}
				httpCtx.WriteCookieObj(cookie)
			}
		}

		//hijack处理
		if isHijack {
			_, hijack_err := httpCtx.Hijack()
			if hijack_err != nil {
				//输出内容
				httpCtx.Response.WriteHeader(http.StatusInternalServerError)
				httpCtx.Response.Header().Set(HeaderContentType, CharsetUTF8)
				httpCtx.WriteString(hijack_err.Error())
			}
		}

		startTime := time.Now()
		defer func() {
			var errmsg string
			if err := recover(); err != nil {
				errmsg = exception.CatchError("httpserver::RouterHandle", LogTarget_HttpServer, err)

				//默认异常处理
				if server.dotweb.ExceptionHandler != nil {
					server.dotweb.ExceptionHandler(httpCtx, err)
				}

				//记录访问日志
				headinfo := fmt.Sprintln(httpCtx.Response.Header)
				logJson := LogJson{
					RequestUrl: httpCtx.Request.RequestURI,
					HttpHeader: headinfo,
					HttpBody:   errmsg,
				}
				logString := jsonutil.GetJsonString(logJson)
				logger.Log(logString, LogTarget_HttpServer, LogLevel_Error)

				//增加错误计数
				GlobalState.AddErrorCount(1)
			}
			timetaken := int64(time.Now().Sub(startTime) / time.Millisecond)
			//HttpServer Logging
			logger.Log(httpCtx.Url()+" "+logString(httpCtx, timetaken), LogTarget_HttpRequest, LogLevel_Debug)

			if server.ServerConfig.EnabledGzip {
				var w io.Writer
				w = res.Writer().(*gzipResponseWriter).Writer
				w.(*gzip.Writer).Close()
			}
			// Return to pool
			server.pool.response.Put(res)
			server.pool.context.Put(httpCtx)
		}()

		//处理前置Module集合
		for _, module := range server.dotweb.Modules {
			if module.OnBeginRequest != nil {
				module.OnBeginRequest(httpCtx)
				//if current module set HttpContext.End,stop this request
				if httpCtx.isEnd {
					return
				}
			}
		}

		//处理用户handle
		handle(httpCtx)
		if httpCtx.isEnd {
			//if current module set HttpContext.End,stop this request
			return
		}

		//处理后置Module集合
		for _, module := range server.dotweb.Modules {
			if module.OnEndRequest != nil {
				module.OnEndRequest(httpCtx)
				if httpCtx.isEnd {
					//if current module set HttpContext.End,stop this request
					return
				}
			}
		}

	}
}

//wrap HttpHandle to websocket.Handle
func (server *HttpServer) wrapWebSocketHandle(handle HttpHandle) websocket.Handler {
	return func(ws *websocket.Conn) {
		//get from pool
		httpCtx := server.pool.context.Get().(*HttpContext)
		httpCtx.Reset(nil, ws.Request(), server, nil)
		httpCtx.WebSocket = &WebSocket{
			Conn: ws,
		}
		httpCtx.IsWebSocket = true

		startTime := time.Now()
		defer func() {
			var errmsg string
			if err := recover(); err != nil {
				errmsg = exception.CatchError("httpserver::WebsocketHandle", LogTarget_HttpServer, err)

				//记录访问日志
				headinfo := fmt.Sprintln(httpCtx.WebSocket.Conn.Request().Header)
				logJson := LogJson{
					RequestUrl: httpCtx.WebSocket.Conn.Request().RequestURI,
					HttpHeader: headinfo,
					HttpBody:   errmsg,
				}
				logString := jsonutil.GetJsonString(logJson)
				logger.Log(logString, LogTarget_HttpServer, LogLevel_Error)

				//增加错误计数
				GlobalState.AddErrorCount(1)
			}
			timetaken := int64(time.Now().Sub(startTime) / time.Millisecond)
			//HttpServer Logging
			logger.Log(httpCtx.Url()+" "+logString(httpCtx, timetaken), LogTarget_HttpRequest, LogLevel_Debug)

			// Return to pool
			server.pool.context.Put(httpCtx)
		}()

		handle(httpCtx)

		//增加状态计数
		GlobalState.AddRequestCount(1)
	}
}

//get default log string
func logString(ctx *HttpContext, timetaken int64) string {

	var reqbytelen, resbytelen, method, proto, status, userip string
	if !ctx.IsWebSocket {
		reqbytelen = convert.Int642String(ctx.Request.ContentLength)
		resbytelen = convert.Int642String(ctx.Response.Size)
		method = ctx.Request.Method
		proto = ctx.Request.Proto
		status = convert.Int2String(ctx.Response.Status)
		userip = ctx.RemoteIP()
	} else {
		reqbytelen = convert.Int642String(ctx.Request.ContentLength)
		resbytelen = "0"
		method = ctx.Request.Method
		proto = ctx.Request.Proto
		status = "0"
		userip = ctx.RemoteIP()
	}

	log := method + " "
	log += userip + " "
	log += proto + " "
	log += status + " "
	log += reqbytelen + " "
	log += resbytelen + " "
	log += convert.Int642String(timetaken)

	return log
}

//check request is the websocket request
func checkIsWebSocketRequest(req *http.Request) bool {
	if req.Header.Get("Connection") == "Upgrade" {
		return true
	}
	return false
}

//check request is startwith /debug/
func checkIsDebugRequest(req *http.Request) bool {
	if strings.Index(req.RequestURI, "/debug/") == 0 {
		return true
	}
	return false
}
<|endoftext|>