jetty引发的两个bug

给存储实现了s3协议已经一段时间, 最近同事测试反馈偶尔会出现上传对象失败

上传对象, 如果key带%,就会失败

现象

上传文件时, 如果指定对象key带%,就会失败,直接返回400 Bad Message.

分析

我看了服务器后台日志,发现上传请求,还没有开始处理就返回了. 应该是jetty层就已经解码出了问题.

打开jetty debug日志, 发现 org.eclipse.jetty.http.BadMessageException: 400: Ambiguous URI empty segment
jetty 400 ambiguous

看了一下githhub issue, 原来是jetty遵守rfc要求, 默认的UriCompliance过于严格.

Error 400 – Ambiguous URI Empty Segment · Issue #11298 · jetty/jetty.project

解决方案

创建 ServletWebServerFactory时,自定义jetty配置, 指定使用 UriCompliance.LEGACY.

使用juicesync 从minio往我们存储s3迁移时,有个别对象迁移失败.

现象

同事使用juicesync从minio往s3gw 迁移数据时,发现有个别对象出现迁移失败, 原因是aws sign v4签名认证错误.

分析

s3gw 实现了aws signature v4, 并且是根据官网说明实现的.

Authenticating Requests: Using Query Parameters (AWS Signature Version 4) – Amazon Simple Storage Service

打开juicesync 的aws-sdk-go debug日志

juicesync 的逻辑实现在 juicefs

下载juicefs 和 juicesync , 修改juicesync 的go.mod指向本地版本的juicefs

[wjh@node1 juicesync]$ cat go.mod |grep replace
replace github.com/juicedata/juicefs v1.2.2 => ../juicefs

修改’juicefs/pkg/object/s3.go’ awsConfig, 设置debug日志,重新编译juicesync

    awsConfig := &aws.Config{
        Region:     &region,
        DisableSSL: aws.Bool(!ssl),
        HTTPClient: httpClient,
        LogLevel: aws.LogLevel(aws.LogDebugWithSigning), //打开aws-sdk-go debug日志
    }

出错地方

对比客户端和服务端签名时,拼接的字符串, 发现

  • 客户端计算的Canonical Headers 中是 ‘Content-Type::text/xml; charset=utf-8’
  • 服务端端计算的Canonical Headers 中是 ‘Content-Type::text/xml; charset=UTF-8’

编码的地方大小写不一样.

问题是出现在客户端呢? 还是服务端呢?

使用tcpdump 抓包, 看了发生错误的http请求
上传对象的http请求

客户端发送的请求出来的 conten-type中utf-8是小写的. 那么问题应该是发生在服务器端.

服务器端是通过spring-boot + jetty 提供http服务的.

使用arthas watch,发现 org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter时 请求的 conten-type中utf-8已经是大写的.

  1. 查看网上资料也没有说, spring会修改过请求头.
  2. 反而看到网上说,jetty会缓存一些请求头, 减少解析请求开销. 并且jetty是遵守rfc 标准的, rfc 标准对conten-type中utf-8不区分大小写.

https://github.com/jetty/jetty.project/blob/jetty-12.0.x/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java 看到有CACHE实现

https://github.com/jetty/jetty.project/blob/6986b18ecb2d6d4ed7288cd92d9a0c1eb8a29d28/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java#L480 可以设置HeaderCacheCaseSensitive

最后通过自定义配置jetty, setHeaderCacheCaseSensitive(true), 签名错误的问题终于解决了.

总结

这两个问题,严格来说不算哪一方的bug, 就是双方的标准有冲突, 这里主要目的是实现对象存储功能, 因此调整jetty配置适应aws s3协议.