This repository has been archived by the owner on Jun 15, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
server.go
207 lines (186 loc) · 5.37 KB
/
server.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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package kvs
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
"time"
)
// KeyValue is used for unmarshalling JSON object in POST request.
type KeyValue struct {
Data []Data `json:"data"`
}
// Data is the element of array. It keeps Key and Value.
type Data struct {
Key string `json:"key"`
Value string `json:"value"`
}
// Response is struct type for JSON response body. It is used for get and save.
type Response struct {
Key string `json:"key"`
Value string `json:"value"`
Result string `json:"result"`
}
const (
headerContent = "Content-Type"
contentValue = "application/json"
)
// Create creates database and Kvs object. It creates database and returns Kvs
// object. If HTTP address is empty, localhost and default port is used.
// In contrast, dbName name needs to be specified. If it is not specified, it
// returns error and the database is not created. Kvs saves the data inside the
// map to the file periodically. User needs to specify the time interval as
// duration. For example, if 2*time.Minute is passed to duration parameter,
// the data that stores in memory, map, saves to the file.
func Create(addr string, dbName string, duration time.Duration) (*Kvs, error) {
if dbName == "" {
return nil, fmt.Errorf("empty database name is not valid")
}
if addr == "" {
addr = "localhost:1234"
}
return open(dbName, addr, duration)
}
// Open creates an HTTP connection. HTTP connection listens HTTP requests from
// client. Create function needs to be called before calling Open function.
func (k *Kvs) Open() {
log.Printf("Kvs server running on %s...", k.Addr)
http.HandleFunc("/set", k.set)
http.HandleFunc("/get/", k.get)
http.HandleFunc("/save", k.save)
log.Fatal(http.ListenAndServe(k.Addr, nil))
}
// set is the /set API endpoint for setting a key-value pair.
func (k *Kvs) set(w http.ResponseWriter, r *http.Request) {
log.Printf("HTTP method: %s", r.Method)
log.Printf("Endpoint: %s", r.URL)
log.Printf("Request header: %s", r.Header)
if r.Method != http.MethodPost {
err := fmt.Sprintf("Wrong HTTP request. You need to send POST request.")
log.Printf(err)
http.Error(w, err, http.StatusBadRequest)
return
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf(err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer r.Body.Close()
log.Printf("Body read.")
var keyVal *KeyValue
err = json.Unmarshal(body, &keyVal)
if err != nil {
log.Printf(err.Error())
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
log.Printf("Body unmarshalled to KeyVal struct type.")
for i := 0; i < len(keyVal.Data); i++ {
key, val := keyVal.Data[i].Key, keyVal.Data[i].Value
k.Set(key, val)
}
log.Printf("Save key-value pair to memory.")
data := Response{
Result: "OK",
}
j, err := json.Marshal(data)
if err != nil {
log.Printf(err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set(headerContent, contentValue)
w.WriteHeader(http.StatusOK)
_, err = w.Write(j)
if err != nil {
return
}
log.Printf("Status code: %d", http.StatusOK)
log.Printf("Response header: %s", w.Header().Get(headerContent))
}
// get returns the value of the key.
func (k *Kvs) get(w http.ResponseWriter, r *http.Request) {
log.Printf("HTTP Method: %s", r.Method)
log.Printf("Endpoint: %s", r.URL)
log.Printf("Request Header: %s", r.Header)
if r.Method != http.MethodGet {
err := fmt.Sprintf("Wrong HTTP request. You need to send GET request.")
log.Printf(err)
http.Error(w, err, http.StatusBadRequest)
return
}
u := strings.Split(r.URL.String(), "/")
if u[len(u)-1] == "" && u[len(u)-2] == "get" {
err := fmt.Sprintf("Key is missing.")
log.Printf(err)
http.Error(w, err, http.StatusBadRequest)
return
}
log.Printf("URL parsed.")
key := u[len(u)-1]
value := k.Get(key)
resp := Response{
Key: key,
Value: value,
Result: "OK",
}
j, err := json.Marshal(resp)
if err != nil {
log.Printf(err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Printf("Response body marshalled.")
w.Header().Set(headerContent, contentValue)
w.WriteHeader(http.StatusOK)
_, err = w.Write(j)
if err != nil {
return
}
log.Printf("%s=%s", key, value)
log.Printf("Status code: %d", http.StatusOK)
log.Printf("Response header: %s", w.Header().Get(headerContent))
}
// save writes the data from map to file.
func (k *Kvs) save(w http.ResponseWriter, r *http.Request) {
log.Printf("HTTP method: %s", r.Method)
log.Printf("Endpoint: %s", r.URL)
log.Printf("Request header: %s", r.Header)
if r.Method != http.MethodPut {
err := fmt.Sprintf("Wrong HTTP request. You need to send PUT request.")
log.Printf(err)
http.Error(w, err, http.StatusBadRequest)
return
}
k.mu.Lock()
err := k.write()
k.mu.Unlock()
if err != nil {
log.Printf(err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Printf("Data saved to database file.")
resp := Response{
Result: "Saved",
}
j, err := json.Marshal(resp)
if err != nil {
log.Printf(err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Printf("Response body marshalled.")
w.Header().Set(headerContent, contentValue)
w.WriteHeader(http.StatusOK)
_, err = w.Write(j)
if err != nil {
return
}
log.Printf("Status code: %d", http.StatusOK)
log.Printf("Response header: %s", w.Header().Get(headerContent))
}