2. Frank Müller
Oldenburg / Germany
Released Summer 1965
Software Engineer
Author
!
mue@tideland.biz
blog.tideland.biz
@themue
github.com/tideland
3. Goals
• Usage of HTTP / HTTPS
• Multiplexing based on path containing functional
domain, resource, and possible resource id
• List of multiple handles to support generic tasks like
authentication and authorization
• Mapping of HTTP methods to CRUD operations
• Major data is JSON, but also XML and templates
5. Go HTTP Package
• Simple
• Types implementing http.Handler interface or
functions with a defined signature for handling
• Integrated server able to handle HTTP and HTTPS
• Not very convenient
6. Go HTTP Package - Handler
type MyHandler struct{}
!
// Implementing http.Handler interface.
func (mh *MyHandler) ServeHTTP(
w http.ResponseWriter,
r *http.Request) {
w.Header().Set(”Content-Type”, ”text/plain”)
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, ”Hello, Go User Group!”)
}
7. Go HTTP Package - Main
!
func main() {
// Register handler for path.
http.Handle(”/myHandler”, &MyHandler{})
!
// Start server on port 8080.
log.Fatal(http.ListenAndServe(”:8080”, nil))
}
8. ❝Simple tasks can be done using the
standard library, but own powerful
packages are easy to create.
–Gopher
10. Multiplexer
• Go default uses a prefix based pattern
• Our RWM maps based on domain and resource
• Request and response wrapped into convenient
context
• Fallback to a default handler
11. Multiplexer - Type
// RESTfulWebMultiplexer is our own multiplexer.
type RESTfulWebMultiplexer struct {
mapping domains
…
}
!
// AddHandler adds a handler based on domain and resource.
func (mux * RESTfulWebMultiplexer) AddHandler(
domain, resource string,
h ResourceHandler) error {
…
}
12. Multiplexer - Interface Method
// ServeHTTP implements the handler interface.
func (mux * RESTfulWebMultiplexer) ServeHTTP(
w http.ResponseWriter,
r *http.Request) {
ctx := newContext(mux, w, r)
if err := mux.mapping.handle(ctx); err != nil {
…
}
}
13. Multiplexer - Main
!
func main() {
// Create multiplexer and add handlers.
mux := NewRESTfulWebMultiplexer()
!
mux.AddHandler(”content”, ”blog”, NewBlogHandler())
…
!
// Start server with our multiplexer on port 8080.
log.Fatal(http.ListenAndServe(”:8080”, mux))
}
15. Multiplexer - Domains
// domains maps domains to their resources.
type domains map[string]resources
!
// handle retrieves the resources for the context domain and
// lets them handle the context.
func (d domains) handle(
ctx *RequestContext) error {
resources, ok := d[ctx.Domain]
if !ok {
resources = d[ctx.Mux.DefaultDomain()]
}
// Continue handling.
return resources.handle(ctx)
}
16. Multiplexer - Resources
// resources maps resources to their handler lists.
type resources map[string]handlers
!
// handle retrieves the handlers for the context resource and lets
// them handle the context.
func (r resources) handle(
ctx *RequestContext) error {
handlers, ok := r[ctx.Resource]
if !ok {
handlers = r[ctx.Mux.DefaultResource(ctx.Domain)]
}
// Continue handling.
return handlers.handle(ctx)
}
17. Multiplexer - Handlers
// handlers chains all handlers for one resource.
type handlers []ResourceHandler
!
// handle lets all handlers handle the context.
func (h handlers) handle(
ctx *RequestContext) error {
for _, handler := range h {
ok, err := ctx.Mux.dispatch(ctx, handler)
if err != nil { return err }
// Handler tells to stop, but w/o error.
if !ok { return nil }
}
return nil
}
18. ❝Use my simple type system for small
types with useful methods.
–Gopher
20. Resource Handler
• Basic interface for initialization and read operation
• Additional interfaces for create, update, and delete
operations
• Dispatcher to map HTTP methods
21. Resource Handler - Base Interface
// ResourceHandler defines the base interface. It handles the
// HTTP GET method with Read().
type ResourceHandler interface {
// Init is called after registration of the handler.
Init(domain, resource string) error
!
// Read is called if the HTTP method is GET.
Read(ctx *Context) (bool, error)
}
22. Resource Handler - Create Interface
// CreateResourceHandler defines the interface to additionally
// handle the HTTP POST with Create().
type CreateResourceHandler interface {
// Create is called if the HTTP method is POST.
Create(ctx *Context) (bool, error)
}
23. Resource Handler - Dispatch
// dispatch maps HTTP methods to handler function calls.
func (mux *RESTfulWebMultiplexer) dispatch(
ctx *Context, h ResourceHandler) (bool, error) {
switch ctx.Request.Method {
case ”GET”:
return h.Read(ctx)
case ”POST”:
if ch, ok := h.(CreateResourceHandler); ok {
return ch.Create(ctx)
}
return false, errors.New(”handler cannot process POST”)
case …
}
return false, errors.New(”invalid HTTP method”)
}
26. Context
• Simple wrapper for request and response
• Provides information about domain, resource and id
• Also provides information about stuff like accepted
content types and languages
• Allows simpler reading and writing of JSON etc.
27. Context - Type
// Context encapsulates all needed data for handling a request.
type Context struct {
Mux *RESTfulWebMultiplexer
Writer http.ResponseWriter
Request *http.Request
Domain, Resource, ResourceId string
}
!
// newContext creates a new context and parses the URL path.
func newContext(
mux *RESTfulWebMultiplexer,
w http.ResponseWriter,
r *http.Request) *Context { … }
28. Context - Simple Request Analysis
// accepts checks if the requestor accepts a content type.
func (ctx *Context) accepts(ct string) bool {
accept := ctx.Request.Header.Get(”Accept”)
return strings.Contains(accept, ct)
}
!
// AcceptsJSON checks if the requestor accepts JSON as
// a content type.
func (ctx *Context) AcceptsJSON() bool {
return ctx.accepts(”application/json”)
}
29. Context - Typical Operations
// Redirect to a domain, resource and resource id (optional).
func (ctx *Context) Redirect(
domain, resource, resourceId string) {
url := ctx.Mux.BasePath() + domain + ”/” + resource
if resourceId != ”” {
url += ”/” + resourceId
}
ctx.Writer.Header().Set(”Location”, url)
ctx.Writer.WriteHeader(http.StatusMovedPermanently)
}
30. ❝Public fields are not evil as long as
the data is not shared.
–Gopher
31. JSON Marshaling
• Go likes JSON
• Really! (scnr)
• Automatically, controlled, and manually
32. JSON - Standard
// Public fields will be marshaled.
type Demo struct {
FieldA string
FieldB int
FieldC *OtherStruct
fieldX bool // No, you won’t see me.
}
!
demo := &demo{ … }
!
// b contains the marshaled demo struct as []byte.
b, err := json.Marshal(demo)
33. JSON - Controlled
// Control with field tags.
type AnotherDemo struct {
FieldA string `json:”-”` // Ignore.
FieldB int `json:”OtherName”` // Change name.
FieldC float64 `json:”,string”` // As string.
FieldD bool `json:”,omitempty”` // Ignore if empty.
FieldE string // As usual.
fieldX int // Still ignored.
}
34. JSON - Manually
// User has to care for it.
type StillADemo struct {
fieldA string
fieldB int
}
!
// MarshalJSON implements the Marshaler interface.
func (d *StillADemo) MarshalJSON() ([]byte, error) {
format := `{”First”: %q, ”Second”: %d}`
json := fmt.Sprintf(format, d.fieldA, d.fieldB)
return []byte(json), nil
}
35. JSON - Integrate in Context
func (ctx *Context) RespondJSON(
data interface{}, html bool) error {
b, err := json.Marshal(data)
if err != nil {
return fmt.Errorf(”cannot respond JSON: %v”, err)
}
if html {
var buf bytes.Buffer
json.HTMLEscape(&buf, b)
b = buf.Bytes()
}
ctx.Writer.Header().Set(”Content-Type”, ”application/json”)
_, err = ctx.Writer.Write(b)
return err
}
36. ❝My standard library provides powerful
encoding packages, also for XML,
CSV, ASN.1, etc.
–Gopher