5.7 کتابخانه http سمت سرور پیشرفته

5.7 کتابخانه http سمت سرور پیشرفته

در قسمت قبل با استفاده از کتابخانه net/http یک api ساده ایجاد کردیم.

در این قسمت به پیاده سازی یک سرور http برای مدیریت لیست TODO های خود میپردازیم و از چهار متد GET, POST, DELET and PATCH استفاده میکنیم.

در این پروژه از دیتابیس استفاده نمیشود. روش ذخیره سازی اطلاعات درون متغییر هاست دلیل استفاده نکردن از یک دیتابیس تمرکز این قسمت روی کتابخانه و پروتکل http است.

در ادامه به ایجاد سرور خود میپردازیم.

package main

import (
	"encoding/json"
	"fmt"
  "log"
	"net/http"
)

// todo struct  with json tags
type Todo struct {
	ID     int    `json:"id"`
	Title  string `json:"title"`
	Status bool   `json:"status"`
}

// just work as a DB for us!
var todos []Todo

// get all the items in the Todo list and write it. GET
func getTodos(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(todos)
}

// append a new data in array. POST
func addTodo(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	var todo Todo
	json.NewDecoder(r.Body).Decode(&todo)

 // NOTE: this isn't a good way to set ids in production!
	todo.ID = len(todos) + 1
	todos = append(todos, todo)

	json.NewEncoder(w).Encode(todo)
}

// change the todo status. PATCH
func updateTodo(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	var todo Todo
	json.NewDecoder(r.Body).Decode(&todo)

	for i, t := range todos {
		if t.ID == todo.ID {
			todos[i].Status = todo.Status
			json.NewEncoder(w).Encode(todos[i])
			return
		}
	}

	w.WriteHeader(http.StatusNotFound)
	json.NewEncoder(w).Encode(map[string]string{"message": "TODO not found"})
}

// remove the TODO from array. DELETE
func deleteTodo(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	var todo Todo
	json.NewDecoder(r.Body).Decode(&todo)

	for i, t := range todos {
		if t.ID == todo.ID {
			todos = append(todos[:i], todos[i+1:]...)
			json.NewEncoder(w).Encode(map[string]string{"message": "TODO deleted"})
			return
		}
	}

	w.WriteHeader(http.StatusNotFound)
	json.NewEncoder(w).Encode(map[string]string{"message": "TODO not found"})
}


func main() {
  // set routes
	http.HandleFunc("/todos", getTodos)
	http.HandleFunc("/todos/add", addTodo)
	http.HandleFunc("/todos/update", updateTodo)
	http.HandleFunc("/todos/delete", deleteTodo)

  // start server
	fmt.Println("Server starting at port 8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

پیش از توضیح نحوه تست سرور http ایجاد شده، اجازه بدهید کمی درباره دو مفهوم کلیدی Encode و Decode که در کد بالا آمده است و ممکن است برای کسانی که تازه وارد بحث برنامه نویسی شبکه شده اند کمی گنگ باشد، توضیح بدهیم.

توضیح Encode و Decode در کد #

در کدی که نوشتیم، دو عمل خیلی مهم وجود دارد: Encode و Decode. این‌ها مسئول تبدیل داده‌ها بین دنیای Go و دنیای JSON هستند.

Encode #

هر وقت بخواهیم داده‌های سمت سرور (مثل یک struct یا slice) را به صورت JSON برای کلاینت بفرستیم، از Encode استفاده می‌کنیم.

مثلاً در تابع getTodos:

json.NewEncoder(w).Encode(todos)

اینجا متغیر todos (که یک slice از structهای Todo است) گرفته می‌شود و به یک رشته JSON تبدیل می‌شود. خروجی نهایی چیزی شبیه به این خواهد بود:

[
  {"id": 1, "title": "Learn Go", "status": false},
  {"id": 2, "title": "Write Book", "status": true}
]

Decode #

وقتی کلاینت داده‌ای را به سرور می‌فرستد (مثلاً با متد POST یا PATCH)، داده‌ها معمولاً در قالب JSON ارسال می‌شوند. ما باید این JSON را به یک struct در Go تبدیل کنیم تا بتوانیم راحت با آن کار کنیم. اینجاست که Decode به کار می‌آید.

مثلاً در تابع addTodo:

json.NewDecoder(r.Body).Decode(&todo)

اینجا بدنه‌ی درخواست (r.Body) خوانده می‌شود و JSON داخل آن مثلاً:

{"title": "Buy milk", "status": false}

به یک struct از نوع Todo تبدیل می‌شود و داخل متغیر todo قرار می‌گیرد.

نحوه تست سرور http ایجاد شده #

بعد از اتمام نوشتن سرور با استفاده از یک کلاینت http مثل postman سرور خود را به روش زیر تست میکنیم:

نکته: شما می توانید کلاینت خود را خودتان با استفاده از اموزش کلاینت http در قسمت های دیگر بنویسید!

add todo #

endpoint: localhost:8080/todos/add

method:POST

request:

{
    "title":"todo1 test"
}

response:

{
    "id": 1,
    "title": "todo1 test",
    "status": false
}

get todo’s #

endpoint: localhost:8080/todos

method:GET

request:

response:

[
    {
        "id": 1,
        "title": "todo1 test",
        "status": false
    },
  //...
]

update todo #

endpoint: localhost:8080/todos/update

method:PATCH

request:

{
    "id":1,
    "status":true
}

response:

{
    "id": 1,
    "title": "todo1 test",
    "status": true
}

delete todo #

endpoint: localhost:8080/todos/delete

method:DELETE

request:

{
    "id":1
}

response:

{
    "message": "TODO deleted"
}