Tarkibga o'tish

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))
}
go run main.go
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.