-
Notifications
You must be signed in to change notification settings - Fork 226
Add baggage support #1659
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: master
Are you sure you want to change the base?
Add baggage support #1659
Changes from all commits
5973074
17d6584
2403e57
4f434d1
f4a2452
25ae330
cf8ed70
33686be
b743a39
e49d03f
f24ec5f
6e5bdd9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| /* | ||
| * Copyright 2024 The Dapr Authors | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package io.dapr.examples.baggage; | ||
|
|
||
| import io.dapr.client.DaprClient; | ||
| import io.dapr.client.DaprClientBuilder; | ||
| import io.dapr.client.Headers; | ||
| import io.dapr.client.domain.HttpExtension; | ||
| import io.dapr.utils.TypeRef; | ||
| import reactor.util.context.Context; | ||
|
|
||
| /** | ||
| * Example demonstrating W3C Baggage propagation with the Dapr Java SDK. | ||
| * | ||
| * <p>Baggage allows propagating key-value pairs across service boundaries alongside | ||
| * distributed traces. This is useful for passing contextual information (e.g., user IDs, | ||
| * tenant IDs, feature flags) without modifying request payloads. | ||
| * | ||
| * <p>The Dapr runtime supports baggage propagation as defined by the | ||
| * <a href="https://www.w3.org/TR/baggage/">W3C Baggage specification</a>. | ||
| * | ||
| * <h2>Usage</h2> | ||
| * <ol> | ||
| * <li>Build and install jars: {@code mvn clean install}</li> | ||
| * <li>{@code cd [repo root]/examples}</li> | ||
| * <li>Start the target service: | ||
| * {@code dapr run --app-id target-service --app-port 3000 -- java -jar target/dapr-java-sdk-examples-exec.jar | ||
| * io.dapr.examples.invoke.http.DemoService -p 3000}</li> | ||
| * <li>Run the client: | ||
| * {@code dapr run -- java -jar target/dapr-java-sdk-examples-exec.jar | ||
| * io.dapr.examples.baggage.BaggageClient}</li> | ||
| * </ol> | ||
| */ | ||
| public class BaggageClient { | ||
|
|
||
| /** | ||
| * The main method to run the baggage example. | ||
| * | ||
| * @param args command line arguments (unused). | ||
| * @throws Exception on any error. | ||
| */ | ||
| public static void main(String[] args) throws Exception { | ||
| try (DaprClient client = new DaprClientBuilder().build()) { | ||
|
|
||
| // Build the W3C Baggage header value. | ||
| // Format: key1=value1,key2=value2 | ||
| // See https://www.w3.org/TR/baggage/#header-content | ||
| String baggageValue = "userId=alice,tenantId=acme-corp,featureFlag=new-ui"; | ||
|
|
||
| System.out.println("Invoking service with baggage: " + baggageValue); | ||
|
|
||
| // Propagate baggage via Reactor context. | ||
| // The SDK automatically injects the "baggage" header into outgoing gRPC | ||
| // and HTTP requests when present in the Reactor context. | ||
| byte[] response = client.invokeMethod( | ||
| "target-service", | ||
| "say", | ||
| "hello with baggage", | ||
| HttpExtension.POST, | ||
| null, | ||
| byte[].class) | ||
| .contextWrite(Context.of(Headers.BAGGAGE, baggageValue)) | ||
| .block(); | ||
|
|
||
| if (response != null) { | ||
| System.out.println("Response: " + new String(response)); | ||
| } | ||
|
|
||
| // You can also combine baggage with tracing context. | ||
| System.out.println("\nInvoking service with baggage and tracing context..."); | ||
| response = client.invokeMethod( | ||
| "target-service", | ||
| "say", | ||
| "hello with baggage and tracing", | ||
| HttpExtension.POST, | ||
| null, | ||
| byte[].class) | ||
| .contextWrite(Context.of(Headers.BAGGAGE, baggageValue) | ||
| .put("traceparent", "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01")) | ||
| .block(); | ||
|
|
||
| if (response != null) { | ||
| System.out.println("Response: " + new String(response)); | ||
| } | ||
|
|
||
| System.out.println("Done."); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| # Baggage Propagation Example | ||
|
|
||
| This example demonstrates [W3C Baggage](https://www.w3.org/TR/baggage/) propagation using the Dapr Java SDK. | ||
|
|
||
| ## Overview | ||
|
|
||
| Baggage allows you to propagate key-value pairs across service boundaries alongside distributed traces. This is useful for passing contextual information — such as user IDs, tenant IDs, or feature flags — without modifying request payloads. | ||
|
|
||
| The Dapr runtime supports baggage propagation as described in [Dapr PR #8649](https://github.com/dapr/dapr/pull/8649). The Java SDK propagates the `baggage` header via both gRPC metadata and HTTP headers automatically when the value is present in Reactor's context. | ||
|
|
||
| ## How It Works | ||
|
|
||
| The SDK reads the `baggage` key from Reactor's `ContextView` and injects it into: | ||
| - **gRPC metadata** via `DaprBaggageInterceptor` | ||
| - **HTTP headers** via `DaprHttp` (added to the context-to-header allowlist) | ||
|
|
||
| To propagate baggage, add it to the Reactor context using `.contextWrite()`: | ||
|
|
||
| ```java | ||
| import io.dapr.client.Headers; | ||
| import reactor.util.context.Context; | ||
|
|
||
| client.invokeMethod("target-service", "say", "hello", HttpExtension.POST, null, byte[].class) | ||
| .contextWrite(Context.of(Headers.BAGGAGE, "userId=alice,tenantId=acme-corp")) | ||
| .block(); | ||
| ``` | ||
|
|
||
| The baggage value follows the [W3C Baggage header format](https://www.w3.org/TR/baggage/#header-content): | ||
| ``` | ||
| key1=value1,key2=value2 | ||
| ``` | ||
|
|
||
| Each list-member can optionally include properties: | ||
| ``` | ||
| key1=value1;property1;property2,key2=value2 | ||
| ``` | ||
|
|
||
| ## Pre-requisites | ||
|
|
||
| * [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) | ||
| * Java JDK 11 (or greater): | ||
| * [Microsoft JDK 11](https://docs.microsoft.com/en-us/java/openjdk/download#openjdk-11) | ||
| * [Oracle JDK 11](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) | ||
| * [OpenJDK 11](https://jdk.java.net/11/) | ||
| * [Apache Maven](https://maven.apache.org/install.html) version 3.x. | ||
|
|
||
| ## Running the Example | ||
|
|
||
| ### 1. Build and install jars | ||
|
|
||
| ```sh | ||
| # From the java-sdk root directory | ||
| mvn clean install | ||
| ``` | ||
|
|
||
| ### 2. Start the target service | ||
|
|
||
| In one terminal, start the demo service: | ||
|
|
||
| ```sh | ||
| cd examples | ||
| dapr run --app-id target-service --app-port 3000 -- \ | ||
| java -jar target/dapr-java-sdk-examples-exec.jar \ | ||
| io.dapr.examples.invoke.http.DemoService -p 3000 | ||
| ``` | ||
|
|
||
| ### 3. Run the baggage client | ||
|
|
||
| In another terminal: | ||
|
|
||
| ```sh | ||
| cd examples | ||
| dapr run -- java -jar target/dapr-java-sdk-examples-exec.jar \ | ||
| io.dapr.examples.baggage.BaggageClient | ||
| ``` | ||
|
|
||
| You should see output like: | ||
|
|
||
| ``` | ||
| Invoking service with baggage: userId=alice,tenantId=acme-corp,featureFlag=new-ui | ||
| Response: ... | ||
| Done. | ||
| ``` | ||
|
|
||
| ## Combining Baggage with Tracing | ||
|
|
||
| You can propagate both baggage and tracing context together by adding multiple entries to the Reactor context: | ||
|
|
||
| ```java | ||
| client.invokeMethod("target-service", "say", "hello", HttpExtension.POST, null, byte[].class) | ||
| .contextWrite(Context.of(Headers.BAGGAGE, "userId=alice") | ||
| .put("traceparent", "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01")) | ||
| .block(); | ||
| ``` | ||
|
|
||
| Both the `baggage` and `traceparent` headers will be propagated to downstream services via Dapr. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,7 +66,8 @@ public class DaprHttp implements AutoCloseable { | |
| /** | ||
| * Context entries allowed to be in HTTP Headers. | ||
| */ | ||
| private static final Set<String> ALLOWED_CONTEXT_IN_HEADERS = Set.of("grpc-trace-bin", "traceparent", "tracestate"); | ||
| private static final Set<String> ALLOWED_CONTEXT_IN_HEADERS = | ||
| Set.of("grpc-trace-bin", "traceparent", "tracestate", "baggage"); | ||
|
Comment on lines
+69
to
+70
|
||
|
|
||
| /** | ||
| * Object mapper to parse DaprError with or without details. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| /* | ||
| * Copyright 2024 The Dapr Authors | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package io.dapr.internal.grpc.interceptors; | ||
|
|
||
| import io.dapr.client.Headers; | ||
| import io.grpc.CallOptions; | ||
| import io.grpc.Channel; | ||
| import io.grpc.ClientCall; | ||
| import io.grpc.ClientInterceptor; | ||
| import io.grpc.ForwardingClientCall; | ||
| import io.grpc.Metadata; | ||
| import io.grpc.MethodDescriptor; | ||
| import reactor.util.context.ContextView; | ||
|
|
||
| /** | ||
| * Injects W3C Baggage header into gRPC metadata from Reactor's context. | ||
| */ | ||
| public class DaprBaggageInterceptor implements ClientInterceptor { | ||
|
|
||
| private static final Metadata.Key<String> BAGGAGE_KEY = | ||
| Metadata.Key.of(Headers.BAGGAGE, Metadata.ASCII_STRING_MARSHALLER); | ||
|
|
||
| private final ContextView context; | ||
|
|
||
| /** | ||
| * Creates an instance of the baggage interceptor for gRPC. | ||
| * | ||
| * @param context Reactor's context | ||
| */ | ||
| public DaprBaggageInterceptor(ContextView context) { | ||
| this.context = context; | ||
| } | ||
|
|
||
| @Override | ||
| public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall( | ||
| MethodDescriptor<ReqT, RespT> methodDescriptor, | ||
| CallOptions callOptions, | ||
| Channel channel) { | ||
| ClientCall<ReqT, RespT> clientCall = channel.newCall(methodDescriptor, callOptions); | ||
| return new ForwardingClientCall.SimpleForwardingClientCall<>(clientCall) { | ||
| @Override | ||
| public void start(final Listener<RespT> responseListener, final Metadata metadata) { | ||
| if (context != null && context.hasKey(Headers.BAGGAGE)) { | ||
| String baggageValue = context.get(Headers.BAGGAGE).toString(); | ||
| if (baggageValue != null && !baggageValue.isEmpty()) { | ||
| metadata.put(BAGGAGE_KEY, baggageValue); | ||
| } | ||
| } | ||
| super.start(responseListener, metadata); | ||
| } | ||
| }; | ||
| } | ||
| } |
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.
Unused import
io.dapr.utils.TypeRefis present but not referenced in this example. Please remove it to keep the example compiling cleanly under stricter build/lint configurations.