Понимание технологии обертки http handlerfunc в Go - PullRequest
0 голосов
/ 08 декабря 2018

Я видел статью , написанную Мэттом Райером о том, как вы можете использовать тип сервера и обработчики http типа, которые являются обертками для func(http.ResponseWriter, *http.Request)

. Я вижу это как болееэлегантный способ создания REST API, однако я совершенно не уверен, чтобы оболочка работала правильно.Я получаю ошибку несоответствующего типа при компиляции или 404 при вызове.

Это в основном то, что у меня есть для учебных целей на данный момент.

package main

import(
   "log"
   "io/ioutil"
   "encoding/json"
   "os"
   "net/http"
   "github.com/gorilla/mux"
)

type Config struct {
   DebugLevel int `json:"debuglevel"`
   ServerPort string `json:"serverport"`
}

func NewConfig() Config {

   var didJsonLoad bool = true

   jsonFile, err := os.Open("config.json")
   if(err != nil){
      log.Println(err)
      panic(err)
      recover()
      didJsonLoad = false
   }

   defer jsonFile.Close()

   jsonBytes, _ := ioutil.ReadAll(jsonFile)

   config := Config{}

   if(didJsonLoad){
      err = json.Unmarshal(jsonBytes, &config)
      if(err != nil){
         log.Println(err)
         panic(err)
         recover()
      }
   }

   return config
}

type Server struct {
   Router *mux.Router
}

func NewServer(config *Config) *Server {
   server := Server{
      Router : mux.NewRouter(),
   }

   server.Routes()

   return &server
}

func (s *Server) Start(config *Config) {
   log.Println("Server started on port", config.ServerPort)
   http.ListenAndServe(":"+config.ServerPort, s.Router)
}

func (s *Server) Routes(){
   http.Handle("/sayhello", s.HandleSayHello(s.Router))
}

func (s *Server) HandleSayHello(h http.Handler) http.Handler {
   log.Println("before")
   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
      w.Write([]byte("Hello."))
      h.ServeHTTP(w, r)
   })
}

func main() {
   config := NewConfig()
   server := NewServer(&config)
   server.Start(&config)
}

Поскольку это сейчас, я вернусь только к 404, вызывающему localhost:8091/sayhello.(Да, это порт, который я установил в своем конфигурационном файле.)

Раньше, так как я использую Gorilla Mux, я устанавливал обработчик так:

func (s *Server) Routes(){
    s.Router.HandleFunc("/sayhello", s.HandleSayHello)
}

Который дал мне эту ошибку, я был полностью озадачен.cannot use s.HandleSayHello (type func(http.Handler) http.Handler) as type func(http.ResponseWriter, *http.Request) in argument to s.Router.HandleFunc

Я видел в решении для этого SO сообщения , что я должен использовать http.Handle и передать в маршрутизаторе.

func (s *Server) Routes(){
   http.Handle("/sayhello", s.HandleSayHello(s.Router))
}

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

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

Я не до конца понимаю, что происходит в этих примерах, потому что типы, которые они определяют, неКажется, не привыкаешь.

Этот ресурс показывает, как пишутся обработчики, а не как настраиваются маршруты.

Я обнаружил, что Gorilla Mux имеет встроенные оболочки для этого материала, но мне трудно понять API.

Пример, который они показывают, выглядит следующим образом:

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Do stuff here
        log.Println(r.RequestURI)
        // Call the next handler, which can be another middleware in the chain, or the final handler.
        next.ServeHTTP(w, r)
    })
}

Имаршруты определены следующим образом:

r := mux.NewRouter()
r.HandleFunc("/", handler)
r.Use(loggingMiddleware)

Какова цель r.Use, когда он не регистрирует URL-маршрут?Как используется handler?

Когда мой код написан так, я не получаю ошибок компиляции, но я не понимаю, как моя функция должна написать «Hello».Я думаю, я мог бы использовать w.Write в неправильном месте.

Ответы [ 2 ]

0 голосов
/ 08 декабря 2018

Я думаю, вы могли бы смешивать «промежуточное ПО» с реальными обработчиками.

http-обработчики

Типы, которые реализуют метод ServeHTTP(w http.ResponseWriter, r *http.Request), удовлетворяют http.Handlerинтерфейс и, следовательно, экземпляры этих типов могут, например, использоваться в качестве второго аргумента функции http.Handle или эквивалентного метода http.ServeMux.Handle.

Пример может прояснить это:

type myHandler struct {
    // ...
}

func (h myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte(`hello world`))
}

func main() {
    http.Handle("/", myHandler{})
    http.ListenAndServe(":8080", nil)
}

Функции обработчика http

