前言

最近在 弄环境的时候出现了这样的一件事情 

同一个 docker 网络下面有 两个容器, 容器A, 容器B 

容器B 依赖于 容器A, 容器A 开放了一个端口 1234 给其他服务 

然后 容器B 通过 容器A的ip能够访问到 容器A:1234 的服务, 但是通过 容器名称:1234 访问不到服务, 报的是一个 400 吧 ?

当然 最后发现, 这个问题 并不是 docker 容器的问题, 而是 应用服务器上面的校验的问题, 又或者说 http 协议约定如此(但是找了一下规范, 似乎是没得关于 host 的具体的约束, 可能是没找对吧, 或者怎么的), 不过 刚好我踩到雷区了而已 

呵呵 这里就 特此记录一下 

 

 

环境信息 : jdk1.8 + Apache Tomcat/9.0.35

 

 

现象如下

复现 我们就直接使用 普通的服务器了, 不使用 docker 了, 我这里只是在 docker 容器上面碰到这个问题而已 

服务开在 本地, 1234 端口, 我们这里测试 使用域名 "config_server", "config-server"

master:~ jerry$ ping config_server
PING config_server (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.039 ms
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.099 ms
^C
--- config_server ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.039/0.069/0.099/0.030 ms


master:~ jerry$ ping config-server
PING config-server (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.046 ms
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.096 ms
^C
--- config-server ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.046/0.071/0.096/0.025 ms

 

使用 ip + port 访问如下 

60 通过容器名称访问不到服务 但是通过ip能够访问服务_docker

 

使用 "config_server" + port 访问如下 

60 通过容器名称访问不到服务 但是通过ip能够访问服务_docker_02

 

使用 "config-server" + port 访问如下 

60 通过容器名称访问不到服务 但是通过ip能够访问服务_tomcat_03

 

 

第一次错误请求打印日志如下, 后面的错误请求 "没有打印日志" 

Note: further occurrences of request parsing errors will be logged at DEBUG level.

java.lang.IllegalArgumentException: The character [_] is never valid in a domain name.
	at org.apache.tomcat.util.http.parser.HttpParser$DomainParseState.next(HttpParser.java:974) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.tomcat.util.http.parser.HttpParser.readHostDomainName(HttpParser.java:870) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.tomcat.util.http.parser.Host.parse(Host.java:71) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.tomcat.util.http.parser.Host.parse(Host.java:45) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.coyote.AbstractProcessor.parseHost(AbstractProcessor.java:295) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.coyote.http11.Http11Processor.prepareRequest(Http11Processor.java:776) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:349) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_211]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_211]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_211]

 

 

tomcat解析请求头的Host部分

如下图 解析域的时候, 将要读取的数据是 config_server:1234 

60 通过容器名称访问不到服务 但是通过ip能够访问服务_docker_04

 

然后在校验的时候, 发现了一个 不是 "ALPHA"字符, 不是 "NUMERIC", 不是 ".", 不是 ":", 不是 "-" 的字符 "_" 

校验抛出了异常 

60 通过容器名称访问不到服务 但是通过ip能够访问服务_java_05

 

我们再来看一下 "ALPHA", "NUMERIC" 的定义 

60 通过容器名称访问不到服务 但是通过ip能够访问服务_docker_06

 

 

tomcat有错误日志, 但是只出现了一次

另外还有一件事情就是, 在问题解决的最后, 发现了一件事情, 对于 这一次 400 的请求, tomcat 服务器 是打印了日志的 

但是 后面的 这种 400 请求就没有再打印日志了 

呵呵 我们在这里 也看一下  

发现了吗, 打印日志也有这么多技巧(花样) 

60 通过容器名称访问不到服务 但是通过ip能够访问服务_jar_07

 

config 的配置是一个 组合的配置, "一定的条件"下以 INFO 打印日志, 后面以 DEBUG 打印日志 

60 通过容器名称访问不到服务 但是通过ip能够访问服务_network_08

 

我们再来看一下 logAtInfo 的这个 "一定的条件" 是什么 

哈哈哈 一招奇淫技巧, 记录了一个 lastInfoTime, "第一次"(不完全线程安全) 打印 info 打印, 后面的 返回 false 

这里作者写的注释 也非常有意思, 很棒, 但是 感觉方法名字 不能更好体现出在这个场景下的意义吧 

60 通过容器名称访问不到服务 但是通过ip能够访问服务_jar_09

 

然后 因为我这里默认是 没有启动 DEBUG 级别的日志, 所以 一次 INFO 的异常打印了之后, 后面对于 这个解析参数的问题, 就没有再 打印出日志了  

 

另外看一下 这种日志技巧 还在那里使用了 

在解析 域, cookie, 请求参数的时候, 都是用到了 

60 通过容器名称访问不到服务 但是通过ip能够访问服务_tomcat_10

 

 

对于 ALPHA, NUMERIC, 规范中的介绍 

对于主机名的介绍, 似乎是 没有太细的限定 

refer : http://web-sniffer.net/rfc/rfc2616.html#section-3.2.2

2.2 Basic Rules The following rules are used throughout this specification to describe basic parsing constructs. The US-ASCII coded character set is defined by ANSI X3.4-1986 [21]. OCTET = <any 8-bit sequence of data> CHAR = <any US-ASCII character (octets 0 - 127)> UPALPHA = <any US-ASCII uppercase letter "A".."Z"> LOALPHA = <any US-ASCII lowercase letter "a".."z"> ALPHA = UPALPHA | LOALPHA DIGIT = <any US-ASCII digit "0".."9"> CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)> CR = <US-ASCII CR, carriage return (13)> LF = <US-ASCII LF, linefeed (10)> SP = <US-ASCII SP, space (32)> HT = <US-ASCII HT, horizontal-tab (9)> <"> = <US-ASCII double-quote mark (34)>

 

 

问题排查花絮

1. 尝试访问 docker_node_1 上面的 8080 服务, ip 能够访问, 域名访问不了 

# 通过 容器名称 访问不到, 但是通过 ip 能够访问
~ # java -cp . DockerNetworkConsume http://docker_node_1:8080/api/users/
Exception in thread "main" java.io.IOException: Server returned HTTP response code: 400 for URL: http://docker_node_1:8080/api/users/
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1894)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
	at DockerNetworkConsume.main(DockerNetworkConsume.java:26)
~ # java -cp . DockerNetworkConsume http://172.25.0.3:8080/api/users/
{"status":401,"body":null,"message":"无效的访问令牌","developerMessage":"com.spring.framework.common.exception.ExceptionHelper.lambda$invalidTokenSupplier$3(ExceptionHelper.java:54)","errorFields":null}

 

2. 访问 independent 是通的, 但是同样的配置 docker_project_independent, 就是 400 

## docker_project_module01 访问 independent, docker_project_independent, docker_config_server 的不同
[root@t3420 independent]# docker exec -it docker_project_module01 /bin/sh
/ # wget http://independent:9999/
Connecting to independent:9999 (172.25.0.9:9999)
wget: server returned error: HTTP/1.1 404
/ # wget http://docker_project_independent:9999/
Connecting to docker_project_independent:9999 (172.25.0.17:9999)
wget: server returned error: HTTP/1.1 400
/ # wget http://docker_config_server:9999/
Connecting to docker_config_server:9999 (172.25.0.22:9999)
wget: can't connect to remote host (172.25.0.22): Connection refused
/ # wget http://docker_config_server:1234
Connecting to docker_config_server:1234 (172.25.0.22:1234)
wget: server returned error: HTTP/1.1 400

 

3. 查看 config_server 上面的具体的日志  

-- 通过 容器名称 访问不到, 但是通过 ip 能够访问 的问题始末
[root@t3420 bak]# docker logs -f docker_project_module01

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.0.RELEASE)

2020-07-08 15:00:36.419  INFO 1 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://docker_config_server:1234
2020-07-08 15:00:36.628  WARN 1 --- [           main] c.c.c.ConfigServicePropertySourceLocator : Could not locate PropertySource: 400 : [<!doctype html><html lang="en"><head><title>HTTP Status 400 – Bad Request</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;... (435 bytes)]

-- 访问的服务 docker_config_server 中的日志信息, 只出现了一次, 呵呵 最开始没看到, 我的天
java.lang.IllegalArgumentException: The character [_] is never valid in a domain name.
	at org.apache.tomcat.util.http.parser.HttpParser$DomainParseState.next(HttpParser.java:974) ~[tomcat-embed-core-9.0.35.jar!/:9.0.35]
	at org.apache.tomcat.util.http.parser.HttpParser.readHostDomainName(HttpParser.java:870) ~[tomcat-embed-core-9.0.35.jar!/:9.0.35]
	at org.apache.tomcat.util.http.parser.Host.parse(Host.java:71) ~[tomcat-embed-core-9.0.35.jar!/:9.0.35]
	at org.apache.tomcat.util.http.parser.Host.parse(Host.java:45) ~[tomcat-embed-core-9.0.35.jar!/:9.0.35]
	at org.apache.coyote.AbstractProcessor.parseHost(AbstractProcessor.java:295) ~[tomcat-embed-core-9.0.35.jar!/:9.0.35]
	at org.apache.coyote.http11.Http11Processor.prepareRequest(Http11Processor.java:776) [tomcat-embed-core-9.0.35.jar!/:9.0.35]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:349) [tomcat-embed-core-9.0.35.jar!/:9.0.35]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.35.jar!/:9.0.35]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) [tomcat-embed-core-9.0.35.jar!/:9.0.35]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590) [tomcat-embed-core-9.0.35.jar!/:9.0.35]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.35.jar!/:9.0.35]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_201]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_201]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.35.jar!/:9.0.35]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_201]

 

4. 还有一些其他的网络测试这里就不贴出来了, 不过写了一个 get 访问网络的脚本 

/**
 * DockerNetworkConsume
 *
 * @author Jerry.X.He <970655147@qq.com>
 * @version 1.0
 * @date 2020-07-08 11:08
 */
public class DockerNetworkConsume {

  // DockerNetworkConsume
  // eg : java -cp . DockerNetworkConsume http://localhost:1234/independent/dev
  // eg : java -cp . DockerNetworkConsume http://10.0.0.129:1234/independent/dev
  // eg : java -cp . DockerNetworkConsume http://docker_config_server:1234/independent/dev
  public static void main(String[] args) throws Exception {
    if (args.length == 0) {
      throw new RuntimeException(" please input url you want to visit ");
    }

//    args = new String[]{"http://localhost:1234/independent/dev"};
    URL url = new URL(args[0]);

    URLConnection connection = url.openConnection();
    InputStream is = connection.getInputStream();
    BufferedReader reader = new BufferedReader(new InputStreamReader(is));

    String line = null;
    while ((line = reader.readLine()) != null) {
      System.out.println(line);
    }

  }

}

 

 

完 

 

 

参考

http://web-sniffer.net/rfc/rfc2616.html