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.

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" }