Функции с подписью func(w http.ResponseWriter, r *http.Request) - это функции обработчика http, которые можно преобразовать в http.Handler с помощьютип http.HandlerFunc.Обратите внимание, что подпись совпадает с подписью метода http.Handler ServeHTTP.

Например:

func myHandlerFunc(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte(`hello world`))
}

func main() {
    http.Handle("/", http.HandlerFunc(myHandlerFunc))
    http.ListenAndServe(":8080", nil)
}

Выражение http.HandlerFunc(myHandlerFunc) преобразует myHandlerFuncфункция типа http.HandlerFunc, которая реализует метод ServeHTTP, поэтому результирующее значение этого выражения является действительным http.Handler и поэтому может быть передано в вызов функции http.Handle("/", ...) в качестве второго аргумента.

Использование простых функций обработчика http вместо типов обработчиков http, которые реализуют метод ServeHTTP, достаточно распространено, и стандартная библиотека предоставляет альтернативы http.HandleFunc и http.ServeMux.HandleFunc.Все, что делает HandleFunc, - это то, что мы делаем в приведенном выше примере: он преобразует переданную функцию в http.HandlerFunc и вызывает http.Handle с результатом.


http middleware

Функции с подписью, аналогичной этой func(h http.Handler) http.Handler, считаются промежуточным программным обеспечением.Имейте в виду, что сигнатура промежуточного программного обеспечения не ограничена, у вас может быть промежуточное программное обеспечение, которое принимает больше аргументов, чем просто один обработчик, и также возвращает больше значений, но в целом функция, которая принимает по крайней мере один обработчик и перезапускает по крайней мере одинНовый обработчик можно рассматривать как промежуточное программное обеспечение.

В качестве примера рассмотрим http.StripPrefix.


Давайте теперь проясним некоторые очевидные недоразумения.

# 1

func (s *Server) HandleSayHello(h http.Handler) http.Handler {

Название метода и способ, которым вы использовали его ранее, передавая его непосредственно HandleFunc, предполагают, что вы хотите, чтобы это был обычный функционал http-обработчика, ноподпись - это промежуточное ПО, и это причина ошибки, которую вы получили:

cannot use s.HandleSayHello (type func(http.Handler) http.Handler) as type func(http.ResponseWriter, *http.Request) in argument to s.Router.HandleFunc

Таким образом, обновление вашего кода до чего-то вроде приведенного ниже кода избавит от этой ошибки компиляции и также правильно отобразит "Hello." текст при посещении /sayhello.

func (s *Server) HandleSayHello(w http.ResponseWriter, r *http.Request) {
      w.Write([]byte("Hello."))
}

func (s *Server) Routes(){
    s.Router.HandleFunc("/sayhello", s.HandleSayHello)
}

# 2

Так как сейчас, я верну только вызов 404 localhost:8091/sayhello.

Проблема в этих двух строках

http.Handle("/sayhello", s.HandleSayHello(s.Router))

и

http.ListenAndServe(":"+config.ServerPort, s.Router)

Функция http.Handle регистрирует переданный обработчик с экземпляром ServeMux по умолчанию , он не регистрирует его в экземпляре маршрутизатора gorilla в s.Router, как вы, вероятно, предполагаете, и затем вы передаете s.Router в функцию ListenAndServe, которая использует его для обслуживания каждого запроса, поступающего в localhost:8091, и с тех порУ s.Router нет зарегистрированного обработчика, вы получаете 404.


# 3

Но теперь, как мне предотвратить выполнение фактической функции при установкемои маршруты?"before" в моем операторе печати появляется перед запуском сервера.

func (s *Server) Routes(){
   http.Handle("/sayhello", s.HandleSayHello(s.Router))
}

func (s *Server) HandleSayHello(h http.Handler) http.Handler {
   log.Println("before")
   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
      w.Write([]byte("Hello."))
      h.ServeHTTP(w, r)
   })
}

Зависит от того, что вы подразумеваете под «фактической функцией».В Go вы выполняете функции, добавляя круглые скобки в конце их имени.То, что выполняется здесь, когда вы устанавливаете маршруты, это функция http.Handle и метод HandleSayHello.

Метод HandleSayHello имеет по существу два оператора в своем теле, функцию-выражение-вызовоператор log.Println("before") и оператор возврата return http.HandlerFunc(..., и оба они будут выполняться каждый раз, когда вы вызываете HandleSayHello.Однако операторы внутри возвращенной функции-обработчика не будут выполняться при вызове HandleSayHello, вместо этого они будут выполняться при вызове возвращенного обработчика.

Вы не хотите, чтобы "before" былнапечатано, когда вызывается HandleSayHello, но вы хотите, чтобы оно печаталось при вызове возвращаемого обработчика?Все, что вам нужно сделать, это переместить строку журнала вниз к возвращаемому обработчику:

func (s *Server) HandleSayHello(h http.Handler) http.Handler {
   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
      log.Println("before")
      w.Write([]byte("Hello."))
      h.ServeHTTP(w, r)
   })
}

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

