+1

Bài 2: Gửi HTTP Requests cơ bản

Package net/http cung cấp sẵn các hàm tiện ích trực tiếp ở cấp độ package như http.Get, http.Post, và http.PostForm. Các hàm này sử dụng một HTTP Client mặc định (http.DefaultClient) bên dưới, cho phép bạn thực hiện các lời gọi API nhanh chóng mà không cần khởi tạo cấu hình phức tạp.

1. Thực hiện GET Request

Hàm http.Get được sử dụng để lấy dữ liệu từ một server. Khi gọi thành công, nó trả về một đối tượng *http.Response.

Go

package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	// 1. Gửi GET request
	resp, err := http.Get("https://jsonplaceholder.typicode.com/posts/1")
	if err != nil {
		fmt.Println("Lỗi khi gọi API:", err)
		return
	}
	
	// 2. Đảm bảo đóng Body để trả lại connection cho TCP Pool
	defer resp.Body.Close()

	// 3. Kiểm tra HTTP Status Code
	if resp.StatusCode != http.StatusOK {
		fmt.Println("Server trả về lỗi, code:", resp.StatusCode)
		return
	}

	// 4. Đọc dữ liệu thô (raw data) từ Body
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("Lỗi đọc body:", err)
		return
	}

	fmt.Println("Nội dung response:", string(body))
}

2. Thực hiện POST Request (Gửi JSON)

Để gửi dữ liệu lên server tạo mới tài nguyên, bạn dùng http.Post. Hàm này yêu cầu 3 tham số: URL, Content-Type, và một io.Reader chứa dữ liệu gửi đi (payload).

Với API hiện đại, dữ liệu thường là JSON. Bạn cần encode một struct hoặc map thành JSON byte slice, sau đó bọc nó vào bytes.Buffer để tạo thành io.Reader.

Go

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

func main() {
	// 1. Chuẩn bị dữ liệu payload
	payload := map[string]string{
		"title":  "Bài viết mới",
		"body":   "Nội dung bài viết",
		"userId": "1",
	}

	// Chuyển map thành JSON bytes
	jsonData, _ := json.Marshal(payload)

	// 2. Gửi POST request
	resp, err := http.Post(
		"https://jsonplaceholder.typicode.com/posts",
		"application/json",
		bytes.NewBuffer(jsonData), // Biến đổi byte slice thành io.Reader
	)
	if err != nil {
		fmt.Println("Lỗi:", err)
		return
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Printf("Status: %d\nResponse: %s\n", resp.StatusCode, string(body))
}

3. Parse JSON trực tiếp (Khuyên dùng cho Backend)

Thay vì dùng io.ReadAll để nạp toàn bộ response body vào RAM dưới dạng chuỗi (rất tốn bộ nhớ nếu payload lớn), trong thực tế hệ thống backend, bạn nên dùng json.NewDecoder để stream dữ liệu trực tiếp vào một struct trong Go.

Go

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
)

// Định nghĩa struct khớp với cấu trúc JSON trả về
type Post struct {
	ID    int    `json:"id"`
	Title string `json:"title"`
	Body  string `json:"body"`
}

func main() {
	resp, err := http.Get("https://jsonplaceholder.typicode.com/posts/1")
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	var p Post
	// Parse JSON stream trực tiếp vào struct p
	if err := json.NewDecoder(resp.Body).Decode(&p); err != nil {
		fmt.Println("Lỗi parse JSON:", err)
		return
	}

	fmt.Printf("ID: %d | Title: %s\n", p.ID, p.Title)
}

4. Nguyên tắc sống còn: Quản lý TCP Connection

Trong Go, net/http sử dụng cơ chế Connection Pooling (giữ lại các kết nối TCP để tái sử dụng cho các request sau tới cùng một host).

  • Vấn đề: Nếu bạn không gọi resp.Body.Close(), Go sẽ coi như bạn vẫn chưa đọc xong dữ liệu. Kết nối TCP đó sẽ bị giữ trạng thái "đang mở" mãi mãi.

  • Hậu quả: Khi hệ thống chạy thời gian dài hoặc chịu tải cao (ví dụ: crawl data, gọi microservices liên tục), ứng dụng của bạn sẽ cạn kiệt tài nguyên file descriptor hoặc socket, dẫn đến lỗi hệ thống sập toàn tập.

  • Giải pháp: Luôn luôn đặt defer resp.Body.Close() ngay sau khi check lỗi HTTP call thành công. Lệnh defer đảm bảo Body sẽ được đóng lại trước khi hàm kết thúc, bất kể logic bên dưới xảy ra lỗi hay rẽ nhánh ra sao.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí