Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Siesta: RESTful Services Made Simple

Siesta: RESTful Services Made Simple

Slides from a beCraft talk in Charlottesville on Nov. 12 2015.

Avatar for Preetam Jinka

Preetam Jinka

November 12, 2015
Tweet

More Decks by Preetam Jinka

Other Decks in Programming

Transcript

  1. func h(w http.ResponseWriter, r *http.Request) { } Boilerplate code (logging,

    auth, parameters) Boilerplate code (response writing, etc) Main handler code
  2. func h(w http.ResponseWriter, r *http.Request) { f(w, r) { }

    g(w, r) { } } Boilerplate code (logging, auth, parameters) Boilerplate code (response writing, etc) Main handler code
  3. func f(w http.ResponseWriter, r *http.Request) { } func h(w http.ResponseWriter,

    r *http.Request) { } func g(w http.ResponseWriter, r *http.Request) { } Boilerplate code (logging, auth, parameters) Boilerplate code (response writing, etc) Main handler code
  4. func(c siesta.Context, w http.ResponseWriter, r *http.Request) { // set interface{}s

    c.Set("foo", bar); // get interface{}s c.Get("foo") // => interface{}(bar) } ◦ ◦
  5. gorilla/context ◦ ◦ ◦ func MyHandler(w http.ResponseWriter, r *http.Request) {

    // ... requestID := context.Get(r, "request-id") // ... }
  6. func (s *Service) ServeHTTPInContext(c Context, w http.ResponseWriter, r *http.Request) {

    // ... quit := false for _, m := range s.pre { // s.pre is a slice of handlers m(c, w, r, func() { quit = true }) if quit { break } } // ... }
  7. var params siesta.Params // GET /resources/:resourceID/... resourceID := params.Int("resourceID", -1,

    "Resource identifier") err := params.Parse(r.Form) if err != nil { // Do something with the err } // Make sure we have a valid resource ID. if *resourceID == -1 { // Handle the invalid resource }
  8. // responseGenerator converts response and/or error data passed through the

    // context into a structured response. func responseGenerator(c siesta.Context, w http.ResponseWriter, r *http.Request) { response := apiResponse{} if data := c.Get("data"); data != nil { response.Data = data } if err := c.Get("error"); err != nil { response.Error = err.(string) } c.Set("response", response) }
  9. func TestHandler(t *testing.T) { c := siesta.NewSiestaContext() // c.Set(...) myHandler(c,

    mockResponseWriter, &http.Request{ Form: url.Values(map[string][]string{ "id": []string{"1"}, // maybe a GET /resource/:id }), } // c.Get(...) }
  10. sql.DB type SQLDB interface { Query(query string, args ...interface{}) (SQLRows,

    error) QueryRow(query string, args ...interface{}) SQLRow Exec(query string, args ...interface{}) (sql.Result, error) Begin() error Rollback() error Commit() error } type SQLRows interface { SQLRow Close() error Err() error Next() bool } type SQLRow interface { Scan(dest ...interface{}) error }
  11. type DB struct { sqlDB *sql.DB tx *sql.Tx context siesta.Context

    } func (db *DB) Query(query string, args ...interface{}) (Rows, error) { // db.context is accessible! if db.tx != nil { return db.tx.Query(query, args...) } return db.sqlDB.Query(query, args...) } // ...
  12. $ curl localhost:8080/resources/1 -u abcde: { "data": "foo" } $

    curl localhost:8080/resources/1?usage -u abcde: { "data": { "resourceID": [ "resourceID", "int", "Resource identifier" ], "usage": [ "usage", "bool", "Show usage" ] } } Name Type Description
  13. // Check parameters var params siesta.Params resourceID := params.Int("resourceID", -1,

    "Resource identifier") params.Bool("usage", false, "Show usage") if r.URL.Query()["usage"] != nil { c.Set("data", params.Usage()) return } err := params.Parse(r.Form) // ...
  14. /resources/:resourceID?summary=true { "method": "GET", "url": [ { "name": "resources", "type":

    "fixed" }, { "name": "resourceID", "type": "parameter" "parameterType": "int", "description": "Resource ID" } ], "queryString": [ { "name": "summary", "parameterType": "bool", "description": "Show only feature summary" } ], "endpoint": "/resources/:resourceID" }