commit 35568f0977c0e78108dbd0c13bcdabf5c9dd211a Author: Wade Wegner Date: Mon Apr 27 19:54:13 2015 -0600 Initial working prototype diff --git a/.deployment b/.deployment new file mode 100644 index 0000000..b267eba --- /dev/null +++ b/.deployment @@ -0,0 +1,2 @@ +[config] +command = GoDeploy.cmd \ No newline at end of file diff --git a/GoDeploy.cmd b/GoDeploy.cmd new file mode 100644 index 0000000..7462c23 --- /dev/null +++ b/GoDeploy.cmd @@ -0,0 +1,70 @@ +@ECHO off + +IF DEFINED WEBROOT_PATH ( + ECHO WEBROOT_PATH is %WEBROOT_PATH% + GOTO :SETUP +) + +ECHO set WEBROOT_PATH to D:\home\site\wwwroot +SET WEBROOT_PATH=D:\home\site\wwwroot + +:SETUP +SET GOROOT=D:\Program Files\go\1.4.2 +SET GOPATH=%WEBROOT_PATH%\gopath +SET GOEXE="%GOROOT%\bin\go.exe" +SET FOLDERNAME=azureapp +SET GOAZUREAPP=%WEBROOT_PATH%\gopath\src\%FOLDERNAME% + +IF EXIST %GOPATH% ( + ECHO %GOPATH% already exist + + ECHO Removing %GOAZUREAPP% + RMDIR /S /Q %GOAZUREAPP% +) else ( + ECHO creating %GOPATH%\bin + MKDIR "%GOPATH%\bin" + ECHO creating %GOPATH%\pkg + MKDIR "%GOPATH%\pkg" + ECHO creating %GOPATH%\src + MKDIR "%GOPATH%\src" +) + +ECHO creating %GOAZUREAPP% +MKDIR %GOAZUREAPP% + +ECHO -------------------------------------------- +ECHO GOROOT: %GOROOT% +ECHO GOEXE: %GOEXE% +ECHO GOPATH: %GOPATH% +ECHO GOAZUREAPP: %GOAZUREAPP% +ECHO -------------------------------------------- +ECHO copying source code to %GOAZUREAPP% + +CP error.go %GOAZUREAPP% +CP handlers.go %GOAZUREAPP% +CP logger.go %GOAZUREAPP% +CP main.go %GOAZUREAPP% +CP repo.go %GOAZUREAPP% +CP router.go %GOAZUREAPP% +CP routes.go %GOAZUREAPP% +CP todo.go %GOAZUREAPP% + +ECHO copying resources to WEBROOT_PATH +CP %DEPLOYMENT_SOURCE%\Web.Config %WEBROOT_PATH%\Web.Config -f +CP %DEPLOYMENT_SOURCE%\apiapp.json %WEBROOT_PATH%\apiapp.json + +MKDIR "%WEBROOT_PATH%\metadata" +CP %DEPLOYMENT_SOURCE%\metadata\apiDefinition.swagger.json %WEBROOT_PATH%\metadata\apiDefinition.swagger.json -f + +ECHO Resolving dependencies +CD "%GOPATH%\src" +%GOEXE% get %FOLDERNAME% + +ECHO Building ... +%GOEXE% build -o %WEBROOT_PATH%\%FOLDERNAME%.exe %FOLDERNAME% + +ECHO cleaning up ... +CD %WEBROOT_PATH% +RMDIR /S /Q %GOPATH% + +ECHO DONE! \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/Web.Config b/Web.Config new file mode 100644 index 0000000..791ecc8 --- /dev/null +++ b/Web.Config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apiapp.json b/apiapp.json new file mode 100644 index 0000000..b2c15f2 --- /dev/null +++ b/apiapp.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/schemas/2014-11-01/apiapp.json#", + "id": "GoTodoApiAppDemo", + "namespace": "microsoft.com", + "gateway": "2015-01-14", + "version": "1.0.0", + "title": "Go Todo Api App Demo", + "summary": "Build demo for showing a Go Api App", + "author": "Wade Wegner" +} \ No newline at end of file diff --git a/error.go b/error.go new file mode 100644 index 0000000..4cf7bba --- /dev/null +++ b/error.go @@ -0,0 +1,6 @@ +package main + +type jsonErr struct { + Code int `json:"code"` + Text string `json:"text"` +} \ No newline at end of file diff --git a/handlers.go b/handlers.go new file mode 100644 index 0000000..e67a955 --- /dev/null +++ b/handlers.go @@ -0,0 +1,79 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "strconv" + + "github.com/gorilla/mux" +) + +func Index(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Welcome!\n") +} + +func TodoIndex(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(todos); err != nil { + panic(err) + } +} + +func TodoShow(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + var todoId int + var err error + if todoId, err = strconv.Atoi(vars["todoId"]); err != nil { + panic(err) + } + todo := RepoFindTodo(todoId) + if todo.Id > 0 { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(todo); err != nil { + panic(err) + } + return + } + + // If we didn't find it, 404 + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusNotFound) + if err := json.NewEncoder(w).Encode(jsonErr{Code: http.StatusNotFound, Text: "Not Found"}); err != nil { + panic(err) + } + +} + +/* +Test with this curl command: +curl -H "Content-Type: application/json" -d '{"name":"New Todo"}' http://localhost:8080/todos +*/ +func TodoCreate(w http.ResponseWriter, r *http.Request) { + var todo Todo + body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576)) + if err != nil { + panic(err) + } + if err := r.Body.Close(); err != nil { + panic(err) + } + if err := json.Unmarshal(body, &todo); err != nil { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(422) // unprocessable entity + if err := json.NewEncoder(w).Encode(err); err != nil { + panic(err) + } + } + + t := RepoCreateTodo(todo) + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusCreated) + if err := json.NewEncoder(w).Encode(t); err != nil { + panic(err) + } +} \ No newline at end of file diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..129484c --- /dev/null +++ b/logger.go @@ -0,0 +1,23 @@ +package main + +import ( + "log" + "net/http" + "time" +) + +func Logger(inner http.Handler, name string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + inner.ServeHTTP(w, r) + + log.Printf( + "%s\t%s\t%s\t%s", + r.Method, + r.RequestURI, + name, + time.Since(start), + ) + }) +} \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..6a2cbf1 --- /dev/null +++ b/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "log" + "net/http" + "os" +) + +func main() { + + router := NewRouter() + + port := "8080" + if os.Getenv("HTTP_PLATFORM_PORT") != "" { + port = os.Getenv("HTTP_PLATFORM_PORT") + } + + log.Fatal(http.ListenAndServe(":" + port, router)) +} diff --git a/metadata/apiDefinition.swagger.json b/metadata/apiDefinition.swagger.json new file mode 100644 index 0000000..0019b3f --- /dev/null +++ b/metadata/apiDefinition.swagger.json @@ -0,0 +1,80 @@ +{ + "swagger": "2.0", + "info": { + "title": "Go Todo Api App Demo", + "description": "Build demo for showing a Go Api App", + "version": "1.0.0" + }, + "host": "azurewebsites.net", + "schemes": [ + "https" + ], + "basePath": "/v1", + "produces": [ + "application/json" + ], + "paths": { + "/todos": { + "get": { + "summary": "Todo Index", + "description": "Get a list of todo items\n", + "tags": [ + "Todos" + ], + "responses": { + "200": { + "description": "An array of todos", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Todo" + } + } + }, + "default": { + "description": "Unexpected error", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + } + }, + "definitions": { + "Todo": { + "properties": { + "Id": { + "type": "number", + "description": "Unique id for todo" + }, + "Name": { + "type": "string", + "description": "Name of the todo item" + }, + "Completed": { + "type": "boolean", + "description": "Is the todo item complete?." + }, + "Due": { + "type": "string", + "description": "Time the todo item is due." + } + } + }, + "Error": { + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "fields": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/repo.go b/repo.go new file mode 100644 index 0000000..65f333b --- /dev/null +++ b/repo.go @@ -0,0 +1,41 @@ +package main + +import "fmt" + +var currentId int + +var todos Todos + +// Give us some seed data +func init() { + RepoCreateTodo(Todo{Name: "Write presentation"}) + RepoCreateTodo(Todo{Name: "Host meetup"}) +} + +func RepoFindTodo(id int) Todo { + for _, t := range todos { + if t.Id == id { + return t + } + } + // return empty Todo if not found + return Todo{} +} + +//this is bad, I don't think it passes race condtions +func RepoCreateTodo(t Todo) Todo { + currentId += 1 + t.Id = currentId + todos = append(todos, t) + return t +} + +func RepoDestroyTodo(id int) error { + for i, t := range todos { + if t.Id == id { + todos = append(todos[:i], todos[i+1:]...) + return nil + } + } + return fmt.Errorf("Could not find Todo with id of %d to delete", id) +} \ No newline at end of file diff --git a/router.go b/router.go new file mode 100644 index 0000000..d8f9741 --- /dev/null +++ b/router.go @@ -0,0 +1,31 @@ +package main + +import ( + "net/http" + + "github.com/gorilla/mux" +) + +func NewRouter() *mux.Router { + + router := mux.NewRouter().StrictSlash(true) + for _, route := range routes { + var handler http.Handler + + handler = route.HandlerFunc + handler = Logger(handler, route.Name) + + router. + Methods(route.Method). + Path(route.Pattern). + Name(route.Name). + Handler(handler) + + } + + // route static content + router.PathPrefix("/").Handler(http.FileServer(http.Dir("./"))) + router.PathPrefix("/metadata").Handler(http.FileServer(http.Dir("./"))) + + return router +} \ No newline at end of file diff --git a/routes.go b/routes.go new file mode 100644 index 0000000..5dcbe39 --- /dev/null +++ b/routes.go @@ -0,0 +1,39 @@ +package main + +import "net/http" + +type Route struct { + Name string + Method string + Pattern string + HandlerFunc http.HandlerFunc +} + +type Routes []Route + +var routes = Routes{ + Route{ + "Index", + "GET", + "/", + Index, + }, + Route{ + "TodoIndex", + "GET", + "/todos", + TodoIndex, + }, + Route{ + "TodoCreate", + "POST", + "/todos", + TodoCreate, + }, + Route{ + "TodoShow", + "GET", + "/todos/{todoId}", + TodoShow, + }, +} \ No newline at end of file diff --git a/todo.go b/todo.go new file mode 100644 index 0000000..c098f6f --- /dev/null +++ b/todo.go @@ -0,0 +1,12 @@ +package main + +import "time" + +type Todo struct { + Id int `json:"id"` + Name string `json:"name"` + Completed bool `json:"completed"` + Due time.Time `json:"due"` +} + +type Todos []Todo \ No newline at end of file