HTTP запросы в Go
HTTP-запросы являются фундаментальной частью Интернета в целом. Они используются для доступа к ресурсам, размещенным на сервере (который может быть удаленным).
Большинство языков программирования имеют различные структуры для настройки HTTP-клиентов для выполнения запросов. В следующих разделах мы рассмотрим практический подход к изучению того, как вы можете делать HTTP-запросы в Golang или Go.
Get запрос
Первый запрос, который мы будем делать, — это запрос GET. Метод HTTP GET используется для запроса данных из указанного источника или сервера. Метод GET в основном используется, когда необходимо получить данные.
Для ясности важно отметить, что методы HTTP, как показано в этой статье, всегда пишутся с заглавной буквы.
В нашем примере мы будем извлекать некоторые примеры данных JSON из https://jsonplaceholder.typicode.com/posts , используя метод GET.
Первый шаг в выполнении HTTP-запроса с помощью Go — импорт net/http
пакета из стандартной библиотеки. Этот пакет предоставляет нам все утилиты, необходимые для простого выполнения HTTP-запросов. Мы можем импортировать net/http
пакет и другие пакеты, которые нам понадобятся, добавив следующие строки кода в main.go
созданный нами файл:
import ( "io/ioutil" "log" "net/http" )
В импортированном нами пакете net/http
есть функция Get, используемая для выполнения запросов GET. Функция Get принимает URL-адрес и возвращает ответ типа указателя на структуру и ошибку. Когда ошибка равна nil
, возвращаемый ответ будет содержать тело ответа и наоборот:
resp, err := http.Get("https://jsonplaceholder.typicode.com/posts/1") if err != nil { log.Fatalln(err) }
Чтобы сделать запрос, мы вызываем функцию Get, передавая строку URL-адреса ( https://jsonplaceholder.typicode.com/posts ), как показано выше. Значения, возвращаемые при вызове этой функции, хранятся в двух переменных, обычно называемых resp и err.
Хотя переменная resp содержит наш ответ, если мы ее распечатаем, то получим кучу бессвязных данных, включая заголовок и свойства сделанного запроса. Чтобы получить интересующий нас ответ, мы должны получить доступ к Body
свойству в структуре ответа и прочитать его, прежде чем, наконец, распечатать его на терминале. Мы можем прочитать тело ответа, используя функцию ioutil.ReadMe
Аналогично функции Get, функция ioutil.ReadMe возвращает тело и сообщение об ошибке. Важно отметить, что тело ответа должно быть закрыто после того, как мы закончим чтение из него, чтобы предотвратить утечки памяти.
Ключевое слово defer, которое выполняет resp.Body.Close() в конце функции используется для закрытия тела ответа. Затем мы можем продолжить и распечатать значение ответа на терминал. Как хорошим программистам важно обрабатывать возможные ошибки, поэтому мы используем оператор if для проверки наличия любых ошибок и регистрации ошибки, если она существует:
package main import ( "io/ioutil" "log" "net/http" ) func main() { resp, err := http.Get("https://jsonplaceholder.typicode.com/posts") if err != nil { log.Fatalln(err) } //We Read the response body on the line below. body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatalln(err) } //Convert the body to type string sb := string(body) log.Printf(sb) }
На данный момент у нас все готово, и мы можем выполнить файл, содержащий наш код. Если все прошло хорошо, вы заметите, что некоторые данные JSON, похожие на изображение ниже, будут напечатаны на терминале:
Поздравляем, вы только что сделали свой первый HTTP-запрос с помощью Go. Теперь, когда мы увидели, как мы можем получать ресурсы с сервера с помощью метода HTTP GET, мы теперь рассмотрим, как отправлять ресурсы на сервер.
POST запрос
Метод HTTP POST используется для отправки запросов, которые обычно содержат тело. Он используется для отправки данных на сервер, отправленные данные обычно используются для создания или обновления ресурсов.
Очевидным примером использования запроса POST является то, что когда пользователь пытается создать учетную запись в социальной сети, от пользователя требуется предоставить свои данные (имя, адрес электронной почты и пароль). Затем эти данные анализируются и отправляются в виде POST-запроса на сервер, который затем создает и сохраняет пользователя.
Точно так же, как и для метода GET, рассмотренного выше, пакет Go net/http также предоставляет функциональность для выполнения POST-запросов через функцию Post. Функция Post принимает три параметра.
- URL-адрес сервера
- Тип содержимого тела в виде строки
- Тело запроса, которое должно быть отправлено методом POST типа
io.Reader
Функция Post возвращает ответ и ошибку. Чтобы вызвать функцию Post, мы должны преобразовать тело запроса в принятый тип. В этом примере мы сделаем почтовый запрос на https://postman-echo.com/post и передадим данные JSON, содержащие имя и адрес электронной почты. Для начала мы преобразуем наши данные JSON в тип, который реализует интерфейс Io.Reader, ожидаемый функцией Post, это двусторонний шаг:
- Первым шагом является кодирование наших данных JSON, чтобы они могли возвращать данные в байтовом формате, для этого мы используем функцию Marshall, которую предоставляет пакет Go Json.
- Далее мы конвертируем закодированные данные JSON в тип, реализованный интерфейсом , мы просто используем для этого функцию, передавая закодированные данные JSON в качестве аргумента. Функция возвращает значение типа buffer, которое мы затем можем передать в функцию Post.
io.ReaderNewBufferNewBuffer
postBody, _ := json.Marshal(map[string]string{ "name": "Toby", "email": "Toby@example.com", }) responseBody := bytes.NewBuffer(postBody)
Теперь, когда у нас есть все аргументы, необходимые функции Post, мы можем продолжить и вызвать ее, передав https://postman-echo.com/post в качестве строки URL, application/JSON в качестве типа контента и тела запроса. возвращается функцией NewBuffer
как тело. Значения, возвращаемые функцией Post
, затем присваиваются resp и err, представляющим ответ и ошибку соответственно. После обработки ошибки мы читаем и печатаем тело ответа, как мы это делали для функции Get в предыдущем разделе. На этом этапе ваш файл должен выглядеть так:
import ( "bytes" "encoding/json" "io/ioutil" "log" "net/http" ) func main() { //Encode the data postBody, _ := json.Marshal(map[string]string{ "name": "Toby", "email": "Toby@example.com", }) responseBody := bytes.NewBuffer(postBody) //Leverage Go's HTTP Post function to make request resp, err := http.Post("https://postman-echo.com/post", "application/json", responseBody) //Handle Error if err != nil { log.Fatalf("An Error Occured %v", err) } defer resp.Body.Close() //Read the response body body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatalln(err) } sb := string(body) log.Printf(sb) }
Когда файл будет запущен, если все работает хорошо, у нас должен быть распечатан ответ. Удивительно, правда? Мы только что сделали post-запрос с Go, используя пакет net /http, который предоставляет функциональность, упрощающую HTTP-запросы. В следующем разделе мы будем работать над проектом, который поможет нам увидеть, как HTTP-запросы используются в реальном сценарии.
HTTP-запросы в действии
В этом разделе мы создадим CLI-инструмент для проверки цен на криптовалюту! Цель этого упражнения — дать вам возможность увидеть реальный пример использования HTTP-запросов.
Создаваемый нами инструмент будет проверять цену любой криптовалюты, указанную пользователем, в указанной фиатной валюте. Мы будем использовать данные о рыночной капитализации и ценах, предоставленные Nomics, чтобы получать цены на криптовалюты в режиме реального времени! Для начала создайте необходимые файлы и папки в соответствии с древовидной структурой ниже:
├── model/ │ ├── crypto-model.go ├── client/ │ ├── crypto-client.go └── main.go
- Файл крипто-клиента будет содержать код, который извлекает данные о криптовалюте из API.
- Файл криптомодели содержит несколько служебных функций, необходимых для нашего приложения.
- Основной файл — это центральный движок приложения, он объединит все части приложения, чтобы сделать его функциональным.
В файле криптомодели мы создаем структуру, которая моделирует данные, полученные от API, эта структура включает только те данные, которые нам нужны/намерены работать. Затем мы создаем функцию с именем TextOutput
, которая является получателем, принадлежащим структуре, Cryptoresponse
которую мы создали выше. Цель функции TextOutput
— форматировать данные, полученные от API, в обычный текст, который легче читать, чем JSON (который мы получаем от сервера). Мы используем функцию для форматирования данных:fmt.Sprintf
package model import ( "fmt" ) // Cryptoresponse is exported, it models the data we receive. type Cryptoresponse []struct { Name string `json:"name"` Price string `json:"price"` Rank string `json:"rank"` High string `json:"high"` CirculatingSupply string `json:"circulating_supply"` } //TextOutput is exported,it formats the data to plain text. func (c Cryptoresponse) TextOutput() string { p := fmt.Sprintf( "Name: %s\nPrice : %s\nRank: %s\nHigh: %s\nCirculatingSupply: %s\n", c[0].Name, c[0].Price, c[0].Rank, c[0].High, c[0].CirculatingSupply) return p }
Теперь, когда файл криптомодели готов, мы можем перейти к файлу криптоклиента, который является наиболее актуальным для нас. В файле crypto-client мы создаем функцию FetchCrypto, которая принимает название криптовалюты и фиатную валюту в качестве параметров.
Обратите внимание, что мы делаем первую букву имени функции заглавной, чтобы обеспечить ее экспорт.
В FetchCrypto
функции мы создаем переменную с именем URL, переменная представляет собой объединение строки URL, предоставленной Nomics API , и различных переменных, которые будут переданы в наше приложение. Помните, наше приложение принимает название нужной криптовалюты и предпочтительную фиатную валюту? Это переменные, которые затем используются для построения нашей строки URL. Наша строка URL будет выглядеть так.
URL := "...currencies/ticker?key=3990ec554a414b59dd85d29b2286dd85&interval=1d&ids="+crypto+"&convert="+fiat
После настройки URL-адреса мы можем продолжить и использовать функцию Get, которую мы видели выше, чтобы сделать запрос. Функция Get возвращает ответ, и мы элегантно обрабатываем ошибку. Чтобы получить нужные нам данные в нужном формате, мы должны их расшифровать! Для этого мы используем Json.NewDecoder
функцию, которая принимает тело ответа, и функция decode, которая принимает переменную типа cryptoresponse, которую мы создали в файле криптомодели.
Наконец, мы вызываем функцию TextOutput для декодированных данных, чтобы получить наш результат в виде обычного текста:
package client import ( "encoding/json" "fmt" "log" "net/http" "github.com/Path/to/model" ) //Fetch is exported ... func FetchCrypto(fiat string , crypto string) (string, error) { //Build The URL string URL := "https://api.nomics.com/v1/currencies/ticker?key=3990ec554a414b59dd85d29b2286dd85&interval=1d&ids="+crypto+"&convert="+fiat //We make HTTP request using the Get function resp, err := http.Get(URL) if err != nil { log.Fatal("ooopsss an error occurred, please try again") } defer resp.Body.Close() //Create a variable of the same type as our model var cResp model.Cryptoresponse //Decode the data if err := json.NewDecoder(resp.Body).Decode(&cResp); err != nil { log.Fatal("ooopsss! an error occurred, please try again") } //Invoke the text output function & return it with nil as the error value return cResp.TextOutput(), nil }
Из того, что мы видели выше, приложение хорошо складывается. Однако, если вы попытаетесь запустить указанный выше файл, вы столкнетесь с парой ошибок, это связано с тем, что мы не вызываем функцию, FetchCrypto
и поэтому значения параметров fiat и crypto не предоставляются.
Мы объединим все различные части нашего приложения в созданный нами файл. Поскольку наше приложение является инструментом командной строки, пользователям придется передавать данные через терминал. Мы справимся с этим с помощью пакета флагов Go.main.go
В основной функции мы создаем две переменные fiatcurrency и nameofcrypto. Обе эти переменные вызывают функцию flag.string, передавая в:
- Имя команд в качестве первого аргумента
- Резервные значения в качестве второго
- Информация о том, как использовать команду в качестве третьего аргумента
Далее мы вызываем функцию FetchCrypto, которую мы определили в файле crypto-client, и передаем переменные fiatcurrency и nameofcrypto. Затем мы можем продолжить и напечатать результат вызова FetchCrypto:
package main import ( "flag" "fmt" "log" "github.com/path/to/client" ) func main() { fiatCurrency := flag.String( "fiat", "USD", "The name of the fiat currency you would like to know the price of your crypto in", ) nameOfCrypto := flag.String( "crypto", "BTC", "Input the name of the CryptoCurrency you would like to know the price of", ) flag.Parse() crypto, err := client.FetchCrypto(*fiatCurrency, *nameOfCrypto) if err != nil { log.Println(err) } fmt.Println(crypto) }
На данный момент все готово, если мы запустим команду go run main.go -fiat=EUR -crypto=ETH, мы получим результат, аналогичный изображению ниже:
Это показывает, что наше приложение работает нормально, что довольно круто. У нас есть приложение, которое извлекает данные с удаленного сервера по протоколу HTTP.
Заключение
В этой статье мы обсудили, как делать HTTP-запросы в Go, и создали инструмент CLI для проверки цен на криптовалюты.