Curl 命令
今天我们来看一下与 curl 相关的知识,学习的原因是因为最近在做接口开发,需要了解一下 curl 相关的参数
curl 是一个命令行工具,可以通过 URL,向服务器发送请求并获取数据,常用于测试 HTTP/HTTPS 接口、排查网络问题、调试 Web 服务等...
运行程序
还是使用 Go 写一份程序来进行学习,当然看不懂也没关系因为是 AI 写的
package main
import (
"compress/gzip"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"log"
"math/big"
"mime"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
const (
maxBodyBytes = 1 << 20 // 1 MiB
maxUploadBytes = 10 << 20 // 10 MiB
defaultHTTP = ":8080"
defaultHTTPS = ":8443"
)
type basicAuthInfo struct {
Provided bool `json:"provided"`
Username string `json:"username,omitempty"`
PasswordLength int `json:"password_length,omitempty"`
}
type requestInfo struct {
Method string `json:"method"`
Path string `json:"path"`
Host string `json:"host"`
Query url.Values `json:"query,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Cookies map[string]string `json:"cookies,omitempty"`
Body string `json:"body,omitempty"`
JSONBody interface{} `json:"json,omitempty"`
Form url.Values `json:"form,omitempty"`
BasicAuth *basicAuthInfo `json:"basic_auth,omitempty"`
}
type uploadedFile struct {
Field string `json:"field"`
Filename string `json:"filename"`
Size int64 `json:"size"`
ContentType string `json:"content_type"`
SHA256 string `json:"sha256"`
}
func main() {
mux := http.NewServeMux()
registerRoutes(mux)
handler := requestLogMiddleware(commonHeadersMiddleware(mux))
httpAddress := envOrDefault("HTTP_ADDR", defaultHTTP)
httpsAddress := envOrDefault("HTTPS_ADDR", defaultHTTPS)
httpListener, err := net.Listen("tcp", httpAddress)
if err != nil {
log.Fatalf("HTTP listen %s failed: %v", httpAddress, err)
}
httpServer := newServer(httpAddress, handler)
errCh := make(chan error, 2)
if os.Getenv("DISABLE_HTTPS") != "1" {
certificate, certErr := newSelfSignedCertificate()
if certErr != nil {
log.Fatalf("generate HTTPS certificate: %v", certErr)
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{certificate},
MinVersion: tls.VersionTLS12,
}
httpsListener, listenErr := tls.Listen("tcp", httpsAddress, tlsConfig)
if listenErr != nil {
log.Fatalf("HTTPS listen %s failed: %v", httpsAddress, listenErr)
}
httpsServer := newServer(httpsAddress, handler)
log.Printf(
"HTTPS curl lab listening on %s (self-signed certificate)",
httpsAddress,
)
go func() {
errCh <- httpsServer.Serve(httpsListener)
}()
}
log.Printf("HTTP curl lab listening on %s", httpAddress)
log.Printf(
"Try: curl -i http://127.0.0.1%s/",
displayPort(httpAddress),
)
go func() {
errCh <- httpServer.Serve(httpListener)
}()
log.Fatal(<-errCh)
}
func newServer(address string, handler http.Handler) *http.Server {
return &http.Server{
Addr: address,
Handler: handler,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 15 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
}
}
func registerRoutes(mux *http.ServeMux) {
mux.HandleFunc("/help", helpHandler)
mux.HandleFunc("/echo", echoHandler)
mux.HandleFunc("/target", echoHandler)
mux.HandleFunc("/upload", uploadHandler)
mux.HandleFunc("/auth/basic", basicAuthHandler)
mux.HandleFunc("/cookies/set", setCookieHandler)
mux.HandleFunc("/cookies", echoHandler)
mux.HandleFunc("/delay", delayHandler)
mux.HandleFunc("/gzip", gzipHandler)
mux.HandleFunc("/files/test.txt", downloadHandler)
mux.HandleFunc("/206", rangeHandler)
mux.HandleFunc("/304", cacheHandler)
mux.HandleFunc("/status/", dynamicStatusHandler)
for _, status := range []int{
http.StatusOK,
http.StatusNoContent,
http.StatusBadRequest,
http.StatusUnauthorized,
http.StatusPaymentRequired,
http.StatusForbidden,
http.StatusNotFound,
http.StatusTeapot,
http.StatusTooManyRequests,
http.StatusInternalServerError,
http.StatusNotImplemented,
http.StatusBadGateway,
http.StatusServiceUnavailable,
http.StatusGatewayTimeout,
} {
code := status
mux.HandleFunc(
fmt.Sprintf("/%d", code),
func(w http.ResponseWriter, r *http.Request) {
serveStatus(w, code)
},
)
}
for _, status := range []int{
http.StatusMovedPermanently,
http.StatusFound,
http.StatusSeeOther,
http.StatusTemporaryRedirect,
http.StatusPermanentRedirect,
} {
code := status
mux.HandleFunc(
fmt.Sprintf("/%d", code),
func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/target", code)
},
)
}
// Catch-all route.
//
// 这样 /api、/users、/login、/search 等任意路径
// 都可以直接用于 curl 请求实验。
mux.HandleFunc("/", echoHandler)
}
func echoHandler(w http.ResponseWriter, r *http.Request) {
request, err := inspectRequest(w, r)
if err != nil {
writeProblem(
w,
http.StatusRequestEntityTooLarge,
err.Error(),
)
return
}
writeJSON(w, http.StatusOK, request)
}
func inspectRequest(
w http.ResponseWriter,
r *http.Request,
) (*requestInfo, error) {
body, err := readBody(w, r, maxBodyBytes)
if err != nil {
return nil, fmt.Errorf(
"request body exceeds the 1 MiB echo limit",
)
}
info := &requestInfo{
Method: r.Method,
Path: r.URL.Path,
Host: r.Host,
Query: r.URL.Query(),
Headers: safeHeaders(r.Header),
Body: string(body),
}
if len(r.Cookies()) > 0 {
info.Cookies = make(map[string]string)
for _, cookie := range r.Cookies() {
info.Cookies[cookie.Name] = cookie.Value
}
}
if username, password, ok := r.BasicAuth(); ok {
info.BasicAuth = &basicAuthInfo{
Provided: true,
Username: username,
PasswordLength: len(password),
}
}
mediaType, _, _ := mime.ParseMediaType(
r.Header.Get("Content-Type"),
)
switch mediaType {
case "application/json":
var value interface{}
if len(body) > 0 && json.Unmarshal(body, &value) == nil {
info.JSONBody = value
info.Body = ""
}
case "application/x-www-form-urlencoded":
form, parseErr := url.ParseQuery(string(body))
if parseErr == nil {
info.Form = form
info.Body = ""
}
}
return info, nil
}
func uploadHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.Header().Set("Allow", http.MethodPost)
writeProblem(
w,
http.StatusMethodNotAllowed,
"use POST with curl -F 'file=@test.txt'",
)
return
}
r.Body = http.MaxBytesReader(
w,
r.Body,
maxUploadBytes,
)
if err := r.ParseMultipartForm(2 << 20); err != nil {
writeProblem(
w,
http.StatusBadRequest,
"expected multipart/form-data; upload limit is 10 MiB",
)
return
}
if r.MultipartForm != nil {
defer r.MultipartForm.RemoveAll()
}
files := make([]uploadedFile, 0)
for field, fileHeaders := range r.MultipartForm.File {
for _, fileHeader := range fileHeaders {
file, err := fileHeader.Open()
if err != nil {
writeProblem(
w,
http.StatusBadRequest,
"cannot read uploaded file",
)
return
}
hash := sha256.New()
_, copyErr := io.Copy(hash, file)
file.Close()
if copyErr != nil {
writeProblem(
w,
http.StatusBadRequest,
"cannot hash uploaded file",
)
return
}
files = append(files, uploadedFile{
Field: field,
Filename: filepath.Base(fileHeader.Filename),
Size: fileHeader.Size,
ContentType: fileHeader.Header.Get("Content-Type"),
SHA256: hex.EncodeToString(hash.Sum(nil)),
})
}
}
writeJSON(
w,
http.StatusOK,
map[string]interface{}{
"message": "文件上传成功。",
"fields": r.MultipartForm.Value,
"files": files,
},
)
}
func basicAuthHandler(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok {
w.Header().Set(
"WWW-Authenticate",
`Basic realm="Curl Lab"`,
)
writeProblem(
w,
http.StatusUnauthorized,
"try: curl -u ray:password http://127.0.0.1:8080/auth/basic",
)
return
}
writeJSON(
w,
http.StatusOK,
map[string]interface{}{
"message": "Basic Auth 凭据已收到(密码不会回显)。",
"username": username,
"password_length": len(password),
},
)
}
func setCookieHandler(w http.ResponseWriter, r *http.Request) {
http.SetCookie(
w,
&http.Cookie{
Name: "curl_demo",
Value: "cookie_received",
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
},
)
writeJSON(
w,
http.StatusOK,
map[string]string{
"message": "cookie 已通过 Set-Cookie 响应头返回;可用 curl -c cookies.txt 保存。",
},
)
}
func delayHandler(w http.ResponseWriter, r *http.Request) {
seconds := 3.0
if raw := r.URL.Query().Get("seconds"); raw != "" {
parsed, err := strconv.ParseFloat(raw, 64)
if err != nil || parsed < 0 || parsed > 15 {
writeProblem(
w,
http.StatusBadRequest,
"seconds must be a number between 0 and 15",
)
return
}
seconds = parsed
}
delay := time.Duration(
seconds * float64(time.Second),
)
select {
case <-time.After(delay):
writeJSON(
w,
http.StatusOK,
map[string]interface{}{
"message": "延迟响应完成。",
"delay_seconds": seconds,
},
)
case <-r.Context().Done():
log.Printf(
"client canceled delayed request: %v",
r.Context().Err(),
)
}
}
func downloadHandler(w http.ResponseWriter, r *http.Request) {
content := `This file came from the Curl learning server.
Try curl -O http://127.0.0.1:8080/files/test.txt
`
w.Header().Set(
"Content-Disposition",
`attachment; filename="test.txt"`,
)
w.Header().Set(
"Content-Type",
"text/plain; charset=utf-8",
)
http.ServeContent(
w,
r,
"test.txt",
time.Date(
2026,
9,
2,
0,
0,
0,
0,
time.UTC,
),
strings.NewReader(content),
)
}
func rangeHandler(w http.ResponseWriter, r *http.Request) {
content := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"
w.Header().Set(
"Content-Type",
"text/plain; charset=utf-8",
)
w.Header().Set(
"Accept-Ranges",
"bytes",
)
http.ServeContent(
w,
r,
"range-demo.txt",
time.Date(
2026,
9,
2,
0,
0,
0,
0,
time.UTC,
),
strings.NewReader(content),
)
}
func cacheHandler(w http.ResponseWriter, r *http.Request) {
const etag = `"curl-lab-v1"`
w.Header().Set("ETag", etag)
w.Header().Set(
"Cache-Control",
"private, max-age=60",
)
if r.Header.Get("If-None-Match") == etag {
w.WriteHeader(http.StatusNotModified)
return
}
writeJSON(
w,
http.StatusOK,
map[string]string{
"message": `再次请求时添加 If-None-Match: "curl-lab-v1",即可看到 304。`,
},
)
}
func gzipHandler(w http.ResponseWriter, r *http.Request) {
message := strings.Repeat(
"curl compression demo; ",
20,
) + "\n"
w.Header().Set(
"Content-Type",
"text/plain; charset=utf-8",
)
w.Header().Set(
"Vary",
"Accept-Encoding",
)
if !strings.Contains(
r.Header.Get("Accept-Encoding"),
"gzip",
) {
fmt.Fprint(w, message)
return
}
w.Header().Set(
"Content-Encoding",
"gzip",
)
compressor := gzip.NewWriter(w)
defer compressor.Close()
fmt.Fprint(compressor, message)
}
func dynamicStatusHandler(
w http.ResponseWriter,
r *http.Request,
) {
raw := strings.TrimPrefix(
r.URL.Path,
"/status/",
)
code, err := strconv.Atoi(raw)
if err != nil || code < 200 || code > 599 {
writeProblem(
w,
http.StatusBadRequest,
"use /status/{code}, where code is between 200 and 599",
)
return
}
serveStatus(w, code)
}
func serveStatus(w http.ResponseWriter, code int) {
switch code {
case http.StatusUnauthorized:
w.Header().Set(
"WWW-Authenticate",
`Basic realm="Curl Lab"`,
)
case http.StatusTooManyRequests:
w.Header().Set(
"Retry-After",
"10",
)
case http.StatusServiceUnavailable:
w.Header().Set(
"Retry-After",
"60",
)
}
if code == http.StatusNoContent ||
code == http.StatusNotModified {
w.WriteHeader(code)
return
}
statusText := http.StatusText(code)
if statusText == "" {
statusText = "Custom Status"
}
writeJSON(
w,
code,
map[string]interface{}{
"status": code,
"message": statusText,
},
)
}
func helpHandler(w http.ResponseWriter, r *http.Request) {
writeJSON(
w,
http.StatusOK,
map[string]interface{}{
"message": "Curl 学习服务器端点",
"endpoints": map[string]string{
"any path": "回显 method、URL、query、headers、body、form、JSON 和 Basic Auth 信息",
"/help":
"查看本帮助",
"/301 /302 /303 /307 /308":
"重定向到 /target,用 -L 观察 method 是否变化",
"/200 /204 /400 ...":
"返回对应状态码,也可使用 /status/{code}",
"/206":
"配合 -H 'Range: bytes=0-9' 观察 206",
"/304":
`配合 -H 'If-None-Match: "curl-lab-v1"' 观察缓存`,
"/upload":
"配合 -F 'file=@test.txt' 上传文件",
"/auth/basic":
"配合 -u username:password 发送 Basic Auth",
"/cookies/set":
"设置 cookie;/cookies 用于观察客户端发回的 cookie",
"/delay?seconds=3":
"延迟响应,用于 --max-time 练习",
"/files/test.txt":
"配合 -o 或 -O 下载文件",
"/gzip":
"配合 --compressed 观察 gzip",
"https://127.0.0.1:8443":
"自签名 HTTPS,配合 -k 使用",
},
},
)
}
func readBody(
w http.ResponseWriter,
r *http.Request,
limit int64,
) ([]byte, error) {
if r.Body == nil || r.Body == http.NoBody {
return nil, nil
}
r.Body = http.MaxBytesReader(
w,
r.Body,
limit,
)
defer r.Body.Close()
return io.ReadAll(r.Body)
}
func safeHeaders(headers http.Header) map[string]string {
result := make(
map[string]string,
len(headers),
)
for key, values := range headers {
canonical := http.CanonicalHeaderKey(key)
switch canonical {
case "Authorization", "Proxy-Authorization":
result[canonical] = "<redacted>"
case "Cookie":
result[canonical] = "<redacted>"
default:
result[canonical] = strings.Join(values, ", ")
}
}
return result
}
func commonHeadersMiddleware(
next http.Handler,
) http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(
"X-Curl-Lab",
"true",
)
w.Header().Set(
"X-Request-Method",
r.Method,
)
next.ServeHTTP(w, r)
},
)
}
func requestLogMiddleware(
next http.Handler,
) http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
started := time.Now()
next.ServeHTTP(w, r)
log.Printf(
"%s %s host=%q remote=%s duration=%s",
r.Method,
r.URL.RequestURI(),
r.Host,
r.RemoteAddr,
time.Since(started).Round(time.Millisecond),
)
},
)
}
func writeJSON(
w http.ResponseWriter,
status int,
value interface{},
) {
w.Header().Set(
"Content-Type",
"application/json; charset=utf-8",
)
w.WriteHeader(status)
encoder := json.NewEncoder(w)
encoder.SetEscapeHTML(false)
encoder.SetIndent("", " ")
if err := encoder.Encode(value); err != nil {
log.Printf(
"encode response: %v",
err,
)
}
}
func writeProblem(
w http.ResponseWriter,
status int,
message string,
) {
writeJSON(
w,
status,
map[string]interface{}{
"status": status,
"error": http.StatusText(status),
"message": message,
},
)
}
func envOrDefault(
name string,
fallback string,
) string {
if value := strings.TrimSpace(
os.Getenv(name),
); value != "" {
return value
}
return fallback
}
func displayPort(address string) string {
if _, port, err := net.SplitHostPort(address); err == nil {
return ":" + port
}
if strings.HasPrefix(address, ":") {
return address
}
return ":8080"
}
func newSelfSignedCertificate() (
tls.Certificate,
error,
) {
privateKey, err := rsa.GenerateKey(
rand.Reader,
2048,
)
if err != nil {
return tls.Certificate{}, err
}
serialLimit := new(big.Int).Lsh(
big.NewInt(1),
128,
)
serialNumber, err := rand.Int(
rand.Reader,
serialLimit,
)
if err != nil {
return tls.Certificate{}, err
}
now := time.Now()
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "Curl Learning Server",
Organization: []string{
"Local Curl Lab",
},
},
NotBefore: now.Add(-time.Hour),
NotAfter: now.Add(24 * time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment |
x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
},
BasicConstraintsValid: true,
DNSNames: []string{
"localhost",
},
IPAddresses: []net.IP{
net.ParseIP("127.0.0.1"),
net.ParseIP("::1"),
},
}
certificateDER, err := x509.CreateCertificate(
rand.Reader,
&template,
&template,
&privateKey.PublicKey,
privateKey,
)
if err != nil {
return tls.Certificate{}, err
}
certificatePEM := pem.EncodeToMemory(
&pem.Block{
Type: "CERTIFICATE",
Bytes: certificateDER,
},
)
privateKeyPEM := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(
privateKey,
),
},
)
return tls.X509KeyPair(
certificatePEM,
privateKeyPEM,
)
}
1. 最基本的 GET 请求
curl http://127.0.0.1:8080/echo
意思就是,像指定的 URL 发送 GET 请求,服务器默认只输出响应体

2. 查看 HTTP 响应头
curl -i http://127.0.0.1:8080/echo
- -i 参数是 include 的意思,表示同时显示 响应头 + 响应体
可以看到,输出包含了 响应头 与 响应体,但仍然是 GET 请求
# 3. 只查看响应头
bash curl -I http://127.0.0.1:8080/echo - -I 参数表示 只发送 HEAD 请求
可以看到,服务器只返回响应头,不返回响应体
# 4. 查看完整请求过程
bash curl -v http://127.0.0.1:8080/echo - -v 参数是 verbose 的意思,会显示 连接过程、请求头、响应头、连接复用或关闭
其中 > 表示 curl 发出的 HTTP 数据;< 表示服务器返回的数据
# 5. 指定 HTTP 方法 GET
bash curl -X GET http://127.0.0.1:8080/echoPOSTbash curl -X POST http://127.0.0.1:8080/echoPUTbash curl -X PUT http://127.0.0.1:8080/echoDELETEbash curl -X DELETE http://127.0.0.1:8080/echo - -X 参数用于指定 HTTP 请求方法
这里就不继续展示了,因为响应基本都差不多,这里程序没有特别要求
很多时候不需要显示写 -X GET,因为直接 curl 默认使用的 HTTP 方法就是 GET # 6. 添加请求头
bash curl -H 'Accept: application/json' http://127.0.0.1:8080/echo添加自定义 Headerbash curl -H 'X-Test: hello' http://127.0.0.1:8080/echo - -H 参数用于添加请求头,比如请求头的格式、Token等...
当然有多个 Header 可以写多个 -H
bash curl -H 'Accept: application/json' -H 'X-Test: hello' -H 'X-Request-ID: 123456' http://127.0.0.1:8080/echo # 7. Content-Type Content-Type 的意思是发送数据时,需要告诉服务器 请求体是什么格式,比如
- application/json 发送的是 JSON 格式的数据
- application/x-www-form-urlencoded 发送的是表单数据,使用 URL 编码格式( key1=value1&key2=value2 形式)
- multipart/form-data 发送的是多部分表单数据,用于文件上传 或 混合文本 + 文件 ## 使用 -d 发送数据
- -d 参数是 --data 的意思,也就是发送数据;使用 -d 会默认使用 POST
## 发送表单数据
bash curl -d 'username=ray&password=123456' http://127.0.0.1:8080/echo通过响应体我们可以看到 HTTP 方法、响应头、响应体,以及可以看到数据格式,因为我们使用的是 URL 编码格式的数据,所以这里的输出就是传统 HTML 表单数据;当然这里是我对输出进行了清洗,原本输出可能不长这样,但是意思还是那么个意思 ## JSON 数据
bash curl -H 'Content-Type: application/json' -d '{"username":"ray","password":"123456"}' http://127.0.0.1:8080/echo可以看到,响应体是 json 的格式 # 8. GET Query 参数 ## 普通写法
bash curl 'http://127.0.0.1:8080/echo?page=1&size=20'通过响应体,我们可以看到,Query 的参数位于?后面,以及多个参数可以通过&连接 ## 使用 -G 构造 Query 当参数复杂时,可以使用 —G 参数进行请求
- -G 参数的意思是 把 -d、--data-urlencode 等数据放到 URL Query 中;而不是放到请求体中
bash curl -G 'http://127.0.0.1:8080/users' --data-urlencode 'page=1' --data-urlencode 'keyword=hello world' 而使用普通写法的话,就是以下的写法;20% 是 空格字符编码
bash curl 'http://127.0.0.1:8080/users?page=1&keyword=hello%20world'# 9. 自动 URL 编码 - --data-urlencode 参数的意思是 自动对数据进行 URL 编码后再发送;因为 curl 会自动处理空格和特殊字符,这样会比手动拼 URL 更安全
bash curl -G 'http://127.0.0.1:8080/search' --data-urlencode 'keyword=hello world' # 11. 静默模式
- -s 参数的意思是 silent mode,意思是不显示进度条和错误信息
- -S 参数的意思是 显示错误,也就是即使是静默模式,出错了也要显示错误信息 使用是通常是 -sS 一起,一起来看看区别吧 ```bash curl -s http://127.0.0.1:9999/echo
curl -sS http://127.0.0.1:9999/echo
可以看到,使用 -s 时,即使发生错误了,也不会有错误信息输出

# 12. 重定向
这个我们很熟悉了,HTTP 响应是 302 ,而自动跟随重定向的参数是 -L
- -L 参数表示的意思是 自动跟随重定向到指定的 URL 中
这里我们使用 -i 查看完整请求过程,使用 -L 进行重定向
bash curl -i -L http://127.0.0.1:8080/302
可以看到,响应头从 302 -> 200

# 13. 代理
## 系统代理
在使用 curl 的时候,会读取部分代理环境变量;当然并不只有以下代理
- HTTP_PROXY
- HTTPS_PROXY
- NO_PROXY
查看当前代理
bash env | grep -i proxy
这是我虚拟机中的代理,走的是主机的梯子;所以在 curl 请求某些地址时可能会经过代理 导致出现一些问题

## 绕过代理
这里也一并学习超时吧
bash curl --noproxy '*' -sS -i --connect-timeout 5 --max-time 10 https://google.com
- `*` 参数表示 所有地址都不使用代理
- --connect-timeout 参数表示 TCP 建立连接最多等待几秒,这里就是 5s 的意思
- --max-time 参数表示 限制整个 curl 请求的时间,这里就是 curl 这个命令最多等 10s
可以看到,超时了,因为没走代理

## 指定代理
bash curl -x http://127.0.0.1:7890 https://example.com
curl --proxy http://127.0.0.1:7890 https://example.com
- -x 参数就是指定代理进行访问,也可以使用 --porxy 参数,一样的效果;这里的 `www.google.com` 与 `goole.com` 其实都是可以的,后者会响应 301 永久重定向,加上 -L 参数跟随即可

# 14. 设置 Host
在 HTTP 请求中包含 Host
bash curl -H 'Host: web.example.com' http://127.0.0.1:8080/echo
这个请求可能并不规范,但表达的意思是相同的,连接 127.0.0.1 这个 IP,然后请求这个 IP 上的 web.example.com 站点

如果一个 IP 通过 Nginx 或者使用 k8s 进行 IP 代理后,可能会出现,一个 IP 存在多个战点,这个时候就需要加 Host 用于访问指定站点
# 15. Basic Auth
bash curl -i -u ray:password http://127.0.0.1:8080/auth/basic
- -u 参数用于发送 HTTP Basic Authentication,格式为 username:password
通过添加 -i 参数观察响应头和响应体,可以看到发送了 Authorization: Basic ...

# 16. Cookie
cookie 是服务器让浏览器保存的一小段数据,之后浏览器访问同一个网站时,会自动把这段数据带回去;人话就是,一个登陆缓存,保存你登陆的证据
bash curl -i http://127.0.0.1:8080/cookies/set
可以看到,响应头中有 Set-Cookie

使用 -c 参数,对 cookie 进行保存、然后使用 -b 参数,读取 Cookie 并发送给服务器
bash curl -c cookies.txt http://127.0.0.1:8080/cookies/set
curl -b cookies.txt http://127.0.0.1:8080/cookies
在日常中,我们使用的 cookie 一般来自于浏览器登录时产生的,可以使用 F12 进行查看

# 17. 耗时
bash time curl -s http://localhost:8080/echo
这个属于 Linux、Mac 自带的 time 命令,其实不能算是 curl 命令中的,但也很常见,会输出三个耗时
- real 实际耗时
- user 用户态 CPU 时间
- sys 内核态 CPU 时间

# 18. 一些常见的参数
| 参数 | 作用 |
| ------------------- | ----------------- |
| `-i` | 显示响应头和响应体 |
| `-I` | 发送 HEAD 请求 |
| `-v` | 显示详细请求和连接过程 |
| `-s` | 静默模式 |
| `-S` | 配合 `-s`,失败时显示错误 |
| `-sS` | Shell 脚本常用组合 |
| `-X` | 指定 HTTP Method |
| `-H` | 添加 HTTP Header |
| `-d` | 发送请求数据 |
| `-G` | 将数据放入 URL Query |
| `--data-urlencode` | 对参数进行 URL 编码 |
| `-L` | 跟随 HTTP 重定向 |
| `-u` | Basic Auth |
| `-F` | multipart 表单或文件上传 |
| `-o` | 保存为指定文件名 |
| `-O` | 使用远程文件名保存 |
| `-k` | 忽略 HTTPS 证书校验 |
| `-x` | 指定代理 |
| `--proxy` | 指定代理 |
| `--noproxy` | 指定不经过代理的地址 |
| `--connect-timeout` | 设置连接阶段超时 |
| `--max-time` | 设置整个请求最大时间 |
| `-w` | 输出状态码、耗时等请求信息 |
| `-c` | 保存 Cookie |
| `-b` | 发送 Cookie |
| `--compressed` | 请求压缩响应并自动解压 |
# 总结
到这里,curl 命令常见的就讲解完了,当然是否真的是常见的,也不一定,因为我也会遗漏很多的信息
所以,我常说的,有印象就足够了
愿诸君顺遂