API ga so'rov yuborish
Oldingi darslarda Go'da API server yozdik. Bu darsda teskari tomoni — Go'dan tashqi API ga so'rov yuborishni ko'ramiz.
Go'ning standart net/http paketi server yozish bilan bir qatorda HTTP klient sifatida ham ishlaydi. Hech qanday qo'shimcha kutubxona kerak emas.
Oddiy GET so'rovi
Eng sodda holat — boshqa serverdan ma'lumot olish:
| main.go |
|---|
| package main
import (
"fmt"
"io"
"net/http"
)
func main() {
resp, err := http.Get("https://httpbin.org/get")
if err != nil {
fmt.Println("Xato:", err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("O'qish xatosi:", err)
return
}
fmt.Println("Status:", resp.StatusCode)
fmt.Println("Javob:", string(body))
}
|
Status: 200
Javob: {
"args": {},
"headers": {...},
"url": "https://httpbin.org/get"
}
Kod qanday ishlaydi:
http.Get(url) — GET so'rovini yuboradi. Ikki qiymat qaytaradi: javob (*http.Response) va xato.
defer resp.Body.Close() — funksiya tugaganda javob tanasini yopadi. Bu har doim kerak — yopilmasa tarmoq ulanishi "qolib ketadi" va xotira sizib chiqadi.
io.ReadAll(resp.Body) — butun tanani baytlar sifatida o'qiydi. string(body) bilan matnга aylantiriladi.
JSON javobni strukturaga o'qish
Ko'pincha javobni to'g'ridan-to'g'ri Go strukturasiga o'girish qulay:
| main.go |
|---|
| package main
import (
"encoding/json"
"fmt"
"net/http"
)
type Post struct {
ID int `json:"id"`
Title string `json:"title"`
Body string `json:"body"`
UserID int `json:"userId"`
}
func main() {
resp, err := http.Get("https://jsonplaceholder.typicode.com/posts/1")
if err != nil {
fmt.Println("Xato:", err)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
fmt.Println("Kutilmagan status:", resp.StatusCode)
return
}
var post Post
if err := json.NewDecoder(resp.Body).Decode(&post); err != nil {
fmt.Println("JSON xatosi:", err)
return
}
fmt.Printf("ID: %d\nSarlavha: %s\n", post.ID, post.Title)
}
|
ID: 1
Sarlavha: sunt aut facere repellat provident occaecati excepturi optio reprehenderit
json.NewDecoder(resp.Body).Decode(&post) — tanadan to'g'ridan-to'g'ri strukturaga o'qiydi. Avval io.ReadAll bilan baytlarga aylantirib, keyin json.Unmarshal qilish shart emas.
Note
Status kodini tekshirish muhim. http.Get xato qaytarmasa ham, server 404 yoki 500 javob bergan bo'lishi mumkin. err == nil — faqat tarmoq ulanishi muvaffaqiyatli, javob mazmuni emas.
POST so'rovi
Ma'lumot yuborish — http.Post yoki to'liq http.NewRequest:
| main.go |
|---|
| package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
type YangiPost struct {
Title string `json:"title"`
Body string `json:"body"`
UserID int `json:"userId"`
}
func main() {
yangiPost := YangiPost{
Title: "Mening postim",
Body: "Bu post tanasi",
UserID: 1,
}
data, err := json.Marshal(yangiPost)
if err != nil {
fmt.Println("JSON xatosi:", err)
return
}
resp, err := http.Post(
"https://jsonplaceholder.typicode.com/posts",
"application/json",
bytes.NewBuffer(data),
)
if err != nil {
fmt.Println("Xato:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println("Status:", resp.StatusCode)
fmt.Println("Javob:", string(body))
}
|
Status: 201
Javob: {
"title": "Mening postim",
"body": "Bu post tanasi",
"userId": 1,
"id": 101
}
http.Post uchta argument qabul qiladi: URL, Content-Type sarlavhasi, va tanа (io.Reader). bytes.NewBuffer(data) bayt tilimini io.Reader ga aylantiradi.
Sarlavha bilan so'rov: http.NewRequest
Authorization yoki boshqa sarlavhalar qo'shish kerak bo'lsa, http.NewRequest ishlatiladi:
| main.go |
|---|
| package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest(http.MethodGet, "https://httpbin.org/bearer", nil)
if err != nil {
fmt.Println("So'rov yaratish xatosi:", err)
return
}
req.Header.Set("Authorization", "Bearer mening-tokenim")
req.Header.Set("Accept", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Xato:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println("Status:", resp.StatusCode)
fmt.Println("Javob:", string(body))
}
|
http.NewRequest — so'rovni yaratadi, lekin yubormasdan. req.Header.Set bilan istalgan sarlavha qo'shiladi. client.Do(req) — so'rovni yuboradi.
So'rov parametrlari (Query params)
URL ga ?key=value parametrlar qo'shish:
| main.go |
|---|
| package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest(http.MethodGet, "https://httpbin.org/get", nil)
if err != nil {
fmt.Println("Xato:", err)
return
}
// Query parametrlarni qo'shish
q := req.URL.Query()
q.Add("sahifa", "1")
q.Add("limit", "10")
q.Add("qidiruv", "go dasturlash")
req.URL.RawQuery = q.Encode()
fmt.Println("URL:", req.URL.String())
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Xato:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println("Status:", resp.StatusCode)
_ = body
}
|
URL: https://httpbin.org/get?limit=10&qidiruv=go+dasturlash&sahifa=1
req.URL.Query() — mavjud parametrlarni url.Values sifatida qaytaradi. .Add bilan yangilar qo'shiladi. .Encode() ularni URL-safe formatga o'giradi (bo'shliqlar + ga, maxsus belgilar %XX ga).
PUT va DELETE so'rovlari
http.Post singari http.Put yoki http.Delete yo'q. Bular uchun http.NewRequest ishlatiladi:
| main.go |
|---|
| package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
// PUT — yangilash
data, _ := json.Marshal(map[string]any{
"title": "Yangilangan sarlavha",
"userId": 1,
})
req, _ := http.NewRequest(
http.MethodPut,
"https://jsonplaceholder.typicode.com/posts/1",
bytes.NewBuffer(data),
)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Xato:", err)
return
}
defer resp.Body.Close()
fmt.Println("PUT status:", resp.StatusCode)
// DELETE — o'chirish
req2, _ := http.NewRequest(
http.MethodDelete,
"https://jsonplaceholder.typicode.com/posts/1",
nil,
)
resp2, err := client.Do(req2)
if err != nil {
fmt.Println("Xato:", err)
return
}
defer resp2.Body.Close()
fmt.Println("DELETE status:", resp2.StatusCode)
}
|
PUT status: 200
DELETE status: 200
nil tanasi — DELETE odatda tanasiz yuboriladi.
Timeout sozlash
Standartda http.Get va http.Post cheksiz kutishi mumkin. Haqiqiy loyihada har doim timeout qo'ying:
| main.go |
|---|
| package main
import (
"fmt"
"net/http"
"time"
)
func main() {
client := &http.Client{
Timeout: 5 * time.Second,
}
resp, err := client.Get("https://httpbin.org/delay/3")
if err != nil {
fmt.Println("Xato (timeout?):", err)
return
}
defer resp.Body.Close()
fmt.Println("Status:", resp.StatusCode)
}
|
5 * time.Second timeout bilan 3 soniyalik kechiktirilgan javobga so'rov yuboriladi. Server 3 soniyadan keyin javob berishga harakat qiladi, lekin klient 5 soniyada uzadi.
Warning
http.DefaultClient (ya'ni http.Get, http.Post da ishlatiluvchi) ning timeouti yo'q. Javob kechiksa dastur cheksiz kutadi. Har doim timeout bilan o'zingizning http.Client ni yarating.
Xatolarni to'g'ri qayta ishlash
Ishlab chiqish uchun mos pattern:
| main.go |
|---|
| package main
import (
"encoding/json"
"fmt"
"net/http"
"time"
)
var httpClient = &http.Client{
Timeout: 10 * time.Second,
}
type APIJavob struct {
Data any `json:"data"`
Message string `json:"message"`
}
func getPost(id int) (*APIJavob, error) {
url := fmt.Sprintf("https://jsonplaceholder.typicode.com/posts/%d", id)
resp, err := httpClient.Get(url)
if err != nil {
return nil, fmt.Errorf("so'rov yuborishda xato: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
return nil, fmt.Errorf("post topilmadi: id=%d", id)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("kutilmagan status: %d", resp.StatusCode)
}
var javob APIJavob
if err := json.NewDecoder(resp.Body).Decode(&javob); err != nil {
return nil, fmt.Errorf("JSON o'qishda xato: %w", err)
}
return &javob, nil
}
func main() {
javob, err := getPost(1)
if err != nil {
fmt.Println("Xato:", err)
return
}
fmt.Println("Ma'lumot:", javob)
}
|
Bu yondashuvning afzalliklari:
- Tarmoq xatosi, HTTP xatosi, JSON xatosi — alohida-alohida qayta ishlanadi
fmt.Errorf("%w", err) — xatoni zanjirlashtiradi, kerak bo'lsa tepada kengaytiriladi
httpClient — paket darajasida bitta klient, har so'rovda yangi yaratilmaydi
Bir vaqtda bir nechta so'rov
Goroutine va channel bilan parallel so'rovlar:
| main.go |
|---|
| package main
import (
"encoding/json"
"fmt"
"net/http"
"sync"
"time"
)
type Post struct {
ID int `json:"id"`
Title string `json:"title"`
}
var client = &http.Client{Timeout: 5 * time.Second}
func fetchPost(id int, wg *sync.WaitGroup, results chan<- Post) {
defer wg.Done()
url := fmt.Sprintf("https://jsonplaceholder.typicode.com/posts/%d", id)
resp, err := client.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
var post Post
if err := json.NewDecoder(resp.Body).Decode(&post); err != nil {
return
}
results <- post
}
func main() {
ids := []int{1, 2, 3, 4, 5}
results := make(chan Post, len(ids))
var wg sync.WaitGroup
for _, id := range ids {
wg.Add(1)
go fetchPost(id, &wg, results)
}
wg.Wait()
close(results)
for post := range results {
fmt.Printf("ID: %d — %s\n", post.ID, post.Title)
}
}
|
5 ta so'rov parallel yuboriladi. Ketma-ket yuborilganda 5 × javob_vaqti kutiladi, parallel yuborilganda faqat eng sekin so'rov qadar.
Xulosa
| Vazifa |
Funksiya |
| Oddiy GET |
http.Get(url) |
| Oddiy POST (JSON) |
http.Post(url, "application/json", body) |
| Sarlavha qo'shish |
http.NewRequest + req.Header.Set |
| Query parametr |
req.URL.Query() + .Add + .Encode() |
| PUT / DELETE |
http.NewRequest(http.MethodPut, ...) |
| Timeout |
&http.Client{Timeout: ...} |
Haqiqiy loyihada bitta http.Client yaratib, uni qayta ishlatish yaxshi amaliyot. http.Get va http.Post — timeout yo'qligi sababli ishlab chiqishda faqat tezkor sinovlar uchun mos.