1
votes

I'm using the most recent apache http:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient-osgi</artifactId>
    <version>4.5.6</version>
</dependency>

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpcore-osgi</artifactId>
    <version>4.4.10</version>
</dependency>

I have the following operation:

public void store(InputStream input) throws IOException {
    HttpClientBuilder builder = HttpClientBuilder.create();
    if (StringUtils.isNotBlank(username)) {
      CredentialsProvider provider = new BasicCredentialsProvider();
      UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username.trim(), StringUtils.trimToEmpty(password));
      provider.setCredentials(AuthScope.ANY, credentials);
      builder.setDefaultCredentialsProvider(provider);
    }
    HttpClient client = builder.build();
    HttpPost post  = new HttpPost(uri);
    post.setEntity(new InputStreamEntity(input));
    HttpResponse response = client.execute(post);
}   

Until basic auth was active, everything was working fine, however, after adding basic auth I get the following error:

Caused by: org.apache.http.client.NonRepeatableRequestException: Cannot retry request with a non-repeatable request entity. at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:226) at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:185) at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89) at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:111) at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185) ... 6 more

I've found a following bug report: https://github.com/http-builder-ng/http-builder-ng/issues/10, however it is assigned to another problem.

What is causing the error? How to use basic auth with apache httpclient? I have no idea what is 'repeatable HTTP request', from what I know all the client need to set is Authorization header. Is it possible that I've misconfigured something on the server so that it requires 'repeatable' HTTP request?

2
Just a wild guess: is your server responding with an authentication challenge and your client library tries to obey but somehow fails on that? edit: it would be helpful to have the HTTP communication going on. Any chance to activiate some verbose log? - UniversE
@UniversE this is basic auth, there is no authentication challenge. Either you set Authorization header, or you get 401. - 9ilsdx 9rvj 0lo
Most probably the InputStreamEntity is the problem because this might not be repeatable. I do not know, though, what to do in this case. So just a hint ... - Seelenvirtuose
OK I've checked the source code, there seems to be a big misdesign in apache library, they set authorization headers only after they receive 401 from the server, which is absurd because they know they have to use basic anyway... - 9ilsdx 9rvj 0lo
@9ilsdx9rvj0lo HTTP 401 is the authentication challenge in the sense that it MUST contain a WWW-Authenticate header. See RFC-7235 if you want to learn more. Now when you initially send valid credentials, the server simply accepts. But if the server (for some reason) does not accept, it sends 401. Back to my wild guess: your client sends the requests, server does not accept and responds with 401, the client intends to retry the request and fails due to the above error. Edit: This would match your recent observations. - UniversE

2 Answers

1
votes

It seems that basic authentication model is broken in apache httpclient. The library tries to sneak around authentication and sends request without Authorization header, which of course fails. Then the library tries to resend the request, which of course fails, because InputStream can't be rewinded.

The solution is to forget BasicCredentialsProvider and use HttpRequestInterceptor to set headers:

HttpClientBuilder builder = HttpClientBuilder.create();
if (StringUtils.isNotBlank(username)) {
  builder.addInterceptorFirst(new HttpRequestInterceptor()
  {

    @Override
    public void process(HttpRequest request, HttpContext context) throws HttpException, IOException
    {
      String token = Base64.getEncoder().encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8));
      request.addHeader("Authorization", "Basic "+token);
    }
  });
}   
1
votes

I do not agree with the OP's own solution, because it is somehow very hackish and circumvents the Credential Mechanism of the library.

There are several entity types for HTTP Entities, as documented here. So now that you know, you cannot use a repeatable entity in your scenario, how about using a self-contained one or a wrapper which uses a buffer.

You can achieve this with a one-liner. Without having tried it out, I think the correct solution is:

post.setEntity(new BufferedHttpEntity(new InputStreamEntity(input)));