org.reaktivity.nukleus.http2.internal.Http2Connection Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nukleus-http2 Show documentation
Show all versions of nukleus-http2 Show documentation
HTTP/2 Nukleus Implementation
The newest version!
/**
* Copyright 2016-2019 The Reaktivity Project
*
* The Reaktivity Project licenses this file to you 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 org.reaktivity.nukleus.http2.internal;
import static org.reaktivity.nukleus.buffer.BufferPool.NO_SLOT;
import static org.reaktivity.nukleus.http2.internal.Http2StreamState.CLOSED;
import static org.reaktivity.nukleus.http2.internal.Http2StreamState.HALF_CLOSED_LOCAL;
import static org.reaktivity.nukleus.http2.internal.Http2StreamState.HALF_CLOSED_REMOTE;
import static org.reaktivity.nukleus.http2.internal.Http2StreamState.OPEN;
import static org.reaktivity.nukleus.http2.internal.types.stream.HpackContext.CONNECTION;
import static org.reaktivity.nukleus.http2.internal.types.stream.HpackContext.DEFAULT_ACCESS_CONTROL_ALLOW_ORIGIN;
import static org.reaktivity.nukleus.http2.internal.types.stream.HpackContext.KEEP_ALIVE;
import static org.reaktivity.nukleus.http2.internal.types.stream.HpackContext.PROXY_CONNECTION;
import static org.reaktivity.nukleus.http2.internal.types.stream.HpackContext.TE;
import static org.reaktivity.nukleus.http2.internal.types.stream.HpackContext.TRAILERS;
import static org.reaktivity.nukleus.http2.internal.types.stream.HpackContext.UPGRADE;
import static org.reaktivity.nukleus.http2.internal.types.stream.HpackHeaderFieldFW.HeaderFieldType.UNKNOWN;
import static org.reaktivity.nukleus.http2.internal.types.stream.HpackLiteralHeaderFieldFW.LiteralType.INCREMENTAL_INDEXING;
import static org.reaktivity.nukleus.http2.internal.types.stream.HpackLiteralHeaderFieldFW.LiteralType.WITHOUT_INDEXING;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.agrona.DirectBuffer;
import org.agrona.MutableDirectBuffer;
import org.agrona.collections.Int2ObjectHashMap;
import org.agrona.concurrent.UnsafeBuffer;
import org.reaktivity.nukleus.function.MessageConsumer;
import org.reaktivity.nukleus.function.MessageFunction;
import org.reaktivity.nukleus.function.MessagePredicate;
import org.reaktivity.nukleus.http2.internal.types.ArrayFW;
import org.reaktivity.nukleus.http2.internal.types.HttpHeaderFW;
import org.reaktivity.nukleus.http2.internal.types.OctetsFW;
import org.reaktivity.nukleus.http2.internal.types.String16FW;
import org.reaktivity.nukleus.http2.internal.types.StringFW;
import org.reaktivity.nukleus.http2.internal.types.control.HttpRouteExFW;
import org.reaktivity.nukleus.http2.internal.types.control.RouteFW;
import org.reaktivity.nukleus.http2.internal.types.stream.AbortFW;
import org.reaktivity.nukleus.http2.internal.types.stream.BeginFW;
import org.reaktivity.nukleus.http2.internal.types.stream.DataFW;
import org.reaktivity.nukleus.http2.internal.types.stream.EndFW;
import org.reaktivity.nukleus.http2.internal.types.stream.HpackContext;
import org.reaktivity.nukleus.http2.internal.types.stream.HpackHeaderBlockFW;
import org.reaktivity.nukleus.http2.internal.types.stream.HpackHeaderFieldFW;
import org.reaktivity.nukleus.http2.internal.types.stream.HpackHuffman;
import org.reaktivity.nukleus.http2.internal.types.stream.HpackLiteralHeaderFieldFW;
import org.reaktivity.nukleus.http2.internal.types.stream.HpackStringFW;
import org.reaktivity.nukleus.http2.internal.types.stream.Http2ContinuationFW;
import org.reaktivity.nukleus.http2.internal.types.stream.Http2DataFW;
import org.reaktivity.nukleus.http2.internal.types.stream.Http2ErrorCode;
import org.reaktivity.nukleus.http2.internal.types.stream.Http2Flags;
import org.reaktivity.nukleus.http2.internal.types.stream.Http2FrameFW;
import org.reaktivity.nukleus.http2.internal.types.stream.Http2FrameHeaderFW;
import org.reaktivity.nukleus.http2.internal.types.stream.Http2FrameType;
import org.reaktivity.nukleus.http2.internal.types.stream.Http2HeadersFW;
import org.reaktivity.nukleus.http2.internal.types.stream.Http2PingFW;
import org.reaktivity.nukleus.http2.internal.types.stream.Http2PrefaceFW;
import org.reaktivity.nukleus.http2.internal.types.stream.Http2PriorityFW;
import org.reaktivity.nukleus.http2.internal.types.stream.Http2RstStreamFW;
import org.reaktivity.nukleus.http2.internal.types.stream.Http2SettingsFW;
import org.reaktivity.nukleus.http2.internal.types.stream.Http2SettingsId;
import org.reaktivity.nukleus.http2.internal.types.stream.Http2WindowUpdateFW;
import org.reaktivity.nukleus.http2.internal.types.stream.HttpBeginExFW;
import org.reaktivity.nukleus.http2.internal.types.stream.HttpDataExFW;
import org.reaktivity.nukleus.http2.internal.types.stream.HttpEndExFW;
import org.reaktivity.nukleus.http2.internal.types.stream.ResetFW;
import org.reaktivity.nukleus.http2.internal.types.stream.WindowFW;
import org.reaktivity.nukleus.route.RouteManager;
final class Http2Connection
{
private static final Map EMPTY_HEADERS = Collections.emptyMap();
ServerStreamFactory factory;
private DecoderState decoderState;
// slab to assemble a complete HTTP2 frame
// no need for separate slab per HTTP2 stream as the frames are not fragmented
private int frameSlot = NO_SLOT;
int frameSlotLimit;
// slab to assemble a complete HTTP2 headers frame(including its continuation frames)
// no need for separate slab per HTTP2 stream as no interleaved frames of any other type
// or from any other stream
private int headersSlotIndex = NO_SLOT;
private int headersSlotOffset;
final long networkRouteId;
final long networkId;
long authorization;
int lastStreamId;
int networkReplyBudget;
int networkReplyPadding;
int outWindowThreshold = -1;
final WriteScheduler writeScheduler;
final MessageConsumer network;
final long networkReplyId;
private final HpackContext decodeContext;
private final HpackContext encodeContext;
private final MessageFunction wrapRoute;
final long networkReplyGroupId;
final Int2ObjectHashMap http2Streams; // HTTP2 stream-id --> Http2Stream
private int clientStreamCount;
private int promisedStreamCount;
private int maxClientStreamId;
private int maxPushPromiseStreamId;
private boolean goaway;
Settings initialSettings;
Settings localSettings;
Settings remoteSettings;
private boolean expectContinuation;
private int expectContinuationStreamId;
private boolean expectDynamicTableSizeUpdate = true;
long http2OutWindow;
long http2InWindow;
private final Consumer headerFieldConsumer;
private final Consumer trailerFieldConsumer;
private final HeadersContext headersContext = new HeadersContext();
private final EncodeHeadersContext encodeHeadersContext = new EncodeHeadersContext();
final Http2Writer http2Writer;
final MessageConsumer networkReply;
RouteManager router;
long traceId;
private Http2ErrorCode decodeError;
Http2Connection(
ServerStreamFactory factory,
RouteManager router,
MessageConsumer network,
long networkRouteId,
long networkId,
MessageConsumer networkReply,
long networkReplyId,
MessageFunction wrapRoute)
{
this.factory = factory;
this.router = router;
this.wrapRoute = wrapRoute;
this.network = network;
this.networkId = networkId;
this.networkRouteId = networkRouteId;
this.networkReplyId = networkReplyId;
this.http2Streams = new Int2ObjectHashMap<>();
this.localSettings = new Settings();
this.remoteSettings = new Settings();
this.decodeContext = new HpackContext(localSettings.headerTableSize, false);
this.encodeContext = new HpackContext(remoteSettings.headerTableSize, true);
this.http2Writer = factory.http2Writer;
this.writeScheduler = new Http2WriteScheduler(this, networkReply, http2Writer, networkRouteId, networkReplyId);
this.http2InWindow = localSettings.initialWindowSize;
this.http2OutWindow = remoteSettings.initialWindowSize;
this.networkReply = networkReply;
this.networkReplyGroupId = factory.supplyGroupId.getAsLong();
BiConsumer nameValue =
((BiConsumer) this::collectHeaders)
.andThen(this::validatePseudoHeaders)
.andThen(this::uppercaseHeaders)
.andThen(this::connectionHeaders)
.andThen(this::contentLengthHeader)
.andThen(this::teHeader)
.andThen(this::mapToHttp);
Consumer consumer = this::validateHeaderFieldType;
consumer = consumer.andThen(this::dynamicTableSizeUpdate);
this.headerFieldConsumer = consumer.andThen(h -> decodeHeaderField(h, nameValue));
this.trailerFieldConsumer = h -> decodeHeaderField(h, this::validateTrailerFieldName);
}
void handleBegin(
BeginFW beginRO)
{
this.authorization = beginRO.authorization();
this.decoderState = this::decodePreface;
this.initialSettings = new Settings(factory.config.serverConcurrentStreams(), 0);
writeScheduler.settings(initialSettings.maxConcurrentStreams, initialSettings.initialWindowSize);
factory.counters.settingsFramesWritten.getAsLong();
}
void handleData(
DataFW data)
{
final long traceId = data.trace();
final OctetsFW payload = data.payload();
final DirectBuffer buffer = payload.buffer();
final int offset = payload.offset();
final int limit = payload.limit();
DirectBuffer decodeBuffer = buffer;
int decodeOffset = offset;
int decodeLimit = limit;
if (frameSlot != NO_SLOT)
{
final MutableDirectBuffer frameBuffer = factory.framePool.buffer(frameSlot);
frameBuffer.putBytes(frameSlotLimit, buffer, offset, limit - offset);
frameSlotLimit += limit - offset;
decodeBuffer = frameBuffer;
decodeOffset = 0;
decodeLimit = frameSlotLimit;
}
this.traceId = traceId;
int decodeProgress = 0;
while (decodeOffset < decodeLimit && decodeError == null)
{
decodeProgress = decoderState.decode(decodeBuffer, decodeOffset, decodeLimit);
if (decodeProgress <= 0)
{
// incomplete frame
break;
}
decodeOffset += decodeProgress;
}
this.traceId = 0;
final int decodeRemaining = decodeLimit - decodeOffset;
if (decodeProgress >= 0 && decodeRemaining > 0)
{
if (frameSlot == NO_SLOT)
{
assert frameSlotLimit == 0;
final int newFrameSlot = factory.framePool.acquire(networkId);
if (newFrameSlot != NO_SLOT)
{
frameSlot = newFrameSlot;
}
else
{
decodeProgress = -1; // error
}
}
if (frameSlot != NO_SLOT)
{
final MutableDirectBuffer frameBuffer = factory.framePool.buffer(frameSlot);
frameBuffer.putBytes(0, decodeBuffer, decodeOffset, decodeRemaining);
frameSlotLimit = decodeRemaining;
}
}
else
{
releaseFrameSlot();
}
if (decodeError != null)
{
error(decodeError);
}
if (decodeProgress < 0 ||
decodeError != null && decodeError != Http2ErrorCode.NO_ERROR)
{
// TODO: use traceId ??
http2Streams.forEach((i, s) -> s.onAbort(traceId));
doResetNetworkAndCleanup();
}
}
void handleAbort(
long traceId)
{
http2Streams.forEach((i, s) -> s.onAbort(traceId));
doCleanup();
}
void handleReset(
ResetFW reset)
{
http2Streams.forEach((i, s) -> s.onReset(reset.trace()));
doCleanup();
}
void handleEnd(
EndFW end)
{
decoderState = (b, o, l) -> o;
http2Streams.forEach((i, s) -> s.onEnd());
writeScheduler.doEnd();
doCleanup();
}
private int decodePreface(
final DirectBuffer buffer,
final int offset,
final int limit)
{
final Http2PrefaceFW preface = factory.prefaceRO.tryWrap(buffer, offset, limit);
int decodeProgress = 0;
if (preface != null)
{
if (preface.error())
{
decodeProgress = -1;
this.decodeError = Http2ErrorCode.PROTOCOL_ERROR;
}
else
{
decodeProgress = preface.sizeof();
this.decoderState = this::decodeFrame;
}
}
return decodeProgress;
}
private int decodeFrame(
final DirectBuffer buffer,
final int offset,
final int limit)
{
final Http2FrameFW http2Frame = factory.http2RO.tryWrap(buffer, offset, limit);
if (http2Frame != null)
{
if (http2Frame.payloadLength() > localSettings.maxFrameSize)
{
this.decodeError = Http2ErrorCode.FRAME_SIZE_ERROR;
}
else if (http2Frame.streamId() == 0)
{
onConnectionFrame(http2Frame);
}
else
{
onStreamFrame(http2Frame);
}
}
else
{
final Http2FrameHeaderFW http2FrameHeader = factory.http2HeaderRO.tryWrap(buffer, offset, limit);
if (http2FrameHeader != null)
{
if (http2FrameHeader.payloadLength() > localSettings.maxFrameSize)
{
this.decodeError = Http2ErrorCode.FRAME_SIZE_ERROR;
}
}
}
return http2Frame != null ? http2Frame.sizeof() : 0;
}
private void onConnectionFrame(
final Http2FrameFW http2Frame)
{
switch (http2Frame.type())
{
case SETTINGS:
factory.counters.settingsFramesRead.getAsLong();
onConnectionSettings(http2Frame);
break;
case PING:
factory.counters.pingFramesRead.getAsLong();
onConnectionPing(http2Frame);
break;
case GO_AWAY:
factory.counters.goawayFramesRead.getAsLong();
onConnectionGoAway(http2Frame);
break;
case WINDOW_UPDATE:
factory.counters.windowUpdateFramesRead.getAsLong();
onConnectionWindowUpdate(http2Frame);
break;
case UNKNOWN:
break;
default:
this.decodeError = Http2ErrorCode.PROTOCOL_ERROR;
break;
}
}
private void onStreamFrame(
final Http2FrameFW http2Frame)
{
int streamId = http2Frame.streamId();
Http2FrameType type = http2Frame.type();
if ((streamId & 0x01) != 0x01 &&
type != Http2FrameType.WINDOW_UPDATE &&
type != Http2FrameType.RST_STREAM &&
type != Http2FrameType.PRIORITY)
{
decodeError = Http2ErrorCode.PROTOCOL_ERROR;
return;
}
if (expectContinuation &&
(type != Http2FrameType.CONTINUATION || streamId != expectContinuationStreamId))
{
this.decodeError = Http2ErrorCode.PROTOCOL_ERROR;
return;
}
if ((streamId & 0x01) == 0x01 &&
streamId > maxClientStreamId &&
type != Http2FrameType.HEADERS &&
type != Http2FrameType.PRIORITY)
{
decodeError = Http2ErrorCode.PROTOCOL_ERROR;
return;
}
Http2Stream stream = http2Streams.get(streamId);
if (stream == null)
{
switch (type)
{
case HEADERS:
factory.counters.headersFramesRead.getAsLong();
onStreamHeaders(http2Frame);
break;
case CONTINUATION:
factory.counters.continuationFramesRead.getAsLong();
onStreamContinuation(http2Frame);
break;
case WINDOW_UPDATE:
// "half-closed (remote)" or "closed" stream MUST NOT be treated as error
factory.counters.windowUpdateFramesRead.getAsLong();
onStreamWindowUpdate(stream, http2Frame);
break;
case PRIORITY:
// "half-closed (remote)" or "closed" stream MUST NOT be treated as error
factory.counters.priorityFramesRead.getAsLong();
onStreamPriority(stream, http2Frame);
break;
case RST_STREAM:
onStreamRst(stream, http2Frame);
break;
case UNKNOWN:
break;
default:
this.decodeError = Http2ErrorCode.PROTOCOL_ERROR;
break;
}
}
else
{
switch (type)
{
case DATA:
factory.counters.dataFramesRead.getAsLong();
onStreamData(stream, http2Frame);
break;
case HEADERS:
factory.counters.headersFramesRead.getAsLong();
onStreamHeaders(stream, http2Frame);
break;
case PRIORITY:
factory.counters.priorityFramesRead.getAsLong();
onStreamPriority(stream, http2Frame);
break;
case RST_STREAM:
factory.counters.resetStreamFramesRead.getAsLong();
onStreamRst(stream, http2Frame);
break;
case WINDOW_UPDATE:
factory.counters.windowUpdateFramesRead.getAsLong();
onStreamWindowUpdate(stream, http2Frame);
break;
case UNKNOWN:
break;
default:
this.decodeError = Http2ErrorCode.PROTOCOL_ERROR;
break;
}
}
}
private void onConnectionSettings(
final Http2FrameFW http2Frame)
{
final Http2SettingsFW settings =
factory.settingsRO.tryWrap(http2Frame.buffer(), http2Frame.offset(), http2Frame.limit());
if (settings == null || settings.ack() && settings.payloadLength() != 0)
{
decodeError = Http2ErrorCode.FRAME_SIZE_ERROR;
}
else if (!settings.ack())
{
settings.forEach(this::applySetting);
writeScheduler.settingsAck();
factory.counters.settingsFramesWritten.getAsLong();
}
else
{
http2Streams.values().forEach(this::applyInitialWindowDelta);
// now that peer acked our initial settings, can use them as our local settings
localSettings = initialSettings;
}
}
private void onConnectionPing(
Http2FrameFW http2Frame)
{
final Http2PingFW ping = factory.pingRO.tryWrap(http2Frame.buffer(), http2Frame.offset(), http2Frame.limit());
if (ping == null)
{
this.decodeError = Http2ErrorCode.FRAME_SIZE_ERROR;
return;
}
if (!ping.ack())
{
writeScheduler.pingAck(ping.payload(), 0, ping.payload().capacity());
factory.counters.pingFramesWritten.getAsLong();
}
}
private void onConnectionGoAway(
Http2FrameFW http2Frame)
{
// TODO: ConnectionState
if (!goaway)
{
goaway = true;
remoteSettings.enablePush = false; // no new streams
this.decodeError = Http2ErrorCode.NO_ERROR;
}
}
private void onConnectionWindowUpdate(
Http2FrameFW http2Frame)
{
final Http2WindowUpdateFW http2Window =
factory.http2WindowRO.tryWrap(http2Frame.buffer(), http2Frame.offset(), http2Frame.limit());
if (http2Window == null)
{
decodeError = Http2ErrorCode.FRAME_SIZE_ERROR;
return;
}
// 6.9 WINDOW_UPDATE - legal range for flow-control window increment is 1 to 2^31-1 octets.
if (http2Window.size() < 1)
{
decodeError = Http2ErrorCode.PROTOCOL_ERROR;
return;
}
http2OutWindow += http2Window.size();
// 6.9.1 A sender MUST NOT allow a flow-control window to exceed 2^31-1 octets.
if (http2OutWindow > Integer.MAX_VALUE)
{
decodeError = Http2ErrorCode.FLOW_CONTROL_ERROR;
return;
}
writeScheduler.onHttp2Window();
}
private void onStreamHeaders(
Http2FrameFW http2Frame)
{
int streamId = http2Frame.streamId();
Http2HeadersFW http2Headers = factory.headersRO.wrap(http2Frame.buffer(), http2Frame.offset(), http2Frame.limit());
int parentStreamId = http2Headers.parentStream();
if (parentStreamId == streamId)
{
// 5.3.1 A stream cannot depend on itself
doRstStream(streamId, Http2ErrorCode.PROTOCOL_ERROR);
}
if (http2Headers.dataLength() < 0)
{
this.decodeError = Http2ErrorCode.PROTOCOL_ERROR;
return;
}
if (streamId <= maxClientStreamId)
{
this.decodeError = Http2ErrorCode.PROTOCOL_ERROR;
return;
}
maxClientStreamId = streamId;
if (clientStreamCount >= localSettings.maxConcurrentStreams)
{
doRstStream(streamId, Http2ErrorCode.REFUSED_STREAM);
return;
}
if (!http2Headers.endHeaders())
{
assert headersSlotIndex == NO_SLOT;
assert headersSlotOffset == 0;
headersSlotIndex = factory.headersPool.acquire(networkId);
if (headersSlotIndex == NO_SLOT)
{
// all slots are in use, just reset the connection
factory.doReset(network, networkRouteId, networkId, factory.supplyTrace.getAsLong());
handleAbort(factory.supplyTrace.getAsLong());
return;
}
MutableDirectBuffer headersBuffer = factory.headersPool.buffer(headersSlotIndex);
headersBuffer.putBytes(headersSlotOffset, http2Headers.buffer(),
http2Headers.dataOffset(), http2Headers.dataLength());
headersSlotOffset = http2Headers.dataLength();
expectContinuation = true;
expectContinuationStreamId = streamId;
return;
}
onStreamHeadersEnd(http2Frame, http2Headers.buffer(), http2Headers.dataOffset(),
http2Headers.dataOffset() + http2Headers.dataLength());
}
private void onStreamHeaders(
Http2Stream stream,
Http2FrameFW http2Frame)
{
if (stream.state == Http2StreamState.HALF_CLOSED_REMOTE)
{
decodeError = Http2ErrorCode.PROTOCOL_ERROR;
return;
}
Http2HeadersFW http2Trailers = factory.headersRO.wrap(http2Frame.buffer(), http2Frame.offset(), http2Frame.limit());
HpackHeaderBlockFW headerBlock = factory.blockRO.wrap(http2Trailers.buffer(), http2Trailers.dataOffset(),
http2Trailers.dataOffset() + http2Trailers.dataLength());
headerBlock.forEach(trailerFieldConsumer);
if (headersContext.error())
{
if (headersContext.streamError != null)
{
doRstStream(stream.http2StreamId, headersContext.streamError);
return;
}
if (headersContext.connectionError != null)
{
decodeError = headersContext.connectionError;
return;
}
}
}
private void onStreamContinuation(
Http2FrameFW http2Frame)
{
if (!expectContinuation)
{
decodeError = Http2ErrorCode.PROTOCOL_ERROR;
return;
}
assert headersSlotIndex != NO_SLOT;
assert headersSlotOffset != 0;
Http2ContinuationFW http2Continuation =
factory.continationRO.wrap(http2Frame.buffer(), http2Frame.offset(), http2Frame.limit());
DirectBuffer payload = http2Continuation.payload();
MutableDirectBuffer headersBuffer = factory.headersPool.buffer(headersSlotIndex);
headersBuffer.putBytes(headersSlotOffset, payload, 0, payload.capacity());
headersSlotOffset += payload.capacity();
if (!http2Continuation.endHeaders())
{
assert expectContinuation;
assert expectContinuationStreamId == http2Continuation.streamId();
return;
}
onStreamHeadersEnd(http2Frame, headersBuffer, 0, headersSlotOffset);
releaseHeadersSlot();
expectContinuation = false;
expectContinuationStreamId = 0;
}
private void onStreamHeadersEnd(
Http2FrameFW http2Frame,
DirectBuffer headersBuffer,
int headersOffset,
int headersLimit)
{
int streamId = http2Frame.streamId();
headersContext.reset();
factory.httpBeginExRW.wrap(factory.scratch, 0, factory.scratch.capacity())
.typeId(factory.httpWriter.httpTypeId);
HpackHeaderBlockFW headerBlock = factory.blockRO.wrap(headersBuffer, headersOffset, headersLimit);
headerBlock.forEach(headerFieldConsumer);
// All HTTP/2 requests MUST include exactly one valid value for the
// ":method", ":scheme", and ":path" pseudo-header fields, unless it is
// a CONNECT request (Section 8.3). An HTTP request that omits
// mandatory pseudo-header fields is malformed
if (!headersContext.error() &&
(headersContext.method != 1 || headersContext.scheme != 1 || headersContext.path != 1))
{
headersContext.streamError = Http2ErrorCode.PROTOCOL_ERROR;
}
if (headersContext.error())
{
if (headersContext.streamError != null)
{
doRstStream(streamId, headersContext.streamError);
return;
}
if (headersContext.connectionError != null)
{
decodeError = headersContext.connectionError;
return;
}
}
addDefaultPortToAuthorityIfNeeded();
RouteFW route = resolveTarget(headersContext.headers);
if (route == null)
{
noRoute(streamId);
}
else
{
overrideHeadersIfNeeded(route);
Http2StreamState nextState = http2Frame.endStream() ? HALF_CLOSED_REMOTE : OPEN;
followRoute(streamId, nextState, route);
}
}
private void onStreamData(
Http2Stream stream,
Http2FrameFW http2Frame)
{
if (stream.state == HALF_CLOSED_REMOTE)
{
decodeError = Http2ErrorCode.STREAM_CLOSED;
closeStream(stream);
return;
}
// handle invalid padding length
Http2DataFW http2Data = factory.http2DataRO.wrap(http2Frame.buffer(), http2Frame.offset(), http2Frame.limit());
if (http2Data.dataLength() < 0)
{
decodeError = Http2ErrorCode.STREAM_CLOSED;
closeStream(stream);
return;
}
final int payloadLength = http2Frame.payloadLength();
if (stream.http2InWindow < payloadLength || http2InWindow < payloadLength)
{
doRstByUs(stream, Http2ErrorCode.FLOW_CONTROL_ERROR);
return;
}
http2InWindow -= payloadLength;
stream.http2InWindow -= payloadLength;
stream.totalData += payloadLength;
if (http2Data.endStream())
{
// 8.1.2.6 A request is malformed if the value of a content-length header field does
// not equal the sum of the DATA frame payload lengths
if (stream.contentLength != -1 && stream.totalData != stream.contentLength)
{
doRstByUs(stream, Http2ErrorCode.PROTOCOL_ERROR);
//stream.httpWriteScheduler.doEnd(stream.targetId);
return;
}
onRequestEnd(stream);
}
stream.onData(traceId, http2Data);
}
private void onStreamPriority(
Http2Stream stream,
Http2FrameFW http2Frame)
{
Http2PriorityFW http2Priority = factory.priorityRO.tryWrap(http2Frame.buffer(), http2Frame.offset(), http2Frame.limit());
if (http2Priority == null)
{
decodeError = Http2ErrorCode.FRAME_SIZE_ERROR;
return;
}
if (stream != null)
{
if (http2Priority.parentStream() == stream.http2StreamId)
{
// 5.3.1 A stream cannot depend on itself
doRstByUs(stream, Http2ErrorCode.PROTOCOL_ERROR);
return;
}
}
}
private void onStreamWindowUpdate(
Http2Stream stream,
Http2FrameFW http2Frame)
{
Http2WindowUpdateFW http2Window =
factory.http2WindowRO.tryWrap(http2Frame.buffer(), http2Frame.offset(), http2Frame.limit());
if (http2Window == null)
{
decodeError = Http2ErrorCode.FRAME_SIZE_ERROR;
return;
}
// 6.9 WINDOW_UPDATE - legal range for flow-control window increment is 1 to 2^31-1 octets.
if (http2Window.size() < 1)
{
doRstByUs(stream, Http2ErrorCode.PROTOCOL_ERROR);
return;
}
if (stream != null)
{
// 6.9.1 A sender MUST NOT allow a flow-control window to exceed 2^31-1 octets.
stream.http2OutWindow += http2Window.size();
if (stream.http2OutWindow > Integer.MAX_VALUE)
{
doRstByUs(stream, Http2ErrorCode.FLOW_CONTROL_ERROR);
return;
}
writeScheduler.onHttp2Window(stream.http2StreamId);
}
}
private void onStreamRst(
Http2Stream stream,
Http2FrameFW http2Frame)
{
Http2RstStreamFW http2RstStream =
factory.http2RstStreamRO.tryWrap(http2Frame.buffer(), http2Frame.offset(), http2Frame.limit());
if (http2RstStream == null)
{
decodeError = Http2ErrorCode.FRAME_SIZE_ERROR;
return;
}
if (stream != null)
{
stream.onReset(factory.supplyTrace.getAsLong());
closeStream(stream);
}
}
private void applySetting(
Http2SettingsId id,
Long value)
{
switch (id)
{
case HEADER_TABLE_SIZE:
remoteSettings.headerTableSize = value.intValue();
break;
case ENABLE_PUSH:
if (!(value == 0L || value == 1L))
{
decodeError = Http2ErrorCode.PROTOCOL_ERROR;
return;
}
remoteSettings.enablePush = value == 1L;
break;
case MAX_CONCURRENT_STREAMS:
remoteSettings.maxConcurrentStreams = value.intValue();
break;
case INITIAL_WINDOW_SIZE:
if (value > Integer.MAX_VALUE)
{
decodeError = Http2ErrorCode.FLOW_CONTROL_ERROR;
return;
}
int old = remoteSettings.initialWindowSize;
remoteSettings.initialWindowSize = value.intValue();
int update = value.intValue() - old;
// 6.9.2. Initial Flow-Control Window Size
// SETTINGS frame can alter the initial flow-control
// window size for streams with active flow-control windows
for (Http2Stream http2Stream: http2Streams.values())
{
http2Stream.http2OutWindow += update; // http2OutWindow can become negative
if (http2Stream.http2OutWindow > Integer.MAX_VALUE)
{
// 6.9.2. Initial Flow-Control Window Size
// An endpoint MUST treat a change to SETTINGS_INITIAL_WINDOW_SIZE that
// causes any flow-control window to exceed the maximum size as a
// connection error of type FLOW_CONTROL_ERROR.
decodeError = Http2ErrorCode.FLOW_CONTROL_ERROR;
return;
}
}
break;
case MAX_FRAME_SIZE:
if (value < Math.pow(2, 14) || value > Math.pow(2, 24) - 1)
{
decodeError = Http2ErrorCode.PROTOCOL_ERROR;
return;
}
remoteSettings.maxFrameSize = value.intValue();
break;
case MAX_HEADER_LIST_SIZE:
remoteSettings.maxHeaderListSize = value.intValue();
break;
default:
// Ignore the unkonwn setting
break;
}
}
private void applyInitialWindowDelta(
Http2Stream http2Stream)
{
final int initialWindowDelta = initialSettings.initialWindowSize - localSettings.initialWindowSize;
http2Stream.http2InWindow += initialWindowDelta; // http2InWindow can become negative
}
private void releaseFrameSlot()
{
if (frameSlot != NO_SLOT)
{
factory.framePool.release(frameSlot);
frameSlot = NO_SLOT;
frameSlotLimit = 0;
}
}
private void releaseHeadersSlot()
{
if (headersSlotIndex != NO_SLOT)
{
factory.headersPool.release(headersSlotIndex);
headersSlotIndex = NO_SLOT;
headersSlotOffset = 0;
}
}
private void doResetNetworkAndCleanup()
{
factory.doReset(networkReply, networkRouteId, networkId, factory.supplyTrace.getAsLong());
doCleanup();
}
private void doCleanup()
{
releaseFrameSlot();
releaseHeadersSlot();
http2Streams.values().forEach(this::closeStream);
http2Streams.clear();
}
private void followRoute(
int streamId,
Http2StreamState state,
RouteFW route)
{
final long applicationRouteId = route.correlationId();
HttpWriter httpWriter = factory.httpWriter;
Http2Stream stream = newStream(streamId, state, applicationRouteId, httpWriter);
stream.contentLength = headersContext.contentLength;
HttpBeginExFW beginEx = factory.httpBeginExRW.build();
httpWriter.doHttpBegin(stream.applicationInitial, applicationRouteId,
stream.applicationInitialId, traceId, networkId, beginEx.buffer(),
beginEx.offset(), beginEx.sizeof());
router.setThrottle(stream.applicationInitialId, stream::onThrottle);
if (state == HALF_CLOSED_REMOTE)
{
// Deferring until WINDOW is received
stream.endDeferred = true;
}
}
// No route for the HTTP2 request, send 404 on the corresponding HTTP2 stream
private void noRoute(
int streamId)
{
send404(streamId);
}
// No route for the HTTP2 request, send 404 on the corresponding HTTP2 stream
void send404(
int streamId)
{
ArrayFW headers =
factory.headersRW.wrap(factory.errorBuf, 0, factory.errorBuf.capacity())
.item(b -> b.name(":status").value("404"))
.build();
writeScheduler.headers(0, streamId, Http2Flags.END_STREAM, headers);
if ((streamId & 0x01L) == 0x00L)
{
factory.counters.pushHeadersFramesWritten.getAsLong();
}
else
{
factory.counters.headersFramesWritten.getAsLong();
}
}
void onRequestEnd(
Http2Stream stream)
{
switch (stream.state)
{
case OPEN:
stream.state = HALF_CLOSED_REMOTE;
break;
case HALF_CLOSED_LOCAL:
closeStream(stream);
break;
default:
break;
}
}
void onResponseEnd(
Http2Stream stream)
{
switch (stream.state)
{
case OPEN:
stream.state = HALF_CLOSED_LOCAL;
break;
case HALF_CLOSED_REMOTE:
closeStream(stream);
break;
default:
break;
}
}
void closeStream(
Http2Stream stream)
{
if (stream.state != CLOSED)
{
stream.state = CLOSED;
if (stream.isClientInitiated())
{
clientStreamCount--;
}
else
{
promisedStreamCount--;
}
factory.correlations.remove(stream.applicationReplyId);
http2Streams.remove(stream.http2StreamId);
stream.close();
}
}
private RouteFW resolveTarget(
Map headers)
{
MessagePredicate filter = (t, b, o, l) ->
{
RouteFW route = factory.routeRO.wrap(b, o, o + l);
OctetsFW extension = route.extension();
Map routeHeaders;
if (extension.sizeof() == 0)
{
routeHeaders = EMPTY_HEADERS;
}
else
{
final HttpRouteExFW routeEx = extension.get(factory.httpRouteExRO::wrap);
routeHeaders = new LinkedHashMap<>();
routeEx.headers().forEach(h -> routeHeaders.put(h.name().asString(), h.value().asString()));
}
return headers.entrySet().containsAll(routeHeaders.entrySet());
};
return router.resolve(networkRouteId, authorization, filter, wrapRoute);
}
void handleWindow(
WindowFW windowRO)
{
writeScheduler.onWindow();
}
void error(
Http2ErrorCode errorCode)
{
writeScheduler.goaway(lastStreamId, errorCode);
factory.counters.goawayFramesWritten.getAsLong();
factory.doReset(network, networkRouteId, networkId, factory.supplyTrace.getAsLong());
factory.doAbort(networkReply, networkRouteId, networkReplyId, factory.supplyTrace.getAsLong());
http2Streams.forEach((i, s) -> s.onError(traceId));
doCleanup();
}
void doRstByUs(
Http2Stream stream,
Http2ErrorCode errorCode)
{
stream.onReset(factory.supplyTrace.getAsLong());
doRstStream(stream.http2StreamId, errorCode);
closeStream(stream);
}
private void doRstStream(
int streamId,
Http2ErrorCode errorCode)
{
writeScheduler.rst(streamId, errorCode);
factory.counters.resetStreamFramesWritten.getAsLong();
}
private int nextPromisedId()
{
maxPushPromiseStreamId += 2;
return maxPushPromiseStreamId;
}
/*
* @param streamId corresponding http2 stream-id on which service response
* will be sent
* @return a stream id on which PUSH_PROMISE can be sent
* -1 otherwise
*/
private int findPushId(
int streamId)
{
if (remoteSettings.enablePush && promisedStreamCount + 1 < remoteSettings.maxConcurrentStreams)
{
// PUSH_PROMISE frames MUST only be sent on a peer-initiated stream
if (streamId % 2 == 0)
{
// Find a stream on which PUSH_PROMISE can be sent
return http2Streams.entrySet()
.stream()
.map(Map.Entry::getValue)
.filter(s -> (s.http2StreamId & 0x01) == 1) // client-initiated stream
.filter(s -> s.state == OPEN || s.state == HALF_CLOSED_REMOTE)
.filter(s -> !s.endStream)
.mapToInt(s -> s.http2StreamId)
.findAny()
.orElse(-1);
}
else
{
return streamId; // client-initiated stream
}
}
return -1;
}
private void doPromisedRequest(
int http2StreamId,
long authorization,
ArrayFW headers)
{
headersContext.reset();
headers.forEach(h -> headersContext.headers.put(h.name().asString(), h.value().asString()));
RouteFW route = resolveTarget(headersContext.headers);
if (route != null)
{
final HttpBeginExFW.Builder httpBeginEx =
factory.httpBeginExRW.wrap(factory.scratch, 0, factory.scratch.capacity())
.typeId(factory.httpWriter.httpTypeId);
for (Map.Entry e : headersContext.headers.entrySet())
{
httpBeginEx.headersItem(item -> item.name(e.getKey())
.value(e.getValue()));
}
overrideHeadersIfNeeded(route);
followRoute(http2StreamId, HALF_CLOSED_REMOTE, route);
}
}
private Http2Stream newStream(
int http2StreamId,
Http2StreamState state,
long applicationRouteId,
HttpWriter httpWriter)
{
assert http2StreamId != 0;
Http2Stream http2Stream = new Http2Stream(factory, this, http2StreamId, state, applicationRouteId, httpWriter);
http2Streams.put(http2StreamId, http2Stream);
Correlation correlation = new Correlation(http2Stream.applicationReplyId, networkReplyId, writeScheduler,
this::doPromisedRequest, this, http2StreamId, encodeContext, this::nextPromisedId, this::findPushId);
factory.correlations.put(http2Stream.applicationReplyId, correlation);
if (http2Stream.isClientInitiated())
{
clientStreamCount++;
}
else
{
promisedStreamCount++;
}
return http2Stream;
}
private void validateHeaderFieldType(
HpackHeaderFieldFW hf)
{
if (!headersContext.error() && hf.type() == UNKNOWN)
{
headersContext.connectionError = Http2ErrorCode.COMPRESSION_ERROR;
}
}
private void dynamicTableSizeUpdate(
HpackHeaderFieldFW hf)
{
if (!headersContext.error())
{
switch (hf.type())
{
case INDEXED:
case LITERAL:
expectDynamicTableSizeUpdate = false;
break;
case UPDATE:
if (!expectDynamicTableSizeUpdate)
{
// dynamic table size update MUST occur at the beginning of the first header block
headersContext.connectionError = Http2ErrorCode.COMPRESSION_ERROR;
return;
}
int maxTableSize = hf.tableSize();
if (maxTableSize > localSettings.headerTableSize)
{
headersContext.connectionError = Http2ErrorCode.COMPRESSION_ERROR;
return;
}
decodeContext.updateSize(hf.tableSize());
break;
default:
break;
}
}
}
private void validatePseudoHeaders(
DirectBuffer name,
DirectBuffer value)
{
if (!headersContext.error())
{
if (name.capacity() > 0 && name.getByte(0) == ':')
{
// All pseudo-header fields MUST appear in the header block before regular header fields
if (headersContext.regularHeader)
{
headersContext.streamError = Http2ErrorCode.PROTOCOL_ERROR;
return;
}
// request pseudo-header fields MUST be one of :authority, :method, :path, :scheme,
int index = decodeContext.index(name);
switch (index)
{
case 1: // :authority
break;
case 2: // :method
headersContext.method++;
break;
case 4: // :path
if (value.capacity() > 0) // :path MUST not be empty
{
headersContext.path++;
}
break;
case 6: // :scheme
headersContext.scheme++;
break;
default:
headersContext.streamError = Http2ErrorCode.PROTOCOL_ERROR;
return;
}
}
else
{
headersContext.regularHeader = true;
}
}
}
private void validateTrailerFieldName(
DirectBuffer name,
DirectBuffer value)
{
if (!headersContext.error())
{
if (name.capacity() > 0 && name.getByte(0) == ':')
{
headersContext.streamError = Http2ErrorCode.PROTOCOL_ERROR;
return;
}
}
}
private void connectionHeaders(
DirectBuffer name,
DirectBuffer value)
{
if (!headersContext.error() && name.equals(HpackContext.CONNECTION))
{
headersContext.streamError = Http2ErrorCode.PROTOCOL_ERROR;
}
}
private void contentLengthHeader(
DirectBuffer name,
DirectBuffer value)
{
if (!headersContext.error() && name.equals(decodeContext.nameBuffer(28)))
{
String contentLength = value.getStringWithoutLengthUtf8(0, value.capacity());
headersContext.contentLength = Long.parseLong(contentLength);
}
}
// 8.1.2.2 TE header MUST NOT contain any value other than "trailers".
private void teHeader(
DirectBuffer name,
DirectBuffer value)
{
if (!headersContext.error() && name.equals(TE) && !value.equals(TRAILERS))
{
headersContext.streamError = Http2ErrorCode.PROTOCOL_ERROR;
}
}
private void uppercaseHeaders(
DirectBuffer name,
DirectBuffer value)
{
if (!headersContext.error())
{
for (int i = 0; i < name.capacity(); i++)
{
if (name.getByte(i) >= 'A' && name.getByte(i) <= 'Z')
{
headersContext.streamError = Http2ErrorCode.PROTOCOL_ERROR;
}
}
}
}
// Collect headers into map to resolve target
// TODO avoid this
private void collectHeaders(
DirectBuffer name,
DirectBuffer value)
{
if (!headersContext.error())
{
String nameStr = name.getStringWithoutLengthUtf8(0, name.capacity());
String valueStr = value.getStringWithoutLengthUtf8(0, value.capacity());
// TODO cookie needs to be appended with ';'
headersContext.headers.merge(nameStr, valueStr, (o, n) -> String.format("%s, %s", o, n));
}
}
// Writes HPACK header field to http representation in a buffer
private void mapToHttp(
DirectBuffer name,
DirectBuffer value)
{
if (!headersContext.error())
{
factory.httpBeginExRW.headersItem(item -> item.name(name, 0, name.capacity())
.value(value, 0, value.capacity()));
}
}
private void addDefaultPortToAuthorityIfNeeded()
{
String authority = headersContext.headers.get(":authority");
if (authority.indexOf(':') == -1)
{
String scheme = headersContext.headers.get(":scheme");
String defaultPort = "https".equals(scheme) ? ":443" : ":80";
headersContext.headers.put(":authority", authority + defaultPort);
// rebuild http request as :authority header is modified
factory.httpBeginExRW.wrap(factory.scratch, 0, factory.scratch.capacity())
.typeId(factory.httpWriter.httpTypeId);
for (Map.Entry e : headersContext.headers.entrySet())
{
factory.httpBeginExRW.headersItem(item -> item.name(e.getKey())
.value(e.getValue()));
}
}
}
private void overrideHeadersIfNeeded(
RouteFW route)
{
final HttpRouteExFW httpRouteEx = route.extension().get(factory.httpRouteExRO::tryWrap);
if (httpRouteEx != null && !httpRouteEx.overrides().isEmpty())
{
httpRouteEx.overrides().forEach(o -> headersContext.headers.put(o.name().asString(), o.value().asString()));
// rebuild http request due to header overrides
factory.httpBeginExRW.wrap(factory.scratch, 0, factory.scratch.capacity())
.typeId(factory.httpWriter.httpTypeId);
for (Map.Entry e : headersContext.headers.entrySet())
{
factory.httpBeginExRW.headersItem(item -> item.name(e.getKey())
.value(e.getValue()));
}
}
}
private void decodeHeaderField(
HpackHeaderFieldFW hf,
BiConsumer nameValue)
{
if (!headersContext.error())
{
decodeHF(hf, nameValue);
}
}
private void decodeHF(
HpackHeaderFieldFW hf,
BiConsumer nameValue)
{
int index;
DirectBuffer name = null;
DirectBuffer value = null;
switch (hf.type())
{
case INDEXED :
index = hf.index();
if (!decodeContext.valid(index))
{
headersContext.connectionError = Http2ErrorCode.COMPRESSION_ERROR;
return;
}
name = decodeContext.nameBuffer(index);
value = decodeContext.valueBuffer(index);
nameValue.accept(name, value);
break;
case LITERAL :
HpackLiteralHeaderFieldFW literalRO = hf.literal();
if (literalRO.error())
{
headersContext.connectionError = Http2ErrorCode.COMPRESSION_ERROR;
return;
}
HpackStringFW valueRO = literalRO.valueLiteral();
switch (literalRO.nameType())
{
case INDEXED:
index = literalRO.nameIndex();
if (!decodeContext.valid(index))
{
headersContext.connectionError = Http2ErrorCode.COMPRESSION_ERROR;
return;
}
name = decodeContext.nameBuffer(index);
value = valueRO.payload();
if (valueRO.huffman())
{
MutableDirectBuffer dst = new UnsafeBuffer(new byte[4096]); // TODO
int length = HpackHuffman.decode(value, dst);
if (length == -1)
{
headersContext.connectionError = Http2ErrorCode.COMPRESSION_ERROR;
return;
}
value = new UnsafeBuffer(dst, 0, length);
}
nameValue.accept(name, value);
break;
case NEW:
HpackStringFW nameRO = literalRO.nameLiteral();
name = nameRO.payload();
if (nameRO.huffman())
{
MutableDirectBuffer dst = new UnsafeBuffer(new byte[4096]); // TODO
int length = HpackHuffman.decode(name, dst);
if (length == -1)
{
headersContext.connectionError = Http2ErrorCode.COMPRESSION_ERROR;
return;
}
name = new UnsafeBuffer(dst, 0, length);
}
value = valueRO.payload();
if (valueRO.huffman())
{
MutableDirectBuffer dst = new UnsafeBuffer(new byte[4096]); // TODO
int length = HpackHuffman.decode(value, dst);
if (length == -1)
{
headersContext.connectionError = Http2ErrorCode.COMPRESSION_ERROR;
return;
}
value = new UnsafeBuffer(dst, 0, length);
}
nameValue.accept(name, value);
break;
}
if (literalRO.literalType() == INCREMENTAL_INDEXING)
{
// make a copy for name and value as they go into dynamic table (outlives current frame)
MutableDirectBuffer nameCopy = new UnsafeBuffer(new byte[name.capacity()]);
nameCopy.putBytes(0, name, 0, name.capacity());
MutableDirectBuffer valueCopy = new UnsafeBuffer(new byte[value.capacity()]);
valueCopy.putBytes(0, value, 0, value.capacity());
decodeContext.add(nameCopy, valueCopy);
}
break;
default:
break;
}
}
void mapPushPromise(
ArrayFW httpHeaders,
HpackHeaderBlockFW.Builder builder)
{
httpHeaders.forEach(h -> builder.header(b -> mapHeader(h, b)));
}
void mapTrailers(
ArrayFW httpHeaders,
HpackHeaderBlockFW.Builder builder)
{
httpHeaders.forEach(h ->
{
if (validHeader(h))
{
builder.header(b -> mapHeader(h, b));
}
});
}
void mapHeaders(
ArrayFW httpHeaders,
HpackHeaderBlockFW.Builder builder)
{
encodeHeadersContext.reset();
httpHeaders.forEach(this::status) // checks if there is :status
.forEach(this::accessControlAllowOrigin) // checks if there is access-control-allow-origin
.forEach(this::serverHeader) // checks if there is server
.forEach(this::connectionHeaders); // collects all connection headers
if (!encodeHeadersContext.status)
{
builder.header(b -> b.indexed(8)); // no mandatory :status header, add :status: 200
}
httpHeaders.forEach(h ->
{
if (validHeader(h))
{
builder.header(b -> mapHeader(h, b));
}
});
if (factory.config.accessControlAllowOrigin() && !encodeHeadersContext.accessControlAllowOrigin)
{
builder.header(b -> b.literal(l -> l.type(WITHOUT_INDEXING).name(20).value(DEFAULT_ACCESS_CONTROL_ALLOW_ORIGIN)));
}
// add configured Server header if there is no Server header in response
if (factory.config.serverHeader() != null && !encodeHeadersContext.serverHeader)
{
DirectBuffer server = factory.config.serverHeader();
builder.header(b -> b.literal(l -> l.type(WITHOUT_INDEXING).name(54).value(server)));
}
}
private void status(
HttpHeaderFW httpHeader)
{
if (!encodeHeadersContext.status)
{
StringFW name = httpHeader.name();
String16FW value = httpHeader.value();
factory.nameRO.wrap(name.buffer(), name.offset() + 1, name.sizeof() - 1); // +1, -1 for length-prefixed buffer
factory.valueRO.wrap(value.buffer(), value.offset() + 2, value.sizeof() - 2);
if (factory.nameRO.equals(encodeContext.nameBuffer(8)))
{
encodeHeadersContext.status = true;
}
}
}
// Checks if response has access-control-allow-origin header
private void accessControlAllowOrigin(
HttpHeaderFW httpHeader)
{
if (factory.config.accessControlAllowOrigin() && !encodeHeadersContext.accessControlAllowOrigin)
{
StringFW name = httpHeader.name();
String16FW value = httpHeader.value();
factory.nameRO.wrap(name.buffer(), name.offset() + 1, name.sizeof() - 1); // +1, -1 for length-prefixed buffer
factory.valueRO.wrap(value.buffer(), value.offset() + 2, value.sizeof() - 2);
if (factory.nameRO.equals(encodeContext.nameBuffer(20)))
{
encodeHeadersContext.accessControlAllowOrigin = true;
}
}
}
// Checks if response has server header
private void serverHeader(
HttpHeaderFW httpHeader)
{
if (factory.config.serverHeader() != null && !encodeHeadersContext.serverHeader)
{
StringFW name = httpHeader.name();
String16FW value = httpHeader.value();
factory.nameRO.wrap(name.buffer(), name.offset() + 1, name.sizeof() - 1); // +1, -1 for length-prefixed buffer
factory.valueRO.wrap(value.buffer(), value.offset() + 2, value.sizeof() - 2);
if (factory.nameRO.equals(encodeContext.nameBuffer(54)))
{
encodeHeadersContext.serverHeader = true;
}
}
}
private void connectionHeaders(
HttpHeaderFW httpHeader)
{
StringFW name = httpHeader.name();
String16FW value = httpHeader.value();
factory.nameRO.wrap(name.buffer(), name.offset() + 1, name.sizeof() - 1); // +1, -1 for length-prefixed buffer
if (factory.nameRO.equals(CONNECTION))
{
String[] headers = value.asString().split(",");
for (String header : headers)
{
encodeHeadersContext.connectionHeaders.add(header.trim());
}
}
}
private boolean validHeader(
HttpHeaderFW httpHeader)
{
StringFW name = httpHeader.name();
String16FW value = httpHeader.value();
factory.nameRO.wrap(name.buffer(), name.offset() + 1, name.sizeof() - 1); // +1, -1 for length-prefixed buffer
factory.valueRO.wrap(value.buffer(), value.offset() + 2, value.sizeof() - 2);
// Removing 8.1.2.1 Pseudo-Header Fields
// Not sending error as it will allow requests to loop back
if (factory.nameRO.equals(encodeContext.nameBuffer(1)) || // :authority
factory.nameRO.equals(encodeContext.nameBuffer(2)) || // :method
factory.nameRO.equals(encodeContext.nameBuffer(4)) || // :path
factory.nameRO.equals(encodeContext.nameBuffer(6))) // :scheme
{
return false;
}
// Removing 8.1.2.2 connection-specific header fields from response
if (factory.nameRO.equals(encodeContext.nameBuffer(57)) || // transfer-encoding
factory.nameRO.equals(CONNECTION) ||
factory.nameRO.equals(KEEP_ALIVE) ||
factory.nameRO.equals(PROXY_CONNECTION) ||
factory.nameRO.equals(UPGRADE))
{
return false;
}
// Removing any header that is nominated by Connection header field
for (String connectionHeader: encodeHeadersContext.connectionHeaders)
{
if (name.asString().equals(connectionHeader))
{
return false;
}
}
return true;
}
// Map http1.1 header to http2 header field in HEADERS, PUSH_PROMISE request
private void mapHeader(
HttpHeaderFW httpHeader,
HpackHeaderFieldFW.Builder builder)
{
StringFW name = httpHeader.name();
String16FW value = httpHeader.value();
factory.nameRO.wrap(name.buffer(), name.offset() + 1, name.sizeof() - 1); // +1, -1 for length-prefixed buffer
factory.valueRO.wrap(value.buffer(), value.offset() + 2, value.sizeof() - 2);
int index = encodeContext.index(factory.nameRO, factory.valueRO);
if (index != -1)
{
// Indexed
builder.indexed(index);
}
else
{
// Literal
builder.literal(literalBuilder -> buildLiteral(literalBuilder, encodeContext));
}
}
// Building Literal representation of header field
// TODO dynamic table, huffman, never indexed
private void buildLiteral(
HpackLiteralHeaderFieldFW.Builder builder,
HpackContext hpackContext)
{
int nameIndex = hpackContext.index(factory.nameRO);
builder.type(WITHOUT_INDEXING);
if (nameIndex != -1)
{
builder.name(nameIndex);
}
else
{
builder.name(factory.nameRO, 0, factory.nameRO.capacity());
}
builder.value(factory.valueRO, 0, factory.valueRO.capacity());
}
void handleHttpBegin(
BeginFW begin,
MessageConsumer applicationReplyThrottle,
long applicationRouteId,
long applicationReplyId,
Correlation correlation)
{
OctetsFW extension = begin.extension();
Http2Stream stream = http2Streams.get(correlation.http2StreamId);
if (stream == null)
{
factory.doReset(applicationReplyThrottle, applicationRouteId, applicationReplyId, factory.supplyTrace.getAsLong());
}
else
{
stream.applicationReplyThrottle = applicationReplyThrottle;
stream.sendHttpWindow();
if (extension.sizeof() > 0)
{
HttpBeginExFW beginEx = extension.get(factory.beginExRO::wrap);
writeScheduler.headers(begin.trace(), correlation.http2StreamId, Http2Flags.NONE, beginEx.headers());
if ((correlation.http2StreamId & 0x01L) == 0x00L)
{
factory.counters.pushHeadersFramesWritten.getAsLong();
}
else
{
factory.counters.headersFramesWritten.getAsLong();
}
}
}
}
void handleHttpData(
DataFW data,
Correlation correlation)
{
OctetsFW extension = data.extension();
OctetsFW payload = data.payload();
long traceId = data.trace();
if (extension.sizeof() > 0)
{
int pushStreamId = correlation.pushStreamIds.applyAsInt(correlation.http2StreamId);
if (pushStreamId != -1)
{
int promisedStreamId = correlation.promisedStreamIds.getAsInt();
HttpDataExFW dataEx = extension.get(factory.dataExRO::wrap);
writeScheduler.pushPromise(traceId, pushStreamId, promisedStreamId, dataEx.promise());
correlation.pushHandler.accept(promisedStreamId, data.authorization(), dataEx.promise());
factory.counters.pushPromiseFramesWritten.getAsLong();
}
else
{
factory.counters.pushPromiseFramesSkipped.getAsLong();
}
}
if (payload != null)
{
Http2Stream stream = http2Streams.get(correlation.http2StreamId);
if (stream != null)
{
stream.applicationReplyBudget -= data.length() + data.padding();
if (stream.applicationReplyBudget < 0)
{
doRstByUs(stream, Http2ErrorCode.INTERNAL_ERROR);
return;
}
}
writeScheduler.data(traceId, correlation.http2StreamId, payload.buffer(), payload.offset(), payload.sizeof());
factory.counters.dataFramesWritten.getAsLong();
}
}
void handleHttpEnd(
EndFW end,
Correlation correlation)
{
Http2Stream stream = http2Streams.get(correlation.http2StreamId);
if (stream != null)
{
final OctetsFW extension = end.extension();
if (extension.sizeof() != 0)
{
final HttpEndExFW httpEndEx = extension.get(factory.httpEndExRO::wrap);
ArrayFW trailers = httpEndEx.trailers();
writeScheduler.trailers(end.trace(), stream.http2StreamId, Http2Flags.END_STREAM, trailers);
}
else
{
stream.onHttpEnd(end.trace());
}
}
}
void handleHttpAbort(
AbortFW abort,
Correlation correlation)
{
Http2Stream stream = http2Streams.get(correlation.http2StreamId);
if (stream != null)
{
stream.onHttpAbort();
}
}
private static final class HeadersContext
{
Http2ErrorCode connectionError;
Map headers = new LinkedHashMap<>();
int method;
int scheme;
int path;
boolean regularHeader;
Http2ErrorCode streamError;
long contentLength = -1;
void reset()
{
connectionError = null;
headers.clear();
method = 0;
scheme = 0;
path = 0;
regularHeader = false;
streamError = null;
contentLength = -1;
}
boolean error()
{
return streamError != null || connectionError != null;
}
}
private static final class EncodeHeadersContext
{
boolean status;
boolean accessControlAllowOrigin;
boolean serverHeader;
final List connectionHeaders = new ArrayList<>();
void reset()
{
status = false;
accessControlAllowOrigin = false;
serverHeader = false;
connectionHeaders.clear();
}
}
@FunctionalInterface
private interface DecoderState
{
int decode(DirectBuffer buffer, int offset, int length);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy