Skip to content

Instantly share code, notes, and snippets.

@athiwatp
Forked from nmerouze/main.go
Created August 27, 2020 05:27
Show Gist options
  • Save athiwatp/2e0d4642826c684490d88894df0037c4 to your computer and use it in GitHub Desktop.
Save athiwatp/2e0d4642826c684490d88894df0037c4 to your computer and use it in GitHub Desktop.

Revisions

  1. @nmerouze nmerouze revised this gist Dec 27, 2014. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion main.go
    Original file line number Diff line number Diff line change
    @@ -226,8 +226,8 @@ func (c *appContext) createTeaHandler(w http.ResponseWriter, r *http.Request) {
    panic(err)
    }

    w.WriteHeader(201)
    w.Header().Set("Content-Type", "application/vnd.api+json")
    w.WriteHeader(201)
    json.NewEncoder(w).Encode(body)
    }

  2. @nmerouze nmerouze created this gist Dec 5, 2014.
    310 changes: 310 additions & 0 deletions main.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,310 @@
    package main

    import (
    "encoding/json"
    "log"
    "net/http"
    "reflect"
    "time"

    "github.com/gorilla/context"
    "github.com/julienschmidt/httprouter"
    "github.com/justinas/alice"
    "gopkg.in/mgo.v2"
    "gopkg.in/mgo.v2/bson"
    )

    // Repo

    type Tea struct {
    Id bson.ObjectId `json:"id,omitempty" bson:"_id,omitempty"`
    Name string `json:"name"`
    Category string `json:"category"`
    }

    type TeasCollection struct {
    Data []Tea `json:"data"`
    }

    type TeaResource struct {
    Data Tea `json:"data"`
    }

    type TeaRepo struct {
    coll *mgo.Collection
    }

    func (r *TeaRepo) All() (TeasCollection, error) {
    result := TeasCollection{[]Tea{}}
    err := r.coll.Find(nil).All(&result.Data)
    if err != nil {
    return result, err
    }

    return result, nil
    }

    func (r *TeaRepo) Find(id string) (TeaResource, error) {
    result := TeaResource{}
    err := r.coll.FindId(bson.ObjectIdHex(id)).One(&result.Data)
    if err != nil {
    return result, err
    }

    return result, nil
    }

    func (r *TeaRepo) Create(tea *Tea) error {
    id := bson.NewObjectId()
    _, err := r.coll.UpsertId(id, tea)
    if err != nil {
    return err
    }

    tea.Id = id

    return nil
    }

    func (r *TeaRepo) Update(tea *Tea) error {
    err := r.coll.UpdateId(tea.Id, tea)
    if err != nil {
    return err
    }

    return nil
    }

    func (r *TeaRepo) Delete(id string) error {
    err := r.coll.RemoveId(bson.ObjectIdHex(id))
    if err != nil {
    return err
    }

    return nil
    }

    // Errors

    type Errors struct {
    Errors []*Error `json:"errors"`
    }

    type Error struct {
    Id string `json:"id"`
    Status int `json:"status"`
    Title string `json:"title"`
    Detail string `json:"detail"`
    }

    func WriteError(w http.ResponseWriter, err *Error) {
    w.Header().Set("Content-Type", "application/vnd.api+json")
    w.WriteHeader(err.Status)
    json.NewEncoder(w).Encode(Errors{[]*Error{err}})
    }

    var (
    ErrBadRequest = &Error{"bad_request", 400, "Bad request", "Request body is not well-formed. It must be JSON."}
    ErrNotAcceptable = &Error{"not_acceptable", 406, "Not Acceptable", "Accept header must be set to 'application/vnd.api+json'."}
    ErrUnsupportedMediaType = &Error{"unsupported_media_type", 415, "Unsupported Media Type", "Content-Type header must be set to: 'application/vnd.api+json'."}
    ErrInternalServer = &Error{"internal_server_error", 500, "Internal Server Error", "Something went wrong."}
    )

    // Middlewares

    func recoverHandler(next http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
    defer func() {
    if err := recover(); err != nil {
    log.Printf("panic: %+v", err)
    WriteError(w, ErrInternalServer)
    }
    }()

    next.ServeHTTP(w, r)
    }

    return http.HandlerFunc(fn)
    }

    func loggingHandler(next http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
    t1 := time.Now()
    next.ServeHTTP(w, r)
    t2 := time.Now()
    log.Printf("[%s] %q %v\n", r.Method, r.URL.String(), t2.Sub(t1))
    }

    return http.HandlerFunc(fn)
    }

    func acceptHandler(next http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
    if r.Header.Get("Accept") != "application/vnd.api+json" {
    WriteError(w, ErrNotAcceptable)
    return
    }

    next.ServeHTTP(w, r)
    }

    return http.HandlerFunc(fn)
    }

    func contentTypeHandler(next http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
    if r.Header.Get("Content-Type") != "application/vnd.api+json" {
    WriteError(w, ErrUnsupportedMediaType)
    return
    }

    next.ServeHTTP(w, r)
    }

    return http.HandlerFunc(fn)
    }

    func bodyHandler(v interface{}) func(http.Handler) http.Handler {
    t := reflect.TypeOf(v)

    m := func(next http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
    val := reflect.New(t).Interface()
    err := json.NewDecoder(r.Body).Decode(val)

    if err != nil {
    WriteError(w, ErrBadRequest)
    return
    }

    if next != nil {
    context.Set(r, "body", val)
    next.ServeHTTP(w, r)
    }
    }

    return http.HandlerFunc(fn)
    }

    return m
    }

    // Main handlers

    type appContext struct {
    db *mgo.Database
    }

    func (c *appContext) teasHandler(w http.ResponseWriter, r *http.Request) {
    repo := TeaRepo{c.db.C("teas")}
    teas, err := repo.All()
    if err != nil {
    panic(err)
    }

    w.Header().Set("Content-Type", "application/vnd.api+json")
    json.NewEncoder(w).Encode(teas)
    }

    func (c *appContext) teaHandler(w http.ResponseWriter, r *http.Request) {
    params := context.Get(r, "params").(httprouter.Params)
    repo := TeaRepo{c.db.C("teas")}
    tea, err := repo.Find(params.ByName("id"))
    if err != nil {
    panic(err)
    }

    w.Header().Set("Content-Type", "application/vnd.api+json")
    json.NewEncoder(w).Encode(tea)
    }

    func (c *appContext) createTeaHandler(w http.ResponseWriter, r *http.Request) {
    body := context.Get(r, "body").(*TeaResource)
    repo := TeaRepo{c.db.C("teas")}
    err := repo.Create(&body.Data)
    if err != nil {
    panic(err)
    }

    w.WriteHeader(201)
    w.Header().Set("Content-Type", "application/vnd.api+json")
    json.NewEncoder(w).Encode(body)
    }

    func (c *appContext) updateTeaHandler(w http.ResponseWriter, r *http.Request) {
    params := context.Get(r, "params").(httprouter.Params)
    body := context.Get(r, "body").(*TeaResource)
    body.Data.Id = bson.ObjectIdHex(params.ByName("id"))
    repo := TeaRepo{c.db.C("teas")}
    err := repo.Update(&body.Data)
    if err != nil {
    panic(err)
    }

    w.WriteHeader(204)
    w.Write([]byte("\n"))
    }

    func (c *appContext) deleteTeaHandler(w http.ResponseWriter, r *http.Request) {
    params := context.Get(r, "params").(httprouter.Params)
    repo := TeaRepo{c.db.C("teas")}
    err := repo.Delete(params.ByName("id"))
    if err != nil {
    panic(err)
    }

    w.WriteHeader(204)
    w.Write([]byte("\n"))
    }

    // Router

    type router struct {
    *httprouter.Router
    }

    func (r *router) Get(path string, handler http.Handler) {
    r.GET(path, wrapHandler(handler))
    }

    func (r *router) Post(path string, handler http.Handler) {
    r.POST(path, wrapHandler(handler))
    }

    func (r *router) Put(path string, handler http.Handler) {
    r.PUT(path, wrapHandler(handler))
    }

    func (r *router) Delete(path string, handler http.Handler) {
    r.DELETE(path, wrapHandler(handler))
    }

    func NewRouter() *router {
    return &router{httprouter.New()}
    }

    func wrapHandler(h http.Handler) httprouter.Handle {
    return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
    context.Set(r, "params", ps)
    h.ServeHTTP(w, r)
    }
    }

    func main() {
    session, err := mgo.Dial("localhost")
    if err != nil {
    panic(err)
    }
    defer session.Close()
    session.SetMode(mgo.Monotonic, true)

    appC := appContext{session.DB("test")}
    commonHandlers := alice.New(context.ClearHandler, loggingHandler, recoverHandler, acceptHandler)
    router := NewRouter()
    router.Get("/teas/:id", commonHandlers.ThenFunc(appC.teaHandler))
    router.Put("/teas/:id", commonHandlers.Append(contentTypeHandler, bodyHandler(TeaResource{})).ThenFunc(appC.updateTeaHandler))
    router.Delete("/teas/:id", commonHandlers.ThenFunc(appC.deleteTeaHandler))
    router.Get("/teas", commonHandlers.ThenFunc(appC.teasHandler))
    router.Post("/teas", commonHandlers.Append(contentTypeHandler, bodyHandler(TeaResource{})).ThenFunc(appC.createTeaHandler))
    http.ListenAndServe(":8080", router)
    }