Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Apache HttpComponents Client dependency adding support for HTTP2. #515

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from

Conversation

PetrusHahol
Copy link

Hi,

Here are the changes to support Apache Client 5. As discussed in the related issue, some methods now function quite differently. I have successfully migrated the main business logic and tests, but there is still one class that I am unable to rewrite due to a lack of knowledge about the previous implementation (src/main/java/com/github/sardine/impl/SardineRedirectStrategy.java).

I would appreciate any ideas from the community on how to address this issue or any assistance in resolving it.

Additionally, I introduced the enableHttp2 method in the Sardine interface.

I will monitor this pull request discussion and aim to finalize it as soon as possible.

Fixes #333

@dkocher dkocher marked this pull request as draft August 22, 2024 10:21
@dkocher
Copy link
Collaborator

dkocher commented Aug 22, 2024

The redirect strategy is meant to handle the additional HTTP methods from WebDAV that are not handled in the default implementation from HTTP components. Thus a server can reply with a redirect for PROPFIND and the entity is properly sent to the redirect URI.

@rPraml
Copy link

rPraml commented Sep 26, 2024

I just stumbled across the same problem and I would also like this feature.

The redirect strategy is meant to handle the additional HTTP methods from WebDAV that are not handled in the default implementation from HTTP components

I did not check, the code here. But is there a unit test, that verifies this? Maybe I can assist

@PetrusHahol
Copy link
Author

I just stumbled across the same problem and I would also like this feature.

The redirect strategy is meant to handle the additional HTTP methods from WebDAV that are not handled in the default implementation from HTTP components

I did not check, the code here. But is there a unit test, that verifies this? Maybe I can assist

Unfortanatly there is no unit tests for it, I would be very happy if you would find a proper solution for redirect stratagy functionality. I am following up this thread and ready to help you to test it.

@dkocher dkocher force-pushed the feature/ApacheClient5 branch from f34c128 to a3531b6 Compare January 10, 2025 13:13
@dkocher
Copy link
Collaborator

dkocher commented Jan 10, 2025

@dkocher dkocher force-pushed the feature/ApacheClient5 branch from a3531b6 to 49a37c9 Compare January 10, 2025 14:07
@dkocher dkocher marked this pull request as ready for review January 10, 2025 14:07
@dkocher dkocher changed the title Feature/apache client5 Update Apache HttpComponents Client dependency adding support for HTTP2. Jan 10, 2025
@dkocher dkocher requested a review from lookfirst January 10, 2025 14:09
@lookfirst
Copy link
Owner

@dkocher I agree with this, but it changes the Sardine API. I just want to confirm that this will be a MAJOR version change on the library.

@dkocher
Copy link
Collaborator

dkocher commented Jan 10, 2025

@dkocher I agree with this, but it changes the Sardine API. I just want to confirm that this will be a MAJOR version change on the library.

Do you mean because of the return value of createDefaultRedirectStrategy 1?

Footnotes

  1. https://github.com/lookfirst/sardine/pull/515/files#diff-d0c6bff269a6dc37447fd635eba385548f6fe3f44394384ffe82ebb496a4ac0fR1172

@lookfirst
Copy link
Owner

@dkocher That link isn't loading for me, but what I'm thinking about are the few places where things change from String -> char[]. Essentially all the chances in Sardine.java.

	void setCredentials(String username, String password);
	void setCredentials(String username, char[] password);

@dkocher
Copy link
Collaborator

dkocher commented Jan 10, 2025

Wondering if this is still interoperable with Sharepoint WebDAV using NTLM.

  • @deprecated Do not use. the NTLM authentication scheme is no longer supported.
  • Consider using Basic or Bearer authentication with TLS instead.

@@ -548,6 +547,8 @@ public interface Sardine
*/
List<String> getPrincipalCollectionSet(String url) throws IOException;

void enableHttp2();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Javadoc describing implications.

return DateUtils.parseDate(value, SUPPORTED_DATE_FORMATS);
final Instant parsedInstant = DateUtils.parseDate(value, Arrays.stream(SUPPORTED_DATE_FORMATS)
.map(pattern -> DateTimeFormatter.ofPattern(pattern, Locale.US).withZone(ZoneId.of("GMT"))).toArray(DateTimeFormatter[]::new));
if (null == parsedInstant)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yoda logic.

