Skip to content

Commit 8c1d38a

Browse files
author
nmittler
committed
Adding default User-Agent for netty and okhttp.
1 parent efbb655 commit 8c1d38a

11 files changed

Lines changed: 225 additions & 75 deletions

File tree

build.gradle

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,21 @@ subprojects {
2828
mavenLocal()
2929
}
3030

31+
3132
[compileJava, compileTestJava].each() {
3233
it.options.compilerArgs += ["-Xlint:unchecked", "-Xlint:deprecation", "-Xlint:-options"]
3334
it.options.encoding = "UTF-8"
3435
}
3536

37+
jar.manifest {
38+
attributes('Implementation-Title': name,
39+
'Implementation-Version': version,
40+
'Built-By': System.getProperty('user.name'),
41+
'Built-JDK': System.getProperty('java.version'),
42+
'Source-Compatibility': sourceCompatibility,
43+
'Target-Compatibility': targetCompatibility)
44+
}
45+
3646
javadoc.options {
3747
encoding = 'UTF-8'
3848
links 'https://docs.oracle.com/javase/8/docs/api/'

core/src/main/java/io/grpc/AbstractChannelBuilder.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ public String toString() {
7171
@Nullable
7272
private ExecutorService userExecutor;
7373

74+
@Nullable
75+
private String userAgent;
76+
7477
/**
7578
* Provides a custom executor.
7679
*
@@ -86,6 +89,18 @@ public final BuilderT executor(ExecutorService executor) {
8689
return (BuilderT) this;
8790
}
8891

92+
/**
93+
* Provides a custom {@code User-Agent} for the application.
94+
*
95+
* <p>It's an optional parameter. If provided, the given agent will be prepended by the
96+
* grpc {@code User-Agent}.
97+
*/
98+
@SuppressWarnings("unchecked")
99+
public final BuilderT userAgent(String userAgent) {
100+
this.userAgent = userAgent;
101+
return (BuilderT) this;
102+
}
103+
89104
/**
90105
* Builds a channel using the given parameters.
91106
*/
@@ -101,7 +116,7 @@ public ChannelImpl build() {
101116
}
102117

103118
final ChannelEssentials essentials = buildEssentials();
104-
ChannelImpl channel = new ChannelImpl(essentials.transportFactory, executor);
119+
ChannelImpl channel = new ChannelImpl(essentials.transportFactory, executor, userAgent);
105120
channel.setTerminationRunnable(new Runnable() {
106121
@Override
107122
public void run() {

core/src/main/java/io/grpc/ChannelImpl.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import java.util.concurrent.TimeUnit;
5151
import java.util.logging.Logger;
5252

53+
import javax.annotation.Nullable;
5354
import javax.annotation.concurrent.GuardedBy;
5455
import javax.annotation.concurrent.ThreadSafe;
5556

@@ -81,6 +82,8 @@ private static class NoopClientStream implements ClientStream {
8182

8283
private final ClientTransportFactory transportFactory;
8384
private final ExecutorService executor;
85+
private final String userAgent;
86+
8487
/**
8588
* All transports that are not stopped. At the very least {@link #activeTransport} will be
8689
* present, but previously used transports that still have streams or are stopping may also be
@@ -99,9 +102,11 @@ private static class NoopClientStream implements ClientStream {
99102
private boolean terminated;
100103
private Runnable terminationRunnable;
101104

102-
public ChannelImpl(ClientTransportFactory transportFactory, ExecutorService executor) {
105+
ChannelImpl(ClientTransportFactory transportFactory, ExecutorService executor,
106+
@Nullable String userAgent) {
103107
this.transportFactory = transportFactory;
104108
this.executor = executor;
109+
this.userAgent = userAgent;
105110
}
106111

107112
/** Hack to allow executors to auto-shutdown. Not for general use. */
@@ -334,13 +339,18 @@ public void start(Listener<RespT> observer, Metadata.Headers headers) {
334339
headers.put(TIMEOUT_KEY, timeoutMicros);
335340
}
336341

342+
// Fill out the User-Agent header.
343+
headers.removeAll(HttpUtil.USER_AGENT_KEY);
344+
if (userAgent != null) {
345+
headers.put(HttpUtil.USER_AGENT_KEY, userAgent);
346+
}
347+
337348
try {
338349
stream = transport.newStream(method, headers, listener);
339350
} catch (IllegalStateException ex) {
340351
// We can race with the transport and end up trying to use a terminated transport.
341352
// TODO(ejona86): Improve the API to remove the possibility of the race.
342353
closeCallPrematurely(listener, Status.fromThrowable(ex));
343-
return;
344354
}
345355
}
346356

core/src/main/java/io/grpc/Metadata.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -357,42 +357,42 @@ public String toString() {
357357
/**
358358
* Marshaller for metadata values that are serialized into raw binary.
359359
*/
360-
public static interface BinaryMarshaller<T> {
360+
public interface BinaryMarshaller<T> {
361361
/**
362362
* Serialize a metadata value to bytes.
363363
* @param value to serialize
364364
* @return serialized version of value
365365
*/
366-
public byte[] toBytes(T value);
366+
byte[] toBytes(T value);
367367

368368
/**
369369
* Parse a serialized metadata value from bytes.
370370
* @param serialized value of metadata to parse
371371
* @return a parsed instance of type T
372372
*/
373-
public T parseBytes(byte[] serialized);
373+
T parseBytes(byte[] serialized);
374374
}
375375

376376
/**
377377
* Marshaller for metadata values that are serialized into ASCII strings that contain only
378378
* printable characters and space.
379379
*/
380-
public static interface AsciiMarshaller<T> {
380+
public interface AsciiMarshaller<T> {
381381
/**
382382
* Serialize a metadata value to a ASCII string that contains only printable characters and
383383
* space.
384384
*
385385
* @param value to serialize
386386
* @return serialized version of value, or null if value cannot be transmitted.
387387
*/
388-
public String toAsciiString(T value);
388+
String toAsciiString(T value);
389389

390390
/**
391391
* Parse a serialized metadata value from an ASCII string.
392392
* @param serialized value of metadata to parse
393393
* @return a parsed instance of type T
394394
*/
395-
public T parseAsciiString(String serialized);
395+
T parseAsciiString(String serialized);
396396
}
397397

398398
/**

core/src/main/java/io/grpc/transport/Http2ClientStream.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ private Status checkContentType(Metadata headers) {
206206
return null;
207207
}
208208
contentTypeChecked = true;
209-
String contentType = headers.get(HttpUtil.CONTENT_TYPE);
209+
String contentType = headers.get(HttpUtil.CONTENT_TYPE_KEY);
210210
if (TEMP_CHECK_CONTENT_TYPE && !HttpUtil.CONTENT_TYPE_GRPC.equalsIgnoreCase(contentType)) {
211211
// Malformed content-type so report an error
212212
return Status.INTERNAL.withDescription("invalid content-type " + contentType);
@@ -218,7 +218,7 @@ private Status checkContentType(Metadata headers) {
218218
* Inspect the raw metadata and figure out what charset is being used.
219219
*/
220220
private static Charset extractCharset(Metadata headers) {
221-
String contentType = headers.get(HttpUtil.CONTENT_TYPE);
221+
String contentType = headers.get(HttpUtil.CONTENT_TYPE_KEY);
222222
if (contentType != null) {
223223
String[] split = contentType.split("charset=");
224224
try {

core/src/main/java/io/grpc/transport/HttpUtil.java

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,24 @@
3636

3737
import java.net.HttpURLConnection;
3838

39+
import javax.annotation.Nullable;
40+
3941
/**
4042
* Constants for GRPC-over-HTTP (or HTTP/2).
4143
*/
4244
public final class HttpUtil {
45+
4346
/**
44-
* The Content-Type header name. Defined here since it is not explicitly defined by the HTTP/2
45-
* spec.
47+
* {@link Metadata.Key} for the Content-Type request/response header.
4648
*/
47-
public static final Metadata.Key<String> CONTENT_TYPE =
48-
Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER);
49+
public static final Metadata.Key<String> CONTENT_TYPE_KEY =
50+
Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER);
51+
52+
/**
53+
* {@link Metadata.Key} for the Content-Type request/response header.
54+
*/
55+
public static final Metadata.Key<String> USER_AGENT_KEY =
56+
Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER);
4957

5058
/**
5159
* Content-Type used for GRPC-over-HTTP/2.
@@ -57,12 +65,6 @@ public final class HttpUtil {
5765
*/
5866
public static final String HTTP_METHOD = "POST";
5967

60-
/**
61-
* The TE header name. Defined here since it is not explicitly defined by the HTTP/2 spec.
62-
*/
63-
public static final Metadata.Key<String> TE = Metadata.Key.of("te",
64-
Metadata.ASCII_STRING_MARSHALLER);
65-
6668
/**
6769
* The TE (transport encoding) header for requests over HTTP/2.
6870
*/
@@ -136,7 +138,7 @@ public enum Http2Error {
136138
private final int code;
137139
private final Status status;
138140

139-
private Http2Error(int code, Status status) {
141+
Http2Error(int code, Status status) {
140142
this.code = code;
141143
this.status = status.augmentDescription("HTTP/2 error code: " + this.name());
142144
}
@@ -191,5 +193,23 @@ public static Status statusForCode(long code) {
191193
}
192194
}
193195

196+
/**
197+
* Gets the User-Agent string for the gRPC transport.
198+
*/
199+
public static String getGrpcUserAgent(String transportName,
200+
@Nullable String applicationUserAgent) {
201+
StringBuilder builder = new StringBuilder("grpc-java-").append(transportName);
202+
String version = HttpUtil.class.getPackage().getImplementationVersion();
203+
if (version != null) {
204+
builder.append("/");
205+
builder.append(version);
206+
}
207+
if (applicationUserAgent != null) {
208+
builder.append(' ');
209+
builder.append(applicationUserAgent);
210+
}
211+
return builder.toString();
212+
}
213+
194214
private HttpUtil() {}
195215
}

core/src/test/java/io/grpc/ChannelImplTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public class ChannelImplTest {
9494
@Before
9595
public void setUp() {
9696
MockitoAnnotations.initMocks(this);
97-
channel = new ChannelImpl(mockTransportFactory, executor);
97+
channel = new ChannelImpl(mockTransportFactory, executor, null);
9898
when(mockTransportFactory.newClientTransport()).thenReturn(mockTransport);
9999
}
100100

netty/src/main/java/io/grpc/transport/netty/Utils.java

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131

3232
package io.grpc.transport.netty;
3333

34+
import static io.grpc.transport.HttpUtil.CONTENT_TYPE_KEY;
35+
import static io.grpc.transport.HttpUtil.USER_AGENT_KEY;
3436
import static io.netty.util.CharsetUtil.UTF_8;
3537

3638
import com.google.common.base.Preconditions;
@@ -40,8 +42,7 @@
4042
import io.grpc.SharedResourceHolder.Resource;
4143
import io.grpc.transport.HttpUtil;
4244
import io.grpc.transport.TransportFrameUtil;
43-
import io.netty.buffer.ByteBuf;
44-
import io.netty.buffer.ByteBufAllocator;
45+
4546
import io.netty.channel.EventLoopGroup;
4647
import io.netty.channel.nio.NioEventLoopGroup;
4748
import io.netty.handler.codec.http2.DefaultHttp2Headers;
@@ -50,7 +51,6 @@
5051
import io.netty.util.concurrent.Future;
5152
import io.netty.util.concurrent.GenericFutureListener;
5253

53-
import java.nio.ByteBuffer;
5454
import java.util.Map;
5555
import java.util.concurrent.ExecutorService;
5656
import java.util.concurrent.Executors;
@@ -66,28 +66,20 @@ class Utils {
6666
public static final ByteString HTTP_METHOD = new ByteString(HttpUtil.HTTP_METHOD.getBytes(UTF_8));
6767
public static final ByteString HTTPS = new ByteString("https".getBytes(UTF_8));
6868
public static final ByteString HTTP = new ByteString("http".getBytes(UTF_8));
69-
public static final ByteString CONTENT_TYPE_HEADER = new ByteString(HttpUtil.CONTENT_TYPE.name()
69+
public static final ByteString CONTENT_TYPE_HEADER = new ByteString(CONTENT_TYPE_KEY.name()
7070
.getBytes(UTF_8));
7171
public static final ByteString CONTENT_TYPE_GRPC = new ByteString(
7272
HttpUtil.CONTENT_TYPE_GRPC.getBytes(UTF_8));
73-
public static final ByteString TE_HEADER = new ByteString(HttpUtil.TE.name().getBytes(UTF_8));
73+
public static final ByteString TE_HEADER = new ByteString("te".getBytes(UTF_8));
7474
public static final ByteString TE_TRAILERS = new ByteString(HttpUtil.TE_TRAILERS.getBytes(UTF_8));
75+
public static final ByteString USER_AGENT = new ByteString(USER_AGENT_KEY.name().getBytes(UTF_8));
7576

7677
public static final Resource<EventLoopGroup> DEFAULT_BOSS_EVENT_LOOP_GROUP =
7778
new DefaultEventLoopGroupResource(1, "grpc-default-boss-ELG");
7879

7980
public static final Resource<EventLoopGroup> DEFAULT_WORKER_EVENT_LOOP_GROUP =
8081
new DefaultEventLoopGroupResource(0, "grpc-default-worker-ELG");
8182

82-
/**
83-
* Copies the content of the given {@link ByteBuffer} to a new {@link ByteBuf} instance.
84-
*/
85-
static ByteBuf toByteBuf(ByteBufAllocator alloc, ByteBuffer source) {
86-
ByteBuf buf = alloc.buffer(source.remaining());
87-
buf.writeBytes(source);
88-
return buf;
89-
}
90-
9183
public static Metadata.Headers convertHeaders(Http2Headers http2Headers) {
9284
Metadata.Headers headers = new Metadata.Headers(convertHeadersToArray(http2Headers));
9385
if (http2Headers.authority() != null) {
@@ -137,6 +129,10 @@ public static Http2Headers convertClientHeaders(Metadata.Headers headers,
137129
http2Headers.path(new ByteString(headers.getPath().getBytes(UTF_8)));
138130
}
139131

132+
// Set the User-Agent header.
133+
String userAgent = HttpUtil.getGrpcUserAgent("netty", headers.get(USER_AGENT_KEY));
134+
http2Headers.set(USER_AGENT, new ByteString(userAgent.getBytes(UTF_8)));
135+
140136
return http2Headers;
141137
}
142138

@@ -165,9 +161,11 @@ private static Http2Headers convertMetadata(Metadata headers) {
165161
Http2Headers http2Headers = new DefaultHttp2Headers();
166162
byte[][] serializedHeaders = TransportFrameUtil.toHttp2Headers(headers);
167163
for (int i = 0; i < serializedHeaders.length; i += 2) {
168-
http2Headers.add(new ByteString(serializedHeaders[i], false),
169-
new ByteString(serializedHeaders[i + 1], false));
164+
ByteString name = new ByteString(serializedHeaders[i], false);
165+
ByteString value = new ByteString(serializedHeaders[i + 1], false);
166+
http2Headers.add(name, value);
170167
}
168+
171169
return http2Headers;
172170
}
173171

0 commit comments

Comments
 (0)