Веб-приложение на Golang для чтения и записи данных в JSON файл

Golang предлагает мощные инструменты для создания веб-серверов.В Golang вы можете создавать веб-серверы, с разными адресами, разными типами запросов и разными типами контента. Для веб-разработки основными пакетами стандартной библиотеки являются net/http и html/template. Используя эти два пакета, вы можете создавать полнофункциональные веб-приложения.
Приведем небольшой пример, с которым вы сможете понять, как работает веб-сервер в Go. Взяв за основу код ниже, вы можете создавать более сложные приложения. В этом примере мы собираемся читать, записывать и обновлять данные в файле JSON через формы пользовательского интерфейса, извлекать данные для определенных записей, а также удалять данные для определенной записи.
Пакет стандартной библиотеки net/http предоставляет несколько методов создания HTTP-серверов и поставляется с базовым маршрутизатором. Пакет net/http используется для выполнения запросов к серверу, а также для ответа на запросы.
Наше веб-приложение создается в папке GOPATH со структурой папок, указанной ниже:

Код:
package main
import (
handlers "./handlers"
"net/http"
)
func main() {
http.HandleFunc("/addnewuser/", handlers.AddNewUserFunc)
http.HandleFunc("/notsucceded", handlers.NotSucceded)
http.HandleFunc("/deleted", handlers.DeletedFunc)
http.HandleFunc("/deleteuser/deleted", handlers.DeleteUserFunc)
http.HandleFunc("/deleteuser/", handlers.DeleteUserServe)
http.HandleFunc("/deleteuser/notsuccededdelete", handlers.NotSuccededDelete)
http.HandleFunc("/", handlers.IndexFunc)
http.HandleFunc("/showuser/show", handlers.ShowUserFunc)
http.HandleFunc("/showuser/", handlers.ShowUser)
http.HandleFunc("/showuser/notsuccededshow/", handlers.NotSuccededShow)
http.ListenAndServe(":8080", nil)
}
Сначала импортируется пакет net/http. Пакет http предоставляет реализации HTTP-клиента и сервера. HandleFunc регистрирует функцию-обработчик. ListenAndServe запускает HTTP-сервер с заданным адресом и обработчиком. Пример данных для тестирования:
list.json
[
{
"id": 1,
"firstName": "Mariya",
"lastName": "Ivanova",
"balance": 300
},
{
"id": 2,
"firstName": "EKatina",
"lastName": "Milevskaya",
"balance": 5000
},
{
"id": 3,
"firstName": "Vasek",
"lastName": "Zalupickiy",
"balance": 2000
}
]
код handler содержит различные функции обработчика.
handlers\handlers.go
package handlers
import (
model "../model"
"encoding/json"
"fmt"
"html/template"
"io/ioutil"
"net/http"
"os"
"regexp"
"strconv"
)
//function to check correct user adding input (regular expression and non-empty field input)
func checkFormValue(w http.ResponseWriter, r *http.Request, forms ...string) (res bool, errStr string) {
for _, form := range forms {
m, _ := regexp.MatchString("^[a-zA-Z]+$", r.FormValue(form))
if r.FormValue(form) == "" {
return false, "All forms must be completed"
}
if m == false {
return false, "Use only english letters if firstname,lastname forms"
}
}
return true, ""
}
func ShowUser(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "templates/showUser.html")
}
func NotSuccededShow(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "templates/notSuccededShow.html")
}
//handler to show user with id input
func ShowUserFunc(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
t, _ := template.ParseFiles("templates/showUserPage.html")
t.Execute(w, nil)
} else {
id, err := strconv.Atoi(r.FormValue("id"))
checkError(err)
var alUsrs model.AllUsers
file, err := os.OpenFile("list.json", os.O_RDONLY, 0666)
checkError(err)
b, err := ioutil.ReadAll(file)
checkError(err)
json.Unmarshal(b, &alUsrs.Users)
var allID []int
for _, usr := range alUsrs.Users {
allID = append(allID, usr.Id)
}
for _, usr := range alUsrs.Users {
if model.IsValueInSlice(allID, id) != true {
http.Redirect(w, r, "/showuser/notsuccededshow/", 302)
return
}
if usr.Id != id {
continue
} else {
t, err := template.ParseFiles("templates/showUserPage.html")
checkError(err)
t.Execute(w, usr)
}
}
}
}
//function to handle page with successful deletion
func DeletedFunc(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "templates/deleted.html")
}
//serving file with error (add function:empty field input or uncorrect input)
func NotSucceded(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "templates/notSucceded.html")
}
//function,which serve html file,when deleting was not succesful(id input is not correct)
func NotSuccededDelete(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "templates/notSuccededDelete.html")
}
//function,which serve page with delete information input
func DeleteUserServe(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "templates/deleteUser.html")
}
//function to delete user
func DeleteUserFunc(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
t, _ := template.ParseFiles("templates/deleteUser.html")
t.Execute(w, nil)
} else {
r.ParseForm()
id, err := strconv.Atoi(r.FormValue("id"))
checkError(err)
//open file with users
file, err := os.OpenFile("list.json", os.O_RDWR|os.O_APPEND, 0666)
defer file.Close()
//read file and unmarshall json to []users
b, err := ioutil.ReadAll(file)
var alUsrs model.AllUsers
err = json.Unmarshal(b, &alUsrs.Users)
checkError(err)
var allID []int
for _, usr := range alUsrs.Users {
allID = append(allID, usr.Id)
}
for i, usr := range alUsrs.Users {
if model.IsValueInSlice(allID, id) != true {
http.Redirect(w, r, "/deleteuser/notsuccededdelete", 302)
return
}
if usr.Id != id {
continue
} else {
alUsrs.Users = append(alUsrs.Users[:i], alUsrs.Users[i+1:]...)
}
}
newUserBytes, err := json.MarshalIndent(&alUsrs.Users, "", " ")
checkError(err)
ioutil.WriteFile("list.json", newUserBytes, 0666)
http.Redirect(w, r, "/deleted", 301)
}
}
func checkError(err error) {
if err != nil {
fmt.Println(err)
}
}
//function to add user
func AddNewUserFunc(w http.ResponseWriter, r *http.Request) {
//creating new instance and checking method
newUser := &model.User{}
if r.Method == "GET" {
t, _ := template.ParseFiles("templates/addNewUser.html")
t.Execute(w, nil)
} else {
resBool, errStr := checkFormValue(w, r, "firstname", "lastname")
if resBool == false {
t, err := template.ParseFiles("templates/notSucceded.html")
checkError(err)
t.Execute(w, errStr)
return
}
newUser.FirstName = r.FormValue("firstname")
newUser.LastName = r.FormValue("lastname")
var err error
newUser.Balance, err = strconv.ParseFloat(r.FormValue("balance"), 64)
checkError(err)
//open file
file, err := os.OpenFile("list.json", os.O_RDWR, 0644)
checkError(err)
defer file.Close()
//read file and unmarshall json file to slice of users
b, err := ioutil.ReadAll(file)
var alUsrs model.AllUsers
err = json.Unmarshal(b, &alUsrs.Users)
checkError(err)
max := 0
//generation of id(last id at the json file+1)
for _, usr := range alUsrs.Users {
if usr.Id > max {
max = usr.Id
}
}
id := max + 1
newUser.Id = id
//appending newUser to slice of all Users and rewrite json file
alUsrs.Users = append(alUsrs.Users, newUser)
newUserBytes, err := json.MarshalIndent(&alUsrs.Users, "", " ")
checkError(err)
ioutil.WriteFile("list.json", newUserBytes, 0666)
http.Redirect(w, r, "/", 301)
}
}
//Index page handler
func IndexFunc(w http.ResponseWriter, r *http.Request) {
au := model.ShowAllUsers()
t, err := template.ParseFiles("templates/indexPage.html")
checkError(err)
t.Execute(w, au)
}
Запросы с путем /addnewuser вызывают функцию AddNewUserFunc, запросы
на /deleted обрабатываются функцией DeletedFunc, запросы на /deleteuser обрабатываются DeleteUserServe и так далее.
Пакет html/template преобразует шаблон HTML в тип Template, а затем выполняет структуру данных для этого шаблона, чтобы создать окончательный выходной HTML. Основная функция, используемая для объекта, — json.Marshal.
Пакет Model содержит пользовательскую структуру и функцию Unmarshaling.
model\model.go
package model
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
)
func checkError(err error) {
if err != nil {
fmt.Println(err)
}
}
func IsValueInSlice(slice []int, value int) (result bool) {
for _, n := range slice {
if n == value {
return true
}
}
return false
}
type User struct {
Id int `json:"id"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
Balance float64 `json:"balance"`
}
type AllUsers struct {
Users []*User
}
func ShowAllUsers() (au *AllUsers) {
file, err := os.OpenFile("list.json", os.O_RDWR|os.O_APPEND, 0666)
checkError(err)
b, err := ioutil.ReadAll(file)
var alUsrs AllUsers
json.Unmarshal(b, &alUsrs.Users)
checkError(err)
return &alUsrs
}
ser. Шаблон Index отвечает за отображение списка пользователей и 3 кнопок. Этот шаблон загружается по веб-пути/:
templates\indexPage.html
<html>
<head>
<title>User info</title>
</head>
<body>
{{range .Users}}
<div><h3> ID:{{ .Id }} </h3></div>
<div> First Name:{{ .FirstName }}</div>
<div> Last Name:{{ .LastName }} </div>
<div> Balance:{{ .Balance }}</div>
{{end}}
<h1>Options</h1>
<form action="/addnewuser/">
<button type="submit">Add new user</button>
</form>
<form action="/deleteuser/">
<button type="submit">Delete user</button>
</form>
<form action="/showuser/">
<button type="submit">Show user</button>
</form>
</body>
</html>
В шаблоне «Добавление нового пользователя» отображается форма для ввода данных пользователя. Этот шаблон загружается по веб-пути /addnewuser:
<html>
<head>
<title>Adding new user</title>
</head>
<body>
<h2>New user's data</h2>
<form method="POST" action="useraded">
<label>Enter firstname</label><br>
<input type="text" name="firstname" /><br><br>
<label>Enter lastname</label><br>
<input type="text" name="lastname" /><br><br>
<label>Enter balance</label><br>
<input type="number" name="balance" /><br><br>
<input type="submit" value="Submit" />
</form>
</body>
</html>
templates\deleteUser.html
<html>
<head>
<title>Delete user</title>
</head>
<body>
<h2>Please,write an id of user you want to delete</h2>
<form method="POST" action="deleted">
<label>Enter id</label><br>
<input type="text" name="id" /><br><br>
<input type="submit" value="Submit" />
</form>
</body>
</html>
templates\deleted.html
<html>
<head>
<title>User info</title>
</head>
<body>
<div>Done</div><br><br>
<form action="/">
<button type="submit">Back to main</button>
</form>
</body>
</html>
templates\notSuccededDelete.html
<html>
<head>
<title>User info</title>
</head>
<body>
<div>There is no user with such ID</div><br><br>
<form action="/">
<button type="submit">Back to main</button>
</form>
</body>
</html>
templates\notSuccededShow.html
<html>
<head>
<title>User info</title>
</head>
<body>
<div>Error:Cant find User with such ID,try again</div><br><br>
<form action="/">
<button type="submit">Back to main</button>
</form>
</body>
</html>
templates\showUser.html
<html>
<head>
<title>Show user</title>
</head>
<body>
<h2>Please,write an id of user you want to show</h2>
<form method="POST" action="show">
<label>Enter id</label><br>
<input type="text" name="id" /><br><br>
<input type="submit" value="Submit" />
</form>
</body>
</html>
templates\showUserPage.html
<html>
<head>
<title>User info</title>
</head>
<body>
<div><h3> ID:{{.Id}} </h3></div>
<div> First Name:{{ .FirstName }}</div>
<div> Last Name:{{ .LastName }} </div>
<div> Balance:{{ .Balance }}</div>
</body>
</html>
templates\notSucceded.html
<html>
<head>
<title>User info</title>
</head>
<body>
<div>Error:{{.}}</div><br><br>
<form action="/">
<button type="submit">Back to main</button>
</form>
</body>
</html>
Теперь запустите приложение
go run main.go
Это создаст статический веб-сайт на веб-сервере через порт 8080 на вашем компьютере. Это также запустит ваш браузер, перейдя по адресу http://localhost:8080.
Вы увидите результат, подобный изображенному ниже:
