response, err := http.Get("http://example.com/index.html")
if err != nil {
fmt.Println(err)
}
http.Get(url string)
возвращает указатель на структуру http.Response
(ответ сервера) и error
(ошибку, если что-то пошло не так). В URL-параметре указывают HTTP- или HTTPS-протокол.http.Response
содержит заголовки ответа, тело и дополнительную информацию. В уроке «Создание HTTP-сервера» вы научились устанавливать кода статуса и заголовки ответа. Эти данные хранятся в полях StatusCode
и Header
соответственно. practicum.yandex.ru
:package main
import (
"fmt"
"net/http"
)
func main() {
response, err := http.Get("https://practicum.yandex.ru")
if err != nil {
fmt.Println(err)
}
fmt.Printf("Status Code: %d\r\n", response.StatusCode)
for k, v := range response.Header {
// заголовок может иметь несколько значений,
// но для простоты запросим только первое
fmt.Printf("%s: %v\r\n", k, v[0])
}
}
Status Code: 200
X-Request-Id: 1663854943634850-3617306141716402244
Date: Thu, 22 Sep 2022 13:55:45 GMT
Set-Cookie: _yasc=6xwp8F8yEKot/1Zso72AZeQrdloTNG4Cl/s+EnMNncw3og==; domain=.yandex.ru; path=/; expires=Sat, 22-Oct-2022 13:55:43 GMT; secure
Vary: Accept-Encoding
Etag: W/"742df-+H+2A9iCP7aCMRNI7y9HsHaKzfI"
Keep-Alive: timeout=5
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Powered-By: Express
Content-Security-Policy:
Content-Type: text/html; charset=utf-8
Report-To: {"group":"default-group","endpoints":[{"url":"https://csp.yandex.net/csp?from=wirth&project=practicum"}],"max_age":1800,"include_subdomains":true}
Content-Type: text/html; charset=utf-8
. (h Header) Values(key string) []string
, а самое первое значение — методом (h Header) Get(key string) string
.contentType := response.Header.Get("Content-Type")
// это может быть, например, "application/json; charset=UTF-8"
Response.Body
интерфейсом потокового чтения io.ReadCloser
, который содержит два метода: Read(p []byte) (n int, err error)
(интерфейс Reader) и Close() error
(интерфейс Closer). Если клиент успешно получил ответ, то нужно закрыть Body
независимо от того, прочитаете вы его или нет. []byte
можно функцией io.ReadAll(r Reader) ([]byte, error)
:response, err := http.Get("http://example.com")
if err != nil {
fmt.Println(err)
return
}
body, err := io.ReadAll(response.Body)
response.Body.Close()
if err != nil {
fmt.Println(err)
return
}
Content-Type
. Если, например, Content-Type
равен text/html; charset=utf-8
, это значит, что тело ответа содержит текст в формате HTML и кодировке UTF-8. POST
, можно использовать функцию Post(url, contentType string, body io.Reader) (resp *Response, err error)
.data := `{"name": "Иванов Иван", "email": "ivan@example.com"}`
resp, err := http.Post("https://example.com/adduser", "application/json", strings.NewReader(data))
if err != nil {
return err
}
Content-Type
.http.Client
, а функции http.Get()
и http.Post()
— обёртки над вызовом этих методов у глобальной переменной http.DefaultClient
.http.Client
можно обратиться к серверу и получить http.Response
:client := &http.Client{}
response, err := client.Get("https://golang.org")
Timeout time.Duration
— максимальная задержка ответа, после которой клиент отменяет запрос. Если это поле имеет нулевое значение, то лимит на ожидание не установлен. client := http.Client{
Timeout: time.Second * 1, // интервал ожидания: 1 секунда
}
http://example.com
→ https://example.com
→ https://www.example.com
. По умолчанию клиент переходит только по 10 адресам. Поле CheckRedirect func(req *Request, via []*Request) error
позволяет контролировать редиректы. client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
fmt.Println(req.URL)
return nil
},
}
response, err := client.Get("http://ya.ru")
http://ya.ru
программа может вывести:https://ya.ru/
https://ya.ru/?nr=1
https://ya.ru/showcaptcha?cc=1&...
.RoundTripper
. Стандартная библиотека предоставляет реализацию этого интерфейса структурой http.Transport
, где можно указать тайм-ауты соединения и другие низкоуровневые настройки. http.DefaultTransport
. При необходимости можно задать свой вариант реализации через поле http.Client.Transport
. http.Transport
позволяет переиспользовать уже открытые TCP-подключения (их называют HTTP keep-alive), поэтому http.Client
обычно применяют повторно, а не создают заново для каждого запроса. К тому же один экземпляр клиента можно параллельно использовать из разных горутин.response.Body
, даже если оно не нужно.// io.Discard выступает в качестве приёмника ненужных данных
_, err := io.Copy(io.Discard, response.Body)
response.Body.Close()
if err != nil {
fmt.Println(err)
}
http.Get()
переиспользовать один клиент?http.DefaultClient
.http.Get()
использует клиент по умолчанию http.DefaultClient
.client.Get()
или client.Post()
, то можно сформировать нужный запрос *http.Request
и отправить его с помощью метода (c *Client) Do(req *Request) (*Response, error)
.NewRequest(method, url string, body io.Reader) (*Request, error)
, где method
— имя метода запроса, url
— адрес. Третьим параметром указывается переменная интерфейсного типа io.Reader
, из которой будет прочитано тело запроса. Если тело запроса отправлять не нужно (например, для метода GET
), можно указывать значение nil
.client.Do()
:request, err := http.NewRequest(http.MethodGet, "http://localhost:8080", nil)
if err != nil {
panic(err)
}
response, err := client.Do(request)
if err != nil {
panic(err)
}
io.Copy(os.Stdout, response.Body) // вывод ответа в консоль
response.Body.Close()
*http.Request
можно установить значения заголовков через поле Header
типа http.Header
. Чтобы задать содержимое заголовка, используют один из двух методов:(h Header) Set(key, value string)
— установить значение заголовка;(h Header) Add(key, value string)
— добавить значение заголовка к уже существующим.MyHeader
c двумя значениями — Hello
и Привет
.req, err := http.NewRequest(http.MethodGet, `http://localhost:8080`, nil)
if err != nil {
panic(err)
}
req.Header.Set(`MyHeader`, "Hello")
req.Header.Add(`MyHeader`, "Привет")
response, err := client.Do(req)
POST
-запроса с часто встречающимися заголовками "Content-Type"
."application/json"
io.Reader
используется bytes.Buffer
, а "Content-Type"
равен "application/json"
. Чаще всего этот подход можно встретить в современных REST API, где запросы и ответы передаются в JSON-формате.var body = []byte(`{"message":"Hello"}`)
request, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(body))
if err != nil {
// обрабатываем ошибку
}
request.Header.Set("Content-Type", "application/json; charset=UTF-8")
response, err := client.Do(request)
"multipart/form-data"
file, _ := os.Open(filename) // открываем файл
defer file.Close() // не забываем закрыть
body := &bytes.Buffer{} // создаём буфер
// на основе буфера конструируем multipart.Writer из пакета mime/multipart
writer := multipart.NewWriter(body)
// готовим форму для отправки файла на сервер
part, err := writer.CreateFormFile("uploadfile", filename)
if err != nil {
// обрабатываем ошибку
}
// копируем файл в форму
// multipart.Writer отформатирует данные и запишет в предоставленный буфер
_, err = io.Copy(part, file)
if err != nil {
// обрабатываем ошибку
}
writer.Close()
// пишем запрос
request, err := http.NewRequest(http.MethodPost, url, body)
if err != nil {
// обрабатываем ошибку
}
// добавляем заголовок запроса
request.Header.Set("Content-Type", writer.FormDataContentType())
response, err := client.Do(request)
"application/x-www-form-urlencoded"
ключ=значение
. Данные кодируются в виде строки key1=value1&key2=value2
. При GET
-запросе параметры указываются после адреса: example.com/endpoint?key1=value1&key2=value2
. Этот формат кодировки по умолчанию используется в браузерах.// готовим контейнер для данных
// используем тип url.Values из пакета net/url
data := url.Values{}
// устанавливаем данные
data.Set("key1", "value1")
data.Set("key2", "value2")
// пишем запрос
request, err := http.NewRequest(http.MethodPost, url, strings.NewReader(data.Encode()))
if err != nil {
// обрабатываем ошибку
}
// устанавливаем заголовки
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
request.Header.Set("Content-Length", strconv.Itoa(len(data.Encode())))
response, err := client.Do(request)
http.Cookie
. Вот её обязательные поля:Name string
— имя куки.Value string
— значение куки.Path string
— путь URL, начиная с которого распространяется действие куки.Domain string
— хост. Если он не задан, то берётся доменная часть адреса.Expires time.Time
— дата и время, когда истекает срок действия куки. После этого кука удаляется.MaxAge int
— время жизни куки в секундах. Если значение равно 0, то атрибут не указан. Это альтернатива полю Expires
.Secure bool
— если true
, то кука будет доступна только по протоколу HTTPS.HttpOnly bool
— если true
, то кука будет недоступна для чтения из JavaScript, при этом браузер всё равно будет отправлять её на сервер.(r *Request) Cookie(name string) (*Cookie, error)
— получить структуру куки с указанным именем.(r *Request) Cookies() []*Cookie
— получить список всех кук.http.SetCookie(w ResponseWriter, cookie *Cookie)
— установить куку в ответ сервера.(r *Request) AddCookie(c *Cookie)
— добавить в запрос нужные куки.(r *Response) Cookies() []*Cookie
— получить куки из ответа сервера.client := &http.Client{}
req, err := http.NewRequest(http.MethodGet, `http://localhost:8080`, nil)
if err != nil {
panic(err)
}
req.AddCookie(&http.Cookie{
Name: "ID",
Value: "3675",
})
req.AddCookie(&http.Cookie{
Name: "Token",
Value: "TEST_TOKEN",
MaxAge: 360,
})
response, err := client.Do(req)
net/http/cookiejar
. Он позволяет автоматически обновлять куки, полученные от сервера, и отправлять их при каждом запросе. Jar
в переменной типа http.Client
:jar, err := cookiejar.New(nil)
if err != nil {
panic(err)
}
client := &http.Client{
Jar: jar,
}
req, err := http.NewRequest(http.MethodGet, `http://localhost:8080`, nil)
if err != nil {
panic(err)
}
cookie := &http.Cookie{
Name: "Token",
Value: "TEST_TOKEN",
MaxAge: 300,
}
req.AddCookie(cookie)
response, err := client.Do(req)
Jar
клиент после запроса запомнит куки, полученные от сервера, и добавит их в следующий запрос к этому доменному имени.POST
«Сервису сокращения URL» и получить ответ — короткий псевдоним.main.go
поддиректории cmd/client
вашего проекта. Тогда у вас всегда будет под рукой программа для проверки сервиса.package main
import (
"bufio"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
"strings"
)
func main() {
endpoint := "http://localhost:8080/"
// контейнер данных для запроса
data := url.Values{}
// приглашение в консоли
fmt.Println("Введите длинный URL")
// открываем потоковое чтение из консоли
reader := bufio.NewReader(os.Stdin)
// читаем строку из консоли
long, err := reader.ReadString('\n')
if err != nil {
panic(err)
}
long = strings.TrimSuffix(long, "\n")
// заполняем контейнер данными
data.Set("url", long)
// добавляем HTTP-клиент
client := &http.Client{}
// пишем запрос
// запрос методом POST должен, помимо заголовков, содержать тело
// тело должно быть источником потокового чтения io.Reader
request, err := http.NewRequest(http.MethodPost, endpoint, strings.NewReader(data.Encode()))
if err != nil {
panic(err)
}
// в заголовках запроса указываем кодировку
request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
// отправляем запрос и получаем ответ
response, err := client.Do(request)
if err != nil {
panic(err)
}
// выводим код ответа
fmt.Println("Статус-код ", response.Status)
defer response.Body.Close()
// читаем поток из тела ответа
body, err := io.ReadAll(response.Body)
if err != nil {
panic(err)
}
// и печатаем его
fmt.Println(string(body))
}
net/http
я тебя познакомил. Но кроме него, есть множество сторонних библиотек, которые ускоряют разработку сервера и клиента, — им будет посвящён следующий урок.cookiejar
.