sardine.delete(url);
}
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we removing this test because it is no longer necessary or just don't want to fix it or...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too lazy to fix the test…

@@ -34,6 +34,7 @@ public class SardineUtilTest
@Test
public void testParseDate() throws Exception
{
assertNotNull(SardineUtil.parseDate("1970-01-01T12:00:00Z"));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the point of this test? would be good to document the "why"...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No particular reason for this addition. Was accidentally included when transitioning implementation.

if (username != null)
{
provider.setCredentials(
new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM, AuthSchemes.NTLM),
new AuthScope(null, null, -1, null, StandardAuthScheme.NTLM),
Copy link
Owner

@lookfirst lookfirst Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we removing the constants?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constants seem no longer to exist.

new GzipDecompressingEntity(entity));
}
});

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no longer a "simple" way to do this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should just toggle contentCompressionDisabled in the client builder instead.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no setter in the builder to enable. Can be just a no-op then as it is enabled by default?

body.setLockscope(scopeType);
Locktype lockType = new Locktype();
lockType.setWrite(new Write());
body.setLocktype(lockType);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why removed?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused. Will open a separate pull request.

Copy link
Collaborator

@dkocher dkocher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also review deprecated usage of ConnectionSocketFactory.

Copy link
Collaborator

@dkocher dkocher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we should maintain a separate branch that retains the 4.x HttpComponents dependency.

@nPraml
Copy link

nPraml commented Jan 15, 2025

Hello everyone,

Thanks for the PR, we would also need sardine with http5.
I tried the http5 version (this branch) in our application and still have authentication issues.

Exception stack trace:

Caused by: com.github.sardine.impl.SardineException: status code: 401, reason phrase: Unexpected response (401 Unauthorized)
	at [email protected]/com.github.sardine.impl.handler.ValidatingResponseHandler.validateResponse(ValidatingResponseHandler.java:50)
	at [email protected]/com.github.sardine.impl.handler.MultiStatusResponseHandler.handleResponse(MultiStatusResponseHandler.java:41)
	at [email protected]/com.github.sardine.impl.handler.MultiStatusResponseHandler.handleResponse(MultiStatusResponseHandler.java:36)
	at [email protected]/org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:247)
	at [email protected]/org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:188)
	at [email protected]/com.github.sardine.impl.SardineImpl.execute(SardineImpl.java:1103)
	at [email protected]/com.github.sardine.impl.SardineImpl.execute(SardineImpl.java:1067)
	at [email protected]/com.github.sardine.impl.SardineImpl.propfind(SardineImpl.java:443)
	at [email protected]/com.github.sardine.impl.SardineImpl.list(SardineImpl.java:376)
	at [email protected]/com.github.sardine.impl.SardineImpl.list(SardineImpl.java:366)

How we use sardine:

Sardine sardine = new SardineImpl();
sardine.setCredentials(user, password.toCharArray());
sardine.list("http://localhost:34047/", 0); // randomized port number

The port number is randomized, so it's ok that there are different port numbers in the different code blocks, it just took me several runs until I collected all the information.

We create the instance on localhost with:

MiltonWebDAVFileServer server = new MiltonWebDAVFileServer(localFile);
server.setPort(port);
server.getUserCredentials().put("user", "pw"); // optional, defaults to no authentication
server.start();

The log with http5 looks like this:

16:07:13.875 [qtp1781459498-28] INFO  io.milton.http.HttpManager -- PROPFIND :: localhost:52832/// start
16:07:13.888 [qtp1781459498-28] WARN  io.milton.http.HandlerHelper -- authorisation declined, requesting authentication: /. resource type: io.github.atetzner.webdav.server.MiltonFolderResource
16:07:13.889 [qtp1781459498-28] INFO  i.milton.http.ResourceHandlerHelper -- authorisation failed. respond with: io.milton.http.webdav.DefaultWebDavResponseHandler resource: io.github.atetzner.webdav.server.MiltonFolderResource
16:07:13.890 [qtp1781459498-28] INFO  i.m.h.h.DefaultHttp11ResponseHandler -- respondUnauthorised: no authenticated user, so return status: HTTP/1.1 401
16:07:13.895 [qtp1781459498-28] INFO  io.milton.http.HttpManager -- PROPFIND :: localhost:52832/// finished 18ms, Status:HTTP/1.1 401, Length:null
16:07:13.920 [main] INFO  org.eclipse.jetty.server.Server -- Stopped Server@2eadc9f6{STOPPING}[10.0.11,sto=0]
16:07:13.924 [main] INFO  o.e.jetty.server.AbstractConnector -- Stopped ServerConnector@c6e0f32{HTTP/1.1, (http/1.1)}{0.0.0.0:52832}

The same test with Sardine with http4, log output:

15:54:52.394 [qtp1048128739-28] INFO  io.milton.http.HttpManager -- PROPFIND :: localhost:43636/// start
15:54:52.404 [qtp1048128739-28] WARN  io.milton.http.HandlerHelper -- authorisation declined, requesting authentication: /. resource type: io.github.atetzner.webdav.server.MiltonFolderResource
15:54:52.405 [qtp1048128739-28] INFO  i.milton.http.ResourceHandlerHelper -- authorisation failed. respond with: io.milton.http.webdav.DefaultWebDavResponseHandler resource: io.github.atetzner.webdav.server.MiltonFolderResource
15:54:52.406 [qtp1048128739-28] INFO  i.m.h.h.DefaultHttp11ResponseHandler -- respondUnauthorised: no authenticated user, so return status: HTTP/1.1 401
15:54:52.415 [qtp1048128739-28] INFO  io.milton.http.HttpManager -- PROPFIND :: localhost:43636/// finished 19ms, Status:HTTP/1.1 401, Length:null
15:54:52.440 [qtp1048128739-20] INFO  io.milton.http.HttpManager -- PROPFIND :: localhost:43636/// start
15:54:52.442 [qtp1048128739-20] INFO  i.m.h.h.a.CookieAuthenticationHandler -- Found child handler who supports this request io.milton.http.http11.auth.BasicAuthHandler@4402596e
15:54:52.443 [qtp1048128739-20] WARN  i.m.h.h.a.CookieAuthenticationHandler -- authenticate: auth.tag is not an instance of interface io.milton.principal.DiscretePrincipal, is: class java.lang.String so is not compatible with cookie authentication
15:54:52.485 [qtp1048128739-20] INFO  io.milton.http.HttpManager -- PROPFIND :: localhost:43636/// finished 44ms, Status:HTTP/1.1 207 Multi-Status, Length:781

So the http4 version apparently received a 401 Unauthorized at first, but then it healed itself so it was tried again with BasicAuth. After that we received 207 multi status and everything was fine.

Do you have an idea why our application doesn't yet work with the http5 branch?

Cheers
Noemi

@dkocher dkocher marked this pull request as draft January 15, 2025 15:24
if (httpMajorVersion != null){
request.setVersion(new ProtocolVersion("HTTP", httpMajorVersion, 0));
}
HttpContext requestLocalContext = new HttpClientContext(context);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
HttpContext requestLocalContext = new HttpClientContext(context);
HttpContext requestLocalContext = context;

removing the localContext would solve our auth problems as HttpClient5 seems to cache the previous authentication.
So we get one 401, then HttpClient selects the correct authenticator and from then, we get 2xx responses and our tests pass

Suggested change:

	protected <T> T execute(HttpClientContext context, HttpUriRequestBase request, HttpClientResponseHandler<T> responseHandler)
			throws IOException
	{
		Integer httpMajorVersion = (Integer) context.getAttribute(HTTP_MAJOR_VERSION);

		if (httpMajorVersion != null){
			request.setVersion(new ProtocolVersion("HTTP", httpMajorVersion, 0));
		}
		try
		{
			if (responseHandler != null)
			{
				return this.client.execute(request, context, responseHandler);
			}
			else
			{
				return (T) this.client.execute(request, context);
			}
		}
		catch (HttpResponseException e)
		{
			// Don't abort if we get this exception, caller may want to repeat request.
			throw e;
		}
		catch (IOException e)
		{
			request.abort();
			throw e;
		}
	}

@nPraml
Copy link

nPraml commented Jan 16, 2025

I implemented a unittest and a fix in PetrusHahol#1
@dkocher FYI

@dkocher
Copy link
Collaborator

dkocher commented Jan 17, 2025

I implemented a unittest and a fix in PetrusHahol#1 @dkocher FYI

That would most probably cause regressions for the following issues

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Update to Apache HttpClient v5
5 participants