HTTP

02.05.2020 http клиент сервер middleware


Все примеры кода находятся в github репозитории.

Рассмотрим средства для работы с http в GO.

Среди стандартных пакетов в GO есть net/http, позволяющий работать как со стороны сервера, так и клиента.

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"])
}

Middleware

Обычно в веб-приложении выполняется не только код конкретного обработчика определенного 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 может быть существенно упрощена.

HTTP-client

В пакете 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.

Другие статьи