All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.redkale.net.http.HttpResourceServlet Maven / Gradle / Ivy

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package org.redkale.net.http;

import java.io.*;
import java.nio.ByteBuffer;
import static java.nio.file.StandardWatchEventKinds.*;
import java.nio.file.*;
import java.util.AbstractMap.SimpleEntry;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.LongAdder;
import java.util.logging.*;
import java.util.regex.*;
import org.redkale.util.AnyValue;

/**
 * 静态资源HttpServlet
 *
 * 

* 详情见: https://redkale.org * * @author zhangjx */ public class HttpResourceServlet extends HttpServlet { protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName()); protected class WatchThread extends Thread { protected final File root; protected final WatchService watcher; public WatchThread(File root) throws IOException { this.root = root; this.setName("HttpResourceServlet-Watch-Thread"); this.setDaemon(true); this.watcher = this.root.toPath().getFileSystem().newWatchService(); } @Override public void run() { try { final String rootstr = root.getCanonicalPath(); while (!this.isInterrupted()) { final WatchKey key = watcher.take(); final Path parent = keymaps.get(key); if (parent == null) { key.cancel(); continue; } key.pollEvents().stream().forEach((event) -> { try { Path path = parent.resolve((Path) event.context()); final String uri = path.toString().substring(rootstr.length()).replace('\\', '/'); //logger.log(Level.FINEST, "file(" + uri + ") happen " + event.kind() + " event"); if (event.kind() == ENTRY_DELETE) { FileEntry en = files.remove(uri); if (en != null) en.remove(); } else if (event.kind() == ENTRY_MODIFY) { FileEntry en = files.get(uri); if (en != null && en.file != null) { long d; //等待update file完毕 for (;;) { d = en.file.lastModified(); Thread.sleep(2000L); if (d == en.file.lastModified()) break; } en.update(); } } } catch (Exception ex) { logger.log(Level.FINE, event.context() + " occur erroneous", ex); } }); key.reset(); } } catch (Exception e) { } } } protected final LongAdder cachedLength = new LongAdder(); //缓存总大小, 默认0 protected long cachelimit = 0 * 1024 * 1024L; //最大可缓存的文件大小, 大于该值的文件将不被缓存 protected long cachelengthmax = 1 * 1024 * 1024; //是否监控缓存文件的变化, 默认不监控 protected boolean watch = false; protected File root = new File("./root/"); protected String indexHtml = "index.html"; protected final ConcurrentHashMap files = new ConcurrentHashMap<>(); protected final ConcurrentHashMap keymaps = new ConcurrentHashMap<>(); protected SimpleEntry[] locationRewrites; protected WatchThread watchThread; @Override public void init(HttpContext context, AnyValue config) { if (config != null) { String rootstr = config.getValue("webroot", "root"); this.indexHtml = config.getValue("index", "index.html"); if (rootstr.indexOf(':') < 0 && rootstr.indexOf('/') != 0 && System.getProperty("APP_HOME") != null) { rootstr = new File(System.getProperty("APP_HOME"), rootstr).getPath(); } try { this.root = new File(rootstr).getCanonicalFile(); } catch (IOException ioe) { this.root = new File(rootstr); } AnyValue cacheconf = config.getAnyValue("cache"); if (cacheconf != null) { this.cachelimit = parseLenth(cacheconf.getValue("limit"), 0 * 1024 * 1024L); this.cachelengthmax = parseLenth(cacheconf.getValue("lengthmax"), 1 * 1024 * 1024L); this.watch = cacheconf.getBoolValue("watch", false); } List> locations = new ArrayList<>(); for (AnyValue av : config.getAnyValues("rewrite")) { if ("location".equals(av.getValue("type"))) { String m = av.getValue("match"); String f = av.getValue("forward"); if (m != null && f != null) { locations.add(new SimpleEntry<>(Pattern.compile(m), f)); } } } this.locationRewrites = locations.isEmpty() ? null : locations.toArray(new SimpleEntry[locations.size()]); } if (this.cachelimit < 1) return; //不缓存不需要开启WatchThread监听 if (this.root != null && this.watch) { try { this.watchThread = new WatchThread(this.root); this.watchThread.start(); } catch (IOException ex) { logger.log(Level.WARNING, HttpResourceServlet.class.getSimpleName() + " start watch-thread error", ex); } } } @Override public void destroy(HttpContext context, AnyValue config) { if (this.watchThread != null) { try { this.watchThread.watcher.close(); } catch (IOException ex) { logger.log(Level.WARNING, HttpResourceServlet.class.getSimpleName() + " close watch-thread error", ex); } if (this.watchThread.isAlive()) this.watchThread.interrupt(); } } public void serRoot(String rootstr) { if (rootstr == null) return; try { this.root = new File(rootstr).getCanonicalFile(); } catch (IOException ioe) { this.root = new File(rootstr); } } public void serRoot(File file) { if (file == null) return; try { this.root = file.getCanonicalFile(); } catch (IOException ioe) { this.root = file; } } protected static long parseLenth(String value, long defValue) { if (value == null) return defValue; value = value.toUpperCase().replace("B", ""); if (value.endsWith("G")) return Long.decode(value.replace("G", "")) * 1024 * 1024 * 1024; if (value.endsWith("M")) return Long.decode(value.replace("M", "")) * 1024 * 1024; if (value.endsWith("K")) return Long.decode(value.replace("K", "")) * 1024; return Long.decode(value); } @Override public void execute(HttpRequest request, HttpResponse response) throws IOException { String uri = request.getRequestURI(); if (uri.contains("../")) { if (logger.isLoggable(Level.FINEST)) logger.log(Level.FINEST, "Not found resource (404) be " + uri + ", request = " + request); response.finish404(); return; } if (locationRewrites != null) { for (SimpleEntry entry : locationRewrites) { Matcher matcher = entry.getKey().matcher(uri); if (matcher.find()) { StringBuffer sb = new StringBuffer(uri.length()); matcher.appendReplacement(sb, entry.getValue()); matcher.appendTail(sb); uri = sb.toString(); break; } } } if (uri.length() == 0 || uri.equals("/")) { uri = this.indexHtml.indexOf('/') == 0 ? this.indexHtml : ("/" + this.indexHtml); } //System.out.println(request); FileEntry entry; if (watchThread == null && files.isEmpty()) { entry = createFileEntry(uri); } else { //有缓存 entry = files.computeIfAbsent(uri, x -> createFileEntry(x)); } if (entry == null) { if (logger.isLoggable(Level.FINER)) logger.log(Level.FINER, "Not found resource (404), request = " + request); response.finish404(); } else { //file = null 表示资源内容在内存而不是在File中 //file = null 时必须传 filename response.finishFile(entry.file == null ? entry.filename : null, entry.file, entry.content); } } protected FileEntry createFileEntry(String uri) { File rfile = new File(root, uri); File file = rfile; if (file.isDirectory()) file = new File(rfile, this.indexHtml); if (file.isDirectory()) file = new File(rfile, "index.html"); if (!file.isFile() || !file.canRead()) return null; FileEntry en = new FileEntry(this, file); if (watchThread == null) return en; try { Path p = file.getParentFile().toPath(); keymaps.put(p.register(watchThread.watcher, ENTRY_MODIFY, ENTRY_DELETE), p); } catch (IOException e) { logger.log(Level.INFO, HttpResourceServlet.class.getSimpleName() + " watch FileEntry(" + uri + ") erroneous", e); } return en; } protected static class FileEntry { protected final String filename; protected final File file; //如果所有资源文件打包成zip文件则file=null protected final HttpResourceServlet servlet; protected ByteBuffer content; public FileEntry(final HttpResourceServlet servlet, File file) { this.servlet = servlet; this.file = file; this.filename = file.getName(); update(); } public FileEntry(final HttpResourceServlet servlet, String filename, ByteBuffer content) { this.servlet = servlet; this.file = null; this.filename = filename; this.content = content.asReadOnlyBuffer(); this.servlet.cachedLength.add(this.content.remaining()); } public FileEntry(final HttpResourceServlet servlet, String filename, InputStream in) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] bytes = new byte[10240]; int pos; while ((pos = in.read(bytes)) != -1) { out.write(bytes, 0, pos); } byte[] bs = out.toByteArray(); ByteBuffer buf = ByteBuffer.allocateDirect(bs.length); buf.put(bs); buf.flip(); this.servlet = servlet; this.file = null; this.filename = filename; this.content = buf.asReadOnlyBuffer(); this.servlet.cachedLength.add(this.content.remaining()); } public void update() { if (this.file == null) return; if (this.content != null) { this.servlet.cachedLength.add(0L - this.content.remaining()); this.content = null; } long length = this.file.length(); if (length > this.servlet.cachelengthmax) return; if (this.servlet.cachedLength.longValue() + length > this.servlet.cachelimit) return; //超过缓存总容量 try { FileInputStream in = new FileInputStream(file); ByteArrayOutputStream out = new ByteArrayOutputStream((int) file.length()); byte[] bytes = new byte[10240]; int pos; while ((pos = in.read(bytes)) != -1) { out.write(bytes, 0, pos); } in.close(); byte[] bs = out.toByteArray(); ByteBuffer buf = ByteBuffer.allocateDirect(bs.length); buf.put(bs); buf.flip(); this.content = buf.asReadOnlyBuffer(); this.servlet.cachedLength.add(this.content.remaining()); } catch (Exception e) { this.servlet.logger.log(Level.INFO, HttpResourceServlet.class.getSimpleName() + " update FileEntry(" + file + ") erroneous", e); } } public void remove() { if (this.content != null) this.servlet.cachedLength.add(0L - this.content.remaining()); } public long getCachedLength() { return this.content == null ? 0L : this.content.remaining(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy