Compare commits

..

4 Commits

Author SHA1 Message Date
Dean Karn c93876b3e9 Merge pull request #10 from go-playground/add-handler-support
Add handler support
2017-04-27 19:24:15 -04:00
Dean Karn 0926003ddf .travis.yml tweak for gopkg.in 2017-04-27 19:19:51 -04:00
Dean Karn 58dd13d367 Add Handler function
Handler(...) return an http.Handler of the webhook for use within your own Mux
2017-04-27 17:57:51 -04:00
Dean Karn 4fa39fdfab Add .travis.yml config 2017-04-27 17:40:55 -04:00
5 changed files with 164 additions and 194 deletions
+39
View File
@@ -0,0 +1,39 @@
language: go
go:
- 1.7.5
- 1.8.1
- tip
matrix:
allow_failures:
- go: tip
notifications:
email:
recipients: dean.karn@gmail.com
on_success: change
on_failure: always
before_install:
- go get -u github.com/go-playground/overalls
- go get -u github.com/mattn/goveralls
- go get -u golang.org/x/tools/cmd/cover
- go get -u github.com/golang/lint/golint
- go get -u github.com/gordonklaus/ineffassign
- mkdir -p $GOPATH/src/gopkg.in
- ln -s $GOPATH/src/github.com/$TRAVIS_REPO_SLUG $GOPATH/src/gopkg.in/webhooks.v2
- ln -s $GOPATH/src/github.com/$TRAVIS_REPO_SLUG $GOPATH/src/gopkg.in/webhooks.v3
before_script:
- go vet ./...
script:
- gofmt -d -s .
- golint ./...
- ineffassign ./
- go test -v ./...
- go test -race
after_success: |
[ $TRAVIS_GO_VERSION = 1.8.1 ] &&
overalls -project="github.com/go-playground/webhooks" -covermode=count -ignore=.git,examples -debug &&
goveralls -coverprofile=overalls.coverprofile -service travis-ci -repotoken $COVERALLS_TOKEN
+2 -2
View File
@@ -1,7 +1,7 @@
Library webhooks Library webhooks
================ ================
<img align="right" src="https://raw.githubusercontent.com/go-playground/webhooks/v3/logo.png">![Project status](https://img.shields.io/badge/version-3.0.0-green.svg) <img align="right" src="https://raw.githubusercontent.com/go-playground/webhooks/v3/logo.png">![Project status](https://img.shields.io/badge/version-3.1.0-green.svg)
[![Build Status](https://semaphoreci.com/api/v1/joeybloggs/webhooks/branches/v3/badge.svg)](https://semaphoreci.com/joeybloggs/webhooks) [![Build Status](https://travis-ci.org/go-playground/webhooks.svg?branch=v3)](https://travis-ci.org/go-playground/webhooks)
[![Coverage Status](https://coveralls.io/repos/go-playground/webhooks/badge.svg?branch=v3&service=github)](https://coveralls.io/github/go-playground/webhooks?branch=v3) [![Coverage Status](https://coveralls.io/repos/go-playground/webhooks/badge.svg?branch=v3&service=github)](https://coveralls.io/github/go-playground/webhooks?branch=v3)
[![Go Report Card](https://goreportcard.com/badge/go-playground/webhooks)](https://goreportcard.com/report/go-playground/webhooks) [![Go Report Card](https://goreportcard.com/badge/go-playground/webhooks)](https://goreportcard.com/report/go-playground/webhooks)
[![GoDoc](https://godoc.org/gopkg.in/go-playground/webhooks.v3?status.svg)](https://godoc.org/gopkg.in/go-playground/webhooks.v3) [![GoDoc](https://godoc.org/gopkg.in/go-playground/webhooks.v3?status.svg)](https://godoc.org/gopkg.in/go-playground/webhooks.v3)
+58 -170
View File
@@ -6,7 +6,6 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"log"
"net/http" "net/http"
"gopkg.in/go-playground/webhooks.v3" "gopkg.in/go-playground/webhooks.v3"
@@ -98,7 +97,6 @@ func (hook Webhook) RegisterEvents(fn webhooks.ProcessPayloadFunc, events ...Eve
// ParsePayload parses and verifies the payload and fires off the mapped function, if it exists. // ParsePayload parses and verifies the payload and fires off the mapped function, if it exists.
func (hook Webhook) ParsePayload(w http.ResponseWriter, r *http.Request) { func (hook Webhook) ParsePayload(w http.ResponseWriter, r *http.Request) {
log.Println("Gettting X-GitHub-Event")
event := r.Header.Get("X-GitHub-Event") event := r.Header.Get("X-GitHub-Event")
if len(event) == 0 { if len(event) == 0 {
http.Error(w, "400 Bad Request - Missing X-GitHub-Event Header", http.StatusBadRequest) http.Error(w, "400 Bad Request - Missing X-GitHub-Event Header", http.StatusBadRequest)
@@ -107,27 +105,21 @@ func (hook Webhook) ParsePayload(w http.ResponseWriter, r *http.Request) {
gitHubEvent := Event(event) gitHubEvent := Event(event)
log.Println("Looking for Hook:", gitHubEvent)
fn, ok := hook.eventFuncs[gitHubEvent] fn, ok := hook.eventFuncs[gitHubEvent]
// if no event registered // if no event registered
if !ok { if !ok {
return return
} }
log.Println("READING PAYLOAD FROM BODY")
payload, err := ioutil.ReadAll(r.Body) payload, err := ioutil.ReadAll(r.Body)
if err != nil || len(payload) == 0 { if err != nil || len(payload) == 0 {
http.Error(w, "Error reading Body", http.StatusInternalServerError) http.Error(w, "Error reading Body", http.StatusInternalServerError)
return return
} }
log.Println("Checking GitHub secret")
// If we have a Secret set, we should check the MAC // If we have a Secret set, we should check the MAC
if len(hook.secret) > 0 { if len(hook.secret) > 0 {
log.Println("Get GitHub signature")
signature := r.Header.Get("X-Hub-Signature") signature := r.Header.Get("X-Hub-Signature")
if len(signature) == 0 { if len(signature) == 0 {
@@ -140,245 +132,141 @@ func (hook Webhook) ParsePayload(w http.ResponseWriter, r *http.Request) {
expectedMAC := hex.EncodeToString(mac.Sum(nil)) expectedMAC := hex.EncodeToString(mac.Sum(nil))
log.Println("Checking HMAC Equality")
if !hmac.Equal([]byte(signature[5:]), []byte(expectedMAC)) { if !hmac.Equal([]byte(signature[5:]), []byte(expectedMAC)) {
http.Error(w, "403 Forbidden - HMAC verification failed", http.StatusForbidden) http.Error(w, "403 Forbidden - HMAC verification failed", http.StatusForbidden)
return return
} }
log.Println("HMAC Equal")
} }
// Make headers available to ProcessPayloadFunc as a webhooks type // Make headers available to ProcessPayloadFunc as a webhooks type
hd := webhooks.Header(r.Header) hd := webhooks.Header(r.Header)
var pl interface{}
log.Println("Unmarshal based on GitHub event:", gitHubEvent)
switch gitHubEvent { switch gitHubEvent {
case CommitCommentEvent: case CommitCommentEvent:
var cc CommitCommentPayload var cc CommitCommentPayload
json.Unmarshal([]byte(payload), &cc)
err = json.Unmarshal([]byte(payload), &cc) hook.runProcessPayloadFunc(fn, cc, hd)
pl = cc
case CreateEvent: case CreateEvent:
var c CreatePayload var c CreatePayload
json.Unmarshal([]byte(payload), &c)
err = json.Unmarshal([]byte(payload), &c) hook.runProcessPayloadFunc(fn, c, hd)
pl = c
case DeleteEvent: case DeleteEvent:
var d DeletePayload var d DeletePayload
json.Unmarshal([]byte(payload), &d) json.Unmarshal([]byte(payload), &d)
hook.runProcessPayloadFunc(fn, d, hd) hook.runProcessPayloadFunc(fn, d, hd)
case DeploymentEvent: case DeploymentEvent:
var d DeploymentPayload var d DeploymentPayload
json.Unmarshal([]byte(payload), &d)
err = json.Unmarshal([]byte(payload), &d) hook.runProcessPayloadFunc(fn, d, hd)
pl = d
case DeploymentStatusEvent: case DeploymentStatusEvent:
var d DeploymentStatusPayload var d DeploymentStatusPayload
json.Unmarshal([]byte(payload), &d)
err = json.Unmarshal([]byte(payload), &d) hook.runProcessPayloadFunc(fn, d, hd)
pl = d
case ForkEvent: case ForkEvent:
var f ForkPayload var f ForkPayload
json.Unmarshal([]byte(payload), &f)
err = json.Unmarshal([]byte(payload), &f) hook.runProcessPayloadFunc(fn, f, hd)
pl = f
case GollumEvent: case GollumEvent:
var g GollumPayload var g GollumPayload
json.Unmarshal([]byte(payload), &g)
err = json.Unmarshal([]byte(payload), &g) hook.runProcessPayloadFunc(fn, g, hd)
pl = g
case IssueCommentEvent: case IssueCommentEvent:
var i IssueCommentPayload var i IssueCommentPayload
json.Unmarshal([]byte(payload), &i)
err = json.Unmarshal([]byte(payload), &i) hook.runProcessPayloadFunc(fn, i, hd)
pl = i
case IssuesEvent: case IssuesEvent:
var i IssuesPayload var i IssuesPayload
json.Unmarshal([]byte(payload), &i)
err = json.Unmarshal([]byte(payload), &i) hook.runProcessPayloadFunc(fn, i, hd)
pl = i
case LabelEvent: case LabelEvent:
var l LabelPayload var l LabelPayload
json.Unmarshal([]byte(payload), &l)
err = json.Unmarshal([]byte(payload), &l) hook.runProcessPayloadFunc(fn, l, hd)
pl = l
case MemberEvent: case MemberEvent:
var m MemberPayload var m MemberPayload
json.Unmarshal([]byte(payload), &m)
err = json.Unmarshal([]byte(payload), &m) hook.runProcessPayloadFunc(fn, m, hd)
pl = m
case MembershipEvent: case MembershipEvent:
var m MembershipPayload var m MembershipPayload
json.Unmarshal([]byte(payload), &m)
err = json.Unmarshal([]byte(payload), &m) hook.runProcessPayloadFunc(fn, m, hd)
pl = m
case MilestoneEvent: case MilestoneEvent:
var m MilestonePayload var m MilestonePayload
json.Unmarshal([]byte(payload), &m)
err = json.Unmarshal([]byte(payload), &m) hook.runProcessPayloadFunc(fn, m, hd)
pl = m
case OrganizationEvent: case OrganizationEvent:
var o OrganizationPayload var o OrganizationPayload
json.Unmarshal([]byte(payload), &o)
err = json.Unmarshal([]byte(payload), &o) hook.runProcessPayloadFunc(fn, o, hd)
pl = o
case OrgBlockEvent: case OrgBlockEvent:
var o OrgBlockPayload var o OrgBlockPayload
json.Unmarshal([]byte(payload), &o)
err = json.Unmarshal([]byte(payload), &o) hook.runProcessPayloadFunc(fn, o, hd)
pl = o
case PageBuildEvent: case PageBuildEvent:
var p PageBuildPayload var p PageBuildPayload
json.Unmarshal([]byte(payload), &p)
err = json.Unmarshal([]byte(payload), &p) hook.runProcessPayloadFunc(fn, p, hd)
pl = p
case ProjectCardEvent: case ProjectCardEvent:
var p ProjectCardPayload var p ProjectCardPayload
json.Unmarshal([]byte(payload), &p)
err = json.Unmarshal([]byte(payload), &p) hook.runProcessPayloadFunc(fn, p, hd)
pl = p
case ProjectColumnEvent: case ProjectColumnEvent:
var p ProjectColumnPayload var p ProjectColumnPayload
json.Unmarshal([]byte(payload), &p)
err = json.Unmarshal([]byte(payload), &p) hook.runProcessPayloadFunc(fn, p, hd)
pl = p
case ProjectEvent: case ProjectEvent:
var p ProjectPayload var p ProjectPayload
json.Unmarshal([]byte(payload), &p)
err = json.Unmarshal([]byte(payload), &p) hook.runProcessPayloadFunc(fn, p, hd)
pl = p
case PublicEvent: case PublicEvent:
var p PublicPayload var p PublicPayload
json.Unmarshal([]byte(payload), &p)
err = json.Unmarshal([]byte(payload), &p) hook.runProcessPayloadFunc(fn, p, hd)
pl = p
case PullRequestEvent: case PullRequestEvent:
var p PullRequestPayload var p PullRequestPayload
json.Unmarshal([]byte(payload), &p)
err = json.Unmarshal([]byte(payload), &p) hook.runProcessPayloadFunc(fn, p, hd)
pl = p
case PullRequestReviewEvent: case PullRequestReviewEvent:
var p PullRequestReviewPayload var p PullRequestReviewPayload
json.Unmarshal([]byte(payload), &p)
err = json.Unmarshal([]byte(payload), &p) hook.runProcessPayloadFunc(fn, p, hd)
pl = p
case PullRequestReviewCommentEvent: case PullRequestReviewCommentEvent:
var p PullRequestReviewCommentPayload var p PullRequestReviewCommentPayload
json.Unmarshal([]byte(payload), &p)
err = json.Unmarshal([]byte(payload), &p) hook.runProcessPayloadFunc(fn, p, hd)
pl = p
case PushEvent: case PushEvent:
var p PushPayload var p PushPayload
json.Unmarshal([]byte(payload), &p)
err = json.Unmarshal([]byte(payload), &p) hook.runProcessPayloadFunc(fn, p, hd)
pl = p
case ReleaseEvent: case ReleaseEvent:
var r ReleasePayload var r ReleasePayload
json.Unmarshal([]byte(payload), &r)
err = json.Unmarshal([]byte(payload), &r) hook.runProcessPayloadFunc(fn, r, hd)
pl = r
case RepositoryEvent: case RepositoryEvent:
var r RepositoryPayload var r RepositoryPayload
json.Unmarshal([]byte(payload), &r)
err = json.Unmarshal([]byte(payload), &r) hook.runProcessPayloadFunc(fn, r, hd)
pl = r
case StatusEvent: case StatusEvent:
var s StatusPayload var s StatusPayload
json.Unmarshal([]byte(payload), &s)
err = json.Unmarshal([]byte(payload), &s) hook.runProcessPayloadFunc(fn, s, hd)
pl = s
case TeamEvent: case TeamEvent:
var t TeamPayload var t TeamPayload
json.Unmarshal([]byte(payload), &t)
err = json.Unmarshal([]byte(payload), &t) hook.runProcessPayloadFunc(fn, t, hd)
pl = t
case TeamAddEvent: case TeamAddEvent:
var t TeamAddPayload var t TeamAddPayload
json.Unmarshal([]byte(payload), &t)
err = json.Unmarshal([]byte(payload), &t) hook.runProcessPayloadFunc(fn, t, hd)
pl = t
case WatchEvent: case WatchEvent:
var w WatchPayload var w WatchPayload
json.Unmarshal([]byte(payload), &w)
err = json.Unmarshal([]byte(payload), &w) hook.runProcessPayloadFunc(fn, w, hd)
pl = w
} }
if err != nil {
log.Println("There was an erro parsing JSON:", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
log.Println("Running runProcessPayloadFunc")
hook.runProcessPayloadFunc(fn, pl, hd)
} }
func (hook Webhook) runProcessPayloadFunc(fn webhooks.ProcessPayloadFunc, results interface{}, header webhooks.Header) { func (hook Webhook) runProcessPayloadFunc(fn webhooks.ProcessPayloadFunc, results interface{}, header webhooks.Header) {
go func(fn webhooks.ProcessPayloadFunc, results interface{}, header webhooks.Header) { go func(fn webhooks.ProcessPayloadFunc, results interface{}, header webhooks.Header) {
log.Println("Calling hook function")
fn(results, header) fn(results, header)
}(fn, results, header) }(fn, results, header)
} }
+26 -22
View File
@@ -1,9 +1,6 @@
package webhooks package webhooks
import ( import "net/http"
"log"
"net/http"
)
// Header provides http.Header to minimize imports // Header provides http.Header to minimize imports
type Header http.Header type Header http.Header
@@ -38,18 +35,27 @@ type Webhook interface {
} }
type server struct { type server struct {
hook Webhook hook Webhook
path string path string
includePathCheck bool
} }
// ProcessPayloadFunc is a common function for payload return values // ProcessPayloadFunc is a common function for payload return values
type ProcessPayloadFunc func(payload interface{}, header Header) type ProcessPayloadFunc func(payload interface{}, header Header)
// Handler returns the webhook http.Handler for use in your own Mux implementation
func Handler(hook Webhook) http.Handler {
return &server{
hook: hook,
}
}
// Run runs a server // Run runs a server
func Run(hook Webhook, addr string, path string) error { func Run(hook Webhook, addr string, path string) error {
srv := &server{ srv := &server{
hook: hook, hook: hook,
path: path, path: path,
includePathCheck: true,
} }
s := &http.Server{Addr: addr, Handler: srv} s := &http.Server{Addr: addr, Handler: srv}
@@ -61,8 +67,9 @@ func Run(hook Webhook, addr string, path string) error {
func RunServer(s *http.Server, hook Webhook, path string) error { func RunServer(s *http.Server, hook Webhook, path string) error {
srv := &server{ srv := &server{
hook: hook, hook: hook,
path: path, path: path,
includePathCheck: true,
} }
s.Handler = srv s.Handler = srv
@@ -77,8 +84,9 @@ func RunServer(s *http.Server, hook Webhook, path string) error {
func RunTLSServer(s *http.Server, hook Webhook, path string) error { func RunTLSServer(s *http.Server, hook Webhook, path string) error {
srv := &server{ srv := &server{
hook: hook, hook: hook,
path: path, path: path,
includePathCheck: true,
} }
s.Handler = srv s.Handler = srv
@@ -88,23 +96,19 @@ func RunTLSServer(s *http.Server, hook Webhook, path string) error {
// ServeHTTP is the Handler for every posted WebHook Event // ServeHTTP is the Handler for every posted WebHook Event
func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer func() { defer r.Body.Close()
log.Println("Closing Request Body")
r.Body.Close()
}()
log.Println("HTTP METHOD:", r.Method)
if r.Method != "POST" { if r.Method != "POST" {
http.Error(w, "405 Method not allowed", http.StatusMethodNotAllowed) http.Error(w, "405 Method not allowed", http.StatusMethodNotAllowed)
return return
} }
log.Println("Chking that paths match:", r.URL.Path == s.path) if s.includePathCheck {
if r.URL.Path != s.path { if r.URL.Path != s.path {
http.Error(w, "404 Not found", http.StatusNotFound) http.Error(w, "404 Not found", http.StatusNotFound)
return return
}
} }
log.Println("Parsing Payload")
s.hook.ParsePayload(w, r) s.hook.ParsePayload(w, r)
} }
+39
View File
@@ -8,6 +8,8 @@ import (
"testing" "testing"
"time" "time"
"net/http/httptest"
. "gopkg.in/go-playground/assert.v1" . "gopkg.in/go-playground/assert.v1"
) )
@@ -47,6 +49,43 @@ func TestMain(m *testing.M) {
// teardown // teardown
} }
func TestHandler(t *testing.T) {
mux := http.NewServeMux()
mux.Handle("/webhooks", Handler(fakeHook))
s := httptest.NewServer(Handler(fakeHook))
defer s.Close()
payload := "{}"
req, err := http.NewRequest("POST", s.URL+"/webhooks", bytes.NewBuffer([]byte(payload)))
req.Header.Set("Content-Type", "application/json")
Equal(t, err, nil)
client := &http.Client{}
resp, err := client.Do(req)
Equal(t, err, nil)
defer resp.Body.Close()
Equal(t, resp.StatusCode, http.StatusOK)
// Test BAD METHOD
req, err = http.NewRequest("GET", s.URL+"/webhooks", bytes.NewBuffer([]byte(payload)))
req.Header.Set("Content-Type", "application/json")
Equal(t, err, nil)
resp, err = client.Do(req)
Equal(t, err, nil)
defer resp.Body.Close()
Equal(t, resp.StatusCode, http.StatusMethodNotAllowed)
}
func TestRun(t *testing.T) { func TestRun(t *testing.T) {
go Run(fakeHook, "127.0.0.1:3006", "/webhooks") go Run(fakeHook, "127.0.0.1:3006", "/webhooks")