Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a8a4980
Add encodeUnsignedMedium to NumberUtils
simonbasle May 15, 2019
1f3a7d7
Add WellKnownMimeType enum with String/ID conversions
simonbasle May 15, 2019
772d0eb
Add a flyweight capable of encoding Composite Metadata
simonbasle May 15, 2019
427fcf5
Move flyweight into metadata package, change decodeToMap to decodeNext
simonbasle May 16, 2019
65ba626
Add CompositeMetadata interface with factory methods to decode/encode
simonbasle May 16, 2019
45b2203
Add first test for CompositeMetadata (decode smoke test)
simonbasle May 16, 2019
f2302da
Expose incremental decode method that decodes a single metadata entry
simonbasle May 20, 2019
e861316
WellKnownMimeType parsing now avoids exceptions (except null arguments)
simonbasle May 23, 2019
54cfe65
rework encoding / decoding API around Entry, accommodate 3 mime case
simonbasle May 24, 2019
cea0a87
No need for a CompositeMetadata interface, single concrete implem
simonbasle May 24, 2019
4364262
Expose the CompositeMetadataFlyweight for low-level, simplify API
simonbasle May 28, 2019
6a77dd2
Remove CompositeMetadata, make Entry child of flyweight
simonbasle Jun 3, 2019
644976a
Add javadoc for CompositeMetadataFlyweight methods
simonbasle Jun 3, 2019
90eafaa
Switch to a single Entry implementation, remove isPassthrough
simonbasle Jun 3, 2019
dfddaf3
Rename Entry#decodeEntry to #decode, add #decodeAll
simonbasle Jun 3, 2019
1d4093f
apply Google-style formatting
simonbasle Jun 3, 2019
f822e7e
Use precomputed array for WellKnownMimeType.fromId
simonbasle Jun 6, 2019
972d6d1
Use precomputed Map<String,WellKnownMimeType> for fromMimeType + jmh …
simonbasle Jun 6, 2019
29b1085
Several custom mime metadata header encoding improvements:
simonbasle Jun 6, 2019
195a3c3
Use Unpooled buffers in tests
simonbasle Jun 6, 2019
19d3556
Remove Entry, replace with CompositeMetadata Iterator-like abstraction
simonbasle Jun 6, 2019
3dd1567
Add an encode flyweight method that attempts compressing String
simonbasle Jun 6, 2019
1720671
Avoid moving full buffer's readerIndex when slicing entries
simonbasle Jun 7, 2019
5d76e10
CompositeMetadata as Iterable
nebhale Jun 11, 2019
dba2684
Apply googleFormat
simonbasle Jun 11, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package io.rsocket.metadata;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;

@BenchmarkMode(Mode.Throughput)
@Fork(value = 1)
@Warmup(iterations = 10)
@Measurement(iterations = 10)
@State(Scope.Thread)
public class WellKnownMimeTypePerf {

// this is the old values() looping implementation of fromIdentifier
private WellKnownMimeType fromIdValuesLoop(int id) {
if (id < 0 || id > 127) {
return WellKnownMimeType.UNPARSEABLE_MIME_TYPE;
}
for (WellKnownMimeType value : WellKnownMimeType.values()) {
if (value.getIdentifier() == id) {
return value;
}
}
return WellKnownMimeType.UNKNOWN_RESERVED_MIME_TYPE;
}

// this is the core of the old values() looping implementation of fromString
private WellKnownMimeType fromStringValuesLoop(String mimeType) {
for (WellKnownMimeType value : WellKnownMimeType.values()) {
if (mimeType.equals(value.getString())) {
return value;
}
}
return WellKnownMimeType.UNPARSEABLE_MIME_TYPE;
}

@Benchmark
public void fromIdArrayLookup(final Blackhole bh) {
// negative lookup
bh.consume(WellKnownMimeType.fromIdentifier(-10));
bh.consume(WellKnownMimeType.fromIdentifier(-1));
// too large lookup
bh.consume(WellKnownMimeType.fromIdentifier(129));
// first lookup
bh.consume(WellKnownMimeType.fromIdentifier(0));
// middle lookup
bh.consume(WellKnownMimeType.fromIdentifier(37));
// reserved lookup
bh.consume(WellKnownMimeType.fromIdentifier(63));
// last lookup
bh.consume(WellKnownMimeType.fromIdentifier(127));
}

@Benchmark
public void fromIdValuesLoopLookup(final Blackhole bh) {
// negative lookup
bh.consume(fromIdValuesLoop(-10));
bh.consume(fromIdValuesLoop(-1));
// too large lookup
bh.consume(fromIdValuesLoop(129));
// first lookup
bh.consume(fromIdValuesLoop(0));
// middle lookup
bh.consume(fromIdValuesLoop(37));
// reserved lookup
bh.consume(fromIdValuesLoop(63));
// last lookup
bh.consume(fromIdValuesLoop(127));
}

@Benchmark
public void fromStringMapLookup(final Blackhole bh) {
// unknown lookup
bh.consume(WellKnownMimeType.fromString("foo/bar"));
// first lookup
bh.consume(WellKnownMimeType.fromString(WellKnownMimeType.APPLICATION_AVRO.getString()));
// middle lookup
bh.consume(WellKnownMimeType.fromString(WellKnownMimeType.VIDEO_VP8.getString()));
// last lookup
bh.consume(
WellKnownMimeType.fromString(
WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString()));
}

@Benchmark
public void fromStringValuesLoopLookup(final Blackhole bh) {
// unknown lookup
bh.consume(fromStringValuesLoop("foo/bar"));
// first lookup
bh.consume(fromStringValuesLoop(WellKnownMimeType.APPLICATION_AVRO.getString()));
// middle lookup
bh.consume(fromStringValuesLoop(WellKnownMimeType.VIDEO_VP8.getString()));
// last lookup
bh.consume(
fromStringValuesLoop(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString()));
}
}
220 changes: 220 additions & 0 deletions rsocket-core/src/main/java/io/rsocket/metadata/CompositeMetadata.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
/*
* Copyright 2015-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.rsocket.metadata;

import static io.rsocket.metadata.CompositeMetadataFlyweight.computeNextEntryIndex;
import static io.rsocket.metadata.CompositeMetadataFlyweight.decodeMimeAndContentBuffersSlices;
import static io.rsocket.metadata.CompositeMetadataFlyweight.decodeMimeIdFromMimeBuffer;
import static io.rsocket.metadata.CompositeMetadataFlyweight.decodeMimeTypeFromMimeBuffer;
import static io.rsocket.metadata.CompositeMetadataFlyweight.hasEntry;
import static io.rsocket.metadata.CompositeMetadataFlyweight.isWellKnownMimeType;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.CompositeByteBuf;
import io.rsocket.metadata.CompositeMetadata.Entry;
import java.util.Iterator;
import reactor.util.annotation.Nullable;

/**
* An {@link Iterable} wrapper around a {@link ByteBuf} that exposes metadata entry information at
* each decoding step. This is only possible on frame types used to initiate interactions, if the
* SETUP metadata mime type was {@link WellKnownMimeType#MESSAGE_RSOCKET_COMPOSITE_METADATA}.
*
* <p>This allows efficient incremental decoding of the entries (without moving the source's {@link
* io.netty.buffer.ByteBuf#readerIndex()}). The buffer is assumed to contain just enough bytes to
* represent one or more entries (mime type compressed or not). The decoding stops when the buffer
* reaches 0 readable bytes, and fails if it contains bytes but not enough to correctly decode an
* entry.
*
* <p>A note on future-proofness: it is possible to come across a compressed mime type that this
* implementation doesn't recognize. This is likely to be due to the use of a byte id that is merely
* reserved in this implementation, but maps to a {@link WellKnownMimeType} in the implementation
* that encoded the metadata. This can be detected by detecting that an entry is a {@link
* ReservedMimeTypeEntry}. In this case {@link Entry#getMimeType()} will return {@code null}. The
* encoded id can be retrieved using {@link ReservedMimeTypeEntry#getType()}. The byte and content
* buffer should be kept around and re-encoded using {@link
* CompositeMetadataFlyweight#encodeAndAddMetadata(CompositeByteBuf, ByteBufAllocator, byte,
* ByteBuf)} in case passing that entry through is required.
*/
public final class CompositeMetadata implements Iterable<Entry> {

private final boolean retainSlices;

private final ByteBuf source;

public CompositeMetadata(ByteBuf source, boolean retainSlices) {
this.source = source;
this.retainSlices = retainSlices;
}

@Override
public Iterator<Entry> iterator() {
return new Iterator<Entry>() {

private int entryIndex = 0;

@Override
public boolean hasNext() {
return hasEntry(CompositeMetadata.this.source, this.entryIndex);
}

@Override
public Entry next() {
ByteBuf[] headerAndData =
decodeMimeAndContentBuffersSlices(
CompositeMetadata.this.source,
this.entryIndex,
CompositeMetadata.this.retainSlices);

ByteBuf header = headerAndData[0];
ByteBuf data = headerAndData[1];

this.entryIndex = computeNextEntryIndex(this.entryIndex, header, data);

if (!isWellKnownMimeType(header)) {
CharSequence typeString = decodeMimeTypeFromMimeBuffer(header);
if (typeString == null) {
throw new IllegalStateException("MIME type cannot be null");
}

return new ExplicitMimeTimeEntry(data, typeString.toString());
}

byte id = decodeMimeIdFromMimeBuffer(header);
WellKnownMimeType type = WellKnownMimeType.fromIdentifier(id);

if (WellKnownMimeType.UNKNOWN_RESERVED_MIME_TYPE == type) {
return new ReservedMimeTypeEntry(data, id);
}

return new WellKnownMimeTypeEntry(data, type);
}
};
}

/** An entry in the {@link CompositeMetadata}. */
public interface Entry {

/**
* Returns the un-decoded content of the {@link Entry}.
*
* @return the un-decoded content of the {@link Entry}
*/
ByteBuf getContent();

/**
* Returns the MIME type of the entry, if it can be decoded.
*
* @return the MIME type of the entry, if it can be decoded, otherwise {@code null}.
*/
@Nullable
String getMimeType();
}

/** An {@link Entry} backed by an explicitly declared MIME type. */
public static final class ExplicitMimeTimeEntry implements Entry {

private final ByteBuf content;

private final String type;

public ExplicitMimeTimeEntry(ByteBuf content, String type) {
this.content = content;
this.type = type;
}

@Override
public ByteBuf getContent() {
return this.content;
}

@Override
public String getMimeType() {
return this.type;
}
}

/**
* An {@link Entry} backed by a {@link WellKnownMimeType} entry, but one that is not understood by
* this implementation.
*/
public static final class ReservedMimeTypeEntry implements Entry {
private final ByteBuf content;
private final int type;

public ReservedMimeTypeEntry(ByteBuf content, int type) {
this.content = content;
this.type = type;
}

@Override
public ByteBuf getContent() {
return this.content;
}

/**
* {@inheritDoc} Since this entry represents a compressed id that couldn't be decoded, this is
* always {@code null}.
*/
@Override
public String getMimeType() {
return null;
}

/**
* Returns the reserved, but unknown {@link WellKnownMimeType} for this entry. Range is 0-127
* (inclusive).
*
* @return the reserved, but unknown {@link WellKnownMimeType} for this entry
*/
public int getType() {
return this.type;
}
}

/** An {@link Entry} backed by a {@link WellKnownMimeType}. */
public static final class WellKnownMimeTypeEntry implements Entry {

private final ByteBuf content;
private final WellKnownMimeType type;

public WellKnownMimeTypeEntry(ByteBuf content, WellKnownMimeType type) {
this.content = content;
this.type = type;
}

@Override
public ByteBuf getContent() {
return this.content;
}

@Override
public String getMimeType() {
return this.type.getString();
}

/**
* Returns the {@link WellKnownMimeType} for this entry.
*
* @return the {@link WellKnownMimeType} for this entry
*/
public WellKnownMimeType getType() {
return this.type;
}
}
}
Loading