Итак, после 2 дней изучения проблемы и изучения класса URLConnection
я, наконец, смог придумать свою собственную (предположительно грубую) реализацию, которой я хотел бы поделиться здесь на случай, если кто-то знает так же мало, как я. натыкаться на эту ветку. Опять же, основная идея заключалась в том, чтобы помещать в кеш только определенные типы файлов, а не все. Я решил хранить файлы jpg
, png
, gif
и js
, так как думал, что они будут наиболее загруженными, хотя любой другой формат файла также должен быть возможен. Перво-наперво: browser.getEngine().setUserDataDirectory(...)
определенно НЕ выполняет эту работу. Я до сих пор понятия не имею, для чего он нужен, но точно не для хранения файлов изображений %)
Вместо этого я в основном создал 5 классов:
CachedResource
: состоит из массива byte[]
, содержащего необработанные данные ресурса и некоторую метаинформацию (поля заголовка, lastModified).
ResourceCache
: Содержит все кэшированные объекты ресурсов.
MyHttpUrlConnection
(расширяет sun.net.www.protocol.http.HttpURLConnection
): класс-оболочка, который отвечает за извлечение файла, на который указывает его заданный URL-адрес. Он делает всю сетевую магию.
CachedUrlConnection
(расширяет java.net.URLConnection
): (почти) пустая реализация, которая уже имеет все необходимые нам данные и только ждет, пока система ее вызовет.
MyUrlConnectionHandler
(расширяет sun.net.www.protocol.http.Handler
): этот класс регистрируется при запуске приложения и решает, когда использовать какой URLConnection
(см. ниже).
Классы ResourceCache
, CachedResource
и CachedUrlConnection
довольно малы и просты в написании. Я разработал кеш ресурсов для сопоставления ресурса URL-адреса с соответствующим объектом CachedResource
, таким образом: ConcurrentHashMap<URL, CachedResource>
плюс геттер и функция 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 (например, здесь), вы быстро заметите, что большинство реализаций его методов являются фиктивными, которые либо возвращают null
, либо выдают UnknownServiceException
.
Это важно: я точно не знаю, что из этого вам нужно реализовать!
Чтобы это выяснить, я использовал класс MyHttpUrlConnection
и добавил почти к каждой функции
System.out.println("function xyz called!");
super.xyz();
но я был ленив и не проверил их все. Пока вроде все работает нормально %)
Следующий класс был MyHttpUrlConnection
. Я не уверен на 100%, действительно ли мне нужно было перезаписать класс HttpURLConnection
, но я все равно это сделал, потому что у него есть конструктор protected
, который будет неявно вызываться с новым sun.net.www.protocol.http.Handler
. Этот обработчик, очевидно, не будет следовать нашей политике http, поэтому я просто хотел быть уверенным (см. исходный код строка 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
. Опять же, проверьте эту тему, куда ее поместить. Все, что нужно самому классу, это перезаписать функцию openConnection(URL, Proxy)
. Прежде чем опубликовать свой код, я расскажу вам, что он делает.
- Если данный URL-адрес является файлом jpg, png, ...:
- Используйте объект
MyHttpUrlConnection
, чтобы получить дату последней модификации ресурса на сервере. это должно вызывать только заголовок, а НЕ весь ресурс. Иначе мы бы ничего не выиграли. Авторы отправляются в эту тему. Однако я не совсем уверен, правильно ли я закрываю здесь URLConnection
. Лучше перепроверьте, если сомневаетесь ;)
- Если в кеше нет ресурса ИЛИ ресурс в кеше устарел:
- Закройте мини-соединение и откройте «правильное», чтобы загрузить все это.
- Создайте новый объект
CachedResource
и добавьте его в кеш.
- Закройте и это новое соединение.
- Вернуть новый объект
CachedUrlConnection
, содержащий данные. Это может показаться немного глупым, поскольку у нас уже есть все, но функция должна возвращать URLConnection
.
- Если есть какое-либо исключение или мы просто не работали с файлом jpg, png, ..., верните объект
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()
.
Я надеюсь, что это когда-нибудь кому-нибудь поможет, в остальном это было хорошим упражнением для меня ^^
person
MightyMalcolm
schedule
23.09.2015