Implement HTTP HEAD Last-Modified checking for ZipCache objects.

Since it seems that no Sun-derived JVM ever invalidates its cache of
URLConnection objects, we have to check for modification "manually".
This implementation is "better than nothing", but not expected to be
especially robust.  Most notably, this implementation does not attempt
to use http proxies if they have been specified to the JVM by the
'http.proxy' system property.
parent 6b5905fd
......@@ -32,21 +32,21 @@
*/
package org.armedbear.lisp;
import org.armedbear.lisp.util.HttpHead;
import static org.armedbear.lisp.Lisp.*;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Enumeration;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipEntry;
/**
* A cache for all zip/jar file accesses by URL that uses the last
......@@ -67,7 +67,7 @@ public class ZipCache {
// that keeps track of the number of outstanding references handed
// out, not allowing ZipFile.close() to succeed until that count
// has been reduced to 1 or the finalizer is executing.
// Unfortunately the relatively simple strategy of extended
// Unfortunately the relatively simple strategy of extending
// ZipFile via a CachedZipFile does not work because there is not
// a null arg constructor for ZipFile.
static class Entry {
......@@ -101,6 +101,9 @@ public class ZipCache {
return get(Pathname.makeURL(arg));
}
static final SimpleDateFormat RFC_1123
= new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
synchronized public static ZipFile get(final URL url) {
if (!cacheEnabled) {
if (url.getProtocol().equals("file")) {
......@@ -145,28 +148,31 @@ public class ZipCache {
Debug.trace(e.toString()); // XXX
}
}
} else {
} else if (url.getProtocol().equals("http")) {
// Unfortunately, the Apple JDK under OS X doesn't do
// HTTP HEAD requests, instead refetching the entire
// resource, so the following code is a waste. I assume
// this is the case in all Sun-dervied JVMs. We'll have
// to implement a custom HTTP lastModified checker.
// URLConnection connection;
// try {
// connection = url.openConnection();
// } catch (IOException ex) {
// Debug.trace("Failed to open "
// + "'" + url + "'");
// return null;
// }
// long current = connection.getLastModified();
// if (current > entry.lastModified) {
// try {
// entry.file.close();
// } catch (IOException ex) {}
// entry = fetchURL(url, false);
// }
// resource, and I assume this is the case in all
// Sun-derived JVMs. So, we use a custom HEAD
// implementation only looking for Last-Modified
// headers, which if we don't find, we give up and
// refetch the resource.n
String dateString = HttpHead.get(url, "Last-Modified");
Date date = null;
try {
date = RFC_1123.parse(dateString);
long current = date.getTime();
if (current > entry.lastModified) {
entry = fetchURL(url, false);
zipCache.put(url, entry);
}
} catch (ParseException e) {
Debug.trace("Failed to parse HTTP Last-Modified field: " + e);
entry = fetchURL(url, false);
zipCache.put(url, entry);
}
} else {
entry = fetchURL(url, false);
zipCache.put(url, entry);
}
} else {
if (url.getProtocol().equals("file")) {
......
/*
* HttpHead.java
*
* Copyright (C) 2010 Mark Evenson
* $Id$
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce an
* executable, regardless of the license terms of these independent
* modules, and to copy and distribute the resulting executable under
* terms of your choice, provided that you also meet, for each linked
* independent module, the terms and conditions of the license of that
* module. An independent module is a module which is not derived from
* or based on this library. If you modify this library, you may extend
* this exception to your version of the library, but you are not
* obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
package org.armedbear.lisp.util;
import org.armedbear.lisp.Debug;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.Socket;
import java.net.URL;
/**
* Use HTTP/1.1 HEAD to retrieve the specified header field.
*/
public class HttpHead {
static private String get(String urlString, String key) {
URL url = null;
try {
url = new URL(urlString);
} catch (MalformedURLException e) {
log("Failed to form url from " + "'" + urlString + "'" + ": " + e);
}
return get(url, key);
}
static public String get(URL url, String key) {
Socket socket = null;
String result = null;
try {
String protocol = url.getProtocol();
if (!protocol.equals("http")) {
log("The protocol " + "'" + protocol + "'" + " is not http.");
return result;
}
socket = new Socket(Proxy.NO_PROXY); // XXX add Proxy
int port = url.getPort();
if (port == -1) {
port = 80;
}
InetSocketAddress address = new InetSocketAddress(url.getHost(), port);
try {
socket.connect(address, 5000); // ??? too long? too short?
} catch (IOException ex) {
log("Connection failed: " + ex);
return result;
}
PrintWriter out = null;
BufferedReader in = null;
try {
out = new PrintWriter(socket.getOutputStream());
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
} catch (IOException e) {
log("Failed to establish socket io: " + e);
return result;
}
String path = url.getPath();
out.println("HEAD " + url + " HTTP/1.1");
out.println("Connection: close");
out.println("");
out.flush();
String line = null;
try {
line = in.readLine();
} catch (IOException e) {
log("Failed to read HTTP response: " + e);
}
String status[] = line.split("\\s");
if (status[1].equals("200")) {
result = findHeader(in, key);
} else if (status[1].startsWith("3")) {
// Follow redirects ad nauseum
String location = findHeader(in, "Location");
if (location != null) {
return get(location, key);
}
} else {
log("Unexpected response: " + line);
}
} finally {
try {
socket.close();
} catch (IOException e) {
}
}
return result;
}
static private String findHeader(BufferedReader in, String key) {
String result = null;
String line;
try {
while ((line = in.readLine()) != null) {
int i = line.indexOf(":");
if (i == -1) {
continue; // XXX parse multi-line HTTP headers
}
String k = line.substring(0, i);
String v = line.substring(i + 1).trim();
if (k.equals(key)) {
result = v;
break;
}
}
} catch (IOException e) {
log("Failed to read headers: " + e);
}
return result;
}
static private void log(String message) {
Debug.warn(message);
}
public static void main(String argv[]) {
if (argv.length != 1) {
System.out.println("Usage: <cmd> URL");
return;
}
String modified = get(argv[0], "Last-Modified");
if (modified != null) {
System.out.println("Last-Modified: " + modified);
} else {
System.out.println("No result returned.");
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment