Разработка веб-сервера на Go и веб-клиента к API этогому серверу
1. Написать HTTP-сервер, сохраняющий получаемые этим сервером HTTP-запросы в базе данных. Реализовать на языке программирования Go/golang
2. Реализовать пакет на Go/golang для клиентского использования этого API сервера из п. 1
Краткое описание работы сервер:
0. Сервер, принимающий запросы по HTTP
1. Разрешаем серверу прием запросов по определенному URL
2. После чего все HTTP-запросы получаемые по этому разрешенному в п.1 URL сохраняются в базе данных
3. После чего получаем от сервера информацию: был ли уже получен запрос по этому разрешенному callback URL
и какой именно это был запрос (вся информация - тело запроса, URL, дата-время, заголовок запроса и пр.)
Требуется подробно логировать работу сервера (вывод лога в стандартные потоки вывода stderr, stdout)
------------------------------------------------------------------------------------------------------
Использовать логирование https://github.com/uber-go/zap
Использовать базу данных https://github.com/etcd-io/bbolt
Для работы с HTTP использовать https://github.com/gin-gonic/gin
Использовать go mod
Использовать go 1.18
Будет использоваться под Linux, то есть должно нормально под Linux компилироваться и запускаться.
Реализовать с помощью обычной программы на Go, способной работать из-под systemd
(это обычная программа на Go, systemd сам делает все необходимое для превращения программы в демона/сервис Linux)
------------------------------------------------------------------------------------------------------
Управление сервером по простому API HTTP/JSON, вызовы защищены подписью проверяемой ключом ed25519
Функции API
1) Разрешить сохранять в БД запрос HTTP, принятый сервером по конкретному URL
2) Отменить п. 1)
3) Очистить все разрешения на URL выданные в п. 1)
4) Узнать был ли к серверу ранее выполнен запрос по определенному URL (получение информации из БД)
Подробная документация по этому API - в личку
Фактически сервер работает с двумя группами URL
/api/v1/API_Управления_Сервером (эти вызовы защищены проверкой подписи ключом ed25519)
/callback/url_разрешенные_для_сохранения_в_БД (эти вызовы сохраняются в БД если в 1) было разрешение для данного конкретного URL).
Сохранятся должна вся информация, получаемая сервером по URL /callback/* - дата-время, URL, тело запроса, заголовки запроса.
Пример проверки подписи ed25519
Как загрузить публичный ключ ed25519 из файла:
import (
"crypto/ed25519"
"encoding/base64"
"errors"
"go.uber.org/zap"
)
func getPublicKeyFromBinary(lg *zap.Logger, data []byte) (ed25519.PublicKey, error) {
l := lg.Named("getKeyFromBinary").With(zap.String("data", string(data)))
decoded, err := base64.StdEncoding.DecodeString(string(data))
if err != nil {
errMsg := "Can not decode key from base64"
l.Error(errMsg, zap.Error(err), zap.String("data", string(data)))
return nil, errors.New(errMsg)
}
publicKey := ed25519.PublicKey(decoded)
if len(publicKey) != ed25519.PublicKeySize {
errMsg := "Unexpected size of ed25519 public key"
l.Error(errMsg, zap.Int("len", len(ed)))
return nil, errors.New(errMsg)
}
return publicKey, nil
}
Как выполнять проверку подписи с помощью ed25519:
import (
"crypto/ed25519"
"encoding/base64"
"io/ioutil"
"net/http"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
func verifySignature(message []byte, signature string) bool {
signatureB, err := base64.StdEncoding.DecodeString(signature)
if err != nil {
return false
}
return ed25519.Verify(publicKey, message, signatureB)
}
func GinSignatureVerifyMiddleware(ctx *gin.Context) {
bd, err := ioutil.ReadAll(ctx.Request.Body)
if err != nil {
ctx.AbortWithStatus(http.InternalError)
}
// Невозможно прочитать HTTP body дважды
// Но нам нужно это сделать первый раз для проверки подписи
// И второй раз собственно для анализа тела запроса (параметров API)
// Поэтому после чтения тела HTTP-запроса первый раз
// Необходимо его сохранить со специальным предопределенным
// пакетом Gin ключем
ctx.Set(gin.BodyBytesKey, bd)
h := ctx.GetHeader("Signature")
if !verifySignature(bd, h) {
ctx.AbortWithStatus(http.InternalError)
}
}
Подключить middleware к gin можно так
router:= gin.Default()
api:= router.Group("/api/v1/callback-register", GinSignature)
api.POST("enable-callback-url", handlerName)