[Введение] 4D ручной разрыв Go http исходный код server.go

Go

Author:Wzy_CC

Полный текст составляет 10 000 слов.

Время чтения 10 ~ 15 минут

предисловие

Цели этой статьи:

Анализ серии действий от регистрации маршрута до запроса маршрутизации после прослушивания локального порта в основном ограничиваетсяnet/http server.goэтот файл пакетаДетали, связанные с маршрутизациейинтерпретировать

Цель написания:

При использовании нативных библиотек для веб-разработки многих новичков легко запутатьmux.Handle()/mux.HandleFunc()/mux.Handler()/Handlerfunc/Handler/Handle()/Handlefunc()/handlerЧтобы быть блефом, несколько имен похожи сами по себе, первая буква иногда прописная, иногда строчная, иногда хендл, иногда хендлер, они выглядят одинаково, но их типы и функции совершенно разные. Поскольку названия похожи и их легко спутать, их истинное значение нелегко понять, а разработчикам нелегко запомнить. Некоторые имена даже не показывают, для чего используется эта функция, а некоторые относятся к историческому наследию, когда библиотека была разработана, что затрудняет понимание библиотеки http.

Многие онлайн-уроки рассказывают только о том, что что-то такое и для чего оно используется, а не о том, почему оно такое, почему оно так спроектировано и в чем преимущества такого дизайна. Что еще более важно, некоторые туториалы старые, прошло два года с 2018. Многие функции были оптимизированы и переписаны, по крайней мереserver.goМногие функции сильно изменились в 2018 году. Многие руководства 2018 года устарели, и в 2022 году может потребоваться переписать интерпретацию http-библиотеки. Однако некоторые вещи постоянны, многие дизайнерские идеи являются общими, и именно эти идеи следует осваивать новичкам. На самом деле, заучивание четырех способов написания дескриптора наизусть не поможет развитию: если вы не понимаете этого глубоко, вы часто перелистываете документ и запутаетесь.

Некоторые принципы дизайна Go очень интересны и не могут быть объяснены в нескольких десятках тысяч слов.

Порядок чтения:

Полезно прочитать эту статью, чтобы

содержание

[TOC]

Обзор

Пример официального сайта

В примере с официальным веб-сайтом использование go для создания стабильного веб-сервера с высокой степенью параллелизма занимает всего несколько строк:

http.Handle("/foo", fooHandler)

http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})

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

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

Шаги для нормальной работы сервера go: регистрация функций, прослушивание портов, прием запросов, обработка запросов, предоставление услуг и закрытие ссылок.

0. Функция регистрации: сначала зарегистрируйте соответствующие правила маршрутизации в таблице маршрутизации.

1. Порт прослушивания: создайте сокет для прослушивания и слушайте в цикле

2. Принять запрос: принять запрос, создать объект подключения к сетевой ссылке, запустить сопрограмму для обработки ссылки (предполагается, что мультиплексирование здесь), новая ссылка для каждой службы, вconn.connect()вызовет serveHTTP для обработки запроса

3. Обработайте запрос: прочитайте параметры запроса, чтобы создать объект запроса, и найдите соответствующий обработчик в таблице маршрутизации карты в соответствии с путем запроса. Затем назначьте запрос функции обработчика

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

5. Закройте ссылку: слой приложений закрывает ссылку после обработки запроса

Предварительное знание

Базовая грамматика Go, веб-основа, * сжатое дерево словарей

Объем/схема анализа исходного кода

основной анализnet/httpФайл server.go в библиотеке, но место ограничено, чтобы сосредоточиться на анализе (использоватьmux.XX()Вместо этого упроститеServeMux.XX()):

1.ServeMuxСтруктура и ее методы:mux.NewServeMux(),mux.Handle(),mux.HandleFunc(),mux.Handler()/mux.handler(),mux.ServeHTTP()

2.HandlerFuncСтруктура и способ ее реализации:HandlerFunc.ServeHTTP()

3.HandlerТип интерфейса

4. ФункцияHandle()и функцияHandleFunc()

Это все для части маршрутизации

ServeMux

ServeMux — это структура

Определение ServeMux

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

type ServeMux struct {
    // contains filtered or unexported fields
}

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

type ServeMux struct {
    mu    sync.RWMutex          // 读写互斥锁
    m     map[string]muxEntry   // 路由表
    es    []muxEntry            // 有序数组,从最长到最短排序
    hosts bool                  // whether any patterns contain hostnames
}

