Skip to content

fix(protocol): single protocol path — EventMeshFrame across HTTP ingress, filter chain and TCP egress (#5299) - #5319

Merged
qqeasonchen merged 3 commits into
apache:developfrom
qqeasonchen:fix/5299-subpr-a-uni-http-frame-adaptor
Aug 31, 2026
Merged

fix(protocol): single protocol path — EventMeshFrame across HTTP ingress, filter chain and TCP egress (#5299)#5319
qqeasonchen merged 3 commits into
apache:developfrom
qqeasonchen:fix/5299-subpr-a-uni-http-frame-adaptor

Conversation

@qqeasonchen

Copy link
Copy Markdown
Contributor

Issue #5299 Sub-PR A: route UniHttpServer ingress through FrameAdaptor SPI

Summary

The HTTP ingress path now converts structured CloudEvents JSON bytes to
EventMeshFrame at the boundary via the FrameAdaptor SPI
(CloudEventsFrameAdaptor). UniIngressService no longer needs to import
io.cloudevents.CloudEvent on the HTTP code path. The runtime primary
path is now Frame-in / Frame-out; the CloudEvent-typed ingress methods
(publish / publishBatch / request / reply / publishLite /
pollLite) are preserved unchanged for binary compatibility with the TCP
bridge and out-of-tree callers.

New public methods on UniIngressService

New method Purpose
publishBatchFrames(topic, List<EventMeshFrame>) Primary batch ingress; HTTP path uses one FrameAdaptor.toFrame per array element, then this method
publishLiteFrame(parent, lite, EventMeshFrame) Primary lite ingress; HTTP /events/lite/publish
pollLiteFrames(parent, lite, max, timeoutMs) → List<EventMeshFrame> Primary lite poll; HTTP /events/lite/poll response body serialized back to CloudEvents JSON by the egress adapter
requestFrame(topic, EventMeshFrame, timeout) → EventMeshFrame Primary request-reply (§17)
replyFrame(correlationId, EventMeshFrame) Primary reply path

The CloudEvent-typed overloads are kept for binary compatibility with the
TCP bridge and out-of-tree callers. They will be deprecated in sub-PR B
once the TCP path also migrates to the MeshMessageFrameAdaptor.

UniHttpServer changes (8 ingress call sites)

Endpoint Before After
POST /events/publish EventFormatProvider.deserialize(body) FrameAdaptors.get("cloudevents").toFrame(new ByteTransport(body))
POST /events/publishBatch same per element, then publishBatch(List<CE>) same per element, then publishBatchFrames(List<Frame>)
POST /events/lite/publish EventFormatProvider.deserialize(body) FrameAdaptor.toFrame(...) + publishLiteFrame(...)
GET /events/lite/poll EventFormatProvider.serialize(ce) per event FrameAdaptor.toCloudEventsJson(frame) per event
POST /request (blocking req-reply) EventFormatProvider.deserialize(body) + request(CE) FrameAdaptor.toFrame(...) + requestFrame(...); response body via FrameAdaptor.toCloudEventsJson(reply)
POST /events/reply EventFormatProvider.deserialize(body) + reply(CE) FrameAdaptor.toFrame(...) + replyFrame(...)
GET /events/poll (egress) FrameAdaptors.toCloudEventsJson(be.getEvent()) unchanged (already FrameAdaptor)

UniHttpServer no longer imports io.cloudevents.*. The 5 imports
FrameAdaptor / ByteTransport / EventMeshFrame are added.

Security stub — temporary bridge (sub-PR B scope)

The filterChain.check(stubEvent, ctx) ACL check inside UniHttpServer.publish
still consumes a CloudEvent. A TODO(#5299 sub-PR B) comment marks the spot.
The next sub-PR will:

  1. Add FilterChain.check(EventMeshFrame, FilterContext) overload
  2. Make UniHttpServer pass the Frame directly to the filter chain
  3. Update CloudEventFilter / AclFilter / SignatureVerifierFilter /
    TokenAuthFilter to look up extensions from Frame attributes

Until then, the HTTP path does a one-shot frame.toCloudEvent() for the
ACL call only. This is a runtime hot-path minor cost (one allocation per
request) that the next sub-PR removes.

Acceptance against #5299

  • Runtime code paths (UniHttpServer) reference only EventMeshFrame,
    not a protocol-specific envelope type
  • Each non-primary protocol has an explicit ingress/egress adapter
    (CE is already primary; MeshMessage and A2A follow in sub-PR C
    and existing sub-PR D)
  • Ingress / egress goes through FrameAdaptor SPI — no direct
    EventFormatProvider calls in UniHttpServer
  • core SDK package has no Netty / gRPC / OpenMessaging — out of
    scope (issue marks this as a follow-up)
  • Documentation marks each protocol path as primary / beta / legacy —
    done in sub-PR D

Verification

  • ./gradlew.bat :eventmesh-runtime:compileJava --offline → BUILD SUCCESSFUL,
    0 errors, no new warnings (45 deprecation warnings all pre-existing)
  • ./gradlew.bat :eventmesh-runtime:checkstyleMain :eventmesh-runtime:checkstyleTest --offline
    → BUILD SUCCESSFUL, 0 violations
  • ./gradlew.bat :eventmesh-runtime:test --offline --tests "*UniIngressServiceTest*" --tests "*UniHttpServer*Test*" --tests "*SecurityWiringTest*" --tests "*SseStreamTest*" → BUILD SUCCESSFUL, 17 tests, 0 failures, 0 ignored

Files changed

  • eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/http/UniHttpServer.java
    (61 +/29 -)
  • eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/ingress/UniIngressService.java
    (67 +/2 -)

Follow-up

…pache#5299 Sub-PR A)

The HTTP ingress path now converts structured CloudEvents JSON bytes to
EventMeshFrame at the boundary via FrameAdaptor SPI, so UniIngressService
no longer imports io.cloudevents.CloudEvent on the HTTP code path.

New ingress methods (Frame-typed):
  - publishBatchFrames(topic, List<EventMeshFrame>)
  - publishLiteFrame(parent, lite, EventMeshFrame)
  - pollLiteFrames(parent, lite, max, timeoutMs) -> List<EventMeshFrame>
  - requestFrame(topic, EventMeshFrame, timeout) -> EventMeshFrame
  - replyFrame(correlationId, EventMeshFrame)

The CloudEvent-typed overloads are preserved for binary compatibility
with the TCP bridge. Sub-PR B will migrate FilterChain; Sub-PR C will
migrate the TCP path.

Closes part of apache#5299.
@qqeasonchen
qqeasonchen force-pushed the fix/5299-subpr-a-uni-http-frame-adaptor branch from cf0d068 to 69e808f Compare August 31, 2026 08:36
@qqeasonchen

Copy link
Copy Markdown
Contributor Author

Force-pushed 69e808f to fix an autocrlf/LF mismatch in the first commit (cf0d068). The diff is now clean: UniHttpServer.java +43/-28 and UniIngressService.java +988/-903 (was previously 1287+/- whole-file rewrite due to CRLF blobs). No code changes between the two commits — only line-ending normalization.

…#5299 Sub-PR B)

The ingress security pipeline (§4.5) — TokenAuthFilter / AclFilter /
SignatureVerifierFilter / FilterChain — now operates on the runtime's
internal wire format (org.apache.eventmesh.common.wire.EventMeshFrame)
instead of io.cloudevents.CloudEvent. This continues the apache#5299 single-
protocol-path migration: sub-PR A routed the HTTP ingress through the
FrameAdaptor SPI; sub-PR B extends the same boundary inward to the
filter chain so that auth/acl/signature now read directly from
frame.attributes().

Changes:
- IngressFilter: new check(EventMeshFrame, FilterContext) primary
  method; the CloudEvent overload is now a default that bridges via
  EventMeshFrame.fromCloudEvent(...) for backward compat.
- FilterChain: new check(EventMeshFrame, FilterContext) overload;
  CloudEvent variant is deprecated and bridges via fromCloudEvent.
- AclFilter: implement check(EventMeshFrame, ...) — also rejects
  non-EVENT frames (e.g. STREAM_REQ / STREAM_CHUNK) since the
  ingress security pipeline is event-shaped only.
- TokenAuthFilter: implement check(EventMeshFrame, ...) — credential
  still comes from FilterContext (HTTP Authorization header), so
  the frame body is unused for this stage.
- SignatureVerifierFilter: implement check(EventMeshFrame, ...) —
  signature travels in frame.attributes() under the same key
  (emsignature) the legacy CloudEvent extension used, so signed
  CloudEvents round-trip transparently through the cloudevents
  FrameAdaptor.
- UniHttpServer: drop the temporary frame.toCloudEvent() bridge in
  the publish() filter call; thread tenant directly from
  frame.attributes(). Also drop the synthetic CloudEvent stub used
  for pre-publish security check — replaced with a minimal
  EventMeshFrame.event(emptyMap, []).
- New test class: EventMeshFrameFilterTest (6 tests) exercising
  the EventMeshFrame-typed path parallel to the existing
  SecurityFilterTest which still covers the CloudEvent bridge.

Backward compat: existing custom filters implementing
IngressFilter.check(CloudEvent, ...) keep working because the
interface now provides a default implementation. They will compile
unchanged and continue to be invoked via the deprecated chain
overload until sub-PR C migrates the TCP path and we can drop the
bridge entirely.

Refs apache#5299.
@qqeasonchen qqeasonchen changed the title fix(protocol): route UniHttpServer ingress through FrameAdaptor SPI (#5299 Sub-PR A) fix(protocol): route UniHttpServer ingress + filter chain through EventMeshFrame (#5299 Sub-PRs A+B) Aug 31, 2026
@qqeasonchen

Copy link
Copy Markdown
Contributor Author

Force-pushed f17c3c7 to fold Sub-PR B (filter chain migration) into this PR as requested. The PR now spans Sub-PRs A+B in one commit:

Sub-PR A (commit 69e808f): route UniHttpServer 8 ingress endpoints through FrameAdaptor SPI → EventMeshFrame

Sub-PR B (commit f17c3c7): migrate ingress security pipeline (TokenAuthFilter / AclFilter / SignatureVerifierFilter / FilterChain) to operate on EventMeshFrame — auth/acl/signature now read directly from frame.attributes()

Diff: 8 files, +1247 / -958. The CloudEvent-based filter methods remain as @deprecated bridges so existing custom filters keep compiling; they will be removed in sub-PR C after the TCP path migrates.

CI is being re-triggered on the new head.

…x + C + D)

Sub-PR B fix:
 - restore the missing io.cloudevents.CloudEvent import in IngressFilter (broke compileJava)
 - move EventMeshFrame into the correct checkstyle ImportOrder group
   (style/checkStyle.xml puts org.apache.eventmesh first; maxWarnings=0 so it fails CI)
 - update SecurityFilterTest for the frame-based SignatureVerifierFilter.canonical(...)

Sub-PR C: migrate the TCP egress path to EventMeshFrame
 - TcpFrameCodec.encodePush / TcpPushChannel.deliver now take an EventMeshFrame directly,
   dropping the frame -> CloudEvent -> wire round trip
 - delete the dead CloudEvent-era egress SPI: CloudEventToPackageBody, MeshEventToPackageBody
 - UniTcpServer drops the now-unused bodyMapper constructor parameter
 - TCP ingress was already frame-native (MeshMessagePackageRouter / TcpRequest / NettyTcpPushChannel)

Sub-PR D: document the apache#5299 acceptance matrix and protocol status labels
 (docs/eventmesh-uni-architecture-redesign.md section 19.6)

Refs apache#5299.
@qqeasonchen qqeasonchen changed the title fix(protocol): route UniHttpServer ingress + filter chain through EventMeshFrame (#5299 Sub-PRs A+B) fix(protocol): single protocol path — EventMeshFrame across HTTP ingress, filter chain and TCP egress (#5299) Aug 31, 2026
@qqeasonchen

Copy link
Copy Markdown
Contributor Author

Folded the remaining #5299 sub-PRs into this PR (4cf9c8c), as requested. The PR now covers A + B + C + D.

Sub-PR A (69e808f) — UniHttpServer's 8 ingress endpoints go through FrameAdaptors.get("cloudevents").toFrame(...); UniIngressService gains publishBatchFrames / publishLiteFrame / pollLiteFrames / requestFrame / replyFrame.

Sub-PR B (f17c3c7) — ingress security pipeline (IngressFilter / FilterChain / AclFilter / TokenAuthFilter / SignatureVerifierFilter) operates on EventMeshFrame; tenant / signature / token read straight from frame.attributes().

Sub-PR C (4cf9c8c) — TCP egress path:

  • TcpFrameCodec.encodePush / TcpPushChannel.deliver now take an EventMeshFrame, dropping the frame → CloudEvent → wire round trip
  • deleted the dead CloudEvent-era egress SPI: CloudEventToPackageBody, MeshEventToPackageBody
  • UniTcpServer drops the now-unused bodyMapper constructor parameter
  • TCP ingress was already frame-native upstream (MeshMessagePackageRoutertoFrameSilent, TcpRequest holds a frame, NettyTcpPushChannelfromFrameSilent), so no change was needed there.

Sub-PR D (4cf9c8c) — docs/eventmesh-uni-architecture-redesign.md §19.6: protocol status labels (primary / beta / legacy), per-path acceptance matrix, per-sub-PR status.

This commit also fixes two defects the previous head introduced:

  • IngressFilter was missing import io.cloudevents.CloudEvent; (broke compileJava)
  • EventMeshFrame imports landed in the wrong checkstyle ImportOrder group (org.apache.eventmesh is group 0; maxWarnings=0 so it failed CI)

Verified locally on JDK 21: compileJava + compileTestJava + checkstyleMain + checkstyleTest all green, 25 targeted tests pass (0 skipped, 0 failed).

@qqeasonchen
qqeasonchen merged commit ba26719 into apache:develop Aug 31, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant