-
Notifications
You must be signed in to change notification settings - Fork 0
/
service.go
168 lines (146 loc) · 5.34 KB
/
service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package service
import (
"context"
"encoding/json"
"log"
"net/http"
"os"
"time"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/letung3105/todosvr/storage"
"rsc.io/quote/v3"
)
// Todo chứa các hàm số dùng để khởi tạo và chạy dịch vụ API.
type Todo struct {
router chi.Router
storage *storage.Todo
}
// NewTodo khởi tạo dịch vụ API.
func NewTodo(storage *storage.Todo) *Todo {
t := Todo{
router: chi.NewRouter(),
storage: storage,
}
t.routes()
return &t
}
// ServeHTTP dùng để gọi hàm số cung cấp bởi router nhằm thoả mãn interface http.Handler.
func (todo *Todo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
todo.router.ServeHTTP(w, r)
}
// routes cài đặt các đường dẫn HTTP được hỗ trợ bởi dịch vụ.
// TODO: thêm handlers để xử lý thao tác CRUD
func (todo *Todo) routes() {
notImpl := func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(map[string]interface{}{"message": "NOT IMPLEMENTED"})
}
todo.router.Use(
HeaderResponseContentTypeJSON,
RequestLogger(log.New(os.Stdout, "", log.LstdFlags)),
middleware.Recoverer,
)
todo.router.Get("/hello", todo.GetHello())
todo.router.Post("/todo", todo.CreateOneTask())
todo.router.Get("/todo", notImpl)
todo.router.Route("/todo/{id}", func(r chi.Router) {
r.Use(TaskIDCtx)
r.Get("/", notImpl)
r.Put("/", notImpl)
r.Delete("/", notImpl)
})
}
// CreateOneTask lấy thông tin của một tác vụ từ body của request rồi gửi cho storage.Todo để xứ lý.
// @Summary Create one task
// @Description tạo một tác vụ mới
// @Accept json
// @Produce json
// @Param task body service.requestCreateOneTask true "tác vụ được thêm vào"
// @Failure 500 {object} service.responseCreateOneTaskErr
// @Failure 400 {object} service.responseCreateOneTaskErr
// @Success 200 {object} service.responseCreateOneTask
// @Router /todo [post]
func (todo *Todo) CreateOneTask() http.HandlerFunc {
// NOTE: chúng ta không cần kiểm tra lỗi trả về từ hàm số `Encode` vì:
// + các struct không chứa các kiểu dữ liệu không được hỗ trợ bởi JSON
// + các struct không phải là cyclic data structure
// Đọc thêm tại https://golang.org/pkg/encoding/json/#Marshal
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
request := requestCreateOneTask{}
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
log.Println(err)
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(responseCreateOneTaskErr{err.Error()})
return
}
// TODO: kiểm tra thông tin của `task`
newTask, err := todo.storage.CreateOne(r.Context(), request.Task)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(responseCreateOneTaskErr{err.Error()})
return
}
response := responseCreateOneTask{Task: *newTask}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
})
}
type requestCreateOneTask struct {
storage.Task
}
type responseCreateOneTaskErr struct {
Err string `json:"error"`
}
type responseCreateOneTask struct {
storage.Task
}
// GetHello trả về một handler có thể xử lý yêu cầu HTTP và trả về chuỗi ký tự "Hello, world."
// @Summary Hello world
// @Description trả về chuỗi kí tự "Hello World"
// @Produce json
// @Success 200 {object} service.responseGetHello
// @Router /hello [get]
func (todo *Todo) GetHello() http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(responseGetHello{quote.HelloV3()})
})
}
type responseGetHello struct {
Message string `json:"message"`
}
// ctxKey là kiểu dữ liệu được dùng để chỉ đến các giá trị được lưu trong request context,
// do bất cứ package nào cũng có thể sử dụng request context nên chúng ta phải có một kiểu
// dữ liệu riêng cho mỗi package để tránh việc các package ghi đè lên thông tin của nhau
type ctxKey string
// TaskIDCtxKey tên của giá trị nằm trong request context
var TaskIDCtxKey = ctxKey("TaskIDCtxKey")
// TaskIDCtx lấy id của task từ đường dẫn url
func TaskIDCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), TaskIDCtxKey, chi.URLParam(r, "id"))
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// Middleware tạo một handler mới dựa trên handler được truyền vào.
type Middleware func(http.Handler) http.Handler
// RequestLogger in ra thông tin của yêu cầu vừa được xử lý.
func RequestLogger(logger *log.Logger) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
begin := time.Now()
next.ServeHTTP(w, r)
logger.Printf(
"Handle %s %s from %s in %v",
r.Method, r.URL, r.RemoteAddr, time.Since(begin),
)
})
}
}
// HeaderResponseContentTypeJSON chèn thông tin định dạng JSON vào tiêu đề của gói tin được gửi đi.
func HeaderResponseContentTypeJSON(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
next.ServeHTTP(w, r)
})
}