ServeMuxСтруктура по существу состоит из мьютекса чтения-записи mu, таблицы маршрутизации m, массива es (во многих старых учебниках нет этого поля обновления) и содержит логическое значение hosts.

в:

1.mu — это мьютекс для чтения и записи, подробности см.дизайн-мышление

2.m — это таблица маршрутизации, которая по сути является таблицей маршрутизации.map[string]muxEntryПеременная, ключ — это строка пути (состоящая из метода и объединенной строки входящего параметра), а значение — соответствующая структура обработки.muxEntry

3.es — это упорядоченный массив, который поддерживает все суффиксы от длинных до коротких, как/Адрес маршрутизации, почему он разработан таким образом, см.дизайн-мышление

4. Логический атрибут hosts отмечает, есть ли в маршруте имя хоста.Если значение hosts равно true, начало маршрута не может быть/

м таблица маршрутизацииmuxEntryСтруктура выглядит следующим образом:

type muxEntry struct {
    h        Handler // 处理程序
    pattern  string  // 路由路径
}

muxEntryПо существу состоит из класса Handler и строки пути маршрутизации, где:

1.h — это тип Handler, а тип Handler требует реализации интерфейсаServeHTTPметод

2.patternСобственно то же самое, что и ключ в таблице маршрутизации

Мультиплексор по умолчанию

существуетnet/httpМультиплексор по умолчанию указан в пакете, если не указать его вручную, то используется мультиплексор по умолчанию:

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

Почему здесь можно использовать переменную перед объявлением?Дейв Чейни сказал мне, что переменные на уровне пакета не имеют ничего общего с порядком объявления, также сказал мне задать этот вопрос в slack позже :sweat_smile:, когда компилятор выполняет инициализацию, он сначала инициализирует переменные уровня пакета, поэтому его можно использовать независимо от того, где он объявлен.

Метод ServeMux

общедоступный метод

mux.NewServeMux()

// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }

создать новый и вернутьServeMuxСтруктура, выделение места для полей внутри структуры. Стоит отметить, что значение по умолчанию инициализируемого и возвращаемого поля структуры hosts равно false

mux.Handler()

Для данного запроса,mux.Handler()Всегда возвращайте ненулевой обработчик для использования. если методCONNECTвидетьчастный методmux.redirectToPathSlash()

mux.Handler()вызвать приватный методmux.handler(),существуетmux.handler()звонил внутреннеmux.match()метод для возврата шаблона соответствия обработчика

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {

	// CONNECT requests are not canonicalized.
	if r.Method == "CONNECT" {
		// If r.URL.Path is /tree and its handler is not registered,
		// the /tree -> /tree/ redirect applies to CONNECT requests
		// but the path canonicalization does not.
		if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
			return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
		}

		return mux.handler(r.Host, r.URL.Path)
	}

	// All other requests have any port stripped and path cleaned
	// before passing to mux.handler.
	host := stripHostPort(r.Host)
	path := cleanPath(r.URL.Path)

	// If the given path is /tree and its handler is not registered,
	// redirect for /tree/.
	if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
		return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
	}

	if path != r.URL.Path {
		_, pattern = mux.handler(host, path)
		url := *r.URL
		url.Path = path
		return RedirectHandler(url.String(), StatusMovedPermanently), pattern
	}

	return mux.handler(host, r.URL.Path)
}

дляr.hostа такжеr.URL.PathПосле простого процесса кратко объясните две функцииcleanPath()а такжеstripHostPort()Что они сделали:

cleanPath()

1. Обработка недопустимых маршрутов

2. Для обработки косых черт замените недопустимые множественные косые черты

3. Удалить все.заменить на эквивалентный путь

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

// cleanPath returns the canonical path for p, eliminating . and .. elements.
func cleanPath(p string) string {
	if p == "" {
		return "/"
	}
	if p[0] != '/' {
		p = "/" + p
	}
	np := path.Clean(p)
	// path.Clean removes trailing slash except for root;
	// put the trailing slash back if necessary.
	if p[len(p)-1] == '/' && np != "/" {
		// Fast path for common case of p being the string we want:
		if len(p) == len(np)+1 && strings.HasPrefix(p, np) {
			np = p
		} else {
			np += "/"
		}
	}
	return np
}

stripHostPort()

является спецификацией формата хоста

// stripHostPort returns h without any trailing ":<port>".
func stripHostPort(h string) string {
	// If no port on host, return unchanged
	if strings.IndexByte(h, ':') == -1 {
		return h
	}
	host, _, err := net.SplitHostPort(h)
	if err != nil {
		return h // on error, return unchanged
	}
	return host
}

mux.ServeHTTP()

mux.ServeHTTP()Сделайте запрос к обработчику, который лучше всего соответствует URL-адресу запроса, и, наконец, вызовите реализациюServeHTTP()метод типа HandlerHandler.ServeHTTP()для обработки запроса

Это немного сбивает с толку, короче говоря, HTTP-запрос сначала делаетmux.ServeHTTP()обработки, внутри функции, называемойmux.Handler()(Видетьчастный методmux.handler()) для выбора обработчика (вызывается в процессеmux.handler()/mux.RedirectHandler()для просмотра таблицы маршрутизации и, наконец, вmux.handler()внутренний звонокmux.match()Для доработки роутинга найти Handler типа H)

// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
	if r.RequestURI == "*" {
		if r.ProtoAtLeast(1, 1) {
			w.Header().Set("Connection", "close")
		}
		w.WriteHeader(StatusBadRequest)
		return
	}
	h, _ := mux.Handler(r)
	h.ServeHTTP(w, r)
}

mux.Handle()

На этапе регистрации маршрута/добавления маршрута зарегистрируйте функциюmux.Handle()Отвечает за регистрацию обработчиков и путей в таблице маршрутизации, по сути, процесс написания таблицы.

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
	mux.mu.Lock()
	defer mux.mu.Unlock()

	if pattern == "" {
		panic("http: invalid pattern")
	}
	if handler == nil {
		panic("http: nil handler")
	}
	if _, exist := mux.m[pattern]; exist {
		panic("http: multiple registrations for " + pattern)
	}

	if mux.m == nil {
		mux.m = make(map[string]muxEntry)
	}
	e := muxEntry{h: handler, pattern: pattern}
	mux.m[pattern] = e
	if pattern[len(pattern)-1] == '/' {
		mux.es = appendSorted(mux.es, e)
	}

	if pattern[0] != '/' {
		mux.hosts = true
	}
}

Повторение маршрутов в таблице вызовет панику, косая черта суффикса/Путь, написанный по длинеmux.es, который также был упомянут в предыдущем анализе структуры MUX.

Просто посмотрите на функцию сортировки его реализации:

func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
	n := len(es)
	i := sort.Search(n, func(i int) bool {
		return len(es[i].pattern) < len(e.pattern)
	})
	if i == n {
		return append(es, e)
	}
	// we now know that i points at where we want to insert
	es = append(es, muxEntry{}) // try to grow the slice in place, any entry works.
	copy(es[i+1:], es[i:])      // Move shorter entries down
	es[i] = e
	return es
}

mux.HandleFunc()

mux.HandleFunc()На мой взгляд, это самый важный способ, а также прописать обработчик в таблице маршрутизации.mux.HandleFunc()а такжеmux.Handle()Разница, собственно, с точки зрения тела функцииmux.HandleFunc()верноmux.Handle()Переупаковка функции под названиемHandlerFunc()Эта функция адаптера,По сути, общая функция используется в качестве синтаксического сахара для обработчиков HTTP-запросов., нам больше не нужно реализовыватьServeHTTP()метод, вместо этого передается обычная функция до тех пор, покаfunc(ResponseWriter, *Request)Типа, вы можете выполнять маршрутизацию функций, в основном можно выполнить строку кода, поэтому мы можем легко создать простую веб-программу на примере официального сайта. Пример на официальном сайтеHandleFunc()Функция, которая вызывает мультиплексор по умолчаниюDefaultServeMux.HandleFunc()метод, разработчикам нужно только самим определить обычные функции:

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if handler == nil {
		panic("http: nil handler")
	}
	mux.Handle(pattern, HandlerFunc(handler))
}

частный метод

mux.match()

при звонкеmux.Handler()При возврате класса Handlermux.handler()будет вызываться внутреннеmux.match()функцию, которую можно рассматривать как процесс поиска маршрута (Handle()процесс регистрации маршрута)

