FileResourceFactory to generate stable file names for the same request Id and entity tag

This commit is contained in:
Oleg Kalnichevski 2024-01-12 10:56:09 +01:00
parent d787637e6e
commit f4f5f73be2
3 changed files with 110 additions and 108 deletions

View File

@ -1,90 +0,0 @@
/*
* ====================================================================
* 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
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.cache;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Formatter;
import java.util.Locale;
import java.util.concurrent.locks.ReentrantLock;
/**
* Should produce reasonably unique tokens.
*/
class BasicIdGenerator {
private final String hostname;
private final SecureRandom rnd;
private long count;
private final ReentrantLock lock;
public BasicIdGenerator() {
super();
String hostname;
try {
hostname = InetAddress.getLocalHost().getHostName();
} catch (final UnknownHostException ex) {
hostname = "localhost";
}
this.hostname = hostname;
try {
this.rnd = SecureRandom.getInstance("SHA1PRNG");
} catch (final NoSuchAlgorithmException ex) {
throw new Error(ex);
}
this.rnd.setSeed(System.currentTimeMillis());
this.lock = new ReentrantLock();
}
public void generate(final StringBuilder buffer) {
lock.lock();
try {
this.count++;
final int rndnum = this.rnd.nextInt();
buffer.append(System.currentTimeMillis());
buffer.append('.');
try (Formatter formatter = new Formatter(buffer, Locale.ROOT)) {
formatter.format("%1$016x-%2$08x", this.count, rndnum);
}
buffer.append('.');
buffer.append(this.hostname);
} finally {
lock.unlock();
}
}
public String generate() {
final StringBuilder buffer = new StringBuilder();
generate(buffer);
return buffer.toString();
}
}

View File

@ -29,13 +29,17 @@ package org.apache.hc.client5.http.impl.cache;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.hc.client5.http.cache.Resource; import org.apache.hc.client5.http.cache.Resource;
import org.apache.hc.client5.http.cache.ResourceFactory; import org.apache.hc.client5.http.cache.ResourceFactory;
import org.apache.hc.client5.http.cache.ResourceIOException; import org.apache.hc.client5.http.cache.ResourceIOException;
import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.net.PercentCodec;
import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.TextUtils;
/** /**
* Generates {@link Resource} instances whose body is stored in a temporary file. * Generates {@link Resource} instances whose body is stored in a temporary file.
@ -46,37 +50,44 @@ import org.apache.hc.core5.util.Args;
public class FileResourceFactory implements ResourceFactory { public class FileResourceFactory implements ResourceFactory {
private final File cacheDir; private final File cacheDir;
private final BasicIdGenerator idgen;
public FileResourceFactory(final File cacheDir) { public FileResourceFactory(final File cacheDir) {
super(); super();
this.cacheDir = cacheDir; this.cacheDir = cacheDir;
this.idgen = new BasicIdGenerator();
} }
private File generateUniqueCacheFile(final String requestId) { static String generateUniqueCacheFileName(final String requestId, final String eTag, final byte[] content, final int off, final int len) {
final StringBuilder buffer = new StringBuilder(); final StringBuilder buf = new StringBuilder();
this.idgen.generate(buffer); if (eTag != null) {
buffer.append('.'); PercentCodec.RFC3986.encode(buf, eTag);
final int len = Math.min(requestId.length(), 100); buf.append('@');
for (int i = 0; i < len; i++) { } else if (content != null) {
final char ch = requestId.charAt(i); final MessageDigest sha256;
if (Character.isLetterOrDigit(ch) || ch == '.') { try {
buffer.append(ch); sha256 = MessageDigest.getInstance("SHA-256");
} else { } catch (final NoSuchAlgorithmException ex) {
buffer.append('-'); throw new IllegalStateException(ex);
} }
sha256.update(content, off, len);
buf.append(TextUtils.toHexString(sha256.digest()));
buf.append('@');
} }
return new File(this.cacheDir, buffer.toString()); PercentCodec.RFC3986.encode(buf, requestId);
return buf.toString();
} }
/**
* @since 5.4
*/
@Override @Override
public Resource generate( public Resource generate(
final String requestId, final String requestId,
final String eTag,
final byte[] content, final int off, final int len) throws ResourceIOException { final byte[] content, final int off, final int len) throws ResourceIOException {
Args.notNull(requestId, "Request id"); Args.notNull(requestId, "Request id");
final File file = generateUniqueCacheFile(requestId); final String filename = generateUniqueCacheFileName(requestId, eTag, content, off, len);
try (FileOutputStream outStream = new FileOutputStream(file)) { final File file = new File(cacheDir, filename);
try (FileOutputStream outStream = new FileOutputStream(file, false)) {
if (content != null) { if (content != null) {
outStream.write(content, off, len); outStream.write(content, off, len);
} }
@ -86,10 +97,22 @@ public class FileResourceFactory implements ResourceFactory {
return new FileResource(file); return new FileResource(file);
} }
@Override
public Resource generate(final String requestId, final byte[] content, final int off, final int len) throws ResourceIOException {
if (content != null) {
return generate(requestId, null, content, off, len);
} else {
return generate(requestId, null, null, 0, 0);
}
}
@Override @Override
public Resource generate(final String requestId, final byte[] content) throws ResourceIOException { public Resource generate(final String requestId, final byte[] content) throws ResourceIOException {
Args.notNull(content, "Content"); if (content != null) {
return generate(requestId, content, 0, content.length); return generate(requestId, null, content, 0, content.length);
} else {
return generate(requestId, null, null, 0, 0);
}
} }
/** /**

View File

@ -0,0 +1,69 @@
/*
* ====================================================================
* 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
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.cache;
import java.net.URI;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class TestFileResourceFactory {
CacheKeyGenerator keyGenerator;
@BeforeEach
public void setUp() {
keyGenerator = new CacheKeyGenerator();
}
@Test
public void testViaValueLookup() throws Exception {
final String requestId = keyGenerator.generateKey(new URI("http://localhost/stuff"));
Assertions.assertEquals(
"blah%20blah@http%3A%2F%2Flocalhost%3A80%2Fstuff",
FileResourceFactory.generateUniqueCacheFileName(requestId, "blah blah", null, 0, 0));
Assertions.assertEquals(
"blah-blah@http%3A%2F%2Flocalhost%3A80%2Fstuff",
FileResourceFactory.generateUniqueCacheFileName(requestId, "blah-blah", null, 0, 0));
Assertions.assertEquals(
"blah%40blah@http%3A%2F%2Flocalhost%3A80%2Fstuff",
FileResourceFactory.generateUniqueCacheFileName(requestId, "blah@blah", null, 0, 0));
Assertions.assertEquals(
"039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81@http%3A%2F%2Flocalhost%3A80%2Fstuff",
FileResourceFactory.generateUniqueCacheFileName(requestId, null, new byte[]{1, 2, 3}, 0, 3));
Assertions.assertEquals(
"039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81@http%3A%2F%2Flocalhost%3A80%2Fstuff",
FileResourceFactory.generateUniqueCacheFileName(requestId, null, new byte[]{1, 2, 3, 4, 5}, 0, 3));
Assertions.assertEquals(
"http%3A%2F%2Flocalhost%3A80%2Fstuff",
FileResourceFactory.generateUniqueCacheFileName(requestId, null, null, 0, 0));
}
}