Commit ae78e3e1 authored by Liang Ding's avatar Liang Ding

页面静态化 #107

parent 6a97facb
......@@ -420,6 +420,7 @@ public final class Server extends BaseServer {
*/
private static void routeIndexProcessors() {
final BeanManager beanManager = BeanManager.getInstance();
final StaticMidware staticMidware = beanManager.getReference(StaticMidware.class);
final ArticleProcessor articleProcessor = beanManager.getReference(ArticleProcessor.class);
final Dispatcher.RouterGroup articleGroup = Dispatcher.group();
......@@ -443,12 +444,14 @@ public final class Server extends BaseServer {
final BlogProcessor blogProcessor = beanManager.getReference(BlogProcessor.class);
final Dispatcher.RouterGroup blogGroup = Dispatcher.group();
blogGroup.middlewares(staticMidware::handle);
blogGroup.get("/manifest.json", blogProcessor::getPWAManifestJSON).
get("/blog/info", blogProcessor::getBlogInfo).
get("/blog/articles-tags", blogProcessor::getArticlesTags);
final CategoryProcessor categoryProcessor = beanManager.getReference(CategoryProcessor.class);
final Dispatcher.RouterGroup categoryGroup = Dispatcher.group();
categoryGroup.middlewares(staticMidware::handle);
categoryGroup.get("/articles/category/{categoryURI}", categoryProcessor::getCategoryArticlesByPage).
get("/category/{categoryURI}", categoryProcessor::showCategoryArticles);
......@@ -458,11 +461,13 @@ public final class Server extends BaseServer {
final FeedProcessor feedProcessor = beanManager.getReference(FeedProcessor.class);
final Dispatcher.RouterGroup feedGroup = Dispatcher.group();
feedGroup.middlewares(staticMidware::handle);
feedGroup.router().get().head().uri("/atom.xml").handler(feedProcessor::blogArticlesAtom).
get().head().uri("/rss.xml").handler(feedProcessor::blogArticlesRSS);
final IndexProcessor indexProcessor = beanManager.getReference(IndexProcessor.class);
final Dispatcher.RouterGroup indexGroup = Dispatcher.group();
indexGroup.middlewares(staticMidware::handle);
indexGroup.router().get(new String[]{"", "/", "/index.html"}, indexProcessor::showIndex);
indexGroup.get("/start", indexProcessor::showStart).
get("/logout", indexProcessor::logout).
......@@ -480,14 +485,17 @@ public final class Server extends BaseServer {
final SitemapProcessor sitemapProcessor = beanManager.getReference(SitemapProcessor.class);
final Dispatcher.RouterGroup sitemapGroup = Dispatcher.group();
sitemapGroup.middlewares(staticMidware::handle);
sitemapGroup.get("/sitemap.xml", sitemapProcessor::sitemap);
final TagProcessor tagProcessor = beanManager.getReference(TagProcessor.class);
final Dispatcher.RouterGroup tagGroup = Dispatcher.group();
tagGroup.middlewares(staticMidware::handle);
tagGroup.get("/tags/{tagTitle}", tagProcessor::showTagArticles);
final UserTemplateProcessor userTemplateProcessor = beanManager.getReference(UserTemplateProcessor.class);
final Dispatcher.RouterGroup userTemplateGroup = Dispatcher.group();
userTemplateGroup.middlewares(staticMidware::handle);
userTemplateGroup.get("/{name}.html", userTemplateProcessor::showPage);
}
......
......@@ -19,6 +19,7 @@ import org.b3log.latke.http.Request;
import org.b3log.latke.http.RequestContext;
import org.b3log.latke.http.renderer.AbstractFreeMarkerRenderer;
import org.b3log.solo.util.Skins;
import org.b3log.solo.util.Statics;
import java.io.StringWriter;
import java.util.Map;
......@@ -27,7 +28,7 @@ import java.util.Map;
* Skin renderer.
*
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.0.0.3, Jan 5, 2019
* @version 1.0.0.4, Apr 14, 2020
* @since 2.9.1
*/
public final class SkinRenderer extends AbstractFreeMarkerRenderer {
......@@ -37,6 +38,8 @@ public final class SkinRenderer extends AbstractFreeMarkerRenderer {
*/
private final RequestContext context;
public static final String LATKE_INFO = "\n<!-- Generated by Latke (https://github.com/88250/latke) in %1$dms, %2$s -->";
/**
* Constructs a skin renderer with the specified request context and template name.
*
......@@ -106,6 +109,7 @@ public final class SkinRenderer extends AbstractFreeMarkerRenderer {
@Override
protected void afterRender(final RequestContext context) {
Statics.put(context);
}
/**
......
/*
* Solo - A small and beautiful blogging system written in Java.
* Copyright (c) 2010-present, b3log.org
*
* Solo is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.b3log.solo.processor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.b3log.latke.http.RequestContext;
import org.b3log.latke.ioc.Singleton;
import org.b3log.solo.util.Statics;
/**
* 页面静态化. https://github.com/88250/solo/issues/107
*
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.0.0.0, Apr 14, 2020
* @since 4.1.0
*/
@Singleton
public class StaticMidware {
/**
* Logger.
*/
private static final Logger LOGGER = LogManager.getLogger(StaticMidware.class);
public void handle(final RequestContext context) {
final String html = Statics.get(context);
if (null == html) {
context.handle();
return;
}
context.getResponse().setContentType("text/html; charset=utf-8");
context.sendString(html);
context.abort();
}
}
/*
* Solo - A small and beautiful blogging system written in Java.
* Copyright (c) 2010-present, b3log.org
*
* Solo is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.b3log.solo.util;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.RandomUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.b3log.latke.http.RequestContext;
import org.b3log.solo.processor.SkinRenderer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* Static utilities.
*
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.0.0.0, Apr 14, 2020
* @since 4.1.0
*/
public final class Statics {
/**
* Logger.
*/
private static Logger LOGGER = LogManager.getLogger(Statics.class);
/**
* Generated page expire time.
*/
private static long EXPIRED = TimeUnit.HOURS.toMillis(6);
private static File DIR;
static {
final String userHome = System.getProperty("user.home");
final Path staticCache = Paths.get(userHome, ".solo", "static-cache");
final String staticDir = staticCache.toString();
if (StringUtils.isNotBlank(staticDir)) {
try {
FileUtils.forceMkdir(new File(staticDir));
DIR = new File(staticDir);
} catch (final Exception e) {
LOGGER.log(Level.ERROR, "Creates static cache dir failed", e);
}
}
}
/**
* Gets static HTML.
*
* @param context the specified context
* @return HTML, returns {@code null} if not found
*/
public static String get(final RequestContext context) {
final String key = key(context);
if (null == key) {
return null;
}
final Path path = Paths.get(DIR.getAbsolutePath(), key);
final File file = path.toFile();
if (!file.exists()) {
return null;
}
final long now = System.currentTimeMillis();
final long lastModified = file.lastModified();
if (EXPIRED <= now - lastModified) {
return null;
}
return readFile(file);
}
/**
* Puts static HTML.
*
* @param context the specified context
*/
public static void put(final RequestContext context) {
final String key = key(context);
if (null == key) {
return;
}
final Path path = Paths.get(DIR.getAbsolutePath(), key);
final File file = path.toFile();
if (file.exists()) {
final long now = System.currentTimeMillis();
final long lastModified = file.lastModified();
if (EXPIRED > now - lastModified) {
return;
}
}
try {
final byte[] html = context.getResponse().getBytes();
byte[] commpressed = gzip(html);
if (null == commpressed) {
commpressed = html;
}
FileUtils.writeByteArrayToFile(path.toFile(), commpressed);
} catch (final Exception e) {
LOGGER.log(Level.ERROR, "Writes static file failed", e);
}
}
private static File[] files;
/**
* Gets static HTML randomly.
*
* @return HTML
*/
public static String random() {
if (null == DIR) {
return null;
}
if (null == files) {
files = DIR.listFiles();
if (null == files) {
return null;
}
}
final File file = files[RandomUtils.nextInt(files.length)];
return readFile(file);
}
private static String readFile(final File file) {
try {
final byte[] compressed = FileUtils.readFileToByteArray(file);
byte[] html = unGzip(compressed);
if (null == html) {
html = compressed;
}
final String content = new String(html, StandardCharsets.UTF_8);
final List<String> lines = Arrays.asList(content.split("\n"));
final long elapsed = ThreadLocalRandom.current().nextLong(64, 128);
final String dateString = DateFormatUtils.format(System.currentTimeMillis(), "yyyy/MM/dd HH:mm:ss");
final String lastLine = String.format(SkinRenderer.LATKE_INFO, elapsed, dateString);
lines.set(lines.size() - 1, lastLine);
return StringUtils.join(lines, "\n");
} catch (final Exception e) {
LOGGER.log(Level.ERROR, "Reads static file failed", e);
}
return null;
}
/**
* Calculates key of the specified context.
*
* @param context the specified context
* @return key, returns {@code null} if can not be static
*/
private static String key(final RequestContext context) {
if (null == DIR) {
return null;
}
if (!StringUtils.equalsIgnoreCase("get", context.method())) {
return null;
}
String ret;
final String requestURI = context.requestURI();
ret = StringUtils.replace(requestURI, "/", "_");
if (Solos.isMobile(context.getRequest())) {
ret = "m" + ret;
}
return ret;
}
private static byte[] gzip(final byte[] data) {
try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
final GZIPOutputStream zout = new GZIPOutputStream(out)) {
zout.write(data);
zout.close();
return out.toByteArray();
} catch (final Exception e) {
LOGGER.log(Level.ERROR, "Gzip failed", e);
return null;
}
}
private static byte[] unGzip(final byte[] compressed) {
try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
final ByteArrayInputStream in = new ByteArrayInputStream(compressed);
final GZIPInputStream zin = new GZIPInputStream(in)) {
final byte[] buffer = new byte[1024];
int offset;
while ((offset = zin.read(buffer)) != -1) {
out.write(buffer, 0, offset);
}
return out.toByteArray();
} catch (final Exception e) {
LOGGER.log(Level.ERROR, "UnGzip failed", e);
return null;
}
}
private Statics() {
}
}
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