Refactored /server page to use ResolverService

This commit is contained in:
Rsl1122 2020-02-15 20:08:03 +02:00 committed by Risto Lahtela
parent e997289a20
commit 4a1234f9b4
22 changed files with 348 additions and 264 deletions

View File

@ -0,0 +1,49 @@
/*
* 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.web.resolver;
import java.util.Optional;
/**
* Special Resolver that gives responses without user authentication.
*
* @author Rsl1122
*/
public interface NoAuthResolver extends Resolver {
default boolean canAccess(WebUser permissions, URIPath target, URIQuery query) {
return true;
}
/**
* Implement request resolution.
*
* @param target Target that is being accessed, /example/target
* @param query Parameters in the URL, ?param=value etc.
* @return Response or empty if the response should be 404 (not found).
* @see Response for return value
*/
Optional<Response> resolve(URIPath target, URIQuery query);
default ResponseBuilder newResponseBuilder() {
return Response.builder();
}
default boolean requiresAuth(URIPath target, URIQuery query) {
return false;
}
}

View File

@ -18,6 +18,12 @@ package com.djrapitops.plan.delivery.web.resolver;
import java.util.Optional;
/**
* Interface for resolving requests of Plan webserver.
*
* @author Rsl1122
* @see NoAuthResolver if resource is always accessible regardless of user.
*/
public interface Resolver {
/**
@ -46,15 +52,6 @@ public interface Resolver {
return Response.builder();
}
/**
* Override this method with false to always allow using this resolver.
* <p>
* Use this when content/style is needed for displaying pages where authentication is not available/needed.
*
* @param target Target that is being accessed, /example/target
* @param query Parameters in the URL, ?param=value etc.
* @return true by default. If false is returned {@link #canAccess(WebUser, URIPath, URIQuery)} will not be called.
*/
default boolean requiresAuth(URIPath target, URIQuery query) {
return true;
}

View File

@ -71,12 +71,13 @@ public class ResponseBuilder {
* @return https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location
*/
public ResponseBuilder redirectTo(String url) {
return setStatus(302).setHeader("Location", url).setContent(new byte[]{});
return setStatus(302).setHeader("Location", url).setContent(new byte[0]);
}
public ResponseBuilder setContent(byte[] bytes) {
response.bytes = bytes;
return setHeader("Content-Length", bytes.length);
return setHeader("Content-Length", bytes.length)
.setHeader("Accept-Ranges", "bytes"); // Does not compress
}
public ResponseBuilder setContent(String utf8String) {

View File

@ -68,7 +68,7 @@ public class NetworkCommand extends CommandNode {
// Link
String address = PlanSystem.getMainAddress(webServer, dbSystem);
String url = address + "/network/";
String url = address + "/network";
String linkPrefix = locale.getString(CommandLang.LINK_PREFIX);
boolean console = !CommandUtils.isPlayer(sender);
if (console) {

View File

@ -20,6 +20,7 @@ import com.djrapitops.plan.delivery.rendering.html.Html;
import com.djrapitops.plan.storage.file.Resource;
import org.apache.commons.lang3.StringUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -70,6 +71,17 @@ abstract class FileExporter {
}
}
void export(Path to, byte[] resource) throws IOException {
Files.createDirectories(to.getParent());
try (
InputStream in = new ByteArrayInputStream(resource);
OutputStream out = Files.newOutputStream(to, OPEN_OPTIONS)
) {
copy(in, out);
}
}
String toFileName(String resourceName) {
return StringUtils.replaceEach(
Html.encodeToURL(resourceName),

View File

@ -128,11 +128,15 @@ public class NetworkPageExporter extends FileExporter {
String jsonResourceName = toFileName(toJSONResourceName(resource)) + ".json";
String relativePlayerLink = toRelativePathFromRoot("player");
export(toDirectory.resolve("data").resolve(jsonResourceName),
// Replace ../player in urls to fix player page links
StringUtils.replace(found.getContent(), "../player", toRelativePathFromRoot("player"))
StringUtils.replaceEach(found.getContent(),
new String[]{"../player", "./player"},
new String[]{relativePlayerLink, relativePlayerLink}
)
);
exportPaths.put("../v1/" + resource, toRelativePathFromRoot("data/" + jsonResourceName));
exportPaths.put("./v1/" + resource, toRelativePathFromRoot("data/" + jsonResourceName));
}
private String toJSONResourceName(String resource) {
@ -150,46 +154,48 @@ public class NetworkPageExporter extends FileExporter {
private void exportRequiredResources(Path toDirectory) throws IOException {
exportResources(toDirectory,
"img/Flaticon_circle.png",
"css/sb-admin-2.css",
"css/style.css",
"vendor/jquery/jquery.min.js",
"vendor/bootstrap/js/bootstrap.bundle.min.js",
"vendor/jquery-easing/jquery.easing.min.js",
"vendor/datatables/jquery.dataTables.min.js",
"vendor/datatables/dataTables.bootstrap4.min.js",
"vendor/highcharts/highstock.js",
"vendor/highcharts/map.js",
"vendor/highcharts/world.js",
"vendor/highcharts/drilldown.js",
"vendor/highcharts/highcharts-more.js",
"vendor/highcharts/no-data-to-display.js",
"vendor/fontawesome-free/css/all.min.css",
"vendor/fontawesome-free/webfonts/fa-brands-400.eot",
"vendor/fontawesome-free/webfonts/fa-brands-400.ttf",
"vendor/fontawesome-free/webfonts/fa-brands-400.woff",
"vendor/fontawesome-free/webfonts/fa-brands-400.woff2",
"vendor/fontawesome-free/webfonts/fa-regular-400.eot",
"vendor/fontawesome-free/webfonts/fa-regular-400.ttf",
"vendor/fontawesome-free/webfonts/fa-regular-400.woff",
"vendor/fontawesome-free/webfonts/fa-regular-400.woff2",
"vendor/fontawesome-free/webfonts/fa-solid-900.eot",
"vendor/fontawesome-free/webfonts/fa-solid-900.ttf",
"vendor/fontawesome-free/webfonts/fa-solid-900.woff",
"vendor/fontawesome-free/webfonts/fa-solid-900.woff2",
"js/sb-admin-2.js",
"js/xmlhttprequests.js",
"js/color-selector.js",
"js/sessionAccordion.js",
"js/pingTable.js",
"js/graphs.js",
"js/network-values.js"
"./img/Flaticon_circle.png",
"./css/sb-admin-2.css",
"./css/style.css",
"./vendor/jquery/jquery.min.js",
"./vendor/bootstrap/js/bootstrap.bundle.min.js",
"./vendor/jquery-easing/jquery.easing.min.js",
"./vendor/datatables/jquery.dataTables.min.js",
"./vendor/datatables/dataTables.bootstrap4.min.js",
"./vendor/highcharts/highstock.js",
"./vendor/highcharts/map.js",
"./vendor/highcharts/world.js",
"./vendor/highcharts/drilldown.js",
"./vendor/highcharts/highcharts-more.js",
"./vendor/highcharts/no-data-to-display.js",
"./vendor/fontawesome-free/css/all.min.css",
"./vendor/fontawesome-free/webfonts/fa-brands-400.eot",
"./vendor/fontawesome-free/webfonts/fa-brands-400.ttf",
"./vendor/fontawesome-free/webfonts/fa-brands-400.woff",
"./vendor/fontawesome-free/webfonts/fa-brands-400.woff2",
"./vendor/fontawesome-free/webfonts/fa-regular-400.eot",
"./vendor/fontawesome-free/webfonts/fa-regular-400.ttf",
"./vendor/fontawesome-free/webfonts/fa-regular-400.woff",
"./vendor/fontawesome-free/webfonts/fa-regular-400.woff2",
"./vendor/fontawesome-free/webfonts/fa-solid-900.eot",
"./vendor/fontawesome-free/webfonts/fa-solid-900.ttf",
"./vendor/fontawesome-free/webfonts/fa-solid-900.woff",
"./vendor/fontawesome-free/webfonts/fa-solid-900.woff2",
"./js/sb-admin-2.js",
"./js/xmlhttprequests.js",
"./js/color-selector.js",
"./js/sessionAccordion.js",
"./js/pingTable.js",
"./js/graphs.js",
"./js/network-values.js"
);
}
private void exportResources(Path toDirectory, String... resourceNames) throws IOException {
for (String resourceName : resourceNames) {
exportResource(toDirectory, resourceName);
String nonRelativePath = toNonRelativePath(resourceName);
exportResource(toDirectory, nonRelativePath);
exportPaths.put(resourceName, toRelativePathFromRoot(nonRelativePath));
}
}
@ -199,16 +205,18 @@ public class NetworkPageExporter extends FileExporter {
if (resourceName.endsWith(".css")) {
export(to, theme.replaceThemeColors(resource.asString()));
} else if ("js/network-values.js".equalsIgnoreCase(resourceName)) {
// Replace /server in urls to fix server page links
export(to, StringUtils.replaceOnce(resource.asString(), "server/", toRelativePathFromRoot("server") + '/'));
} else if ("js/network-values.js".equalsIgnoreCase(resourceName) || "js/sessionAccordion.js".equalsIgnoreCase(resourceName)) {
String relativePlayerLink = toRelativePathFromRoot("player");
String relativeServerLink = toRelativePathFromRoot("server/");
export(to, StringUtils.replaceEach(resource.asString(),
new String[]{"../player", "./player", "./server/", "server/"},
new String[]{relativePlayerLink, relativePlayerLink, relativeServerLink, relativeServerLink}
));
} else if (Resource.isTextResource(resourceName)) {
export(to, resource.asLines());
} else {
export(to, resource);
}
exportPaths.put(resourceName, toRelativePathFromRoot(resourceName));
}
private String toRelativePathFromRoot(String resourceName) {
@ -217,7 +225,7 @@ public class NetworkPageExporter extends FileExporter {
}
private String toNonRelativePath(String resourceName) {
return StringUtils.remove(resourceName, "../");
return StringUtils.remove(StringUtils.remove(resourceName, "../"), "./");
}
}

View File

@ -57,6 +57,6 @@ public class PlayerJSONExporter extends FileExporter {
}
private void exportJSON(Path to, UUID playerUUID) throws IOException {
export(to, responseFactory.rawPlayerPageResponse_old(playerUUID).getContent());
export(to, responseFactory.rawPlayerPageResponse(playerUUID).getBytes());
}
}

View File

@ -78,7 +78,7 @@ public class PlayerPageExporter extends FileExporter {
if (!dbSystem.getDatabase().query(PlayerFetchQueries.isPlayerRegistered(playerUUID))) return;
ExportPaths exportPaths = new ExportPaths();
exportPaths.put("../network/", toRelativePathFromRoot("network"));
exportPaths.put("../network", toRelativePathFromRoot("network"));
exportPaths.put("../server/", toRelativePathFromRoot("server"));
exportRequiredResources(exportPaths, toDirectory);
@ -197,7 +197,7 @@ public class PlayerPageExporter extends FileExporter {
}
private String toNonRelativePath(String resourceName) {
return StringUtils.remove(resourceName, "../");
return StringUtils.remove(StringUtils.remove(resourceName, "../"), "./");
}
}

View File

@ -84,7 +84,7 @@ public class ServerPageExporter extends FileExporter {
Database.State dbState = dbSystem.getDatabase().getState();
if (dbState == Database.State.CLOSED || dbState == Database.State.CLOSING) return;
exportPaths.put("../network/", toRelativePathFromRoot("network"));
exportPaths.put("../network", toRelativePathFromRoot("network"));
exportRequiredResources(toDirectory);
exportJSON(toDirectory, server);
exportHtml(toDirectory, server);
@ -163,49 +163,51 @@ public class ServerPageExporter extends FileExporter {
private void exportRequiredResources(Path toDirectory) throws IOException {
// Style
exportResources(toDirectory,
"img/Flaticon_circle.png",
"css/sb-admin-2.css",
"css/style.css",
"vendor/jquery/jquery.min.js",
"vendor/bootstrap/js/bootstrap.bundle.min.js",
"vendor/jquery-easing/jquery.easing.min.js",
"vendor/datatables/jquery.dataTables.min.js",
"vendor/datatables/dataTables.bootstrap4.min.js",
"vendor/highcharts/highstock.js",
"vendor/highcharts/map.js",
"vendor/highcharts/world.js",
"vendor/highcharts/drilldown.js",
"vendor/highcharts/highcharts-more.js",
"vendor/highcharts/no-data-to-display.js",
"vendor/fullcalendar/fullcalendar.min.css",
"vendor/momentjs/moment.js",
"vendor/fullcalendar/fullcalendar.min.js",
"vendor/fontawesome-free/css/all.min.css",
"vendor/fontawesome-free/webfonts/fa-brands-400.eot",
"vendor/fontawesome-free/webfonts/fa-brands-400.ttf",
"vendor/fontawesome-free/webfonts/fa-brands-400.woff",
"vendor/fontawesome-free/webfonts/fa-brands-400.woff2",
"vendor/fontawesome-free/webfonts/fa-regular-400.eot",
"vendor/fontawesome-free/webfonts/fa-regular-400.ttf",
"vendor/fontawesome-free/webfonts/fa-regular-400.woff",
"vendor/fontawesome-free/webfonts/fa-regular-400.woff2",
"vendor/fontawesome-free/webfonts/fa-solid-900.eot",
"vendor/fontawesome-free/webfonts/fa-solid-900.ttf",
"vendor/fontawesome-free/webfonts/fa-solid-900.woff",
"vendor/fontawesome-free/webfonts/fa-solid-900.woff2",
"js/sb-admin-2.js",
"js/xmlhttprequests.js",
"js/color-selector.js",
"js/sessionAccordion.js",
"js/pingTable.js",
"js/graphs.js",
"js/server-values.js"
"../img/Flaticon_circle.png",
"../css/sb-admin-2.css",
"../css/style.css",
"../vendor/jquery/jquery.min.js",
"../vendor/bootstrap/js/bootstrap.bundle.min.js",
"../vendor/jquery-easing/jquery.easing.min.js",
"../vendor/datatables/jquery.dataTables.min.js",
"../vendor/datatables/dataTables.bootstrap4.min.js",
"../vendor/highcharts/highstock.js",
"../vendor/highcharts/map.js",
"../vendor/highcharts/world.js",
"../vendor/highcharts/drilldown.js",
"../vendor/highcharts/highcharts-more.js",
"../vendor/highcharts/no-data-to-display.js",
"../vendor/fullcalendar/fullcalendar.min.css",
"../vendor/momentjs/moment.js",
"../vendor/fullcalendar/fullcalendar.min.js",
"../vendor/fontawesome-free/css/all.min.css",
"../vendor/fontawesome-free/webfonts/fa-brands-400.eot",
"../vendor/fontawesome-free/webfonts/fa-brands-400.ttf",
"../vendor/fontawesome-free/webfonts/fa-brands-400.woff",
"../vendor/fontawesome-free/webfonts/fa-brands-400.woff2",
"../vendor/fontawesome-free/webfonts/fa-regular-400.eot",
"../vendor/fontawesome-free/webfonts/fa-regular-400.ttf",
"../vendor/fontawesome-free/webfonts/fa-regular-400.woff",
"../vendor/fontawesome-free/webfonts/fa-regular-400.woff2",
"../vendor/fontawesome-free/webfonts/fa-solid-900.eot",
"../vendor/fontawesome-free/webfonts/fa-solid-900.ttf",
"../vendor/fontawesome-free/webfonts/fa-solid-900.woff",
"../vendor/fontawesome-free/webfonts/fa-solid-900.woff2",
"../js/sb-admin-2.js",
"../js/xmlhttprequests.js",
"../js/color-selector.js",
"../js/sessionAccordion.js",
"../js/pingTable.js",
"../js/graphs.js",
"../js/server-values.js"
);
}
private void exportResources(Path toDirectory, String... resourceNames) throws IOException {
for (String resourceName : resourceNames) {
exportResource(toDirectory, resourceName);
String nonRelativePath = toNonRelativePath(resourceName);
exportResource(toDirectory, nonRelativePath);
exportPaths.put(resourceName, toRelativePathFromRoot(nonRelativePath));
}
}
@ -220,8 +222,6 @@ public class ServerPageExporter extends FileExporter {
} else {
export(to, resource);
}
exportPaths.put(resourceName, toRelativePathFromRoot(resourceName));
}
private String toRelativePathFromRoot(String resourceName) {
@ -230,7 +230,7 @@ public class ServerPageExporter extends FileExporter {
}
private String toNonRelativePath(String resourceName) {
return StringUtils.remove(resourceName, "../");
return StringUtils.remove(StringUtils.remove(resourceName, "../"), "./");
}
}

View File

@ -52,7 +52,7 @@ public enum Html {
LINK("<a class=\"link\" href=\"${0}\">${1}</a>"),
LINK_EXTERNAL("<a class=\"link\" rel=\"noopener noreferrer\" target=\"_blank\" href=\"${0}\">${1}</a>"),
BACK_BUTTON_NETWORK("<a class=\"btn bg-plan btn-icon-split\" href=\"../network/\">" +
BACK_BUTTON_NETWORK("<a class=\"btn bg-plan btn-icon-split\" href=\"../network\">" +
"<span class=\"icon text-white-50\">" +
"<i class=\"fas fa-fw fa-arrow-left\"></i><i class=\"fas fa-fw fa-cloud\"></i>" +
"</span>" +

View File

@ -18,9 +18,7 @@ package com.djrapitops.plan.delivery.webserver;
import com.djrapitops.plan.delivery.web.ResolverService;
import com.djrapitops.plan.delivery.web.ResolverSvc;
import com.djrapitops.plan.delivery.web.resolver.Resolver;
import com.djrapitops.plan.delivery.web.resolver.URIPath;
import com.djrapitops.plan.delivery.web.resolver.URIQuery;
import com.djrapitops.plan.delivery.web.resolver.*;
import com.djrapitops.plan.delivery.webserver.auth.Authentication;
import com.djrapitops.plan.delivery.webserver.pages.*;
import com.djrapitops.plan.delivery.webserver.pages.json.RootJSONResolver;
@ -91,12 +89,13 @@ public class ResponseResolver extends CompositePageResolver {
}
public void registerPages() {
resolverService.registerResolver("Plan", "/debug", debugPageResolver);
resolverService.registerResolver("Plan", "/players", playersPageResolver);
resolverService.registerResolver("Plan", "/player", playerPageResolver);
registerPage("network", serverPageResolver);
registerPage("server", serverPageResolver);
String pluginName = "Plan";
resolverService.registerResolver(pluginName, "/debug", debugPageResolver);
resolverService.registerResolver(pluginName, "/players", playersPageResolver);
resolverService.registerResolver(pluginName, "/player", playerPageResolver);
resolverService.registerResolver(pluginName, "/favicon.ico", noAuthResolverFor(responseFactory.faviconResponse()));
resolverService.registerResolver(pluginName, "/network", serverPageResolver);
resolverService.registerResolver(pluginName, "/server", serverPageResolver);
// TODO Figure out how to deal with stuff like this
registerPage("", new RootPageResolver(responseFactory, webServer.get(), serverInfo));
@ -104,6 +103,10 @@ public class ResponseResolver extends CompositePageResolver {
registerPage("v1", rootJSONResolver);
}
public NoAuthResolver noAuthResolverFor(Response response) {
return (target, query) -> Optional.of(response);
}
public Response_old getResponse(Request request) {
try {
return tryToGetResponse(request);
@ -171,9 +174,6 @@ public class ResponseResolver extends CompositePageResolver {
if (target.endsWith(".png")) {
return responseFactory.imageResponse_old(resource);
}
if (target.endsWith("favicon.ico")) {
return responseFactory.faviconResponse_old();
}
if (target.endsWithAny(".woff", ".woff2", ".eot", ".ttf")) {
return responseFactory.fontResponse_old(resource);
}

View File

@ -17,19 +17,12 @@
package com.djrapitops.plan.delivery.webserver.pages;
import com.djrapitops.plan.delivery.rendering.html.Html;
import com.djrapitops.plan.delivery.webserver.Request;
import com.djrapitops.plan.delivery.webserver.RequestTarget;
import com.djrapitops.plan.delivery.web.resolver.*;
import com.djrapitops.plan.delivery.webserver.WebServer;
import com.djrapitops.plan.delivery.webserver.auth.Authentication;
import com.djrapitops.plan.delivery.webserver.response.ResponseFactory;
import com.djrapitops.plan.delivery.webserver.response.Response_old;
import com.djrapitops.plan.exceptions.WebUserAuthException;
import com.djrapitops.plan.exceptions.connection.ForbiddenException;
import com.djrapitops.plan.exceptions.connection.WebException;
import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
import dagger.Lazy;
@ -44,7 +37,7 @@ import java.util.UUID;
* @author Rsl1122
*/
@Singleton
public class ServerPageResolver implements PageResolver {
public class ServerPageResolver implements Resolver {
private final ResponseFactory responseFactory;
private final DBSystem dbSystem;
@ -65,45 +58,42 @@ public class ServerPageResolver implements PageResolver {
}
@Override
public Response_old resolve(Request request, RequestTarget target) throws WebException {
Optional<UUID> serverUUID = getServerUUID(target);
boolean proxy = serverInfo.getServer().isProxy();
if (serverUUID.isPresent()) {
checkDBState();
if (proxy && serverInfo.getServerUUID().equals(serverUUID.get())) {
return responseFactory.networkPageResponse_old();
}
return responseFactory.serverPageResponse_old(serverUUID.get());
} else {
// Redirect to base server page.
String directTo = proxy ? "/network" : "/server/" + Html.encodeToURL(serverInfo.getServer().getIdentifiableName());
return responseFactory.redirectResponse_old(webServer.get().getAccessAddress() + directTo);
}
}
private void checkDBState() throws ForbiddenException {
Database.State dbState = dbSystem.getDatabase().getState();
if (dbState != Database.State.OPEN) {
throw new ForbiddenException("Database is " + dbState.name() + " - Please try again later. You can check database status with /plan info");
}
}
private Optional<UUID> getServerUUID(RequestTarget target) {
if (!target.isEmpty()) {
try {
String serverName = target.get(0);
return dbSystem.getDatabase()
.query(ServerQueries.fetchServerMatchingIdentifier(serverName))
.map(Server::getUuid);
} catch (IllegalArgumentException ignore) {
/*ignored*/
}
}
return Optional.of(serverInfo.getServer().getUuid());
public boolean canAccess(WebUser permissions, URIPath target, URIQuery query) {
String firstPart = target.getPart(0).orElse("");
boolean forServerPage = firstPart.equalsIgnoreCase("server") && permissions.hasPermission("page.server");
boolean forNetworkPage = firstPart.equalsIgnoreCase("network") && permissions.hasPermission("page.network");
return forServerPage || forNetworkPage;
}
@Override
public boolean isAuthorized(Authentication auth, RequestTarget target) throws WebUserAuthException {
return auth.getWebUser().getPermLevel() <= 0;
public Optional<Response> resolve(URIPath target, URIQuery query) {
return getServerUUID(target)
.map(this::getServerPage)
.orElseGet(this::redirectToCurrentServer);
}
private Optional<Response> redirectToCurrentServer() {
String directTo = serverInfo.getServer().isProxy()
? "/network"
: "/server/" + Html.encodeToURL(serverInfo.getServer().getIdentifiableName());
return Optional.of(responseFactory.redirectResponse(webServer.get().getAccessAddress() + directTo));
}
private Optional<Response> getServerPage(UUID serverUUID) {
boolean toNetworkPage = serverInfo.getServer().isProxy() && serverInfo.getServerUUID().equals(serverUUID);
if (toNetworkPage) return Optional.of(responseFactory.networkPageResponse());
return Optional.of(responseFactory.serverPageResponse(serverUUID));
}
private Optional<UUID> getServerUUID(URIPath path) {
if (serverInfo.getServer().isProxy()
&& path.getPart(0).map("network"::equals).orElse(false)
&& !path.getPart(1).isPresent() // No slash at the end.
) {
return Optional.of(serverInfo.getServerUUID());
}
return path.getPart(1).flatMap(serverName -> dbSystem.getDatabase()
.query(ServerQueries.fetchServerMatchingIdentifier(serverName))
.map(Server::getUuid));
}
}

View File

@ -22,9 +22,7 @@ import com.djrapitops.plan.delivery.rendering.pages.PageFactory;
import com.djrapitops.plan.delivery.web.resolver.MimeType;
import com.djrapitops.plan.delivery.web.resolver.Response;
import com.djrapitops.plan.delivery.webserver.response.errors.*;
import com.djrapitops.plan.delivery.webserver.response.pages.PageResponse;
import com.djrapitops.plan.delivery.webserver.response.pages.RawDataResponse;
import com.djrapitops.plan.delivery.webserver.response.pages.RawPlayerDataResponse;
import com.djrapitops.plan.exceptions.WebUserAuthException;
import com.djrapitops.plan.exceptions.connection.NotFoundException;
import com.djrapitops.plan.settings.locale.Locale;
@ -74,7 +72,7 @@ public class ResponseFactory {
try {
return forPage(pageFactory.debugPage());
} catch (IOException e) {
return forInternalError("Failed to generate debug page", e);
return forInternalError(e, "Failed to generate debug page");
}
}
@ -85,7 +83,7 @@ public class ResponseFactory {
.build();
}
private Response forInternalError(String cause, Throwable error) {
private Response forInternalError(Throwable error, String cause) {
return Response.builder()
.setMimeType(MimeType.HTML)
.setContent(pageFactory.internalErrorPage(cause, error).toHtml())
@ -95,21 +93,21 @@ public class ResponseFactory {
public Response playersPageResponse() {
try {
Optional<Response> error = checkIfDBIsOpen();
Optional<Response> error = checkDbClosedError();
if (error.isPresent()) return error.get();
return forPage(pageFactory.playersPage());
} catch (IOException e) {
return forInternalError("Failed to generate players page", e);
return forInternalError(e, "Failed to generate players page");
}
}
private Optional<Response> checkIfDBIsOpen() {
private Optional<Response> checkDbClosedError() {
Database.State dbState = dbSystem.getDatabase().getState();
if (dbState != Database.State.OPEN) {
try {
return Optional.of(buildDBNotOpenResponse(dbState));
} catch (IOException e) {
return Optional.of(forInternalError("Database was not open, additionally failed to generate error page for that", e));
return Optional.of(forInternalError(e, "Database was not open, additionally failed to generate error page for that"));
}
}
return Optional.empty();
@ -139,29 +137,28 @@ public class ResponseFactory {
}
}
@Deprecated
public Response_old networkPageResponse_old() {
public Response networkPageResponse() {
Optional<Response> error = checkDbClosedError();
if (error.isPresent()) return error.get();
try {
return new PageResponse(pageFactory.networkPage());
return forPage(pageFactory.networkPage());
} catch (IOException e) {
return internalErrorResponse_old(e, "Failed to generate network page");
return forInternalError(e, "Failed to generate network page");
}
}
@Deprecated
public Response_old serverPageResponse_old(UUID serverUUID) throws NotFoundException {
public Response serverPageResponse(UUID serverUUID) {
Optional<Response> error = checkDbClosedError();
if (error.isPresent()) return error.get();
try {
return new PageResponse(pageFactory.serverPage(serverUUID));
return forPage(pageFactory.serverPage(serverUUID));
} catch (NotFoundException e) {
return notFound404(e.getMessage());
} catch (IOException e) {
return internalErrorResponse_old(e, "Failed to generate server page");
return forInternalError(e, "Failed to generate server page");
}
}
@Deprecated
public RawDataResponse rawPlayerPageResponse_old(UUID uuid) {
return new RawPlayerDataResponse(dbSystem.getDatabase().query(ContainerFetchQueries.fetchPlayerContainer(uuid)));
}
public Response rawPlayerPageResponse(UUID playerUUID) {
PlayerContainer player = dbSystem.getDatabase().query(ContainerFetchQueries.fetchPlayerContainer(playerUUID));
return Response.builder()
@ -219,9 +216,19 @@ public class ResponseFactory {
return new RedirectResponse(location);
}
@Deprecated
public Response_old faviconResponse_old() {
return new ByteResponse(ResponseType.X_ICON, "web/favicon.ico", files);
public Response redirectResponse(String location) {
return Response.builder().redirectTo(location).build();
}
public Response faviconResponse() {
try {
return Response.builder()
.setMimeType(MimeType.FAVICON)
.setContent(files.getCustomizableResourceOrDefault("web/favicon.ico").asBytes())
.build();
} catch (IOException e) {
return forInternalError(e, "Could not read favicon");
}
}
@Deprecated
@ -229,20 +236,10 @@ public class ResponseFactory {
return notFound404_old(locale.getString(ErrorPageLang.UNKNOWN_PAGE_404));
}
@Deprecated
public ErrorResponse uuidNotFound404_old() {
return notFound404_old(locale.getString(ErrorPageLang.UUID_404));
}
public Response uuidNotFound404() {
return notFound404(locale.getString(ErrorPageLang.UUID_404));
}
@Deprecated
public ErrorResponse playerNotFound404_old() {
return notFound404_old(locale.getString(ErrorPageLang.NOT_PLAYED_404));
}
public Response playerNotFound404() {
return notFound404(locale.getString(ErrorPageLang.NOT_PLAYED_404));
}
@ -265,7 +262,7 @@ public class ResponseFactory {
.setStatus(404)
.build();
} catch (IOException e) {
return forInternalError("Failed to generate 404 page with message '" + message + "'", e);
return forInternalError(e, "Failed to generate 404 page with message '" + message + "'");
}
}
@ -313,18 +310,7 @@ public class ResponseFactory {
} catch (IllegalStateException e) {
return playerNotFound404();
} catch (IOException e) {
return forInternalError("Failed to generate player page", e);
}
}
@Deprecated
public Response_old playerPageResponse_old(UUID playerUUID) {
try {
return new PageResponse(pageFactory.playerPage(playerUUID));
} catch (IllegalStateException e) {
return playerNotFound404_old();
} catch (IOException e) {
return internalErrorResponse_old(e, "Failed to generate player page");
return forInternalError(e, "Failed to generate player page");
}
}
}

View File

@ -103,6 +103,10 @@ public abstract class Response_old {
.orElse(new String(apiResponse.getBytes())));
response.setHeader("HTTP/1.1 " + apiResponse.getCode() + " ");
for (Map.Entry<String, String> header : apiResponse.getHeaders().entrySet()) {
if (header.getKey().equals("Content-Type")) {
response.type = header.getValue();
continue;
}
response.header += header.getKey() + ": " + header.getValue() + ";\r\n";
}
return response;
@ -132,6 +136,7 @@ public abstract class Response_old {
public void send(HttpExchange exchange, Locale locale, Theme theme) throws IOException {
responseHeaders.set("Content-Type", type);
responseHeaders.set("Content-Encoding", "gzip");
// TODO handle case of ByteResponse when "Accept-Ranges", "bytes" is set
exchange.sendResponseHeaders(getCode(), 0);
try (

View File

@ -63,6 +63,11 @@ public class FileResource implements Resource {
return new FileInputStream(file);
}
@Override
public byte[] asBytes() throws IOException {
return Files.readAllBytes(file.toPath());
}
@Override
public List<String> asLines() throws IOException {
return lines(file);

View File

@ -16,6 +16,9 @@
*/
package com.djrapitops.plan.storage.file;
import org.apache.commons.compress.utils.IOUtils;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@ -77,6 +80,17 @@ public class JarResource implements Resource {
return flat.toString();
}
@Override
public byte[] asBytes() throws IOException {
try (
InputStream in = asInputStream();
ByteArrayOutputStream out = new ByteArrayOutputStream()
) {
IOUtils.copy(in, out);
return out.toByteArray();
}
}
@Override
public String getResourceName() {
return resourceName;

View File

@ -36,6 +36,8 @@ public interface Resource {
*/
String getResourceName();
byte[] asBytes() throws IOException;
/**
* Get the resource as an InputStream.
*

View File

@ -54,4 +54,9 @@ public class StringCachingResource implements Resource {
ResourceCache.cache(implementation.getResourceName(), got);
return got;
}
@Override
public byte[] asBytes() throws IOException {
return implementation.asBytes();
}
}

View File

@ -58,4 +58,9 @@ public class StringResource implements Resource {
public String asString() {
return resource;
}
@Override
public byte[] asBytes() {
return resource.getBytes(StandardCharsets.UTF_8);
}
}

View File

@ -13,14 +13,14 @@
<title>Plan | Network</title>
<!-- Custom fonts for this template-->
<link href="vendor/fontawesome-free/css/all.min.css" rel="stylesheet">
<link href="./vendor/fontawesome-free/css/all.min.css" rel="stylesheet">
<link crossorigin="anonymous"
href="https://fonts.googleapis.com/css?family=Nunito:400,700,800,900&display=swap&subset=latin-ext"
rel="stylesheet">
<!-- Custom styles for this template-->
<link href="css/sb-admin-2.css" rel="stylesheet">
<link href="css/style.css" rel="stylesheet">
<link href="./css/sb-admin-2.css" rel="stylesheet">
<link href="./css/style.css" rel="stylesheet">
</head>
@ -41,7 +41,7 @@
<!-- Sidebar - Brand -->
<a class="sidebar-brand d-flex align-items-center justify-content-center">
<img class="w-22" src="img/Flaticon_circle.png">
<img class="w-22" src="./img/Flaticon_circle.png">
</a>
<!-- Divider -->
@ -777,41 +777,41 @@
</a>
<!-- Bootstrap core JavaScript-->
<script src="vendor/jquery/jquery.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="./vendor/jquery/jquery.min.js"></script>
<script src="./vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Core plugin JavaScript-->
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
<script src="./vendor/jquery-easing/jquery.easing.min.js"></script>
<!-- Page level plugins -->
<script src="vendor/datatables/jquery.dataTables.min.js"></script>
<script src="vendor/datatables/dataTables.bootstrap4.min.js"></script>
<script src="vendor/highcharts/highstock.js"></script>
<script src="vendor/highcharts/map.js"></script>
<script src="vendor/highcharts/world.js"></script>
<script src="vendor/highcharts/drilldown.js"></script>
<script src="vendor/highcharts/highcharts-more.js"></script>
<script src="vendor/highcharts/no-data-to-display.js"></script>
<script src="./vendor/datatables/jquery.dataTables.min.js"></script>
<script src="./vendor/datatables/dataTables.bootstrap4.min.js"></script>
<script src="./vendor/highcharts/highstock.js"></script>
<script src="./vendor/highcharts/map.js"></script>
<script src="./vendor/highcharts/world.js"></script>
<script src="./vendor/highcharts/drilldown.js"></script>
<script src="./vendor/highcharts/highcharts-more.js"></script>
<script src="./vendor/highcharts/no-data-to-display.js"></script>
<!-- Custom scripts for all pages-->
<script src="js/sb-admin-2.js"></script>
<script src="js/xmlhttprequests.js"></script>
<script src="js/color-selector.js"></script>
<script src="./js/sb-admin-2.js"></script>
<script src="./js/xmlhttprequests.js"></script>
<script src="./js/color-selector.js"></script>
<!-- Page level custom scripts -->
<script src="js/sessionAccordion.js"></script>
<script src="js/pingTable.js"></script>
<script src="js/graphs.js"></script>
<script src="js/network-values.js"></script>
<script src="./js/sessionAccordion.js"></script>
<script src="./js/pingTable.js"></script>
<script src="./js/graphs.js"></script>
<script src="./js/network-values.js"></script>
<script>
try {
setLoadingText('Calculating values..');
jsonRequest("../v1/network/overview", loadNetworkOverviewValues);
jsonRequest("../v1/network/servers", loadservers);
jsonRequest("../v1/network/sessionsOverview", loadSessionValues);
jsonRequest("../v1/network/playerbaseOverview", loadPlayerbaseOverviewValues);
jsonRequest("../v1/sessions", loadSessionAccordion);
jsonRequest("./v1/network/overview", loadNetworkOverviewValues);
jsonRequest("./v1/network/servers", loadservers);
jsonRequest("./v1/network/sessionsOverview", loadSessionValues);
jsonRequest("./v1/network/playerbaseOverview", loadPlayerbaseOverviewValues);
jsonRequest("./v1/sessions", loadSessionAccordion);
setLoadingText('Rendering graphs..');
var v = {
@ -858,7 +858,7 @@
}
};
jsonRequest("../v1/graph?type=playersOnline&server=${serverUUID}", function (json, error) {
jsonRequest("./v1/graph?type=playersOnline&server=${serverUUID}", function (json, error) {
if (json) {
var series = {
playersOnline: {
@ -872,7 +872,7 @@
}
});
jsonRequest("../v1/graph?type=uniqueAndNew", function (json, error) {
jsonRequest("./v1/graph?type=uniqueAndNew", function (json, error) {
if (json) {
var uniquePlayers = {
name: s.name.uniquePlayers, type: s.type.spline, tooltip: s.tooltip.zeroDecimals,
@ -888,7 +888,7 @@
}
});
jsonRequest("../v1/graph?type=serverPie", function (json, error) {
jsonRequest("./v1/graph?type=serverPie", function (json, error) {
if (json) {
serverPieSeries = {
name: 'Server Playtime',
@ -902,7 +902,7 @@
}
});
jsonRequest("../v1/graph?type=activity", function (json, error) {
jsonRequest("./v1/graph?type=activity", function (json, error) {
if (json) {
activityPie('activityPie', {
name: 'Players', colorByPoint: true, data: json.activity_pie_series
@ -914,7 +914,7 @@
}
});
jsonRequest("../v1/graph?type=geolocation", function (json, error) {
jsonRequest("./v1/graph?type=geolocation", function (json, error) {
if (json) {
var geolocationSeries = {
name: 'Players',
@ -943,7 +943,7 @@
setLoadingText('Sorting out plugin tables..');
jsonRequest("../v1/network/pingTable", loadPingTable);
jsonRequest("./v1/network/pingTable", loadPingTable);
$('.player-plugin-table').DataTable({
responsive: true

View File

@ -12,14 +12,14 @@
<title>Plan | Server Analysis</title>
<!-- Custom fonts for this template-->
<link href="vendor/fontawesome-free/css/all.min.css" rel="stylesheet">
<link href="../vendor/fontawesome-free/css/all.min.css" rel="stylesheet">
<link crossorigin="anonymous"
href="https://fonts.googleapis.com/css?family=Nunito:400,700,800,900&display=swap&subset=latin-ext"
rel="stylesheet">
<!-- Custom styles for this template-->
<link href="css/sb-admin-2.css" rel="stylesheet">
<link href="css/style.css" rel="stylesheet">
<link href="../css/sb-admin-2.css" rel="stylesheet">
<link href="../css/style.css" rel="stylesheet">
</head>
<body id="page-top">
@ -39,7 +39,7 @@
<!-- Sidebar - Brand -->
<a class="sidebar-brand d-flex align-items-center justify-content-center">
<img class="w-22" src="img/Flaticon_circle.png">
<img class="w-22" src="../img/Flaticon_circle.png">
</a>
<!-- Divider -->
@ -1252,35 +1252,35 @@
</a>
<!-- Bootstrap core JavaScript-->
<script src="vendor/jquery/jquery.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="../vendor/jquery/jquery.min.js"></script>
<script src="../vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Core plugin JavaScript-->
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
<script src="../vendor/jquery-easing/jquery.easing.min.js"></script>
<!-- Page level plugins -->
<script src="vendor/datatables/jquery.dataTables.min.js"></script>
<script src="vendor/datatables/dataTables.bootstrap4.min.js"></script>
<script src="vendor/highcharts/highstock.js"></script>
<script src="vendor/highcharts/map.js"></script>
<script src="vendor/highcharts/world.js"></script>
<script src="vendor/highcharts/drilldown.js"></script>
<script src="vendor/highcharts/highcharts-more.js"></script>
<script src="vendor/highcharts/no-data-to-display.js"></script>
<link href='vendor/fullcalendar/fullcalendar.min.css' rel='stylesheet'/>
<script src='vendor/momentjs/moment.js'></script>
<script src='vendor/fullcalendar/fullcalendar.min.js'></script>
<script src="../vendor/datatables/jquery.dataTables.min.js"></script>
<script src="../vendor/datatables/dataTables.bootstrap4.min.js"></script>
<script src="../vendor/highcharts/highstock.js"></script>
<script src="../vendor/highcharts/map.js"></script>
<script src="../vendor/highcharts/world.js"></script>
<script src="../vendor/highcharts/drilldown.js"></script>
<script src="../vendor/highcharts/highcharts-more.js"></script>
<script src="../vendor/highcharts/no-data-to-display.js"></script>
<link href='../vendor/fullcalendar/fullcalendar.min.css' rel='stylesheet'/>
<script src='../vendor/momentjs/moment.js'></script>
<script src='../vendor/fullcalendar/fullcalendar.min.js'></script>
<!-- Custom scripts for all pages-->
<script src="js/sb-admin-2.js"></script>
<script src="js/xmlhttprequests.js"></script>
<script src="js/color-selector.js"></script>
<script src="../js/sb-admin-2.js"></script>
<script src="../js/xmlhttprequests.js"></script>
<script src="../js/color-selector.js"></script>
<!-- Page level custom scripts -->
<script src="js/sessionAccordion.js"></script>
<script src="js/pingTable.js"></script>
<script src="js/graphs.js"></script>
<script src="js/server-values.js"></script>
<script src="../js/sessionAccordion.js"></script>
<script src="../js/pingTable.js"></script>
<script src="../js/graphs.js"></script>
<script src="../js/server-values.js"></script>
<script>
try {

View File

@ -68,4 +68,9 @@ public class SpongeAssetResource implements Resource {
public String asString() throws IOException {
return asset.readString(StandardCharsets.UTF_8);
}
@Override
public byte[] asBytes() throws IOException {
return asset.readBytes();
}
}