restful web applications with google go
DESCRIPTION
TRANSCRIPT
RESTful Web Applications with Google Go
Frank Müller
Oldenburg / Germany Released Summer 1965
Software Engineer Author
!
[email protected] 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 { fieldAstring fieldBint } !// 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 Requestgets HTML
JS Requestgets 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 123RFiStockphotoOwn Sources