Skip to content

Commit

Permalink
Implement http caching (#2840)
Browse files Browse the repository at this point in the history
* Implement first response parts of http caching
* Implement cached response for static resources
* Implement HTTP caching for json responses
* Fix last seen value for online players
* Implement http caching for pages (.html)
* Use placeholder cache even with async requests.

Affects issues:
- Close #2813
  • Loading branch information
AuroraLS3 authored Jan 22, 2023
1 parent 0ddda27 commit 88b4191
Show file tree
Hide file tree
Showing 42 changed files with 700 additions and 242 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@
*/
package com.djrapitops.plan.delivery.web.resource;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.function.Supplier;

/**
* Represents a customizable resource.
Expand Down Expand Up @@ -61,19 +60,55 @@ static WebResource create(String utf8String) {
* @throws IOException If the stream can not be read.
*/
static WebResource create(InputStream in) throws IOException {
return create(in, null);
}

/**
* Creates a new WebResource from an InputStream.
*
* @param in InputStream for the resource, closed after inside the method.
* @param lastModified Epoch millisecond the resource was last modified
* @return WebResource.
* @throws IOException If the stream can not be read.
*/
static WebResource create(InputStream in, Long lastModified) throws IOException {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
int read;
byte[] bytes = new byte[1024];
while ((read = in.read(bytes)) != -1) {
out.write(bytes, 0, read);
}

return new ByteResource(out.toByteArray());
return new ByteResource(out.toByteArray(), lastModified);
} finally {
in.close();
}
}

/**
* Create a lazy WebResource that only reads contents if necessary.
*
* @param in Supplier for InputStream, a lazy method that reads input when necessary.
* @param lastModified Last modified date for the resource.
* @return WebResource.
*/
static WebResource create(Supplier<InputStream> in, Long lastModified) {
return new LazyWebResource(in, () -> {
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream input = in.get()) {
int read;
byte[] bytes = new byte[1024];
while ((read = input.read(bytes)) != -1) {
out.write(bytes, 0, read);
}

return out.toByteArray();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}, lastModified);
}

byte[] asBytes();

