• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #18720

pending completion
#18720

push

github-actions

web-flow
xds: Encode the service authority in XdsNameResolver (#10207)

Encode the service authority before passing it into gRPC util in the xDS name resolver to handle xDS requests which might contain multiple slashes. Example: xds:///path/to/service:port.

As currently the underlying Java URI library does not break the encoded authority into host/port correctly simplify the check to just look for '@' as we are only interested in checking for user info to validate the authority for HTTP.

This change also leads to few changes in unit tests that relied on this check for invalid authorities which now will be considered valid.

Just like #9376, depending on Guava packages such as URLEscapers or PercentEscapers leads to internal failures(Ex: Unresolvable reference to com.google.common.escape.Escaper from io.grpc.internal.GrpcUtil). To avoid these issues create an in house version that is heavily inspired by grpc-go/grpc.

30655 of 34740 relevant lines covered (88.24%)

0.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.88
/../core/src/main/java/io/grpc/internal/GrpcUtil.java
1
/*
2
 * Copyright 2014 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc.internal;
18

19
import static com.google.common.base.Preconditions.checkArgument;
20
import static com.google.common.base.Preconditions.checkState;
21

22
import com.google.common.annotations.VisibleForTesting;
23
import com.google.common.base.Objects;
24
import com.google.common.base.Preconditions;
25
import com.google.common.base.Splitter;
26
import com.google.common.base.Stopwatch;
27
import com.google.common.base.Supplier;
28
import com.google.common.util.concurrent.ListenableFuture;
29
import com.google.common.util.concurrent.ThreadFactoryBuilder;
30
import io.grpc.CallOptions;
31
import io.grpc.ClientStreamTracer;
32
import io.grpc.ClientStreamTracer.StreamInfo;
33
import io.grpc.InternalChannelz.SocketStats;
34
import io.grpc.InternalLogId;
35
import io.grpc.InternalMetadata;
36
import io.grpc.InternalMetadata.TrustedAsciiMarshaller;
37
import io.grpc.LoadBalancer.PickResult;
38
import io.grpc.LoadBalancer.Subchannel;
39
import io.grpc.Metadata;
40
import io.grpc.MethodDescriptor;
41
import io.grpc.ProxiedSocketAddress;
42
import io.grpc.ProxyDetector;
43
import io.grpc.Status;
44
import io.grpc.internal.ClientStreamListener.RpcProgress;
45
import io.grpc.internal.SharedResourceHolder.Resource;
46
import io.grpc.internal.StreamListener.MessageProducer;
47
import java.io.Closeable;
48
import java.io.IOException;
49
import java.io.InputStream;
50
import java.lang.reflect.InvocationTargetException;
51
import java.lang.reflect.Method;
52
import java.net.HttpURLConnection;
53
import java.net.InetSocketAddress;
54
import java.net.SocketAddress;
55
import java.net.URI;
56
import java.net.URISyntaxException;
57
import java.nio.charset.Charset;
58
import java.util.Arrays;
59
import java.util.Collection;
60
import java.util.Collections;
61
import java.util.EnumSet;
62
import java.util.HashSet;
63
import java.util.List;
64
import java.util.Locale;
65
import java.util.Set;
66
import java.util.concurrent.Executor;
67
import java.util.concurrent.ExecutorService;
68
import java.util.concurrent.Executors;
69
import java.util.concurrent.ScheduledExecutorService;
70
import java.util.concurrent.ThreadFactory;
71
import java.util.concurrent.TimeUnit;
72
import java.util.logging.Level;
73
import java.util.logging.Logger;
74
import javax.annotation.Nullable;
75
import javax.annotation.concurrent.Immutable;
76

77
/**
78
 * Common utilities for GRPC.
79
 */
80
public final class GrpcUtil {
81

82
  private static final Logger log = Logger.getLogger(GrpcUtil.class.getName());
1✔
83

84
  private static final Set<Status.Code> INAPPROPRIATE_CONTROL_PLANE_STATUS
1✔
85
      = Collections.unmodifiableSet(EnumSet.of(
1✔
86
          Status.Code.OK,
87
          Status.Code.INVALID_ARGUMENT,
88
          Status.Code.NOT_FOUND,
89
          Status.Code.ALREADY_EXISTS,
90
          Status.Code.FAILED_PRECONDITION,
91
          Status.Code.ABORTED,
92
          Status.Code.OUT_OF_RANGE,
93
          Status.Code.DATA_LOSS));
94

95
  public static final Charset US_ASCII = Charset.forName("US-ASCII");
1✔
96

97
  /**
98
   * {@link io.grpc.Metadata.Key} for the timeout header.
99
   */
100
  public static final Metadata.Key<Long> TIMEOUT_KEY =
1✔
101
          Metadata.Key.of(GrpcUtil.TIMEOUT, new TimeoutMarshaller());
1✔
102

103
  /**
104
   * {@link io.grpc.Metadata.Key} for the message encoding header.
105
   */
106
  public static final Metadata.Key<String> MESSAGE_ENCODING_KEY =
1✔
107
          Metadata.Key.of(GrpcUtil.MESSAGE_ENCODING, Metadata.ASCII_STRING_MARSHALLER);
1✔
108

109
  /**
110
   * {@link io.grpc.Metadata.Key} for the accepted message encodings header.
111
   */
112
  public static final Metadata.Key<byte[]> MESSAGE_ACCEPT_ENCODING_KEY =
1✔
113
      InternalMetadata.keyOf(GrpcUtil.MESSAGE_ACCEPT_ENCODING, new AcceptEncodingMarshaller());
1✔
114

115
  /**
116
   * {@link io.grpc.Metadata.Key} for the stream's content encoding header.
117
   */
118
  public static final Metadata.Key<String> CONTENT_ENCODING_KEY =
1✔
119
      Metadata.Key.of(GrpcUtil.CONTENT_ENCODING, Metadata.ASCII_STRING_MARSHALLER);
1✔
120

121
  /**
122
   * {@link io.grpc.Metadata.Key} for the stream's accepted content encoding header.
123
   */
124
  public static final Metadata.Key<byte[]> CONTENT_ACCEPT_ENCODING_KEY =
1✔
125
      InternalMetadata.keyOf(GrpcUtil.CONTENT_ACCEPT_ENCODING, new AcceptEncodingMarshaller());
1✔
126

127
  static final Metadata.Key<String> CONTENT_LENGTH_KEY =
1✔
128
      Metadata.Key.of("content-length", Metadata.ASCII_STRING_MARSHALLER);
1✔
129

130
  private static final class AcceptEncodingMarshaller implements TrustedAsciiMarshaller<byte[]> {
131
    @Override
132
    public byte[] toAsciiString(byte[] value) {
133
      return value;
1✔
134
    }
135

136
    @Override
137
    public byte[] parseAsciiString(byte[] serialized) {
138
      return serialized;
1✔
139
    }
140
  }
141

142
  /**
143
   * {@link io.grpc.Metadata.Key} for the Content-Type request/response header.
144
   */
145
  public static final Metadata.Key<String> CONTENT_TYPE_KEY =
1✔
146
          Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER);
1✔
147

148
  /**
149
   * {@link io.grpc.Metadata.Key} for the Transfer encoding.
150
   */
151
  public static final Metadata.Key<String> TE_HEADER =
1✔
152
      Metadata.Key.of("te", Metadata.ASCII_STRING_MARSHALLER);
1✔
153

154
  /**
155
   * {@link io.grpc.Metadata.Key} for the Content-Type request/response header.
156
   */
157
  public static final Metadata.Key<String> USER_AGENT_KEY =
1✔
158
          Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER);
1✔
159

160
  /**
161
   * The default port for plain-text connections.
162
   */
163
  public static final int DEFAULT_PORT_PLAINTEXT = 80;
164

165
  /**
166
   * The default port for SSL connections.
167
   */
168
  public static final int DEFAULT_PORT_SSL = 443;
169

170
  /**
171
   * Content-Type used for GRPC-over-HTTP/2.
172
   */
173
  public static final String CONTENT_TYPE_GRPC = "application/grpc";
174

175
  /**
176
   * The HTTP method used for GRPC requests.
177
   */
178
  public static final String HTTP_METHOD = "POST";
179

180
  /**
181
   * The TE (transport encoding) header for requests over HTTP/2.
182
   */
183
  public static final String TE_TRAILERS = "trailers";
184

185
  /**
186
   * The Timeout header name.
187
   */
188
  public static final String TIMEOUT = "grpc-timeout";
189

190
  /**
191
   * The message encoding (i.e. compression) that can be used in the stream.
192
   */
193
  public static final String MESSAGE_ENCODING = "grpc-encoding";
194

195
  /**
196
   * The accepted message encodings (i.e. compression) that can be used in the stream.
197
   */
198
  public static final String MESSAGE_ACCEPT_ENCODING = "grpc-accept-encoding";
199

200
  /**
201
   * The content-encoding used to compress the full gRPC stream.
202
   */
203
  public static final String CONTENT_ENCODING = "content-encoding";
204

205
  /**
206
   * The accepted content-encodings that can be used to compress the full gRPC stream.
207
   */
208
  public static final String CONTENT_ACCEPT_ENCODING = "accept-encoding";
209

210
  /**
211
   * The default maximum uncompressed size (in bytes) for inbound messages. Defaults to 4 MiB.
212
   */
213
  public static final int DEFAULT_MAX_MESSAGE_SIZE = 4 * 1024 * 1024;
214

215
  /**
216
   * The default maximum size (in bytes) for inbound header/trailer.
217
   */
218
  // Update documentation in public-facing Builders when changing this value.
219
  public static final int DEFAULT_MAX_HEADER_LIST_SIZE = 8192;
220

221
  public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults();
1✔
222

223
  private static final String IMPLEMENTATION_VERSION = "1.58.0-SNAPSHOT"; // CURRENT_GRPC_VERSION
224

225
  /**
226
   * The default timeout in nanos for a keepalive ping request.
227
   */
228
  public static final long DEFAULT_KEEPALIVE_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(20L);
1✔
229

230
  /**
231
   * The magic keepalive time value that disables client keepalive.
232
   */
233
  public static final long KEEPALIVE_TIME_NANOS_DISABLED = Long.MAX_VALUE;
234

235
  /**
236
   * The default delay in nanos for server keepalive.
237
   */
238
  public static final long DEFAULT_SERVER_KEEPALIVE_TIME_NANOS = TimeUnit.HOURS.toNanos(2L);
1✔
239

240
  /**
241
   * The default timeout in nanos for a server keepalive ping request.
242
   */
243
  public static final long DEFAULT_SERVER_KEEPALIVE_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(20L);
1✔
244

245
  /**
246
   * The magic keepalive time value that disables keepalive.
247
   */
248
  public static final long SERVER_KEEPALIVE_TIME_NANOS_DISABLED = Long.MAX_VALUE;
249

250
  /**
251
   * The default proxy detector.
252
   */
253
  public static final ProxyDetector DEFAULT_PROXY_DETECTOR = new ProxyDetectorImpl();
1✔
254

255
  /**
256
   * A proxy detector that always claims no proxy is needed.
257
   */
258
  public static final ProxyDetector NOOP_PROXY_DETECTOR = new ProxyDetector() {
1✔
259
    @Nullable
260
    @Override
261
    public ProxiedSocketAddress proxyFor(SocketAddress targetServerAddress) {
262
      return null;
1✔
263
    }
264
  };
265

266
  /**
267
   * The very default load-balancing policy.
268
   */
269
  public static final String DEFAULT_LB_POLICY = "pick_first";
270

271
  /**
272
   * RPCs created on the Channel returned by {@link io.grpc.LoadBalancer.Subchannel#asChannel}
273
   * will have this option with value {@code true}.  They will be treated differently from
274
   * the ones created by application.
275
   */
276
  public static final CallOptions.Key<Boolean> CALL_OPTIONS_RPC_OWNED_BY_BALANCER =
1✔
277
      CallOptions.Key.create("io.grpc.internal.CALL_OPTIONS_RPC_OWNED_BY_BALANCER");
1✔
278

279
  private static final ClientStreamTracer NOOP_TRACER = new ClientStreamTracer() {};
1✔
280

281
  /**
282
   * Returns true if an RPC with the given properties should be counted when calculating the
283
   * in-use state of a transport.
284
   */
285
  public static boolean shouldBeCountedForInUse(CallOptions callOptions) {
286
    return !Boolean.TRUE.equals(callOptions.getOption(CALL_OPTIONS_RPC_OWNED_BY_BALANCER));
1✔
287
  }
288

289
  /**
290
   * Maps HTTP error response status codes to transport codes, as defined in <a
291
   * href="https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md">
292
   * http-grpc-status-mapping.md</a>. Never returns a status for which {@code status.isOk()} is
293
   * {@code true}.
294
   */
295
  public static Status httpStatusToGrpcStatus(int httpStatusCode) {
296
    return httpStatusToGrpcCode(httpStatusCode).toStatus()
1✔
297
        .withDescription("HTTP status code " + httpStatusCode);
1✔
298
  }
299

300
  private static Status.Code httpStatusToGrpcCode(int httpStatusCode) {
301
    if (httpStatusCode >= 100 && httpStatusCode < 200) {
1✔
302
      // 1xx. These headers should have been ignored.
303
      return Status.Code.INTERNAL;
1✔
304
    }
305
    switch (httpStatusCode) {
1✔
306
      case HttpURLConnection.HTTP_BAD_REQUEST:  // 400
307
      case 431: // Request Header Fields Too Large
308
        // TODO(carl-mastrangelo): this should be added to the http-grpc-status-mapping.md doc.
309
        return Status.Code.INTERNAL;
1✔
310
      case HttpURLConnection.HTTP_UNAUTHORIZED:  // 401
311
        return Status.Code.UNAUTHENTICATED;
1✔
312
      case HttpURLConnection.HTTP_FORBIDDEN:  // 403
313
        return Status.Code.PERMISSION_DENIED;
1✔
314
      case HttpURLConnection.HTTP_NOT_FOUND:  // 404
315
        return Status.Code.UNIMPLEMENTED;
1✔
316
      case 429:  // Too Many Requests
317
      case HttpURLConnection.HTTP_BAD_GATEWAY:  // 502
318
      case HttpURLConnection.HTTP_UNAVAILABLE:  // 503
319
      case HttpURLConnection.HTTP_GATEWAY_TIMEOUT:  // 504
320
        return Status.Code.UNAVAILABLE;
1✔
321
      default:
322
        return Status.Code.UNKNOWN;
1✔
323
    }
324
  }
325

326
  /**
327
   * All error codes identified by the HTTP/2 spec. Used in GOAWAY and RST_STREAM frames.
328
   */
329
  public enum Http2Error {
1✔
330
    /**
331
     * Servers implementing a graceful shutdown of the connection will send {@code GOAWAY} with
332
     * {@code NO_ERROR}. In this case it is important to indicate to the application that the
333
     * request should be retried (i.e. {@link Status#UNAVAILABLE}).
334
     */
335
    NO_ERROR(0x0, Status.UNAVAILABLE),
1✔
336
    PROTOCOL_ERROR(0x1, Status.INTERNAL),
1✔
337
    INTERNAL_ERROR(0x2, Status.INTERNAL),
1✔
338
    FLOW_CONTROL_ERROR(0x3, Status.INTERNAL),
1✔
339
    SETTINGS_TIMEOUT(0x4, Status.INTERNAL),
1✔
340
    STREAM_CLOSED(0x5, Status.INTERNAL),
1✔
341
    FRAME_SIZE_ERROR(0x6, Status.INTERNAL),
1✔
342
    REFUSED_STREAM(0x7, Status.UNAVAILABLE),
1✔
343
    CANCEL(0x8, Status.CANCELLED),
1✔
344
    COMPRESSION_ERROR(0x9, Status.INTERNAL),
1✔
345
    CONNECT_ERROR(0xA, Status.INTERNAL),
1✔
346
    ENHANCE_YOUR_CALM(0xB, Status.RESOURCE_EXHAUSTED.withDescription("Bandwidth exhausted")),
1✔
347
    INADEQUATE_SECURITY(0xC, Status.PERMISSION_DENIED.withDescription("Permission denied as "
1✔
348
        + "protocol is not secure enough to call")),
349
    HTTP_1_1_REQUIRED(0xD, Status.UNKNOWN);
1✔
350

351
    // Populate a mapping of code to enum value for quick look-up.
352
    private static final Http2Error[] codeMap = buildHttp2CodeMap();
1✔
353

354
    private static Http2Error[] buildHttp2CodeMap() {
355
      Http2Error[] errors = Http2Error.values();
1✔
356
      int size = (int) errors[errors.length - 1].code() + 1;
1✔
357
      Http2Error[] http2CodeMap = new Http2Error[size];
1✔
358
      for (Http2Error error : errors) {
1✔
359
        int index = (int) error.code();
1✔
360
        http2CodeMap[index] = error;
1✔
361
      }
362
      return http2CodeMap;
1✔
363
    }
364

365
    private final int code;
366
    // Status is not guaranteed to be deeply immutable. Don't care though, since that's only true
367
    // when there are exceptions in the Status, which is not true here.
368
    @SuppressWarnings("ImmutableEnumChecker")
369
    private final Status status;
370

371
    Http2Error(int code, Status status) {
1✔
372
      this.code = code;
1✔
373
      String description = "HTTP/2 error code: " + this.name();
1✔
374
      if (status.getDescription() != null) {
1✔
375
        description += " (" + status.getDescription() + ")";
1✔
376
      }
377
      this.status = status.withDescription(description);
1✔
378
    }
1✔
379

380
    /**
381
     * Gets the code for this error used on the wire.
382
     */
383
    public long code() {
384
      return code;
1✔
385
    }
386

387
    /**
388
     * Gets the {@link Status} associated with this HTTP/2 code.
389
     */
390
    public Status status() {
391
      return status;
1✔
392
    }
393

394
    /**
395
     * Looks up the HTTP/2 error code enum value for the specified code.
396
     *
397
     * @param code an HTTP/2 error code value.
398
     * @return the HTTP/2 error code enum or {@code null} if not found.
399
     */
400
    public static Http2Error forCode(long code) {
401
      if (code >= codeMap.length || code < 0) {
1✔
402
        return null;
1✔
403
      }
404
      return codeMap[(int) code];
1✔
405
    }
406

407
    /**
408
     * Looks up the {@link Status} from the given HTTP/2 error code. This is preferred over {@code
409
     * forCode(code).status()}, to more easily conform to HTTP/2:
410
     *
411
     * <blockquote>Unknown or unsupported error codes MUST NOT trigger any special behavior.
412
     * These MAY be treated by an implementation as being equivalent to INTERNAL_ERROR.</blockquote>
413
     *
414
     * @param code the HTTP/2 error code.
415
     * @return a {@link Status} representing the given error.
416
     */
417
    public static Status statusForCode(long code) {
418
      Http2Error error = forCode(code);
1✔
419
      if (error == null) {
1✔
420
        // This "forgets" the message of INTERNAL_ERROR while keeping the same status code.
421
        Status.Code statusCode = INTERNAL_ERROR.status().getCode();
1✔
422
        return Status.fromCodeValue(statusCode.value())
1✔
423
            .withDescription("Unrecognized HTTP/2 error code: " + code);
1✔
424
      }
425

426
      return error.status();
1✔
427
    }
428
  }
429

430
  /**
431
   * Indicates whether or not the given value is a valid gRPC content-type.
432
   */
433
  public static boolean isGrpcContentType(String contentType) {
434
    if (contentType == null) {
1✔
435
      return false;
1✔
436
    }
437

438
    if (CONTENT_TYPE_GRPC.length() > contentType.length()) {
1✔
439
      return false;
1✔
440
    }
441

442
    contentType = contentType.toLowerCase(Locale.US);
1✔
443
    if (!contentType.startsWith(CONTENT_TYPE_GRPC)) {
1✔
444
      // Not a gRPC content-type.
445
      return false;
1✔
446
    }
447

448
    if (contentType.length() == CONTENT_TYPE_GRPC.length()) {
1✔
449
      // The strings match exactly.
450
      return true;
1✔
451
    }
452

453
    // The contentType matches, but is longer than the expected string.
454
    // We need to support variations on the content-type (e.g. +proto, +json) as defined by the
455
    // gRPC wire spec.
456
    char nextChar = contentType.charAt(CONTENT_TYPE_GRPC.length());
1✔
457
    return nextChar == '+' || nextChar == ';';
1✔
458
  }
459

460
  /**
461
   * Gets the User-Agent string for the gRPC transport.
462
   */
463
  public static String getGrpcUserAgent(
464
      String transportName, @Nullable String applicationUserAgent) {
465
    StringBuilder builder = new StringBuilder();
1✔
466
    if (applicationUserAgent != null) {
1✔
467
      builder.append(applicationUserAgent);
1✔
468
      builder.append(' ');
1✔
469
    }
470
    builder.append("grpc-java-");
1✔
471
    builder.append(transportName);
1✔
472
    builder.append('/');
1✔
473
    builder.append(IMPLEMENTATION_VERSION);
1✔
474
    return builder.toString();
1✔
475
  }
476

477
  @Immutable
478
  public static final class GrpcBuildVersion {
479
    private final String userAgent;
480
    private final String implementationVersion;
481

482
    private GrpcBuildVersion(String userAgent, String implementationVersion) {
1✔
483
      this.userAgent = Preconditions.checkNotNull(userAgent, "userAgentName");
1✔
484
      this.implementationVersion =
1✔
485
          Preconditions.checkNotNull(implementationVersion, "implementationVersion");
1✔
486
    }
1✔
487

488
    public String getUserAgent() {
489
      return userAgent;
1✔
490
    }
491

492
    public String getImplementationVersion() {
493
      return implementationVersion;
1✔
494
    }
495

496
    @Override
497
    public String toString() {
498
      return userAgent + " " + implementationVersion;
1✔
499
    }
500
  }
501

502
  /**
503
   * Returns the build version of gRPC.
504
   */
505
  public static GrpcBuildVersion getGrpcBuildVersion() {
506
    return new GrpcBuildVersion("gRPC Java", IMPLEMENTATION_VERSION);
1✔
507
  }
508

509
  /**
510
   * Parse an authority into a URI for retrieving the host and port.
511
   */
512
  public static URI authorityToUri(String authority) {
513
    Preconditions.checkNotNull(authority, "authority");
1✔
514
    URI uri;
515
    try {
516
      uri = new URI(null, authority, null, null, null);
1✔
517
    } catch (URISyntaxException ex) {
1✔
518
      throw new IllegalArgumentException("Invalid authority: " + authority, ex);
1✔
519
    }
1✔
520
    return uri;
1✔
521
  }
522

523
  /**
524
   * Verify {@code authority} is valid for use with gRPC. The syntax must be valid and it must not
525
   * include userinfo.
526
   *
527
   * @return the {@code authority} provided
528
   */
529
  public static String checkAuthority(String authority) {
530
    URI uri = authorityToUri(authority);
1✔
531
    // Verify that the user Info is not provided.
532
    checkArgument(uri.getAuthority().indexOf('@') == -1,
1✔
533
        "Userinfo must not be present on authority: '%s'", authority);
534
    return authority;
1✔
535
  }
536

537
  /**
538
   * Combine a host and port into an authority string.
539
   */
540
  // There is a copy of this method in io.grpc.Grpc
541
  public static String authorityFromHostAndPort(String host, int port) {
542
    try {
543
      return new URI(null, null, host, port, null, null, null).getAuthority();
1✔
544
    } catch (URISyntaxException ex) {
1✔
545
      throw new IllegalArgumentException("Invalid host or port: " + host + " " + port, ex);
1✔
546
    }
547
  }
548

549
  /**
550
   * Shared executor for channels.
551
   */
552
  public static final Resource<Executor> SHARED_CHANNEL_EXECUTOR =
1✔
553
      new Resource<Executor>() {
1✔
554
        private static final String NAME = "grpc-default-executor";
555
        @Override
556
        public Executor create() {
557
          return Executors.newCachedThreadPool(getThreadFactory(NAME + "-%d", true));
1✔
558
        }
559

560
        @Override
561
        public void close(Executor instance) {
562
          ((ExecutorService) instance).shutdown();
1✔
563
        }
1✔
564

565
        @Override
566
        public String toString() {
567
          return NAME;
×
568
        }
569
      };
570

571
  /**
572
   * Shared single-threaded executor for managing channel timers.
573
   */
574
  public static final Resource<ScheduledExecutorService> TIMER_SERVICE =
1✔
575
      new Resource<ScheduledExecutorService>() {
1✔
576
        @Override
577
        public ScheduledExecutorService create() {
578
          // We don't use newSingleThreadScheduledExecutor because it doesn't return a
579
          // ScheduledThreadPoolExecutor.
580
          ScheduledExecutorService service = Executors.newScheduledThreadPool(
1✔
581
              1,
582
              getThreadFactory("grpc-timer-%d", true));
1✔
583

584
          // If there are long timeouts that are cancelled, they will not actually be removed from
585
          // the executors queue.  This forces immediate removal upon cancellation to avoid a
586
          // memory leak.  Reflection is used because we cannot use methods added in Java 1.7.  If
587
          // the method does not exist, we give up.  Note that the method is not present in 1.6, but
588
          // _is_ present in the android standard library.
589
          try {
590
            Method method = service.getClass().getMethod("setRemoveOnCancelPolicy", boolean.class);
1✔
591
            method.invoke(service, true);
1✔
592
          } catch (NoSuchMethodException e) {
×
593
            // no op
594
          } catch (RuntimeException e) {
×
595
            throw e;
×
596
          } catch (Exception e) {
×
597
            throw new RuntimeException(e);
×
598
          }
1✔
599

600
          return Executors.unconfigurableScheduledExecutorService(service);
1✔
601
        }
602

603
        @Override
604
        public void close(ScheduledExecutorService instance) {
605
          instance.shutdown();
1✔
606
        }
1✔
607
      };
608

609

610
  /**
611
   * Get a {@link ThreadFactory} suitable for use in the current environment.
612
   * @param nameFormat to apply to threads created by the factory.
613
   * @param daemon {@code true} if the threads the factory creates are daemon threads, {@code false}
614
   *     otherwise.
615
   * @return a {@link ThreadFactory}.
616
   */
617
  public static ThreadFactory getThreadFactory(String nameFormat, boolean daemon) {
618
    return new ThreadFactoryBuilder()
1✔
619
        .setDaemon(daemon)
1✔
620
        .setNameFormat(nameFormat)
1✔
621
        .build();
1✔
622
  }
623

624
  /**
625
   * The factory of default Stopwatches.
626
   */
627
  public static final Supplier<Stopwatch> STOPWATCH_SUPPLIER = new Supplier<Stopwatch>() {
1✔
628
      @Override
629
      public Stopwatch get() {
630
        return Stopwatch.createUnstarted();
1✔
631
      }
632
    };
633

634
  /**
635
   * Returns the host via {@link InetSocketAddress#getHostString} if it is possible,
636
   * i.e. in jdk >= 7.
637
   * Otherwise, return it via {@link InetSocketAddress#getHostName} which may incur a DNS lookup.
638
   */
639
  public static String getHost(InetSocketAddress addr) {
640
    try {
641
      Method getHostStringMethod = InetSocketAddress.class.getMethod("getHostString");
1✔
642
      return (String) getHostStringMethod.invoke(addr);
1✔
643
    } catch (NoSuchMethodException e) {
×
644
      // noop
645
    } catch (IllegalAccessException e) {
×
646
      // noop
647
    } catch (InvocationTargetException e) {
×
648
      // noop
649
    }
×
650
    return addr.getHostName();
×
651
  }
652

653
  /**
654
   * Marshals a nanoseconds representation of the timeout to and from a string representation,
655
   * consisting of an ASCII decimal representation of a number with at most 8 digits, followed by a
656
   * unit. Available units:
657
   * n = nanoseconds
658
   * u = microseconds
659
   * m = milliseconds
660
   * S = seconds
661
   * M = minutes
662
   * H = hours
663
   *
664
   * <p>The representation is greedy with respect to precision. That is, 2 seconds will be
665
   * represented as `2000000u`.</p>
666
   *
667
   * <p>See <a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests">the
668
   * request header definition</a></p>
669
   */
670
  @VisibleForTesting
671
  static class TimeoutMarshaller implements Metadata.AsciiMarshaller<Long> {
1✔
672

673
    @Override
674
    public String toAsciiString(Long timeoutNanos) {
675
      long cutoff = 100000000;
1✔
676
      TimeUnit unit = TimeUnit.NANOSECONDS;
1✔
677
      if (timeoutNanos < 0) {
1✔
678
        throw new IllegalArgumentException("Timeout too small");
×
679
      } else if (timeoutNanos < cutoff) {
1✔
680
        return timeoutNanos + "n";
1✔
681
      } else if (timeoutNanos < cutoff * 1000L) {
1✔
682
        return unit.toMicros(timeoutNanos) + "u";
1✔
683
      } else if (timeoutNanos < cutoff * 1000L * 1000L) {
1✔
684
        return unit.toMillis(timeoutNanos) + "m";
1✔
685
      } else if (timeoutNanos < cutoff * 1000L * 1000L * 1000L) {
1✔
686
        return unit.toSeconds(timeoutNanos) + "S";
1✔
687
      } else if (timeoutNanos < cutoff * 1000L * 1000L * 1000L * 60L) {
1✔
688
        return unit.toMinutes(timeoutNanos) + "M";
1✔
689
      } else {
690
        return unit.toHours(timeoutNanos) + "H";
1✔
691
      }
692
    }
693

694
    @Override
695
    public Long parseAsciiString(String serialized) {
696
      checkArgument(serialized.length() > 0, "empty timeout");
1✔
697
      checkArgument(serialized.length() <= 9, "bad timeout format");
1✔
698
      long value = Long.parseLong(serialized.substring(0, serialized.length() - 1));
1✔
699
      char unit = serialized.charAt(serialized.length() - 1);
1✔
700
      switch (unit) {
1✔
701
        case 'n':
702
          return value;
1✔
703
        case 'u':
704
          return TimeUnit.MICROSECONDS.toNanos(value);
1✔
705
        case 'm':
706
          return TimeUnit.MILLISECONDS.toNanos(value);
1✔
707
        case 'S':
708
          return TimeUnit.SECONDS.toNanos(value);
1✔
709
        case 'M':
710
          return TimeUnit.MINUTES.toNanos(value);
1✔
711
        case 'H':
712
          return TimeUnit.HOURS.toNanos(value);
1✔
713
        default:
714
          throw new IllegalArgumentException(String.format("Invalid timeout unit: %s", unit));
×
715
      }
716
    }
717
  }
718

719
  /**
720
   * Returns a transport out of a PickResult, or {@code null} if the result is "buffer".
721
   */
722
  @Nullable
723
  static ClientTransport getTransportFromPickResult(PickResult result, boolean isWaitForReady) {
724
    final ClientTransport transport;
725
    Subchannel subchannel = result.getSubchannel();
1✔
726
    if (subchannel != null) {
1✔
727
      transport = ((TransportProvider) subchannel.getInternalSubchannel()).obtainActiveTransport();
1✔
728
    } else {
729
      transport = null;
1✔
730
    }
731
    if (transport != null) {
1✔
732
      final ClientStreamTracer.Factory streamTracerFactory = result.getStreamTracerFactory();
1✔
733
      if (streamTracerFactory == null) {
1✔
734
        return transport;
1✔
735
      }
736
      return new ClientTransport() {
1✔
737
        @Override
738
        public ClientStream newStream(
739
            MethodDescriptor<?, ?> method, Metadata headers, CallOptions callOptions,
740
            ClientStreamTracer[] tracers) {
741
          StreamInfo info = StreamInfo.newBuilder().setCallOptions(callOptions).build();
1✔
742
          ClientStreamTracer streamTracer =
1✔
743
              streamTracerFactory.newClientStreamTracer(info, headers);
1✔
744
          checkState(tracers[tracers.length - 1] == NOOP_TRACER, "lb tracer already assigned");
1✔
745
          tracers[tracers.length - 1] = streamTracer;
1✔
746
          return transport.newStream(method, headers, callOptions, tracers);
1✔
747
        }
748

749
        @Override
750
        public void ping(PingCallback callback, Executor executor) {
751
          transport.ping(callback, executor);
×
752
        }
×
753

754
        @Override
755
        public InternalLogId getLogId() {
756
          return transport.getLogId();
×
757
        }
758

759
        @Override
760
        public ListenableFuture<SocketStats> getStats() {
761
          return transport.getStats();
×
762
        }
763
      };
764
    }
765
    if (!result.getStatus().isOk()) {
1✔
766
      if (result.isDrop()) {
1✔
767
        return new FailingClientTransport(
1✔
768
            replaceInappropriateControlPlaneStatus(result.getStatus()), RpcProgress.DROPPED);
1✔
769
      }
770
      if (!isWaitForReady) {
1✔
771
        return new FailingClientTransport(
1✔
772
            replaceInappropriateControlPlaneStatus(result.getStatus()), RpcProgress.PROCESSED);
1✔
773
      }
774
    }
775
    return null;
1✔
776
  }
777

778
  /** Gets stream tracers based on CallOptions. */
779
  public static ClientStreamTracer[] getClientStreamTracers(
780
      CallOptions callOptions, Metadata headers, int previousAttempts, boolean isTransparentRetry) {
781
    List<ClientStreamTracer.Factory> factories = callOptions.getStreamTracerFactories();
1✔
782
    ClientStreamTracer[] tracers = new ClientStreamTracer[factories.size() + 1];
1✔
783
    StreamInfo streamInfo = StreamInfo.newBuilder()
1✔
784
        .setCallOptions(callOptions)
1✔
785
        .setPreviousAttempts(previousAttempts)
1✔
786
        .setIsTransparentRetry(isTransparentRetry)
1✔
787
        .build();
1✔
788
    for (int i = 0; i < factories.size(); i++) {
1✔
789
      tracers[i] = factories.get(i).newClientStreamTracer(streamInfo, headers);
1✔
790
    }
791
    // Reserved to be set later by the lb as per the API contract of ClientTransport.newStream().
792
    // See also GrpcUtil.getTransportFromPickResult()
793
    tracers[tracers.length - 1] = NOOP_TRACER;
1✔
794
    return tracers;
1✔
795
  }
796

797
  /** Quietly closes all messages in MessageProducer. */
798
  static void closeQuietly(MessageProducer producer) {
799
    InputStream message;
800
    while ((message = producer.next()) != null) {
1✔
801
      closeQuietly(message);
1✔
802
    }
803
  }
1✔
804

805
  /**
806
   * Closes a Closeable, ignoring IOExceptions.
807
   * This method exists because Guava's {@code Closeables.closeQuietly()} is beta.
808
   */
809
  public static void closeQuietly(@Nullable Closeable message) {
810
    if (message == null) {
1✔
811
      return;
×
812
    }
813
    try {
814
      message.close();
1✔
815
    } catch (IOException ioException) {
×
816
      // do nothing except log
817
      log.log(Level.WARNING, "exception caught in closeQuietly", ioException);
×
818
    }
1✔
819
  }
1✔
820

821
  /** Reads {@code in} until end of stream. */
822
  public static void exhaust(InputStream in) throws IOException {
823
    byte[] buf = new byte[256];
1✔
824
    while (in.read(buf) != -1) {}
1✔
825
  }
1✔
826

827
  /**
828
   * Some status codes from the control plane are not appropritate to use in the data plane. If one
829
   * is given it will be replaced with INTERNAL, indicating a bug in the control plane
830
   * implementation.
831
   */
832
  public static Status replaceInappropriateControlPlaneStatus(Status status) {
833
    checkArgument(status != null);
1✔
834
    return INAPPROPRIATE_CONTROL_PLANE_STATUS.contains(status.getCode())
1✔
835
        ? Status.INTERNAL.withDescription(
1✔
836
        "Inappropriate status code from control plane: " + status.getCode() + " "
1✔
837
            + status.getDescription()).withCause(status.getCause()) : status;
1✔
838
  }
839

840
  /**
841
   * Checks whether the given item exists in the iterable.  This is copied from Guava Collect's
842
   * {@code Iterables.contains()} because Guava Collect is not Android-friendly thus core can't
843
   * depend on it.
844
   */
845
  static <T> boolean iterableContains(Iterable<T> iterable, T item) {
846
    if (iterable instanceof Collection) {
1✔
847
      Collection<?> collection = (Collection<?>) iterable;
×
848
      try {
849
        return collection.contains(item);
×
850
      } catch (NullPointerException e) {
×
851
        return false;
×
852
      } catch (ClassCastException e) {
×
853
        return false;
×
854
      }
855
    }
856
    for (T i : iterable) {
1✔
857
      if (Objects.equal(i, item)) {
1✔
858
        return true;
1✔
859
      }
860
    }
1✔
861
    return false;
1✔
862
  }
863

864
  /**
865
   * Percent encode the {@code authority} based on
866
   * https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.
867
   *
868
   * <p>When escaping a String, the following rules apply:
869
   *
870
   * <ul>
871
   *   <li>The alphanumeric characters "a" through "z", "A" through "Z" and "0" through "9" remain
872
   *       the same.
873
   *   <li>The unreserved characters ".", "-", "~", and "_" remain the same.
874
   *   <li>The general delimiters for authority, "[", "]", "@" and ":" remain the same.
875
   *   <li>The subdelimiters "!", "$", "&amp;", "'", "(", ")", "*", "+", ",", ";", and "=" remain
876
   *       the same.
877
   *   <li>The space character " " is converted into %20.
878
   *   <li>All other characters are converted into one or more bytes using UTF-8 encoding and each
879
   *       byte is then represented by the 3-character string "%XY", where "XY" is the two-digit,
880
   *       uppercase, hexadecimal representation of the byte value.
881
   * </ul>
882
   * 
883
   * <p>This section does not use URLEscapers from Guava Net as its not Android-friendly thus core
884
   *    can't depend on it.
885
   */
886
  public static class AuthorityEscaper {
×
887
    // Escapers should output upper case hex digits.
888
    private static final char[] UPPER_HEX_DIGITS = "0123456789ABCDEF".toCharArray();
1✔
889
    private static final Set<Character> UNRESERVED_CHARACTERS = Collections
1✔
890
        .unmodifiableSet(new HashSet<>(Arrays.asList('-', '_', '.', '~')));
1✔
891
    private static final Set<Character> SUB_DELIMS = Collections
1✔
892
        .unmodifiableSet(new HashSet<>(
1✔
893
            Arrays.asList('!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=')));
1✔
894
    private static final Set<Character> AUTHORITY_DELIMS = Collections
1✔
895
        .unmodifiableSet(new HashSet<>(Arrays.asList(':', '[', ']', '@')));
1✔
896

897
    private static boolean shouldEscape(char c) {
898
      // Only encode ASCII.
899
      if (c > 127) {
1✔
900
        return false;
1✔
901
      }
902
      // Letters don't need an escape.
903
      if (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'))) {
1✔
904
        return false;
1✔
905
      }
906
      // Numbers don't need to be escaped.
907
      if ((c >= '0' && c <= '9'))  {
1✔
908
        return false;
1✔
909
      }
910
      // Don't escape allowed characters.
911
      if (UNRESERVED_CHARACTERS.contains(c)
1✔
912
          || SUB_DELIMS.contains(c)
1✔
913
          || AUTHORITY_DELIMS.contains(c)) {
1✔
914
        return false;
1✔
915
      }
916
      return true;
1✔
917
    }
918

919
    public static String encodeAuthority(String authority) {
920
      Preconditions.checkNotNull(authority, "authority");
1✔
921
      int authorityLength = authority.length();
1✔
922
      int hexCount = 0;
1✔
923
      // Calculate how many characters actually need escaping.
924
      for (int index = 0; index < authorityLength; index++) {
1✔
925
        char c = authority.charAt(index);
1✔
926
        if (shouldEscape(c)) {
1✔
927
          hexCount++;
1✔
928
        }
929
      }
930
      // If no char need escaping, just return the original string back.
931
      if (hexCount == 0) {
1✔
932
        return authority;
1✔
933
      }
934

935
      // Allocate enough space as encoded characters need 2 extra chars.
936
      StringBuilder encoded_authority = new StringBuilder((2 * hexCount) + authorityLength);
1✔
937
      for (int index = 0; index < authorityLength; index++) {
1✔
938
        char c = authority.charAt(index);
1✔
939
        if (shouldEscape(c)) {
1✔
940
          encoded_authority.append('%');
1✔
941
          encoded_authority.append(UPPER_HEX_DIGITS[c >>> 4]);
1✔
942
          encoded_authority.append(UPPER_HEX_DIGITS[c & 0xF]);
1✔
943
        } else {
944
          encoded_authority.append(c);
1✔
945
        }
946
      }
947
      return encoded_authority.toString();
1✔
948
    }
949
  }
950

951
  private GrpcUtil() {}
952
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc