Skip to content

Commit

Permalink
Updated server to use reorganized code with helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
yanatan16 committed Aug 19, 2012
1 parent 1f12e54 commit 25beacf
Show file tree
Hide file tree
Showing 17 changed files with 495 additions and 2,650 deletions.
30 changes: 6 additions & 24 deletions app.go
@@ -1,13 +1,13 @@
package main

import (
_ "expvar"
"flag"
"fmt"
"github.com/hoisie/web"
"github.com/yanatan16/go-todo-app/controller"
"github.com/yanatan16/go-todo-app/helpers"
"github.com/yanatan16/go-todo-app/model"
"github.com/yanatan16/go-todo-app/view"
"io/ioutil"
"log"
)

Expand All @@ -23,24 +23,22 @@ func Start() {
server := web.NewServer()

// Model
model.Init(prod)
model.Init(server)

// View
view.Init(server, templateRoot)

// Controller
controller.Init(server)

// Static files (non-prod)
if serveStaticFiles {
ServeStatic("./static", server)
helpers.ServeStatic("./static", server)
}

log.Printf("Now starting Todo App Server on port %d...", port)
if useCGI {
server.RunFcgi(fmt.Sprintf("0.0.0.0:%d", port))
} else {
server.Run(fmt.Sprintf("0.0.0.0:%d", port))
}
server.Run(fmt.Sprintf("0.0.0.0:%d", port))
}

