mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-02-08 00:11:20 +01:00
Implement http caching (#2840)
* 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
This commit is contained in:
parent
0ddda27384
commit
88b4191f6b
@ -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.
|
||||
@ -61,6 +60,18 @@ public interface WebResource {
|
||||
* @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];
|
||||
@ -68,12 +79,36 @@ public interface WebResource {
|
||||
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();
|
||||
|
||||
/**
|
||||
@ -85,11 +120,21 @@ public interface WebResource {
|
||||
|
||||
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
|
||||
@ -106,5 +151,42 @@ public interface WebResource {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -96,7 +96,9 @@ public class PlanPlaceholderExtension extends PlaceholderExpansion {
|
||||
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.
|
||||
|
@ -46,6 +46,7 @@ import com.djrapitops.plan.storage.database.DBSystem;
|
||||
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;
|
||||
@ -91,6 +92,10 @@ public class 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();
|
||||
|
||||
@ -226,6 +231,7 @@ public class PlayerJSONCreator {
|
||||
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;
|
||||
}
|
||||
|
@ -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;
|
||||
@ -30,7 +31,7 @@ import com.djrapitops.plan.version.VersionChecker;
|
||||
*/
|
||||
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;
|
||||
@ -38,7 +39,7 @@ public class LoginPage implements Page {
|
||||
private final VersionChecker versionChecker;
|
||||
|
||||
LoginPage(
|
||||
String htmlTemplate,
|
||||
WebResource htmlTemplate,
|
||||
ServerInfo serverInfo,
|
||||
Locale locale,
|
||||
Theme theme,
|
||||
@ -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)
|
||||
|
@ -23,4 +23,8 @@ package com.djrapitops.plan.delivery.rendering.pages;
|
||||
*/
|
||||
public interface Page {
|
||||
String toHtml();
|
||||
|
||||
default long lastModified() {
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
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;
|
||||
@ -41,7 +42,6 @@ import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
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;
|
||||
@ -101,15 +101,12 @@ public class PageFactory {
|
||||
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() {
|
||||
@ -135,7 +132,7 @@ public class PageFactory {
|
||||
}
|
||||
|
||||
return new ServerPage(
|
||||
getResource("server.html"),
|
||||
getResourceAsString("server.html"),
|
||||
server,
|
||||
config.get(),
|
||||
theme.get(),
|
||||
@ -158,7 +155,7 @@ public class PageFactory {
|
||||
}
|
||||
|
||||
return new PlayerPage(
|
||||
getResource("player.html"), player,
|
||||
getResourceAsString("player.html"), player,
|
||||
versionChecker.get(),
|
||||
config.get(),
|
||||
this,
|
||||
@ -207,7 +204,7 @@ public class PageFactory {
|
||||
return reactPage();
|
||||
}
|
||||
|
||||
return new NetworkPage(getResource("network.html"),
|
||||
return new NetworkPage(getResourceAsString("network.html"),
|
||||
dbSystem.get(),
|
||||
versionChecker.get(),
|
||||
config.get(),
|
||||
@ -223,7 +220,7 @@ public class PageFactory {
|
||||
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() +
|
||||
@ -234,20 +231,24 @@ public class PageFactory {
|
||||
|
||||
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();
|
||||
}
|
||||
@ -274,7 +275,7 @@ public class PageFactory {
|
||||
return reactPage();
|
||||
}
|
||||
return new QueryPage(
|
||||
getResource("query.html"),
|
||||
getResourceAsString("query.html"),
|
||||
locale.get(), theme.get(), versionChecker.get()
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.webserver;
|
||||
|
||||
/**
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class CacheStrategy {
|
||||
|
||||
public static final String CACHE_IN_BROWSER = "max-age: 2592000";
|
||||
public static final String CHECK_ETAG = "no-cache";
|
||||
public static final String CHECK_ETAG_USER_SPECIFIC = "no-cache, private";
|
||||
|
||||
private CacheStrategy() {
|
||||
// Static variable class
|
||||
}
|
||||
|
||||
}
|
@ -17,6 +17,8 @@
|
||||
package com.djrapitops.plan.delivery.webserver;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.container.PlayerContainer;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.rendering.html.icon.Family;
|
||||
import com.djrapitops.plan.delivery.rendering.html.icon.Icon;
|
||||
import com.djrapitops.plan.delivery.rendering.pages.Page;
|
||||
@ -24,10 +26,13 @@ import com.djrapitops.plan.delivery.rendering.pages.PageFactory;
|
||||
import com.djrapitops.plan.delivery.web.ResourceService;
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.ResponseBuilder;
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.NotFoundException;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resource.WebResource;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.FailReason;
|
||||
import com.djrapitops.plan.exceptions.WebUserAuthException;
|
||||
import com.djrapitops.plan.identification.Identifiers;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
import com.djrapitops.plan.settings.locale.lang.ErrorPageLang;
|
||||
@ -42,6 +47,7 @@ import com.djrapitops.plan.utilities.java.UnaryChain;
|
||||
import dagger.Lazy;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@ -51,6 +57,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Factory for creating different {@link Response} objects.
|
||||
@ -66,6 +73,7 @@ public class ResponseFactory {
|
||||
private final DBSystem dbSystem;
|
||||
private final Theme theme;
|
||||
private final Lazy<Addresses> addresses;
|
||||
private final Formatter<Long> httpLastModifiedFormatter;
|
||||
|
||||
@Inject
|
||||
public ResponseFactory(
|
||||
@ -73,6 +81,7 @@ public class ResponseFactory {
|
||||
PageFactory pageFactory,
|
||||
Locale locale,
|
||||
DBSystem dbSystem,
|
||||
Formatters formatters,
|
||||
Theme theme,
|
||||
Lazy<Addresses> addresses
|
||||
) {
|
||||
@ -82,6 +91,8 @@ public class ResponseFactory {
|
||||
this.dbSystem = dbSystem;
|
||||
this.theme = theme;
|
||||
this.addresses = addresses;
|
||||
|
||||
httpLastModifiedFormatter = formatters.httpLastModifiedLong();
|
||||
}
|
||||
|
||||
public WebResource getResource(@Untrusted String resourceName) {
|
||||
@ -89,10 +100,27 @@ public class ResponseFactory {
|
||||
() -> files.getResourceFromJar("web/" + resourceName).asWebResource());
|
||||
}
|
||||
|
||||
private Response forPage(Page page) {
|
||||
private static Response browserCachedNotChangedResponse() {
|
||||
return Response.builder()
|
||||
.setStatus(304)
|
||||
.setContent(new byte[0])
|
||||
.build();
|
||||
}
|
||||
|
||||
private Response forPage(@Untrusted Request request, Page page) {
|
||||
long modified = page.lastModified();
|
||||
Optional<Long> etag = Identifiers.getEtag(request);
|
||||
|
||||
if (etag.isPresent() && modified == etag.get()) {
|
||||
return browserCachedNotChangedResponse();
|
||||
}
|
||||
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.HTML)
|
||||
.setContent(page.toHtml())
|
||||
.setHeader(HttpHeader.CACHE_CONTROL.asString(), CacheStrategy.CHECK_ETAG)
|
||||
.setHeader(HttpHeader.LAST_MODIFIED.asString(), httpLastModifiedFormatter.apply(modified))
|
||||
.setHeader(HttpHeader.ETAG.asString(), modified)
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -104,11 +132,11 @@ public class ResponseFactory {
|
||||
.build();
|
||||
}
|
||||
|
||||
public Response playersPageResponse() {
|
||||
public Response playersPageResponse(@Untrusted Request request) {
|
||||
try {
|
||||
Optional<Response> error = checkDbClosedError();
|
||||
if (error.isPresent()) return error.get();
|
||||
return forPage(pageFactory.playersPage());
|
||||
return forPage(request, pageFactory.playersPage());
|
||||
} catch (IOException e) {
|
||||
return forInternalError(e, "Failed to generate players page");
|
||||
}
|
||||
@ -137,25 +165,35 @@ public class ResponseFactory {
|
||||
.build();
|
||||
}
|
||||
|
||||
private Response getCachedOrNew(long modified, String fileName, Function<String, Response> newResponseFunction) {
|
||||
WebResource resource = getResource(fileName);
|
||||
Optional<Long> lastModified = resource.getLastModified();
|
||||
if (lastModified.isPresent() && modified == lastModified.get()) {
|
||||
return browserCachedNotChangedResponse();
|
||||
} else {
|
||||
return newResponseFunction.apply(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
public Response internalErrorResponse(Throwable e, String cause) {
|
||||
return forInternalError(e, cause);
|
||||
}
|
||||
|
||||
public Response networkPageResponse() {
|
||||
public Response networkPageResponse(@Untrusted Request request) {
|
||||
Optional<Response> error = checkDbClosedError();
|
||||
if (error.isPresent()) return error.get();
|
||||
try {
|
||||
return forPage(pageFactory.networkPage());
|
||||
return forPage(request, pageFactory.networkPage());
|
||||
} catch (IOException e) {
|
||||
return forInternalError(e, "Failed to generate network page");
|
||||
}
|
||||
}
|
||||
|
||||
public Response serverPageResponse(ServerUUID serverUUID) {
|
||||
public Response serverPageResponse(@Untrusted Request request, ServerUUID serverUUID) {
|
||||
Optional<Response> error = checkDbClosedError();
|
||||
if (error.isPresent()) return error.get();
|
||||
try {
|
||||
return forPage(pageFactory.serverPage(serverUUID));
|
||||
return forPage(request, pageFactory.serverPage(serverUUID));
|
||||
} catch (NotFoundException e) {
|
||||
return notFound404(e.getMessage());
|
||||
} catch (IOException e) {
|
||||
@ -171,23 +209,36 @@ public class ResponseFactory {
|
||||
.build();
|
||||
}
|
||||
|
||||
public Response javaScriptResponse(long modified, @Untrusted String fileName) {
|
||||
return getCachedOrNew(modified, fileName, this::javaScriptResponse);
|
||||
}
|
||||
|
||||
public Response javaScriptResponse(@Untrusted String fileName) {
|
||||
try {
|
||||
String content = UnaryChain.of(getResource(fileName).asString())
|
||||
WebResource resource = getResource(fileName);
|
||||
String content = UnaryChain.of(resource.asString())
|
||||
.chain(this::replaceMainAddressPlaceholder)
|
||||
.chain(theme::replaceThemeColors)
|
||||
.chain(resource -> {
|
||||
if (fileName.startsWith("vendor/") || fileName.startsWith("/vendor/")) {return resource;}
|
||||
return locale.replaceLanguageInJavascript(resource);
|
||||
.chain(contents -> {
|
||||
if (fileName.startsWith("vendor/") || fileName.startsWith("/vendor/")) {return contents;}
|
||||
return locale.replaceLanguageInJavascript(contents);
|
||||
})
|
||||
.chain(resource -> StringUtils.replace(resource, "n.p=\"/\"",
|
||||
.chain(contents -> StringUtils.replace(contents, "n.p=\"/\"",
|
||||
"n.p=\"" + getBasePath() + "/\""))
|
||||
.apply();
|
||||
return Response.builder()
|
||||
ResponseBuilder responseBuilder = Response.builder()
|
||||
.setMimeType(MimeType.JS)
|
||||
.setContent(content)
|
||||
.setStatus(200)
|
||||
.build();
|
||||
.setStatus(200);
|
||||
|
||||
if (fileName.contains("static")) {
|
||||
resource.getLastModified().ifPresent(lastModified -> responseBuilder
|
||||
// Can't cache main bundle in browser since base path might change
|
||||
.setHeader(HttpHeader.CACHE_CONTROL.asString(), fileName.contains("main") ? CacheStrategy.CHECK_ETAG : CacheStrategy.CACHE_IN_BROWSER)
|
||||
.setHeader(HttpHeader.LAST_MODIFIED.asString(), httpLastModifiedFormatter.apply(lastModified))
|
||||
.setHeader(HttpHeader.ETAG.asString(), lastModified));
|
||||
}
|
||||
return responseBuilder.build();
|
||||
} catch (UncheckedIOException e) {
|
||||
return notFound404("Javascript File not found");
|
||||
}
|
||||
@ -205,34 +256,64 @@ public class ResponseFactory {
|
||||
return StringUtils.replace(resource, "PLAN_BASE_ADDRESS", address);
|
||||
}
|
||||
|
||||
public Response cssResponse(long modified, @Untrusted String fileName) {
|
||||
return getCachedOrNew(modified, fileName, this::cssResponse);
|
||||
}
|
||||
|
||||
public Response cssResponse(@Untrusted String fileName) {
|
||||
try {
|
||||
String content = UnaryChain.of(getResource(fileName).asString())
|
||||
WebResource resource = getResource(fileName);
|
||||
String content = UnaryChain.of(resource.asString())
|
||||
.chain(theme::replaceThemeColors)
|
||||
.chain(resource -> StringUtils.replace(resource, "/static", getBasePath() + "/static"))
|
||||
.chain(contents -> StringUtils.replace(contents, "/static", getBasePath() + "/static"))
|
||||
.apply();
|
||||
return Response.builder()
|
||||
|
||||
ResponseBuilder responseBuilder = Response.builder()
|
||||
.setMimeType(MimeType.CSS)
|
||||
.setContent(content)
|
||||
.setStatus(200)
|
||||
.build();
|
||||
.setStatus(200);
|
||||
|
||||
if (fileName.contains("static")) {
|
||||
resource.getLastModified().ifPresent(lastModified -> responseBuilder
|
||||
// Can't cache css bundles in browser since base path might change
|
||||
.setHeader(HttpHeader.CACHE_CONTROL.asString(), CacheStrategy.CHECK_ETAG)
|
||||
.setHeader(HttpHeader.LAST_MODIFIED.asString(), httpLastModifiedFormatter.apply(lastModified))
|
||||
.setHeader(HttpHeader.ETAG.asString(), lastModified));
|
||||
}
|
||||
return responseBuilder.build();
|
||||
} catch (UncheckedIOException e) {
|
||||
return notFound404("CSS File not found");
|
||||
}
|
||||
}
|
||||
|
||||
public Response imageResponse(long modified, @Untrusted String fileName) {
|
||||
return getCachedOrNew(modified, fileName, this::imageResponse);
|
||||
}
|
||||
|
||||
public Response imageResponse(@Untrusted String fileName) {
|
||||
try {
|
||||
return Response.builder()
|
||||
WebResource resource = getResource(fileName);
|
||||
ResponseBuilder responseBuilder = Response.builder()
|
||||
.setMimeType(MimeType.IMAGE)
|
||||
.setContent(getResource(fileName))
|
||||
.setStatus(200)
|
||||
.build();
|
||||
.setContent(resource)
|
||||
.setStatus(200);
|
||||
|
||||
if (fileName.contains("static")) {
|
||||
resource.getLastModified().ifPresent(lastModified -> responseBuilder
|
||||
.setHeader(HttpHeader.CACHE_CONTROL.asString(), CacheStrategy.CACHE_IN_BROWSER)
|
||||
.setHeader(HttpHeader.LAST_MODIFIED.asString(), httpLastModifiedFormatter.apply(lastModified))
|
||||
.setHeader(HttpHeader.ETAG.asString(), lastModified));
|
||||
}
|
||||
return responseBuilder.build();
|
||||
} catch (UncheckedIOException e) {
|
||||
return notFound404("Image File not found");
|
||||
}
|
||||
}
|
||||
|
||||
public Response fontResponse(long modified, @Untrusted String fileName) {
|
||||
return getCachedOrNew(modified, fileName, this::fontResponse);
|
||||
}
|
||||
|
||||
public Response fontResponse(@Untrusted String fileName) {
|
||||
String type;
|
||||
if (fileName.endsWith(".woff")) {
|
||||
@ -247,10 +328,18 @@ public class ResponseFactory {
|
||||
type = MimeType.FONT_BYTESTREAM;
|
||||
}
|
||||
try {
|
||||
return Response.builder()
|
||||
WebResource resource = getResource(fileName);
|
||||
ResponseBuilder responseBuilder = Response.builder()
|
||||
.setMimeType(type)
|
||||
.setContent(getResource(fileName))
|
||||
.build();
|
||||
.setContent(resource);
|
||||
|
||||
if (fileName.contains("static")) {
|
||||
resource.getLastModified().ifPresent(lastModified -> responseBuilder
|
||||
.setHeader(HttpHeader.CACHE_CONTROL.asString(), CacheStrategy.CACHE_IN_BROWSER)
|
||||
.setHeader(HttpHeader.LAST_MODIFIED.asString(), httpLastModifiedFormatter.apply(lastModified))
|
||||
.setHeader(HttpHeader.ETAG.asString(), lastModified));
|
||||
}
|
||||
return responseBuilder.build();
|
||||
} catch (UncheckedIOException e) {
|
||||
return notFound404("Font File not found");
|
||||
}
|
||||
@ -273,9 +362,14 @@ public class ResponseFactory {
|
||||
|
||||
public Response robotsResponse() {
|
||||
try {
|
||||
WebResource resource = getResource("robots.txt");
|
||||
Long lastModified = resource.getLastModified().orElseGet(System::currentTimeMillis);
|
||||
return Response.builder()
|
||||
.setMimeType("text/plain")
|
||||
.setContent(getResource("robots.txt"))
|
||||
.setContent(resource)
|
||||
.setHeader(HttpHeader.CACHE_CONTROL.asString(), CacheStrategy.CACHE_IN_BROWSER)
|
||||
.setHeader(HttpHeader.LAST_MODIFIED.asString(), httpLastModifiedFormatter.apply(lastModified))
|
||||
.setHeader(HttpHeader.ETAG.asString(), lastModified)
|
||||
.build();
|
||||
} catch (UncheckedIOException e) {
|
||||
return forInternalError(e, "Could not read robots.txt");
|
||||
@ -415,9 +509,9 @@ public class ResponseFactory {
|
||||
.build();
|
||||
}
|
||||
|
||||
public Response playerPageResponse(UUID playerUUID) {
|
||||
public Response playerPageResponse(@Untrusted Request request, UUID playerUUID) {
|
||||
try {
|
||||
return forPage(pageFactory.playerPage(playerUUID));
|
||||
return forPage(request, pageFactory.playerPage(playerUUID));
|
||||
} catch (IllegalStateException e) {
|
||||
return playerNotFound404();
|
||||
} catch (IOException e) {
|
||||
@ -425,33 +519,33 @@ public class ResponseFactory {
|
||||
}
|
||||
}
|
||||
|
||||
public Response loginPageResponse() {
|
||||
public Response loginPageResponse(@Untrusted Request request) {
|
||||
try {
|
||||
return forPage(pageFactory.loginPage());
|
||||
return forPage(request, pageFactory.loginPage());
|
||||
} catch (IOException e) {
|
||||
return forInternalError(e, "Failed to generate player page");
|
||||
}
|
||||
}
|
||||
|
||||
public Response registerPageResponse() {
|
||||
public Response registerPageResponse(@Untrusted Request request) {
|
||||
try {
|
||||
return forPage(pageFactory.registerPage());
|
||||
return forPage(request, pageFactory.registerPage());
|
||||
} catch (IOException e) {
|
||||
return forInternalError(e, "Failed to generate player page");
|
||||
}
|
||||
}
|
||||
|
||||
public Response queryPageResponse() {
|
||||
public Response queryPageResponse(@Untrusted Request request) {
|
||||
try {
|
||||
return forPage(pageFactory.queryPage());
|
||||
return forPage(request, pageFactory.queryPage());
|
||||
} catch (IOException e) {
|
||||
return forInternalError(e, "Failed to generate query page");
|
||||
}
|
||||
}
|
||||
|
||||
public Response errorsPageResponse() {
|
||||
public Response errorsPageResponse(@Untrusted Request request) {
|
||||
try {
|
||||
return forPage(pageFactory.errorsPage());
|
||||
return forPage(request, pageFactory.errorsPage());
|
||||
} catch (IOException e) {
|
||||
return forInternalError(e, "Failed to generate errors page");
|
||||
}
|
||||
@ -468,12 +562,9 @@ public class ResponseFactory {
|
||||
}
|
||||
}
|
||||
|
||||
public Response reactPageResponse() {
|
||||
public Response reactPageResponse(Request request) {
|
||||
try {
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.HTML)
|
||||
.setContent(pageFactory.reactPage().toHtml())
|
||||
.build();
|
||||
return forPage(request, pageFactory.reactPage());
|
||||
} catch (UncheckedIOException | IOException e) {
|
||||
return forInternalError(e, "Could not read index.html");
|
||||
}
|
||||
|
@ -16,6 +16,8 @@
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver.cache;
|
||||
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.processing.Processing;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
@ -46,10 +48,12 @@ public class AsyncJSONResolverService {
|
||||
private final Map<String, Future<JSONStorage.StoredJSON>> currentlyProcessing;
|
||||
private final Map<String, Long> previousUpdates;
|
||||
private final ReentrantLock accessLock; // Access lock prevents double processing same resource
|
||||
private final Formatter<Long> httpLastModifiedFormatter;
|
||||
|
||||
@Inject
|
||||
public AsyncJSONResolverService(
|
||||
PlanConfig config,
|
||||
Formatters formatters,
|
||||
Processing processing,
|
||||
JSONStorage jsonStorage
|
||||
) {
|
||||
@ -60,6 +64,8 @@ public class AsyncJSONResolverService {
|
||||
currentlyProcessing = new ConcurrentHashMap<>();
|
||||
previousUpdates = new ConcurrentHashMap<>();
|
||||
accessLock = new ReentrantLock();
|
||||
|
||||
httpLastModifiedFormatter = formatters.httpLastModifiedLong();
|
||||
}
|
||||
|
||||
public <T> JSONStorage.StoredJSON resolve(
|
||||
@ -155,4 +161,8 @@ public class AsyncJSONResolverService {
|
||||
return created;
|
||||
});
|
||||
}
|
||||
|
||||
public Formatter<Long> getHttpLastModifiedFormatter() {
|
||||
return httpLastModifiedFormatter;
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ public enum DataID {
|
||||
JOIN_ADDRESSES_BY_DAY;
|
||||
|
||||
public String of(ServerUUID serverUUID) {
|
||||
if (serverUUID == null) return name();
|
||||
return name() + '-' + serverUUID;
|
||||
}
|
||||
}
|
||||
|
@ -129,9 +129,11 @@ public class JSONFileStorage implements JSONStorage {
|
||||
public Optional<StoredJSON> fetchJSON(String identifier) {
|
||||
File[] stored = jsonDirectory.toFile().listFiles();
|
||||
if (stored == null) return Optional.empty();
|
||||
|
||||
String lookForStart = identifier + '-';
|
||||
for (File file : stored) {
|
||||
String fileName = file.getName();
|
||||
if (fileName.endsWith(JSON_FILE_EXTENSION) && fileName.startsWith(identifier + '-')) {
|
||||
if (fileName.endsWith(JSON_FILE_EXTENSION) && fileName.startsWith(lookForStart)) {
|
||||
return Optional.ofNullable(readStoredJSON(file));
|
||||
}
|
||||
}
|
||||
@ -179,10 +181,12 @@ public class JSONFileStorage implements JSONStorage {
|
||||
private Optional<StoredJSON> fetchJSONWithTimestamp(String identifier, long timestamp, BiPredicate<Matcher, Long> timestampComparator) {
|
||||
File[] stored = jsonDirectory.toFile().listFiles();
|
||||
if (stored == null) return Optional.empty();
|
||||
|
||||
String lookForStart = identifier + '-';
|
||||
for (File file : stored) {
|
||||
try {
|
||||
String fileName = file.getName();
|
||||
if (fileName.endsWith(JSON_FILE_EXTENSION) && fileName.startsWith(identifier + '-')) {
|
||||
if (fileName.endsWith(JSON_FILE_EXTENSION) && fileName.startsWith(lookForStart)) {
|
||||
Matcher timestampMatch = timestampRegex.matcher(fileName);
|
||||
if (timestampMatch.find() && timestampComparator.test(timestampMatch, timestamp)) {
|
||||
return Optional.ofNullable(readStoredJSON(file));
|
||||
@ -270,6 +274,28 @@ public class JSONFileStorage implements JSONStorage {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Long> getTimestamp(String identifier) {
|
||||
File[] stored = jsonDirectory.toFile().listFiles();
|
||||
if (stored == null) return Optional.empty();
|
||||
|
||||
String lookForStart = identifier + '-';
|
||||
for (File file : stored) {
|
||||
try {
|
||||
String fileName = file.getName();
|
||||
if (fileName.endsWith(JSON_FILE_EXTENSION) && fileName.startsWith(lookForStart)) {
|
||||
Matcher timestampMatch = timestampRegex.matcher(fileName);
|
||||
if (timestampMatch.find()) {
|
||||
return Optional.of(Long.parseLong(timestampMatch.group(1)));
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
// Ignore this file, malformed timestamp
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Singleton
|
||||
public static class CleanTask extends TaskSystem.Task {
|
||||
private final PlanConfig config;
|
||||
|
@ -120,6 +120,16 @@ public class JSONMemoryStorageShim implements JSONStorage {
|
||||
underlyingStorage.invalidateOlder(identifier, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Long> getTimestamp(String identifier) {
|
||||
for (TimestampedIdentifier key : getCache().asMap().keySet()) {
|
||||
if (key.identifier.equalsIgnoreCase(identifier)) {
|
||||
return Optional.of(key.timestamp);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
static class TimestampedIdentifier {
|
||||
private final String identifier;
|
||||
private final long timestamp;
|
||||
|
@ -63,6 +63,8 @@ public interface JSONStorage extends SubSystem {
|
||||
|
||||
void invalidateOlder(String identifier, long timestamp);
|
||||
|
||||
Optional<Long> getTimestamp(String identifier);
|
||||
|
||||
final class StoredJSON {
|
||||
public final String json;
|
||||
public final long timestamp;
|
||||
@ -72,6 +74,14 @@ public interface JSONStorage extends SubSystem {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getJson() {
|
||||
return json;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
@ -46,7 +46,7 @@ public class JettyResponseSender {
|
||||
}
|
||||
|
||||
public void send() throws IOException {
|
||||
if ("HEAD".equals(servletRequest.getMethod()) || response.getCode() == 204) {
|
||||
if ("HEAD".equals(servletRequest.getMethod()) || response.getCode() == 204 || response.getCode() == 304) {
|
||||
setResponseHeaders();
|
||||
sendHeadResponse();
|
||||
} else if (canGzip()) {
|
||||
@ -117,7 +117,12 @@ public class JettyResponseSender {
|
||||
|
||||
private void beginSend() {
|
||||
String length = response.getHeaders().get(HttpHeader.CONTENT_LENGTH.asString());
|
||||
if (length == null || "0".equals(length) || response.getCode() == 204 || "HEAD".equals(servletRequest.getMethod())) {
|
||||
if (length == null
|
||||
|| "0".equals(length)
|
||||
|| response.getCode() == 204
|
||||
|| response.getCode() == 304
|
||||
|| "HEAD".equals(servletRequest.getMethod())
|
||||
) {
|
||||
servletResponse.setHeader(HttpHeader.CONTENT_LENGTH.asString(), null);
|
||||
}
|
||||
// Return a content length of -1 for HTTP code 204 (No content)
|
||||
|
@ -40,7 +40,7 @@ public class ErrorsPageResolver implements Resolver {
|
||||
|
||||
@Override
|
||||
public Optional<Response> resolve(Request request) {
|
||||
return Optional.of(responseFactory.errorsPageResponse());
|
||||
return Optional.of(responseFactory.errorsPageResponse(request));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -82,10 +82,11 @@ public class PlayerPageResolver implements Resolver {
|
||||
return Optional.empty();
|
||||
}
|
||||
return path.getPart(1)
|
||||
.map(playerName -> getResponse(request.getPath(), playerName));
|
||||
.map(playerName -> getResponse(request, playerName));
|
||||
}
|
||||
|
||||
private Response getResponse(@Untrusted URIPath path, @Untrusted String playerName) {
|
||||
private Response getResponse(@Untrusted Request request, @Untrusted String playerName) {
|
||||
@Untrusted URIPath path = request.getPath();
|
||||
UUID playerUUID = uuidUtility.getUUIDOf(playerName);
|
||||
if (playerUUID == null) return responseFactory.uuidNotFound404();
|
||||
|
||||
@ -98,6 +99,6 @@ public class PlayerPageResolver implements Resolver {
|
||||
// Redirect /player/{uuid/name}/ to /player/{uuid}
|
||||
return responseFactory.redirectResponse("../" + Html.encodeToURL(playerUUID.toString()));
|
||||
}
|
||||
return responseFactory.playerPageResponse(playerUUID);
|
||||
return responseFactory.playerPageResponse(request, playerUUID);
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,6 @@ public class PlayersPageResolver implements Resolver {
|
||||
public Optional<Response> resolve(Request request) {
|
||||
// Redirect /players/ to /players
|
||||
if (request.getPath().getPart(1).isPresent()) return Optional.of(responseFactory.redirectResponse("/players"));
|
||||
return Optional.of(responseFactory.playersPageResponse());
|
||||
return Optional.of(responseFactory.playersPageResponse(request));
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,6 @@ public class QueryPageResolver implements Resolver {
|
||||
|
||||
@Override
|
||||
public Optional<Response> resolve(Request request) {
|
||||
return Optional.of(responseFactory.queryPageResponse());
|
||||
return Optional.of(responseFactory.queryPageResponse(request));
|
||||
}
|
||||
}
|
||||
|
@ -80,17 +80,17 @@ public class ServerPageResolver implements Resolver {
|
||||
return Optional.of(responseFactory.redirectResponse(directTo));
|
||||
}
|
||||
|
||||
private Optional<Response> getServerPage(ServerUUID serverUUID, Request request) {
|
||||
private Optional<Response> getServerPage(ServerUUID serverUUID, @Untrusted Request request) {
|
||||
boolean toNetworkPage = serverInfo.getServer().isProxy() && serverInfo.getServerUUID().equals(serverUUID);
|
||||
if (toNetworkPage) {
|
||||
if (request.getPath().getPart(0).map("network"::equals).orElse(false)) {
|
||||
return Optional.of(responseFactory.networkPageResponse());
|
||||
return Optional.of(responseFactory.networkPageResponse(request));
|
||||
} else {
|
||||
// Accessing /server/Server <Bungee ID> which should be redirected to /network
|
||||
return redirectToCurrentServer();
|
||||
}
|
||||
}
|
||||
return Optional.of(responseFactory.serverPageResponse(serverUUID));
|
||||
return Optional.of(responseFactory.serverPageResponse(request, serverUUID));
|
||||
}
|
||||
|
||||
private Optional<ServerUUID> getServerUUID(@Untrusted URIPath path) {
|
||||
|
@ -21,6 +21,7 @@ import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.URIPath;
|
||||
import com.djrapitops.plan.delivery.webserver.ResponseFactory;
|
||||
import com.djrapitops.plan.identification.Identifiers;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@ -53,17 +54,22 @@ public class StaticResourceResolver implements NoAuthResolver {
|
||||
|
||||
private Response getResponse(Request request) {
|
||||
@Untrusted String resource = getPath(request).asString().substring(1);
|
||||
@Untrusted Optional<Long> etag = Identifiers.getEtag(request);
|
||||
if (resource.endsWith(".css")) {
|
||||
return responseFactory.cssResponse(resource);
|
||||
return etag.map(tag -> responseFactory.cssResponse(tag, resource))
|
||||
.orElseGet(() -> responseFactory.cssResponse(resource));
|
||||
}
|
||||
if (resource.endsWith(".js")) {
|
||||
return responseFactory.javaScriptResponse(resource);
|
||||
return etag.map(tag -> responseFactory.javaScriptResponse(tag, resource))
|
||||
.orElseGet(() -> responseFactory.javaScriptResponse(resource));
|
||||
}
|
||||
if (resource.endsWith(".png")) {
|
||||
return responseFactory.imageResponse(resource);
|
||||
return etag.map(tag -> responseFactory.imageResponse(tag, resource))
|
||||
.orElseGet(() -> responseFactory.imageResponse(resource));
|
||||
}
|
||||
if (StringUtils.endsWithAny(resource, ".woff", ".woff2", ".eot", ".ttf")) {
|
||||
return responseFactory.fontResponse(resource);
|
||||
return etag.map(tag -> responseFactory.fontResponse(tag, resource))
|
||||
.orElseGet(() -> responseFactory.fontResponse(resource));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -52,6 +52,6 @@ public class LoginPageResolver implements NoAuthResolver {
|
||||
.filter(redirectBackTo -> !redirectBackTo.startsWith("http"));
|
||||
return Optional.of(responseFactory.redirectResponse(from.orElse("/")));
|
||||
}
|
||||
return Optional.of(responseFactory.loginPageResponse());
|
||||
return Optional.of(responseFactory.loginPageResponse(request));
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,6 @@ public class RegisterPageResolver implements NoAuthResolver {
|
||||
if (user.isPresent() || !webServer.get().isAuthRequired()) {
|
||||
return Optional.of(responseFactory.redirectResponse("/"));
|
||||
}
|
||||
return Optional.of(responseFactory.registerPageResponse());
|
||||
return Optional.of(responseFactory.registerPageResponse(request));
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,8 @@
|
||||
package com.djrapitops.plan.delivery.webserver.resolver.json;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.extension.ExtensionDataDto;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Resolver;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.NotFoundException;
|
||||
@ -53,7 +53,7 @@ import java.util.stream.Collectors;
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
@Singleton
|
||||
public class ExtensionJSONResolver implements Resolver {
|
||||
public class ExtensionJSONResolver extends JSONResolver {
|
||||
|
||||
private final DBSystem dbSystem;
|
||||
private final Identifiers identifiers;
|
||||
@ -66,6 +66,9 @@ public class ExtensionJSONResolver implements Resolver {
|
||||
this.jsonResolverService = jsonResolverService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formatter<Long> getHttpLastModifiedFormatter() {return jsonResolverService.getHttpLastModifiedFormatter();}
|
||||
|
||||
@Override
|
||||
public boolean canAccess(Request request) {
|
||||
WebUser permissions = request.getUser().orElse(new WebUser(""));
|
||||
@ -107,10 +110,7 @@ public class ExtensionJSONResolver implements Resolver {
|
||||
|
||||
private Response getResponse(Request request, ServerUUID serverUUID) {
|
||||
JSONStorage.StoredJSON json = getJSON(request, serverUUID);
|
||||
|
||||
return Response.builder()
|
||||
.setJSONContent(json.json)
|
||||
.build();
|
||||
return getCachedOrNewResponse(request, json);
|
||||
}
|
||||
|
||||
private Map<String, List<ExtensionDataDto>> getExtensionData(ServerUUID serverUUID) {
|
||||
|
@ -16,9 +16,8 @@
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver.resolver.json;
|
||||
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.rendering.json.graphs.GraphJSONCreator;
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Resolver;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
@ -51,7 +50,7 @@ import java.util.Optional;
|
||||
*/
|
||||
@Singleton
|
||||
@Path("/v1/graph")
|
||||
public class GraphsJSONResolver implements Resolver {
|
||||
public class GraphsJSONResolver extends JSONResolver {
|
||||
|
||||
private final Identifiers identifiers;
|
||||
private final AsyncJSONResolverService jsonResolverService;
|
||||
@ -60,13 +59,17 @@ public class GraphsJSONResolver implements Resolver {
|
||||
@Inject
|
||||
public GraphsJSONResolver(
|
||||
Identifiers identifiers,
|
||||
AsyncJSONResolverService jsonResolverService, GraphJSONCreator graphJSON
|
||||
AsyncJSONResolverService jsonResolverService,
|
||||
GraphJSONCreator graphJSON
|
||||
) {
|
||||
this.identifiers = identifiers;
|
||||
this.jsonResolverService = jsonResolverService;
|
||||
this.graphJSON = graphJSON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formatter<Long> getHttpLastModifiedFormatter() {return jsonResolverService.getHttpLastModifiedFormatter();}
|
||||
|
||||
@Override
|
||||
public boolean canAccess(Request request) {
|
||||
return request.getUser().orElse(new WebUser("")).hasPermission("page.server");
|
||||
@ -126,10 +129,8 @@ public class GraphsJSONResolver implements Resolver {
|
||||
|
||||
DataID dataID = getDataID(type);
|
||||
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.JSON)
|
||||
.setJSONContent(getGraphJSON(request, dataID).json)
|
||||
.build();
|
||||
JSONStorage.StoredJSON storedJSON = getGraphJSON(request, dataID);
|
||||
return getCachedOrNewResponse(request, storedJSON);
|
||||
}
|
||||
|
||||
private JSONStorage.StoredJSON getGraphJSON(@Untrusted Request request, DataID dataID) {
|
||||
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.webserver.resolver.json;
|
||||
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Resolver;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.webserver.CacheStrategy;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONStorage;
|
||||
import com.djrapitops.plan.identification.Identifiers;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import com.djrapitops.plan.utilities.java.Maps;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public abstract class JSONResolver implements Resolver {
|
||||
|
||||
protected Response getCachedOrNewResponse(@Untrusted Request request, JSONStorage.StoredJSON storedJSON) {
|
||||
if (storedJSON == null) {
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.JSON)
|
||||
.setJSONContent(Maps.builder(String.class, String.class)
|
||||
.put("error", "Json failed to generate for some reason, see /Plan/logs for errors")
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
Optional<Long> browserCached = Identifiers.getEtag(request);
|
||||
if (browserCached.isPresent() && browserCached.get() == storedJSON.getTimestamp()) {
|
||||
return Response.builder()
|
||||
.setStatus(304)
|
||||
.setContent(new byte[0])
|
||||
.build();
|
||||
}
|
||||
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.JSON)
|
||||
.setJSONContent(storedJSON.getJson())
|
||||
.setHeader(HttpHeader.CACHE_CONTROL.asString(), CacheStrategy.CHECK_ETAG_USER_SPECIFIC)
|
||||
.setHeader(HttpHeader.LAST_MODIFIED.asString(), getHttpLastModifiedFormatter().apply(storedJSON.getTimestamp()))
|
||||
.setHeader(HttpHeader.ETAG.asString(), storedJSON.getTimestamp())
|
||||
.build();
|
||||
}
|
||||
|
||||
protected abstract Formatter<Long> getHttpLastModifiedFormatter();
|
||||
|
||||
}
|
@ -16,9 +16,8 @@
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver.resolver.json;
|
||||
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.rendering.json.network.NetworkTabJSONCreator;
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Resolver;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
@ -26,7 +25,6 @@ import com.djrapitops.plan.delivery.webserver.cache.AsyncJSONResolverService;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.DataID;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONStorage;
|
||||
import com.djrapitops.plan.identification.Identifiers;
|
||||
import com.djrapitops.plan.utilities.java.Maps;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
@ -36,7 +34,7 @@ import java.util.function.Supplier;
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class NetworkTabJSONResolver<T> implements Resolver {
|
||||
public class NetworkTabJSONResolver<T> extends JSONResolver {
|
||||
|
||||
private final DataID dataID;
|
||||
private final Supplier<T> jsonCreator;
|
||||
@ -51,6 +49,9 @@ public class NetworkTabJSONResolver<T> implements Resolver {
|
||||
this.asyncJSONResolverService = asyncJSONResolverService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formatter<Long> getHttpLastModifiedFormatter() {return asyncJSONResolverService.getHttpLastModifiedFormatter();}
|
||||
|
||||
@Override
|
||||
public boolean canAccess(Request request) {
|
||||
return request.getUser().orElse(new WebUser("")).hasPermission("page.network");
|
||||
@ -63,18 +64,6 @@ public class NetworkTabJSONResolver<T> implements Resolver {
|
||||
|
||||
private Response getResponse(Request request) {
|
||||
JSONStorage.StoredJSON json = asyncJSONResolverService.resolve(Identifiers.getTimestamp(request), dataID, jsonCreator);
|
||||
if (json == null) {
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.JSON)
|
||||
.setJSONContent(Maps.builder(String.class, String.class)
|
||||
.put("error", "Json failed to generate for some reason, see /Plan/logs for errors")
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.JSON)
|
||||
.setJSONContent(json.json)
|
||||
.build();
|
||||
return getCachedOrNewResponse(request, json);
|
||||
}
|
||||
}
|
@ -16,6 +16,8 @@
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver.resolver.json;
|
||||
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.rendering.json.PlayerJSONCreator;
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Resolver;
|
||||
@ -23,6 +25,7 @@ import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
import com.djrapitops.plan.delivery.webserver.CacheStrategy;
|
||||
import com.djrapitops.plan.identification.Identifiers;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@ -33,9 +36,11 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -45,11 +50,14 @@ public class PlayerJSONResolver implements Resolver {
|
||||
|
||||
private final Identifiers identifiers;
|
||||
private final PlayerJSONCreator jsonCreator;
|
||||
private final Formatter<Long> httpLastModifiedFormatter;
|
||||
|
||||
@Inject
|
||||
public PlayerJSONResolver(Identifiers identifiers, PlayerJSONCreator jsonCreator) {
|
||||
public PlayerJSONResolver(Identifiers identifiers, Formatters formatters, PlayerJSONCreator jsonCreator) {
|
||||
this.identifiers = identifiers;
|
||||
this.jsonCreator = jsonCreator;
|
||||
|
||||
httpLastModifiedFormatter = formatters.httpLastModifiedLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -88,9 +96,30 @@ public class PlayerJSONResolver implements Resolver {
|
||||
|
||||
private Response getResponse(Request request) {
|
||||
UUID playerUUID = identifiers.getPlayerUUID(request); // Can throw BadRequestException
|
||||
|
||||
Optional<Long> etag = Identifiers.getEtag(request);
|
||||
if (etag.isPresent()) {
|
||||
long lastSeen = jsonCreator.getLastSeen(playerUUID);
|
||||
if (etag.get() == lastSeen) {
|
||||
return Response.builder()
|
||||
.setStatus(304)
|
||||
.setContent(new byte[0])
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> jsonAsMap = jsonCreator.createJSONAsMap(playerUUID);
|
||||
long lastSeenRawValue = Optional.ofNullable(jsonAsMap.get("info"))
|
||||
.map(Map.class::cast)
|
||||
.map(info -> info.get("last_seen_raw_value"))
|
||||
.map(Long.class::cast)
|
||||
.orElseGet(System::currentTimeMillis);
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.JSON)
|
||||
.setJSONContent(jsonCreator.createJSONAsMap(playerUUID))
|
||||
.setJSONContent(jsonAsMap)
|
||||
.setHeader(HttpHeader.CACHE_CONTROL.asString(), CacheStrategy.CHECK_ETAG_USER_SPECIFIC)
|
||||
.setHeader(HttpHeader.LAST_MODIFIED.asString(), httpLastModifiedFormatter.apply(lastSeenRawValue))
|
||||
.setHeader(HttpHeader.ETAG.asString(), lastSeenRawValue)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,9 @@
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver.resolver.json;
|
||||
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.rendering.json.JSONFactory;
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Resolver;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
@ -49,7 +49,7 @@ import java.util.Optional;
|
||||
*/
|
||||
@Singleton
|
||||
@Path("/v1/kills")
|
||||
public class PlayerKillsJSONResolver implements Resolver {
|
||||
public class PlayerKillsJSONResolver extends JSONResolver {
|
||||
|
||||
private final Identifiers identifiers;
|
||||
private final AsyncJSONResolverService jsonResolverService;
|
||||
@ -66,6 +66,9 @@ public class PlayerKillsJSONResolver implements Resolver {
|
||||
this.jsonFactory = jsonFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formatter<Long> getHttpLastModifiedFormatter() {return jsonResolverService.getHttpLastModifiedFormatter();}
|
||||
|
||||
@Override
|
||||
public boolean canAccess(Request request) {
|
||||
return request.getUser().orElse(new WebUser("")).hasPermission("page.server");
|
||||
@ -99,9 +102,6 @@ public class PlayerKillsJSONResolver implements Resolver {
|
||||
JSONStorage.StoredJSON storedJSON = jsonResolverService.resolve(timestamp, DataID.KILLS, serverUUID,
|
||||
theUUID -> Collections.singletonMap("player_kills", jsonFactory.serverPlayerKillsAsJSONMaps(theUUID))
|
||||
);
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.JSON)
|
||||
.setJSONContent(storedJSON.json)
|
||||
.build();
|
||||
return getCachedOrNewResponse(request, storedJSON);
|
||||
}
|
||||
}
|
@ -16,9 +16,9 @@
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver.resolver.json;
|
||||
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.rendering.json.JSONFactory;
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Resolver;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
@ -49,7 +49,7 @@ import java.util.Optional;
|
||||
*/
|
||||
@Singleton
|
||||
@Path("/v1/players")
|
||||
public class PlayersTableJSONResolver implements Resolver {
|
||||
public class PlayersTableJSONResolver extends JSONResolver {
|
||||
|
||||
private final Identifiers identifiers;
|
||||
private final AsyncJSONResolverService jsonResolverService;
|
||||
@ -66,6 +66,9 @@ public class PlayersTableJSONResolver implements Resolver {
|
||||
this.jsonFactory = jsonFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formatter<Long> getHttpLastModifiedFormatter() {return jsonResolverService.getHttpLastModifiedFormatter();}
|
||||
|
||||
@Override
|
||||
public boolean canAccess(Request request) {
|
||||
WebUser user = request.getUser().orElse(new WebUser(""));
|
||||
@ -95,10 +98,8 @@ public class PlayersTableJSONResolver implements Resolver {
|
||||
}
|
||||
|
||||
private Response getResponse(Request request) {
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.JSON)
|
||||
.setJSONContent(getStoredJSON(request).json)
|
||||
.build();
|
||||
JSONStorage.StoredJSON storedJSON = getStoredJSON(request);
|
||||
return getCachedOrNewResponse(request, storedJSON);
|
||||
}
|
||||
|
||||
private JSONStorage.StoredJSON getStoredJSON(@Untrusted Request request) {
|
||||
|
@ -16,14 +16,14 @@
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver.resolver.json;
|
||||
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.rendering.json.ServerTabJSONCreator;
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Resolver;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.AsyncJSONResolverService;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.DataID;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONStorage;
|
||||
import com.djrapitops.plan.identification.Identifiers;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
@ -36,7 +36,7 @@ import java.util.function.Function;
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class ServerTabJSONResolver<T> implements Resolver {
|
||||
public class ServerTabJSONResolver<T> extends JSONResolver {
|
||||
|
||||
private final DataID dataID;
|
||||
private final Identifiers identifiers;
|
||||
@ -53,6 +53,9 @@ public class ServerTabJSONResolver<T> implements Resolver {
|
||||
this.asyncJSONResolverService = asyncJSONResolverService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formatter<Long> getHttpLastModifiedFormatter() {return asyncJSONResolverService.getHttpLastModifiedFormatter();}
|
||||
|
||||
@Override
|
||||
public boolean canAccess(Request request) {
|
||||
return request.getUser().orElse(new WebUser("")).hasPermission("page.server");
|
||||
@ -65,9 +68,7 @@ public class ServerTabJSONResolver<T> implements Resolver {
|
||||
|
||||
private Response getResponse(@Untrusted Request request) {
|
||||
ServerUUID serverUUID = identifiers.getServerUUID(request); // Can throw BadRequestException
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.JSON)
|
||||
.setJSONContent(asyncJSONResolverService.resolve(Identifiers.getTimestamp(request), dataID, serverUUID, jsonCreator).json)
|
||||
.build();
|
||||
JSONStorage.StoredJSON storedJson = asyncJSONResolverService.resolve(Identifiers.getTimestamp(request), dataID, serverUUID, jsonCreator);
|
||||
return getCachedOrNewResponse(request, storedJson);
|
||||
}
|
||||
}
|
@ -16,9 +16,9 @@
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver.resolver.json;
|
||||
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.rendering.json.JSONFactory;
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Resolver;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
@ -50,7 +50,7 @@ import java.util.Optional;
|
||||
*/
|
||||
@Singleton
|
||||
@Path("/v1/sessions")
|
||||
public class SessionsJSONResolver implements Resolver {
|
||||
public class SessionsJSONResolver extends JSONResolver {
|
||||
|
||||
private final Identifiers identifiers;
|
||||
private final AsyncJSONResolverService jsonResolverService;
|
||||
@ -67,6 +67,9 @@ public class SessionsJSONResolver implements Resolver {
|
||||
this.jsonFactory = jsonFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formatter<Long> getHttpLastModifiedFormatter() {return jsonResolverService.getHttpLastModifiedFormatter();}
|
||||
|
||||
@Override
|
||||
public boolean canAccess(Request request) {
|
||||
return request.getUser().orElse(new WebUser("")).hasPermission("page.server");
|
||||
@ -92,10 +95,8 @@ public class SessionsJSONResolver implements Resolver {
|
||||
}
|
||||
|
||||
private Response getResponse(Request request) {
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.JSON)
|
||||
.setJSONContent(getStoredJSON(request).json)
|
||||
.build();
|
||||
JSONStorage.StoredJSON result = getStoredJSON(request);
|
||||
return getCachedOrNewResponse(request, result);
|
||||
}
|
||||
|
||||
private JSONStorage.StoredJSON getStoredJSON(@Untrusted Request request) {
|
||||
|
@ -44,6 +44,6 @@ public class SwaggerPageResolver implements Resolver {
|
||||
|
||||
@Override
|
||||
public Optional<Response> resolve(Request request) {
|
||||
return Optional.of(responseFactory.reactPageResponse());
|
||||
return Optional.of(responseFactory.reactPageResponse(request));
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.UserIdentifierQueries;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@ -67,9 +68,11 @@ public class Identifiers {
|
||||
public static Optional<Long> getTimestamp(@Untrusted Request request) {
|
||||
try {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long timestamp = request.getQuery().get("timestamp")
|
||||
long timestamp = request.getHeader("X-Plan-Timestamp")
|
||||
.map(Long::parseLong)
|
||||
.orElse(currentTime);
|
||||
.orElseGet(() -> request.getQuery().get("timestamp")
|
||||
.map(Long::parseLong)
|
||||
.orElse(currentTime));
|
||||
if (currentTime + TimeUnit.SECONDS.toMillis(10L) < timestamp) {
|
||||
return Optional.empty();
|
||||
}
|
||||
@ -79,6 +82,17 @@ public class Identifiers {
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<Long> getEtag(Request request) {
|
||||
return request.getHeader(HttpHeader.IF_NONE_MATCH.asString())
|
||||
.map(tag -> {
|
||||
try {
|
||||
return Long.parseLong(tag);
|
||||
} catch (NumberFormatException notANumber) {
|
||||
throw new BadRequestException("'" + HttpHeader.IF_NONE_MATCH.asString() + "'-header was not a number. Clear browser cache.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain UUID of the server.
|
||||
*
|
||||
|
@ -33,6 +33,7 @@ import com.djrapitops.plan.storage.database.queries.objects.*;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@ -85,7 +86,11 @@ public class PlayerContainerQuery implements Query<PlayerContainer> {
|
||||
return worldTimes;
|
||||
});
|
||||
|
||||
container.putSupplier(PlayerKeys.LAST_SEEN, () -> SessionsMutator.forContainer(container).toLastSeen());
|
||||
container.putSupplier(PlayerKeys.LAST_SEEN, () -> {
|
||||
Optional<ActiveSession> activeSession = container.getValue(PlayerKeys.ACTIVE_SESSION);
|
||||
if (activeSession.isPresent()) return System.currentTimeMillis();
|
||||
return SessionsMutator.forContainer(container).toLastSeen();
|
||||
});
|
||||
container.putSupplier(PlayerKeys.PLAYER_KILLS, () -> db.query(KillQueries.fetchPlayerKillsOfPlayer(uuid)));
|
||||
container.putSupplier(PlayerKeys.PLAYER_DEATHS_KILLS, () -> db.query(KillQueries.fetchPlayerDeathsOfPlayer(uuid)));
|
||||
container.putSupplier(PlayerKeys.PLAYER_KILL_COUNT, () -> container.getValue(PlayerKeys.PLAYER_KILLS).map(Collection::size).orElse(0));
|
||||
|
@ -786,6 +786,14 @@ public class SessionQueries {
|
||||
};
|
||||
}
|
||||
|
||||
public static Query<Long> lastSeen(UUID playerUUID) {
|
||||
String sql = SELECT + "MAX(" + SessionsTable.SESSION_END + ") as last_seen" +
|
||||
FROM + SessionsTable.TABLE_NAME +
|
||||
WHERE + SessionsTable.USER_ID + "=" + UsersTable.SELECT_USER_ID;
|
||||
return db -> db.queryOptional(sql, set -> set.getLong("last_seen"), playerUUID)
|
||||
.orElse(0L);
|
||||
}
|
||||
|
||||
public static Query<Long> lastSeen(UUID playerUUID, ServerUUID serverUUID) {
|
||||
String sql = SELECT + "MAX(" + SessionsTable.SESSION_END + ") as last_seen" +
|
||||
FROM + SessionsTable.TABLE_NAME +
|
||||
|
@ -81,11 +81,13 @@ public interface Resource {
|
||||
* @throws UncheckedIOException if fails to read the file.
|
||||
*/
|
||||
default WebResource asWebResource() {
|
||||
try {
|
||||
return WebResource.create(asInputStream());
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Failed to read '" + getResourceName() + "'", e);
|
||||
}
|
||||
return WebResource.create(() -> {
|
||||
try {
|
||||
return asInputStream();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Failed to read '" + getResourceName() + "'", e);
|
||||
}
|
||||
}, getLastModifiedDate());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -14,8 +14,9 @@ const isCurrentAddress = (address) => {
|
||||
export const baseAddress = javaReplaced.address.startsWith('PLAN_') || !isCurrentAddress(javaReplaced.address) ? "" : javaReplaced.address;
|
||||
export const staticSite = javaReplaced.isStatic === 'true';
|
||||
|
||||
export const doSomeGetRequest = async (url, statusOptions) => {
|
||||
return doSomeRequest(url, statusOptions, async () => axios.get(baseAddress + url));
|
||||
export const doSomeGetRequest = async (url, updateRequested, statusOptions) => {
|
||||
return doSomeRequest(url, statusOptions, async () => axios.get(baseAddress + url,
|
||||
updateRequested ? {headers: {"X-Plan-Timestamp": updateRequested}} : {}));
|
||||
}
|
||||
|
||||
export const doSomePostRequest = async (url, statusOptions, body) => {
|
||||
@ -72,6 +73,6 @@ export const doSomeRequest = async (url, statusOptions, axiosFunction) => {
|
||||
export const standard200option = {status: 200, get: response => response.data}
|
||||
const exported404options = {status: 404, get: () => 'Data not yet exported'}
|
||||
|
||||
export const doGetRequest = async url => {
|
||||
return doSomeGetRequest(url, staticSite ? [standard200option, exported404options] : [standard200option])
|
||||
export const doGetRequest = async (url, updateRequested) => {
|
||||
return doSomeGetRequest(url, updateRequested, staticSite ? [standard200option, exported404options] : [standard200option])
|
||||
}
|
@ -1,42 +1,42 @@
|
||||
import {doGetRequest, staticSite} from "./backendConfiguration";
|
||||
|
||||
export const fetchNetworkOverview = async (updateRequested) => {
|
||||
let url = `/v1/network/overview?timestamp=${updateRequested}`;
|
||||
let url = `/v1/network/overview`;
|
||||
if (staticSite) url = `/data/network-overview.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, updateRequested);
|
||||
}
|
||||
|
||||
export const fetchServersOverview = async (updateRequested) => {
|
||||
let url = `/v1/network/servers?timestamp=${updateRequested}`;
|
||||
let url = `/v1/network/servers`;
|
||||
if (staticSite) url = `/data/network-servers.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, updateRequested);
|
||||
}
|
||||
|
||||
export const fetchServerPie = async (timestamp) => {
|
||||
let url = `/v1/graph?type=serverPie×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=serverPie`;
|
||||
if (staticSite) url = `/data/graph-serverPie.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchNetworkSessionsOverview = async (timestamp) => {
|
||||
let url = `/v1/network/sessionsOverview?timestamp=${timestamp}`;
|
||||
let url = `/v1/network/sessionsOverview`;
|
||||
if (staticSite) url = `/data/network-sessionsOverview.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchNetworkPlayerbaseOverview = async (timestamp) => {
|
||||
let url = `/v1/network/playerbaseOverview?timestamp=${timestamp}`;
|
||||
let url = `/v1/network/playerbaseOverview`;
|
||||
if (staticSite) url = `/data/network-playerbaseOverview.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchNetworkPingTable = async (timestamp) => {
|
||||
let url = `/v1/network/pingTable?timestamp=${timestamp}`;
|
||||
let url = `/v1/network/pingTable`;
|
||||
if (staticSite) url = `/data/network-pingTable.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchNetworkPerformanceOverview = async (timestamp, serverUUIDs) => {
|
||||
let url = `/v1/network/performanceOverview?servers=${encodeURIComponent(JSON.stringify(serverUUIDs))}×tamp=${timestamp}`;
|
||||
return doGetRequest(url);
|
||||
let url = `/v1/network/performanceOverview?servers=${encodeURIComponent(JSON.stringify(serverUUIDs))}`;
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
@ -2,9 +2,9 @@ import {faMapSigns} from "@fortawesome/free-solid-svg-icons";
|
||||
import {doSomeGetRequest, standard200option, staticSite} from "./backendConfiguration";
|
||||
|
||||
export const fetchPlayer = async (timestamp, uuid) => {
|
||||
let url = `/v1/player?player=${uuid}×tamp=${timestamp}`;
|
||||
let url = `/v1/player?player=${uuid}`;
|
||||
if (staticSite) url = `/player/${uuid}/player-${uuid}.json`
|
||||
return doSomeGetRequest(url, [
|
||||
return doSomeGetRequest(url, timestamp, [
|
||||
standard200option,
|
||||
{
|
||||
status: staticSite ? 404 : 400,
|
||||
|
@ -3,49 +3,49 @@ import {doGetRequest, staticSite} from "./backendConfiguration";
|
||||
export const fetchServerIdentity = async (timestamp, identifier) => {
|
||||
let url = `/v1/serverIdentity?server=${identifier}`;
|
||||
if (staticSite) url = `/data/serverIdentity-${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchServerOverview = async (timestamp, identifier) => {
|
||||
let url = `/v1/serverOverview?server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/serverOverview?server=${identifier}`;
|
||||
if (staticSite) url = `/data/serverOverview-${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchOnlineActivityOverview = async (timestamp, identifier) => {
|
||||
let url = `/v1/onlineOverview?server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/onlineOverview?server=${identifier}`;
|
||||
if (staticSite) url = `/data/onlineOverview-${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchPlayerbaseOverview = async (timestamp, identifier) => {
|
||||
let url = `/v1/playerbaseOverview?server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/playerbaseOverview?server=${identifier}`;
|
||||
if (staticSite) url = `/data/playerbaseOverview-${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchSessionOverview = async (timestamp, identifier) => {
|
||||
let url = `/v1/sessionsOverview?server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/sessionsOverview?server=${identifier}`;
|
||||
if (staticSite) url = `/data/sessionsOverview-${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchPvpPve = async (timestamp, identifier) => {
|
||||
let url = `/v1/playerVersus?server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/playerVersus?server=${identifier}`;
|
||||
if (staticSite) url = `/data/playerVersus-${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchPerformanceOverview = async (timestamp, identifier) => {
|
||||
let url = `/v1/performanceOverview?server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/performanceOverview?server=${identifier}`;
|
||||
if (staticSite) url = `/data/performanceOverview-${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchExtensionData = async (timestamp, identifier) => {
|
||||
let url = `/v1/extensionData?server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/extensionData?server=${identifier}`;
|
||||
if (staticSite) url = `/data/extensionData-${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchSessions = async (timestamp, identifier) => {
|
||||
@ -57,21 +57,21 @@ export const fetchSessions = async (timestamp, identifier) => {
|
||||
}
|
||||
|
||||
const fetchSessionsServer = async (timestamp, identifier) => {
|
||||
let url = `/v1/sessions?server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/sessions?server=${identifier}`;
|
||||
if (staticSite) url = `/data/sessions-${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
const fetchSessionsNetwork = async (timestamp) => {
|
||||
let url = `/v1/sessions?timestamp=${timestamp}`;
|
||||
let url = `/v1/sessions`;
|
||||
if (staticSite) url = `/data/sessions.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchKills = async (timestamp, identifier) => {
|
||||
let url = `/v1/kills?server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/kills?server=${identifier}`;
|
||||
if (staticSite) url = `/data/kills-${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchPlayers = async (timestamp, identifier) => {
|
||||
@ -82,21 +82,21 @@ export const fetchPlayers = async (timestamp, identifier) => {
|
||||
}
|
||||
}
|
||||
const fetchPlayersServer = async (timestamp, identifier) => {
|
||||
let url = `/v1/players?server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/players?server=${identifier}`;
|
||||
if (staticSite) url = `/data/players-${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
const fetchPlayersNetwork = async (timestamp) => {
|
||||
let url = `/v1/players?timestamp=${timestamp}`;
|
||||
let url = `/v1/players`;
|
||||
if (staticSite) url = `/data/players.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchPingTable = async (timestamp, identifier) => {
|
||||
let url = `/v1/pingTable?server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/pingTable?server=${identifier}`;
|
||||
if (staticSite) url = `/data/pingTable-${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchPlayersOnlineGraph = async (timestamp, identifier) => {
|
||||
@ -108,15 +108,15 @@ export const fetchPlayersOnlineGraph = async (timestamp, identifier) => {
|
||||
}
|
||||
|
||||
const fetchPlayersOnlineGraphServer = async (timestamp, identifier) => {
|
||||
let url = `/v1/graph?type=playersOnline&server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=playersOnline&server=${identifier}`;
|
||||
if (staticSite) url = `/data/graph-playersOnline_${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
const fetchPlayersOnlineGraphNetwork = async (timestamp) => {
|
||||
let url = `/v1/graph?type=playersOnline×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=playersOnline`;
|
||||
if (staticSite) url = `/data/graph-playersOnline.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchPlayerbaseDevelopmentGraph = async (timestamp, identifier) => {
|
||||
@ -128,15 +128,15 @@ export const fetchPlayerbaseDevelopmentGraph = async (timestamp, identifier) =>
|
||||
}
|
||||
|
||||
const fetchPlayerbaseDevelopmentGraphServer = async (timestamp, identifier) => {
|
||||
let url = `/v1/graph?type=activity&server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=activity&server=${identifier}`;
|
||||
if (staticSite) url = `/data/graph-activity_${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
const fetchPlayerbaseDevelopmentGraphNetwork = async (timestamp) => {
|
||||
let url = `/v1/graph?type=activity×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=activity`;
|
||||
if (staticSite) url = `/data/graph-activity.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchDayByDayGraph = async (timestamp, identifier) => {
|
||||
@ -148,15 +148,15 @@ export const fetchDayByDayGraph = async (timestamp, identifier) => {
|
||||
}
|
||||
|
||||
const fetchDayByDayGraphServer = async (timestamp, identifier) => {
|
||||
let url = `/v1/graph?type=uniqueAndNew&server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=uniqueAndNew&server=${identifier}`;
|
||||
if (staticSite) url = `/data/graph-uniqueAndNew_${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
const fetchDayByDayGraphNetwork = async (timestamp) => {
|
||||
let url = `/v1/graph?type=uniqueAndNew×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=uniqueAndNew`;
|
||||
if (staticSite) url = `/data/graph-uniqueAndNew.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchHourByHourGraph = async (timestamp, identifier) => {
|
||||
@ -168,33 +168,33 @@ export const fetchHourByHourGraph = async (timestamp, identifier) => {
|
||||
}
|
||||
|
||||
const fetchHourByHourGraphServer = async (timestamp, identifier) => {
|
||||
let url = `/v1/graph?type=hourlyUniqueAndNew&server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=hourlyUniqueAndNew&server=${identifier}`;
|
||||
if (staticSite) url = `/data/graph-hourlyUniqueAndNew_${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
const fetchHourByHourGraphNetwork = async (timestamp) => {
|
||||
let url = `/v1/graph?type=hourlyUniqueAndNew×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=hourlyUniqueAndNew`;
|
||||
if (staticSite) url = `/data/graph-hourlyUniqueAndNew.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchServerCalendarGraph = async (timestamp, identifier) => {
|
||||
let url = `/v1/graph?type=serverCalendar&server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=serverCalendar&server=${identifier}`;
|
||||
if (staticSite) url = `/data/graph-serverCalendar_${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchPunchCardGraph = async (timestamp, identifier) => {
|
||||
let url = `/v1/graph?type=punchCard&server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=punchCard&server=${identifier}`;
|
||||
if (staticSite) url = `/data/graph-punchCard_${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchWorldPie = async (timestamp, identifier) => {
|
||||
let url = `/v1/graph?type=worldPie&server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=worldPie&server=${identifier}`;
|
||||
if (staticSite) url = `/data/graph-worldPie_${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchGeolocations = async (timestamp, identifier) => {
|
||||
@ -206,27 +206,27 @@ export const fetchGeolocations = async (timestamp, identifier) => {
|
||||
}
|
||||
|
||||
const fetchGeolocationsServer = async (timestamp, identifier) => {
|
||||
let url = `/v1/graph?type=geolocation&server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=geolocation&server=${identifier}`;
|
||||
if (staticSite) url = `/data/graph-geolocation_${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
const fetchGeolocationsNetwork = async (timestamp) => {
|
||||
let url = `/v1/graph?type=geolocation×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=geolocation`;
|
||||
if (staticSite) url = `/data/graph-geolocation.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchOptimizedPerformance = async (timestamp, identifier, after) => {
|
||||
let url = `/v1/graph?type=optimizedPerformance&server=${identifier}×tamp=${timestamp}&after=${after}`;
|
||||
export const fetchOptimizedPerformance = async (timestamp, identifier) => {
|
||||
let url = `/v1/graph?type=optimizedPerformance&server=${identifier}`;
|
||||
if (staticSite) url = `/data/graph-optimizedPerformance_${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchPingGraph = async (timestamp, identifier) => {
|
||||
let url = `/v1/graph?type=aggregatedPing&server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=aggregatedPing&server=${identifier}`;
|
||||
if (staticSite) url = `/data/graph-aggregatedPing_${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchJoinAddressPie = async (timestamp, identifier) => {
|
||||
@ -238,15 +238,15 @@ export const fetchJoinAddressPie = async (timestamp, identifier) => {
|
||||
}
|
||||
|
||||
const fetchJoinAddressPieServer = async (timestamp, identifier) => {
|
||||
let url = `/v1/graph?type=joinAddressPie&server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=joinAddressPie&server=${identifier}`;
|
||||
if (staticSite) url = `/data/graph-joinAddressPie_${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
const fetchJoinAddressPieNetwork = async (timestamp) => {
|
||||
let url = `/v1/graph?type=joinAddressPie×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=joinAddressPie`;
|
||||
if (staticSite) url = `/data/graph-joinAddressPie.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchJoinAddressByDay = async (timestamp, identifier) => {
|
||||
@ -258,13 +258,13 @@ export const fetchJoinAddressByDay = async (timestamp, identifier) => {
|
||||
}
|
||||
|
||||
const fetchJoinAddressByDayServer = async (timestamp, identifier) => {
|
||||
let url = `/v1/graph?type=joinAddressByDay&server=${identifier}×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=joinAddressByDay&server=${identifier}`;
|
||||
if (staticSite) url = `/data/graph-joinAddressByDay_${identifier}.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
const fetchJoinAddressByDayNetwork = async (timestamp) => {
|
||||
let url = `/v1/graph?type=joinAddressByDay×tamp=${timestamp}`;
|
||||
let url = `/v1/graph?type=joinAddressByDay`;
|
||||
if (staticSite) url = `/data/graph-joinAddressByDay.json`;
|
||||
return doGetRequest(url);
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
@ -52,13 +52,11 @@ const NetworkPerformance = () => {
|
||||
timestamp_f: ''
|
||||
}
|
||||
const time = new Date().getTime();
|
||||
const monthMs = 2592000000;
|
||||
const after = time - monthMs;
|
||||
|
||||
for (const index of visualizedServers) {
|
||||
const server = serverOptions[index];
|
||||
|
||||
const {data, error} = await fetchOptimizedPerformance(time, encodeURIComponent(server.serverUUID), after);
|
||||
const {data, error} = await fetchOptimizedPerformance(time, encodeURIComponent(server.serverUUID));
|
||||
if (data) {
|
||||
loaded.servers.push(server);
|
||||
const values = data.values;
|
||||
|
Loading…
Reference in New Issue
Block a user