// Find a handler on a handler map given a path string.
// Most-specific (longest) pattern wins.
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
	// Check for exact match first.
	v, ok := mux.m[path]
	if ok {
		return v.h, v.pattern
	}

	// Check for longest valid match.  mux.es contains all patterns
	// that end in / sorted from longest to shortest.
	for _, e := range mux.es {
		if strings.HasPrefix(path, e.pattern) {
			return e.h, e.pattern
		}
	}
	return nil, ""
}

В процессе согласования:

1. Сначала сделайте это в таблице маршрутизацииточныйматч, матчmuxEntryназад после

2. Если он не запрашивается в таблице маршрутизации, он сопоставляется в упорядоченном массиве es, начиная сstrings.HasPrefix()Видно, что это по сути нечеткое совпадение, только при совпадении соответствующего префикса совпадение считается успешным.

3. Если соответствующий префикс не может быть запрошен, считается, что сопоставление не удалось, и возвращается нулевой обработчик.

Описание сводных правил сопоставления, состоящее из одного предложения, выглядит следующим образом:Longer patterns take precedence over shorter ones, образец длинной строки имеет более высокий приоритет, чем образец короткой строки, и длинная строка сопоставляется предпочтительно

mux.shouldRedirectRLocked()

mux.shouldRedirectRLocked()Функция метода относительно проста, чтобы определить, нужно ли перенаправлять маршрут, подобный «/дереву/» (в ServeMux «/дерево» будет автоматически перенаправлено на «/дерево/», если в маршруте уже нет «/» дерево таблицы маршрутизации", этот процесс находится вmux.Handler()вызыватьmux.redirectToPathSlash()Заканчивать)

1. Определите, есть ли в таблице маршрутизации комбинация host+path или path, если да, то перенаправление не требуется

2. Если путь представляет собой пустую строку, перенаправление не требуется.

3. Если в текущей таблице маршрутизации есть path+"/", то его необходимо перенаправить (например, когда при регистрации в таблице прописано "/tree/", маршрут для "/tree" перенаправляется на "/ дерево/", )

func (mux *ServeMux) shouldRedirectRLocked(host, path string) bool {
	p := []string{path, host + path}

	for _, c := range p {
		if _, exist := mux.m[c]; exist {
			return false
		}
	}

	n := len(path)
	if n == 0 {
		return false
	}
	for _, c := range p {
		if _, exist := mux.m[c+"/"]; exist {
			return path[n-1] != '/'
		}
	}

	return false
}

mux.redirectToPathSlash()

mux.redirectToPathSlash()Функция определяет, нужно ли ей добавлять «/» к своему пути, и как только она определяет, что необходимо добавить «/», она возвращает новый URL-адрес и перенаправленный обработчик:

func (mux *ServeMux) redirectToPathSlash(host, path string, u *url.URL) (*url.URL, bool) {
	mux.mu.RLock()
	shouldRedirect := mux.shouldRedirectRLocked(host, path)
	mux.mu.RUnlock()
	if !shouldRedirect {
		return u, false
	}
	path = path + "/"
	u = &url.URL{Path: path, RawQuery: u.RawQuery}
	return u, true
}

Решив, что он должен быть перенаправлен, верните путь с / в конце

mux.Handler(), если Http.Method имеет значение CONNECT, он вернет RedirectHandler (также тип Handler) и напишет StatusMovedPermanently (см. определение в status.go), вызовитеRedirectHandler.ServeHTTP()для обработки HTTP-запросов

	if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
			return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
		}

		return mux.handler(r.Host, r.URL.Path)

mux.handler()

mux.handler()функцияmux.Handler()реализация

// handler is the main implementation of Handler.
// The path is known to be in canonical form, except for CONNECT methods.
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
	mux.mu.RLock()
	defer mux.mu.RUnlock()

	// Host-specific pattern takes precedence over generic ones
	if mux.hosts {
		h, pattern = mux.match(host + path)
	}
	if h == nil {
		h, pattern = mux.match(path)
	}
	if h == nil {
		h, pattern = NotFoundHandler(), ""
	}
	return
}

Если соответствующий обработчик не найден после двух совпадений, вернуть NotFoundHandler()

Суммировать

На самом деле Go поддерживает внешне реализованные маршрутизаторы.Второй параметр ListenAndServe используется для настройки внешних маршрутизаторов.Это интерфейс Handler, то есть пока внешний маршрутизатор реализует интерфейс Handler, мы можем реализовать самообслуживание HTTP в нашем собственном маршрутизаторе. ServeHTTP, определение функции маршрутизации

