RESTful Web Applications
with Google Go
Frank Müller
Oldenburg / Germany
Released Summer 1965
Software Engineer
Author
!
mue@tideland.biz
blog.tideland.biz
@themue
github.com/tideland
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
Google Go
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
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!”)
}
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))
}
❝Simple tasks can be done using the
standard library, but own powerful
packages are easy to create.
–Gopher
RESTful Web Multiplexer
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
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 {
	 …
}
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 {
	 	 …
	 }
}
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))
}
❝Own multiplexers make HTTP server
more flexible.
–Gopher
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)
}
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)
}
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
}
❝Use my simple type system for small
types with useful methods.
–Gopher
Handle your resources
Resource Handler
• Basic interface for initialization and read operation

• Additional interfaces for create, update, and delete
operations

• Dispatcher to map HTTP methods
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)
}
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)
}
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”)
}
❝Small interfaces and type assertions
are a powerful combination.
–Gopher
See the context
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.
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 { … }
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”)
}
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)
}
❝Public fields are not evil as long as
the data is not shared.
–Gopher
JSON Marshaling
• Go likes JSON

• Really! (scnr)

• Automatically, controlled, and manually
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)
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.
}
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
}
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
}
❝My standard library provides powerful
encoding packages, also for XML,
CSV, ASN.1, etc.
–Gopher
Scenario
Tags by Interest
Browser
Stat
Handler
Content
Handler
Tag
Handler
Content
Backend
Stat
Backend
DB
GET /content/page/4711
GET /content/tags/interest Goroutines
Async Update
Page Request

gets HTML
JS Request

gets JSON
Stat Handler
func (h *StatHandler) Read(ctx *rwm.Context) (bool, error) {
	 if ctx.ResourceId != ”” {
	 	 // Backend handles update in background.
	 	 statBackend.UpdatePage(ctx.ResourceId)
	 }
	 return true, nil
}
Content Handler
func (h *ContentHandler) Read(
	 ctx *rwm.Context) (bool, error) {
	 var page *Page
	 if ctx.ResourceId != ”” {
	 	 page = contentBackend.Page(ctx.ResourceId)	 	
	 } else {
	 	 page = contentBackend.Index()
	 }
	 if err := ctx.RespondTemplate(h.template, page); err != nil {
	 	 return false, err
	 }
	 return true, nil
}
Tag Handler
func (h *StatHandler) Read(ctx *rwm.Context) (bool, error) {
	 var err error
	 switch ctx.ResourceId {
	 case ”interest”:
	 	 tags := statBackend.TagsByInterest()
	 	 if ctx.AcceptsJSON() {
	 	 	 err = ctx.RespondJSON(tags, true)
	 	 } else if ctx.AcceptsXML() {
	 	 	 err = ctx.RespondXML(tags)
	 	 }
	 case …
	 }
	 …	
}
❝Enjoy Go, it’s lightweight, simple and
very productive.
–Gopher
❝ Zitat hier eingeben.
–Christian BauerImages
123RF

iStockphoto

Own Sources

RESTful Web Applications with Google Go

  • 1.
  • 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 ofHTTP / 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
  • 4.
  • 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 canbe done using the standard library, but own powerful packages are easy to create. –Gopher
  • 9.
  • 10.
    Multiplexer • Go defaultuses 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 - InterfaceMethod // 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 ! funcmain() { // 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)) }
  • 14.
    ❝Own multiplexers makeHTTP server more flexible. –Gopher
  • 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 simpletype system for small types with useful methods. –Gopher
  • 19.
  • 20.
    Resource Handler • Basicinterface 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”) }
  • 24.
    ❝Small interfaces andtype assertions are a powerful combination. –Gopher
  • 25.
  • 26.
    Context • Simple wrapperfor 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 - SimpleRequest 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 - TypicalOperations // 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 arenot evil as long as the data is not shared. –Gopher
  • 31.
    JSON Marshaling • Golikes 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 - Integratein 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 libraryprovides powerful encoding packages, also for XML, CSV, ASN.1, etc. –Gopher
  • 37.
  • 38.
    Tags by Interest Browser Stat Handler Content Handler Tag Handler Content Backend Stat Backend DB GET/content/page/4711 GET /content/tags/interest Goroutines Async Update Page Request gets HTML JS Request gets JSON
  • 39.
    Stat Handler func (h*StatHandler) Read(ctx *rwm.Context) (bool, error) { if ctx.ResourceId != ”” { // Backend handles update in background. statBackend.UpdatePage(ctx.ResourceId) } return true, nil }
  • 40.
    Content Handler func (h*ContentHandler) Read( ctx *rwm.Context) (bool, error) { var page *Page if ctx.ResourceId != ”” { page = contentBackend.Page(ctx.ResourceId) } else { page = contentBackend.Index() } if err := ctx.RespondTemplate(h.template, page); err != nil { return false, err } return true, nil }
  • 41.
    Tag Handler func (h*StatHandler) Read(ctx *rwm.Context) (bool, error) { var err error switch ctx.ResourceId { case ”interest”: tags := statBackend.TagsByInterest() if ctx.AcceptsJSON() { err = ctx.RespondJSON(tags, true) } else if ctx.AcceptsXML() { err = ctx.RespondXML(tags) } case … } … }
  • 42.
    ❝Enjoy Go, it’slightweight, simple and very productive. –Gopher
  • 44.
    ❝ Zitat hiereingeben. –Christian BauerImages 123RF iStockphoto Own Sources