Created
August 7, 2013 10:30
-
-
Save otykhonruk/6172911 to your computer and use it in GitHub Desktop.
HTTP server that serves content of zip files. Useful for huge zipped piles of html, for example Java SDK docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package alt; | |
import static java.net.HttpURLConnection.*; // http status codes | |
import java.io.*; | |
import java.net.InetSocketAddress; | |
import java.text.SimpleDateFormat; | |
import java.util.*; | |
import java.util.concurrent.Executors; | |
import java.util.logging.Logger; | |
import java.util.zip.*; | |
import javax.activation.MimetypesFileTypeMap; | |
import com.sun.net.httpserver.*; | |
/** | |
* Serves zip files. | |
*/ | |
public class ZipHttpServer { | |
private final Properties props; | |
private final Logger logger = Logger.getLogger(getClass().getName()); | |
private HttpServer server; | |
private int port; | |
public ZipHttpServer(Properties props) { | |
this.props = props; | |
} | |
public void initialize() throws IOException { | |
port = Integer.parseInt(props.getProperty("alt.ziphttp.port", "8080")); | |
InetSocketAddress addr = new InetSocketAddress(port); | |
server = HttpServer.create(addr, 0); | |
for(String ctx : props.stringPropertyNames()) { | |
if(ctx.startsWith("/")) { | |
String[] def = props.getProperty(ctx).split("\\s+"); | |
String fileName = def[0]; | |
String docRoot = ""; | |
if(def.length > 1) { | |
docRoot = def[1]; | |
} | |
File file = new File(fileName); | |
if(file.canRead()) { | |
server.createContext(ctx, new ZipFileHandler(new ZipFile(file), docRoot, logger)); | |
logger.info("Context initialized: " + ctx); | |
} | |
else { | |
logger.warning("Cannot create context " + ctx +": file " + fileName + " does not exists or not readable"); | |
} | |
} | |
} | |
} | |
public void start() { | |
server.setExecutor(Executors.newCachedThreadPool()); | |
server.start(); | |
logger.info("Server listening on port " + port); | |
} | |
public static void main(String[] args) throws IOException { | |
Properties props = new Properties(); | |
File propsFile = (args.length == 1) ? new File(args[0]) : new File("ziphttp.properties"); | |
if (propsFile.canRead()) { | |
props.load(new FileReader(propsFile)); | |
} else { | |
System.out.println("Usage: java alt.ZipHttpServer ziphttp.properties"); | |
System.exit(1); | |
} | |
ZipHttpServer server = new ZipHttpServer(props); | |
server.initialize(); | |
server.start(); | |
} | |
} | |
/** | |
* Handles single zip file | |
*/ | |
class ZipFileHandler implements HttpHandler { | |
private static final SimpleDateFormat RFC1123DateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz"); | |
private static final MimetypesFileTypeMap types = new MimetypesFileTypeMap(); | |
static { types.addMimeTypes("text/css css CSS"); } // basic map supplied by jre does not contain css mime type, causing browsers to ignore stylesheets | |
private final ZipFile zipfile; | |
private final String docroot; | |
private Logger logger; | |
/** | |
* Constructs handler for specified zip file | |
* | |
* @param file zip file | |
* @param docroot document root, directory within given file | |
* @param logger logger for this context | |
*/ | |
ZipFileHandler(ZipFile file, String docroot, Logger logger) { | |
this.zipfile = file; | |
this.docroot = docroot; | |
this.logger = logger; | |
} | |
public void handle(HttpExchange exchange) throws IOException { | |
final String requestMethod = exchange.getRequestMethod(); | |
final String ctxPath = exchange.getHttpContext().getPath(); | |
final String reqPath = exchange.getRequestURI().getPath(); | |
if (requestMethod.equalsIgnoreCase("GET")) { | |
String path = reqPath.replaceFirst(ctxPath, docroot); | |
ZipEntry entry = zipfile.getEntry(path); | |
// if entry is directory, append "index.html". TODO: IndexFiles | |
if((entry != null) && entry.isDirectory()) { | |
path += "index.html"; | |
entry = zipfile.getEntry(path); | |
} | |
logger.finer("Request path: " + reqPath + "; rewritten path: " + path); | |
if (entry != null) { // 200 OK | |
// headers | |
Headers responseHeaders = exchange.getResponseHeaders(); | |
responseHeaders.set("Content-Type", types.getContentType(path)); | |
responseHeaders.set("Last-Modified", RFC1123DateFormat.format(new Date(entry.getTime()))); | |
exchange.sendResponseHeaders(HTTP_OK, entry.getSize()); | |
// body | |
OutputStream responseBody = exchange.getResponseBody(); | |
InputStream is = zipfile.getInputStream(entry); | |
byte[] buf = new byte[4096]; | |
int read; | |
while((read = is.read(buf)) != -1) { | |
responseBody.write(buf, 0, read); | |
} | |
is.close(); | |
responseBody.close(); | |
logger.fine(HTTP_OK + " OK " + reqPath); | |
} else { // 404 Not found | |
exchange.sendResponseHeaders(HTTP_NOT_FOUND, -1); | |
logger.fine(HTTP_NOT_FOUND + " NOT_FOUND " + reqPath); | |
} | |
} else { | |
exchange.sendResponseHeaders(HTTP_BAD_METHOD, -1); | |
logger.fine(HTTP_BAD_METHOD + " BAD_METHOD " + reqPath); | |
} | |
exchange.close(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment