新技巧: 用/dev/tcp实现HTTP请求测试


除了curl实现Http请求,还可以通过 /dev/tcp 进行 HTTP 请求,如果不想将curl安装在一个只运行简单健康检查过程的sidecar容器中。这东西派上用场了

来自rednafi分享:
今天,我学到了 Bash 的一个小技巧,那就是可以使用 /dev/tcp 文件描述符发出原始 HTTP 请求,而无需使用 curl 或 wget 等工具。 这在编写一个需要向服务发出 TCP 请求的健康检查脚本时派上了用场。

下面的脚本打开了一个 TCP 连接,并向 example.com 发送了一个简单的 GET 请求:

#!/bin/bash

# 在 80 端口上打开与 example.com 的 TCP 连接,并分配文件描述符 3
# 执行命令会在整个脚本生命周期内保持 /dev/fd/3 打开
# 3<> 启用双向读写器
exec 3<>/dev/tcp/example.com/80

# 向服务器发送 HTTP GET 请求
# >& 将 stdout 重定向到 /dev/fd/3
echo -e "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n" >&3

# 读取并打印服务器的响应
# <& 将 /dev/fd/3 的输出重定向到 cat
cat <&3

#关闭文件描述符,终止 TCP 连接
exec 3>&-

运行该脚本将在控制台中打印来自网站的响应。

该代码段首先打开了与 example.com 80 端口的 TCP 连接,并指定文件描述符 3 来管理该连接。 执行程序确保文件描述符 3 在脚本运行期间保持打开状态,这样就可以进行多次读写操作,而无需每次都重新打开连接。

使用文件描述符可以使代码更简洁。 如果没有文件描述符,每次读写操作我们都需要将输入和输出直接重定向到 /dev/tcp/example.com/80,这会使脚本变得更加繁琐和难以阅读。

然后,我们向服务器发送 HTTP GET 请求,并将请求呼应到文件描述符 3。

我们使用 cat <&3 读取并打印服务器的响应,它从文件描述符中读取并将输出打印到控制台。

最后,脚本使用 exec 3>&- 终止文件描述符 3,从而关闭连接。

这是 Bash 特有的技巧,在 Zsh 或 Fish 等其他 shell 中无法使用。 它还允许你以同样的方式打开 UDP 连接。 Bash manpage 是这样解释其用法的:

/dev/tcp/host/port
  如果 host 是有效的主机名或互联网地址,port
是整数端口号或服务名,则 bash 会尝试
打开相应的 TCP 套接字。

/dev/udp/host/port
   如果 host 是有效的主机名或互联网地址,port
是整数端口号或服务名,则 bash 会尝试
打开相应的 UDP 套接字。

我用它编写了下面这个健康检查脚本。 我不想把 curl 安装在一个只运行一个健康检查进程的 sidecar 容器中,这样可以让事情更简单。

#!/bin/bash

#启用 bash 严格模式
set -euo pipefail

# Constants
readonly HOST="example.com"
readonly PORT=80
readonly HEALTH_PATH=
"/"

# 打开与指定主机和端口的 TCP 连接
exec 3<>
"/dev/tcp/${HOST}/${PORT}"

# Send the HTTP GET request to the server
echo -e \
   
"GET ${HEALTH_PATH} HTTP/1.1\r\nHost: ${HOST}\r\nConnection: close\r\n\r\n" >&3

# Read the HTTP status from the server's response
HTTP_STATUS=
"$(head -n 1 <&3 | awk '{print $2}')"
if [[
"${HTTP_STATUS}" == "200" ]]; then
    echo
"Service is healthy."
    exit 0
else
    echo
"Service is not healthy. HTTP status: ${HTTP_STATUS}"
    exit 1
fi

# Close the file descriptor, terminating the TCP connection
exec 3>&-

脚本向服务发出 GET 请求,并检查原始响应的 HTTP 状态是否为 200。
请注意,如果服务返回的是 301 重定向代码,脚本就会失败。

另外,你需要进行原始文本 HTTP 请求,如果你需要做的事情超出了简单的 GET 调用,这可能会变得很麻烦。 这时,你最好使用 curl。