在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称:romapres2010/httpserver开源软件地址:https://github.com/romapres2010/httpserver开源编程语言:Go 76.1%开源软件介绍:Шаблон backend сервера на Golang - часть 1 (HTTP сервер)Представленный ниже шаблон сервера на Golang был подготовлен для передачи знаний внутри нашей команды. Основная цель шаблона, кроме обучения - это снизить время на прототипирование небольших серверных задач на Go. Шаблон включает:
Ссылка на репозиторий проекта. В состав шаблона включено несколько HTTP обработчиков:
Подход к упрощению написания HTTP обработчиков для этого шаблона описан в статье Упрощаем написание HTTP обработчиков на Golang Содержание статьи
1. ПредысторияВ ходе внедрения 1С:ERP, появилась интересная задача - интеграция 1С с шиной IBM MQ. Ключевыми требованиями в части взаимодействия с IBM MQ были:
Дополнительно были выдвинуты требования к обработке XML сообщений:
Стандартного адаптера в 1C к IBM MQ не было. Существующий REST API к IBM MQ не подходил под требования. Адаптер 1C к IBM MQ был успешно разработан на Go с использованием официальной библиотеки IBM MQ. Библиотека отличается неплохой стабильностью, что и не удивительно, так как она написана в виде "обертки" над стандартной C библиотекой. За полгода работы с ней было зафиксировано всего 2 бага с обработкой слайсов []. Представленный в статье шаблон backend сервера, является обобщением полученного опыта. Архитектура адаптера 1С к IBM MQ укрупнено показана на следующем рисунке. 2. Передача параметров серверу2.1. Командная строкаВсе чувствительные, с точки зрения безопасности, параметры сервера передаются через командную строку. Для этого используется библиотека github.com/urfave/cli. Список основных параметров:
2.2. Конфигурационный файлДля обработки конфигурационного файла используется библиотека github.com/sasbury/mini. Список типовых параметров, включенных в шаблон:
3. Создание, запуск и остановка сервераНа следующем рисунке показана упрощенная UML диаграмма последовательности запуска и остановки сервера. Для координации создания, запуска и остановки сервера используется daemon. В его задачи входит:
3.1. Создание daemon и сервисовВ общем случае, сервисы создаются и настраиваются при создании daemon. httpserverErrCh: make(chan error, 1), // канал ошибок HTTP сервера При создание сервиса, ему передаются параметры:
Примеры задач при создании сервисов:
3.2. Запуск daemon и сервисовЗапуск daemon заключается в скоординированном запуске сервисов. go func() { httpserverErrCh <- d.httpserver.Run() }() После запуска сервисов, daemon подписывается на основные системные прерывания и переходит в режим ожидания сигналов или возврата в каналы ошибок от сервисов. syscalCh := make(chan os.Signal, 1) // канал системных прерываний
signal.Notify(syscalCh, syscall.SIGINT, syscall.SIGTERM)
// ожидаем прерывания или возврат в канал ошибок
select {
case s := <-syscalCh: // системное прерывание
mylog.PrintfInfoMsg("Exiting, got signal", s)
d.Shutdown() // останавливаем daemon
return nil
case err := <-d.httpserverErrCh: // возврат от HTTP сервера в канал ошибок
mylog.PrintfErrorInfo(err) // логируем ошибку
d.Shutdown() // останавливаем daemon
return err
} В запуск сервисов, работающих в фоне, добавляется анонимная функция восстановления после паники (пример ниже). При обработке паники, ошибка возвращается в канал ошибок для уведомления daemon. func (s *Server) Run() error {
defer func() {
var myerr error
r := recover()
if r != nil {
msg := "Recover from panic"
switch t := r.(type) {
case string:
myerr = myerror.New("8888", msg, t)
case error:
myerr = myerror.WithCause("8888", msg, t)
default:
myerr = myerror.New("8888", msg)
}
mylog.PrintfErrorInfo(myerr) // логируем ошибку
s.errCh <- myerr // передаем ошибку в канал для уведомления daemon
}
}()
// Запуск сервера
} 3.3. Остановка daemon и сервисовОстановка daemon заключается в скоординированной остановке сервисов и последующем закрытии корневого контекста. Остановка сервисов, работающих в фоне, осуществляется по следующему сценарию:
Для корректной остановки сервисов при закрытии контекста, используется такой подход:
for {
select {
case <-ctx.Done(): // получен сигнал закрытия контекста
// Освободить ресурсы
s.stopCh <- struct{}{} // отправить подтверждение об успешном закрытии
return
default:
// Обработка очередной итерации
}
} Для остановки HTTP сервера использовался несколько другой подход: // создаем новый контекст с отменой и отсрочкой ShutdownTimeout
cancelCtx, cancel := context.WithTimeout(s.ctx, time.Duration(s.cfg.ShutdownTimeout*int(time.Second)))
defer cancel()
// ожидаем закрытия активных подключений в течении ShutdownTimeout
if err := s.httpServer.Shutdown(cancelCtx); err != nil {
return err
}
s.httpService.Shutdown() // Останавливаем служебные сервисы
// подтверждение об успешном закрытии HTTP сервера
s.stopCh <- struct{}{} 4. Обработка ошибок4.1. Кастомная структура ошибкиОдин из наиболее удачных пакетов для обработки ошибок github.com/pkg/errors. Структура для хранения ошибки: type Error struct {
ID uint64 // уникальный номер ошибки
Code string // код ошибки
Msg string // текст ошибки
Caller string // файл, строка и наименование метода в месте регистрации ошибки
Args string // строка аргументов
CauseErr error // ошибка - причина
CauseMsg string // текст ошибки - причины
Trace string // стек вызова в месте регистрации ошибки
} Мне удобно работать с типизированными ошибками, поэтому код ошибки выделен отдельным атрибутом. Например, в адаптере 1С к IBM MQ, использовался простой 4 символьный числовой код. Например, ошибки начинающиеся с "8ххх" относились к HTTP, с "7ххх" - к IBM MQ. Caller - файл, строка и наименование метода в месте регистрации ошибки. Удобно использовать, если нет необходимости выводить полный стек. Пример вывода:
Caller вычисляется функцией func caller(depth int) string {
pc := make([]uintptr, 15)
n := runtime.Callers(depth+1, pc)
frame, _ := runtime.CallersFrames(pc[:n]).Next()
idxFile := strings.LastIndexByte(frame.File, '/')
idx := strings.LastIndexByte(frame.Function, '/')
idxName := strings.IndexByte(frame.Function[idx+1:], '.') + idx + 1
return frame.File[idxFile+1:] + ":[" + strconv.Itoa(frame.Line) + "] - " + frame.Function[idxName+1:] + "()"
} Args - отдельная строка аргументов, которые можно добавить к сообщению при регистрации ошибки. Используется для целей отладки. CauseErr и CauseMsg - исходная ошибка и сообщение. Используется, если оборачиваем чужую ошибку в свою структуру. Trace - стандартный трейс стека. Для его получения использовал несколько своеобразный подход. При регистрации ошибки создавал дополнительно пустую ошибку из пакета github.com/pkg/errors и печатал ее с ключом '%+v'. В этом режиме она выводит стек. fmt.Sprintf("'%+v'", pkgerr.New("")) 4.2. Форматирование печати ошибкиДля соответствия интерфейсу Error, используется вывод в сокращенном формате. func (e *Error) Error() string {
mes := fmt.Sprintf("ID=[%v], code=[%s], mes=[%s]", e.ID, e.Code, e.Msg)
if e.Args != "" {
mes = fmt.Sprintf("%s, args=[%s]", mes, e.Args)
}
if e.CauseMsg != "" {
mes = fmt.Sprintf("%s, causemes=[%s]", mes, e.CauseMsg)
}
return mes
} Пример вывода в сокращенном формате
Для расширенного форматированного вывода используются ключи // %s print the error code, message, arguments, and cause message.
// %v in addition to %s, print caller
// %+v extended format. Each Frame of the error's StackTrace will be printed in detail.
func (e *Error) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
fmt.Fprint(s, e.Error())
fmt.Fprintf(s, ", caller=[%s]", e.Caller)
if s.Flag('+') {
fmt.Fprintf(s, ", trace=%s", e.Trace)
return
}
case 's':
fmt.Fprint(s, e.Error())
case 'q':
fmt.Fprint(s, e.Error())
}
} Пример вывода с ключом '%+v'
Предложенный формат вывода не полностью соответствует подходу структурированного логирования. Это сделано специально для более удобного чтения лога в ходе отладки. Если нужен боле строгий формат - достаточно поправить в одном месте метод Format. 4.3. Регистрация ошибокИспользуются два метода для регистрации ошибок:
New(code string, msg string, args ...interface{}) error
WithCause(code string, msg string, causeErr error, args ...interface{}) error Дополнительные аргументы можно либо встроить в сообщение ошибки, либо передать дополнительными параметрами в args ...interface{}. Все ошибки от сторонних и стандартных пакетов оборачиваются в кастомную ошибку в месте возникновения. Исходная ошибка вкладывается внутрь кастомной, например: myerr = myerror.WithCause("8001", "Failed to read HTTP body: reqID", err, reqID) 4.4. Логирование и обработка ошибокИспользовался следующий подход:
Пример логирования при ошибке чтения тела HTTP запроса: 全部评论
专题导读
热门推荐
热门话题
阅读排行榜
|
请发表评论