Итак, после 2-х дневных исследований проблемы и изучения класса URLConnection
, я наконец-то смог придумать свою собственную (предположительно грубую) реализацию, которую я хотел бы поделиться здесь, на случай, если кто-либо с таким маленьким поскольку я случайно наткнулся на эту тему. Опять же, основная идея заключалась в том, чтобы помещать только определенные типы файлов в кеш, а не все. Я решил хранить файлы jpg
, png
, gif
и js
, так как я считал их самыми загруженными, хотя каждый другой формат файла также должен быть возможен. Прежде всего: browser.getEngine().setUserDataDirectory(...)
определенно НЕ выполняет эту работу. Я до сих пор понятия не имеют, что это хорошо для, но это, конечно, не хранить файлы изображений%)
Вместо того, что я был в основном создание 5 классов:
CachedResource
: Состоит из byte[]
массива холдинг необработанные данные ресурса и некоторая метаинформация (поля заголовка, lastModified)
ResourceCache
: Сохраняет все кэшируемые объекты ресурсов.
MyHttpUrlConnection
(extends sun.net.www.protocol.http.HttpURLConnection
): Класс оболочки, который отвечает за получение файла, на который указывает данный URL. Он делает всю сетевую магию.
CachedUrlConnection
(extends java.net.URLConnection
): (почти) пустая реализация, которая уже имеет все необходимые данные и только ждет, когда система ее вызовет.
MyUrlConnectionHandler
(распространяется sun.net.www.protocol.http.Handler
): Этот класс зарегистрирован при запуске приложения и принимает решение о том, когда следует использовать URLConnection
(см. Ниже).
The ResourceCache
, CachedResource
и CachedUrlConnection
классы достаточно малы и легко писать. Я разработал кеш ресурсов для сопоставления ресурса url с его соответствующим объектом CachedResource
, таким образом: ConcurrentHashMap<URL, CachedResource>
плюс getter и addResource(...)
. Я добавил к нему некоторые другие вещи, такие как хранение файлов локально, но это приводит к отключению темы.
Затем я реализовал CachedUrlConnection
класс следующим образом:
public class CachedUrlConnection extends URLConnection {
private CachedResource resource;
private ByteArrayInputStream inputStream;
/* Constructors */
public CachedUrlConnection(URL url, CachedResource resource) throws IOException {
super(url);
this.resource = resource;
this.inputStream = new ByteArrayInputStream(resource.getByteData());
}
@Override
public void connect() throws IOException {
// No need to do anything.
}
/* Object Methods */
/* Getters and Setters */
@Override
public String getHeaderField(int index) { ... }
@Override
public String getHeaderField(String key) { ... }
@Override
public Map<String, List<String>> getHeaderFields() { ... }
@Override
public InputStream getInputStream() throws IOException {
return inputStream; // <---- Here, the system can grab the data.
}
}
При взгляде на исходный код из URLConnection (например here), вы быстро заметите, что большинство его реализаций метода являются манекены, которые либо возвращают null
или бросьте UnknownServiceException
.
Это важно: я не знаю, какие именно из них вам нужно реализовать!
Для того, чтобы выяснить, я использовал MyHttpUrlConnection
класс и добавил почти каждый функции
System.out.println("function xyz called!");
super.xyz();
, но я был ленивым и не проверял их все. Пока что все работает нормально%)
Следующий класс был MyHttpUrlConnection
. Я не уверен на 100%, если мне действительно нужно перезаписать класс HttpURLConnection
, но я так и сделал, потому что у него есть конструктор protected
, который будет неявно вызываться с новым sun.net.www.protocol.http.Handler
. Этот обработчик, очевидно, не будет следовать нашей политике http, поэтому я просто хотел быть уверенным (cf. sourcecode строка 801). Класс, таким образом, выглядит довольно пустым:
public class MyHttpUrlConnection extends HttpURLConnection {
protected MyHttpUrlConnection(URL url, Handler handler) {
this(url, null, handler);
}
public MyHttpUrlConnection(URL url, Proxy proxy) {
this(url, proxy, new MyUrlConnectionHandler()); // <--- No way sneaking around^^
}
protected MyHttpUrlConnection(URL url, Proxy proxy, Handler handler) {
super(url, proxy, handler);
}
public MyHttpUrlConnection(URL url, String host, int port) {
this(url, new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(host, port))); // Taken over from the HttpURLConnection sourcecode.
}
}
Теперь наступает самый важный часть: MyUrlConnectionHandler
. Опять же, отметьте this thread о том, куда его поместить. Всем классом необходимо перезаписать функцию openConnection(URL, Proxy)
. Прежде чем отправлять мой код, я дам вам полный контроль над тем, что он делает.
- Если данный URL является JPG, PNG, ... Файл:
- Используйте
MyHttpUrlConnection
объект, чтобы получить дату последнего ресурса модификации на сервере. это должно вызывать только заголовок и НЕ весь ресурс. В противном случае мы ничего бы не выиграли. Кредиты идут до this thread. Я не совсем уверен, хотя я закрываю URLConnection
здесь. Лучше дважды проверить, если у вас есть сомнения;)
- Если в кеше нет ресурса или ресурс в кеше устарел:
- Закройте мини-соединение и откройте «правильный», чтобы загрузить все это ,
- Создайте новый объект
CachedResource
и добавьте его в кеш.
- Закройте это новое соединение, тоже.
- Возвращает новый объект
CachedUrlConnection
, содержащий данные. Это может показаться немного глупым, поскольку у нас уже есть все, но функция должна вернуть URLConnection
.
- Если есть какое-либо исключение или мы просто не имеем дело с файлом jpg, png, ..., возвращаем объект «default»
MyHttpUrlConnection
для обработки URL-адреса в обычном режиме.
И соответствующий код выглядит следующим образом.Обратите внимание, что я использовал Apache org.apache.commons.io.IOUtils
:
@Override
protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
try {
// Is this some resource that we'd like to cache?
if (ResourceCache.isCachableURL(url)) {
// Retrieve whatever is in the cache first.
ResourceCache cache = ResourceCache.getInstance();
CachedResource resource = cache.getCachedResource(url);
// Open a connection to the server to at least check for the last-modified field.
MyHttpUrlConnection conn = new MyHttpUrlConnection(url, this); // Don't use URL#openConnection to avoid looping!
conn.setRequestMethod("HEAD");
conn.connect();
long lastModified = conn.getLastModified();
// Did we get the last-modified value at all?
if (lastModified == 0) {
throw new Exception("No last-modified value could be read! \n\t" + url);
}
// Resource not cached or out of date?
if (resource == null || resource.getLastModified() < lastModified) {
conn = new MyHttpUrlConnection(url, this);
conn.connect();
InputStream input = conn.getInputStream();
byte[] data = IOUtils.toByteArray(input);
Map<String, List<String>> headerFields = conn.getHeaderFields();
IOUtils.closeQuietly(input);
resource = new CachedResource(url.getFile(), data, headerFields, lastModified); // I use url.getFile() to store the file on my hard drive.
cache.addCachedResource(url, resource);
}
return new CachedUrlConnection(url, resource);
}
} catch (Exception e) {
e.printStackTrace();
}
// Return the default HttpURLConnection in our wrapper class.
return new MyHttpUrlConnection(url, proxy, this);
}
последнее: Чтобы быть на безопасной стороне, НИКОГДА не используйте метод URL#openConnection
внутри функции MyUrlConnectionHandler#openConnection
. Написание этого способа делает это довольно очевидным, почему, но сегодня мне потребовалось довольно много времени, чтобы выяснить, откуда пришел бесконечный цикл из%). Вместо этого используйте конструктор и вызовите connect()
.
Я надеюсь, что это когда-нибудь помочь любому, в противном случае это было хорошее упражнение для меня ^^
Ну сейчас я был бы счастлив, если бы я мог, по крайней мере сохранить нагрузки тяжелых графических файлов на жестком диске а затем загрузите их оттуда. Однако я не знаю, как это сделать. Можно ли расширить «net.www.protocol.http.Handler» или «HttpURLConnection» и «просто» изменить часть своей функциональности в соответствии с моими потребностями? (Мне нужно только поддерживать http на данный момент.) – dadoosh