1

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!

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
Jamie
  • 1,888
  • 1
  • 18
  • 21

1 Answers1

2

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.

sbordet
  • 16,856
  • 1
  • 50
  • 45
  • Cool! That's great. – Jamie Sep 04 '20 at 18:04
  • Shoot, it looks like this method only supports Basic authentication. – Jamie Sep 08 '20 at 15:05
  • 1
    It supports all kind of authentication, specifically `Basic` and `Digest` are supported out of the box. If you have another type of authentication, you need to say which one but the `HttpClient` authentication mechanism is pluggable so you can basically plug in your own. – sbordet Sep 08 '20 at 16:03
  • thank you very much for your answer. I hope you don't mind another question: You said Digest is supported. However, the `org.eclipse.jetty.client.util.DigestAuthentication` class does not have anything similar to the `BasicAuthentication.BasicResult` class that is used to apply to the request. Looking at the javadoc for `DigestAuthentication` leads me to believe that this would be a two-step process, where you would submit the request, get a 401 error, call `DigestAuthentication.authenticate` to get the Authentication.Result object, and resubmit. Does that sound about right? – Jamie Sep 10 '20 at 14:42
  • 1
    It's about right. For your initial problem, though, rather than using authentication in this way, use cookies. Authenticate N users at startup, have the server produce different cookies for different users (e.g. `//host/path1`, `//host/pathN` where 1..N are different users). Then, different paths mean different users, and the built-in cookie functionality in `HttpClient` will do the rest. – sbordet Sep 10 '20 at 19:46