Files
lysenko/vendor/github.com/tcolgate/hugot/mux.go
2016-10-14 23:42:51 +01:00

346 lines
8.0 KiB
Go

// Copyright (c) 2016 Tristan Colgate-McFarlane
//
// This file is part of hugot.
//
// hugot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// hugot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with hugot. If not, see <http://www.gnu.org/licenses/>.
package hugot
import (
"errors"
"fmt"
"net/http"
"net/url"
"regexp"
"sync"
"github.com/golang/glog"
"context"
)
func init() {
DefaultMux = NewMux("hugot", "")
http.Handle("/hugot", DefaultMux)
http.Handle("/hugot/", DefaultMux)
}
// Mux is a Handler that multiplexes messages to a set of Command, Hears, and
// Raw handlers.
type Mux struct {
name string
desc string
burl *url.URL
*sync.RWMutex
hndlrs []Handler // All the handlers
rhndlrs []RawHandler // Raw handlers
bghndlrs []BackgroundHandler // Long running background handlers
whhndlrs map[string]WebHookHandler // WebHooks
hears map[*regexp.Regexp][]HearsHandler // Hearing handlers
cmds *CommandSet // Command handlers
httpm *http.ServeMux // http Mux
}
// DefaultMux is a default Mux instance, http Handlers will be added to
// http.DefaultServeMux
var DefaultMux *Mux
// NewMux creates a new Mux.
func NewMux(name, desc string) *Mux {
mx := &Mux{
name: name,
desc: desc,
RWMutex: &sync.RWMutex{},
rhndlrs: []RawHandler{},
bghndlrs: []BackgroundHandler{},
whhndlrs: map[string]WebHookHandler{},
hears: map[*regexp.Regexp][]HearsHandler{},
cmds: NewCommandSet(),
httpm: http.NewServeMux(),
burl: &url.URL{Path: "/" + name},
}
mx.HandleCommand(&muxHelp{mx})
return mx
}
// Describe implements the Describe method of Handler for
// the Mux
func (mx *Mux) Describe() (string, string) {
return mx.name, mx.desc
}
// URL returns the base URL for the default Mux
func URL() *url.URL {
return DefaultMux.URL()
}
// URL returns the base URL for this Mux
func (mx *Mux) URL() *url.URL {
mx.RLock()
defer mx.RUnlock()
return mx.url()
}
func (mx *Mux) url() *url.URL {
return mx.burl
}
// SetURL sets the base URL for web hooks.
func SetURL(b *url.URL) {
if b.Path != "" {
panic(errors.New("Can't set URL with path at the moment, sorry"))
}
DefaultMux.SetURL(b)
}
// SetURL sets the base URL for this mux's web hooks.
func (mx *Mux) SetURL(b *url.URL) {
mx.Lock()
defer mx.Unlock()
mx.burl = b
for _, h := range mx.whhndlrs {
n, _ := h.Describe()
p := fmt.Sprintf("%s/%s/%s/", b.Path, mx.name, n)
nu := *b
nu.Path = p
h.SetURL(&nu)
}
}
// StartBackground starts any registered background handlers.
func (mx *Mux) StartBackground(ctx context.Context, w ResponseWriter) {
mx.Lock()
defer mx.Unlock()
for _, h := range mx.bghndlrs {
go runBackgroundHandler(ctx, h, w.Copy())
}
}
// SetAdapter sets the adapter on all the webhook of this mux.
func (mx *Mux) SetAdapter(a Adapter) {
mx.Lock()
defer mx.Unlock()
for _, wh := range mx.whhndlrs {
wh.SetAdapter(a)
}
}
// ProcessMessage implements the Handler interface. Message will first be passed to
// any registered RawHandlers. If the message has been deemed, by the Adapter
// to have been sent directly to the bot, any comand handlers will be processed.
// Then, if appropriate, the message will be matched against any Hears patterns
// and all matching Heard functions will then be called.
// Any unrecognized errors from the Command handlers will be passed back to the
// user that sent us the message.
func (mx *Mux) ProcessMessage(ctx context.Context, w ResponseWriter, m *Message) error {
mx.RLock()
defer mx.RUnlock()
var err error
// We run all raw message handlers
for _, rh := range mx.rhndlrs {
mc := *m
go rh.ProcessMessage(ctx, w, &mc)
}
if m.ToBot {
err = mx.cmds.NextCommand(ctx, w, m)
}
if err == ErrSkipHears {
return nil
}
for _, hhs := range mx.hears {
for _, hh := range hhs {
mc := *m
if runHearsHandler(ctx, hh, w, &mc) {
err = nil
}
}
}
if err != nil {
fmt.Fprintf(w, "error, %s", err.Error())
}
return nil
}
// Handle adds the provided handler to the DefaultMux
func Handle(h Handler) error {
return DefaultMux.Handle(h)
}
// Handle adds a generic handler that supports one or more of the handler
// types. WARNING: This may be removed in the future. Prefer to
// the specific Add*Handler methods.
func (mx *Mux) Handle(h Handler) error {
var used bool
if h, ok := h.(RawHandler); ok {
mx.HandleRaw(h)
used = true
}
if h, ok := h.(BackgroundHandler); ok {
mx.HandleBackground(h)
used = true
}
if h, ok := h.(CommandHandler); ok {
mx.HandleCommand(h)
used = true
}
if h, ok := h.(HearsHandler); ok {
mx.HandleHears(h)
used = true
}
if h, ok := h.(WebHookHandler); ok {
mx.HandleHTTP(h)
used = true
}
mx.Lock()
defer mx.Unlock()
if !used {
return fmt.Errorf("Don't know how to use %T as a handler", h)
}
mx.hndlrs = append(mx.hndlrs, h)
return nil
}
// HandleRaw adds the provided handler to the DefaultMux
func HandleRaw(h RawHandler) error {
return DefaultMux.HandleRaw(h)
}
// HandleRaw adds the provided handler to the Mux. All
// messages sent to the mux will be forwarded to this handler.
func (mx *Mux) HandleRaw(h RawHandler) error {
mx.Lock()
defer mx.Unlock()
if h, ok := h.(RawHandler); ok {
mx.rhndlrs = append(mx.rhndlrs, h)
}
return nil
}
// HandleBackground adds the provided handler to the DefaultMux
func HandleBackground(h BackgroundHandler) error {
return DefaultMux.HandleBackground(h)
}
// HandleBackground adds the provided handler to the Mux. It
// will be started with the Mux is started.
func (mx *Mux) HandleBackground(h BackgroundHandler) error {
mx.Lock()
defer mx.Unlock()
//name, _ := h.Describe()
mx.bghndlrs = append(mx.bghndlrs, h)
return nil
}
// HandleHears adds the provided handler to the DefaultMux
func HandleHears(h HearsHandler) error {
return DefaultMux.HandleHears(h)
}
// HandleHears adds the provided handler to the mux. All
// messages matching the Hears patterns will be forwarded to
// the handler.
func (mx *Mux) HandleHears(h HearsHandler) error {
mx.Lock()
defer mx.Unlock()
r := h.Hears()
mx.hears[r] = append(mx.hears[r], h)
return nil
}
// HandleCommand adds the provided handler to the DefaultMux
func HandleCommand(h CommandHandler) {
DefaultMux.HandleCommand(h)
}
// HandleCommand adds the provided handler to the mux.
func (mx *Mux) HandleCommand(h CommandHandler) {
mx.Lock()
defer mx.Unlock()
mx.cmds.AddCommandHandler(h)
}
type webHookBridge struct {
nh http.Handler
}
func (whb *webHookBridge) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if glog.V(2) {
glog.Infof("webHookBridge ServeHTTP %v %s\n", *whb, *r)
}
whb.nh.ServeHTTP(w, r)
}
// HandleHTTP adds the provided handler to the DefaultMux
func HandleHTTP(h WebHookHandler) {
DefaultMux.HandleHTTP(h)
}
// HandleHTTP registers h as a WebHook handler. The name
// of the Mux, and the name of the handler are used to
// construct a unique URL that can be used to send web
// requests to this handler
func (mx *Mux) HandleHTTP(h WebHookHandler) {
mx.Lock()
defer mx.Unlock()
n, _ := h.Describe()
p := fmt.Sprintf("/%s/%s", mx.name, n)
mx.httpm.Handle(p, h)
mx.httpm.Handle(p+"/", h)
if glog.V(2) {
glog.Infof("registering %v at %s, on %v\n", h, p, mx.httpm)
}
mx.whhndlrs[n] = h
mu := mx.url()
nu := *mu
nu.Path = fmt.Sprintf("/%s/%s/", mx.name, n)
h.SetURL(&nu)
}
// ServeHTTP iplements http.ServeHTTP for a Mux to allow it to
// act as a web server.
func (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if glog.V(2) {
glog.Infof("Mux ServeHTTP %s\n", *r)
}
mx.httpm.ServeHTTP(w, r)
}