02.05.2020 http клиент сервер middleware
Все примеры кода находятся в github репозитории.
Рассмотрим средства для работы с http в GO.
Среди стандартных пакетов в GO есть net/http
,
позволяющий работать как со стороны сервера, так и клиента.
Простейший способ запустить HTTP-сервер — вызвать http.ListenAndServe
:
package main import ( "log" "net/http" ) type Handler struct {} func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request){ _,err := w.Write([]byte("hello")) if err != nil { log.Println(err) } } func main() { log.Println(http.ListenAndServe("127.0.0.1:9090", &Handler{})) }
Здесь мы запуcтили сервер на порту 9090.
Вторым параметром мы указали переменную, удовлетворяющую интерфейсу http.Handle
,
который требует функции ServeHTTP(w http.ResponseWriter, req *http.Request)
.
Данная функция будет принимать все запросы к нашему серверу:
curl localhost:9090/someurl hello curl localhost:9090/secondurl hello
Также мы можем сконфигурировать наш сервер обрабатывать конкретный URL:
package main import ( "log" "net/http" ) func main() { http.HandleFunc("/hello", func(w http.ResponseWriter, req *http.Request){ _,err := w.Write([]byte("hello")) if err != nil { log.Println(err) } }) log.Println(http.ListenAndServe(":9090", nil)) }
Как это работает?
Когда мы добавляем функции обработичики, они сохраняются
в структуру net/http.DefaultServeMux
,
которая хранит все обрабатываемые URL-ы и функции для их обработки.
Стандартный роутер обладает простейшей функциональностью — при запросе
явно сопоставляет URL запроса с каждым зарегистрированным обработчиком.
Возможно, вы захотите обрабатывать не конкретные URL-ы,
а некоторые шаблоны. Например, /hello/{page}
,
где page — параметр из строки запроса (номер страницы).
Для реализации этого нам потребуется нестандартный роутер.
Мы можем использовать другой роутер, например gorilla/mux.
func main() { r := mux.NewRouter() r.HandleFunc("/products/{key}", ProductHandler) r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) http.Handle("/", r) }
Далее, в обработчике мы можем получить значения переменных следующим образом:
func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "Category: %v\n", vars["category"]) }
Обычно в веб-приложении выполняется не только код конкретного обработчика определенного URL, но также некоторый общий код — проверка авторизации, сбор статистики выполнения запроса и тп. Данный код может работать вместе с обработчиком, добавляя некоторую логику. Такой общий код принято называть middleware.
В стандартной библиотеке нет явной реализации middleware. Но вы можете реализовать что-то похожее на это, используя вложенные обработчики. Рассмотрим пример кода, реализующий логгирование всех запрошенных урлов в консоль:
много кодаpackage main import ( "log" "net/http" ) type LoggingMiddleware struct { handler http.Handler } func NewLoggingMiddleware(handler http.Handler) *LoggingMiddleware { return &LoggingMiddleware{ handler: handler, } } func (l *LoggingMiddleware) ServeHTTP(w http.ResponseWriter, req *http.Request) { log.Println(req.URL, "requested") l.handler.ServeHTTP(w, req) } type Handler struct {} func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request){ _,err := w.Write([]byte("hello")) if err != nil { log.Println(err) } } func main() { log.Println(http.ListenAndServe(":9090", NewLoggingMiddleware(&Handler{}))) }
При каждом запросе теперь можем видеть вызовы логгера:
2020/05/01 18:09:42 / requested 2020/05/01 18:09:58 /someurl requested
При использовании библиотек gorilla/mux
или
echo
, gin
работа с middleware может быть существенно упрощена.
В пакете net/http
есть структура Client
,
позволяющая выполнять запросы:
client := http.Client{} resp, err := client.Get("https://golangforall.com/en/") if err != nil { log.Fatal(err) } buf, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(string(buf)) // some html is printed
Клиент достаточно прост, позволяет использовать всего несколько методов, но этого является достаточно для практически всех задач.
Значительная часть конфигурирования клиента осуществляется через свойство Transport
.