среда, 6 февраля 2019 г.

Веб-приложение на Go: литералы функций и замыкания

Перехват условия ошибки в каждом обработчике вводит много повторяющегося кода. Что если бы мы могли обернуть каждый из обработчиков в функцию, которая делает валидацию и проверку ошибок? Литералы Go функций предоставляют мощные средства абстрагирования функциональности и это может помочь нам здесь.

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

func viewHandler(w http.ResponseWriter, 
                 r *http.Request, title string)
func editHandler(w http.ResponseWriter, 
                 r *http.Request, title string)
func saveHandler(w http.ResponseWriter, 
                 r *http.Request, title string)

Теперь давайте определим функцию-обертку, которая принимает функцию верхнего типа и возвращает функцию типа http.HandlerFunc (подходит для передачи в функцию http.HandleFunc):

func makeHandler(fn func (http.ResponseWriter, 
                 *http.Request, string)) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // Здесь мы извлечем заголовок страницы из запроса,
        // и вызываем предоставленный обработчик 'fn'
    }
}

Возвращенная функция называется замыканием, потому что она содержит значения, определенные вне функции. В этом случае переменная fn (единственный аргумент makeHandler) заключен в замыкание. Переменная fn будет одним из наших обработчиков сохранения, редактирования или просмотра.

Теперь мы можем взять код из getTitle и использовать его здесь (с некоторыми незначительными изменениями):

func makeHandler(fn func(http.ResponseWriter, 
                 *http.Request, string)) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        m := validPath.FindStringSubmatch(r.URL.Path)
        if m == nil {
            http.NotFound(w, r)
            return
        }
        fn(w, r, m[2])
    }
}

Замыкание, возвращаемое makeHandler, является функцией, которая принимает http.ResponseWriter и http.Request (другими словами, http.HandlerFunc). Замыкание извлекает title из пути запроса и проверяет его с помощью TitleValidator regexp. Если title недействителен, ошибка будет записана в ResponseWriter с помощью функции http.NotFound. Если title допустим, вложенная функция-обработчик fn будет вызываться с помощью ResponseWriter, Request и title в качестве аргументов.

Теперь мы можем обернуть функции-обработчики с помощью makeHandler в main, прежде чем они будут зарегистрированы в http пакете:

func main() {
    http.HandleFunc("/view/", makeHandler(viewHandler))
    http.HandleFunc("/edit/", makeHandler(editHandler))
    http.HandleFunc("/save/", makeHandler(saveHandler))

    log.Fatal(http.ListenAndServe(":8080", nil))
}

Наконец, мы удаляем вызовы getTitle из функций обработчиков, делая их намного проще:

func viewHandler(w http.ResponseWriter, 
                 r *http.Request, title string) {
    p, err := loadPage(title)
    if err != nil {
        http.Redirect(w, r, "/edit/"+title, 
                      http.StatusFound)
        return
    }
    renderTemplate(w, "view", p)
}

func editHandler(w http.ResponseWriter, 
                 r *http.Request, title string) {
    p, err := loadPage(title)
    if err != nil {
        p = &Page{Title: title}
    }
    renderTemplate(w, "edit", p)
}

func saveHandler(w http.ResponseWriter, 
                 r *http.Request, title string) {
    body := r.FormValue("body")
    p := &Page{Title: title, Body: []byte(body)}
    err := p.save()
    if err != nil {
        http.Error(w, err.Error(), 
                   http.StatusInternalServerError)
        return
    }
    http.Redirect(w, r, "/view/"+title, 
                  http.StatusFound)
}


Читайте также:


Комментариев нет:

Отправить комментарий