Обзор
Ранее мы узнали об обработчиках и функциях-обработчиках, как писать и регистрировать обработчики. В этой статье мы узнаем, как получить информацию из запроса.
структура запроса
Из предыдущего исследования мы знаем, что функция обработчика должна соответствовать следующей сигнатуре:
func (w http.ResponseWriter, r *http.Request)
в,http.Request
это тип запроса. Данные, переданные клиентом, могут быть получены через эту структуру. структураRequest
Определяется в пакете net/http:
// src/net/http/request.go
type Request struct {
Method string
URL *url.URL
Proto string
ProtoMajor int
ProtoMinor int
Header Header
Body io.ReadCloser
ContentLength int
// 省略一些字段...
}
Рассмотрим несколько важных полей.
Method
в запросеMethod
Поле указывает, какой метод сервера хочет вызвать клиент. существуетпервая статья, мы упомянули метод протокола HTTP. Его значениеGET/POST/PUT/DELETE
Ждать. Сервер будет обрабатывать по-разному в зависимости от метода запроса, напримерGET
Метод заключается в том, чтобы просто получить информацию (основную информацию о пользователе, информацию о товарах и т. д.),POST
Методы создания новых ресурсов (регистрация новых пользователей, перечисление новых продуктов и т. д.).
URL
Создавая Всемирную паутину, Тим Бернерс-Ли также представил концепцию использования строк для представления интернет-ресурсов. Он называет строку какЕдиный идентификатор ресурса(URI, универсальный идентификатор ресурса). URI состоит из двух частей. часть представляет имя ресурса, т.е.Единое имя ресурса(URN, универсальное имя ресурса). Другая часть представляет собой местоположение ресурса, т.е.Единый указатель ресурсов(URL, единое расположение ресурса).
В HTTP-запросе используйте URL-адрес для описания расположения ресурса, с которым нужно работать. Общий формат URL:
[scheme:][//[userinfo@]host][/]path[?query][#fragment]
-
scheme
: Имя протокола, общиеhttphttpsftp
; -
userInfo
: Если есть, это означает, что можно записать информацию о пользователе, такую как имя пользователя и пароль.dj:password
; -
host
: Указывает доменное имя или адрес хоста и дополнительную информацию о порте. Если порт не указан, по умолчанию используется 80. Напримерwww.example.com
,www.example.com:8080
,127.0.0.1:8080
; -
path
: путь ресурса на хосте, начиная с/
разделены, например/posts
; -
query
: необязательная строка запроса, параметры пары ключ-значение, передаваемые клиентом, ключ-значение используется напрямую=
, используется между несколькими парами ключ-значение&
Соединение, напримерpage=1&count=10
; -
fragment
: Фрагмент, также известный как якорь. Представляет информацию о местоположении на странице. Эта часть информации обычно отсутствует в URL-адресе запроса, инициированном браузером. но черезajax
Отправьте эти данные в виде кода;
Давайте посмотрим на полный URL:
http://dj:password@www.example.com/posts?page=1&count=10#fmt
Структура URL определена в Gonet/url
в сумке:
// net/url/url.go
type URL struct {
Scheme string
Opaque string
User *Userinfo
Host string
Path string
RawPath string
RawQuery string
Fragment string
}
через объект запросаURL
поле для получения этой информации. Далее давайте напишем программу, чтобы взглянуть на нее (используя базовую структуру веб-программы, описанную в предыдущей статье, нам нужно только добавить функции процессора и регистрацию):
func urlHandler(w http.ResponseWriter, r *http.Request) {
URL := r.URL
fmt.Fprintf(w, "Scheme: %s\n", URL.Scheme)
fmt.Fprintf(w, "Host: %s\n", URL.Host)
fmt.Fprintf(w, "Path: %s\n", URL.Path)
fmt.Fprintf(w, "RawPath: %s\n", URL.RawPath)
fmt.Fprintf(w, "RawQuery: %s\n", URL.RawQuery)
fmt.Fprintf(w, "Fragment: %s\n", URL.Fragment)
}
// 注册
mux.HandleFunc("/url", urlHandler)
Запустите сервер и получите к нему доступ через браузерlocalhost:8080/url/posts?page=1&count=10#main
:
Scheme:
Host:
Path: /url/posts
RawPath:
RawQuery: page=1&count=10
Fragment:
Почему пустые поля? Обратите внимание на исходный кодRequest
в структуреURL
На поле есть комментарий:
// URL specifies either the URI being requested (for server
// requests) or the URL to access (for client requests).
//
// For server requests, the URL is parsed from the URI
// supplied on the Request-Line as stored in RequestURI. For
// most requests, fields other than Path and RawQuery will be
// empty. (See RFC 7230, Section 5.3)
//
// For client requests, the URL's Host specifies the server to
// connect to, while the Request's Host field optionally
// specifies the Host header value to send in the HTTP
// request.
В том смысле, что когда запрос получен как сервер,URL
КромеPath
иRawQuery
, остальные поля в основном пусты. Для этой проблемы в репозитории Go GithubIssue 28940Были обсуждения.
Мы также можем пройтиURL
Структура получает строку URL:
URL := &net.URL {
Scheme: "http",
Host: "example.com",
Path: "/posts",
RawQuery: "page=1&count=10",
Fragment: "main",
}
fmt.Println(URL.String())
Приведенная выше программа запускает выходную строку:
http://example.com/posts?page=1&count=10#main
Proto/ProtoMajor/ProtoMinor
Proto
Указывает версию протокола HTTP, напримерHTTP/1.1
,ProtoMajor
указывает на большую версию,ProtoMinor
Обозначает минорную версию.
func protoFunc(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Proto: %s\n", r.Proto)
fmt.Fprintf(w, "ProtoMajor: %d\n", r.ProtoMajor)
fmt.Fprintf(w, "ProtoMinor: %d\n", r.ProtoMinor)
}
mux.HandleFunc("/proto", protoFunc)
Запустите сервер, запросы браузераlocalhost:8080
возвращение:
Proto: HTTP/1.1
ProtoMajor: 1
ProtoMinor: 1
В настоящее время HTTP/1.1 является основной версией.
Header
Header
Информация заголовка, отправленная клиентом, хранится в , в виде пар ключ-значение.Header
Базовый тип на самом делеmap[string][]string
:
// src/net/http/header.go
type Header map[string][]string
Ключ и значение каждого заголовка являются строками, и можно задать несколько одинаковых ключей. уведомлениеHeader
значение[]string
Тип, который содержит несколько значений для одного и того же ключа. Когда браузер инициирует HTTP-запрос, он автоматически добавляет некоторые заголовки. Напишем программу для просмотра:
func headerHandler(w http.ResponseWriter, r *http.Request) {
for key, value := range r.Header {
fmt.Fprintf(w, "%s: %v\n", key, value)
}
}
mux.HandleFunc("/header", headerHandler)
Запускаем сервер, браузер запрашиваетlocalhost:8080/header
возвращение:
Accept-Encoding: [gzip, deflate, br]
Sec-Fetch-Site: [none]
Sec-Fetch-Mode: [navigate]
Connection: [keep-alive]
Upgrade-Insecure-Requests: [1]
User-Agent: [Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36]
Sec-Fetch-User: [?1]
Accept: [text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3]
Accept-Language: [zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7]
Я использую браузер Chrome, и разные браузеры добавляют разные заголовки.
Общие заголовки:
-
Accept
: тип контента, который клиент хочет, чтобы сервер отправил; -
Accept-Charset
: указывает кодировку символов, которую может принять клиент; -
Content-Length
: тела запросабайтДлина, как правило, больше в запросах POST/PUT; -
Content-Type
: при включении тела запроса этот заголовок используется для записи типа содержимого тела. При отправке запроса POST или PUT тип содержимого по умолчанию равенx-www-form-urlecoded
. Но при загрузке файла вы должны установить типmultipart/form-data
. -
User-Agent
: Используется для описания информации о клиенте, который инициировал запрос, например, какой браузер.
Content-Length/Body
Content-Length
представляющий тело запросабайтдлина, содержимое тела запроса может быть изменено сBody
читать в поле. Осторожные друзья, возможно, нашлиBody
поле представляет собойio.ReadCloser
интерфейс.Закройте его после прочтения, иначе будут утечки ресурсов. можно использоватьdefer
Упрощение кодирования.
func bodyHandler(w http.ResponseWriter, r *http.Request) {
data := make([]byte, r.ContentLength)
r.Body.Read(data) // 忽略错误处理
defer r.Body.Close()
fmt.Fprintln(w, string(data))
}
mux.HandleFunc("/body", bodyHandler)
Приведенный выше код возвращает содержимое тела запроса от клиента к клиенту. также можно использоватьio/ioutil
Пакет упрощает операции чтения:
data, _ := ioutil.ReadAll(r.Body)
Ввод URL-адреса непосредственно в браузере инициируетGET
Запрос не может содержать тело запроса. Есть много способов инициировать запрос с телом запроса, два из которых описаны ниже:
использовать форму
Через форма HTML мы можем отправить запрос на почту на сервер и отправить содержимое формы в качестве тела запроса.
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, `
<html>
<head>
<title>Go Web 编程之 request</title>
</head>
<body>
<form method="post" action="/body">
<label for="username">用户名:</label>
<input type="text" id="username" name="username">
<label for="email">邮箱:</label>
<input type="text" id="email" name="email">
<button type="submit">提交</button>
</form>
</body>
</html>
`)
}
mux.HandleFunc("/", indexHandler)
использовать в HTMLform
для отображения формы. После нажатия кнопки отправки браузер отправит POST-запрос на путь/body
, используйте имя пользователя и адрес электронной почты в качестве тела запроса.
Запустите сервер и войдите на домашнюю страницуlocalhost:8080/
, чтобы отобразить форму. Заполните информацию и нажмите отправить:
Браузер отправляет запрос на почту на сервер с URL/body
,bodyHandler
После завершения обработки тело пакета отправляется обратно клиенту. Наконец клиент показывает:
Данные выше используютx-www-form-urlencoded
кодировка, которая является кодировкой по умолчанию для формы. Подробности позже.
Использование почтальона
PostmanЭто очень мощный инструмент для тестирования API.
- Поддерживаются все запросы методов протокола HTTP (
GET/POST/PUT/DELETE
). - Информация заголовка и содержимое тела запроса могут переноситься в запросе;
- служба поддержки
json/xml/http
контент в различных форматах; - Дружественный интерфейс.
Далее давайте посмотрим, как использовать PostMan для проверки нашегоbodyHandler
.
- Черный: метод выбора протокола HTTP, поэтому запрос Pick POST может содержать тело;
- Зеленая часть: запрошенный URL;
- Синяя часть: вы можете установить заголовок запроса и тело запроса;
- Светло-красная часть: тело запроса поддерживает несколько форматов, и здесь выбирается исходный формат;
- Серая часть: конкретное содержание тела запроса;
- Красная часть: информация об ответе, отображаемая после отправки, вы можете просмотреть заголовок ответа, файл cookie, тело ответа и т. д. Видно, что возвращается как есть.
Получить параметры запроса
Выше мы проанализировали общие поля HTTP-запросов в Go. В реальной разработке клиенту обычно необходимо передать некоторые параметры в запросе. Обычно существует два способа передачи параметров:
- Пары ключ-значение URL, также известные как строка запроса, строка запроса;
- форма.
Далее мы представим их по очереди.
Пара "ключ-значение" URL
Как упоминалось ранее в общем формате URL-адресов, за URL-адресом может следовать необязательная строка запроса для?
отделенный от пути, какkey1=value1&key2=value2
.
Структура URL имеетRawQuery
поле. Это поле является строкой запроса.
func queryHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, r.URL.RawQuery)
}
mux.HandleFunc("/query", queryHandler)
если мы начнем сlocalhost:8080/query?name=dj&age=20
запрос, строка запросаname=dj&age=20
будет передан обратно клиенту как есть. ноRawQuery
Это строковый тип, и его также можно разобрать с помощью строкового метода, но это слишком хлопотно! ! !
форма
В узком смысле форму состоит в том, чтобы отправить запрос через форму, и в широком смысле данные могут быть отправлены на сервер в тело запроса. Далее мы просто пишем HTML-страницу и отправить HTTP-запрос через форму страницы:
<html>
<head>
<title>Go Web 编程之 request</title>
</head>
<body>
<form action="/form?lang=cpp&name=dj" method="post" enctype="application/x-www-form-urlencoded">
<label>Form:</label>
<input type="text" name="lang" />
<input type="text" name="age" />
<button type="submit">提交</button>
</form>
</body>
</html>
-
action
представляет URL-адрес, запрошенный при отправке формы,method
Представляет метод запроса.При использованииGET
просьба, из-заGET
У метода нет тела запроса, параметры будут вставлены в конец URL; -
enctype
Указывает метод кодирования тела запроса, по умолчаниюapplication/x-www-form-urlencoded
. Если файл необходимо отправить, он должен быть указан какmultipart/form-data
;
Давайте представим, что такоеurlencoded
кодирование. Зарезервированные и незарезервированные слова в URL-адресах определены в RFC 3986, и все зарезервированные символы должны быть закодированы в URL-адресе. Кодирование URL-адреса преобразует символ в его значение байта в кодировке ASCII, затем представляет значение байта как двузначное шестнадцатеричное число и, наконец, добавляет к числу префикс знака процента (% ). Например, кодировка ASCII пробела — 32, а шестнадцатеричная — 20, поэтому кодировка URL%20
.
Form
поле
использоватьx-www-form-urlencoded
Закодированное тело запроса, которое вызывается первым при обработке запроса.ParseForm
разбор метода, затем изForm
Получить данные с поля:
func formHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
fmt.Fprintln(w, r.Form)
}
mux.HandleFunc("/form", formHandler)
Запустите программу и проверьте результат:
Form
тип поляurl.Values
Нижний слой на самом делеmap[string][]string
. перечислитьParseForm
метод, вы можете использоватьurl.Values
способ манипулирования данными.
использоватьParseForm
Он также анализирует строку запроса, изменяя приведенную выше форму на:
<html>
<head>
<title>Go Web 编程之 request</title>
</head>
<body>
<form action="/form?lang=cpp&name=dj" method="post" enctype="application/x-www-form-urlencoded">
<label>Form:</label>
<input type="text" name="lang" />
<input type="text" name="age" />
<button type="submit">提交</button>
</form>
</body>
</html>
Результат запроса:
Видно, что пары ключ-значение в строке запроса объединяются с обработкой синтаксического анализа в форме. Под одним и тем же ключом значение формы всегда идет первым, например[golang cpp]
.
PostForm
поле
Если запрос содержит как пары "ключ-значение" URL, так и данные формы, а пользователь хочет получить только данные формы, вы можете использоватьPostForm
поле.
использоватьPostForm
Будут возвращены только данные формы, за исключением ключей URL. Если поставить вышеуказанную программу,r.Form
изменить наr.PostForm
, то программа выдаст следующие результаты:
MultipartForm
поле
Если вы хотите обработать загруженные файлы, вы должны использоватьmultipart/form-data
кодирование. с предыдущимForm/PostForm
аналог, обработкаmultipart/form-data
При кодировании запроса его также необходимо разобрать перед использованием. Но метод используется другой, анализ используетParseMultipartForm
, то изMultipartForm
Значение поля.
<form action="/multipartform?lang=cpp&name=dj" method="post" enctype="multipart/form-data">
<label>MultipartForm:</label>
<input type="text" name="lang" />
<input type="text" name="age" />
<input type="file" name="uploaded" />
<button type="submit">提交</button>
</form>
func multipartFormHandler(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(1024)
fmt.Fprintln(w, r.MultipartForm)
fileHeader := r.MultipartForm.File["uploaded"][0]
file, err := fileHeader.Open()
if err != nil {
fmt.Println("Open failed: ", err)
return
}
data, err := ioutil.ReadAll(file)
if err == nil {
fmt.Fprintln(w, string(data))
}
}
mux.HandleFunc("/multipartform", multipartFormHandler)
Запустите программу:
MultipartForm
содержит дваmap
Поля типа: одно представляет пару ключ-значение формы, а другое — информацию о загруженном файле.
Используйте имя элемента управления файлом в форме, чтобы получитьMultipartForm.File
Получите файлы, загруженные через этот элемент управления, которых может быть несколько. получить этоmultipart.FileHeader
Тип, через который можно получить различные атрибуты файла.
Следует отметить, что этот метод используется для обработки файлов. для безопасности,ParseMultipartForm
Метод должен передавать параметр, указывающий максимальное использование памяти, чтобы загружаемый файл не занимал слишком много места.
FormValue/PostFormValue
Чтобы легко получить значение,net/http
пакет предоставляетFormValue/PostFormValue
метод. Они автоматически вызываются при необходимостиParseForm/ParseMultipartForm
метод.
FormValue
метод возвращает запрошенныйForm
Значение указанного ключа в поле.Если для одного и того же ключа есть несколько значений, вернуть первое. Если вам нужно получить все значения, используйте напрямуюForm
поле. Код ниже вернетhello
Соответствующее первое значение:
fmt.Fprintln(w, r.FormValue("hello"))
PostFormValue
метод возвращает запрошенныйPostForm
Значение указанного ключа в поле.Если для одного и того же ключа есть несколько значений, вернуть первое. Если вам нужно получить все значения, используйте напрямуюPostForm
поле
Уведомление:
Когда кодировка указана какmultipart/form-data
час,FormValue/PostFormValue
не вернет значения, они читаютForm/PostForm
поле, покаParseMultipartForm
записать данныеMultipartForm
поле.
Другие форматы
Другие форматы данных могут быть отправлены с помощью таких методов, как AJAX, таких какapplication/json
Ждать. В этой ситуации:
- сначала через шапку
Content-Type
узнать, какой это формат; - пройти через
r.Body
чтение потока байтов; - Использование декодирования.
Суммировать
В этой статье описываетсяnet/http
Аспекты запроса в пакете. отRequest
Структура того, как передавать параметры, и, наконец, введение в обработку различных закодированных запросов.