private Http2To1ProxyRequestQueue getProxyRequestQueue(ChannelHandlerContext ctx) { Http2To1ProxyRequestQueue queue = ctx.channel().attr(CHANNEL_PROXY_REQUEST_QUEUE).get(); if (queue == null) { queue = new Http2To1ProxyRequestQueue(); ctx.channel().attr(CHANNEL_PROXY_REQUEST_QUEUE).set(queue); } return queue; }
public void onRequestWriteOrEnqueue( ChannelHandlerContext ctx, Integer streamId, Object request, ChannelPromise promise) { if (streamId == null || streamId == Message.H1_STREAM_ID_NONE) { log.debug("writing request {}", request); ctx.write(request, promise); } else { boolean shouldWrite = currentProxiedH2StreamId().map(id -> id.equals(streamId)).orElse(Boolean.TRUE); Queue<PendingRequest> queue = streamQueue.computeIfAbsent(streamId, k -> Queues.newArrayDeque()); if (shouldWrite) { log.debug("writing h2-h1 proxy request {}", request); ctx.write(request, promise); } else { log.debug("enqueuing h2-h1 proxy request {}", request); queue.offer(new PendingRequest(request, promise)); } } }
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof HttpObject) { Response response = wrapResponse(ctx, (HttpObject) msg); ctx.fireChannelRead(response); getProxyRequestQueue(ctx).onResponseDrainNext(ctx, response); } else { ctx.fireChannelRead(msg); } }
assertTrue(subject.isEmpty()); subject.onRequestWriteOrEnqueue(mockCtx, 3, "request 1a", mock(ChannelPromise.class)); verify(mockCtx).write(eq("request 1a"), any()); assertTrue(subject.isEmpty()); subject.onRequestWriteOrEnqueue(mockCtx, 3, "request 1b", mock(ChannelPromise.class)); verify(mockCtx).write(eq("request 1b"), any()); assertTrue(subject.isEmpty()); subject.onRequestWriteOrEnqueue(mockCtx, 5, "request 2a", mock(ChannelPromise.class)); verify(mockCtx, never()).write(eq("request 2a"), any()); assertFalse(subject.isEmpty()); subject.onRequestWriteOrEnqueue(mockCtx, 3, "request 1c", mock(ChannelPromise.class)); verify(mockCtx).write(eq("request 1c"), any()); assertFalse(subject.isEmpty()); assertEquals(3, subject.currentProxiedH2StreamId().orElse(0), 0); Response response = DefaultFullResponse.builder() .streamId(3) .build(); subject.onResponseDrainNext(mockCtx, response); assertSame(response, subject.currentResponse().orElse(null)); assertEquals(5, subject.currentProxiedH2StreamId().orElse(0), 0);
Response wrapResponse(ChannelHandlerContext ctx, HttpObject msg) { log.debug("wrapResponse msg={}", msg); final Response response; if (msg instanceof FullHttpResponse) { response = new FullHttp1Response((FullHttpResponse) msg); } else if (msg instanceof HttpResponse) { response = new SegmentedHttp1Response((HttpResponse) msg); } else if (msg instanceof HttpContent) { response = getProxyRequestQueue(ctx) .currentResponse() .map(r -> new SegmentedResponseData(r, new Http1SegmentedData((HttpContent) msg))) .orElse(null); } else { // TODO(CK): throw an exception if response is null? response = null; } return getProxyRequestQueue(ctx) .currentProxiedH2StreamId() .<Response>map( streamId -> { if (response instanceof SegmentedResponseData) { return new ProxySegmentedResponseData((SegmentedResponseData) response, streamId); } else { return new ProxyResponse(response, streamId); } }) .orElse(response); }
@Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { log.debug("write: msg={}", msg); if (msg instanceof SegmentedData) { SegmentedData segmentedData = (SegmentedData) msg; HttpContent content = buildContent(segmentedData); getProxyRequestQueue(ctx) .onRequestWriteOrEnqueue(ctx, segmentedData.streamId(), content, promise); } else if (msg instanceof Request) { Request request = (Request) msg; HttpRequest message = buildRequest(request); getProxyRequestQueue(ctx).onRequestWriteOrEnqueue(ctx, request.streamId(), message, promise); } else { ctx.write(msg, promise); } } }
public void onResponseDrainNext(ChannelHandlerContext ctx, Response response) { currentResponse = response; if (response.endOfMessage()) { streamQueue.remove(response.streamId()); nextStreamsQueue() .ifPresent( queue -> { while (!queue.isEmpty()) { PendingRequest pending = queue.remove(); log.debug("writing enqueued h2-h1 proxy request {}", pending.request); if (queue.isEmpty()) { ctx.writeAndFlush(pending.request, pending.promise); } else { ctx.write(pending.request, pending.promise); } } }); } }
assertTrue(subject.isEmpty()); subject.onRequestWriteOrEnqueue(mockCtx, 3, "request 1a", mock(ChannelPromise.class)); verify(mockCtx).write(eq("request 1a"), any()); assertTrue(subject.isEmpty()); subject.onRequestWriteOrEnqueue(mockCtx, 3, "request 1b", mock(ChannelPromise.class)); verify(mockCtx).write(eq("request 1b"), any()); assertTrue(subject.isEmpty()); subject.onRequestWriteOrEnqueue(mockCtx, 5, "request 2a", mock(ChannelPromise.class)); verify(mockCtx, never()).write(eq("request 2a"), any()); assertFalse(subject.isEmpty()); subject.onRequestWriteOrEnqueue(mockCtx, 3, "request 1c", mock(ChannelPromise.class)); verify(mockCtx).write(eq("request 1c"), any()); assertFalse(subject.isEmpty()); subject.onRequestWriteOrEnqueue(mockCtx, 5, "request 2b", mock(ChannelPromise.class)); verify(mockCtx, never()).write(eq("request 2b"), any()); assertFalse(subject.isEmpty()); assertEquals(3, subject.currentProxiedH2StreamId().orElse(0), 0); Response stream3Response0 = DefaultSegmentedResponse.builder() subject.onResponseDrainNext(mockCtx, stream3Response0); assertSame(stream3Response0, subject.currentResponse().orElse(null));
@Before public void beforeEach() { MockitoAnnotations.initMocks(this); subject = new Http2To1ProxyRequestQueue(); }