Несколько дней назад в "Zhihu Idea" я рассказывал о том, как имитировать обучение, и приводил пример имитации запросов Pyhton через клиент net/http. Но могут ли идеи без практики оставаться только идеями? Конечно нет, поэтому я решил приостановить заметки GO на неделю, чтобы попрактиковаться в своих идеях.
Некоторые новые знания, которые мы можем узнать путем подражания
В этой статье будут реализованы все примеры из краткого руководства по запросам через GO, а также систематически изучено использование http-клиента. Хотя название является быстрым началом, на самом деле контента много.
Быстрый опыт
Для начала сделаем GET запрос, код очень простой. следующее:
func get() {
r, err := http.Get("https://api.github.com/events")
if err != nil {
panic(err)
}
defer func() { _ = r.Body.Close() }()
body, _ := ioutil.ReadAll(r.Body)
fmt.Printf("%s", body)
}
Через метод http.Get получается Response и ошибка, а именно r и err. Через r мы можем получить информацию об ответе, а err может реализовать проверку ошибок.
r.Body нужно закрыть после прочтения, вы можете отложить это. Чтение содержимого может быть достигнуто с помощью ioutil.ReadAll.
метод запроса
В дополнение к GET, HTTP имеет ряд других методов, включая POST, PUT, DELETE, HEAD, OPTIONS. GET в быстром опыте реализован удобным способом, который скрывает множество деталей. Не используйте его здесь пока.
Давайте сначала представим общие методы, которые помогут нам реализовать все запросы методов HTTP. В основном это два важных типа: клиент и запрос.
Клиент — это клиент, который отправляет HTTP-запрос, и выполнение запроса инициируется Клиентом. Он предоставляет несколько удобных методов запроса, например, если мы хотим инициировать запрос Get, это можно реализовать через client.Get(url). Более общий способ — через client.Do(req) , где req имеет тип Request.
Запрос — это структура, используемая для описания информации о запросе, такой как метод запроса, адрес, заголовок и другая информация, которую мы все можем установить через нее. Создание запроса может быть достигнуто через http.NewRequest.
Затем перечислите коды реализации всех методов HTTP.
GET
r, err := http.DefaultClient.Do(
http.NewRequest(http.MethodGet, "https://api.github.com/events", nil))
POST
r, err := http.DefaultClient.Do(
http.NewRequest(http.MethodPost, "http://httpbin.org/post", nil))
PUT
r, err := http.DefaultClient.Do(
http.NewRequest(http.MethodPut, "http://httpbin.org/put", nil))
DELETE
r, err := http.DefaultClient.Do(
http.NewRequest(http.MethodDelete, "http://httpbin.org/delete", nil))
HEAD
r, err := http.DefaultClient.Do(
http.NewRequest(http.MethodHead, "http://httpbin.org/get", nil))
OPTIONS
r, err := http.DefaultClient.Do(
http.NewRequest(http.MethodOptions, "http://httpbin.org/get", nil))
Выше показана реализация всех методов HTTP. Здесь следует отметить еще несколько моментов.
DefaultClient, это клиент по умолчанию, предоставляемый пакетом net/http.Для общих запросов нам не нужно создавать новый клиент, просто используйте значение по умолчанию.
Для запросов GET, POST и HEAD в GO предусмотрена более удобная реализация, и Request не нужно создавать вручную.
Пример кода, каждый метод HTTP-запроса имеет две реализации.
GET
r, err := http.DefaultClient.Get("http://httpbin.org/get")
r, err := http.Get("http://httpbin.org/get")
POST
bodyJson, _ := json.Marshal(map[string]interface{}{
"key": "value",
})
r, err := http.DefaultClient.Post(
"http://httpbin.org/post",
"application/json",
strings.NewReader(string(bodyJson)),
)
r, err := http.Post(
"http://httpbin.org/post",
"application/json",
strings.NewReader(string(bodyJson)),
)
Вот, кстати, как подать JSON данные в POST интерфейс, основные настройки content-type, вообще то content-type интерфейса JSON это application/json.
HEAD
r, err := http.DefaultClient.Head("http://httpbin.org/get")
r, err := http.Head("http://httpbin.org/get")
Если вы посмотрите на исходный код, то обнаружите, что вызов в http.Get — это http.DefaultClient.Get, который имеет то же значение, но этот метод предоставлен для удобства. То же самое касается Head и Post.
URL-параметры
Размещая пары ключ/значение в URL-адресе, мы можем передавать данные по определенному адресу. За ключом/значением будет следовать вопросительный знак, например.http bin.org/individual?can=val…Было бы более проблематично создать URL-адрес вручную, мы можем сделать это с помощью методов, предоставляемых net/http.
Например, если вы хотите передать ключ1=значение1 и ключ2=значение2 вhttpbin.org/get. код показывает, как показано ниже:
req, err := http.NewRequest(http.MethodGet, "http://httpbin.org/get", nil)
if err != nil {
panic(err)
}
params := make(url.Values)
params.Add("key1", "value1")
params.Add("key2", "value2")
req.URL.RawQuery = params.Encode()
// URL 的具体情况 http://httpbin.org/get?key1=value1&key2=value2
// fmt.Println(req.URL.String())
r, err := http.DefaultClient.Do(req)
url.Values может помочь организовать QueryString, посмотреть исходный код и обнаружить, что url.Values на самом деле является map[string][]string. Вызовите метод Encode, передав упорядоченную строку RawQuery, запрашивающему req. Параметр массива также можно задать через url.Values, как показано ниже:
http bin.org/individual?can 1=va…
Как это сделать?
params := make(url.Values)
params.Add("key1", "value1")
params.Add("key2", "value2")
params.Add("key2", "value3")
Обратите внимание на последнюю строку кода. На самом деле просто добавьте еще одно значение к key2.
ответная информация
Запрос на выполнение выполнен успешно, как просмотреть информацию об ответе. Чтобы просмотреть информацию об ответе, вы можете получить общее представление о том, что обычно находится в ответе? Распространенными являются тело, статус, заголовок, кодировка и т. д.
Body
На самом деле, процесс чтения Тела был продемонстрирован в самом начале. Чтение содержимого ответа можно выполнить с помощью ioutil.
body, err := ioutil.ReadAll(r.Body)
Содержимое ответа разнообразно. Если это json, вы можете напрямую использовать json.Unmarshal для его декодирования.Знание JSON не вводится.
r.Body реализует интерфейс io.ReadeCloser, чтобы уменьшить трату ресурсов, он должен освобождаться вовремя, что можно реализовать через defer.
defer func() { _ = r.Body.Close() }()
StatusCode
В информации ответа, помимо содержимого тела, есть и другая информация, такая как код состояния и кодировка и т. д.
r.StatusCode
r.Status
r.StatusCode — это код возврата HTTP, а Status — описание статуса возврата.
Header
Информация заголовка ответа может быть получена через Response.Header.Следует отметить, что ключ заголовка ответа не чувствителен к регистру.
r.Header.Get("content-type")
r.Header.Get("Content-Type")
Вы обнаружите, что content-type и Content-Type получают точно такое же содержимое.
Encoding
Как определить кодировку содержимого ответа? Нам нужна помощьgolang.org/small/net/HTML/…реализация пакета. Сначала определите функцию, код выглядит следующим образом:
func determineEncoding(r *bufio.Reader) encoding.Encoding {
bytes, err := r.Peek(1024)
if err != nil {
fmt.Printf("err %v", err)
return unicode.UTF8
}
e, _, _ := charset.DetermineEncoding(bytes, "")
return e
}
Как это назвать?
bodyReader := bufio.NewReader(r.Body)
e := determineEncoding(bodyReader)
fmt.Printf("Encoding %v\n", e)
decodeReader := transform.NewReader(bodyReader, e.NewDecoder())
Используйте bufio для создания нового средства чтения, затем используйте DefinEncoding для определения кодировки содержимого и используйте преобразование для выполнения преобразования кодировки.
загрузка изображения
Если содержимое доступа представляет собой изображение, как его загрузить? Например, картина по следующему адресу.
2 партия. кунжут официально.com/V2-5 ой 8 не 41 руб…
На самом деле это очень просто, просто создайте новый файл и сохраните в нем содержимое ответа.
f, err := os.Create("as.jpg")
if err != nil {
panic(err)
}
defer func() { _ = f.Close() }()
_, err = io.Copy(f, r.Body)
if err != nil {
panic(err)
}
r — это ответ, используйте os для создания нового файла, а затем сохраните содержимое ответа в файл с помощью io.Copy.
настраиваемые заголовки запросов
Как настроить заголовки запросов для запросов? На самом деле Request уже предоставил соответствующий метод, что можно сделать через req.Header.Add.
Например, предположим, что мы собираемся посетитьhttpbin.org/get, но этот адрес дляпользовательский агент устанавливает стратегию сканирования. Нам нужно изменить пользовательский агент по умолчанию.
Образец кода:
req, err := http.NewRequest(http.MethodGet, "http://httpbin.org/get", nil)
if err != nil {
panic(err)
}
req.Header.Add("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0)")
Вышеупомянутое может завершить задачу.
сложный POST-запрос
Способ отправки данных JSON в интерфейс POST был показан ранее. Далее мы представим несколько других способов отправки данных в интерфейс POST, а именно отправку формы и отправку файла.
отправка формы
Отправка формы — очень распространенная функция, поэтому в net/http, в дополнение к стандартному использованию, она также предоставляет нам упрощенный метод.
Давайте сначала представим стандартный метод реализации.
Например, предположим, что вы хотитеhttpbin.org/postОтправьте форму с именем poloxue и паролем 123456.
payload := make(url.Values)
payload.Add("name", "poloxue")
payload.Add("password", "123456")
req, err := http.NewRequest(
http.MethodPost,
"http://httpbin.org/post",
strings.NewReader(payload.Encode()),
)
if err != nil {
panic(err)
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
r, err := http.DefaultClient.Do(req)
Полезная нагрузка POST представляет собой строку в виде name=poloxue&password=123456, поэтому мы можем упорядочить ее по url.Values.
Содержимое, отправляемое в NewRequest, должно быть типа, реализующего интерфейс Reader, поэтому его необходимо преобразовать с помощью strings.NewReader.
Если тип содержимого, отправленный формой формы, — application/x-www-form-urlencoded, его также следует установить.
Вводится сложный способ. Затем я представлю упрощенный метод.На самом деле, отправка формы должна быть вызвана только http.PostForm Это может быть завершено. Пример кода выглядит следующим образом:
payload := make(url.Values)
payload.Add("name", "poloxue")
payload.Add("password", "123456")
r, err := http.PostForm("http://httpbin.org/post", form)
Это так просто.
Подать документы
Отправка файлов должна представлять собой более сложное содержание HTTP-запросов. На самом деле нетрудно сказать, что, в отличие от других запросов, нам нужно потратить некоторые усилия, чтобы прочитать файл и упорядочить данные, отправленные для POST.
В качестве примера предположим, что теперь у меня есть файл изображения с именем as.jpg в каталоге /Users/polo. Теперь отправьте это изображение наhttpbin.org/post.
Сначала нам нужно организовать контент, отправленный POST, код выглядит следующим образом:
filename := "/Users/polo/as.jpg"
f, err := os.Open(filename)
if err != nil {
panic(err)
}
defer func() { _ = f.Close() }()
uploadBody := &bytes.Buffer{}
writer := multipart.NewWriter(uploadBody)
fWriter, err := writer.CreateFormFile("uploadFile", filename)
if err != nil {
fmt.Printf("copy file writer %v", err)
}
_, err = io.Copy(fWriter, f)
if err != nil {
panic(err)
}
fieldMap := map[string]string{
"filename": filename,
}
for k, v := range fieldMap {
_ = writer.WriteField(k, v)
}
err = writer.Close()
if err != nil {
panic(err)
}
На мой взгляд, организация данных осуществляется в несколько этапов, а именно:
Первый шаг — открыть загружаемый файл и использовать функцию defer f.Close() для подготовки к выпуску ресурсов; Второй шаг — создать bytes.Buffer, в котором хранится загруженный контент, а имя переменной — uploadBody; Третий шаг — создать средство записи с помощью multipart.NewWriter для записи содержимого, предоставленного файлом, в буфер; Четвертый шаг — создать загружаемый файл через Writer.CreateFormFile и записать в него содержимое через io.Copy; Наконец, добавьте другую дополнительную информацию через Writer.WriteField, обратите внимание на закрытие модуля записи в конце; На этом этапе данные, загруженные файлом, организованы. Затем просто вызовите метод http.Post, чтобы завершить загрузку файла.
r, err := http.Post("http://httpbin.org/post", writer.FormDataContentType(), uploadBody)
Следует отметить, что необходимо установить тип содержимого запроса, а тип загружаемого файла можно получить с помощью Writer.FormDataContentType() .
На данный момент подача файла также завершена, я не знаю, есть ли очень простое чувство.
Cookie
В основном это включает две части, а именно чтение файла cookie ответа и установку файла cookie запроса. Способ получить ответный файл cookie очень прост, просто вызовите r.Cookies напрямую.
Дело в том, как установить файл cookie запроса. Существует два способа установки файлов cookie: один устанавливается на клиенте, а другой — на запросе.
Установить куки на клиенте
Посмотрите непосредственно на пример кода:
cookies := make([]*http.Cookie, 0)
cookies = append(cookies, &http.Cookie{
Name: "name",
Value: "poloxue",
Domain: "httpbin.org",
Path: "/cookies",
})
cookies = append(cookies, &http.Cookie{
Name: "id",
Value: "10000",
Domain: "httpbin.org",
Path: "/elsewhere",
})
url, err := url.Parse("http://httpbin.org/cookies")
if err != nil {
panic(err)
}
jar, err := cookiejar.New(nil)
if err != nil {
panic(err)
}
jar.SetCookies(url, cookies)
client := http.Client{Jar: jar}
r, err := client.Get("http://httpbin.org/cookies")
В коде мы сначала создаем срез http.Cookie, а затем добавляем к нему 2 данных cookie. Здесь 2 новых файла cookie сохраняются через cookiejar.
На этот раз мы больше не можем использовать DefaultClient по умолчанию, а создадим новый клиент и привяжем к клиенту файл cookiejar, содержащий информацию о файлах cookie. Затем просто используйте только что созданный клиент, чтобы инициировать запрос.
установить куки по запросу
Настройка файла cookie для запроса может быть достигнута с помощью req.AddCookie. Образец кода:
req, err := http.NewRequest(http.MethodGet, "http://httpbin.org/cookies", nil)
if err != nil {
panic(err)
}
req.AddCookie(&http.Cookie{
Name: "name",
Value: "poloxue",
Domain: "httpbin.org",
Path: "/cookies",
})
r, err := http.DefaultClient.Do(req)
Довольно просто, нечего представлять.
В чем разница между установкой файла cookie на клиенте и настройкой его по запросу? Одним из самых простых отличий является то, что файл cookie запроса действителен только для текущего запроса, в то время как файл cookie клиента действителен в любое время, пока вы используете только что созданный клиент.
История редиректов и запросов
По умолчанию перенаправления автоматически обрабатываются для всех типов запросов.
Запрос HEAD в пакете запросов Python не перенаправляется, но результаты теста показывают, что HEAD из net/http перенаправляется автоматически.
Управление перенаправлением в net/http может контролироваться членом CheckRedirect в Client, который является типом функции. Определяется следующим образом:
type Client struct {
...
CheckRedirect func(req *Request, via []*Request) error
...
}
Далее, давайте посмотрим, как его использовать.
Предположим, мы хотим реализовать функцию: чтобы предотвратить циклическое перенаправление, количество перенаправлений не может быть определено более 10 раз, а исторические ответы должны быть записаны.
Образец кода:
var r *http.Response
history := make([]*http.Response, 0)
client := http.Client{
CheckRedirect: func(req *http.Request, hrs []*http.Request) error {
if len(hrs) >= 10 {
return errors.New("redirect to many times")
}
history = append(history, req.Response)
return nil
},
}
r, err := client.Get("http://github.com")
Сначала создайте переменную для среза http.Response с именем history. Затем назначьте анонимную функцию CheckRedirect в http.Client для управления поведением перенаправления. Первый параметр функции CheckRedirect указывает Запрос, который будет запрошен в следующий раз, а второй параметр указывает Запрос, который уже был запрошен.
Когда происходит перенаправление, текущий запрос сохранит ответ последнего запроса, поэтому здесь вы можете добавить req.Response к переменной истории.
настройка тайм-аута
После отправки Запроса, если сервер долго не отвечает, не стыдно. Потом подумаем, можно ли установить правило таймаута для запроса? Без сомнения, конечно.
Тайм-аут можно разделить на тайм-аут соединения и тайм-аут чтения ответа, который можно установить. Но в обычных обстоятельствах вам не нужно иметь такое четкое различие, поэтому вы также можете установить общий тайм-аут.
общее время ожидания
Параметр общего времени ожидания привязан к члену клиента с именем Timeout, то есть time.Duration.
Предполагая, что это тайм-аут 10 секунд, пример кода:
client := http.Client{
Timeout: time.Duration(10 * time.Second),
}
Время соединения истекло
Время ожидания соединения может быть реализовано через Transport в Client. Transport имеет функцию-член Dial, которую можно использовать для установки времени ожидания соединения. Транспорт — это транспортер данных, лежащий в основе HTTP.
Предполагая, что время ожидания соединения установлено на 2 секунды, пример кода:
t := &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
timeout := time.Duration(2 * time.Second)
return net.DialTimeout(network, addr, timeout)
},
}
В функции Dial мы используем net.DialTimeout для подключения к сети и реализуем функцию тайм-аута соединения.
тайм-аут чтения
Тайм-аут чтения также должен быть установлен через транспорт клиента, например, установка ответа чтения на 8 секунд.
Образец кода:
t := &http.Transport{
ResponseHeaderTimeout: time.Second * 8,
}
综合所有,Client 的创建代码如下:
t := &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
timeout := time.Duration(2 * time.Second)
return net.DialTimeout(network, addr, timeout)
},
ResponseHeaderTimeout: time.Second * 8,
}
client := http.Client{
Transport: t,
Timeout: time.Duration(10 * time.Second),
}
В дополнение к указанным выше настройкам тайм-аута у транспорта есть другие настройки тайм-аута, вы можете посмотреть определение транспорта и найти три определения, связанные с тайм-аутом:
// IdleConnTimeout is the maximum amount of time an idle
// (keep-alive) connection will remain idle before closing
// itself.
// Zero means no limit.
IdleConnTimeout time.Duration
// ResponseHeaderTimeout, if non-zero, specifies the amount of
// time to wait for a server's response headers after fully
// writing the request (including its body, if any). This
// time does not include the time to read the response body.
ResponseHeaderTimeout time.Duration
// ExpectContinueTimeout, if non-zero, specifies the amount of
// time to wait for a server's first response headers after fully
// writing the request headers if the request has an
// "Expect: 100-continue" header. Zero means no timeout and
// causes the body to be sent immediately, without
// waiting for the server to approve.
// This time does not include the time to send the request header.
ExpectContinueTimeout time.Duration
Это IdleConnTimeout (тайм-аут простоя соединения, включена поддержка), TLSHandshakeTimeout (время рукопожатия TLS) и ExpectContinueTimeout (кажется, включено в ResponseHeaderTimeout, см. комментарии).
На этом настройка тайм-аута завершена. По сравнению с запросами Python это действительно намного сложнее.
запросить прокси
Агенты по-прежнему очень важны, особенно для студентов, разрабатывающих сканеры. Как настроить прокси для сети/http? Эта работа по-прежнему зависит от реализации клиентского транспорта, что по-прежнему очень важно.
В Transport есть элемент Proxy, давайте посмотрим, как его использовать. Предположим, мы хотим запросить домашнюю страницу Google, установив прокси-сервер, адрес прокси-сервераhttp://127.0.0.1:8087.
Образец кода:
proxyUrl, err := url.Parse("http://127.0.0.1:8087")
if err != nil {
panic(err)
}
t := &http.Transport{
Proxy: http.ProxyURL(proxyUrl),
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := http.Client{
Transport: t,
Timeout: time.Duration(10 * time.Second),
}
r, err := client.Get("https://google.com")
В основном сосредоточьтесь на коде, созданном http.Transport. Два параметра, прокси-сервер с разделением времени и TLSClientConfig, используются для установки прокси-сервера и отключения проверки https соответственно. Я обнаружил, что запрос может быть успешным без установки TLSClientConfig, но конкретные причины тщательно не изучались.
обработка ошибок
На самом деле, нет необходимости вводить обработку ошибок.Общая ошибка в GO в основном заключается в проверке возвращаемой ошибки, и то же самое верно для HTTP-запросов.Он будет возвращать соответствующую информацию об ошибке в зависимости от ситуации, такой как тайм-аут, сеть сбой соединения и т.д.
Ошибки в примере кода выбрасываются паникой, чего точно не бывает в реальных проектах, нам нужно постоянно вести соответствующие логи и проводить работу по исправлению ошибок.
Суммировать
В этой статье документация по запросам Python используется в качестве руководства, чтобы разобраться, как кейсы из документации по быстрому запуску запросов реализованы в GO. Следует отметить, что GO фактически предоставляет клонированную версию, соответствующую запросам,гитхаб-адрес. Я еще не смотрел его, и друзья, которым интересно, могут изучить его.