NettyClient.java

/*
 * Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0, which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package org.glassfish.jersey.examples.expect100continue.netty.connector;

import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.RequestEntityProcessing;
import org.glassfish.jersey.client.http.Expect100ContinueFeature;
import org.glassfish.jersey.logging.LoggingFeature;
import org.glassfish.jersey.netty.connector.NettyConnectorProvider;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;

public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
//        enableLogging(Level.FINE);
        test();
    }

    public static void test() throws InterruptedException {
        ClientConfig defaultConfig = new ClientConfig();
        defaultConfig.property(LoggingFeature.LOGGING_FEATURE_VERBOSITY_CLIENT, LoggingFeature.Verbosity.PAYLOAD_ANY);

        //The issue can be produced only by using NettyConnectorProvider
        defaultConfig.connectorProvider(new NettyConnectorProvider());

        //with below two lines, enabled 100-continue feature
        defaultConfig.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.CHUNKED);
        defaultConfig.register(Expect100ContinueFeature.basic());

        Client client = ClientBuilder.newClient(defaultConfig);
        WebTarget webTarget = client.target("http://127.0.0.1:3000");
        Invocation.Builder invocationBuilder = webTarget.request();
        invocationBuilder.header("Accept", "application/json");

        for (int i = 0; i < 5; i++) { //iterating few times here to demonstrate
            // the 100-continue processing works on any iteration

            System.out.println();
            System.out.println("****************** Iteration #" + i + " ******************");

            final Response response = invocationBuilder.post(generateSimpleEntity());

            System.out.println("Response status = " + response.getStatus());
            System.out.println("Response status 204 means No Content, so we do not expect body here");
            System.out.println("**************************************************");
            System.out.println();
        }
        System.out.println("Client connection should be closed manually with Ctrl-C");
    }

    private static Entity<String> generateSimpleEntity(){
        return Entity.entity("{\"message\":\"Hello from java client\"}", MediaType.APPLICATION_JSON_TYPE);
    }

    private static void enableLogging(Level logLevel) {
        Logger rootLogger = Logger.getLogger("");
        rootLogger.setLevel(logLevel);
        Logger nettyLog = Logger.getLogger("io.netty");
        nettyLog.setLevel(logLevel);
        Handler[] handlers = rootLogger.getHandlers();
        for (final Handler handler : handlers) {
            handler.setLevel(logLevel);
        }
    }
}