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.base.Preconditions;
|
||||
|
||||
import io.grpc.InternalMetadata.TrustedAsciiMarshaller;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
|
|
@ -460,6 +462,10 @@ public final class Metadata {
|
|||
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 name;
|
||||
|
|
@ -603,7 +609,7 @@ public final class Metadata {
|
|||
super(name);
|
||||
Preconditions.checkArgument(
|
||||
!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);
|
||||
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 {
|
||||
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.Objects;
|
||||
|
||||
import io.grpc.Metadata.AsciiMarshaller;
|
||||
import io.grpc.InternalMetadata.TrustedAsciiMarshaller;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
|
|
@ -215,11 +215,11 @@ public final class Status {
|
|||
UNAUTHENTICATED(16);
|
||||
|
||||
private final int value;
|
||||
private final String valueAscii;
|
||||
private final byte[] valueAscii;
|
||||
|
||||
private Code(int 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);
|
||||
}
|
||||
|
||||
private String valueAscii() {
|
||||
private byte[] 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.
|
||||
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.
|
||||
*/
|
||||
|
|
@ -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
|
||||
* reinterpretted back as a string.
|
||||
*/
|
||||
private static final AsciiMarshaller<String> STATUS_MESSAGE_MARSHALLER =
|
||||
new AsciiMarshaller<String>() {
|
||||
|
||||
@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"));
|
||||
}
|
||||
};
|
||||
private static final InternalMetadata.TrustedAsciiMarshaller<String> STATUS_MESSAGE_MARSHALLER =
|
||||
new StatusMessageMarshaller();
|
||||
|
||||
/**
|
||||
* Key to bind status message to trailing metadata.
|
||||
*/
|
||||
@Internal
|
||||
public static final Metadata.Key<String> MESSAGE_KEY
|
||||
= Metadata.Key.of("grpc-message", STATUS_MESSAGE_MARSHALLER);
|
||||
public static final Metadata.Key<String> MESSAGE_KEY =
|
||||
Metadata.Key.of("grpc-message", STATUS_MESSAGE_MARSHALLER);
|
||||
|
||||
/**
|
||||
* Extract an error {@link Status} from the causal chain of a {@link Throwable}.
|
||||
|
|
@ -571,15 +569,97 @@ public final class Status {
|
|||
.toString();
|
||||
}
|
||||
|
||||
private static class StatusCodeMarshaller implements Metadata.AsciiMarshaller<Status> {
|
||||
private static final class StatusCodeMarshaller implements TrustedAsciiMarshaller<Status> {
|
||||
@Override
|
||||
public String toAsciiString(Status status) {
|
||||
public byte[] toAsciiString(Status status) {
|
||||
return status.getCode().valueAscii();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Status parseAsciiString(String serialized) {
|
||||
return fromCodeValue(Integer.valueOf(serialized));
|
||||
public Status parseAsciiString(byte[] 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.Preconditions;
|
||||
|
||||
import io.grpc.InternalMetadata;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.Status;
|
||||
|
||||
|
|
@ -49,21 +50,30 @@ public abstract class Http2ClientStream extends AbstractClientStream {
|
|||
/**
|
||||
* Metadata marshaller for HTTP status lines.
|
||||
*/
|
||||
private static final Metadata.AsciiMarshaller<Integer> HTTP_STATUS_LINE_MARSHALLER =
|
||||
new Metadata.AsciiMarshaller<Integer>() {
|
||||
private static final InternalMetadata.TrustedAsciiMarshaller<Integer> HTTP_STATUS_MARSHALLER =
|
||||
new InternalMetadata.TrustedAsciiMarshaller<Integer>() {
|
||||
@Override
|
||||
public String toAsciiString(Integer value) {
|
||||
return value.toString();
|
||||
public byte[] toAsciiString(Integer value) {
|
||||
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
|
||||
public Integer parseAsciiString(String serialized) {
|
||||
return Integer.parseInt(serialized.split(" ", 2)[0]);
|
||||
public Integer parseAsciiString(byte[] serialized) {
|
||||
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",
|
||||
HTTP_STATUS_LINE_MARSHALLER);
|
||||
private static final Metadata.Key<Integer> HTTP2_STATUS = InternalMetadata.keyOf(":status",
|
||||
HTTP_STATUS_MARSHALLER);
|
||||
|
||||
/** When non-{@code null}, {@link #transportErrorMetadata} must also be non-{@code null}. */
|
||||
private Status transportError;
|
||||
|
|
|
|||
Loading…
Reference in New Issue