Вместо этого, возможно, рассмотрим что-то вроде этого:

// the handler func
func (s *Server) HandleSayHello(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello."))
}

// the middleware
func (s *Server) PrintBefore(h http.Handler) http.Handler {
       return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
               log.Println("before") // execute before the actual handler
               h.ServeHTTP(w, r)     // execute the actual handler
       })
}

func (s *Server) Routes(){
        // PrintBefore takes an http.Handler but HandleSayHello is an http handler func so
        // we first need to convert it to an http.Hanlder using the http.HandlerFunc type.
        s.Router.HandleFunc("/sayhello", s.PrintBefore(http.HandlerFunc(s.HandleSayHello)))
}

# 4

r := mux.NewRouter()
r.HandleFunc("/", handler)
r.Use(loggingMiddleware)

Какова цель r.Use, когда он не регистрирует URL-маршрут?Как используется handler?

Use регистрирует промежуточное программное обеспечение на уровне маршрутизатора, что означает, что всем обработчикам, зарегистрированным на этом маршрутизаторе, будет выполнено промежуточное программное обеспечение, прежде чем они сами будут выполнены.

Например, приведенный выше код эквивалентен следующему:

r := mux.NewRouter()
r.HandleFunc("/", loggingMiddleware(handler))

Конечно, Use не является ненужным и запутанным, это полезно, если у вас много конечных точек с разными обработчиками и всемииз них требуется связка промежуточного программного обеспечения.

Затем код, подобный этому:

r.Handle("/foo", mw1(mw2(mw3(foohandler))))
r.Handle("/bar", mw1(mw2(mw3(barhandler))))
r.Handle("/baz", mw1(mw2(mw3(bazhandler))))
// ... hundreds more

Может быть радикально упрощен:

r.Handle("/foo", foohandler)
r.Handle("/bar", barhandler)
r.Handle("/baz", bazhandler)
// ... hundreds more
r.Use(mw1, mw2, m3)
0 голосов
/ 08 декабря 2018

Из gorilla mux файл документа:

Промежуточные программы - это (обычно) небольшие кусочки кода, которые принимают один запрос, что-то с ним делают и передают другому промежуточному программному обеспечению.или окончательный обработчик.

r.Use() полезен для регистрации промежуточного программного обеспечения.Вы можете зарегистрировать промежуточное программное обеспечение как можно больше.

r.HandleFunc("/hello", func (w http.ResponseWriter, r *http.Request) {
    fmt.Println("from handler")
    w.Write([]byte("Hello! \n"))
})

r.Use(func (next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // do something here
        fmt.Println("from middleware one")
        next.ServeHTTP(w, r)
    })
})

r.Use(func (next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // do another thing here
        fmt.Println("from middleware two")
        next.ServeHTTP(w, r)
    })
})

r.Use(func (next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // do something again but differently here
        fmt.Println("from middleware three")
        next.ServeHTTP(w, r)
    })
})

Если вы видите код выше, на каждом промежуточном программном обеспечении есть оператор next.ServeHTTP(w, r).Оператор используется для перехода входящего запроса к следующему шагу (это может быть следующее промежуточное программное обеспечение или фактический обработчик).

Каждое промежуточное программное обеспечение всегда выполняется перед фактическим обработчиком.Само выполнение происходит в последовательном порядке, в зависимости от порядка регистрации промежуточного программного обеспечения.

После успешного выполнения всего промежуточного программного обеспечения next.ServeHTTP(w, r) последнего промежуточного программного обеспечения будет обрабатывать входящий запрос для перехода к фактическому обработчику (в приведенном выше примере это обработчик маршрута /hello).

При доступе к /hello журнал выдаст:

from middleware one
from middleware two
from middleware three
from handler

Если при определенных условиях входящий запрос не будет обработан, просто не вызывайте next.ServeHTTP(w, r).Пример:

r.Use(func (next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // ...

        if someCondition {
            next.ServeHTTP(w, r)
        } else {
            http.Error(w, "some error happen", http.StatusBadRequest)
        }
    })
})

Промежуточное ПО, часто используемое для выполнения какого-либо процесса над входящим запросом, до или после вызова обработчика.Например: конфигурация CORS, проверка CRSF, сжатие gzip, ведение журнала и т. Д.

...