Передача переменных в http обработчики

Что ни день то вопрос. В общем делал я как все: Слушал порт, с nil в качестве хендлера

err := http.ListenAndServeTLS(configuration.TLSPort, TLSFolderAdress+"Certificate.pem", TLSFolderAdress+"Key.pem", nil)

(на случай если не понятно про что)

Дальше обычное http.HandleFunc с паттерном строки и функцией обработчиком. Когда нужно было взять что-то снаружи этой функции закидывал это в глобальные переменные (обьявленные вне функций) и брал просто используя такие переменные.

Но что-то подсказывает, что вообще так делать не надо, что вызывает вопрос:

Как передать в функцию обработчик что-то кроме http.ResponseWriter и *http.Request которые по дефолту передаёт http.HandleFunc и больше ничего передавать не хочет?

Чтобы отбросить глупые вопросы - у программы есть большая часть которая держит данные в памяти и работает с ними и сохранять их как файлы чтобы тут-же прочитать другой функцией - ну как-то звучит неразумно.


Ответы (1 шт):

Автор решения: Ainar-G

Вообще глобальным HTTP-обработчиком пользоваться крайне не рекомендую. Да и глобальным состоянием в общем. Тем более, что пакет net/http вам предоставляет всё необходимое для работы с нормально инкапсулированными сущностями. Делаете свою собственную сущность обработчика:

type APIHandler struct {
	db   DB
	repo DBRepo
	// …
}

func NewAPIHandler(db DB, repo DBRepo, /* … */) (h *APIHandler, err error) {
	// …
}

func (h *APIHandler) Users(w http.ResponseWriter, r *http.Request) { /* … */ }
func (h *APIHandler) Posts(w http.ResponseWriter, r *http.Request) { /* … */ }

Делаете свой роутер и сервер (глобальный сервер по умолчанию без таймаутов, если я правильно помню, так что любой Вася Кулхацкер его вам положит) и запускаете:

h, err = NewAPIHandler(db, repo)
// Check err.

m := http.NewServeMux()
m.HandleFunc("/users", h.Users)
m.HandleFunc("/posts", h.Posts)

var srv = &http.Server{
	Addr:      ":8080",
	Handler:   m,
	TLSConfig: /* … */,

	IdleTimeout:       120 * time.Second,
	MaxHeaderBytes:    16 * 1024,
	ReadHeaderTimeout: 10 * time.Second,
	ReadTimeout:       10 * time.Second,
	WriteTimeout:      10 * time.Second,
}

err = srv.ListenAndServe()
// Check err.

Можете сделать отдельные обработчики для отдельных методов API, а можете как здесь использовать один. Пакет гибкий, всё зависит от сложности и ширины вашего задания.


После уточнений из комментариев, стало понятно, что ваш вопрос также и про спуск данных «вниз по стеку». Сейчас для этого в основном используется context.Context. Пример middleware:

func (h *APIHandler) Middleware(next http.Handler) (wrapped http.Handler) {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var id, err = GetID()
		// Check err.
		r = r.WithContext(context.WithValue(r.Context(), ctxKeyID, id))
		next.ServeHTTP(w, r)
	})
}
m.Handle("/users", h.Middleware(http.HandlerFunc(h.Users)))

Пример использования контекста в обработчике:

func (h *APIHandler) Users(w http.ResponseWriter, r *http.Request) {
	var ctx = r.Context()
	var v = ctx.Value(ctxKeyID)

	var id, ok = v.(ID)
	if !ok {
		// Ошибка: либо неверен тип, либо значения нету вообще.
	}

	// …
}

О ключах для контекста я писал на английском StackOverflow.

→ Ссылка