mirror of https://github.com/grpc/grpc-java.git
core: speed up Status code and message parsing
This introduces the idea of a "Trusted" Ascii Marshaller, which is known to always produce valid ASCII byte arrays. This saves a surprising amount of garbage, since String conversion involves creating a new java.lang.StringCoding, and a sun.nio.cs.US_ASCII. There are other types that can be converted (notably Http2ClientStream's :status marshaller, which is particularly wasteful). Before: Benchmark Mode Cnt Score Error Units StatusBenchmark.codeDecode sample 641278 88.889 ± 9.673 ns/op StatusBenchmark.codeEncode sample 430800 73.014 ± 1.444 ns/op StatusBenchmark.messageDecodeEscape sample 433467 441.078 ± 58.373 ns/op StatusBenchmark.messageDecodePlain sample 676526 268.620 ± 7.849 ns/op StatusBenchmark.messageEncodeEscape sample 547350 1211.243 ± 29.907 ns/op StatusBenchmark.messageEncodePlain sample 419318 223.263 ± 9.673 ns/op After: Benchmark Mode Cnt Score Error Units StatusBenchmark.codeDecode sample 442241 48.310 ± 2.409 ns/op StatusBenchmark.codeEncode sample 622026 35.475 ± 0.642 ns/op StatusBenchmark.messageDecodeEscape sample 595572 312.407 ± 15.870 ns/op StatusBenchmark.messageDecodePlain sample 565581 99.090 ± 8.799 ns/op StatusBenchmark.messageEncodeEscape sample 479147 201.422 ± 10.765 ns/op StatusBenchmark.messageEncodePlain sample 560957 94.722 ± 1.187 ns/op Also fixes #2237 Before: Result "unaryCall1024": mean = 155710.268 ±(99.9%) 149.278 ns/op Percentiles, ns/op: p(0.0000) = 63552.000 ns/op p(50.0000) = 151552.000 ns/op p(90.0000) = 188672.000 ns/op p(95.0000) = 207360.000 ns/op p(99.0000) = 260608.000 ns/op p(99.9000) = 358912.000 ns/op p(99.9900) = 1851425.792 ns/op p(99.9990) = 11161178.767 ns/op p(99.9999) = 14985005.383 ns/op p(100.0000) = 17235968.000 ns/op Benchmark (direct) (transport) Mode Cnt Score Error Units TransportBenchmark.unaryCall1024 true NETTY sample 3205966 155710.268 ± 149.278 ns/op After: Result "unaryCall1024": mean = 147474.794 ±(99.9%) 128.733 ns/op Percentiles, ns/op: p(0.0000) = 59520.000 ns/op p(50.0000) = 144640.000 ns/op p(90.0000) = 176128.000 ns/op p(95.0000) = 190464.000 ns/op p(99.0000) = 236544.000 ns/op p(99.9000) = 314880.000 ns/op p(99.9900) = 1113084.723 ns/op p(99.9990) = 10783126.979 ns/op p(99.9999) = 13887153.242 ns/op p(100.0000) = 15253504.000 ns/op Benchmark (direct) (transport) Mode Cnt Score Error Units TransportBenchmark.unaryCall1024 true NETTY sample 3385015 147474.794 ± 128.733 ns/op
This commit is contained in:
parent
af2434375a
commit
1623063143
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016, 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 io.grpc;
|
||||||
|
|
||||||
|
import org.openjdk.jmh.annotations.Benchmark;
|
||||||
|
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||||
|
import org.openjdk.jmh.annotations.Mode;
|
||||||
|
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||||
|
import org.openjdk.jmh.annotations.Scope;
|
||||||
|
import org.openjdk.jmh.annotations.State;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/** StatusBenchmark. */
|
||||||
|
@State(Scope.Benchmark)
|
||||||
|
public class StatusBenchmark {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Javadoc comment.
|
||||||
|
*/
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.SampleTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||||
|
public byte[] messageEncodePlain() {
|
||||||
|
return Status.MESSAGE_KEY.toBytes("Unexpected RST in stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Javadoc comment.
|
||||||
|
*/
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.SampleTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||||
|
public byte[] messageEncodeEscape() {
|
||||||
|
return Status.MESSAGE_KEY.toBytes("Some Error\nWasabi and Horseradish are the same");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Javadoc comment.
|
||||||
|
*/
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.SampleTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||||
|
public String messageDecodePlain() {
|
||||||
|
return Status.MESSAGE_KEY.parseBytes(
|
||||||
|
"Unexpected RST in stream".getBytes(Charset.forName("US-ASCII")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Javadoc comment.
|
||||||
|
*/
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.SampleTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||||
|
public String messageDecodeEscape() {
|
||||||
|
return Status.MESSAGE_KEY.parseBytes(
|
||||||
|
"Some Error%10Wasabi and Horseradish are the same".getBytes(Charset.forName("US-ASCII")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Javadoc comment.
|
||||||
|
*/
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.SampleTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||||
|
public byte[] codeEncode() {
|
||||||
|
return Status.CODE_KEY.toBytes(Status.DATA_LOSS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Javadoc comment.
|
||||||
|
*/
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.SampleTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||||
|
public Status codeDecode() {
|
||||||
|
return Status.CODE_KEY.parseBytes("15".getBytes(Charset.forName("US-ASCII")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016, 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 io.grpc;
|
||||||
|
|
||||||
|
import io.grpc.Metadata.Key;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal {@link Metadata} accessor. This is intended for use by io.grpc.internal, and the
|
||||||
|
* specifically supported transport packages. If you *really* think you need to use this, contact
|
||||||
|
* the gRPC team first.
|
||||||
|
*/
|
||||||
|
@Internal
|
||||||
|
public final class InternalMetadata {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A specialized plain ASCII marshaller. Both input and output are assumed to be valid header
|
||||||
|
* ASCII.
|
||||||
|
*/
|
||||||
|
@Internal
|
||||||
|
public interface TrustedAsciiMarshaller<T> {
|
||||||
|
/**
|
||||||
|
* Serialize a metadata value to a ASCII string that contains only the characters listed in the
|
||||||
|
* class comment of {@link io.grpc.Metadata.AsciiMarshaller}. Otherwise the output may be
|
||||||
|
* considered invalid and discarded by the transport, or the call may fail.
|
||||||
|
*
|
||||||
|
* @param value to serialize
|
||||||
|
* @return serialized version of value, or null if value cannot be transmitted.
|
||||||
|
*/
|
||||||
|
byte[] toAsciiString(T value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a serialized metadata value from an ASCII string.
|
||||||
|
*
|
||||||
|
* @param serialized value of metadata to parse
|
||||||
|
* @return a parsed instance of type T
|
||||||
|
*/
|
||||||
|
T parseAsciiString(byte[] serialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy of StandardCharsets, which is only available on Java 1.7 and above.
|
||||||
|
*/
|
||||||
|
public static final Charset US_ASCII = Charset.forName("US-ASCII");
|
||||||
|
|
||||||
|
@Internal
|
||||||
|
public static <T> Key<T> keyOf(String name, TrustedAsciiMarshaller<T> marshaller) {
|
||||||
|
return Metadata.Key.of(name, marshaller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,6 +38,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
import io.grpc.InternalMetadata.TrustedAsciiMarshaller;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
|
|
@ -460,6 +462,10 @@ public final class Metadata {
|
||||||
return new AsciiKey<T>(name, marshaller);
|
return new AsciiKey<T>(name, marshaller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static <T> Key<T> of(String name, TrustedAsciiMarshaller<T> marshaller) {
|
||||||
|
return new TrustedAsciiKey<T>(name, marshaller);
|
||||||
|
}
|
||||||
|
|
||||||
private final String originalName;
|
private final String originalName;
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
@ -603,7 +609,7 @@ public final class Metadata {
|
||||||
super(name);
|
super(name);
|
||||||
Preconditions.checkArgument(
|
Preconditions.checkArgument(
|
||||||
!name.endsWith(BINARY_HEADER_SUFFIX),
|
!name.endsWith(BINARY_HEADER_SUFFIX),
|
||||||
"ASCII header is named %s. It must not end with %s",
|
"ASCII header is named %s. Only binary headers may end with %s",
|
||||||
name, BINARY_HEADER_SUFFIX);
|
name, BINARY_HEADER_SUFFIX);
|
||||||
this.marshaller = Preconditions.checkNotNull(marshaller, "marshaller");
|
this.marshaller = Preconditions.checkNotNull(marshaller, "marshaller");
|
||||||
}
|
}
|
||||||
|
|
@ -619,6 +625,32 @@ public final class Metadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class TrustedAsciiKey<T> extends Key<T> {
|
||||||
|
private final TrustedAsciiMarshaller<T> marshaller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keys have a name and an ASCII marshaller used for serialization.
|
||||||
|
*/
|
||||||
|
private TrustedAsciiKey(String name, TrustedAsciiMarshaller<T> marshaller) {
|
||||||
|
super(name);
|
||||||
|
Preconditions.checkArgument(
|
||||||
|
!name.endsWith(BINARY_HEADER_SUFFIX),
|
||||||
|
"ASCII header is named %s. Only binary headers may end with %s",
|
||||||
|
name, BINARY_HEADER_SUFFIX);
|
||||||
|
this.marshaller = Preconditions.checkNotNull(marshaller, "marshaller");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
byte[] toBytes(T value) {
|
||||||
|
return marshaller.toAsciiString(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
T parseBytes(byte[] serialized) {
|
||||||
|
return marshaller.parseAsciiString(serialized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class MetadataEntry {
|
private static class MetadataEntry {
|
||||||
Object parsed;
|
Object parsed;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
|
|
||||||
import io.grpc.Metadata.AsciiMarshaller;
|
import io.grpc.InternalMetadata.TrustedAsciiMarshaller;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
|
@ -215,11 +215,11 @@ public final class Status {
|
||||||
UNAUTHENTICATED(16);
|
UNAUTHENTICATED(16);
|
||||||
|
|
||||||
private final int value;
|
private final int value;
|
||||||
private final String valueAscii;
|
private final byte[] valueAscii;
|
||||||
|
|
||||||
private Code(int value) {
|
private Code(int value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.valueAscii = Integer.toString(value);
|
this.valueAscii = Integer.toString(value).getBytes(US_ASCII);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -233,11 +233,14 @@ public final class Status {
|
||||||
return STATUS_LIST.get(value);
|
return STATUS_LIST.get(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String valueAscii() {
|
private byte[] valueAscii() {
|
||||||
return valueAscii;
|
return valueAscii;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Charset US_ASCII = Charset.forName("US-ASCII");
|
||||||
|
private static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||||
|
|
||||||
// Create the canonical list of Status instances indexed by their code values.
|
// Create the canonical list of Status instances indexed by their code values.
|
||||||
private static final List<Status> STATUS_LIST = buildStatusList();
|
private static final List<Status> STATUS_LIST = buildStatusList();
|
||||||
|
|
||||||
|
|
@ -314,6 +317,39 @@ public final class Status {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Status fromCodeValue(byte[] asciiCodeValue) {
|
||||||
|
if (asciiCodeValue.length == 1 && asciiCodeValue[0] == '0') {
|
||||||
|
return Status.OK;
|
||||||
|
}
|
||||||
|
return fromCodeValueSlow(asciiCodeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("fallthrough")
|
||||||
|
private static Status fromCodeValueSlow(byte[] asciiCodeValue) {
|
||||||
|
int index = 0;
|
||||||
|
int codeValue = 0;
|
||||||
|
switch (asciiCodeValue.length) {
|
||||||
|
case 2:
|
||||||
|
if (asciiCodeValue[index] < '0' || asciiCodeValue[index] > '9') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
codeValue += (asciiCodeValue[index++] - '0') * 10;
|
||||||
|
// fall through
|
||||||
|
case 1:
|
||||||
|
if (asciiCodeValue[index] < '0' || asciiCodeValue[index] > '9') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
codeValue += asciiCodeValue[index] - '0';
|
||||||
|
if (codeValue < STATUS_LIST.size()) {
|
||||||
|
return STATUS_LIST.get(codeValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return UNKNOWN.withDescription("Unknown code " + new String(asciiCodeValue, US_ASCII));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a {@link Status} given a canonical error {@link Code} object.
|
* Return a {@link Status} given a canonical error {@link Code} object.
|
||||||
*/
|
*/
|
||||||
|
|
@ -350,53 +386,15 @@ public final class Status {
|
||||||
* sequence. After the input header bytes are converted into UTF-8 bytes, the new byte array is
|
* sequence. After the input header bytes are converted into UTF-8 bytes, the new byte array is
|
||||||
* reinterpretted back as a string.
|
* reinterpretted back as a string.
|
||||||
*/
|
*/
|
||||||
private static final AsciiMarshaller<String> STATUS_MESSAGE_MARSHALLER =
|
private static final InternalMetadata.TrustedAsciiMarshaller<String> STATUS_MESSAGE_MARSHALLER =
|
||||||
new AsciiMarshaller<String>() {
|
new StatusMessageMarshaller();
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toAsciiString(String value) {
|
|
||||||
// This can be made faster if necessary.
|
|
||||||
StringBuilder sb = new StringBuilder(value.length());
|
|
||||||
for (byte b : value.getBytes(Charset.forName("UTF-8"))) {
|
|
||||||
if (b >= ' ' && b < '%' || b > '%' && b < '~') {
|
|
||||||
// fast path, if it's plain ascii and not a percent, pass it through.
|
|
||||||
sb.append((char) b);
|
|
||||||
} else {
|
|
||||||
sb.append(String.format("%%%02X", b));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String parseAsciiString(String value) {
|
|
||||||
Charset transerEncoding = Charset.forName("US-ASCII");
|
|
||||||
// This can be made faster if necessary.
|
|
||||||
byte[] source = value.getBytes(transerEncoding);
|
|
||||||
ByteBuffer buf = ByteBuffer.allocate(source.length);
|
|
||||||
for (int i = 0; i < source.length; ) {
|
|
||||||
if (source[i] == '%' && i + 2 < source.length) {
|
|
||||||
try {
|
|
||||||
buf.put((byte)Integer.parseInt(new String(source, i + 1, 2, transerEncoding), 16));
|
|
||||||
i += 3;
|
|
||||||
continue;
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
// ignore, fall through, just push the bytes.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf.put(source[i]);
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
return new String(buf.array(), 0, buf.position(), Charset.forName("UTF-8"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key to bind status message to trailing metadata.
|
* Key to bind status message to trailing metadata.
|
||||||
*/
|
*/
|
||||||
@Internal
|
@Internal
|
||||||
public static final Metadata.Key<String> MESSAGE_KEY
|
public static final Metadata.Key<String> MESSAGE_KEY =
|
||||||
= Metadata.Key.of("grpc-message", STATUS_MESSAGE_MARSHALLER);
|
Metadata.Key.of("grpc-message", STATUS_MESSAGE_MARSHALLER);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract an error {@link Status} from the causal chain of a {@link Throwable}.
|
* Extract an error {@link Status} from the causal chain of a {@link Throwable}.
|
||||||
|
|
@ -571,15 +569,97 @@ public final class Status {
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class StatusCodeMarshaller implements Metadata.AsciiMarshaller<Status> {
|
private static final class StatusCodeMarshaller implements TrustedAsciiMarshaller<Status> {
|
||||||
@Override
|
@Override
|
||||||
public String toAsciiString(Status status) {
|
public byte[] toAsciiString(Status status) {
|
||||||
return status.getCode().valueAscii();
|
return status.getCode().valueAscii();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Status parseAsciiString(String serialized) {
|
public Status parseAsciiString(byte[] serialized) {
|
||||||
return fromCodeValue(Integer.valueOf(serialized));
|
return fromCodeValue(serialized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class StatusMessageMarshaller implements TrustedAsciiMarshaller<String> {
|
||||||
|
|
||||||
|
private static final byte[] HEX =
|
||||||
|
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] toAsciiString(String value) {
|
||||||
|
byte[] valueBytes = value.getBytes(UTF_8);
|
||||||
|
for (int i = 0; i < valueBytes.length; i++) {
|
||||||
|
byte b = valueBytes[i];
|
||||||
|
// If there are only non escaping characters, skip the slow path.
|
||||||
|
if (isEscapingChar(b)) {
|
||||||
|
return toAsciiStringSlow(valueBytes, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return valueBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isEscapingChar(byte b) {
|
||||||
|
return b < ' ' || b >= '~' || b == '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param valueBytes the UTF-8 bytes
|
||||||
|
* @param ri The reader index, pointed at the first byte that needs escaping.
|
||||||
|
*/
|
||||||
|
private static byte[] toAsciiStringSlow(byte[] valueBytes, int ri) {
|
||||||
|
byte[] escapedBytes = new byte[ri + (valueBytes.length - ri) * 3];
|
||||||
|
// copy over the good bytes
|
||||||
|
if (ri != 0) {
|
||||||
|
System.arraycopy(valueBytes, 0, escapedBytes, 0, ri);
|
||||||
|
}
|
||||||
|
int wi = ri;
|
||||||
|
for (; ri < valueBytes.length; ri++) {
|
||||||
|
byte b = valueBytes[ri];
|
||||||
|
// Manually implement URL encoding, per the gRPC spec.
|
||||||
|
if (isEscapingChar(b)) {
|
||||||
|
escapedBytes[wi] = '%';
|
||||||
|
escapedBytes[wi + 1] = HEX[(b >> 4) & 0xF];
|
||||||
|
escapedBytes[wi + 2] = HEX[b & 0xF];
|
||||||
|
wi += 3;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
escapedBytes[wi++] = b;
|
||||||
|
}
|
||||||
|
byte[] dest = new byte[wi];
|
||||||
|
System.arraycopy(escapedBytes, 0, dest, 0, wi);
|
||||||
|
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Use fast but deprecated String ctor
|
||||||
|
@Override
|
||||||
|
public String parseAsciiString(byte[] value) {
|
||||||
|
for (int i = 0; i < value.length; i++) {
|
||||||
|
byte b = value[i];
|
||||||
|
if (b < ' ' || b >= '~' || b == '%' && i + 2 < value.length) {
|
||||||
|
return parseAsciiStringSlow(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new String(value, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String parseAsciiStringSlow(byte[] value) {
|
||||||
|
ByteBuffer buf = ByteBuffer.allocate(value.length);
|
||||||
|
for (int i = 0; i < value.length;) {
|
||||||
|
if (value[i] == '%' && i + 2 < value.length) {
|
||||||
|
try {
|
||||||
|
buf.put((byte)Integer.parseInt(new String(value, i + 1, 2, US_ASCII), 16));
|
||||||
|
i += 3;
|
||||||
|
continue;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// ignore, fall through, just push the bytes.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.put(value[i]);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
return new String(buf.array(), 0, buf.position(), UTF_8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ package io.grpc.internal;
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
import io.grpc.InternalMetadata;
|
||||||
import io.grpc.Metadata;
|
import io.grpc.Metadata;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
|
|
||||||
|
|
@ -49,21 +50,30 @@ public abstract class Http2ClientStream extends AbstractClientStream {
|
||||||
/**
|
/**
|
||||||
* Metadata marshaller for HTTP status lines.
|
* Metadata marshaller for HTTP status lines.
|
||||||
*/
|
*/
|
||||||
private static final Metadata.AsciiMarshaller<Integer> HTTP_STATUS_LINE_MARSHALLER =
|
private static final InternalMetadata.TrustedAsciiMarshaller<Integer> HTTP_STATUS_MARSHALLER =
|
||||||
new Metadata.AsciiMarshaller<Integer>() {
|
new InternalMetadata.TrustedAsciiMarshaller<Integer>() {
|
||||||
@Override
|
@Override
|
||||||
public String toAsciiString(Integer value) {
|
public byte[] toAsciiString(Integer value) {
|
||||||
return value.toString();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RFC 7231 says status codes are 3 digits long.
|
||||||
|
*
|
||||||
|
* @see: <a href="https://tools.ietf.org/html/rfc7231#section-6">RFC 7231</a>
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Integer parseAsciiString(String serialized) {
|
public Integer parseAsciiString(byte[] serialized) {
|
||||||
return Integer.parseInt(serialized.split(" ", 2)[0]);
|
if (serialized.length >= 3) {
|
||||||
|
return (serialized[0] - '0') * 100 + (serialized[1] - '0') * 10 + (serialized[2] - '0');
|
||||||
|
}
|
||||||
|
throw new NumberFormatException(
|
||||||
|
"Malformed status code " + new String(serialized, InternalMetadata.US_ASCII));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final Metadata.Key<Integer> HTTP2_STATUS = Metadata.Key.of(":status",
|
private static final Metadata.Key<Integer> HTTP2_STATUS = InternalMetadata.keyOf(":status",
|
||||||
HTTP_STATUS_LINE_MARSHALLER);
|
HTTP_STATUS_MARSHALLER);
|
||||||
|
|
||||||
/** When non-{@code null}, {@link #transportErrorMetadata} must also be non-{@code null}. */
|
/** When non-{@code null}, {@link #transportErrorMetadata} must also be non-{@code null}. */
|
||||||
private Status transportError;
|
private Status transportError;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue