Skip to content

Commit 76d9d53

Browse files
authored
Merge branch 'master' into 1688-langchain4j
2 parents 497593e + 8f304f6 commit 76d9d53

File tree

7 files changed

+463
-1
lines changed

7 files changed

+463
-1
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2024 The Dapr Authors
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package io.dapr.examples.baggage;
15+
16+
import io.dapr.client.DaprClient;
17+
import io.dapr.client.DaprClientBuilder;
18+
import io.dapr.client.Headers;
19+
import io.dapr.client.domain.HttpExtension;
20+
import reactor.util.context.Context;
21+
22+
/**
23+
* Example demonstrating W3C Baggage propagation with the Dapr Java SDK.
24+
*
25+
* <p>Baggage allows propagating key-value pairs across service boundaries alongside
26+
* distributed traces. This is useful for passing contextual information (e.g., user IDs,
27+
* tenant IDs, feature flags) without modifying request payloads.
28+
*
29+
* <p>The Dapr runtime supports baggage propagation as defined by the
30+
* <a href="https://www.w3.org/TR/baggage/">W3C Baggage specification</a>.
31+
*
32+
* <h2>Usage</h2>
33+
* <ol>
34+
* <li>Build and install jars: {@code mvn clean install}</li>
35+
* <li>{@code cd [repo root]/examples}</li>
36+
* <li>Start the target service:
37+
* {@code dapr run --app-id target-service --app-port 3000 -- java -jar target/dapr-java-sdk-examples-exec.jar
38+
* io.dapr.examples.invoke.http.DemoService -p 3000}</li>
39+
* <li>Run the client:
40+
* {@code dapr run -- java -jar target/dapr-java-sdk-examples-exec.jar
41+
* io.dapr.examples.baggage.BaggageClient}</li>
42+
* </ol>
43+
*/
44+
public class BaggageClient {
45+
46+
/**
47+
* The main method to run the baggage example.
48+
*
49+
* @param args command line arguments (unused).
50+
* @throws Exception on any error.
51+
*/
52+
public static void main(String[] args) throws Exception {
53+
try (DaprClient client = new DaprClientBuilder().build()) {
54+
55+
// Build the W3C Baggage header value.
56+
// Format: key1=value1,key2=value2
57+
// See https://www.w3.org/TR/baggage/#header-content
58+
String baggageValue = "userId=alice,tenantId=acme-corp,featureFlag=new-ui";
59+
60+
System.out.println("Invoking service with baggage: " + baggageValue);
61+
62+
// Propagate baggage via Reactor context.
63+
// The SDK automatically injects the "baggage" header into outgoing gRPC
64+
// and HTTP requests when present in the Reactor context.
65+
byte[] response = client.invokeMethod(
66+
"target-service",
67+
"say",
68+
"hello with baggage",
69+
HttpExtension.POST,
70+
null,
71+
byte[].class)
72+
.contextWrite(Context.of(Headers.BAGGAGE, baggageValue))
73+
.block();
74+
75+
if (response != null) {
76+
System.out.println("Response: " + new String(response));
77+
}
78+
79+
// You can also combine baggage with tracing context.
80+
System.out.println("\nInvoking service with baggage and tracing context...");
81+
response = client.invokeMethod(
82+
"target-service",
83+
"say",
84+
"hello with baggage and tracing",
85+
HttpExtension.POST,
86+
null,
87+
byte[].class)
88+
.contextWrite(Context.of(Headers.BAGGAGE, baggageValue)
89+
.put("traceparent", "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"))
90+
.block();
91+
92+
if (response != null) {
93+
System.out.println("Response: " + new String(response));
94+
}
95+
96+
System.out.println("Done.");
97+
}
98+
}
99+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Baggage Propagation Example
2+
3+
This example demonstrates [W3C Baggage](https://www.w3.org/TR/baggage/) propagation using the Dapr Java SDK.
4+
5+
## Overview
6+
7+
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.
8+
9+
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.
10+
11+
## How It Works
12+
13+
The SDK reads the `baggage` key from Reactor's `ContextView` and injects it into:
14+
- **gRPC metadata** via `DaprBaggageInterceptor`
15+
- **HTTP headers** via `DaprHttp` (added to the context-to-header allowlist)
16+
17+
To propagate baggage, add it to the Reactor context using `.contextWrite()`:
18+
19+
```java
20+
import io.dapr.client.Headers;
21+
import reactor.util.context.Context;
22+
23+
client.invokeMethod("target-service", "say", "hello", HttpExtension.POST, null, byte[].class)
24+
.contextWrite(Context.of(Headers.BAGGAGE, "userId=alice,tenantId=acme-corp"))
25+
.block();
26+
```
27+
28+
The baggage value follows the [W3C Baggage header format](https://www.w3.org/TR/baggage/#header-content):
29+
```
30+
key1=value1,key2=value2
31+
```
32+
33+
Each list-member can optionally include properties:
34+
```
35+
key1=value1;property1;property2,key2=value2
36+
```
37+
38+
## Pre-requisites
39+
40+
* [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/)
41+
* Java JDK 11 (or greater):
42+
* [Microsoft JDK 11](https://docs.microsoft.com/en-us/java/openjdk/download#openjdk-11)
43+
* [Oracle JDK 11](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11)
44+
* [OpenJDK 11](https://jdk.java.net/11/)
45+
* [Apache Maven](https://maven.apache.org/install.html) version 3.x.
46+
47+
## Running the Example
48+
49+
### 1. Build and install jars
50+
51+
```sh
52+
# From the java-sdk root directory
53+
mvn clean install
54+
```
55+
56+
### 2. Start the target service
57+
58+
In one terminal, start the demo service:
59+
60+
```sh
61+
cd examples
62+
dapr run --app-id target-service --app-port 3000 -- \
63+
java -jar target/dapr-java-sdk-examples-exec.jar \
64+
io.dapr.examples.invoke.http.DemoService -p 3000
65+
```
66+
67+
### 3. Run the baggage client
68+
69+
In another terminal:
70+
71+
```sh
72+
cd examples
73+
dapr run -- java -jar target/dapr-java-sdk-examples-exec.jar \
74+
io.dapr.examples.baggage.BaggageClient
75+
```
76+
77+
You should see output like:
78+
79+
```
80+
Invoking service with baggage: userId=alice,tenantId=acme-corp,featureFlag=new-ui
81+
Response: ...
82+
Done.
83+
```
84+
85+
## Combining Baggage with Tracing
86+
87+
You can propagate both baggage and tracing context together by adding multiple entries to the Reactor context:
88+
89+
```java
90+
client.invokeMethod("target-service", "say", "hello", HttpExtension.POST, null, byte[].class)
91+
.contextWrite(Context.of(Headers.BAGGAGE, "userId=alice")
92+
.put("traceparent", "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"))
93+
.block();
94+
```
95+
96+
Both the `baggage` and `traceparent` headers will be propagated to downstream services via Dapr.

sdk/src/main/java/io/dapr/client/DaprHttp.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ public class DaprHttp implements AutoCloseable {
6666
/**
6767
* Context entries allowed to be in HTTP Headers.
6868
*/
69-
private static final Set<String> ALLOWED_CONTEXT_IN_HEADERS = Set.of("grpc-trace-bin", "traceparent", "tracestate");
69+
private static final Set<String> ALLOWED_CONTEXT_IN_HEADERS =
70+
Set.of("grpc-trace-bin", "traceparent", "tracestate", "baggage");
7071

7172
/**
7273
* Object mapper to parse DaprError with or without details.

sdk/src/main/java/io/dapr/client/Headers.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,9 @@ public final class Headers {
3232
* Header for Api Logging User-Agent.
3333
*/
3434
public static final String DAPR_USER_AGENT = "User-Agent";
35+
36+
/**
37+
* W3C Baggage header for context propagation.
38+
*/
39+
public static final String BAGGAGE = "baggage";
3540
}

sdk/src/main/java/io/dapr/internal/grpc/DaprClientGrpcInterceptors.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import io.dapr.internal.grpc.interceptors.DaprApiTokenInterceptor;
1717
import io.dapr.internal.grpc.interceptors.DaprAppIdInterceptor;
18+
import io.dapr.internal.grpc.interceptors.DaprBaggageInterceptor;
1819
import io.dapr.internal.grpc.interceptors.DaprMetadataReceiverInterceptor;
1920
import io.dapr.internal.grpc.interceptors.DaprTimeoutInterceptor;
2021
import io.dapr.internal.grpc.interceptors.DaprTracingInterceptor;
@@ -126,6 +127,7 @@ public <T extends AbstractStub<T>> T intercept(
126127
new DaprApiTokenInterceptor(this.daprApiToken),
127128
new DaprTimeoutInterceptor(this.timeoutPolicy),
128129
new DaprTracingInterceptor(context),
130+
new DaprBaggageInterceptor(context),
129131
new DaprMetadataReceiverInterceptor(metadataConsumer));
130132
}
131133

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2024 The Dapr Authors
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package io.dapr.internal.grpc.interceptors;
15+
16+
import io.dapr.client.Headers;
17+
import io.grpc.CallOptions;
18+
import io.grpc.Channel;
19+
import io.grpc.ClientCall;
20+
import io.grpc.ClientInterceptor;
21+
import io.grpc.ForwardingClientCall;
22+
import io.grpc.Metadata;
23+
import io.grpc.MethodDescriptor;
24+
import reactor.util.context.ContextView;
25+
26+
/**
27+
* Injects W3C Baggage header into gRPC metadata from Reactor's context.
28+
*/
29+
public class DaprBaggageInterceptor implements ClientInterceptor {
30+
31+
private static final Metadata.Key<String> BAGGAGE_KEY =
32+
Metadata.Key.of(Headers.BAGGAGE, Metadata.ASCII_STRING_MARSHALLER);
33+
34+
private final ContextView context;
35+
36+
/**
37+
* Creates an instance of the baggage interceptor for gRPC.
38+
*
39+
* @param context Reactor's context
40+
*/
41+
public DaprBaggageInterceptor(ContextView context) {
42+
this.context = context;
43+
}
44+
45+
@Override
46+
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
47+
MethodDescriptor<ReqT, RespT> methodDescriptor,
48+
CallOptions callOptions,
49+
Channel channel) {
50+
ClientCall<ReqT, RespT> clientCall = channel.newCall(methodDescriptor, callOptions);
51+
return new ForwardingClientCall.SimpleForwardingClientCall<>(clientCall) {
52+
@Override
53+
public void start(final Listener<RespT> responseListener, final Metadata metadata) {
54+
if (context != null && context.hasKey(Headers.BAGGAGE)) {
55+
String baggageValue = context.get(Headers.BAGGAGE).toString();
56+
if (baggageValue != null && !baggageValue.isEmpty()) {
57+
metadata.put(BAGGAGE_KEY, baggageValue);
58+
}
59+
}
60+
super.start(responseListener, metadata);
61+
}
62+
};
63+
}
64+
}

0 commit comments

Comments
 (0)