структура запроса
HTTP взаимодействует в режиме запроса и ответа. Go запрос мы видели давно, вторым параметром функции-обработчика является http.Requests. Его структура:
type Request struct {
Method string
URL *url.URL
Proto string // "HTTP/1.0"
ProtoMajor int // 1
ProtoMinor int // 0
Header Header
Body io.ReadCloser
ContentLength int64
TransferEncoding []string
Close bool
Host string
Form url.Values
PostForm url.Values
MultipartForm *multipart.Form
....
ctx context.Context
}
Как видно из структуры запроса, в него включена основная информация http-запроса. Для запроса в основном сосредоточьтесь на структуре URL-адреса запроса, методе, заголовке и теле.
URL
Формат URL-запроса HTTP:scheme://[userinfo@]host/path[?query][#fragment]
, Go предоставляет структуру URL для сопоставления URL-адресов HTTP-запросов.
type URL struct {
Scheme string
Opaque string
User *Userinfo
Host string
Path string
RawQuery string
Fragment string
}
Формат URL-адреса относительно ясен, лучше использовать термин URI, унифицированное расположение ресурса. Самое главное в URL-адресе — это строка запроса query. Обычно как параметр запроса на получение. запрос какой-то пользы&
разделенный символамиkey1=value1&key2=value2
Пара ключ-значение, поскольку кодировка URL-адреса является кодом ASSIC, запрос должен быть кодирован-урленкодом. go может читать запрос через request.URI.RawQuery
func indexHandler(w http.ResponseWriter, r *http.Request) {
info := fmt.Sprintln("URL", r.URL, "HOST", r.Host, "Method", r.Method, "RequestURL", r.RequestURI, "RawQuery", r.URL.RawQuery)
fmt.Fprintln(w, info)
}
☁ ~ curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'name=vanyar&age=27' "http://127.0.0.1:8000?lang=zh&version=1.1.0"
URL /?lang=zh&version=1.1.0 HOST 127.0.0.1:8000 Method POST RequestURL /?lang=zh&version=1.1.0 RawQuery lang=zh&version=1.1.0
header
Заголовок также является важной частью HTTP. В структуре Запроса есть структура Заголовка, а Заголовок по сути является картой (map[string][]string
). Сопоставьте ключ-значение заголовка http-протокола с графиком:
Host: example.com
accept-encoding: gzip, deflate
Accept-Language: en-us
fOO: Bar
foo: two
Header = map[string][]string{
"Accept-Encoding": {"gzip, deflate"},
"Accept-Language": {"en-us"},
"Foo": {"Bar", "two"},
}
Поля в шапке содержат множество настроек связи, и много раз нужно указать запросContent-Type
.
func indexHandler(w http.ResponseWriter, r *http.Request) {
info := fmt.Sprintln(r.Header.Get("Content-Type"))
fmt.Fprintln(w, info)
}
☁ ~ curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'name=vanyar&age=27' "http://127.0.0.1:8000?lang=zh&version=1.1.0"
application/x-www-form-urlencoded
Golng предоставляет множество функций печати, которые в основном делятся на три категории. который
Println
иPrintf
.
Println
Та же разница в том, что печатается дополнительная новая строка. Что касаетсяPrintf
Он предназначен для печати строки формата, и все три метода возвращают количество напечатанных байтов.Sprint
,Sprinln
иSprintf
Напечатанная строка возвращается, а не выводится в стандартный поток.Fprint
,Fprintf
иFprinln
распечатать вывод вio.Writer
В интерфейсе, в http, этоhttp.ReponseWriter
В этом объекте возвращает количество напечатанных байтов.
Body
Передача данных в http в основном передается через тело. Go инкапсулирует тело как тело запроса, которое является интерфейсом ReadCloser. Интерфейсный метод Reader также является интерфейсом, который имеетRead(p []byte) (n int, err error)
метод, поэтому тело может получить запрошенные данные, прочитав массив байтов.
func indexHandler(w http.ResponseWriter, r *http.Request) {
info := fmt.Sprintln(r.Header.Get("Content-Type"))
len := r.ContentLength
body := make([]byte, len)
r.Body.Read(body)
fmt.Fprintln(w, info, string(body))
}
☁ ~ curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'name=vanyar&age=27' "http://127.0.0.1:8000?lang=zh&version=1.1.0"
application/x-www-form-urlencoded
name=vanyar&age=27
Видно, что когда тип содержимого запроса — application/x-www-form-urlencoded, тело также имеет тот же формат, что и запрос, а пара ключ-значение — это ключ-значение. Метод запроса, замененный на json, выглядит следующим образом:
☁ ~ curl -X POST -H "Content-Type: application/json" -d '{name: "vanyar", age: 27}' "http://127.0.0.1:8000?lang=zh&version=1.1.0"
application/json
{name: "vanyar", age: 27}
Для загрузки изображений используется формат multipart/form-data.Тело запроса выглядит следующим образом:
☁ ~ curl -X POST -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" -F "name=vanyar" -F "age=27" "http://127.0.0.1:8000?lang=zh&version=1.1.0"
multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW; boundary=------------------------d07972c7800e4c23
--------------------------d07972c7800e4c23
Content-Disposition: form-data; name="name"
vanyar
--------------------------d07972c7800e4c23
Content-Disposition: form-data; name="age"
27
--------------------------d07972c7800e4c23--
форма
Проанализируйте тело, чтобы прочитать данные, запрошенные клиентом. И эти данные относительно примитивны, будь то пара ключ-значение или данные формы. Читать и анализировать напрямую довольно проблематично. Эти данные тела обычно также предоставляются формой. Итак, go предоставляет методы для обработки этих данных формы.
Form
go предоставляет метод ParseForm для анализа данных, предоставляемых формой, то есть данных, тип содержимого которых равен x-www-form-urlencode.
func indexHandler(w http.ResponseWriter, r *http.Request) {
contentType := fmt.Sprintln(r.Header.Get("Content-Type"))
r.ParseForm()
fromData := fmt.Sprintf("%#v", r.Form)
fmt.Fprintf(w, contentType, fromData)
}
☁ ~ curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'name=vanyar&age=27' "http://127.0.0.1:8000?lang=zh&version=1.1.0"
application/x-www-form-urlencoded
%!(EXTRA string=url.Values{"name":[]string{"vanyar"}, "age":[]string{"27"}, "lang":[]string{"zh"}, "version":[]string{"1.1.0"}})%
Структуры и методы, используемые для чтения данных, примерно следующие:
fmt.Println(r.Form["lang"])
fmt.Println(r.PostForm["lang"])
fmt.Println(r.FormValue("lang"))
fmt.Println(r.PostFormValue("lang"))
вr.Form
иr.PostForm
должен быть вызванParseForm
После этого будут данные, иначе это будет пустой массив.
иr.FormValue
иr.PostFormValue("lang")
нет нуждыParseForm
вызов для чтения данных.
Кроме того, r.Form и r.PostForm являются структурами массивов.Для параметров с одинаковыми именами, которые существуют и в теле, и в URL, r.Form будет иметь два значения, а именно ["en", "zh"] иPOST
Префиксные массивы и методы могут только читать данные тела.
☁ ~ curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'name=vanyar&age=27&lang=en' "http://127.0.0.1:8000?lang=zh&version=1.1.0"
application/x-www-form-urlencoded
%!(EXTRA string=url.Values{"version":[]string{"1.1.0"}, "name":[]string{"vanyar"}, "age":[]string{"27"}, "lang":[]string{"en", "zh"}})%
На этом этапе вы можете видеть, что параметр lang предоставляется не только запросом URL-адреса, но также предоставляется телом сообщения.По умолчанию данные тела получают приоритет от go, а данные оба доступны и не будут перезаписаны.
Если вы не хотите читать параметры URL-адреса, вызовитеPostForm
илиPostFormValue
Просто прочитайте значение поля.
r.PostForm["lang"][0]
r.PostFormValue["lang"]
Для данных в формате данных формы метод ParseForm будет анализировать только параметры в URL-адресе, но не будет анализировать параметры в теле.
☁ ~ curl -X POST -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" -F "name=vanyar" -F "age=27" -F "lang=en" "http://127.0.0.1:8000?lang=zh&version=1.1.0"
multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW; boundary=------------------------5f87d5bfa764488d
%!(EXTRA string=url.Values{"lang":[]string{"zh"}, "version":[]string{"1.1.0"}}
)%
Поэтому, когда запрошенный тип содержимого — данные формы, ParseFrom необходимо изменить на MutilpartFrom, иначе r.From не сможет прочитать содержимое тела, а сможет прочитать только содержимое строки запроса.
MutilpartFrom
Метод ParseMutilpartFrom должен предоставить параметр для чтения длины данных, а затем использовать тот же метод для чтения данных формы. MutilpartFrom будет читать только данные тела и не будет читать данные запроса URL-адреса.
func indexHandler(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(1024)
fmt.Println(r.Form["lang"])
fmt.Println(r.PostForm["lang"])
fmt.Println(r.FormValue("lang"))
fmt.Println(r.PostFormValue("lang"))
fmt.Println(r.MultipartForm.Value["lang"])
fmt.Fprintln(w, r.MultipartForm.Value)
}
Вы можете вернуться после просмотра запросаmap[name:[vanyar] age:[27] lang:[en]]
. которыйr.MultipartForm.Value
В URL нет параметров.
Подводя итог, можно сказать, что для чтения метода кодирования urlencode требуется только ParseForm, а для чтения кодировки данных формы требуется метод ParseMultipartForm. Если в параметре есть и URL, и тело, его могут прочитать оба метода From и FromValue. с участиемPost
Метод префикса может только читать содержимое данных тела. Данные MultipartForm передаются черезr.MultipartForm.Value
получить доступ.
Файл загружен
Формат данных формы чаще всего используется при загрузке изображений.r.MultipartForm.Value
данные поля тела поста,r.MultipartForm.File
содержит данные изображения:
func indexHandler(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(1024)
fileHeader := r.MultipartForm.File["file"][0]
fmt.Println(fileHeader)
file, err := fileHeader.Open()
if err == nil{
data, err := ioutil.ReadAll(file)
if err == nil{
fmt.Println(len(data))
fmt.Fprintln(w, string(data))
}
}
fmt.Println(err)
}
Сделав запрос, вы увидите, что изображение возвращено. Конечно, go предоставляет лучшие служебные функции.r.FormFile
, непосредственно читать данные загруженного файла. Вместо использования метода ParseMultipartForm.
file, _, err := r.FormFile("file")
if err == nil{
data, err := ioutil.ReadAll(file)
if err == nil{
fmt.Println(len(data))
fmt.Fprintln(w, string(data))
}
}
fmt.Println(err)
Эта ситуация применима только тогда, когда в поле файла нет других полей.Если вам все еще нужно прочитать параметр lang, вам все равно нужно добавить вызов ParseMultipartForm. После чтения загруженного файла следующим шагом является очень распространенная операция ввода-вывода по записи файлов.
JSON
Сейчас популярно разделение фронтенда и бекенда, а на стороне клиента появились некоторые фреймворки.Данные, отправляемые angular, vue, react и т. д. обычно в формате json. Для формата json body — это нативная строка json. То есть go расшифровывает json в структуру данных go.
type Person struct {
Name string
Age int
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
decode := json.NewDecoder(r.Body)
var p Person
err := decode.Decode(&p)
if err != nil{
log.Fatalln(err)
}
info := fmt.Sprintf("%T\n%#v\n", p, p)
fmt.Fprintln(w, info)
}
☁ ~ curl -X POST -H "Content-Type: application/json" -d '{"name": "vanyar", "age": 27 }' "http://127.0.0.1:8000?lang=zh&version=1.1.0"
main.Person
main.Person{Name:"vanyar", Age:27}
Подробнее о json будет рассказано позже. Посетите официальный сайтДокументацияПолучите больше информации.
Response
Запрос и ответ являются братьями-близнецами http, не только их форматы сообщений схожи, но также и связанная обработка и построение. Структура ответа конструкции go следующая:ResponseWriter
интерфейс.
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(int)
}
Метод внутри также очень прост.Header
Метод возвращает карту заголовков.WriteHeader
Будет возвращен код состояния ответа.Write
Данные возвращаются клиенту.
Мы использовали метод fmt.Fprintln для записи данных ответа непосредственно в w. Вы также можете вызвать символ, возвращаемый методом Write.
func indexHandler(w http.ResponseWriter, r *http.Request) {
str := `<html>
<head><title>Go Web Programming</title></head>
<body><h1>Hello World</h1></body>
</html>`
w.Write([]byte(str))
}
☁ ~ curl -i http://127.0.0.1:8000/
HTTP/1.1 200 OK
Date: Wed, 07 Dec 2016 09:13:04 GMT
Content-Length: 95
Content-Type: text/html; charset=utf-8
<html>
<head><title>Go Web Programming</title></head>
<body><h1>Hello World</h1></body>
</html>% ☁ ~
В соответствии с возвращенным символом go автоматически изменяется наtext/html
изContent-Type
Формат. Настройка возвращаемых данных обычно требует изменения информации, связанной с заголовком.
func indexHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(501)
fmt.Fprintln(w, "No such service, try next door")
}
☁ ~ curl -i http://127.0.0.1:8000/
HTTP/1.1 501 Not Implemented
Date: Wed, 07 Dec 2016 09:14:58 GMT
Content-Length: 31
Content-Type: text/plain; charset=utf-8
No such service, try next door
перенаправить
Функцию перенаправления можно реализовать, установив местоположение заголовка и код состояния http.
func indexHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", "https://google.com")
w.WriteHeader(302)
}
☁ ~ curl -i http://127.0.0.1:8000/
HTTP/1.1 302 Found
Location: https://google.com
Date: Wed, 07 Dec 2016 09:20:19 GMT
Content-Length: 31
Content-Type: text/plain; charset=utf-8
Перенаправление является часто используемой функцией, поэтому go также предоставляет инструментальные методы,http.Redirect(w, r, "https://google.com", http.StatusFound)
.
Как и структура заголовка запроса, w.Header также имеет несколько методов для установки заголовков.
func (h Header) Add(key, value string) {
textproto.MIMEHeader(h).Add(key, value)
}
func (h Header) Set(key, value string) {
textproto.MIMEHeader(h).Set(key, value)
}
func (h MIMEHeader) Add(key, value string) {
key = CanonicalMIMEHeaderKey(key)
h[key] = append(h[key], value)
}
func (h MIMEHeader) Set(key, value string) {
h[CanonicalMIMEHeaderKey(key)] = []string{value}
}
Оба метода Set и Add могут устанавливать заголовки.Для существующего ключа Add добавит массив значений value, а set заменит непосредственно значение value. То есть разница между добавлением и присваиванием.
Json
Данные, отправляемые запросом, могут быть в формате JSON, а данные в ответе также могут быть в формате JSON. API в стиле Restful также возвращает данные в формате json. Поскольку запрос должен декодировать строку json, ответ должен кодировать строку json, go предоставляет стандартную библиотекуencoding/json
type Post struct {
User string
Threads []string
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
post := &Post{
User: "vanyar",
Threads: []string{"first", "second", "third"},
}
json, _ := json.Marshal(post)
w.Write(json)
}
☁ ~ curl -i http://127.0.0.1:8000/
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 08 Dec 2016 06:45:17 GMT
Content-Length: 54
{"User":"vanyar","Threads":["first","second","third"]}%
Конечно, более подробная информация об обработке json будет рассмотрена позже.
Суммировать
Для веб-приложений обработка запроса и возврат ответа является основным содержимым. golang очень хорошо инкапсулирует Request и ResponseWriter для разработчиков. Будь то запрос или ответ, это обработка URL-адреса, заголовка и данных, связанных с телом. Это также основное содержание протокола http.
Помимо обработки данных тела, иногда требуется обработка и данных в заголовке, типичным примером является обработка файлов cookie. Об этом пойдет речь в теме cookies.