0

Looking for the possibility to configure the custom socket factories to set traffic class or type-of-service octet in the IP header for packets sent from this Socket.

The below setup worked so far with below apache HttpClient versions

org.apache.httpcomponents.client5 5.3.1

org.apache.httpcomponents.core5 5.2.4

DSCP marking for HTTP

import java.io.IOException;
import java.net.Proxy;
import java.net.Socket;
import java.net.SocketException;

import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.core5.http.protocol.HttpContext;


class CustomDscpPlainConnectionSocketFactory extends PlainConnectionSocketFactory {

    private int dscpValue;

    CustomDscpPlainConnectionSocketFactory(int dscpValue) {
        this.dscpValue = dscpValue;
    }

    @Override
    public Socket createSocket(final HttpContext context) throws IOException {
        Socket socket = super.createSocket(context);
        socket.setTrafficClass(dscpValue << 2);
        return socket;
    }

    @Override
    public Socket createSocket(final Proxy proxy, final HttpContext context) throws IOException {
        Socket socket = super.createSocket(proxy, context);
        socket.setTrafficClass(dscpValue << 2);
        return socket;
    }
}

DSCP marking for HTTPS

import java.io.IOException;
import java.net.Proxy;
import java.net.Socket;
import java.net.SocketException;

import javax.net.ssl.SSLContext;

import org.apache.hc.client5.http.ssl.HttpsSupport;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.protocol.HttpContext;

class CustomDscpSSLConnectionSocketFactory extends SSLConnectionSocketFactory {

    private int dscpValue;

    public CustomDscpSSLConnectionSocketFactory(final int dscpValue,
            final SSLContext sslContext,
            final String[] supportedProtocols) {
        super(sslContext.getSocketFactory(), supportedProtocols,
                getCipherSuites().toArray(String[]::new),
                HttpsSupport.getDefaultHostnameVerifier());
        this.dscpValue = dscpValue;
    }

    public CustomDscpSSLConnectionSocketFactory(SSLContext sslContext) {
        super(sslContext);
    }

    @Override
    public Socket createSocket(HttpContext context) throws IOException {
        Socket socket = super.createSocket(context);
        socket.setTrafficClass(dscpValue << 2);
        return socket;
    }

    @Override
    public Socket createSocket(final Proxy proxy, final HttpContext context) throws IOException {
        Socket socket = super.createSocket(proxy, context);
        socket.setTrafficClass(dscpValue << 2);
        return socket;
    }
}

HttpClient

import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;

var plainSocketFactory = new CustomDscpPlainConnectionSocketFactory(20);
var sslConnSocketFactory =  new CustomDscpSSLConnectionSocketFactory(20, sslContext, getProtocols())
PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager(
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register(URIScheme.HTTP.id, plainSocketFactory)
                        .register(URIScheme.HTTPS.id, sslConnSocketFactory)
                        .build());
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create()
                .setRedirectStrategy(DefaultRedirectStrategy.INSTANCE)
                .setConnectionManager(poolingmgr);
CloseableHttpClient httpClient = httpClientBuilder.build();

Would like to achieve the similar setup with latest apache HTTP client update versions where PlainConnectionSocketFactory and SSLConnectionSocketFactory are deprecated.

org.apache.httpcomponents.client5 5.4.2

org.apache.httpcomponents.core5 5.3.3

DSCP marking for HTTPS which works

import java.net.Socket;
import java.net.SocketException;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;

import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.client5.http.ssl.HttpsSupport;
import org.apache.hc.core5.reactor.ssl.SSLBufferMode;


    public class CustomClientTlsStrategy extends DefaultClientTlsStrategy {
    
        private int dscpValue;
    
        public CustomClientTlsStrategy(final int dscpValue,
                final SSLContext sslContext,
                final String[] supportedProtocols) {
            super(sslContext, supportedProtocols,
                   getCipherSuites().toArray(String[]::new),
                    SSLBufferMode.STATIC, HttpsSupport.getDefaultHostnameVerifier());
            this.dscpValue = dscpValue;
        }
    
        protected void initializeSocket(final SSLSocket socket) {
          socket.setTrafficClass(dscpValue << 2);
        }
    }

DSCP marking for HTTP doesn't work and may be the below setup is not valid for HTTP.

import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;

import org.apache.hc.client5.http.impl.io.ManagedHttpClientConnectionFactory;
import org.apache.hc.client5.http.io.ManagedHttpClientConnection;

    public class CustomManagedHttpClientConnectionFactory extends ManagedHttpClientConnectionFactory {
    
        private int dscpValue;
        public CustomManagedHttpClientConnectionFactory(int dscpValue) {
            super();
            this.dscpValue = dscpValue;
        }
        @Override
        public ManagedHttpClientConnection createConnection(final Socket socket) throws IOException {
            ManagedHttpClientConnection connection = super.createConnection(socket);
            if (connection.getSocket() != null ) { // Socket here is always null and thus DSCP is never marked.
                connection.getSocket().setTrafficClass(dscpValue << 2);
            }
            return connection;
        }
    }

HttpClient

CustomManagedHttpClientConnectionFactory customManagedHttpClientConnectionFactory = new CustomManagedHttpClientConnectionFactory(20));
var poolingmgrBuilder = PoolingHttpClientConnectionManagerBuilder.create();
poolingmgrBuilder.setTlsSocketStrategy(buildCustomTlsStrategy());
poolingmgrBuilder.setConnectionFactory(customManagedHttpClientConnectionFactory);

var poolingmgr = poolingmgrBuilder.build();

HttpClientBuilder httpClientBuilder = HttpClientBuilder.create()
                .setRedirectStrategy(DefaultRedirectStrategy.INSTANCE)
                .setConnectionManager(poolingmgr);
CloseableHttpClient httpClient = httpClientBuilder.build();

1 Answer 1

1

One needs a custom HttpClientConnectionOperator to accomplish that. In your case you many get away with a single extra lambda that creates a detached Socket.

PoolingHttpClientConnectionManagerBuilder connectionManagerBuilder = new PoolingHttpClientConnectionManagerBuilder() {

    @Override
    protected HttpClientConnectionOperator createConnectionOperator(SchemePortResolver schemePortResolver, DnsResolver dnsResolver, TlsSocketStrategy tlsSocketStrategy) {
        return new DefaultHttpClientConnectionOperator(
                proxy -> {
                    Socket socket = proxy != null ? new Socket(proxy) : new Socket();
                    // Apply custom socket config here
                    return socket;
                },
                schemePortResolver,
                dnsResolver,
                RegistryBuilder.<TlsSocketStrategy>create()
                        .register(URIScheme.HTTPS.id, tlsSocketStrategy)
                        .build());
    }
};
PoolingHttpClientConnectionManager connectionManager = connectionManagerBuilder.build();
Sign up to request clarification or add additional context in comments.

3 Comments

I noticed that both the DefaultHttpClientConnectionOperator class and the PoolingHttpClientConnectionManagerBuilder default constructor are marked as @Internal. While they seem to work fine now, I’m concerned about potential compatibility issues in future Apache HttpClient updates.
Those APIs are intentionally non-public
@ok2c you are professional with HTTP Client and SSL, please help me stackoverflow.com/questions/79537759/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.