go-rest-service/

В этой заметке я опишу реализацию простого REST API для телефонной книги, созданной в прошлый раз. Должен сразу оговориться, что интерфейс к базе данных был изменен. Во-первых, прошлая реализация была направлена скорее на демонстрацию возможностей пакета database/sql, чем на решение задачи, а во-вторых, она была подвержена SQL-инъекциям.

Примечание: Другие статьи Владимира вы найдете по следующим ссылкам:

Сначала, как и прежде, считываются параметры подключения к базе из файла конфигурации и создается таблица, если ее еще нет. Далее создается роутер, регистрируются обработчики, запускается сервер на 8080 порту:

router := httprouter . New ()
router . GET ( «/api/v1/records» , getRecords )
router . GET ( «/api/v1/records/:id» , getRecord )
router . POST ( «/api/v1/records» , addRecord )
router . PUT ( «/api/v1/records/:id» , updateRecord )
router . DELETE ( «/api/v1/records/:id» , deleteRecord )
http . ListenAndServe ( «:8080» , router )

Вместо стандартного http.ServeMux в качестве роутера я выбрал httprouter . Он проще в использовании и славится высокой производительностью . Есть, впрочем, у него и недостатки. В гибкости настройки он уступает многим другим роутерам. И он несовместим напрямую со стандартным интерфейсом http.Handler. Но для данной задачи это не проблема.

Для кодирования/декодирования JSON используется пакет из стандартной библиотеки encoding/json. Его особенность в том, что он может работать только с экспортируемыми полями структур. А так как в языке Go экспортируемые идентификаторы всегда начинаются с заглавной буквы, они будут такими же и после кодирования в JSON. Изменить это поведение можно с помощью тегов.

Тег — это строковый литерал, состоящий, по соглашению, из одной и более пар ключ:"значение" , разделенных пробелом. Теги могут быть присвоены полям структуры в качестве атрибутов и становятся в таком случае частью типа структуры. Программно теги могут быть получены с помощью рефлексии времени выполнения (пакет reflect). Так пакет encoding/json при разборе структуры Record считает теги и заменит имена ключей в JSON на заданные в тегах:

type Record struct {
Id int `json:»id»`
Name string `json:»name»`
Phone string `json:»phone»`
}

Первый хэндлер, помимо возврата всех записей, поддерживает параметры запроса и может вернуть записи, содержащие заданную подстроку в поле имени.

То есть, GET-запрос:

http://localhost:8080/api/v1/records?name=abc

… вернет все записи, где name содержит строку «abc». В случае некорректного параметра запроса вернется ошибка 400 Bad Request. В случае ошибки базы данных или проблем с кодированием в JSON — 500 Internal Server Error. Кроме того, сервер может вернуть ошибку 404 Not Found или 405 Method Not Allowed, если запрошенный URL не существует или не поддерживает данный метод.

func getRecords ( w http . ResponseWriter , r * http. Request ,
_ httprouter . Params ) {
var str string
if len ( r . URL . RawQuery ) > 0 {
str = r . URL . Query () . Get ( «name» )
if str == «» {
w . WriteHeader ( 400 )
return
}
}
recs , err := read ( str )
if err != nil {
w . WriteHeader ( 500 )
return
}
w . Header () . Set ( «Content-Type» , «application/json; charset=utf-8» )
if err = json . NewEncoder ( w ) . Encode ( recs ); err != nil {
w . WriteHeader ( 500 )
}
}

Второй хэндлер возвращает запись с заданным id или ошибку 404 Not Found, если такого id нет в базе. Но сначала id проверяется на корректность:

func getID ( w http . ResponseWriter , ps httprouter . Params ) ( int , bool ) {
id , err := strconv . Atoi ( ps . ByName ( «id» ))
if err != nil {
w . WriteHeader ( 400 )
return 0 , false
}
return id , true
}

func getRecord ( w http . ResponseWriter , r * http. Request ,
ps httprouter . Params ) {
id , ok := getID ( w , ps )
if ! ok {
return
}
rec , err := readOne ( id )
if err != nil {
if err == sql . ErrNoRows {
w . WriteHeader ( 404 )
return
}
w . WriteHeader ( 500 )
return
}
w . Header () . Set ( «Content-Type» , «application/json; charset=utf-8» )
if err = json . NewEncoder ( w ) . Encode ( rec ); err != nil {
w . WriteHeader ( 500 )
}
}

Третий хэндлер добавляет запись в базу и возвращает код 201 Created в случае успеха. Если во время декодирования JSON возникла ошибка, возвращается код 400 Bad Request.

func addRecord ( w http . ResponseWriter , r * http. Request ,
_ httprouter . Params ) {
var rec Record
err := json . NewDecoder ( r . Body ) . Decode ( &rec )
if err != nil || rec . Name == «» || rec . Phone == «» {
w . WriteHeader ( 400 )
return
}
if _ , err := insert ( rec . Name , rec . Phone ); err != nil {
w . WriteHeader ( 500 )
return
}
w . WriteHeader ( 201 )
}

Четвертый хэндлер изменяет запись с данным id. В случае успеха возвращается 204 No Content. Если такого id нет в базе, возвращается ошибка 404 Not Found.

func updateRecord ( w http . ResponseWriter , r * http. Request ,
ps httprouter . Params ) {
id , ok := getID ( w , ps )
if ! ok {
return
}
var rec Record
err := json . NewDecoder ( r . Body ) . Decode ( &rec )
if err != nil || rec . Name == «» || rec . Phone == «» {
w . WriteHeader ( 400 )
return
}
res , err := update ( id , rec . Name , rec . Phone )
if err != nil {
w . WriteHeader ( 500 )
return
}
n , _ := res . RowsAffected ()
if n == 0 {
w . WriteHeader ( 404 )
return
}
w . WriteHeader ( 204 )
}

Пятый хэндлер удаляет запись с данным id. В случае успеха возвращается 204 No Content.

func deleteRecord ( w http . ResponseWriter , r * http. Request ,
ps httprouter . Params ) {
id , ok := getID ( w , ps )
if ! ok {
return
}
if _ , err := remove ( id ); err != nil {
w . WriteHeader ( 500 )
}
w . WriteHeader ( 204 )
}

Проверить работу приложения можно с помощью curl примерно таким образом.

Получение всех записей:

curl -i http: // localhost: 8080 / api / v1 / records

Получение всех записей имеющих в поле name подстроку «Маша»:

curl -i http: // localhost: 8080 / api / v1 / records? name =Маша

Получение записи с id = 3:

curl -i http: // localhost: 8080 / api / v1 / records / 3

Создание новой записи:

curl -i http: // localhost: 8080 / api / v1 / records
-d ‘{«name»:»Иванов Иван»,»phone»:»9284724″}’

Редактирование записи с id = 5:

curl -i http: // localhost: 8080 / api / v1 / records / 5 -XPUT
-d ‘{«name»:»Петрова Лена»,»phone»:»2341233″}’

Удаление записи c id = 2:

curl -i http: // localhost: 8080 / api / v1 / records / 2 -XDELETE

Полный листинг программы доступен по этой ссылке ( зеркало ).

Дополнение: Многопоточный генератор шоунотов на Go

EnglishRussianUkrainian