无 curl 容器中,用 bash /dev/tcp 发起 HTTP 请求的方法与注意事项
用 bash /dev/tcp 发起 HTTP 请求的背景
2026 年 6 月 16 日,有人需要检查一个容器能否通过 Docker 内部网络访问另一个容器,即对共享网络上的服务执行一个简单的 `GET /health` 请求。最直接的做法是使用 `curl http://service:8642/health`。但这个应用镜像经过了深度精简,既没有 `curl` 也没有 `wget`,也没有其他可以用来打开套接字的工具。
bash 手动编写小型 HTTP 请求的方法
实际上,`bash` 可以打开 TCP 套接字,能手动编写一个小型 HTTP 请求。打开与主机和端口的连接并写入请求,只需要使用现有的 shell 即可:
exec 3<>/dev/tcp/service/8642 printf 'GET /health HTTP/1.1\r\nHost: service\r\nConnection: close\r\n\r\n' >&3 cat <&3这里的 `service` 是要访问的主机名。它必须能够解析,并且从运行此命令的地方可以访问,所以需要先进行设置,比如在配置的 Docker 网络中的容器或服务名称,或者任何可解析的 DNS 名称。也可以替换为自己的主机和端口。
添加头部信息的请求示例
上述代码会打印出完整的响应,包括状态行、头部信息、空行和响应体。如果要添加一个头部信息,比如 `Authorization: Bearer` 令牌,可以在请求结束的空行之前添加另一个以 `\r\n` 结尾的行:
exec 3<>/dev/tcp/service/8642 printf 'GET /v1/models HTTP/1.1\r\nHost: service\r\nAuthorization: Bearer %s\r\nConnection: close\r\n\r\n' "$API_KEY" >&3 cat <&3/dev/tcp 的原理与特点
第一次使用时遇到的问题是,`/dev/tcp` 并不是一个真正的设备文件。磁盘上并没有这样的路径,`ls /dev/tcp` 不会找到任何内容,从另一个 shell 执行 `cat /dev/tcp/...` 会报错。它是 `bash` 内部处理的一种重定向。根据 [Bash 手册](https://www.gnu.org/software/bash/manual/bash.html#Redirections):“`/dev/tcp/host/port` - 如果 `host` 是有效的主机名或互联网地址,`port` 是整数端口号或服务名称,bash 会尝试打开相应的 TCP 套接字。”选择这些名称是因为([参考](https://www.gnu.org/software/bash/manual/bash.html#Redirections))没有真正的 Unix 系统有 `/dev/tcp` 或 `/dev/udp` 层级结构,所以不会产生冲突。Bash 会为进行 DNS 查找和 `connect(2)` 操作,`exec 3<>` 会为套接字分配一个文件描述符(`3`),可以像操作其他文件一样对其进行读写。
使用 bash /dev/tcp 发起请求的注意事项
有几点需要注意:
- 这不是一个真正的 HTTP 客户端。它不能正确解析 HTTP,也不能处理重定向、分块响应、压缩、重试、TLS 或 `curl` 能默默处理的其他功能。这只是一个快速检查连接性和调试的技巧。
- `Connection: close` 头部信息很重要。如果没有它,服务器在响应后会保持连接打开,这是 HTTP/1.1 的默认行为,`cat <&3` 会一直等待永远不会到来的字节。要求服务器关闭连接意味着 `cat` 会到达文件末尾并返回。使用 `timeout 6 bash -c '...'` 可以应对各种情况。
- 不支持 TLS。`/dev/tcp` 打开的是一个原始套接字,所以这只适用于纯文本 HTTP。对于 `https`,需要使用 `openssl s_client`,到那时可能还是使用合适的工具更好。
- 这是 `bash` 的特性,不是 POSIX 标准。`dash`(Debian 的 `/bin/sh`)和 `zsh` 没有这个功能,所以 `#!/bin/sh` 脚本不能使用它。需要直接调用 `bash`。
- 这是一个编译时选项,在使用 `--enable-net-redirections` 构建 `bash` 时会启用。大多数主流版本都会启用它,在基于 Debian 的镜像中使用时没有任何问题,但 Debian 曾经有几年默认禁用了这个功能([参考](https://bugs.launchpad.net/ubuntu/+source/bash/+bug/215034)),所以在旧系统或非常精简的系统上,最好先检查一下。
总结与适用场景
在日常工作中,`curl` 仍然是首选工具。但在有意精简、无法安装任何软件的容器中,这种方法可以在不添加软件包的情况下快速完成检查。这种方法真的能满足所有需求吗?
