Шаблон использования пакета net/http языка Go

Go

Аннотация: Содержание этой статьи очень простое и легкое для понимания.исходный адрес, я чувствую, что это самое ясноеnet/httpСтатья об использовании пакета, поэтому переведите ее и поделитесь.

Основа всего: ServeMux и обработчики

Обработка HTTP-запросов на языке Go в основном связана с двумя вещами:ServeMuxа такжеHandler.

ServrMuxПо сути, маршрутизатор HTTP-запросов (или мультиплексор). Он сравнивает входящий запрос со списком предопределенных URL-адресов и вызывает связанный обработчик, когда путь совпадает.

ОбработчикОтвечает за вывод заголовков и тела ответа HTTP. любой удовлетворенныйhttp.HandlerинтерфейсВсе объекты могут быть использованы в качестве обработчика. С точки зрения непрофессионала, если объект имеет следующую подписьServeHTTPМетод может быть:

ServeHTTP(http.ResponseWriter, *http.Request)

HTTP-пакет Go поставляется с несколькими функциями для обычных обработчиков, таких какFileServer,NotFoundHandlerа такжеRedirectHandler. Начнем с простого конкретного примера:

$ mkdir handler-example
$ cd handler-example
$ touch main.go
//File: main.go
package main

import (
  "log"
  "net/http"
)

func main() {
  mux := http.NewServeMux()

  rh := http.RedirectHandler("http://example.org", 307)
  mux.Handle("/foo", rh)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}

Быстро пройдитесь по коду:

  • существуетmainВ функции мы используем толькоhttp.NewServeMuxфункция для создания пустогоServeMux.

  • Затем мы используемhttp.RedirectHandlerФункция создает новый обработчик, который выполняет операцию перенаправления 307 для всех полученных запросов наhttp://example.org.

  • Далее мы используемServeMux.HandleФункция регистрирует обработчик во вновь созданномServeMux, поэтому он находится в пути URL/fooВсе полученные запросы передаются этому процессору.

  • Наконец мы создаем новый сервер и проходимhttp.ListenAndServeФункция слушает все входящие запросы, передавая только что созданныйServeMuxчтобы соответствовать соответствующему обработчику для запроса.

Идем дальше и запускаем эту программу:

$ go run main.go
Listening...

затем посетите в своем браузереhttp://localhost:3000/foo, вы должны увидеть, что запрос был успешно перенаправлен.

Вы должны заметить некоторые интересные вещи:ListenAndServerСигнатура функцииListenAndServe(addr string, handler Handler), но второй параметр, который мы передаем, этоServeMux.

Мы можем сделать это, потому чтоServeMuxТакже естьServeHTTPметод, поэтому он также является законнымHandler.

Для меня будетServerMuxИспользование его в качестве специального обработчика является упрощением. Вместо вывода самого ответа он передает запрос другим обработчикам, зарегистрированным в нем. Поначалу это может показаться не очевидным скачком, но в Go theHandlerЦепочка вместе - очень распространенное использование.

Пользовательские обработчики

Давайте создадим собственный обработчик, который будет выводить текущее местное время в определенном формате:

type timeHandler struct {
  format string
}

func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  tm := time.Now().Format(th.format)
  w.Write([]byte("The time is: " + tm))
}

Сам код не имеет значения в этом примере.

Суть в том, что у нас есть объект (в данном случаеtimerHandlerструктура, но это также может быть строка, функция или что-то еще), мы реализуемServeHTTP(http.ResponseWriter, *http.Request)Подписанный метод — это все, что нам нужно для создания обработчика.

Мы интегрируем это в конкретный пример:

//File: main.go

package main

import (
  "log"
  "net/http"
  "time"
)

type timeHandler struct {
  format string
}

func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  tm := time.Now().Format(th.format)
  w.Write([]byte("The time is: " + tm))
}

func main() {
  mux := http.NewServeMux()

  th := &timeHandler{format: time.RFC1123}
  mux.Handle("/time", th)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}

mainВ функции мы инициализируем как обычную структуруtimeHandler,использовать&Символ получает свой адрес. Затем, как и в предыдущем примере, используемmux.Handleфункция, чтобы зарегистрировать его с помощьюServerMux.

Теперь, когда мы запускаем это приложение,ServerMuxлюбая пара/timeзапросить напрямуюtimeHandler.ServeHTTPобработка методом.

Посетите этот адрес, чтобы увидеть эффект:http://localhost:3000/time.

Обратите внимание, что мы можем легко повторно использовать на нескольких маршрутахtimeHandler:

func main() {
  mux := http.NewServeMux()

  th1123 := &timeHandler{format: time.RFC1123}
  mux.Handle("/time/rfc1123", th1123)

  th3339 := &timeHandler{format: time.RFC3339}
  mux.Handle("/time/rfc3339", th3339)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}

использовать функцию как обработчик

Для простых случаев (например, в примере выше) определите новый с помощьюServerHTTPПользовательские типы для методов немного громоздки. Давайте посмотрим на другой способ, мы используемhttp.HandlerFuncтип, чтобы обычная функция удовлетворялаHandlerУсловия интерфейса.

любой естьfunc(http.ResponseWriter, *http.Request)Знаковые функции могут быть преобразованы вHandlerFuncТипы. Это полезно, потому чтоHandlerFuncвстроенный объектServeHTTPметод, последний может быть умным и удобным для вызова содержимого функции, которую мы изначально предоставили.

Если вы все еще немного запутались, попробуйте взглянуть на [связанный исходный код]gowave.org/doublebed/pkg/net…. Вы увидите, что создание функционального объекта удовлетворяетHandlerИнтерфейс очень простой и элегантный.

Давайте повторно реализуем эту техникуtimeHandlerзаявление:

//File: main.go
package main

import (
  "log"
  "net/http"
  "time"
)

func timeHandler(w http.ResponseWriter, r *http.Request) {
  tm := time.Now().Format(time.RFC1123)
  w.Write([]byte("The time is: " + tm))
}

func main() {
  mux := http.NewServeMux()

  // Convert the timeHandler function to a HandlerFunc type
  th := http.HandlerFunc(timeHandler)
  // And add it to the ServeMux
  mux.Handle("/time", th)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}

По сути, превращение функции вHandlerFuncпосле регистрации наServeMuxявляется распространенным использованием, поэтому язык Go предоставляет удобный способ сделать это:ServerMux.HandlerFuncметод.

Переписываем с удобствомmain()Функция выглядит следующим образом:

func main() {
  mux := http.NewServeMux()

  mux.HandleFunc("/time", timeHandler)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}

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

Вы могли заметить, что, в отличие от предыдущего способа, нам пришлось жестко запрограммировать формат времени, чтобыtimeHandlerв методе. Если мы хотим отmain()Как передать некоторую информацию или переменные процессору в функции?

Элегантный способ — поместить наш обработчик в замыкание с переменными, которые мы хотим использовать в нем:

//File: main.go
package main

import (
  "log"
  "net/http"
  "time"
)

func timeHandler(format string) http.Handler {
  fn := func(w http.ResponseWriter, r *http.Request) {
    tm := time.Now().Format(format)
    w.Write([]byte("The time is: " + tm))
  }
  return http.HandlerFunc(fn)
}

func main() {
  mux := http.NewServeMux()

  th := timeHandler(time.RFC1123)
  mux.Handle("/time", th)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}

timeHandlerФункции теперь имеют более тонкую идентичность. Вместо того, чтобы оборачивать функцию как обработчик (как мы делали раньше), мы теперь используем ее для возврата обработчика. Этот механизм имеет два ключевых момента:

Первый заключается в созданииfn, которая является анонимной функцией, которая будетformatПеременные инкапсулируются в замыкание. Природа замыкания позволяет процессору в любом случае обращаться к локальной области видимости.formatПеременная.

Во-вторых, наша функция замыкания удовлетворяетfunc(http.ResponseWriter, *http.Request)подписать. Если вы помните, что мы говорили ранее, это означает, что мы можем преобразовать его вHandlerFuncтип (удовлетворенныйhttp.Handlerинтерфейс). нашtimeHandlerЗатем функция преобразуется вHandlerFuncвернуть.

В приведенном выше примере мы уже можем передать обработчику простую строку. Однако в реальных приложениях вы можете использовать этот метод для передачи соединений с базой данных, групп шаблонов или других контекстов уровня приложения. Использование глобальных переменных также является хорошим вариантом, с дополнительным преимуществом написания более элегантных автономных обработчиков для тестирования.

Возможно, вы также видели такое же написание, например:

func timeHandler(format string) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    tm := time.Now().Format(format)
    w.Write([]byte("The time is: " + tm))
  })
}

или по возвращении используйтеHandlerFuncНеявное преобразование типов:

func timeHandler(format string) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) {
    tm := time.Now().Format(format)
    w.Write([]byte("The time is: " + tm))
  }
}

Более удобный DefaultServeMux

Вы могли видеть это во многих местахDefaultServeMux, из самого простогоHello WorldНапример, в исходный код языка go.

Мне потребовалось много времени, чтобы понятьDefaultServerMuxВ этом нет ничего особенного.DefaultServerMuxэто то, что мы использовали раньшеServerMux, только следуетnet/httppКогда пакет инициализируется, он инициализируется автоматически. Соответствующие строки в исходном коде Go выглядят следующим образом:

var DefaultServeMux = NewServeMux()

net/httppackage предоставляет набор ярлыков для сопровожденияDefaultServeMux:http.Handleа такжеhttp.HandleFunc. Эти функции делают то же самое, что и функции с похожими именами, которые мы видели ранее, с той лишь разницей, что они регистрируют обработчик дляDefaultServerMux, и до того, как мы зарегистрировались, чтобы создать собственныйServeMux.

также,ListenAndServeВ случае, когда другой процессор не предусмотрен (т. е. второй параметр установлен вnil), который используется внутриDefaultServeMux.

Итак, в качестве последнего шага мы используемDefaultServeMuxпереписать нашtimeHandlerзаявление:

//File: main.go
package main

import (
  "log"
  "net/http"
  "time"
)

func timeHandler(format string) http.Handler {
  fn := func(w http.ResponseWriter, r *http.Request) {
    tm := time.Now().Format(format)
    w.Write([]byte("The time is: " + tm))
  }
  return http.HandlerFunc(fn)
}

func main() {
  // Note that we skip creating the ServeMux...

  var format string = time.RFC1123
  th := timeHandler(format)

  // We use http.Handle instead of mux.Handle...
  http.Handle("/time", th)

  log.Println("Listening...")
  // And pass nil as the handler to ListenAndServe.
  http.ListenAndServe(":3000", nil)
}