Skip to content

Instantly share code, notes, and snippets.

@sgykfjsm
Last active March 13, 2016 13:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sgykfjsm/1dd9a8eee1f70a7068c9 to your computer and use it in GitHub Desktop.
Save sgykfjsm/1dd9a8eee1f70a7068c9 to your computer and use it in GitHub Desktop.
simple json api by Go version2. This code is based on http://thenewstack.io/make-a-restful-json-api-go/ and http://openmymind.net/Go-action-responses/
#!/usr/bin/env bash
for i in $(seq 1 3)
do
curl --silent -XGET "localhost:8080/todos/${i}" \
--write-out '\nGET, %{http_code}, %{content_type}\n'
sleep 1
done
curl --silent -XGET "localhost:8080/todos/foo" \
--write-out '\nGET, %{http_code}, %{content_type}\n'
curl --silent \
-XPOST \
-H "Content-Type: application/json" -d '{"name":"New Todo"}' \
"localhost:8080/todos" \
--write-out '\nPOST, %{http_code}, %{content_type}\n'
sleep 1
curl --silent -XGET "localhost:8080/todos/3" \
--output /dev/null \
--write-out '\nGET, %{http_code}, %{content_type}\n'
curl --silent \
-XDELETE \
"localhost:8080/todos/3" \
--write-out '\nDELETE, %{http_code}, %{content_type}\n'
curl --silent -XGET "localhost:8080/todos/3" \
--write-out '\nGET, %{http_code}, %{content_type}\n'
$ bash ./curl.sh
* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /todos/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Date: Sun, 13 Mar 2016 13:29:29 GMT
< Content-Length: 83
<
* Connection #0 to host localhost left intact
{"id":1,"name":"Write presentation","completed":false,"due":"0001-01-01T00:00:00Z"}
GET, 200, application/json
---------------------------
* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /todos/2 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Date: Sun, 13 Mar 2016 13:29:30 GMT
< Content-Length: 76
<
* Connection #0 to host localhost left intact
{"id":2,"name":"Host meetup","completed":false,"due":"0001-01-01T00:00:00Z"}
GET, 200, application/json
---------------------------
* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /todos/3 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Date: Sun, 13 Mar 2016 13:29:31 GMT
< Content-Length: 4
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host localhost left intact
null
GET, 404, text/plain; charset=utf-8
---------------------------
* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /todos/foo HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 422 status code 422
< Content-Type: application/json
< Date: Sun, 13 Mar 2016 13:29:32 GMT
< Content-Length: 23
<
* Connection #0 to host localhost left intact
todoId should be number
GET, 422, application/json
---------------------------
* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> POST /todos HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 19
>
* upload completely sent off: 19 out of 19 bytes
< HTTP/1.1 201 Created
< Content-Type: application/json
< Location: http://localhost:8080//todos/3
< Date: Sun, 13 Mar 2016 13:29:32 GMT
< Content-Length: 73
<
* Connection #0 to host localhost left intact
{"id":3,"name":"New Todo","completed":false,"due":"0001-01-01T00:00:00Z"}
POST, 201, application/json
---------------------------
* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /todos/3 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Date: Sun, 13 Mar 2016 13:29:33 GMT
< Content-Length: 73
<
{ [73 bytes data]
* Connection #0 to host localhost left intact
GET, 200, application/json
---------------------------
* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> DELETE /todos/3 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 204 No Content
< Date: Sun, 13 Mar 2016 13:29:34 GMT
<
* Connection #0 to host localhost left intact
DELETE, 204,
---------------------------
* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /todos/3 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Date: Sun, 13 Mar 2016 13:29:34 GMT
< Content-Length: 4
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host localhost left intact
null
GET, 404, text/plain; charset=utf-8
---------------------------
package main
import (
"net/http"
"strconv"
"github.com/gorilla/mux"
)
type MyHandle func(*http.Request) Response
func IDShouldBeInt(h func(r *http.Request) Response, name string) MyHandle {
return Logging(func(r *http.Request) Response {
_, err := strconv.Atoi(mux.Vars(r)["todoId"])
if err != nil {
return Error(422, "todoId should be number", err)
}
return h(r)
}, name)
}
package main
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
func Index(r *http.Request) Response {
return Respond(http.StatusOK, "Welcmoe")
}
func TodoIndex(r *http.Request) Response {
return Json(http.StatusOK, todos)
}
func TodoShow(r *http.Request) Response {
id, _ := strconv.Atoi(mux.Vars(r)["todoId"])
t := RepoFindTodo(id)
if t.ID == 0 && t.Name == "" {
return Empty(http.StatusNotFound)
}
return Json(http.StatusOK, t)
}
func TodoCreate(r *http.Request) Response {
var todo Todo
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576)) // 1MiB
if err != nil {
return Error(http.StatusInternalServerError, "request body is too large", err)
}
defer r.Body.Close()
if err := json.Unmarshal(body, &todo); err != nil {
return Error(http.StatusInternalServerError, "failed marshalling json", err)
}
t := RepoCreateTodo(todo)
location := fmt.Sprintf("http://%s/%s/%d", r.Host, r.URL.Path, t.ID)
return Created(http.StatusCreated, t, location)
}
func TodoDelete(r *http.Request) Response {
id, _ := strconv.Atoi(mux.Vars(r)["todoId"])
if err := RepoDestroyTodo(id); err != nil {
return Empty(http.StatusNotFound)
}
return Empty(204) // 204 No Content
}
package main
import (
"log"
"net/http"
"time"
)
var logger = func(method, uri, name string, status int, start time.Time) {
log.Printf("\"method\":%q \"uri\":%q \"name\":%q \"status\":%d \"time\":%q", method, uri, name, status, time.Since(start))
}
func Logging(h func(r *http.Request) Response, name string) MyHandle {
return func(r *http.Request) Response {
start := time.Now()
result := h(r)
logger(r.Method, r.URL.Path, name, result.Status(), start)
return result
}
}
package main
import (
"log"
"net/http"
"github.com/gorilla/mux"
)
func decorator(h func(r *http.Request) Response) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
result := h(r)
result.Write(w)
}
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/", decorator(Logging(Index, "index"))).Methods("GET")
r.HandleFunc("/todos", decorator(Logging(TodoIndex, "todo-index"))).Methods("GET")
r.HandleFunc("/todos/{todoId}", decorator(IDShouldBeInt(TodoShow, "todo-show"))).Methods("GET")
r.HandleFunc("/todos", decorator(Logging(TodoCreate, "todo-create"))).Methods("POST")
r.HandleFunc("/todos/{todoId}", decorator(IDShouldBeInt(TodoDelete, "todo-delete"))).Methods("DELETE")
http.Handle("/", r)
log.Println("start")
log.Fatal(http.ListenAndServe(":8080", nil))
}
package main
import "fmt"
var (
todos Todos
currentID int
)
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 Todo{}
}
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)
}
package main
import (
"encoding/json"
"log"
"net/http"
)
type Response interface {
Write(w http.ResponseWriter)
Status() int
}
type NormalResponse struct {
status int // HTTPステータスコードに対応する
body []byte // レスポンスボディに対応する
header http.Header // HTTPレスポンスヘッダーに対応する
}
func (r *NormalResponse) Write(w http.ResponseWriter) {
header := w.Header()
for k, v := range r.header {
header[k] = v
}
w.WriteHeader(r.status)
w.Write(r.body)
}
func (r *NormalResponse) Status() int {
return r.status
}
func (r *NormalResponse) Header(key, value string) *NormalResponse {
r.header.Set(key, value)
return r
}
func Empty(status int) *NormalResponse {
return Respond(status, nil)
}
func Json(status int, body interface{}) *NormalResponse {
return Respond(status, body).Header("Content-Type", "application/json")
}
func Created(status int, body interface{}, location string) *NormalResponse {
return Json(status, body).Header("Location", location)
}
func Error(status int, message string, err error) *NormalResponse {
log.Printf("%s, %s", message, err)
return Respond(status, message).Header("Content-Type", "application/json")
}
func Respond(status int, body interface{}) *NormalResponse {
var b []byte
var err error
switch t := body.(type) {
case string:
b = []byte(t)
default:
if b, err = json.Marshal(body); err != nil {
return Error(http.StatusInternalServerError, "failed marshalling json", err)
}
}
return &NormalResponse{
status: status,
body: b,
header: make(http.Header),
}
}
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment