All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glowroot.agent.shaded.grpc.internal.GrpcUtil Maven / Gradle / Ivy

There is a newer version: 0.9.28
Show newest version
/*
 * Copyright 2014, Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *    * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *
 *    * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.glowroot.agent.shaded.grpc.internal;

import static org.glowroot.agent.shaded.grpc.Status.Code.CANCELLED;
import static org.glowroot.agent.shaded.grpc.Status.Code.DEADLINE_EXCEEDED;

import org.glowroot.agent.shaded.google.common.annotations.VisibleForTesting;
import org.glowroot.agent.shaded.google.common.base.Preconditions;
import org.glowroot.agent.shaded.google.common.util.concurrent.ThreadFactoryBuilder;

import org.glowroot.agent.shaded.grpc.Metadata;
import org.glowroot.agent.shaded.grpc.Status;
import org.glowroot.agent.shaded.grpc.internal.SharedResourceHolder.Resource;

import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.EnumSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

import javax.annotation.Nullable;

/**
 * Common utilities for GRPC.
 */
public final class GrpcUtil {

  /**
   * {@link org.glowroot.agent.shaded.grpc.Metadata.Key} for the timeout header.
   */
  public static final Metadata.Key TIMEOUT_KEY =
          Metadata.Key.of(GrpcUtil.TIMEOUT, new TimeoutMarshaller());

  /**
   * {@link org.glowroot.agent.shaded.grpc.Metadata.Key} for the message encoding header.
   */
  public static final Metadata.Key MESSAGE_ENCODING_KEY =
          Metadata.Key.of(GrpcUtil.MESSAGE_ENCODING, Metadata.ASCII_STRING_MARSHALLER);

  /**
   * {@link org.glowroot.agent.shaded.grpc.Metadata.Key} for the :authority pseudo header.
   *
   * 

Don't actually serialized this. * *

TODO(carl-mastrangelo): This is a hack and should exist as shortly as possible. Remove it * once a cleaner alternative exists (passing it directly into the transport, etc.) */ public static final Metadata.Key AUTHORITY_KEY = Metadata.Key.of("grpc-authority", Metadata.ASCII_STRING_MARSHALLER); /** * {@link org.glowroot.agent.shaded.grpc.Metadata.Key} for the Content-Type request/response header. */ public static final Metadata.Key CONTENT_TYPE_KEY = Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER); /** * {@link org.glowroot.agent.shaded.grpc.Metadata.Key} for the Content-Type request/response header. */ public static final Metadata.Key USER_AGENT_KEY = Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER); /** * Content-Type used for GRPC-over-HTTP/2. */ public static final String CONTENT_TYPE_GRPC = "application/grpc"; /** * The HTTP method used for GRPC requests. */ public static final String HTTP_METHOD = "POST"; /** * The TE (transport encoding) header for requests over HTTP/2. */ public static final String TE_TRAILERS = "trailers"; /** * The Timeout header name. */ public static final String TIMEOUT = "grpc-timeout"; /** * The message encoding (i.e. compression) that can be used in the stream. */ public static final String MESSAGE_ENCODING = "grpc-encoding"; /** * The default maximum uncompressed size (in bytes) for inbound messages. Defaults to 100 MiB. */ public static final int DEFAULT_MAX_MESSAGE_SIZE = 100 * 1024 * 1024; /** * The set of valid status codes for client cancellation. */ public static final Set CANCEL_REASONS = EnumSet.of(CANCELLED, DEADLINE_EXCEEDED, Status.Code.INTERNAL, Status.Code.UNKNOWN); /** * Maps HTTP error response status codes to transport codes. */ public static Status httpStatusToGrpcStatus(int httpStatusCode) { // Specific HTTP code handling. switch (httpStatusCode) { case HttpURLConnection.HTTP_UNAUTHORIZED: // 401 return Status.UNAUTHENTICATED; case HttpURLConnection.HTTP_FORBIDDEN: // 403 return Status.PERMISSION_DENIED; default: } // Generic HTTP code handling. if (httpStatusCode < 100) { // 0xx. These don't exist. return Status.UNKNOWN; } if (httpStatusCode < 200) { // 1xx. These headers should have been ignored. return Status.INTERNAL; } if (httpStatusCode < 300) { // 2xx return Status.OK; } return Status.UNKNOWN; } /** * All error codes identified by the HTTP/2 spec. */ public enum Http2Error { NO_ERROR(0x0, Status.INTERNAL), PROTOCOL_ERROR(0x1, Status.INTERNAL), INTERNAL_ERROR(0x2, Status.INTERNAL), FLOW_CONTROL_ERROR(0x3, Status.INTERNAL), SETTINGS_TIMEOUT(0x4, Status.INTERNAL), STREAM_CLOSED(0x5, Status.INTERNAL), FRAME_SIZE_ERROR(0x6, Status.INTERNAL), REFUSED_STREAM(0x7, Status.UNAVAILABLE), CANCEL(0x8, Status.CANCELLED), COMPRESSION_ERROR(0x9, Status.INTERNAL), CONNECT_ERROR(0xA, Status.INTERNAL), ENHANCE_YOUR_CALM(0xB, Status.RESOURCE_EXHAUSTED.withDescription("Bandwidth exhausted")), INADEQUATE_SECURITY(0xC, Status.PERMISSION_DENIED.withDescription("Permission denied as " + "protocol is not secure enough to call")), HTTP_1_1_REQUIRED(0xD, Status.UNKNOWN); // Populate a mapping of code to enum value for quick look-up. private static final Http2Error[] codeMap; static { Http2Error[] errors = Http2Error.values(); int size = (int) errors[errors.length - 1].code() + 1; codeMap = new Http2Error[size]; for (Http2Error error : errors) { int index = (int) error.code(); codeMap[index] = error; } } private final int code; private final Status status; Http2Error(int code, Status status) { this.code = code; this.status = status.augmentDescription("HTTP/2 error code: " + this.name()); } /** * Gets the code for this error used on the wire. */ public long code() { return code; } /** * Gets the {@link Status} associated with this HTTP/2 code. */ public Status status() { return status; } /** * Looks up the HTTP/2 error code enum value for the specified code. * * @param code an HTTP/2 error code value. * @return the HTTP/2 error code enum or {@code null} if not found. */ public static Http2Error forCode(long code) { if (code >= codeMap.length || code < 0) { return null; } return codeMap[(int) code]; } /** * Looks up the {@link Status} from the given HTTP/2 error code. This is preferred over {@code * forCode(code).status()}, to more easily conform to HTTP/2: * *

Unknown or unsupported error codes MUST NOT trigger any special behavior. * These MAY be treated by an implementation as being equivalent to INTERNAL_ERROR.
* * @param code the HTTP/2 error code. * @return a {@link Status} representing the given error. */ public static Status statusForCode(long code) { Http2Error error = forCode(code); if (error == null) { // This "forgets" the message of INTERNAL_ERROR while keeping the same status code. Status.Code statusCode = INTERNAL_ERROR.status().getCode(); return Status.fromCodeValue(statusCode.value()) .withDescription("Unrecognized HTTP/2 error code: " + code); } return error.status(); } } /** * Indicates whether or not the given value is a valid gRPC content-type. */ public static boolean isGrpcContentType(String contentType) { if (contentType == null) { return false; } if (CONTENT_TYPE_GRPC.length() > contentType.length()) { return false; } contentType = contentType.toLowerCase(); if (!contentType.startsWith(CONTENT_TYPE_GRPC)) { // Not a gRPC content-type. return false; } if (contentType.length() == CONTENT_TYPE_GRPC.length()) { // The strings match exactly. return true; } // The contentType matches, but is longer than the expected string. // We need to support variations on the content-type (e.g. +proto, +json) as defined by the // gRPC wire spec. char nextChar = contentType.charAt(CONTENT_TYPE_GRPC.length()); return nextChar == '+' || nextChar == ';'; } /** * Gets the User-Agent string for the gRPC transport. */ public static String getGrpcUserAgent(String transportName, @Nullable String applicationUserAgent) { StringBuilder builder = new StringBuilder(); if (applicationUserAgent != null) { builder.append(applicationUserAgent); builder.append(' '); } builder.append("grpc-java-"); builder.append(transportName); String version = GrpcUtil.class.getPackage().getImplementationVersion(); if (version != null) { builder.append("/"); builder.append(version); } return builder.toString(); } /** * Parse an authority into a URI for retrieving the host and port. */ public static URI authorityToUri(String authority) { Preconditions.checkNotNull(authority, "authority"); URI uri; try { uri = new URI(null, authority, null, null, null); } catch (URISyntaxException ex) { throw new IllegalArgumentException("Invalid authority: " + authority, ex); } if (uri.getUserInfo() != null) { throw new IllegalArgumentException( "Userinfo must not be present on authority: " + authority); } return uri; } /** * Verify {@code authority} is valid for use with gRPC. The syntax must be valid and it must not * include userinfo. * * @return the {@code authority} provided */ public static String checkAuthority(String authority) { authorityToUri(authority); return authority; } /** * Combine a host and port into an authority string. */ public static String authorityFromHostAndPort(String host, int port) { try { return new URI(null, null, host, port, null, null, null).getAuthority(); } catch (URISyntaxException ex) { throw new IllegalArgumentException("Invalid host or port: " + host + " " + port, ex); } } /** * Shared executor for channels. */ static final Resource SHARED_CHANNEL_EXECUTOR = new Resource() { private static final String name = "grpc-default-executor"; @Override public ExecutorService create() { return Executors.newCachedThreadPool(new ThreadFactoryBuilder() .setDaemon(true) .setNameFormat(name + "-%d") .build()); } @Override public void close(ExecutorService instance) { instance.shutdown(); } @Override public String toString() { return name; } }; /** * Shared executor for managing channel timers. */ public static final Resource TIMER_SERVICE = new Resource() { @Override public ScheduledExecutorService create() { return Executors.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder() .setDaemon(true) .setNameFormat("grpc-timer-%d") .build()); } @Override public void close(ScheduledExecutorService instance) { instance.shutdown(); } }; /** * Marshals a microseconds representation of the timeout to and from a string representation, * consisting of an ASCII decimal representation of a number with at most 8 digits, followed by a * unit: * u = microseconds * m = milliseconds * S = seconds * M = minutes * H = hours * *

The representation is greedy with respect to precision. That is, 2 seconds will be * represented as `2000000u`.

* *

See the * request header definition

*/ @VisibleForTesting static class TimeoutMarshaller implements Metadata.AsciiMarshaller { @Override public String toAsciiString(Long timeoutMicros) { Preconditions.checkArgument(timeoutMicros >= 0, "Negative timeout"); long timeout; String timeoutUnit; // the smallest integer with 9 digits int cutoff = 100000000; if (timeoutMicros < cutoff) { timeout = timeoutMicros; timeoutUnit = "u"; } else if (timeoutMicros / 1000 < cutoff) { timeout = timeoutMicros / 1000; timeoutUnit = "m"; } else if (timeoutMicros / (1000 * 1000) < cutoff) { timeout = timeoutMicros / (1000 * 1000); timeoutUnit = "S"; } else if (timeoutMicros / (60 * 1000 * 1000) < cutoff) { timeout = timeoutMicros / (60 * 1000 * 1000); timeoutUnit = "M"; } else if (timeoutMicros / (60L * 60L * 1000L * 1000L) < cutoff) { timeout = timeoutMicros / (60L * 60L * 1000L * 1000L); timeoutUnit = "H"; } else { throw new IllegalArgumentException("Timeout too large"); } return Long.toString(timeout) + timeoutUnit; } @Override public Long parseAsciiString(String serialized) { String valuePart = serialized.substring(0, serialized.length() - 1); char unit = serialized.charAt(serialized.length() - 1); long factor; switch (unit) { case 'u': factor = 1; break; case 'm': factor = 1000L; break; case 'S': factor = 1000L * 1000L; break; case 'M': factor = 60L * 1000L * 1000L; break; case 'H': factor = 60L * 60L * 1000L * 1000L; break; default: throw new IllegalArgumentException(String.format("Invalid timeout unit: %s", unit)); } return Long.parseLong(valuePart) * factor; } } private GrpcUtil() {} }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy