Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
* Copyright (c) 2022, 2025 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -388,6 +388,16 @@ default <T> ClientResponseTyped<T> outputStream(OutputStreamHandler outputStream
*/
T readContinueTimeout(Duration readContinueTimeout);

/**
* Whether Expect 100-Continue header is sent to verify server availability before sending an entity.
* Can be used to override the setting inherited from {@link HttpClientConfig#sendExpectContinue()}
* on a per-request basis.
*
* @param sendExpectContinue value to override behavior for a single request
* @return updated client request
*/
T sendExpectContinue(boolean sendExpectContinue);

/**
* Handle output stream.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
* Copyright (c) 2023, 2025 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -87,18 +87,30 @@ public abstract class ClientRequestBase<T extends ClientRequest<T>, R extends Ht
private Proxy proxy;
private boolean keepAlive;
private ClientConnection connection;
private Boolean sendExpectContinue;

protected ClientRequestBase(HttpClientConfig clientConfig,
WebClientCookieManager cookieManager,
String protocolId,
Method method,
ClientUri clientUri,
Map<String, String> properties) {
this(clientConfig, cookieManager, protocolId, method, clientUri, null, properties);
}

protected ClientRequestBase(HttpClientConfig clientConfig,
WebClientCookieManager cookieManager,
String protocolId,
Method method,
ClientUri clientUri,
Boolean sendExpectContinue,
Map<String, String> properties) {
this.clientConfig = clientConfig;
this.cookieManager = cookieManager;
this.protocolId = protocolId;
this.method = method;
this.clientUri = clientUri;
this.sendExpectContinue = sendExpectContinue;
this.properties = new HashMap<>(properties);

this.headers = clientConfig.defaultRequestHeaders();
Expand Down Expand Up @@ -277,6 +289,12 @@ public final R outputStream(OutputStreamHandler outputStreamConsumer) {
return doOutputStream(outputStreamConsumer);
}

@Override
public T sendExpectContinue(boolean sendExpectContinue) {
this.sendExpectContinue = sendExpectContinue;
return identity();
}

/**
* Append additional headers before sending the request.
*/
Expand Down Expand Up @@ -364,6 +382,11 @@ public boolean skipUriEncoding() {
return skipUriEncoding;
}

@Override
public Optional<Boolean> sendExpectContinue() {
return Optional.ofNullable(sendExpectContinue);
}

protected abstract R doSubmit(Object entity);

protected abstract R doOutputStream(OutputStreamHandler outputStreamHandler);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
* Copyright (c) 2025 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -112,4 +112,12 @@ public interface FullClientRequest<T extends ClientRequest<T>> extends ClientReq
* @return whether to skip encoding
*/
boolean skipUriEncoding();

/**
* Whether Expect 100-Continue header is sent to verify server availability before sending
* an entity. Overrides the setting from {@link HttpClientConfig#sendExpectContinue()}.
*
* @return Expect 100-Continue value if set
*/
Optional<Boolean> sendExpectContinue();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
* Copyright (c) 2023, 2025 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -51,7 +51,22 @@ public class HttpClientRequest extends ClientRequestBase<HttpClientRequest, Http
List<LoomClient.ProtocolSpi> tcpProtocols,
List<String> tcpProtocolIds,
LruCache<LoomClient.EndpointKey, HttpClientSpi> clientSpiCache) {
super(clientConfig, webClient.cookieManager(), "any", method, clientUri, clientConfig.properties());
this(webClient, clientConfig, method, clientUri, protocolsToClients, protocols, tcpProtocols,
tcpProtocolIds, null, clientSpiCache);
}

