Rest-assured: byte[] content is not repeatable?

Created on 21 Jul 2015  路  6Comments  路  Source: rest-assured/rest-assured

_From normanwalsh on September 03, 2014 23:13:52_

If you attempt to post a byte[] body to an endpoint that performs digest authentication, you get a long stack trace that ends near

org.apache.http.client.NonRepeatableRequestException: Cannot retry request with a non-repeatable request entity.

Why isn't a byte[] array repeatable?

_Original issue: http://code.google.com/p/rest-assured/issues/detail?id=352_

Priority-Medium imported bug

Most helpful comment

@johanhaleby thanks for the clue about auth. I just added "preemptive" to my basic auth call, changing...

.auth().basic(username, password)

... to ...

.auth().preemptive.basic(username, password)

I'm not sure why this works but here's the commit I just made where I switched from .multiPart(new File(pathToFileName)) to .body(myArrayOfBytes): https://github.com/IQSS/dataverse/commit/3d2ac41e67cbb4154b918aa7d9df041881c1a878

Here also is the stacktrace where I switched to bytes but had not yet added "preemptive" to auth that shows org.apache.http.client.ClientProtocolException and Caused by: org.apache.http.client.NonRepeatableRequestException: Cannot retry request with a non-repeatable request entity.: stacktrace.txt

See also https://github.com/jayway/rest-assured/wiki/Usage#preemptive-basic-authentication

All 6 comments

I believe that this issue is not just limited to digest auth. I'm seeing the same problem with basic auth as well: .auth().basic(username, password)

As a workaround I'm shelling out to curl for now, but I'd much rather use rather use rest-assured. Please see uploadZipFile vs. uploadZipFileWithCurl at https://github.com/IQSS/dataverse/blob/v4.2.1/src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java#L851

@johanhaleby thanks for the clue about auth. I just added "preemptive" to my basic auth call, changing...

.auth().basic(username, password)

... to ...

.auth().preemptive.basic(username, password)

I'm not sure why this works but here's the commit I just made where I switched from .multiPart(new File(pathToFileName)) to .body(myArrayOfBytes): https://github.com/IQSS/dataverse/commit/3d2ac41e67cbb4154b918aa7d9df041881c1a878

Here also is the stacktrace where I switched to bytes but had not yet added "preemptive" to auth that shows org.apache.http.client.ClientProtocolException and Caused by: org.apache.http.client.NonRepeatableRequestException: Cannot retry request with a non-repeatable request entity.: stacktrace.txt

See also https://github.com/jayway/rest-assured/wiki/Usage#preemptive-basic-authentication

@pdurbin Hmm not sure if I gave you the clue about auth :) When did I do that?

It's very interesting that it works when you switch to preemptive basic auth. I don't know why it would work better with preemptive basic auth.. Preemptive basic auth just pre-populates a header with the authentication details instead of waiting for the server to be challenged. But perhaps that's also the answer. Could it be that the server rejects the multipart upload if there are no credentials attached to the request?! But if so the error of NonRepeatableRequestException seem quite confusing.

I agree with @pdurbin: NonRepeatableRequestException is very confusing. Imo, this is a bug. In my scenario, posting JSON content works fine, but a byte array does not. This does not make sense.

Why should requests be repeatable in the first place?

Just for anyone else finding this by googling, I found that I had this issue when the body was anything other than String, but only when I was using restassured in combination with com.github.dzieciou.testing:curl-logger, and it even happened when using preemptive basic auth.

My solution to the issue was to add an interceptor which wraps the entity in a buffered http entity, which makes it repeatable. This is acceptable to me for test cases but may not be great for other scenarios. You can do this like so (add the returned config to your restassured request).

HttpClientConfig.httpClientConfig()
        .httpClientFactory(
            () -> {
              HttpRequestInterceptor reqIntercept = (req, context) -> {
                if (req instanceof HttpEntityEnclosingRequest)
                {
                  HttpEntityEnclosingRequest erq = (HttpEntityEnclosingRequest) req;
                  if (erq.getEntity() != null) {
                    erq.setEntity(new BufferedHttpEntity(erq.getEntity()));
                  }
                }
              };
              client.addRequestInterceptor(reqIntercept);
              return client;
            });
Was this page helpful?
0 / 5 - 0 ratings