javajettyjetty-httpclient

How to isolate Jetty HttpClient for multiple users?


I am using Eclipse Jetty HttpClient to send POST requests to a server, for load testing.

TL;DR: Is there a way to use a single HttpClient instance with multiple user credential sets to a single destination URL?

For this purpose, I need to log in to the server-under-test as separate users. Even though HttpClient is thread safe, it does not appear to support this with a single instance, due to its shared authentication store.

The solution seems easy, just use one HttpClient per user, or per thread.

This works okay, except that HttpClient creates a number of threads (5 to 10 it seems) for each instance, and so my load test needs a very large heap or else it will start throwing OutOfMemory exceptions when trying to create new threads.

For example, in this very basic test, the first set of credentials is used for all subsequent POSTs:

public class Test 
{
    static class User
    {
        String username;
        String password;
        User(String username, String password)
        {
            this.username = username;
            this.password = password;
        }
    }

    public static void main(String[] args) throws Exception 
    {
        SslContextFactory sslContextFactory = new SslContextFactory.Client();
        HttpClient httpClient = new HttpClient(sslContextFactory);
        httpClient.start();
        
        List<User> users = new ArrayList<>();
        
        users.add(new User("fry", "1234"));
        users.add(new User("leela", "2345"));
        users.add(new User("zoidberg", "3456"));
        
        URI uri = new URI("http://localhost:8080/myapi");

        for (User user : users)
        {
            AuthenticationStore auth = httpClient.getAuthenticationStore();
            auth.addAuthentication(new DigestAuthentication(uri, DigestAuthentication.ANY_REALM, user.username, user.password));

            Request request = httpClient.newRequest(uri);
            request.method("POST");
            
            ContentResponse result = request.send();

            System.out.println(result.getStatus());
        }
    }
}

Now, I realize in this contrived test that I can call httpClient.getAuthenticationStore().clearAuthenticationResults() and httpClient.getAuthenticationStore().clearAuthentications(); between loops, however that does not work for my actual testing, where I have multiple threads posting at the same time.

Am I stuck using a separate HttpClient instance for each user?

Thanks for any ideas!


Solution

  • What you need can be done by "preempting" the authentication headers for every request, as explained in the documentation.

    This is how you would do it:

    // Single HttpClient instance.
    HttpClient httpClient = new HttpClient();
    
    // The server URI.
    URI uri = URI.create("http://example.com/secure");
    
    // The authentication credential for each user.
    Authentication.Result authn1 = new BasicAuthentication.BasicResult(uri, "user1", "password1");
    Authentication.Result authn2 = new BasicAuthentication.BasicResult(uri, "user2", "password2");
    
    // Create a request instance.
    Request request1 = httpClient.newRequest(uri);
    // Add the authorization headers for user1.
    authn1.apply(request1);
    request1.send();
    
    Request request2 = httpClient.newRequest(uri);
    // Add the authorization headers for user2.
    authn2.apply(request2);
    request2.send();
    

    Sending the requests does not need to be sequential or using the blocking APIs like in the simple example above.

    You can do it from a for loop, and pick a user (and its correspondent authorization) randomly, for example, and use the asynchronous APIs for better performance.