Skip to content

Commit

Permalink
new: request processor (see JSON and XML and /_examples/8_bind_req_se…
Browse files Browse the repository at this point in the history
…nd_res)\n Also: allow more than one methods to be passed on muxie.Methods().Handle/HandleFunc to register the same handler for specific methods
  • Loading branch information
kataras committed Oct 18, 2018
1 parent 3c4b632 commit 282682d
Show file tree
Hide file tree
Showing 9 changed files with 358 additions and 9 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@
<div align="center">
<!-- Release -->
<a href="https://github.com/kataras/muxie/releases">
<img src="https://img.shields.io/badge/release%20-v1.0.2-0077b3.svg?style=flat-squaree"
<img src="https://img.shields.io/badge/release%20-v1.0.3-0077b3.svg?style=flat-squaree"
alt="Release/stability" />
</a>
<!-- Godocs -->
<a href="https://godoc.org/github.com/kataras/muxie">
<img src="https://img.shields.io/badge/go-docs-teal.svg?style=flat-square"
alt="Godocs" />
</a>
<!-- Build Status -->
<a href="https://travis-ci.org/kataras/muxie">
<img src="https://img.shields.io/travis/kataras/muxie/master.svg?style=flat-square"
Expand Down
17 changes: 17 additions & 0 deletions _examples/7_by_methods/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,23 @@ func main() {
HandleFunc(http.MethodGet, getUser).
HandleFunc(http.MethodPost, saveUser).
HandleFunc(http.MethodDelete, deleteUser))
/*
muxie.Methods().
HandleFunc("POST, PUT", func(w http.ResponseWriter, r *http.Request) {[...]}
^ can accept many methods for the same handler
^ methods should be separated by comma, comma following by a space or just space
Equivalent to:
mux.HandleFunc("/save", func(w http.ResponseWriter, r *http.Request){
if r.Method != http.MethodPost && r.Method != http.MethodPut {
w.Header().Set("Allow", "POST, PUT")
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
[...]
})
*/

log.Println("Server started at http://localhost:8080\nGET: http://localhost:8080/users\nGET, POST, DELETE: http://localhost:8080/user/:id")
log.Fatal(http.ListenAndServe(":8080", mux))
Expand Down
77 changes: 77 additions & 0 deletions _examples/8_bind_req_send_res/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package main

import (
"encoding/xml"
"fmt"
"net/http"

"github.com/kataras/muxie"
)

// Read more at https://golang.org/pkg/encoding/xml
type person struct {
XMLName xml.Name `json:"-" xml:"person"` // element name
Name string `json:"name" xml:"name,attr"` // ,attr for attribute.
Age int `json:"age" xml:"age,attr"` // ,attr attribute.
Description string `json:"description" xml:"description"` // inner element name, value is its body.
}

func main() {
mux := muxie.NewMux()
mux.PathCorrection = true

// Read from incoming request.
mux.Handle("/save", muxie.Methods().
HandleFunc("POST, PUT", func(w http.ResponseWriter, r *http.Request) {
var p person
// muxie.Bind(r, muxie.JSON,...) for JSON.
// You can implement your own Binders by implementing the muxie.Binder interface like the muxie.JSON/XML.
err := muxie.Bind(r, muxie.XML, &p)
if err != nil {
http.Error(w, fmt.Sprintf("unable to read body: %v", err), http.StatusInternalServerError)
return
}

fmt.Fprintf(w, "Go value of the request body:\n%#+v\n", p)
}))

// Send a response.
mux.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) {
send := person{Name: "kataras", Age: 25, Description: "software engineer"}

// muxie.Dispatch(w, muxie.JSON,...) for JSON.
// You can implement your own Dispatchers by implementing the muxie.Dispatcher interface like the muxie.JSON/XML.
err := muxie.Dispatch(w, muxie.XML, send)
if err != nil {

http.Error(w, fmt.Sprintf("unable to send the value of %#+v. Error: %v", send, err), http.StatusInternalServerError)
return
}
})

fmt.Println(`Server started at http://localhost:8080\n
:: How to...
Read from incoming request
request:
POST or PUT: http://localhost:8080/save
request body:
<person name="kataras" age="25"><description>software engineer</description></person>
request header:
"Content-Type": "text/xml"
response:
Go value of the request body:
main.person{XMLName:xml.Name{Space:"", Local:"person"}, Name:"kataras", Age:25, Description:"software engineer"}
Send a response
request:
GET: http://localhost:8080/get
response header:
"Content-Type": "text/xml; charset=utf-8" (can be modified by muxie.Charset variable)
response:
<person name="kataras" age="25">
<description>software engineer</description>
</person>`)
http.ListenAndServe(":8080", mux)
}
2 changes: 1 addition & 1 deletion doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Source code and other details for the project are available at GitHub:
Current Version
1.0.0
1.0.3
Installation
Expand Down
30 changes: 24 additions & 6 deletions method_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
// to register handler for specific HTTP Methods inside the `Mux#Handle/HandleFunc`.
// Usage:
// mux := muxie.NewMux()
// mux.Handle("/user", muxie.Methods().
// mux.Handle("/user/:id", muxie.Methods().
// Handle("GET", getUserHandler).
// Handle("POST", saveUserHandler))
func Methods() *MethodHandler {
Expand All @@ -18,17 +18,17 @@ func Methods() *MethodHandler {
//
// mux := muxie.NewMux()
//
// 1. mux.Handle("/user", muxie.ByMethod("GET", getHandler).And/AndFunc("POST", postHandlerFunc))
// 1. mux.Handle("/user/:id", muxie.ByMethod("GET", getHandler).And/AndFunc("POST", postHandlerFunc))
//
// 2. mux.Handle("/user", muxie.ByMethods{
// 2. mux.Handle("/user/:id", muxie.ByMethods{
// "GET": getHandler,
// "POST" http.HandlerFunc(postHandlerFunc),
// }) <- the only downside of this is that
// we lose the "Allow" header, which is not so important but it is RCF so we have to follow it.
//
// 3. mux.Handle("/user", muxie.Method("GET", getUserHandler).Method("POST", saveUserHandler))
// 3. mux.Handle("/user/:id", muxie.Method("GET", getUserHandler).Method("POST", saveUserHandler))
//
// 4. mux.Handle("/user", muxie.Methods().
// 4. mux.Handle("/user/:id", muxie.Methods().
// Handle("GET", getHandler).
// HandleFunc("POST", postHandler))
//
Expand All @@ -49,8 +49,26 @@ type MethodHandler struct {

// Handle adds a handler to be responsible for a specific HTTP Method.
// Returns this MethodHandler for further calls.
// Usage:
// Handle("GET", myGetHandler).HandleFunc("DELETE", func(w http.ResponseWriter, r *http.Request){[...]})
// Handle("POST, PUT", saveOrUpdateHandler)
// ^ can accept many methods for the same handler
// ^ methods should be separated by comma, comma following by a space or just space
func (m *MethodHandler) Handle(method string, handler http.Handler) *MethodHandler {
method = strings.ToUpper(method)
multiMethods := strings.FieldsFunc(method, func(c rune) bool {
return c == ',' || c == ' '
})

if len(multiMethods) > 1 {
for _, method := range multiMethods {
m.Handle(method, handler)
}

return m
}

method = strings.ToUpper(strings.TrimSpace(method))

if m.methodsAllowedStr == "" {
m.methodsAllowedStr = method
} else {
Expand Down
1 change: 0 additions & 1 deletion method_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ func TestMethodHandler(t *testing.T) {
bodyEq("POST: save user with ID: 42\n")
expect(t, http.MethodDelete, srv.URL+"/user/42").statusCode(http.StatusOK).
bodyEq("DELETE: remove user with ID: 42\n")

expect(t, http.MethodPut, srv.URL+"/user/42").statusCode(http.StatusMethodNotAllowed).
bodyEq("Method Not Allowed\n").headerEq("Allow", "GET, POST, DELETE")
}
22 changes: 22 additions & 0 deletions mux_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package muxie

import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
Expand All @@ -13,6 +14,27 @@ func expect(t *testing.T, method, url string) *testie {
if err != nil {
t.Fatal(err)
}

return testReq(t, req)
}

func expectWithBody(t *testing.T, method, url string, body string, headers http.Header) *testie {
req, err := http.NewRequest(method, url, bytes.NewBufferString(body))
if err != nil {
t.Fatal(err)
}

if len(headers) > 0 {
req.Header = http.Header{}
for k, v := range headers {
req.Header[k] = v
}
}

return testReq(t, req)
}

func testReq(t *testing.T, req *http.Request) *testie {
res, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
Expand Down
148 changes: 148 additions & 0 deletions request_processor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package muxie

import (
"bytes"
"encoding/json"
"encoding/xml"
"io/ioutil"
"net/http"
)

var (
Charset = "utf-8"

JSON = &jsonProcessor{Prefix: nil, Indent: "", UnescapeHTML: false}
XML = &xmlProcessor{Indent: ""}
)

func withCharset(cType string) string {
return cType + "; charset=" + Charset
}

type Binder interface {
Bind(*http.Request, interface{}) error
}

func Bind(r *http.Request, b Binder, ptrOut interface{}) error {
return b.Bind(r, ptrOut)
}

type Dispatcher interface {
// no io.Writer because we need to set the headers here,
// Binder and Processor are only for HTTP.
Dispatch(http.ResponseWriter, interface{}) error
}

func Dispatch(w http.ResponseWriter, d Dispatcher, v interface{}) error {
return d.Dispatch(w, v)
}

type Processor interface {
Binder
Dispatcher
}

var (
newLineB byte = '\n'
// the html codes for unescaping
ltHex = []byte("\\u003c")
lt = []byte("<")

gtHex = []byte("\\u003e")
gt = []byte(">")

andHex = []byte("\\u0026")
and = []byte("&")
)

type jsonProcessor struct {
Prefix []byte
Indent string
UnescapeHTML bool
}

var _ Processor = (*jsonProcessor)(nil)

func (p *jsonProcessor) Bind(r *http.Request, v interface{}) error {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}

return json.Unmarshal(b, v)
}

func (p *jsonProcessor) Dispatch(w http.ResponseWriter, v interface{}) error {
var (
result []byte
err error
)

if indent := p.Indent; indent != "" {
marshalIndent := json.MarshalIndent

result, err = marshalIndent(v, "", indent)
result = append(result, newLineB)
} else {
marshal := json.Marshal
result, err = marshal(v)
}

if err != nil {
return err
}

if p.UnescapeHTML {
result = bytes.Replace(result, ltHex, lt, -1)
result = bytes.Replace(result, gtHex, gt, -1)
result = bytes.Replace(result, andHex, and, -1)
}

if len(p.Prefix) > 0 {
result = append([]byte(p.Prefix), result...)
}

w.Header().Set("Content-Type", withCharset("application/json"))
_, err = w.Write(result)
return err
}

type xmlProcessor struct {
Indent string
}

var _ Processor = (*xmlProcessor)(nil)

func (p *xmlProcessor) Bind(r *http.Request, v interface{}) error {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}

return xml.Unmarshal(b, v)
}

func (p *xmlProcessor) Dispatch(w http.ResponseWriter, v interface{}) error {
var (
result []byte
err error
)

if indent := p.Indent; indent != "" {
marshalIndent := xml.MarshalIndent

result, err = marshalIndent(v, "", indent)
result = append(result, newLineB)
} else {
marshal := xml.Marshal
result, err = marshal(v)
}

if err != nil {
return err
}

w.Header().Set("Content-Type", withCharset("text/xml"))
_, err = w.Write(result)
return err
}
Loading

0 comments on commit 282682d

Please sign in to comment.