diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/StateHolder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/StateHolder.java new file mode 100644 index 000000000..308160dc4 --- /dev/null +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/StateHolder.java @@ -0,0 +1,41 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.client5.http.impl; + +import org.apache.hc.core5.annotation.Internal; + +/** + * @since 5.4 + */ +@Internal +public interface StateHolder { + + T store(); + + void restore(T state); + +} diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicAuthCache.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicAuthCache.java index 7450a401f..659fe532b 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicAuthCache.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicAuthCache.java @@ -26,12 +26,6 @@ */ package org.apache.hc.client5.http.impl.auth; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; import java.util.Locale; import java.util.Map; import java.util.Objects; @@ -41,6 +35,7 @@ import org.apache.hc.client5.http.SchemePortResolver; import org.apache.hc.client5.http.auth.AuthCache; import org.apache.hc.client5.http.auth.AuthScheme; import org.apache.hc.client5.http.impl.DefaultSchemePortResolver; +import org.apache.hc.client5.http.impl.StateHolder; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpHost; @@ -123,7 +118,19 @@ public class BasicAuthCache implements AuthCache { } } - private final Map map; + static class AuthData { + + final Class clazz; + final Object state; + + public AuthData(final Class clazz, final Object state) { + this.clazz = clazz; + this.state = state; + } + + } + + private final Map map; private final SchemePortResolver schemePortResolver; /** @@ -145,6 +152,10 @@ public class BasicAuthCache implements AuthCache { return new Key(scheme, authority.getHostName(), schemePortResolver.resolve(scheme, authority), pathPrefix); } + private AuthData data(final AuthScheme authScheme) { + return new AuthData(authScheme.getClass(), ((StateHolder) authScheme).store()); + } + @Override public void put(final HttpHost host, final AuthScheme authScheme) { put(host, null, authScheme); @@ -166,42 +177,28 @@ public class BasicAuthCache implements AuthCache { if (authScheme == null) { return; } - if (authScheme instanceof Serializable) { - try { - final ByteArrayOutputStream buf = new ByteArrayOutputStream(); - try (final ObjectOutputStream out = new ObjectOutputStream(buf)) { - out.writeObject(authScheme); - } - this.map.put(key(host.getSchemeName(), host, pathPrefix), buf.toByteArray()); - } catch (final IOException ex) { - if (LOG.isWarnEnabled()) { - LOG.warn("Unexpected I/O error while serializing auth scheme", ex); - } - } + if (authScheme instanceof StateHolder) { + this.map.put(key(host.getSchemeName(), host, pathPrefix), data(authScheme)); } else { if (LOG.isDebugEnabled()) { - LOG.debug("Auth scheme {} is not serializable", authScheme.getClass()); + LOG.debug("Auth scheme {} cannot be cached", authScheme.getClass()); } } } @Override + @SuppressWarnings("unchecked") public AuthScheme get(final HttpHost host, final String pathPrefix) { Args.notNull(host, "HTTP host"); - final byte[] bytes = this.map.get(key(host.getSchemeName(), host, pathPrefix)); - if (bytes != null) { + final AuthData authData = this.map.get(key(host.getSchemeName(), host, pathPrefix)); + if (authData != null) { try { - final ByteArrayInputStream buf = new ByteArrayInputStream(bytes); - try (final ObjectInputStream in = new ObjectInputStream(buf)) { - return (AuthScheme) in.readObject(); - } - } catch (final IOException ex) { + final AuthScheme authScheme = authData.clazz.newInstance(); + ((StateHolder) authScheme).restore(authData.state); + return authScheme; + } catch (final IllegalAccessException | InstantiationException ex) { if (LOG.isWarnEnabled()) { - LOG.warn("Unexpected I/O error while de-serializing auth scheme", ex); - } - } catch (final ClassNotFoundException ex) { - if (LOG.isWarnEnabled()) { - LOG.warn("Unexpected error while de-serializing auth scheme", ex); + LOG.warn("Unexpected error while reading auth scheme state", ex); } } } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicScheme.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicScheme.java index 4ba0c4120..1d2c5b59c 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicScheme.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicScheme.java @@ -26,13 +26,9 @@ */ package org.apache.hc.client5.http.impl.auth; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; import java.io.Serializable; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.nio.charset.UnsupportedCharsetException; import java.security.Principal; import java.util.HashMap; import java.util.List; @@ -49,9 +45,11 @@ import org.apache.hc.client5.http.auth.CredentialsProvider; import org.apache.hc.client5.http.auth.MalformedChallengeException; import org.apache.hc.client5.http.auth.StandardAuthScheme; import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.impl.StateHolder; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.client5.http.utils.Base64; import org.apache.hc.client5.http.utils.ByteArrayBuilder; +import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.NameValuePair; @@ -66,14 +64,13 @@ import org.slf4j.LoggerFactory; * @since 4.0 */ @AuthStateCacheable -public class BasicScheme implements AuthScheme, Serializable { +public class BasicScheme implements AuthScheme, StateHolder, Serializable { private static final long serialVersionUID = -1931571557597830536L; private static final Logger LOG = LoggerFactory.getLogger(BasicScheme.class); private final Map paramMap; - private transient Charset defaultCharset; private transient ByteArrayBuilder buffer; private transient Base64 base64codec; private boolean complete; @@ -89,7 +86,6 @@ public class BasicScheme implements AuthScheme, Serializable { @Deprecated public BasicScheme(final Charset charset) { this.paramMap = new HashMap<>(); - this.defaultCharset = StandardCharsets.UTF_8; // Always use UTF-8 this.complete = false; } @@ -100,7 +96,6 @@ public class BasicScheme implements AuthScheme, Serializable { */ public BasicScheme() { this.paramMap = new HashMap<>(); - this.defaultCharset = StandardCharsets.UTF_8; this.complete = false; } @@ -109,6 +104,7 @@ public class BasicScheme implements AuthScheme, Serializable { Args.check(credentials instanceof UsernamePasswordCredentials, "Unsupported credential type: " + credentials.getClass()); this.credentials = (UsernamePasswordCredentials) credentials; + this.complete = true; } else { this.credentials = null; } @@ -221,7 +217,7 @@ public class BasicScheme implements AuthScheme, Serializable { } else { this.buffer.reset(); } - final Charset charset = AuthSchemeSupport.parseCharset(paramMap.get("charset"), defaultCharset); + final Charset charset = AuthSchemeSupport.parseCharset(paramMap.get("charset"), StandardCharsets.UTF_8); this.buffer.charset(charset); this.buffer.append(this.credentials.getUserName()).append(":").append(this.credentials.getUserPassword()); if (this.base64codec == null) { @@ -232,22 +228,23 @@ public class BasicScheme implements AuthScheme, Serializable { return StandardAuthScheme.BASIC + " " + new String(encodedCreds, 0, encodedCreds.length, StandardCharsets.US_ASCII); } - private void writeObject(final ObjectOutputStream out) throws IOException { - out.defaultWriteObject(); - out.writeUTF(this.defaultCharset.name()); - } - - @SuppressWarnings("unchecked") - private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - try { - this.defaultCharset = Charset.forName(in.readUTF()); - } catch (final UnsupportedCharsetException ex) { - this.defaultCharset = StandardCharsets.UTF_8; + @Override + public State store() { + if (complete) { + return new State(new HashMap<>(paramMap), credentials); + } else { + return null; } } - private void readObjectNoData() { + @Override + public void restore(final State state) { + if (state != null) { + paramMap.clear(); + paramMap.putAll(state.params); + credentials = state.credentials; + complete = true; + } } @Override @@ -255,4 +252,23 @@ public class BasicScheme implements AuthScheme, Serializable { return getName() + this.paramMap; } + @Internal + public static class State { + + final Map params; + final UsernamePasswordCredentials credentials; + + State(final Map params, final UsernamePasswordCredentials credentials) { + this.params = params; + this.credentials = credentials; + } + + @Override + public String toString() { + return "State{" + + "params=" + params + + '}'; + } + } + } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BearerScheme.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BearerScheme.java index 02fcb7a3c..113f1d380 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BearerScheme.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BearerScheme.java @@ -43,7 +43,9 @@ import org.apache.hc.client5.http.auth.Credentials; import org.apache.hc.client5.http.auth.CredentialsProvider; import org.apache.hc.client5.http.auth.MalformedChallengeException; import org.apache.hc.client5.http.auth.StandardAuthScheme; +import org.apache.hc.client5.http.impl.StateHolder; import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.NameValuePair; @@ -59,7 +61,7 @@ import org.slf4j.LoggerFactory; * @since 5.3 */ @AuthStateCacheable -public class BearerScheme implements AuthScheme, Serializable { +public class BearerScheme implements AuthScheme, StateHolder, Serializable { private static final Logger LOG = LoggerFactory.getLogger(BearerScheme.class); @@ -161,9 +163,49 @@ public class BearerScheme implements AuthScheme, Serializable { return StandardAuthScheme.BEARER + " " + bearerToken.getToken(); } + @Override + public State store() { + if (complete) { + return new State(new HashMap<>(paramMap), bearerToken); + } else { + return null; + } + } + + @Override + public void restore(final State state) { + if (state != null) { + paramMap.clear(); + paramMap.putAll(state.params); + bearerToken = state.bearerToken; + complete = true; + } + } + @Override public String toString() { return getName() + this.paramMap; } + @Internal + public static class State { + + final Map params; + final BearerToken bearerToken; + + State(final Map params, final BearerToken bearerToken) { + this.params = params; + this.bearerToken = bearerToken; + } + + @Override + public String toString() { + return "State{" + + "params=" + params + + ", bearerToken=" + bearerToken + + '}'; + } + + } + } diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBasicScheme.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBasicScheme.java index b960045b7..c75ca8f2b 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBasicScheme.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBasicScheme.java @@ -26,10 +26,6 @@ */ package org.apache.hc.client5.http.impl.auth; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.List; @@ -153,34 +149,13 @@ public class TestBasicScheme { final BasicScheme basicScheme = new BasicScheme(); basicScheme.processChallenge(authChallenge, null); - final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - final ObjectOutputStream out = new ObjectOutputStream(buffer); - out.writeObject(basicScheme); - out.flush(); - final byte[] raw = buffer.toByteArray(); - final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(raw)); - final BasicScheme authScheme = (BasicScheme) in.readObject(); + final BasicScheme.State state = basicScheme.store(); + final BasicScheme cached = new BasicScheme(); + cached.restore(state); - Assertions.assertEquals(basicScheme.getName(), authScheme.getName()); - Assertions.assertEquals(basicScheme.getRealm(), authScheme.getRealm()); - Assertions.assertEquals(basicScheme.isChallengeComplete(), authScheme.isChallengeComplete()); - } - - @Test - public void testSerializationUnchallenged() throws Exception { - final BasicScheme basicScheme = new BasicScheme(); - - final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - final ObjectOutputStream out = new ObjectOutputStream(buffer); - out.writeObject(basicScheme); - out.flush(); - final byte[] raw = buffer.toByteArray(); - final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(raw)); - final BasicScheme authScheme = (BasicScheme) in.readObject(); - - Assertions.assertEquals(basicScheme.getName(), authScheme.getName()); - Assertions.assertEquals(basicScheme.getRealm(), authScheme.getRealm()); - Assertions.assertEquals(basicScheme.isChallengeComplete(), authScheme.isChallengeComplete()); + Assertions.assertEquals(basicScheme.getName(), cached.getName()); + Assertions.assertEquals(basicScheme.getRealm(), cached.getRealm()); + Assertions.assertEquals(basicScheme.isChallengeComplete(), cached.isChallengeComplete()); } @Test diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBearerScheme.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBearerScheme.java index 420475baa..109cd6b1d 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBearerScheme.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBearerScheme.java @@ -26,11 +26,6 @@ */ package org.apache.hc.client5.http.impl.auth; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; - import org.apache.hc.client5.http.auth.AuthChallenge; import org.apache.hc.client5.http.auth.AuthScheme; import org.apache.hc.client5.http.auth.AuthScope; @@ -80,25 +75,21 @@ public class TestBearerScheme { } @Test - public void testSerialization() throws Exception { + public void testStateStorage() throws Exception { final AuthChallenge authChallenge = new AuthChallenge(ChallengeType.TARGET, "Bearer", new BasicNameValuePair("realm", "test"), new BasicNameValuePair("code", "read")); - final AuthScheme authscheme = new BearerScheme(); + final BearerScheme authscheme = new BearerScheme(); authscheme.processChallenge(authChallenge, null); - final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - final ObjectOutputStream out = new ObjectOutputStream(buffer); - out.writeObject(authscheme); - out.flush(); - final byte[] raw = buffer.toByteArray(); - final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(raw)); - final BearerScheme authcheme2 = (BearerScheme) in.readObject(); + final BearerScheme.State state = authscheme.store(); + final BearerScheme cached = new BearerScheme(); + cached.restore(state); - Assertions.assertEquals(authcheme2.getName(), authcheme2.getName()); - Assertions.assertEquals(authcheme2.getRealm(), authcheme2.getRealm()); - Assertions.assertEquals(authcheme2.isChallengeComplete(), authcheme2.isChallengeComplete()); + Assertions.assertEquals(cached.getName(), cached.getName()); + Assertions.assertEquals(cached.getRealm(), cached.getRealm()); + Assertions.assertEquals(cached.isChallengeComplete(), cached.isChallengeComplete()); } }