Guest User

Simple Go resource abstraction on top of BoltDb

a guest
Nov 10th, 2015
309
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Go 8.78 KB | None | 0 0
  1. // Generic handler for mapping database-stored resources to a REST endpoint.                                                                                                                                      
  2. package main
  3.  
  4. import (
  5.         "encoding/json"
  6.         "github.com/boltdb/bolt"
  7.         "github.com/djherbis/stow"
  8.         "io/ioutil"
  9.         "net/http"
  10.         "regexp"
  11.         "strings"
  12. )
  13.  
  14.  
  15. // Storable entity in an interface.                                                                                                                                                                                
  16. type Storable interface {
  17.         // Get the key from a storable resource                                                                                                                                                                    
  18.         key() []byte
  19.         // Get an instance of an empty resource for population by a resource handler                                                                                                                              
  20.         empty() Storable
  21. }
  22.  
  23. // All the information needed to manage retrieval and storage of a RESTful resource.                                                                                                                              
  24. type Resource struct {
  25.         // URL path to the resource                                                                                                                                                                                
  26.         ResourcePath string
  27.         // Database that stores the resource                                                                                                                                                                      
  28.         Db *bolt.DB
  29.         // Name of the bucket used for storage                                                                                                                                                                    
  30.         BucketName []byte
  31.         // Exemplar instance of the resource that should be storable                                                                                                                                              
  32.         Storable Storable
  33.         // Function to call when resource is successfully stored                                                                                                                                                  
  34.         OnStore func()
  35. }
  36.  
  37. // Initialize a resource handler. This sets up the http handlers necessary to manage the resource.                                                                                                                
  38. func (r *Resource) Init() {
  39.         http.HandleFunc(r.ResourcePath, r.resourceHandler)
  40.         http.HandleFunc(r.ResourcePath + "/", r.resourceHandler)
  41. }
  42.  
  43. func (resource *Resource) resourceHandler(w http.ResponseWriter, r *http.Request) {
  44.         switch r.Method {
  45.         case "GET":
  46.                 resourceName := resource.getNameFromPath(r)
  47.                 if resourceName == "" {
  48.                         resources, err := resource.GetAll()
  49.                         if err != nil {
  50.                                 WriteError(w, err)
  51.                                 return
  52.                         }
  53.                         WriteAsJson(w, resources)
  54.                 } else {
  55.                         resource, err := resource.Get(resourceName)
  56.                         if err != nil {
  57.                                 WriteError(w, err)
  58.                                 return
  59.                         }
  60.                         if resource == nil {
  61.                                 http.Error(w, "", 404)
  62.                 return
  63.                         }
  64.                         WriteAsJson(w, resource)
  65.                 }
  66.         case "PUT":
  67.                 r, err := resource.FetchFromBody(r)
  68.         if err != nil {
  69.                         WriteError(w, err)
  70.                         return
  71.                 }
  72.  
  73.                 err = resource.AddResource(r)
  74.                 if err != nil {
  75.                         WriteError(w, err)
  76.                         return
  77.                 }
  78.                 w.Write([]byte(""))
  79.                 if resource.OnStore != nil {
  80.                         resource.OnStore()
  81.                 }
  82.  
  83.         case "POST":
  84.                 resourceName := resource.getNameFromPath(r)
  85.                 entity, err := resource.FetchFromBody(r)
  86.                 if err != nil {
  87.                         WriteError(w, err)
  88.                         return
  89.                 }
  90.                 err = resource.Update(resourceName, entity)
  91.                 if err != nil {
  92.                         WriteError(w, err)
  93.                         return
  94.                 }
  95.                 w.Write([]byte(""))
  96.                 if resource.OnStore != nil {
  97.                         resource.OnStore()
  98.         }
  99.  
  100.         case "DELETE":
  101.                 resourceName := resource.getNameFromPath(r)
  102.                 err := resource.Delete(resourceName)
  103.                 if err != nil {
  104.                         WriteError(w, err)
  105.                         return
  106.                 }
  107.                 w.Write([]byte(""))
  108.         }
  109.  
  110. }
  111.  
  112.  
  113. const pathRegexpString = ".*/([^/]*)$"
  114.  
  115.  
  116. func (resource *Resource) FetchFromBody(r *http.Request) (s Storable, err error) {
  117.         s = resource.Storable.empty()
  118.         data, err := ioutil.ReadAll(r.Body)
  119.         if err != nil {
  120.                 return
  121.         }
  122.         err = json.Unmarshal(data, s)
  123.         return
  124. }
  125.  
  126. func (resource *Resource) getNameFromPath(r *http.Request) string {
  127.         requestPath := r.URL.Path
  128.         if !strings.HasPrefix(requestPath, resource.ResourcePath + "/") {
  129.                 return ""
  130.         }
  131.         matcher := regexp.MustCompile(pathRegexpString)
  132.         result := matcher.FindStringSubmatch(r.URL.Path)
  133.  
  134.         if len(result) < 2 {
  135.                 return ""
  136.         }
  137.         return result[1]
  138. }
  139.  
  140. func (resource *Resource) GetAll() (s []Storable, err error) {
  141.         s = make([]Storable, 0)
  142.  
  143.         err = resource.Db.View(func(tx *bolt.Tx) error {
  144.                 b := tx.Bucket(resource.BucketName)
  145.                 if b == nil {
  146.                         return nil
  147.                 }
  148.                 return b.ForEach(func(k, v []byte) error {
  149.                         elem := resource.Storable.empty()
  150.                         err = json.Unmarshal(v, elem)
  151.                         if err != nil {
  152.                                 return err
  153.                         }
  154.                         s = append(s, elem)
  155.                         return nil
  156.                 })
  157.         })
  158.         return
  159. }
  160.  
  161. // Fetch the specified resource. Returns the resource and an error code; if                                                                                                                                        
  162. // resource is nil, it didn't exist.                                                                                                                                                                              
  163. func (resource *Resource) Get(resourceName string) (s Storable, err error) {
  164.         err = resource.Db.View(func(tx *bolt.Tx) error {
  165.                 b := tx.Bucket(resource.BucketName)
  166.                 if b == nil {
  167.                         return nil
  168.                 }
  169.                 v := b.Get([]byte(resourceName))
  170.                 if v == nil {
  171.                         s = nil
  172.                         return nil
  173.                 }
  174.                 s = resource.Storable.empty()
  175.                 err = json.Unmarshal(v, s)
  176.                 if err != nil {
  177.                         return err
  178.                 }
  179.                 return nil
  180.         })
  181.         return
  182. }
  183.  
  184.  
  185. func (resource *Resource) AddResource(r Storable) error {
  186.         store := stow.NewJSONStore(resource.Db, resource.BucketName)
  187.         return store.Put(r.key(), r)
  188. }
  189.  
  190. func (resource *Resource) Update(resourceName string, r Storable) error {
  191.         // TODO(mtomczak): Should confirm old name existed.                                                                                                                                                        
  192.         if resourceName != string(r.key()) {
  193.                 err := resource.Delete(resourceName)
  194.                 if err != nil {
  195.                         return err
  196.                 }
  197.         }
  198.         return resource.AddResource(r)
  199. }
  200.  
  201. func (resource *Resource) Delete(resourceName string) error {
  202.         return resource.Db.Update(func(tx *bolt.Tx) error {
  203.                 bucket := tx.Bucket(resource.BucketName)
  204.                 if bucket == nil {
  205.                         return nil
  206.                 }
  207.                 return bucket.Delete([]byte(resourceName))
  208.         })
  209. }
Advertisement
Add Comment
Please, Sign In to add comment