HttpClientRequest(WebClient webClient,
WebClientConfig clientConfig,
Method method,
ClientUri clientUri,
Map<String, LoomClient.ProtocolSpi> protocolsToClients,
List<LoomClient.ProtocolSpi> protocols,
List<LoomClient.ProtocolSpi> tcpProtocols,
List<String> tcpProtocolIds,
Boolean send100Continue,
LruCache<LoomClient.EndpointKey, HttpClientSpi> clientSpiCache) {
super(clientConfig, webClient.cookieManager(), "any", method, clientUri,
send100Continue, clientConfig.properties());
this.webClient = webClient;
this.clients = protocolsToClients;
this.protocols = protocols;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
* Copyright (c) 2023, 2025 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -114,6 +114,11 @@ public FakeHttpClientRequest readContinueTimeout(Duration readContinueTimeout) {
return this;
}

@Override
public FakeHttpClientRequest sendExpectContinue(boolean sendExpectContinue) {
return null;
}

@Override
public FakeHttpClientRequest tls(Tls tls) {
return this;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
* Copyright (c) 2023, 2025 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -354,7 +354,10 @@ private void writeContent(BufferData buffer) throws IOException {
}

private void sendPrologueAndHeader() {
boolean expects100Continue = clientConfig.sendExpectContinue() && !noData;
// setting for expect 100 header, can be overridden for each request
boolean expects100Continue = !noData;
Boolean override100Continue = originalRequest.sendExpectContinue().orElse(null);
expects100Continue &= (override100Continue != null) ? override100Continue : clientConfig.sendExpectContinue();
if (expects100Continue) {
headers.add(HeaderValues.EXPECT_100);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
* Copyright (c) 2022, 2025 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -74,6 +74,7 @@ public ClientRequest<?> clientRequest(FullClientRequest<?> clientRequest, Client
Http1ClientRequest request = new Http1ClientRequestImpl(this,
clientRequest.method(),
clientUri,
clientRequest.sendExpectContinue().orElse(null),
clientRequest.properties());

clientRequest.connection().ifPresent(request::connection);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2024 Oracle and/or its affiliates.
* Copyright (c) 2022, 2025 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -43,10 +43,20 @@ class Http1ClientRequestImpl extends ClientRequestBase<Http1ClientRequest, Http1
Method method,
ClientUri clientUri,
Map<String, String> properties) {
this(http1Client, method, clientUri, null, properties);
}

Http1ClientRequestImpl(Http1ClientImpl http1Client,
Method method,
ClientUri clientUri,
Boolean sendExpectContinue,
Map<String, String> properties) {
super(http1Client.clientConfig(),
http1Client.webClient().cookieManager(),
Http1Client.PROTOCOL_ID,
method, clientUri,
method,
clientUri,
sendExpectContinue,
properties);
this.http1Client = http1Client;
}
Expand All @@ -59,6 +69,7 @@ class Http1ClientRequestImpl extends ClientRequestBase<Http1ClientRequest, Http1
this(request.http1Client,
method,
clientUri,
null,
properties);

followRedirects(request.followRedirects());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright (c) 2025 Oracle and/or its affiliates.
*
* 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.helidon.webclient.tests;

import io.helidon.http.HeaderValues;
import io.helidon.http.Status;
import io.helidon.webclient.api.HttpClientResponse;
import io.helidon.webclient.api.WebClient;
import io.helidon.webclient.http1.Http1Client;
import io.helidon.webserver.http.HttpRouting;
import io.helidon.webserver.testing.junit5.ServerTest;
import io.helidon.webserver.testing.junit5.SetUpRoute;

import org.junit.jupiter.api.Test;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

@ServerTest
class Send100ContinueTest {
private static final byte[] DATA = new byte[] { 1, 2, 3 };

private final WebClient webClient;
private final Http1Client http1Client;

Send100ContinueTest(WebClient webClient, Http1Client http1Client) {
this.webClient = webClient;
this.http1Client = http1Client;
}

@SetUpRoute
static void routing(HttpRouting.Builder router) {
router.post("/100Continue", (req, res) -> {
res.status(req.headers().contains(HeaderValues.EXPECT_100) ?
Status.OK_200 : Status.BAD_REQUEST_400).send();
})
.post("/no100Continue", (req, res) -> {
res.status(req.headers().contains(HeaderValues.EXPECT_100) ?
Status.BAD_REQUEST_400 : Status.OK_200).send();
});
}

@Test
public void test100ContinueDefaultWeb() {
try (HttpClientResponse response = webClient.post("/100Continue")
.outputStream(os -> { os.write(DATA); os.close(); })) {
assertThat(response.status(), is(Status.OK_200));
}
}

@Test
public void no100ContinueWeb() {
try (HttpClientResponse response = webClient.post("/no100Continue")
.sendExpectContinue(false) // turns off 100 continue
.outputStream(os -> { os.write(DATA); os.close(); })) {
assertThat(response.status(), is(Status.OK_200));
}
}

@Test
public void test100ContinueDefaultHttp1() {
try (HttpClientResponse response = http1Client.post("/100Continue")
.outputStream(os -> { os.write(DATA); os.close(); })) {
assertThat(response.status(), is(Status.OK_200));
}
}

@Test
public void no100ContinueHttp1() {
try (HttpClientResponse response = http1Client.post("/no100Continue")
.sendExpectContinue(false) // turns off 100 continue
.outputStream(os -> { os.write(DATA); os.close(); })) {
assertThat(response.status(), is(Status.OK_200));
}
}
}
Loading