Shanraq.org Shanraq.org
Өңдеуші және middleware: барлық бағытқа бір орама
IT

Go тілі: нөлден өз блогыңызға дейін 21-сабақ (барлығы 50)

Өңдеуші және middleware: барлық бағытқа бір орама

Go курсының жиырма бірінші сабағы. Бағыттауыштың айналасындағы орама әр сұраныс үшін бірдей істі істейді: журнал, жауап уақыты, тексеру. http.Handler деген не, орама неге өңдеушіні алып, өңдеуші қайтарады және ResponseWriter сақтамайтын жауап кодын қалай білуге болады.

Не үшін керек

Журнал әр бетте керек. Кіру тексерісі — жартысында. Уақытты өлшеу — бәрінде. Үш жолды әр өңдеушіге көшіру деген — бір күні біреуінде ұмытып, «неге бұл бет журналға жазылмайды» деп жарты күн іздеу деген сөз.

Мұндай нәрсені бір рет жазып, бәріне бірден кигізеді.

Бірден тұтас

Жаңа қалта, go mod init sabaq19, main.go:

package main

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

// logging кез келген өңдеушіні орап, журналға бір жол жазады.
func logging(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		rec := &recorder{ResponseWriter: w, code: http.StatusOK}

		next.ServeHTTP(rec, r)

		log.Printf("%s %s → %d, %s ішінде", r.Method, r.URL.Path, rec.code, time.Since(start).Round(time.Millisecond))
	})
}

// recorder жауап кодын есте сақтайды: ResponseWriter оны сақтамайды.
type recorder struct {
	http.ResponseWriter
	code int
}

func (rec *recorder) WriteHeader(code int) {
	rec.code = code
	rec.ResponseWriter.WriteHeader(code)
}

func home(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "басты бет")
}

func slow(w http.ResponseWriter, r *http.Request) {
	time.Sleep(120 * time.Millisecond)
	fmt.Fprintln(w, "баяу бет")
}

func gone(w http.ResponseWriter, r *http.Request) {
	http.Error(w, "жоқ", http.StatusNotFound)
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /{$}", home)
	mux.HandleFunc("GET /slow", slow)
	mux.HandleFunc("GET /gone", gone)

	log.Println("http://localhost:8080")
	log.Fatal(http.ListenAndServe(":8080", logging(mux)))
}

Іске қосып, төрт мекенжайға да барыңыз — /, /slow, /gone және жоқ /kokek. Төменге қарамас бұрын — соңғысы журналға түсе ме, дауыстап айтыңыз. Содан кейін терминалмен салыстырыңыз:

GET / → 200, 0s ішінде
GET /slow → 200, 121ms ішінде
GET /gone → 404, 0s ішінде
GET /kokek → 404, 0s ішінде

Соңғы жол — маңыздысы. /kokek бірде-бір өңдеушіңізге жеткен жоқ, 404-ті бағыттауыштың өзі берді, — ал журналда жазба бар.

Талдау

http.Handler — бір әдісті интерфейс

type Handler interface {
	ServeHTTP(w http.ResponseWriter, r *http.Request)
}

ServeHTTP-ті білетіннің бәрі — өңдеуші. ServeMux те өңдеуші: онда осы әдіс бар, ол жолға қарап, керекті функцияны шақырады.

Сабақтың бүкіл құрылысы осыдан. Бағыттауыш өңдеуші болса, оны өңдеуші күтетін жерге беруге болады. Мысалы — басқа өңдеушіге.

HandlerFunc — функциядан интерфейске көпір

Сіздің home мен slow — кәдімгі функциялар, оларда ServeHTTP әдісі жоқ. Оларды http.HandlerFunc айналдырады: бұл — жалғыз әдісі бар тип, ол функцияның өзін шақыра салады.

http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ... })

Жазудан қорықпаңыз: бұл int64(n) сияқты түрді түрлендіру. Функцияны функция етіп жариялайды, ал интерфейске осы жолмен кигізеді.

Middleware — өңдеушіні алып, өңдеуші қайтаратын функция

func logging(next http.Handler) http.Handler

Бүкіл мәні қолтаңбада. Кірісте — бұрынғысы. Шығыста — сол баяғысы, тек орамада. Сондықтан орамаларды бірінің үстіне бірін кигізуге болады, ал бағыттауыш та, өңдеушілер де олар туралы білмейді.

Ораманың ішінде үш бөлік бар: алдында бірдеңе, next.ServeHTTP(w, r) шақыруы, содан кейін бірдеңе.

Елестетіп көріңіз. Ғимарат кіреберісіндегі турникет. Ол сіздің қай кабинетке бара жатқаныңызды білмейді және сіздің орныңызға шешім қабылдамайды. Ол кірген уақытты белгілейді, өткізеді және шыққан уақытты белгілейді — бәріне бірдей.

Рет: орамалар матрёшкадай кигізіледі

Екеуін кигізіп, қай ретпен істейтінін көрейік:

http.ListenAndServe(":8080", mark("сыртқы")(mark("ішкі")(mux)))
сыртқы кірді
ішкі кірді
өңдеуші
ішкі шықты
сыртқы шықты

Сыртқысы сұранысты бірінші қарсы алады, жауапты соңғы шығарып салады. Осыдан қарапайым ереже: бәрінен бұрын болуға тиіс нәрсені соңғы, сырттан кигізеді.

Елестетіп көріңіз. Матрёшкалар. Сыртқысын алдыңыз — астында келесісі, ең кішкентайына дейін солай; кері жинағанда рет керісінше. Сұраныс олардың бәрінен өтіп барады да, сол жолмен қайтады.

Жауап кодын өзіңіз есте сақтауға тура келеді

Жаңадан бастағандар сүрінетін жер осы. ResponseWriter-де қандай код жіберілгенін сұрайтын тәсіл жоқ. Ол тек жаза алады.

Сондықтан орама өзінікін ұсынады:

type recorder struct {
	http.ResponseWriter
	code int
}

func (rec *recorder) WriteHeader(code int) {
	rec.code = code
	rec.ResponseWriter.WriteHeader(code)
}

Құрылымның бірінші жолы — ендіру: recorder ResponseWriter-дің барлық әдісін тегін алады және толыққанды ResponseWriter болып қалады. Өзінікі біреу ғана — WriteHeader, ол кодты есте сақтап, әрі қарай береді.

Ескерту. Бұл recorder — оқуға арналған. Нағызы көбірек біледі: кодты екі рет жазуға жол бермейді (Go-да екінші WriteHeader үнсіз еленбейді, ал мұны білген пайдалы) және ResponseWriter-дің қосымша қабілеттерін әрі қарай береді — мысалы, ағынмен беру істемейтін http.Flusher-ді. Журналға біздікі жетеді; кітапханаға — жоқ.

Ал жасау кезіндегі code: http.StatusOK — әшекей емес: денені жаза салатын өңдеуші WriteHeader-ді мүлде шақырмайды, ал жауап бәрібір 200 кодымен кетеді.

Елестетіп көріңіз. Бланк астындағы көшірме қағаз. Түпнұсқа алушыға кетеді, көшірмесі сізде қалады — әрі алушының алғанында ештеңе өзгермейді.

Барлық бағытқа орама және біреуіне ғана орама

http.ListenAndServe(":8080", logging(mux))          // бәріне
mux.Handle("GET /admin", onlyStaff(adminPage))      // бір бағытқа

Біріншісі — сайтқа ортақ: журнал, уақыт, panicнен кейін қалпына келу. Екіншісі — бәріне керек емесі: кіру тексерісі, сұраныс жиілігін шектеу.

Орамаға не салмау керек

Орама азғырады: ол бәрін көреді. Сондықтан онда орны жоқ нәрселер тез пайда болады.

Ережесі қарапайым: орама бәріне бірдей істі істейді. Ішінде if r.URL.Path == … пайда болды дегенше, бұл орама емес, екінші рет әрі нашарлау жазылған бағыттауыш. Ондай жер — не бөлек өңдеуші, не бір бағытқа арналған орама.

Сабақ картасы

Сабақ картасы: сұраныс орамадан екі рет өтеді — барғанда және қайтқанда

Өз сөзіңізбен айтыңыз

Қарамай, дауыстап немесе қағазға жазып жауап беріңіз. Жауаптары — сабақтың соңында.

  1. mux функция болмаса да, logging оны неге орай алады?
  2. Екі орама кигізіңіз. Сұранысты қайсысы бірінші көреді?
  3. Жауап коды w арқылы жіберіліп қойса, орамаға өз recorder-і не үшін керек?

Тапсырма

Міндетті. Әр жауапқа X-Blog: go-oqu тақырыбын қосатын withHeader орамасын жазыңыз да, оны logging-пен бірге бүкіл бағыттауышқа кигізіңіз. curl -i арқылы тақырыптың үш мекенжайда да, 404-те де бар екенін тексеріңіз. Содан кейін алдында және соңында хабар шығаратын екінші орама жазып, реттің дәл болжағаныңыздай екеніне көз жеткізіңіз.

Қалауыңызша.

  • recorder ішіне жауап байттарын санауды қосып, оны журналға басып шығарыңыз.
  • Өңдеушінің panicін ұстап, байланысты үзудің орнына 500 беретін recover орамасын жазыңыз.
  • Тек /slow үшін орама жасаңыз — mux.Handle арқылы — де, басқа мекенжайларда ол істемейтініне көз жеткізіңіз.

Блогта қайда тұрады

Журналды жаңа ғана жаздыңыз — ол курстың соңына дейін қалады. Оған panicнен кейін қалпына келу және пайдаланушылар пайда болғанда кіру тексерісі қосылады.

Ал келесі сабақта жауаптың денесіне ақыры HTML кетеді: үлгілер []Article-ды бетке айналдырады, ал fmt.Fprintln өңдеушілерден кетеді.

Жауаптар

Жауаптарды көрсету
  1. Себебі орама функцияны емес, http.Handler интерфейсін қабылдайды — ServeHTTP әдісі бар нәрсенің бәрін. ServeMux-та ол бар, сондықтан бағыттауыш та кез келгені сияқты өңдеуші.
  2. Сыртқысы: ол сұранысты бірінші алады, жауапты соңғы береді. Рет матрёшкадағыдай — сырттан кигізілгені бірінші қарсы алады.
  3. Себебі ResponseWriter тек жаза алады, одан жіберілген кодты оқу мүмкін емес. Орама өзінің recorder-ін қояды, ол кодты жол-жөнекей есте сақтап, нағыз жазушыға өзгеріссіз береді.

Дереккөздер

Мәтінде қате не теру қатесі кездессе, бізге айтыңыз

Тапсырманы тексеру

Алдымен VS Code-та шешіп, іске қосыңыз — редактор қатені сол жерде көрсетеді. Дайын шешімді осында қойыңыз. Тексеретін — модель: ол қатені атап көрсетеді, бірақ дайын жауапты бермейді.

Тексеру үшін кіру керек. Кіру

Пікірлер (0)

Әзірге пікір жоқ. Бірінші болыңыз.