11/*
2- * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
2+ * Copyright (c) 2023, 2025 Oracle and/or its affiliates. All rights reserved.
33 *
44 * This program and the accompanying materials are made available under the
55 * terms of the Eclipse Public License v. 2.0, which is available at
1616
1717package org .glassfish .jersey .netty .connector ;
1818
19- import io .netty .channel .Channel ;
20- import io .netty .channel .ChannelFuture ;
2119import io .netty .channel .ChannelHandlerContext ;
2220import io .netty .channel .ChannelInboundHandlerAdapter ;
23- import io .netty .handler .codec .http .DefaultFullHttpRequest ;
24- import io .netty .handler .codec .http .HttpHeaderNames ;
25- import io .netty .handler .codec .http .HttpRequest ;
21+ import io .netty .handler .codec .http .FullHttpMessage ;
2622import io .netty .handler .codec .http .HttpResponse ;
2723import io .netty .handler .codec .http .HttpResponseStatus ;
28- import io .netty .handler .codec .http .HttpUtil ;
29- import org .glassfish .jersey .client .ClientRequest ;
24+ import io .netty .handler .codec .http .LastHttpContent ;
3025
3126import javax .ws .rs .ProcessingException ;
27+ import java .io .IOException ;
28+ import java .util .ArrayList ;
3229import java .util .Arrays ;
3330import java .util .List ;
34- import java .util .concurrent .CompletableFuture ;
35- import java .util .concurrent .ExecutionException ;
36- import java .util .concurrent .TimeUnit ;
31+ import java .util .concurrent .CountDownLatch ;
3732import java .util .concurrent .TimeoutException ;
3833
3934public class JerseyExpectContinueHandler extends ChannelInboundHandlerAdapter {
4035
41- private boolean isExpected ;
36+ private ExpectationState currentState = ExpectationState . IDLE ;
4237
43- private static final List <HttpResponseStatus > statusesToBeConsidered = Arrays .asList (HttpResponseStatus .CONTINUE ,
44- HttpResponseStatus .UNAUTHORIZED , HttpResponseStatus .EXPECTATION_FAILED ,
45- HttpResponseStatus .METHOD_NOT_ALLOWED , HttpResponseStatus .REQUEST_ENTITY_TOO_LARGE );
38+ private static final List <HttpResponseStatus > finalErrorStatuses = Arrays .asList (HttpResponseStatus .UNAUTHORIZED ,
39+ HttpResponseStatus .REQUEST_ENTITY_TOO_LARGE );
40+ private static final List <HttpResponseStatus > reSendErrorStatuses = Arrays .asList (
41+ HttpResponseStatus .METHOD_NOT_ALLOWED ,
42+ HttpResponseStatus .EXPECTATION_FAILED );
4643
47- private CompletableFuture <HttpResponseStatus > expectedFuture = new CompletableFuture <>();
44+ private static final List <HttpResponseStatus > errorStatuses = new ArrayList <>(finalErrorStatuses );
45+ private static final List <HttpResponseStatus > statusesToBeConsidered = new ArrayList <>(reSendErrorStatuses );
46+
47+ static {
48+ errorStatuses .addAll (reSendErrorStatuses );
49+ statusesToBeConsidered .addAll (finalErrorStatuses );
50+ statusesToBeConsidered .add (HttpResponseStatus .CONTINUE );
51+ }
52+
53+ private HttpResponseStatus status = null ;
54+
55+ private CountDownLatch latch = null ;
56+
57+ private boolean propagateLastMessage = false ;
4858
4959 @ Override
5060 public void channelRead (ChannelHandlerContext ctx , Object msg ) throws Exception {
51- if (isExpected && msg instanceof HttpResponse ) {
52- final HttpResponse response = (HttpResponse ) msg ;
53- if (statusesToBeConsidered .contains (response .status ())) {
54- expectedFuture .complete (response .status ());
55- }
56- if (!HttpResponseStatus .CONTINUE .equals (response .status ())) {
61+
62+ if (checkExpectResponse (msg )) {
63+ currentState = ExpectationState .AWAITING ;
64+ }
65+ switch (currentState ) {
66+ case AWAITING :
67+ final HttpResponse response = (HttpResponse ) msg ;
68+ status = response .status ();
69+
70+ boolean handshakeDone = processErrorStatuses (status , ctx ) || msg instanceof FullHttpMessage ;
71+ currentState = (handshakeDone ) ? ExpectationState .IDLE : ExpectationState .FINISHING ;
72+ processLatch ();
73+ return ;
74+ case FINISHING :
75+ if (msg instanceof LastHttpContent ) {
76+ currentState = ExpectationState .IDLE ;
77+ if (propagateLastMessage ) {
78+ propagateLastMessage = false ;
79+ ctx .writeAndFlush (LastHttpContent .EMPTY_LAST_CONTENT );
80+ }
81+ }
82+ return ;
83+ default :
5784 ctx .fireChannelRead (msg ); //bypass the message to the next handler in line
58- } else {
59- ctx .pipeline ().remove (JerseyExpectContinueHandler .class );
60- }
61- } else {
62- if (!isExpected ) {
63- ctx .pipeline ().remove (JerseyExpectContinueHandler .class );
64- }
65- ctx .fireChannelRead (msg ); //bypass the message to the next handler in line
6685 }
6786 }
6887
69- CompletableFuture <HttpResponseStatus > processExpect100ContinueRequest (HttpRequest nettyRequest ,
70- ClientRequest jerseyRequest ,
71- Channel ch ,
72- Integer timeout )
73- throws InterruptedException , ExecutionException , TimeoutException {
74- //check for 100-Continue presence/availability
75- final Expect100ContinueConnectorExtension expect100ContinueExtension
76- = new Expect100ContinueConnectorExtension ();
77-
78- final DefaultFullHttpRequest nettyRequestHeaders =
79- new DefaultFullHttpRequest (nettyRequest .protocolVersion (), nettyRequest .method (), nettyRequest .uri ());
80- nettyRequestHeaders .headers ().setAll (nettyRequest .headers ());
81-
82- if (!nettyRequestHeaders .headers ().contains (HttpHeaderNames .HOST )) {
83- nettyRequestHeaders .headers ().add (HttpHeaderNames .HOST , jerseyRequest .getUri ().getHost ());
88+ private boolean checkExpectResponse (Object msg ) {
89+ if (currentState == ExpectationState .IDLE && latch != null && msg instanceof HttpResponse ) {
90+ return statusesToBeConsidered .contains (((HttpResponse ) msg ).status ());
8491 }
92+ return false ;
93+ }
8594
86- //If Expect:100-continue feature is enabled and client supports it, the nettyRequestHeaders will be
87- //enriched with the 'Expect:100-continue' header.
88- expect100ContinueExtension .invoke (jerseyRequest , nettyRequestHeaders );
89-
90- final ChannelFuture expect100ContinueFuture = (HttpUtil .is100ContinueExpected (nettyRequestHeaders ))
91- // Send only head of the HTTP request enriched with Expect:100-continue header.
92- ? ch .writeAndFlush (nettyRequestHeaders )
93- // Expect:100-Continue either is not supported or is turned off
94- : null ;
95- isExpected = expect100ContinueFuture != null ;
96- if (!isExpected ) {
97- ch .pipeline ().remove (JerseyExpectContinueHandler .class );
98- } else {
99- final HttpResponseStatus status = expectedFuture
100- .get (timeout , TimeUnit .MILLISECONDS );
101-
102- processExpectationStatus (status );
95+ boolean processErrorStatuses (HttpResponseStatus status , ChannelHandlerContext ctx )
96+ throws InterruptedException {
97+ if (reSendErrorStatuses .contains (status )) {
98+ propagateLastMessage = true ;
10399 }
104- return expectedFuture ;
100+ return ( finalErrorStatuses . contains ( status )) ;
105101 }
106102
107- private void processExpectationStatus (HttpResponseStatus status )
108- throws TimeoutException {
103+ boolean processExpectationStatus ()
104+ throws TimeoutException , IOException {
105+ if (status == null ) {
106+ throw new TimeoutException (); // continue without expectations
107+ }
109108 if (!statusesToBeConsidered .contains (status )) {
110109 throw new ProcessingException (LocalizationMessages
111110 .UNEXPECTED_VALUE_FOR_EXPECT_100_CONTINUE_STATUSES (status .code ()), null );
112111 }
113- if (!expectedFuture .isDone () || HttpResponseStatus .EXPECTATION_FAILED .equals (status )) {
114- isExpected = false ;
115- throw new TimeoutException (); // continue without expectations
112+
113+ if (finalErrorStatuses .contains (status )) {
114+ throw new IOException (LocalizationMessages
115+ .EXPECT_100_CONTINUE_FAILED_REQUEST_FAILED (), null );
116116 }
117- if (!HttpResponseStatus .CONTINUE .equals (status )) {
118- throw new ProcessingException (LocalizationMessages
119- .UNEXPECTED_VALUE_FOR_EXPECT_100_CONTINUE_STATUSES (status .code ()), null );
117+
118+ if (reSendErrorStatuses .contains (status )) {
119+ throw new TimeoutException (LocalizationMessages
120+ .EXPECT_100_CONTINUE_FAILED_REQUEST_SHOULD_BE_RESENT ()); // Re-send request without expectations
121+ }
122+
123+ return true ;
124+ }
125+
126+ void resetHandler () {
127+ latch = null ;
128+ }
129+
130+ void attachCountDownLatch (CountDownLatch latch ) {
131+ this .latch = latch ;
132+ }
133+
134+ private void processLatch () {
135+ if (latch != null ) {
136+ latch .countDown ();
120137 }
121138 }
122139
123- boolean isExpected () {
124- return isExpected ;
140+ private enum ExpectationState {
141+ AWAITING ,
142+ FINISHING ,
143+ IDLE
125144 }
126- }
145+ }
0 commit comments