Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTPCLIENT-751: Support for RFC 2817 (Upgrading to TLS Within HTTP/1.1) #542

Merged
merged 4 commits into from
Jan 29, 2024
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
Expand Up @@ -57,6 +57,7 @@
import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
import org.apache.hc.core5.http.io.HttpClientConnection;
import org.apache.hc.core5.http.io.HttpRequestHandler;
import org.apache.hc.core5.http.io.HttpResponseInformationCallback;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.InputStreamEntity;
import org.apache.hc.core5.http.io.entity.StringEntity;
Expand Down Expand Up @@ -137,8 +138,17 @@ public ClassicHttpResponse execute(
final ClassicHttpRequest request,
final HttpClientConnection conn,
final HttpContext context) throws IOException, HttpException {
return execute(request, conn, null, context);
}

@Override
public ClassicHttpResponse execute(
final ClassicHttpRequest request,
final HttpClientConnection conn,
final HttpResponseInformationCallback informationCallback,
final HttpContext context) throws IOException, HttpException {

final ClassicHttpResponse response = super.execute(request, conn, context);
final ClassicHttpResponse response = super.execute(request, conn, informationCallback, context);
final Object marker = context.getAttribute(MARKER);
if (marker == null) {
context.setAttribute(MARKER, Boolean.TRUE);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http;

import javax.net.ssl.SSLSession;

import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.http.ProtocolVersion;

/**
* @since 5.4
*/
public final class EndpointInfo {

private final ProtocolVersion protocol;
private final SSLSession sslSession;

@Internal
public EndpointInfo(final ProtocolVersion protocol, final SSLSession sslSession) {
this.protocol = protocol;
this.sslSession = sslSession;
}

public ProtocolVersion getProtocol() {
return protocol;
}

public SSLSession getSslSession() {
return sslSession;
}

@Override
public String toString() {
return "EndpointInfo{" +
"protocol=" + protocol +
", sslSession=" + sslSession +
'}';
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

package org.apache.hc.client5.http.async;

import org.apache.hc.client5.http.EndpointInfo;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.core5.annotation.Internal;
Expand Down Expand Up @@ -133,6 +134,13 @@ default void upgradeTls(HttpClientContext context,
}
}

/**
* Returns information about the underlying endpoint when connected or {@code null} otherwise.
*/
default EndpointInfo getEndpointInfo() {
return null;
}

/**
* Validates the connection making sure it can be used to execute requests.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@

import java.io.IOException;

import org.apache.hc.client5.http.EndpointInfo;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.concurrent.CancellableDependency;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.io.HttpResponseInformationCallback;
import org.apache.hc.core5.util.TimeValue;

/**
Expand Down Expand Up @@ -121,6 +123,13 @@ void acquireEndpoint(
*/
void upgradeTls(HttpClientContext context) throws IOException;

/**
* Returns information about the underlying endpoint when connected or {@code null} otherwise.
*/
default EndpointInfo getEndpointInfo() {
return null;
}

/**
* Executes HTTP request using the given context.
*
Expand All @@ -133,6 +142,24 @@ ClassicHttpResponse execute(
ClassicHttpRequest request,
HttpClientContext context) throws IOException, HttpException;

/**
* Executes HTTP request using the given context.
*
* @param id unique operation ID or {@code null}.
* @param request the request message.
* @param informationCallback information (1xx) response handler
* @param context the execution context.
*
* @since 5.4
*/
default ClassicHttpResponse execute(
String id,
ClassicHttpRequest request,
HttpResponseInformationCallback informationCallback,
HttpClientContext context) throws IOException, HttpException {
return execute(id, request, context);
}

/**
* Determines of the connection is considered re-usable.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,14 @@ public class RequestConfig implements Cloneable {
private final TimeValue connectionKeepAlive;
private final boolean contentCompressionEnabled;
private final boolean hardCancellationEnabled;
private final boolean protocolUpgradeEnabled;

/**
* Intended for CDI compatibility
*/
protected RequestConfig() {
this(false, null, null, false, false, 0, false, null, null,
DEFAULT_CONNECTION_REQUEST_TIMEOUT, null, null, DEFAULT_CONN_KEEP_ALIVE, false, false);
DEFAULT_CONNECTION_REQUEST_TIMEOUT, null, null, DEFAULT_CONN_KEEP_ALIVE, false, false, false);
}

RequestConfig(
Expand All @@ -86,7 +87,8 @@ protected RequestConfig() {
final Timeout responseTimeout,
final TimeValue connectionKeepAlive,
final boolean contentCompressionEnabled,
final boolean hardCancellationEnabled) {
final boolean hardCancellationEnabled,
final boolean protocolUpgradeEnabled) {
super();
this.expectContinueEnabled = expectContinueEnabled;
this.proxy = proxy;
Expand All @@ -103,6 +105,7 @@ protected RequestConfig() {
this.connectionKeepAlive = connectionKeepAlive;
this.contentCompressionEnabled = contentCompressionEnabled;
this.hardCancellationEnabled = hardCancellationEnabled;
this.protocolUpgradeEnabled = protocolUpgradeEnabled;
}

/**
Expand Down Expand Up @@ -217,6 +220,13 @@ public boolean isHardCancellationEnabled() {
return hardCancellationEnabled;
}

/**
* @see Builder#setProtocolUpgradeEnabled(boolean) (boolean)
*/
public boolean isProtocolUpgradeEnabled() {
return protocolUpgradeEnabled;
}

@Override
protected RequestConfig clone() throws CloneNotSupportedException {
return (RequestConfig) super.clone();
Expand All @@ -241,6 +251,7 @@ public String toString() {
builder.append(", connectionKeepAlive=").append(connectionKeepAlive);
builder.append(", contentCompressionEnabled=").append(contentCompressionEnabled);
builder.append(", hardCancellationEnabled=").append(hardCancellationEnabled);
builder.append(", protocolUpgradeEnabled=").append(protocolUpgradeEnabled);
builder.append("]");
return builder.toString();
}
Expand All @@ -265,7 +276,8 @@ public static RequestConfig.Builder copy(final RequestConfig config) {
.setResponseTimeout(config.getResponseTimeout())
.setConnectionKeepAlive(config.getConnectionKeepAlive())
.setContentCompressionEnabled(config.isContentCompressionEnabled())
.setHardCancellationEnabled(config.isHardCancellationEnabled());
.setHardCancellationEnabled(config.isHardCancellationEnabled())
.setProtocolUpgradeEnabled(config.isProtocolUpgradeEnabled());
}

public static class Builder {
Expand All @@ -285,6 +297,7 @@ public static class Builder {
private TimeValue connectionKeepAlive;
private boolean contentCompressionEnabled;
private boolean hardCancellationEnabled;
private boolean protocolUpgradeEnabled;

Builder() {
super();
Expand All @@ -294,6 +307,7 @@ public static class Builder {
this.connectionRequestTimeout = DEFAULT_CONNECTION_REQUEST_TIMEOUT;
this.contentCompressionEnabled = true;
this.hardCancellationEnabled = true;
this.protocolUpgradeEnabled = true;
}

/**
Expand Down Expand Up @@ -570,6 +584,23 @@ public Builder setHardCancellationEnabled(final boolean hardCancellationEnabled)
return this;
}

/**
* Determines whether the client server should automatically attempt to upgrade
* to a safer or a newer version of the protocol, whenever possible.
* <p>
* Presently supported: HTTP/1.1 TLS upgrade
* </p>
* <p>
* Default: {@code true}
* </p>
*
* @since 5.4
*/
public Builder setProtocolUpgradeEnabled(final boolean protocolUpgradeEnabled) {
this.protocolUpgradeEnabled = protocolUpgradeEnabled;
return this;
}

public RequestConfig build() {
return new RequestConfig(
expectContinueEnabled,
Expand All @@ -586,7 +617,8 @@ public RequestConfig build() {
responseTimeout,
connectionKeepAlive != null ? connectionKeepAlive : DEFAULT_CONN_KEEP_ALIVE,
contentCompressionEnabled,
hardCancellationEnabled);
hardCancellationEnabled,
protocolUpgradeEnabled);
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl;

import java.util.Iterator;

import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpMessage;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.ProtocolVersion;
import org.apache.hc.core5.http.message.MessageSupport;
import org.apache.hc.core5.http.ssl.TLS;

/**
* Protocol switch handler.
*
* @since 5.4
*/
@Internal
public final class ProtocolSwitchStrategy {

enum ProtocolSwitch { FAILURE, TLS }

public ProtocolVersion switchProtocol(final HttpMessage response) throws ProtocolException {
final Iterator<String> it = MessageSupport.iterateTokens(response, HttpHeaders.UPGRADE);

ProtocolVersion tlsUpgrade = null;
while (it.hasNext()) {
final String token = it.next();
if (token.startsWith("TLS")) {
// TODO: Improve handling of HTTP protocol token once HttpVersion has a #parse method
try {
tlsUpgrade = token.length() == 3 ? TLS.V_1_2.getVersion() : TLS.parse(token.replace("TLS/", "TLSv"));
} catch (final ParseException ex) {
throw new ProtocolException("Invalid protocol: " + token);
}
} else if (token.equals("HTTP/1.1")) {
// TODO: Improve handling of HTTP protocol token once HttpVersion has a #parse method
} else {
throw new ProtocolException("Unsupported protocol: " + token);
}
}
if (tlsUpgrade == null) {
throw new ProtocolException("Invalid protocol switch response");
}
return tlsUpgrade;
}

}
Loading
Loading