Skip to content

Commit cb78c5d

Browse files
authored
merge of the current 3.0 into the 3.1
merge of the current 3.0 into the 3.1
2 parents 97e6743 + 71704af commit cb78c5d

File tree

65 files changed

+2691
-515
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+2691
-515
lines changed

NOTICE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ KineticJS, v4.7.1
9595
* Project: http://www.kineticjs.com, https://github.com/ericdrowell/KineticJS
9696
* Copyright: Eric Rowell
9797

98-
org.objectweb.asm Version 9.7.1
98+
org.objectweb.asm Version 9.8
9999
* License: Modified BSD (https://asm.ow2.io/license.html)
100100
* Copyright (c) 2000-2011 INRIA, France Telecom. All rights reserved.
101101

connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyClientHandler.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ class JerseyClientHandler extends SimpleChannelInboundHandler<HttpObject> {
9191

9292
@Override
9393
public void channelReadComplete(ChannelHandlerContext ctx) {
94-
notifyResponse();
94+
notifyResponse(ctx);
9595
}
9696

9797
@Override
@@ -107,7 +107,7 @@ public void channelInactive(ChannelHandlerContext ctx) {
107107
}
108108
}
109109

110-
protected void notifyResponse() {
110+
protected void notifyResponse(ChannelHandlerContext ctx) {
111111
if (jerseyResponse != null) {
112112
ClientResponse cr = jerseyResponse;
113113
jerseyResponse = null;
@@ -146,6 +146,7 @@ protected void notifyResponse() {
146146
} else {
147147
ClientRequest newReq = new ClientRequest(jerseyRequest);
148148
newReq.setUri(newUri);
149+
ctx.close();
149150
if (redirectController.prepareRedirect(newReq, cr)) {
150151
final NettyConnector newConnector = new NettyConnector(newReq.getClient());
151152
newConnector.execute(newReq, redirectUriHistory, new CompletableFuture<ClientResponse>() {
@@ -227,7 +228,7 @@ public String getReasonPhrase() {
227228

228229
if (msg instanceof LastHttpContent) {
229230
responseDone.complete(null);
230-
notifyResponse();
231+
notifyResponse(ctx);
231232
}
232233
}
233234
}
Lines changed: 96 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023, 2024 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
@@ -16,112 +16,133 @@
1616

1717
package org.glassfish.jersey.netty.connector;
1818

19-
import io.netty.channel.Channel;
20-
import io.netty.channel.ChannelFuture;
2119
import io.netty.channel.ChannelHandlerContext;
2220
import 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;
2622
import io.netty.handler.codec.http.HttpResponse;
2723
import 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

3126
import jakarta.ws.rs.ProcessingException;
27+
import java.io.IOException;
28+
import java.util.ArrayList;
3229
import java.util.Arrays;
3330
import 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;
3732
import java.util.concurrent.TimeoutException;
3833

3934
public 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> statusesToBeConsidered = new ArrayList<>(reSendErrorStatuses);
45+
46+
static {
47+
statusesToBeConsidered.addAll(finalErrorStatuses);
48+
statusesToBeConsidered.add(HttpResponseStatus.CONTINUE);
49+
}
50+
51+
private HttpResponseStatus status = null;
52+
53+
private CountDownLatch latch = null;
54+
55+
private boolean propagateLastMessage = false;
4856

4957
@Override
5058
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())) {
59+
60+
if (checkExpectResponse(msg) || checkInvalidExpect(msg)) {
61+
currentState = ExpectationState.AWAITING;
62+
}
63+
switch (currentState) {
64+
case AWAITING:
65+
final HttpResponse response = (HttpResponse) msg;
66+
status = response.status();
67+
boolean handshakeDone = processErrorStatuses(status) || msg instanceof FullHttpMessage;
68+
currentState = (handshakeDone) ? ExpectationState.IDLE : ExpectationState.FINISHING;
69+
processLatch();
70+
return;
71+
case FINISHING:
72+
if (msg instanceof LastHttpContent) {
73+
currentState = ExpectationState.IDLE;
74+
if (propagateLastMessage) {
75+
propagateLastMessage = false;
76+
ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
77+
}
78+
}
79+
return;
80+
default:
5781
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().context(JerseyExpectContinueHandler.class) != null) {
64-
ctx.pipeline().remove(JerseyExpectContinueHandler.class);
65-
}
66-
ctx.fireChannelRead(msg); //bypass the message to the next handler in line
6782
}
6883
}
6984

70-
CompletableFuture<HttpResponseStatus> processExpect100ContinueRequest(HttpRequest nettyRequest,
71-
ClientRequest jerseyRequest,
72-
Channel ch,
73-
Integer timeout)
74-
throws InterruptedException, ExecutionException, TimeoutException {
75-
//check for 100-Continue presence/availability
76-
final Expect100ContinueConnectorExtension expect100ContinueExtension
77-
= new Expect100ContinueConnectorExtension();
78-
79-
final DefaultFullHttpRequest nettyRequestHeaders =
80-
new DefaultFullHttpRequest(nettyRequest.protocolVersion(), nettyRequest.method(), nettyRequest.uri());
81-
nettyRequestHeaders.headers().setAll(nettyRequest.headers());
82-
83-
if (!nettyRequestHeaders.headers().contains(HttpHeaderNames.HOST)) {
84-
nettyRequestHeaders.headers().add(HttpHeaderNames.HOST, jerseyRequest.getUri().getHost());
85+
private boolean checkExpectResponse(Object msg) {
86+
if (currentState == ExpectationState.IDLE && latch != null && msg instanceof HttpResponse) {
87+
return statusesToBeConsidered.contains(((HttpResponse) msg).status());
8588
}
89+
return false;
90+
}
91+
92+
private boolean checkInvalidExpect(Object msg) {
93+
return (ExpectationState.IDLE.equals(currentState)
94+
&& msg instanceof HttpResponse
95+
&& (HttpResponseStatus.CONTINUE.equals(((HttpResponse) msg).status())
96+
|| reSendErrorStatuses.contains(((HttpResponse) msg).status()))
97+
);
98+
}
8699

87-
//If Expect:100-continue feature is enabled and client supports it, the nettyRequestHeaders will be
88-
//enriched with the 'Expect:100-continue' header.
89-
expect100ContinueExtension.invoke(jerseyRequest, nettyRequestHeaders);
90-
91-
final ChannelFuture expect100ContinueFuture = (HttpUtil.is100ContinueExpected(nettyRequestHeaders))
92-
// Send only head of the HTTP request enriched with Expect:100-continue header.
93-
? ch.writeAndFlush(nettyRequestHeaders)
94-
// Expect:100-Continue either is not supported or is turned off
95-
: null;
96-
isExpected = expect100ContinueFuture != null;
97-
if (!isExpected) {
98-
ch.pipeline().remove(JerseyExpectContinueHandler.class);
99-
} else {
100-
final HttpResponseStatus status = expectedFuture
101-
.get(timeout, TimeUnit.MILLISECONDS);
102-
103-
processExpectationStatus(status);
100+
boolean processErrorStatuses(HttpResponseStatus status) {
101+
if (reSendErrorStatuses.contains(status)) {
102+
propagateLastMessage = true;
104103
}
105-
return expectedFuture;
104+
return (finalErrorStatuses.contains(status));
106105
}
107106

108-
private void processExpectationStatus(HttpResponseStatus status)
109-
throws TimeoutException {
107+
void processExpectationStatus()
108+
throws TimeoutException, IOException {
109+
if (status == null) {
110+
throw new TimeoutException(); // continue without expectations
111+
}
110112
if (!statusesToBeConsidered.contains(status)) {
111113
throw new ProcessingException(LocalizationMessages
112114
.UNEXPECTED_VALUE_FOR_EXPECT_100_CONTINUE_STATUSES(status.code()), null);
113115
}
114-
if (!expectedFuture.isDone() || HttpResponseStatus.EXPECTATION_FAILED.equals(status)) {
115-
isExpected = false;
116-
throw new TimeoutException(); // continue without expectations
116+
117+
if (finalErrorStatuses.contains(status)) {
118+
throw new IOException(LocalizationMessages
119+
.EXPECT_100_CONTINUE_FAILED_REQUEST_FAILED(), null);
117120
}
118-
if (!HttpResponseStatus.CONTINUE.equals(status)) {
119-
throw new ProcessingException(LocalizationMessages
120-
.UNEXPECTED_VALUE_FOR_EXPECT_100_CONTINUE_STATUSES(status.code()), null);
121+
122+
if (reSendErrorStatuses.contains(status)) {
123+
throw new TimeoutException(LocalizationMessages
124+
.EXPECT_100_CONTINUE_FAILED_REQUEST_SHOULD_BE_RESENT()); // Re-send request without expectations
125+
}
126+
127+
}
128+
129+
void resetHandler() {
130+
latch = null;
131+
}
132+
133+
void attachCountDownLatch(CountDownLatch latch) {
134+
this.latch = latch;
135+
}
136+
137+
private void processLatch() {
138+
if (latch != null) {
139+
latch.countDown();
121140
}
122141
}
123142

124-
boolean isExpected() {
125-
return isExpected;
143+
private enum ExpectationState {
144+
AWAITING,
145+
FINISHING,
146+
IDLE
126147
}
127-
}
148+
}

connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
1717
package org.glassfish.jersey.netty.connector;
1818

1919
import java.io.IOException;
20-
import java.io.InterruptedIOException;
2120
import java.io.OutputStream;
22-
import java.io.OutputStreamWriter;
2321
import java.net.InetSocketAddress;
2422
import java.net.SocketAddress;
2523
import java.net.URI;
@@ -34,7 +32,6 @@
3432
import java.util.concurrent.CompletableFuture;
3533
import java.util.concurrent.CompletionException;
3634
import java.util.concurrent.CountDownLatch;
37-
import java.util.concurrent.ExecutionException;
3835
import java.util.concurrent.ExecutorService;
3936
import java.util.concurrent.Future;
4037
import java.util.concurrent.TimeUnit;
@@ -70,6 +67,7 @@
7067
import io.netty.handler.codec.http.HttpRequest;
7168
import io.netty.handler.codec.http.HttpUtil;
7269
import io.netty.handler.codec.http.HttpVersion;
70+
import io.netty.handler.codec.http.LastHttpContent;
7371
import io.netty.handler.proxy.HttpProxyHandler;
7472
import io.netty.handler.proxy.ProxyHandler;
7573
import io.netty.handler.ssl.ApplicationProtocolConfig;
@@ -257,6 +255,8 @@ protected void execute(final ClientRequest jerseyRequest, final Set<URI> redirec
257255
}
258256
}
259257

258+
final JerseyExpectContinueHandler expect100ContinueHandler = new JerseyExpectContinueHandler();
259+
260260
if (chan == null) {
261261
Integer connectTimeout = jerseyRequest.resolveProperty(ClientProperties.CONNECT_TIMEOUT, 0);
262262
Bootstrap b = new Bootstrap();
@@ -329,8 +329,8 @@ protected void initChannel(SocketChannel ch) throws Exception {
329329
final Integer maxInitialLineLength = ClientProperties.getValue(config.getProperties(),
330330
NettyClientProperties.MAX_INITIAL_LINE_LENGTH,
331331
NettyClientProperties.DEFAULT_INITIAL_LINE_LENGTH);
332-
333332
p.addLast(new HttpClientCodec(maxInitialLineLength, maxHeaderSize, maxChunkSize));
333+
p.addLast(EXPECT_100_CONTINUE_HANDLER, expect100ContinueHandler);
334334
p.addLast(new ChunkedWriteHandler());
335335
p.addLast(new HttpContentDecompressor());
336336
}
@@ -359,11 +359,10 @@ protected void initChannel(SocketChannel ch) throws Exception {
359359
final Channel ch = chan;
360360
JerseyClientHandler clientHandler =
361361
new JerseyClientHandler(jerseyRequest, responseAvailable, responseDone, redirectUriHistory, this);
362-
final JerseyExpectContinueHandler expect100ContinueHandler = new JerseyExpectContinueHandler();
362+
363363
// read timeout makes sense really as an inactivity timeout
364364
ch.pipeline().addLast(READ_TIMEOUT_HANDLER,
365365
new IdleStateHandler(0, 0, timeout, TimeUnit.MILLISECONDS));
366-
ch.pipeline().addLast(EXPECT_100_CONTINUE_HANDLER, expect100ContinueHandler);
367366
ch.pipeline().addLast(REQUEST_HANDLER, clientHandler);
368367

369368
responseDone.whenComplete((_r, th) -> {
@@ -446,22 +445,11 @@ public void operationComplete(io.netty.util.concurrent.Future<? super Void> futu
446445
// // Set later after the entity is "written"
447446
// break;
448447
}
449-
try {
450-
expect100ContinueHandler.processExpect100ContinueRequest(nettyRequest, jerseyRequest,
451-
ch, expect100ContinueTimeout);
452-
} catch (ExecutionException e) {
453-
responseDone.completeExceptionally(e);
454-
} catch (TimeoutException e) {
455-
//Expect:100-continue allows timeouts by the spec
456-
//just removing the pipeline from processing
457-
if (ch.pipeline().context(JerseyExpectContinueHandler.class) != null) {
458-
ch.pipeline().remove(EXPECT_100_CONTINUE_HANDLER);
459-
}
460-
}
461448

462449
final CountDownLatch headersSet = new CountDownLatch(1);
463450
final CountDownLatch contentLengthSet = new CountDownLatch(1);
464451

452+
465453
jerseyRequest.setStreamProvider(new OutboundMessageContext.StreamProvider() {
466454
@Override
467455
public OutputStream getOutputStream(int contentLength) throws IOException {
@@ -486,7 +474,6 @@ public void run() {
486474

487475
try {
488476
jerseyRequest.writeEntity();
489-
490477
if (entityWriter.getType() == NettyEntityWriter.Type.DELAYED) {
491478
nettyRequest.headers().set(HttpHeaderNames.CONTENT_LENGTH, entityWriter.getLength());
492479
contentLengthSet.countDown();
@@ -506,12 +493,36 @@ public void run() {
506493
});
507494

508495
headersSet.await();
509-
if (!expect100ContinueHandler.isExpected()) {
510-
// Send the HTTP request. Expect:100-continue processing is not applicable
511-
// in this case.
496+
new Expect100ContinueConnectorExtension().invoke(jerseyRequest, nettyRequest);
497+
498+
boolean continueExpected = HttpUtil.is100ContinueExpected(nettyRequest);
499+
boolean expectationsFailed = false;
500+
501+
if (continueExpected) {
502+
final CountDownLatch expect100ContinueLatch = new CountDownLatch(1);
503+
expect100ContinueHandler.attachCountDownLatch(expect100ContinueLatch);
504+
//send expect request, sync and wait till either response or timeout received
512505
entityWriter.writeAndFlush(nettyRequest);
506+
expect100ContinueLatch.await(expect100ContinueTimeout, TimeUnit.MILLISECONDS);
507+
try {
508+
expect100ContinueHandler.processExpectationStatus();
509+
} catch (TimeoutException e) {
510+
//Expect:100-continue allows timeouts by the spec
511+
//so, send request directly without Expect header.
512+
expectationsFailed = true;
513+
} finally {
514+
//restore request and handler to the original state.
515+
HttpUtil.set100ContinueExpected(nettyRequest, false);
516+
expect100ContinueHandler.resetHandler();
517+
}
513518
}
514519

520+
if (!continueExpected || expectationsFailed) {
521+
if (expectationsFailed) {
522+
ch.pipeline().writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT).sync();
523+
}
524+
entityWriter.writeAndFlush(nettyRequest);
525+
}
515526
if (HttpUtil.isTransferEncodingChunked(nettyRequest)) {
516527
entityWriter.write(new HttpChunkedInput(entityWriter.getChunkedInput()));
517528
} else {

0 commit comments

Comments
 (0)