http status 400 bad request

背景:

http 请求,路径参数没有进行urlEncoded,出现报错。

错误表现:

错误日志:

复制代码
 The valid characters are defined in RFC 7230 and RFC 3986
        at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:482)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:263)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:750)

URL 解析策略不同

Resin:

  • 对URL字符的检查相对宽松

  • 默认允许更多特殊字符

  • 更注重兼容性而非严格遵循RFC规范

Tomcat:

  • 严格遵循RFC 7230和RFC 3986规范

  • 对特殊字符有严格限制

  • 更注重安全性和标准符合性

|{}\[\]"\都属于特殊字符串

源码

HTTP 请求行解析入口
org.apache.coyote.http11.Http11InputBuffer#parseRequestLine()

复制代码
org.apache.coyote.http11.Http11InputBuffer

if (this.parsingRequestLineQPos != -1 && !this.httpParser.isQueryRelaxed(this.chr)) {
	this.request.protocol().setString("HTTP/1.1");
	invalidRequestTarget = this.parseInvalid(this.parsingRequestLineStart, this.byteBuffer);
	throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget", new Object[]{invalidRequestTarget}));
}

if (this.httpParser.isNotRequestTargetRelaxed(this.chr)) {
	this.request.protocol().setString("HTTP/1.1");
	invalidRequestTarget = this.parseInvalid(this.parsingRequestLineStart, this.byteBuffer);
	throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget", new Object[]{invalidRequestTarget}));
}


package org.apache.tomcat.util.http.parser;
public class HttpParser {

	private final boolean[] IS_ABSOLUTEPATH_RELAXED = new boolean[128];
	private final boolean[] IS_QUERY_RELAXED = new boolean[128];

	public HttpParser(String relaxedPathChars, String relaxedQueryChars) {
	for(int i = 0; i < 128; ++i) {
		if (IS_CONTROL[i] || i == 32 || i == 34 || i == 35 || i == 60 || i == 62 || i == 92 || i == 94 || i == 96 || i == 123 || i == 124 || i == 125) {
			this.IS_NOT_REQUEST_TARGET[i] = true;
		}

		if (IS_USERINFO[i] || i == 64 || i == 47) {
			this.IS_ABSOLUTEPATH_RELAXED[i] = true;
		}

		if (this.IS_ABSOLUTEPATH_RELAXED[i] || i == 63) {
			this.IS_QUERY_RELAXED[i] = true;
		}
	}

	this.relax(this.IS_ABSOLUTEPATH_RELAXED, relaxedPathChars);
	this.relax(this.IS_QUERY_RELAXED, relaxedQueryChars);
}

}

public boolean isQueryRelaxed(int c) {
	try {
		return this.IS_QUERY_RELAXED[c];
	} catch (ArrayIndexOutOfBoundsException var3) {
		return false;
	}
}

先把 0-127 号 ASCII 字符全部扫一遍

  • IS_NOT_REQUEST_TARGET[i] = true → 表示该字节默认禁止出现在路径/查询串

  • 禁止列表 = RFC 3986 保留字符之外 的所有符号:
    " # < > \ ^ ````` { | } 以及空格、控制字符

    也就是说,只要不在这个黑名单里,就默认放行 ;反之必须被 relaxedPathChars/relaxedQueryChars 显式加白。

| 十进制 | 字符 | 默认是否被禁 | 备注 |
|-------|-------|--------|--------------|---|
| 32 | 空格 | ✅ | 必须 URLEncode |
| 34 | " | ✅ | 双引号 |
| 35 | # | ✅ | 片段标识符 |
| 60 62 | < > | ✅ | XML/HTML 危险 |
| 63 | ? | ❌ | 查询分隔符,放行 |
| 92 | \ | ✅ | 反斜杠 |
| 94 | ^ | ✅ | 脱字符 |
| 96 | ````` | ✅ | 反引号 |
| 123 | { | ✅ | 左花括号 | |
| 124 | ` | ✅ | 竖线(管道符 |
| 125 | } | ✅ | 右花括号 |

tomcat兼容

复制代码
@Slf4j
@Configuration
public class TomcatConfig implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

    /**
     * RFC 3986 之外、Tomcat 默认拦截的常见字符
     * 空格、#、<>、^、`、{}、|、\、[]、"
     */
    private static final String RELAXED_CHARS = " #<>^`{}|[]\\\"";
    
    @Override
    public void customize(TomcatServletWebServerFactory factory) {
        factory.addConnectorCustomizers(connector -> {
            connector.setProperty("relaxedQueryChars", RELAXED_CHARS);
            connector.setProperty("relaxedPathChars", RELAXED_CHARS);
            log.info(">>>> Tomcat relaxed characters: {}", RELAXED_CHARS);
        });
    }
}
相关推荐
XUEYUAN52123 天前
ASN 自治系统号风控:平台如何通过 IP 所属自治域批量识别代理流量
python·网络协议·http·网络安全·socks5
captain3763 天前
HTTP(2)
java·网络·http·java-ee
比兔代理4 天前
正向代理与反向代理技术辨析,为什么代理 IP 属于正向代理
服务器·网络·http·ip
碳基修炼5 天前
排查记:本地 devServer 是 http,浏览器却把 302 重定向升级成了 https
前端·http
那年窗外下的雪.5 天前
第 02 天:Linux 权限、inode、目录项与链接
学习·http
那年窗外下的雪.5 天前
AIDC 学习日志|第 27 天|EVPN 多归属收敛时间线与 Leaf2 接管验证
网络协议·学习·http·tcpdump
j7~5 天前
【Linux】三十八.C++ 手写 HTTP 服务器:裸 socket + fork 多进程,从 HTTP 报文解析到浏览器打开网页
linux·网络·网络协议·学习·http·网络编程
布莱克6057 天前
HTTP 状态码详解:从分类到实战
计算机网络·http·状态码
StevenSurpass7 天前
打印业务彻底解耦:FastPrintAgent 统一 JSON 模型 + FastReport 引擎的设计与落地
mqtt·http·中间件·打印·fastreport
小此方8 天前
Linux网络(十二):HTTP短连接与长连接:从Connection机制到Cookie、Session,彻底理解HTTP的无状态
服务器·网络·http