-
Notifications
You must be signed in to change notification settings - Fork 8.9k
refactor:add the dependency probing logic of http and unify the apis of http1 and http2 #7684
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 2.x
Are you sure you want to change the base?
Conversation
common/src/main/java/org/apache/seata/common/http/Http2HttpExecutor.java
Show resolved
Hide resolved
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## 2.x #7684 +/- ##
============================================
- Coverage 61.52% 61.46% -0.07%
- Complexity 680 684 +4
============================================
Files 1314 1316 +2
Lines 49890 49949 +59
Branches 5878 5890 +12
============================================
+ Hits 30696 30702 +6
- Misses 16404 16447 +43
- Partials 2790 2800 +10
🚀 New features to boost your workflow:
|
@YongGoose @funky-eyes PTAL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The attempt at abstraction was good.
However, the way it’s actually used in the business logic feels a bit lacking.
Please let me know if I missed something.
/** | ||
* Gets instance. | ||
* | ||
* @return the instance | ||
*/ | ||
public static Http1HttpExecutor getInstance() { | ||
|
||
if (instance == null) { | ||
synchronized (Http1HttpExecutor.class) { | ||
if (instance == null) { | ||
instance = new Http1HttpExecutor(); | ||
} | ||
} | ||
} | ||
return instance; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this instance need to be a singleton?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think some resources may need to be reused here, such as the connection pool here, and using a singleton can avoid the state inconsistency problems caused by multiple instances and the performance problems caused by frequent object creation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HTTP_CLIENT_MAP
is already managed as a static final
field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this instance need to be a singleton?
当我们希望通过 HTTP/2 发起请求时,不仅需要检测客户端是否支持,还必须确认 Seata Server 端 是否具备 HTTP/2 能力。
如果直接使用抽象类提供的通用 API 发起调用,可能会出现这样的问题:客户端最终使用了 HTTP/2 实现类,但服务端并不支持,从而导致不可预期的异常。原因在于当前的依赖检测逻辑中,尚未包含对 Server 端协议版本 的校验。
因此,为了保证在某些场景下能够安全运行,我暂时为每个实现类单独保留了 getInstance() 方法,便于手动选择合适的实现。
待后续在依赖探测中集成 “Server 端 HTTP/2 支持判断” 能力后,这些单例获取方法就可以被移除。届时,所有 HTTP 请求都可统一通过 HttpExecutorFactory.getInstance()
进行调用。
This logic serves as a transitional solution.
When we intend to initiate requests over HTTP/2, we must verify not only the client’s capability but also whether the Seata Server supports HTTP/2.
If we directly invoke the generic API provided by the abstract class, it’s possible that the actual implementation used on the client side is based on HTTP/2 while the server does not support it. This could lead to unpredictable exceptions, since the current dependency check mechanism does not include validation for the server-side protocol version.
To ensure stability under such conditions, each implementation class temporarily provides its own getInstance() method, allowing manual selection of the proper executor.
Once the capability detection logic is enhanced to determine whether the server supports HTTP/2, these per-class singleton access methods can be safely removed. At that point, all HTTP requests can be uniformly initiated via HttpExecutorFactory.getInstance()
.
try (CloseableHttpResponse response = HttpClientUtil.doPost(url, jsonBody, header, 3000)) { | ||
int statusCode = response.getStatusLine().getStatusCode(); | ||
try { | ||
HttpResult httpResult = Http1HttpExecutor.getInstance().doPost(url, jsonBody, header, 3000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels awkward to retrieve an object through the implementation’s getInstance() when we already have an abstraction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels awkward to retrieve an object through the implementation’s getInstance() when we already have an abstraction.
It’s currently unclear whether Seata’s internal namingServer requires server-side version compatibility checks when operating over HTTP/2.
If such checks are unnecessary, this logic can be simplified to use the abstract class’s generic API directly.
However, if the server’s protocol version must still be validated, this issue essentially aligns with the scenario described above — meaning that server-side version detection should be integrated into the dependency checking mechanism.
private static HttpExecutor createInstance() { | ||
String implName = isOkHttpAvailable() ? HTTP1_IMPL : HTTP2_IMPL; | ||
return EnhancedServiceLoader.load(HttpExecutor.class, implName); | ||
} | ||
|
||
private static boolean isOkHttpAvailable() { | ||
try { | ||
Class.forName("okhttp3.OkHttpClient", false, HttpExecutorFactory.class.getClassLoader()); | ||
return true; | ||
} catch (ClassNotFoundException ignored) { | ||
LOGGER.warn("HTTP2 implementation not available in classpath, falling back to default executor!"); | ||
return false; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is it used for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is it used for?
Checks whether the client includes the OkHttp3 dependency to determine if it has the capability to send HTTP/2 requests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@YongGoose In your PR(#7586 ), would it be possible to integrate the server-side version check(isHttp2Supported
) into the current dependency detection logic?
If so, I think you could refactor it after my PR gets merged — that way, the per-implementation singleton methods would no longer be necessary.
Ⅰ. Describe what this PR did
1.Dynamically detects whether HTTP/1 or HTTP/2 is available in the target system, and gracefully falls back to HTTP/1 when HTTP/2 is unavailable.
2.Unifies the HTTP/1 and HTTP/2 API interfaces, allowing upper-level services to initiate HTTP requests simply through
HttpExecutorFactory.getInstance().doGet(Post)
Ⅱ. Does this pull request fix one issue?
fix #7561
fix #7685
Ⅲ. Why don't you add test cases (unit test/integration test)?
Ⅳ. Describe how to verify it
Ⅴ. Special notes for reviews
重构了 HTTP/1 和 HTTP/2 的服务能力,实现统一且易用的 API:
1.之前 HTTP/2 请求是异步的,需要调用方通过额外编写回调逻辑来处理响应,这对业务代码侵入性很强、改造量大,且与 HTTP/1 API 差异较大。本次通过引入 CompletableFuture 将异步调用转为同步调用,并统一返回值,使上层服务调用 HTTP/2 时可以像调用 HTTP/1 一样,无需编写回调逻辑,降低了调用方的改造成本。
HTTP/1 和 HTTP/2 的平滑切换逻辑封装在
HttpExecutorFactory
内,上层业务代码无需关注协议差异或手动判断是否支持 HTTP/2,保证了调用的简洁性和一致性。本次针对HTTP/2的识别 并未考虑到Server端的版本是否支持,所以在
RaftRegistryServiceImpl
中做改造时(feature : Integrate Raft and Protocol Switching Based on Server Version #7586 )仍需要加入对Server端的判断Refactored the HTTP/1 and HTTP/2 service capabilities to provide a unified and user-friendly API:
Previously, HTTP/2 requests were asynchronous and required the caller to handle callbacks. This approach was highly invasive to business code, required significant modifications, and was inconsistent with the HTTP/1 API. In this update, we use CompletableFuture to convert asynchronous calls into synchronous ones and standardize the return values, allowing upper-level services to call HTTP/2 just like HTTP/1, without writing callback logic, thus reducing the impact on the caller’s code.
The smooth fallback between HTTP/1 and HTTP/2 is fully encapsulated within HttpExecutorFactory. The business code does not need to worry about protocol differences or manually check for HTTP/2 support, ensuring simplicity and consistency in usage.
This update does not include detection of the server-side HTTP/2 support. Therefore, when refactoring
RaftRegistryServiceImpl
, it is still necessary to check whether the server supports HTTP/2.