Skip to content

Commit

Permalink
Move some request parsing into hook package
Browse files Browse the repository at this point in the history
Trying to simplify hookHandler.  No functional changes introduced.
  • Loading branch information
moorereason committed Nov 17, 2020
1 parent 4fadb11 commit 22c8a16
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 69 deletions.
25 changes: 0 additions & 25 deletions internal/hook/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"log"
"math"
"net"
"net/http"
"net/textproto"
"os"
"reflect"
Expand Down Expand Up @@ -48,30 +47,6 @@ const (
EnvNamespace string = "HOOK_"
)

// Request represents a webhook request.
type Request struct {
// The request ID set by the RequestID middleware.
ID string

// The Content-Type of the request.
ContentType string

// The raw request body.
Body []byte

// Headers is a map of the parsed headers.
Headers map[string]interface{}

// Query is a map of the parsed URL query values.
Query map[string]interface{}

// Payload is a map of the parsed payload.
Payload map[string]interface{}

// The underlying HTTP request.
RawRequest *http.Request
}

// ParameterNodeError describes an error walking a parameter node.
type ParameterNodeError struct {
key string
Expand Down
116 changes: 116 additions & 0 deletions internal/hook/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package hook

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"unicode"

"github.com/clbanning/mxj"
)

// Request represents a webhook request.
type Request struct {
// The request ID set by the RequestID middleware.
ID string

// The Content-Type of the request.
ContentType string

// The raw request body.
Body []byte

// Headers is a map of the parsed headers.
Headers map[string]interface{}

// Query is a map of the parsed URL query values.
Query map[string]interface{}

// Payload is a map of the parsed payload.
Payload map[string]interface{}

// The underlying HTTP request.
RawRequest *http.Request
}

func (r *Request) ParseJSONPayload() error {
decoder := json.NewDecoder(bytes.NewReader(r.Body))
decoder.UseNumber()

var firstChar byte
for i := 0; i < len(r.Body); i++ {
if unicode.IsSpace(rune(r.Body[i])) {
continue
}
firstChar = r.Body[i]
break
}

if firstChar == byte('[') {
var arrayPayload interface{}
err := decoder.Decode(&arrayPayload)
if err != nil {
return fmt.Errorf("error parsing JSON array payload %+v", err)
}

r.Payload = make(map[string]interface{}, 1)
r.Payload["root"] = arrayPayload
} else {
err := decoder.Decode(&r.Payload)
if err != nil {
return fmt.Errorf("error parsing JSON payload %+v", err)
}
}

return nil
}

func (r *Request) ParseHeaders(headers map[string][]string) {
r.Headers = make(map[string]interface{}, len(headers))

for k, v := range headers {
if len(v) > 0 {
r.Headers[k] = v[0]
}
}
}

func (r *Request) ParseQuery(query map[string][]string) {
r.Query = make(map[string]interface{}, len(query))

for k, v := range query {
if len(v) > 0 {
r.Query[k] = v[0]
}
}
}

func (r *Request) ParseFormPayload() error {
fd, err := url.ParseQuery(string(r.Body))
if err != nil {
return fmt.Errorf("error parsing form payload %+v", err)
}

r.Payload = make(map[string]interface{}, len(fd))

for k, v := range fd {
if len(v) > 0 {
r.Payload[k] = v[0]
}
}

return nil
}

func (r *Request) ParseXMLPayload() error {
var err error

r.Payload, err = mxj.NewMapXmlReader(bytes.NewReader(r.Body))
if err != nil {
return fmt.Errorf("error parsing XML payload: %+v", err)
}

return nil
}
53 changes: 9 additions & 44 deletions webhook.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"bytes"
"crypto/tls"
"encoding/json"
"flag"
Expand All @@ -10,19 +9,16 @@ import (
"log"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"unicode"

"github.com/adnanh/webhook/internal/hook"
"github.com/adnanh/webhook/internal/middleware"
"github.com/adnanh/webhook/internal/pidfile"

"github.com/clbanning/mxj"
chimiddleware "github.com/go-chi/chi/middleware"
"github.com/gorilla/mux"
fsnotify "gopkg.in/fsnotify.v1"
Expand Down Expand Up @@ -373,57 +369,26 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
}
}

// parse headers
req.Headers = valuesToMap(r.Header)

// parse query variables
req.Query = valuesToMap(r.URL.Query())

// parse body
// var payload map[string]interface{}
req.ParseHeaders(r.Header)
req.ParseQuery(r.URL.Query())

switch {
case strings.Contains(req.ContentType, "json"):
decoder := json.NewDecoder(bytes.NewReader(req.Body))
decoder.UseNumber()

var firstChar byte
for i := 0; i < len(req.Body); i++ {
if unicode.IsSpace(rune(req.Body[i])) {
continue
}
firstChar = req.Body[i]
break
}

if firstChar == byte('[') {
var arrayPayload interface{}
err := decoder.Decode(&arrayPayload)
if err != nil {
log.Printf("[%s] error parsing JSON array payload %+v\n", req.ID, err)
}

req.Payload = make(map[string]interface{}, 1)
req.Payload["root"] = arrayPayload
} else {
err := decoder.Decode(&req.Payload)
if err != nil {
log.Printf("[%s] error parsing JSON payload %+v\n", req.ID, err)
}
err = req.ParseJSONPayload()
if err != nil {
log.Printf("[%s] %s", req.ID, err)
}

case strings.Contains(req.ContentType, "x-www-form-urlencoded"):
fd, err := url.ParseQuery(string(req.Body))
err = req.ParseFormPayload()
if err != nil {
log.Printf("[%s] error parsing form payload %+v\n", req.ID, err)
} else {
req.Payload = valuesToMap(fd)
log.Printf("[%s] %s", req.ID, err)
}

case strings.Contains(req.ContentType, "xml"):
req.Payload, err = mxj.NewMapXmlReader(bytes.NewReader(req.Body))
err = req.ParseXMLPayload()
if err != nil {
log.Printf("[%s] error parsing XML payload: %+v\n", req.ID, err)
log.Printf("[%s] %s", req.ID, err)
}

case isMultipart:
Expand Down

0 comments on commit 22c8a16

Please sign in to comment.