func main() {
Expand All @@ -62,19 +60,3 @@ func main() {
// Run the server
Start()
}

func ServeStatic(root string, svr *web.Server) {
svr.Get("/static(.*)",
func(ctx *web.Context, path string) {
fn := root + path
log.Printf("Serving static file %s", fn)
data, err := ioutil.ReadFile(fn)
if err != nil {
ctx.NotFound("File not found!")
log.Println("Could not read file!", err)
return
}

ctx.WriteString(string(data))
})
}
19 changes: 19 additions & 0 deletions client/views/List.js
@@ -0,0 +1,19 @@
(function() {

var ListView = Backbone.View.extend({
el: $('body'); // attaches 'this.el' to an existing element

initialize: function() {
_.bindAll(this, 'render'); // fixes loss of context for 'this' within methods

this.render();
}

render: function() {
$(this.el).append("<ul><li>hello world</li></ul>");
}
});

var listView = new ListView();

})();
132 changes: 132 additions & 0 deletions helpers/controller.go
@@ -0,0 +1,132 @@
// The controllers represent the actions that can be
// taken with the web application.
package helpers

import (
"encoding/json"
"fmt"
"github.com/hoisie/web"
"io/ioutil"
)

// A Controller for a corresponding Backbone Model object.
type Controller interface {
// Create model object based on parameters
// attr is a json-formatted string of attributes
// Return a json-formattable object of all model attributes
Create(attr string) (interface{}, error)
// Read a model object back
// ID may be empty string
// Return a json-formattable object of all model attributes
Read(id string) (interface{}, error)
// Update a model object based on parameters.
// ID is required and will be non-empty
// attr is a json-formatted string of attributes
// Return a json-formattable object of updated model attributes
// If no attributes other than the updated ones changed, it is acceptable to return nil
Update(id, attr string) (interface{}, error)
// Delete a model object.
// ID is required and will be non-empty
Delete(id string) error
}

type Context struct {
*web.Context
}

func (ctx *Context) readBody() (string, error) {
str, err := ioutil.ReadAll(ctx.Request.Body)
if err != nil {
return "", err
}

return string(str), nil
}

func (ctx *Context) writeJson(m interface{}) {
str, err := json.Marshal(m)
if err != nil {
ctx.Abort(500, "Error marshalling map: "+err.Error())
}
ctx.WriteString(string(str))
}

func (ctx *Context) writeError(err error) {
ctx.WriteHeader(400)
rv := map[string]string{
"error": err.Error(),
}
str, err2 := json.Marshal(rv)
if err2 != nil {
ctx.Abort(500, fmt.Sprintf("Error mashalling error(%s): %s", err.Error(), err2.Error()))
}
ctx.WriteString(string(str))
}

func BindController(svr *web.Server, path string, ctrl Controller) {
// Create
svr.Post(path, func(wctx *web.Context) {
ctx := &Context{wctx}
body, err := ctx.readBody()
if err != nil {
ctx.writeError(err)
return
}

ret, err := ctrl.Create(body)
if err != nil {
ctx.writeError(err)
return
}

ctx.writeJson(ret)
})

// Read
svr.Get(path+"/?(.*)", func(wctx *web.Context, id string) {
ctx := &Context{wctx}
ret, err := ctrl.Read(id)
if err != nil {
ctx.writeError(err)
return
}

ctx.writeJson(ret)
})

// Update
svr.Put(path+"/(.+)", func(wctx *web.Context, id string) {
ctx := &Context{wctx}
body, err := ctx.readBody()

if err != nil {
ctx.writeError(err)
return
}

ret, err := ctrl.Update(id, body)
if err != nil {
ctx.writeError(err)
return
}

// Accept nil responses
if ret != nil {
ctx.writeJson(ret)
} else {
ctx.NotModified()
}
})

// Delete
svr.Delete(path+"/(.+)", func(wctx *web.Context, id string) {
ctx := &Context{wctx}
err := ctrl.Delete(id)
if err != nil {
ctx.writeError(err)
return
}

ctx.NotModified()
})
}
121 changes: 121 additions & 0 deletions helpers/controller_test.go
@@ -0,0 +1,121 @@
package helpers

import (
"bytes"
"encoding/json"
"fmt"
"github.com/hoisie/web"
"io"
"net/http"
"testing"
"time"
)

const (
port int = 16123
path string = "/test"
)

type ExampleController map[string]string

func (e ExampleController) Create(attr string) (interface{}, error) {
return e, nil
}
func (e ExampleController) Read(id string) (interface{}, error) {
return e, nil
}
func (e ExampleController) Update(id, attr string) (interface{}, error) {
return e, nil
}
func (e ExampleController) Delete(id string) error {
return nil
}

func ExampleInitController(svr *web.Server, path string) {
m := map[string]string{"hello": "world"}
ctrl := ExampleController(m)

BindController(svr, path, ctrl)
}

func init() {
svr := web.NewServer()
ExampleInitController(svr, path)
go svr.Run(fmt.Sprintf("0.0.0.0:%d", port))
<-time.After(10 * time.Millisecond)
}

func checkRespBody(t *testing.T, r io.Reader) {
m := map[string]string{}
j := json.NewDecoder(r)
err := j.Decode(&m)
if err != nil {
t.Error("Response Body was not decodable into json", err)
} else if m["hello"] != "world" {
t.Error("Response Body did not contain correct map", m)
}
}

func TestCreate(t *testing.T) {
url := fmt.Sprintf("http://127.0.0.1:%d%s", port, path)
client := &http.Client{}

cbody := bytes.NewBuffer([]byte(`{}`))
creq, err := http.NewRequest("POST", url, cbody)
if err != nil {
t.Fatal("Error creating the create request", err)
}
cres, err := client.Do(creq)
if err != nil {
t.Fatal("Error performing create request", err)
}
defer cres.Body.Close()
checkRespBody(t, cres.Body)
}

func TestRead(t *testing.T) {
url := fmt.Sprintf("http://127.0.0.1:%d%s/id", port, path)
client := &http.Client{}

creq, err := http.NewRequest("GET", url, nil)
if err != nil {
t.Fatal("Error creating the create request", err)
}
cres, err := client.Do(creq)
if err != nil {
t.Fatal("Error performing create request", err)
}
defer cres.Body.Close()
checkRespBody(t, cres.Body)
}

func TestUpdate(t *testing.T) {
url := fmt.Sprintf("http://127.0.0.1:%d%s/id", port, path)
client := &http.Client{}

creq, err := http.NewRequest("PUT", url, nil)
if err != nil {
t.Fatal("Error creating the create request", err)
}
cres, err := client.Do(creq)
if err != nil {
t.Fatal("Error performing create request", err)
}
defer cres.Body.Close()
checkRespBody(t, cres.Body)
}

func TestDelete(t *testing.T) {
url := fmt.Sprintf("http://127.0.0.1:%d%s/id", port, path)
client := &http.Client{}

creq, err := http.NewRequest("DELETE", url, nil)
if err != nil {
t.Fatal("Error creating the create request", err)
}
cres, err := client.Do(creq)
if err != nil {
t.Fatal("Error performing create request", err)
}
defer cres.Body.Close()
}
25 changes: 25 additions & 0 deletions helpers/static.go
@@ -0,0 +1,25 @@
package helpers

import (
"github.com/hoisie/web"
"io/ioutil"
"log"
)

// Web.go handler to serve static files
// This should not be used in production
func ServeStatic(root string, svr *web.Server) {
svr.Get("/static(.*)",
func(ctx *web.Context, path string) {
fn := root + path
log.Printf("Serving static file %s", fn)
data, err := ioutil.ReadFile(fn)
if err != nil {
ctx.NotFound("File not found!")
log.Println("Could not read file!", err)
return
}

ctx.WriteString(string(data))
})
}

0 comments on commit 25beacf

Please sign in to comment.