HandleFunc()

HandleFunc()это функция

Определение HandleFunc

Для заданной строки шаблона HandleFunc будетфункция обработчикаЗарегистрируйтесь на соответствующий маршрут. Другими словами, когда запрашиваются разные пути URL, задается другая логика обработки, и HandleFunc может реализовать эту взаимосвязь между логикой обработки и привязкой URL.

Определение функции:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}

Первый параметр — это строка, второй параметр — это обработчик, а HandleFunc обрабатывает соответствующий запрос пути URL-адреса.

HandleFunc()который по существу вызывает мультиплексор по умолчаниюmux.HandleFunc()

пример:

package main

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

func main() {
	h1 := func(w http.ResponseWriter, _ *http.Request) {
		io.WriteString(w, "Hello from a HandleFunc #1!\n")
	}
	h2 := func(w http.ResponseWriter, _ *http.Request) {
		io.WriteString(w, "Hello from a HandleFunc #2!\n")
	}

	http.HandleFunc("/", h1)
	http.HandleFunc("/endpoint", h2)

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

Преимущества HandleFunc

Существование функции HandleFunc позволяет нам напрямую преобразоватьfunc(ResponseWriter, *Request)Функция типа используется как обработчик, и больше нет необходимости реализовывать интерфейс Handler и настраивать тип, реализующий функцию ServeHTTP.HandleFunc можеточень простоЗарегистрированный путь для URL.

Handle()

Handle()это функция

Определение дескриптора()

Подобно HandleFunc, он, по сути, вызывает мультиплексор по умолчанию.mux.Handle()метод

Определение функции:

// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

Первый параметр — это строка для сопоставления, а второй параметр — обработчик типа Handler В отличие от обработчика в приведенном выше примере, вам нужно реализовать новый класс и реализовать методы в классе.ServeHTTP

пример:

package main

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

type countHandler struct {
	mu sync.Mutex // guards n
	n  int
}

func (h *countHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h.mu.Lock()
	defer h.mu.Unlock()
	h.n++
	fmt.Fprintf(w, "count is %d\n", h.n)
}

func main() {
	http.Handle("/count", new(countHandler))
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Handler

Обработчик — это интерфейс

Определение обработчика

в пакетеОфициальная документация сайтаТаким образом, «Handler отвечает на HTTP-запросы» Handler определяется таким образом, что на китайском языке понимается как «обработчик».

Handler — это интерфейс, определенный в родном пакете net/http:

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

для любого реализованногоServeHTTPВсе методы принадлежат Handler.

Свойства обработчика

1. Обработчик можно использовать только для чтения тела запроса, а не для изменения запроса.

2. Сначала прочитайте тело, а затем напишите соотв.

3. На самом деле в реальном процессе разработкиМы не используем Handler очень часто, потому что net/http предоставляет нам более удобныйHandleFunc()функция, и эта функция позволяет нам напрямую использовать функцию в качестве обработчика, здесьобработчик - это тип функции, а не этот интерфейс Handle, эта реализация болееServeHTTP()более удобный.

Обработчик используется для обработки запросов и предоставления ответов,Более строго, он используется для чтения тела запроса, записи поля ответа (заголовка ответов), соответствующего запросу, в ResponseWriter, а затем возврата.

HandlerFunc

http также предоставляет класс HandlerFunc иHandleFunc()Функции разделены одним словом

HandlerFunc по сути является функцией адаптера., этот класс внутренне реализуетServeHTTP()функция, поэтому этот класс по существу является типом Handler

Определение HandlerFunc()

HandlerFunc(f) это обработчик, который вызывает функцию f

Прототип функции:

type HandlerFunc func(ResponseWriter, *Request)

исходный код

// 调用默认ServerMux的HandleFunc方法
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {                                          
        DefaultServeMux.HandleFunc(pattern, handler)
}
// 把方法handler转换成HandlerFunc类型,即实现了Handler接口;再执行Handle方法
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
        mux.Handle(pattern, HandlerFunc(handler))
}

// 路由器注册一个handler给指定的parttern
func (mux *ServeMux) Handle(pattern string, handler Handler) {
        ....
}

воплощать в жизньHandleFunc()По сути, это регистрация обработчика запроса определенного правила.

Разница между обработчиком и функцией HandlerFunc

Обработчик — это интерфейс

HandlerFunc — тип Handler, функция-адаптер.

Разница между Handle()/HandleFunc()

HandleFunc()а такжеHandle()Все вызывают соответствующий метод DefaultServeMux, а DefaultServeMux является экземпляром ServeMux, а ServeMux реализует интерфейс Handler. Фактически,HandleFunc()В итоге все равно будет вызван метод Handle, о котором также упоминалось ранее:

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    ...
    mux.Handle(pattern, HandlerFunc(handler))
}

