潘逸安的博客

潘逸安

golang请求http

我来详细介绍一下 Go 中的 HTTP 请求实现:

1. 基本 GET 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

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

func main() {
// 简单的 GET 请求
resp, err := http.Get("https://api.github.com")
if err != nil {
panic(err)
}
defer resp.Body.Close()

body, _ := io.ReadAll(resp.Body)
fmt.Printf("Status: %s\n", resp.Status)
fmt.Printf("Body: %s\n", body[:100])
}

2. 自定义客户端和请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package main

import (
"fmt"
"io"
"net/http"
"net/url"
"time"
"strings"
)

func main() {
// 创建自定义客户端
client := &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
DisableCompression: false,
},
}

// 构建 POST 请求
data := url.Values{}
data.Set("username", "admin")
data.Set("password", "secret")

req, err := http.NewRequest(
"POST",
"https://httpbin.org/post",
strings.NewReader(data.Encode()),
)
if err != nil {
panic(err)
}

// 设置请求头
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("User-Agent", "MyGoClient/1.0")

// 发送请求
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()

// 处理响应
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}

3. 处理 JSON 请求和响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package main

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

// 定义结构体
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}

type Post struct {
ID int `json:"id"`
Title string `json:"title"`
Body string `json:"body"`
UserID int `json:"userId"`
}

func main() {
// 发送 JSON 数据
user := User{
Name: "John Doe",
Email: "john@example.com",
}

jsonData, _ := json.Marshal(user)

req, err := http.NewRequest(
"POST",
"https://jsonplaceholder.typicode.com/users",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}

req.Header.Set("Content-Type", "application/json")

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()

// 解析 JSON 响应
var result User
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
panic(err)
}

fmt.Printf("Created user: %+v\n", result)
}

4. 处理不同类型的请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package main

import (
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
)

func main() {
// GET with query parameters
params := url.Values{}
params.Add("page", "1")
params.Add("limit", "10")

resp, _ := http.Get("https://httpbin.org/get?" + params.Encode())
defer resp.Body.Close()
fmt.Println("GET request completed")

// POST with form data
formData := url.Values{
"key1": {"value1"},
"key2": {"value2"},
}

resp, _ = http.PostForm("https://httpbin.org/post", formData)
defer resp.Body.Close()
fmt.Println("POST form request completed")

// 文件上传
uploadFile("test.txt", "https://httpbin.org/post")
}

func uploadFile(filename string, targetURL string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()

body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", filepath.Base(filename))
if err != nil {
return err
}

io.Copy(part, file)
writer.Close()

req, err := http.NewRequest("POST", targetURL, body)
if err != nil {
return err
}

req.Header.Set("Content-Type", writer.FormDataContentType())

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

fmt.Println("File upload completed")
return nil
}

5. 并发请求和错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package main

import (
"context"
"fmt"
"net/http"
"sync"
"time"
)

func main() {
urls := []string{
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/3",
}

var wg sync.WaitGroup
results := make(chan string, len(urls))

for _, url := range urls {
wg.Add(1)
go func(url string) {
defer wg.Done()
if result, err := fetchWithTimeout(url, 5*time.Second); err != nil {
results <- fmt.Sprintf("Error fetching %s: %v", url, err)
} else {
results <- fmt.Sprintf("Success: %s - %s", url, result)
}
}(url)
}

go func() {
wg.Wait()
close(results)
}()

for result := range results {
fmt.Println(result)
}
}

func fetchWithTimeout(url string, timeout time.Duration) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return "", err
}

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()

return resp.Status, nil
}

6. 高级特性 - 中间件和重试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package main

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

// 重试中间件
func withRetry(maxRetries int, client *http.Client) *http.Client {
client.Transport = &retryTransport{
maxRetries: maxRetries,
transport: http.DefaultTransport,
}
return client
}

type retryTransport struct {
maxRetries int
transport http.RoundTripper
}

func (t *retryTransport) RoundTrip(req *http.Request) (*http.Response, error) {
for i := 0; i <= t.maxRetries; i++ {
resp, err := t.transport.RoundTrip(req)
if err == nil && resp.StatusCode < 500 {
return resp, nil
}

if i < t.maxRetries {
time.Sleep(time.Duration(i*i) * 100 * time.Millisecond)
}
}
return nil, fmt.Errorf("max retries exceeded")
}

func main() {
// 使用重试机制的客户端
client := &http.Client{
Timeout: 30 * time.Second,
}
client = withRetry(3, client)

// 设置代理
// proxyURL, _ := url.Parse("http://proxy:8080")
// client.Transport = &http.Transport{Proxy: http.ProxyURL(proxyURL)}

resp, err := client.Get("https://httpbin.org/status/500")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
defer resp.Body.Close()

fmt.Printf("Final status: %s\n", resp.Status)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

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

func main() {
// 创建 Cookie Jar
jar, _ := cookiejar.New(nil)

client := &http.Client{
Jar: jar,
}

// 第一次请求会设置 Cookie
resp, _ := client.Get("https://httpbin.org/cookies/set?name=value")
defer resp.Body.Close()

// 后续请求会自动携带 Cookie
resp, _ = client.Get("https://httpbin.org/cookies")
defer resp.Body.Close()

fmt.Println("Cookies handled automatically")
}

8. 性能优化建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"net/http"
"time"
)

func createOptimizedClient() *http.Client {
return &http.Client{
Timeout: time.Second * 30,
Transport: &http.Transport{
MaxIdleConns: 100, // 最大空闲连接数
MaxConnsPerHost: 10, // 每个主机最大连接数
MaxIdleConnsPerHost: 10, // 每个主机最大空闲连接数
IdleConnTimeout: 90 * time.Second, // 空闲连接超时时间
TLSHandshakeTimeout: 10 * time.Second, // TLS握手超时
ExpectContinueTimeout: 1 * time.Second,
},
}
}

最佳实践

  1. 总是关闭响应体:使用 defer resp.Body.Close()
  2. 设置超时:避免请求阻塞
  3. 使用 context:支持取消和超时
  4. 重用 Client:避免为每个请求创建新客户端
  5. 处理错误:检查所有可能返回错误的操作
  6. 限制并发:避免过多并发请求
  7. 启用连接池:提高性能

这些示例涵盖了 Go 中 HTTP 请求的常见用法,你可以根据具体需求进行调整和扩展。