/**
Expand All @@ -85,11 +120,21 @@ static WebResource create(InputStream in) throws IOException {

InputStream asStream();

default Optional<Long> getLastModified() {
return Optional.empty();
}

final class ByteResource implements WebResource {
private final byte[] content;
private final Long lastModified;

public ByteResource(byte[] content) {
this(content, null);
}

public ByteResource(byte[] content, Long lastModified) {
this.content = content;
this.lastModified = lastModified;
}

@Override
Expand All @@ -106,5 +151,42 @@ public String asString() {
public InputStream asStream() {
return new ByteArrayInputStream(content);
}

@Override
public Optional<Long> getLastModified() {
return Optional.ofNullable(lastModified);
}
}

final class LazyWebResource implements WebResource {
private final Supplier<InputStream> inputStreamSupplier;
private final Supplier<byte[]> contentSupplier;
private final Long lastModified;

public LazyWebResource(Supplier<InputStream> inputStreamSupplier, Supplier<byte[]> contentSupplier, Long lastModified) {
this.inputStreamSupplier = inputStreamSupplier;
this.contentSupplier = contentSupplier;
this.lastModified = lastModified;
}

@Override
public byte[] asBytes() {
return contentSupplier.get();
}

@Override
public String asString() {
return new String(asBytes(), StandardCharsets.UTF_8);
}

@Override
public InputStream asStream() {
return inputStreamSupplier.get();
}

@Override
public Optional<Long> getLastModified() {
return Optional.ofNullable(lastModified);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ public String onRequest(OfflinePlayer player, @Untrusted String params) {
if ("Server thread".equalsIgnoreCase(Thread.currentThread().getName())) {
return getCached(params, uuid);
}
return getPlaceholderValue(params, uuid);

return Optional.ofNullable(getCached(params, uuid))
.orElseGet(() -> getPlaceholderValue(params, uuid));
} catch (IllegalStateException e) {
if ("zip file closed".equals(e.getMessage())) {
return null; // Plan is disabled.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.queries.containers.PlayerContainerQuery;
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
import com.djrapitops.plan.storage.database.queries.objects.SessionQueries;
import com.djrapitops.plan.utilities.comparators.DateHolderRecentComparator;
import com.djrapitops.plan.utilities.java.Lists;
import com.djrapitops.plan.utilities.java.Maps;
Expand Down Expand Up @@ -91,6 +92,10 @@ public PlayerJSONCreator(
this.graphs = graphs;
}

public long getLastSeen(UUID playerUUID) {
return dbSystem.getDatabase().query(SessionQueries.lastSeen(playerUUID));
}

public Map<String, Object> createJSONAsMap(UUID playerUUID) {
Database db = dbSystem.getDatabase();

Expand Down Expand Up @@ -226,6 +231,7 @@ private Map<String, Object> createInfoJSONMap(PlayerContainer player, Map<Server
info.put("best_ping", bestPing != -1.0 ? bestPing + " ms" : unavailable);
info.put("registered", player.getValue(PlayerKeys.REGISTERED).map(year).orElse("-"));
info.put("last_seen", player.getValue(PlayerKeys.LAST_SEEN).map(year).orElse("-"));
info.put("last_seen_raw_value", player.getValue(PlayerKeys.LAST_SEEN).orElse(0L));

return info;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.djrapitops.plan.delivery.rendering.pages;

import com.djrapitops.plan.delivery.formatting.PlaceholderReplacer;
import com.djrapitops.plan.delivery.web.resource.WebResource;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.settings.locale.Locale;
import com.djrapitops.plan.settings.theme.Theme;
Expand All @@ -30,15 +31,15 @@
*/
public class LoginPage implements Page {

private final String template;
private final WebResource template;
private final ServerInfo serverInfo;
private final Locale locale;
private final Theme theme;

private final VersionChecker versionChecker;

LoginPage(
String htmlTemplate,
WebResource htmlTemplate,
ServerInfo serverInfo,
Locale locale,
Theme theme,
Expand All @@ -51,12 +52,17 @@ public class LoginPage implements Page {
this.versionChecker = versionChecker;
}

@Override
public long lastModified() {
return template.getLastModified().orElseGet(System::currentTimeMillis);
}

@Override
public String toHtml() {
PlaceholderReplacer placeholders = new PlaceholderReplacer();
placeholders.put("command", getCommand());
placeholders.put("version", versionChecker.getCurrentVersion());
return UnaryChain.of(template)
return UnaryChain.of(template.asString())
.chain(theme::replaceThemeColors)
.chain(placeholders::apply)
.chain(locale::replaceLanguageInHtml)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@
*/
public interface Page {
String toHtml();

default long lastModified() {
return System.currentTimeMillis();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.djrapitops.plan.delivery.rendering.html.icon.Icon;
import com.djrapitops.plan.delivery.web.ResourceService;
import com.djrapitops.plan.delivery.web.resolver.exception.NotFoundException;
import com.djrapitops.plan.delivery.web.resource.WebResource;
import com.djrapitops.plan.delivery.webserver.Addresses;
import com.djrapitops.plan.delivery.webserver.cache.JSONStorage;
import com.djrapitops.plan.extension.implementation.results.ExtensionData;
Expand All @@ -41,7 +42,6 @@
import com.djrapitops.plan.utilities.dev.Untrusted;
import com.djrapitops.plan.version.VersionChecker;
import dagger.Lazy;
import org.apache.commons.lang3.StringUtils;

import javax.inject.Inject;
import javax.inject.Singleton;
Expand Down Expand Up @@ -101,15 +101,12 @@ public Page playersPage() throws IOException {
return reactPage();
}

return new PlayersPage(getResource("players.html"), versionChecker.get(),
return new PlayersPage(getResourceAsString("players.html"), versionChecker.get(),
config.get(), theme.get(), serverInfo.get());
}

public Page reactPage() throws IOException {
String reactHtml = StringUtils.replace(
getResource("index.html"),
"/static", getBasePath() + "/static");
return () -> reactHtml;
return new ReactPage(getBasePath(), getResource("index.html"));
}

private String getBasePath() {
Expand All @@ -135,7 +132,7 @@ public Page serverPage(ServerUUID serverUUID) throws IOException {
}

return new ServerPage(
getResource("server.html"),
getResourceAsString("server.html"),
server,
config.get(),
theme.get(),
Expand All @@ -158,7 +155,7 @@ public Page playerPage(UUID playerUUID) throws IOException {
}

return new PlayerPage(
getResource("player.html"), player,
getResourceAsString("player.html"), player,
versionChecker.get(),
config.get(),
this,
Expand Down Expand Up @@ -207,7 +204,7 @@ public Page networkPage() throws IOException {
return reactPage();
}

return new NetworkPage(getResource("network.html"),
return new NetworkPage(getResourceAsString("network.html"),
dbSystem.get(),
versionChecker.get(),
config.get(),
Expand All @@ -223,7 +220,7 @@ public Page networkPage() throws IOException {
public Page internalErrorPage(String message, @Untrusted Throwable error) {
try {
return new InternalErrorPage(
getResource("error.html"), message, error,
getResourceAsString("error.html"), message, error,
versionChecker.get());
} catch (IOException noParse) {
return () -> "Error occurred: " + error.toString() +
Expand All @@ -234,20 +231,24 @@ public Page internalErrorPage(String message, @Untrusted Throwable error) {

public Page errorPage(String title, String error) throws IOException {
return new ErrorMessagePage(
getResource("error.html"), title, error,
getResourceAsString("error.html"), title, error,
versionChecker.get(), theme.get());
}

public Page errorPage(Icon icon, String title, String error) throws IOException {
return new ErrorMessagePage(
getResource("error.html"), icon, title, error, theme.get(), versionChecker.get());
getResourceAsString("error.html"), icon, title, error, theme.get(), versionChecker.get());
}

public String getResource(String name) throws IOException {
public String getResourceAsString(String name) throws IOException {
return getResource(name).asString();
}

public WebResource getResource(String name) throws IOException {
try {
return ResourceService.getInstance().getResource("Plan", name,
() -> files.get().getResourceFromJar("web/" + name).asWebResource()
).asString();
);
} catch (UncheckedIOException readFail) {
throw readFail.getCause();
}
Expand All @@ -274,7 +275,7 @@ public Page queryPage() throws IOException {
return reactPage();
}
return new QueryPage(
getResource("query.html"),
getResourceAsString("query.html"),
locale.get(), theme.get(), versionChecker.get()
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.delivery.rendering.pages;

import com.djrapitops.plan.delivery.web.resource.WebResource;
import org.apache.commons.lang3.StringUtils;

/**
* Represents React index.html.
*
* @author AuroraLS3
*/
public class ReactPage implements Page {

private final String basePath;
private final WebResource reactHtml;

public ReactPage(String basePath, WebResource reactHtml) {
this.basePath = basePath;
this.reactHtml = reactHtml;
}

@Override
public String toHtml() {
return StringUtils.replace(
reactHtml.asString(),
"/static", basePath + "/static");
}

@Override
public long lastModified() {
return reactHtml.getLastModified().orElseGet(System::currentTimeMillis);
}
}
Loading

0 comments on commit 88b4191

Please sign in to comment.