процесс регистрации маршрута

Процесс регистрации маршрута состоит из двух методов в структуре мультиплексора.mux.Handle()а такжеmux.HandleFunc()осуществленный

mux.Handle()

У меня есть общее представление о теле функции. Процесс регистрации маршрутизации в основном состоит изServeMuxвнутри конструкцииHandle()Реализован метод, если маршрут уже существует в таблице маршрутизации, это вызовет панику

ServeMuxвнутри конструкцииHandle()Метод примерно делает следующее:

1. Проверьте легитимность маршрута.Если он неправильный, будет выдано сообщение об ошибке.Сообщение об ошибке множественных регистраций для /xxx относится к проблеме, вызванной повторной регистрацией маршрута.

2. Если в мультиплексоре нет таблицы маршрутизации, создайте таблицу маршрутизации

3. Если маршрут разрешен, создайте запись маршрута.muxEntryдобавить в таблицу маршрутизации

4. Если последний символ маршрута имеет "/", то по пользовательской функции сортировкиappendSorted()Правила нахождения индекса, вставки и сортировки (хотя я скептически отношусь к тому, насколько эффективно работает эта функция) возвращают отсортированный массив.

5. Если первая буква маршрута не "/", он содержит имя хоста

mux.HandleFunc()

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if handler == nil {
		panic("http: nil handler")
	}
	mux.Handle(pattern, HandlerFunc(handler))
}

Helpful behavior

В предыдущих версиях server.go функция регистрацииmux.HandleСуществуют некоторые дополнительные варианты поведения. Когда вы устанавливаете путь маршрутизации на /tree/, полезное поведение будет неявно и постоянно регистрировать /tree в реестре для вас. Конечно, вы также можете явно указать маршрут для переопределения. доступ, обработчик /tree автоматически перенаправит запрос на /tree/:

 状态代码: 301 / Moved Permanently

В server.go сейчас,ServeMuxВ структуре поддерживается массив типа es, то есть последняя буква записи от длинной к короткой является строкой маршрутизации '/'

В использованииmux.match()При сопоставлении пути маршрутизации (подробности см. в разделе «Процесс поиска маршрута») сначала просмотрите таблицу маршрутизации. Если маршрут не существует в таблице маршрутизации, просмотрите массив es и сопоставьте его максимальную длину:

// Find a handler on a handler map given a path string.
// Most-specific (longest) pattern wins.
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
	// Check for exact match first.
	v, ok := mux.m[path]
	if ok {
		return v.h, v.pattern
	}

	// Check for longest valid match.  mux.es contains all patterns
	// that end in / sorted from longest to shortest.
	for _, e := range mux.es {
		if strings.HasPrefix(path, e.pattern) {
			return e.h, e.pattern
		}
	}
	return nil, ""
}

процесс запроса на обслуживание

Ссылаясь на документацию на официальном сайте, мы можем использовать нативную библиотеку golang net/http для реализации простого примера веб-маршрутизации:

// serve.go
package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", HelloWorld)
    http.ListenAndServe(":8080", nil)
}

func HelloWorld(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello World")
}

На самом деле в функции main всего два метода,HandleFunc()а такжеListenAndServe(), поток выполнения всего ответа на запрос выглядит следующим образом:

  1. Процесс маршрутизации регистрации: первый звонокHandleFunc(): Мультиплексор по умолчанию называетсяDefaultServeMuxизHandleFunc(), называетсяDefaultServeMuxизHandle, По умолчаниюmap[string]muxEntryДобавьте соответствующий обработчик и правила маршрутизации в .
  2. Создайте экземпляр сервера и позвонитеserver.ListenAndServe(),передачаnet.Listen("tcp", addr)порт прослушивания. Запустите цикл for и примите запрос в теле цикла
  3. Создайте экземпляр Conn для каждого запроса и запустите горутину для обслуживания запроса. go c.serve()
  4. Прочитайте содержимое каждого запроса W, ERR: = C.ReadRequest ()
  5. Enter Over Handler.serveHTTP, если у вас есть свой MUX, используйте свой собственный MUX. Определите, пуст ли обработчик. Если обработчик не установлен (обработчик Nil в этом примере), обработчик установлен на dealkeServemux
  6. Вызовите ServeHttp обработчика и введите DefaultServeMux.ServeHttp.
  7. Выберите соответствующий обработчик в соответствии с запросом и введите ServeHTTP этого обработчика.

Для каждого HTTP-запроса сервер запускает сопрограмму для обслуживания, что выходит за рамки этой статьи. Для примера с официального сайта:

http.ListenAndServe(":8080", nil)

Если входящий обработчик равен нулю, используется мультиплексор по умолчанию.DefaultServeMux, при вызове HandleFunc обработчик регистрируется с мультиплексором по умолчанию

// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
	if r.RequestURI == "*" {
		if r.ProtoAtLeast(1, 1) {
			w.Header().Set("Connection", "close")
		}
		w.WriteHeader(StatusBadRequest)
		return
	}
	h, _ := mux.Handler(r)
	h.ServeHTTP(w, r)
}

Учитывая, что объем этой статьи слишком мал, у нас есть возможность проанализировать на server.goserver.Serve()функция

процесс поиска маршрута

Как обеспечить, чтобы при доступе к ''/path/subpath' сначала сопоставлялся '/path/subpath', а не '/path/'', из-за правил поиска в процессе поиска маршрута (также упомянутых ранее):

mux.ServerHTTP() -> mux.Handler() -> mux.handler() -> mux.match()

Получите обработчик, обрабатывающий запрос, а затем вызовитеh.ServeHTTP(w, r), чтобы выполнить соответствующий метод обработчика:

type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}

f(w,r)Он реализует выполнение обработчика

дизайн-мышление

mu блокировка чтения-записи: параллельная обработка запроса, где используется параллелизм?

нет времени писать

Может ли hasPrefix() выполнять точное соответствие?

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

Почему вам нужно поддерживать отдельный массив с суффиксом /?

1. То же, что и выше, для нечеткого сопоставления

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

Суммировать

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

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

перспективы на будущее

один

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

Для существующих фреймворков веб-маршрутизации некоторые люди сравнивали плюсы и минусы этих фреймворков. Вы можете просмотреть их заранее, прочитав эту статью:

Супер полное сравнение производительности инфраструктуры маршрутизации Go Http

два

Я давно хотел систематически объяснить интеллектуальный поисковый робот и его структуру, потому что связь между поисковым роботом и http-библиотекой очень велика. В основном я пользовался более-менее фреймворками с большим количеством звездочек, а также читал некоторые исходники.Конечно, из-за сложности самого фреймворка рвать исходники вручную будет сложнее и дольше. Я также кратко представил один за другим распространенные фреймворки краулеров, такие как colly, и в будущем обновлю больше интерпретаций исходного кода фреймворков краулеров. Техническая начинка самого краулера не высока, но чтобы понять чем и чем хорош этот фреймворк, техническая начинка намного выше, ведь надо сравнивать с другими краулерными фреймворками, чтобы знать преимущества и недостатки каждого.

Но, ограниченные уровнем каждого разработчика, эти фреймворки имеют более или менее проблемы, они не такие «универсальные», как рекламируется на домашней странице github, поэтому в этом относительно низкоуровневом месте осваивайте и используйте. Разница огромна, и большинство люди будут только использовать его, не освоив его.

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

Я настаиваю на том, что: Фреймворк краулера — это окончательный компромисс.

три

Некоторые принципы дизайна Go очень интересны, и их нельзя объяснить в нескольких десятках тысяч слов.

Я не хочу называть себя евангелистом Go или участвовать в языковом джихаде, Gopher должен быть веселым сусликом:

Gopher is moe, but confusing

Ссылка на ссылку

Перейти в Интернет: обработчик

Package http

Разбор ServeMux в golang

Разбор http.ServeMux

Резюме реализации веб-маршрутизации Golang

Чтение заметок о правилах сопоставления маршрутов по умолчанию для пакетов HTTP в GOLANG

Связь между дескриптором, handlefunc и обработчиком, handlerfunc

Как Golang ServeMux реализует мультиплексирование

Анализ сопоставления маршрутов Golang [1]