Some changes to remove 404-responses from the webserver on empty map-tiles

This commit is contained in:
Blue (Lukas Rieger) 2021-03-28 13:25:39 +02:00
parent bd5071eb61
commit 481a452b9c
No known key found for this signature in database
GPG Key ID: 904C4995F9E1F800
7 changed files with 95 additions and 75 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@ build/
bin/ bin/
doc/ doc/
logs/ logs/
.run/
run/ run/
node_modules/ node_modules/

@ -1 +1 @@
Subproject commit f3dc8d5dd478475cf8a1f0f1b166843821109ad2 Subproject commit 4bef101c6ee6ddef23049287cc1a3736d954979e

View File

@ -31,7 +31,6 @@
import de.bluecolored.bluemap.api.marker.Marker; import de.bluecolored.bluemap.api.marker.Marker;
import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.ConfigurationNode;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
public abstract class MarkerImpl implements Marker { public abstract class MarkerImpl implements Marker {

View File

@ -24,24 +24,7 @@
*/ */
package de.bluecolored.bluemap.common.plugin; package de.bluecolored.bluemap.common.plugin;
import java.io.DataInputStream; import de.bluecolored.bluemap.common.*;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import de.bluecolored.bluemap.common.BlueMapService;
import de.bluecolored.bluemap.common.InterruptableReentrantLock;
import de.bluecolored.bluemap.common.MapType;
import de.bluecolored.bluemap.common.MissingResourcesException;
import de.bluecolored.bluemap.common.RenderManager;
import de.bluecolored.bluemap.common.api.BlueMapAPIImpl; import de.bluecolored.bluemap.common.api.BlueMapAPIImpl;
import de.bluecolored.bluemap.common.live.LiveAPIRequestHandler; import de.bluecolored.bluemap.common.live.LiveAPIRequestHandler;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
@ -59,6 +42,14 @@
import de.bluecolored.bluemap.core.webserver.WebServer; import de.bluecolored.bluemap.core.webserver.WebServer;
import de.bluecolored.bluemap.core.world.World; import de.bluecolored.bluemap.core.world.World;
import java.io.*;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class Plugin { public class Plugin {
public static final String PLUGIN_ID = "bluemap"; public static final String PLUGIN_ID = "bluemap";
@ -207,7 +198,10 @@ public void load() throws IOException, ParseResourceException {
//start skin updater //start skin updater
if (pluginConfig.isLiveUpdatesEnabled()) { if (pluginConfig.isLiveUpdatesEnabled()) {
this.skinUpdater = new PlayerSkinUpdater(new File(renderConfig.getWebRoot(), "assets" + File.separator + "playerheads")); this.skinUpdater = new PlayerSkinUpdater(
new File(renderConfig.getWebRoot(), "assets" + File.separator + "playerheads"),
new File(renderConfig.getWebRoot(), "assets" + File.separator + "steve.png")
);
serverInterface.registerListener(skinUpdater); serverInterface.registerListener(skinUpdater);
} }

View File

@ -24,7 +24,13 @@
*/ */
package de.bluecolored.bluemap.common.plugin.skins; package de.bluecolored.bluemap.common.plugin.skins;
import java.awt.Graphics2D; import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import de.bluecolored.bluemap.core.logger.Logger;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -33,19 +39,7 @@
import java.net.URL; import java.net.URL;
import java.util.Base64; import java.util.Base64;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.imageio.ImageIO;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import de.bluecolored.bluemap.core.logger.Logger;
public class PlayerSkin { public class PlayerSkin {
@ -57,23 +51,32 @@ public PlayerSkin(UUID uuid) {
this.lastUpdate = -1; this.lastUpdate = -1;
} }
public void update(File storageFolder) { public void update(File storageFolder, File fallback) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
if (lastUpdate > 0 && lastUpdate + 600000 > now) return; // only update if skin is older than 10 minutes if (lastUpdate > 0 && lastUpdate + 600000 > now) return; // only update if skin is older than 10 minutes
lastUpdate = now; lastUpdate = now;
new Thread(() -> { new Thread(() -> {
BufferedImage head = null;
try { try {
Future<BufferedImage> futureSkin = loadSkin(); Future<BufferedImage> futureSkin = loadSkin();
BufferedImage skin = futureSkin.get(10, TimeUnit.SECONDS); BufferedImage skin = futureSkin.get(10, TimeUnit.SECONDS);
BufferedImage head = createHead(skin); head = createHead(skin);
ImageIO.write(head, "png", new File(storageFolder, uuid.toString() + ".png"));
} catch (ExecutionException | TimeoutException e) { } catch (ExecutionException | TimeoutException e) {
Logger.global.logDebug("Failed to load player-skin from mojang-servers: " + e); Logger.global.logDebug("Failed to load player-skin from mojang-servers: " + e);
} catch (InterruptedException ignore) {
Thread.currentThread().interrupt();
return;
}
try {
if (head == null) head = ImageIO.read(fallback);
ImageIO.write(head, "png", new File(storageFolder, uuid.toString() + ".png"));
} catch (IOException e) { } catch (IOException e) {
Logger.global.logError("Failed to write player-head image!", e); Logger.global.logError("Failed to write player-head image!", e);
} catch (InterruptedException ignore) { Thread.currentThread().interrupt(); } }
}).start(); }).start();
} }

View File

@ -24,24 +24,28 @@
*/ */
package de.bluecolored.bluemap.common.plugin.skins; package de.bluecolored.bluemap.common.plugin.skins;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import org.apache.commons.io.FileUtils;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
public class PlayerSkinUpdater implements ServerEventListener { public class PlayerSkinUpdater implements ServerEventListener {
private File storageFolder; private File storageFolder;
private File defaultSkin;
private Map<UUID, PlayerSkin> skins; private final Map<UUID, PlayerSkin> skins;
public PlayerSkinUpdater(File storageFolder) { public PlayerSkinUpdater(File storageFolder, File defaultSkin) throws IOException {
this.storageFolder = storageFolder; this.storageFolder = storageFolder;
this.defaultSkin = defaultSkin;
this.skins = new ConcurrentHashMap<>(); this.skins = new ConcurrentHashMap<>();
this.storageFolder.mkdirs(); FileUtils.forceMkdir(this.storageFolder);
} }
public void updateSkin(UUID playerUuid) { public void updateSkin(UUID playerUuid) {
@ -52,7 +56,7 @@ public void updateSkin(UUID playerUuid) {
skins.put(playerUuid, skin); skins.put(playerUuid, skin);
} }
skin.update(storageFolder); skin.update(storageFolder, defaultSkin);
} }
@Override @Override
@ -60,4 +64,20 @@ public void onPlayerJoin(UUID playerUuid) {
updateSkin(playerUuid); updateSkin(playerUuid);
} }
public File getStorageFolder() {
return storageFolder;
}
public void setStorageFolder(File storageFolder) {
this.storageFolder = storageFolder;
}
public File getDefaultSkin() {
return defaultSkin;
}
public void setDefaultSkin(File defaultSkin) {
this.defaultSkin = defaultSkin;
}
} }

View File

@ -24,12 +24,14 @@
*/ */
package de.bluecolored.bluemap.core.web; package de.bluecolored.bluemap.core.web;
import java.io.ByteArrayInputStream; import de.bluecolored.bluemap.core.webserver.HttpRequest;
import java.io.ByteArrayOutputStream; import de.bluecolored.bluemap.core.webserver.HttpRequestHandler;
import java.io.File; import de.bluecolored.bluemap.core.webserver.HttpResponse;
import java.io.FileInputStream; import de.bluecolored.bluemap.core.webserver.HttpStatusCode;
import java.io.FileNotFoundException; import org.apache.commons.io.IOUtils;
import java.io.IOException; import org.apache.commons.lang3.time.DateFormatUtils;
import java.io.*;
import java.nio.file.InvalidPathException; import java.nio.file.InvalidPathException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
@ -40,26 +42,22 @@
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import de.bluecolored.bluemap.core.webserver.HttpRequest;
import de.bluecolored.bluemap.core.webserver.HttpRequestHandler;
import de.bluecolored.bluemap.core.webserver.HttpResponse;
import de.bluecolored.bluemap.core.webserver.HttpStatusCode;
public class FileRequestHandler implements HttpRequestHandler { public class FileRequestHandler implements HttpRequestHandler {
private static final long DEFLATE_MIN_SIZE = 10L * 1024L; private static final long DEFLATE_MIN_SIZE = 10L * 1024L;
private static final long DEFLATE_MAX_SIZE = 10L * 1024L * 1024L; private static final long DEFLATE_MAX_SIZE = 10L * 1024L * 1024L;
private static final long INFLATE_MAX_SIZE = 10L * 1024L * 1024L; private static final long INFLATE_MAX_SIZE = 10L * 1024L * 1024L;
private Path webRoot; private final Path webRoot;
private String serverName; private final String serverName;
private final File emptyTileFile;
public FileRequestHandler(Path webRoot, String serverName) { public FileRequestHandler(Path webRoot, String serverName) {
this.webRoot = webRoot; this.webRoot = webRoot.normalize();
this.serverName = serverName; this.serverName = serverName;
this.emptyTileFile = webRoot.resolve("assets").resolve("emptyTile.json").toFile();
} }
@Override @Override
@ -84,7 +82,7 @@ public HttpResponse handle(HttpRequest request) {
private HttpResponse generateResponse(HttpRequest request) { private HttpResponse generateResponse(HttpRequest request) {
String path = request.getPath(); String path = request.getPath();
//normalize path // normalize path
if (path.startsWith("/")) path = path.substring(1); if (path.startsWith("/")) path = path.substring(1);
if (path.endsWith("/")) path = path.substring(0, path.length() - 1); if (path.endsWith("/")) path = path.substring(0, path.length() - 1);
@ -95,12 +93,12 @@ private HttpResponse generateResponse(HttpRequest request) {
return new HttpResponse(HttpStatusCode.NOT_FOUND); return new HttpResponse(HttpStatusCode.NOT_FOUND);
} }
//can we use deflation? // can we use deflation?
boolean isDeflationPossible = request.getLowercaseHeader("Accept-Encoding").contains("gzip"); boolean isDeflationPossible = request.getLowercaseHeader("Accept-Encoding").contains("gzip");
boolean isDeflated = false; boolean isDeflated = false;
//check if file is in web-root // check if file is in web-root
if (!filePath.normalize().startsWith(webRoot.normalize())){ if (!filePath.normalize().startsWith(webRoot)){
return new HttpResponse(HttpStatusCode.FORBIDDEN); return new HttpResponse(HttpStatusCode.FORBIDDEN);
} }
@ -128,7 +126,12 @@ private HttpResponse generateResponse(HttpRequest request) {
isDeflated = true; isDeflated = true;
} }
if (!file.exists()){ if (!file.exists() && file.toPath().startsWith(webRoot.resolve("data"))){
file = emptyTileFile;
isDeflated = false;
}
if (!file.exists() || file.isDirectory()) {
return new HttpResponse(HttpStatusCode.NOT_FOUND); return new HttpResponse(HttpStatusCode.NOT_FOUND);
} }
@ -140,12 +143,12 @@ private HttpResponse generateResponse(HttpRequest request) {
} }
} }
//check if file is still in web-root // check if file is still in web-root and is not a directory
if (!file.toPath().normalize().startsWith(webRoot.normalize())){ if (!file.toPath().normalize().startsWith(webRoot) || file.isDirectory()){
return new HttpResponse(HttpStatusCode.FORBIDDEN); return new HttpResponse(HttpStatusCode.FORBIDDEN);
} }
//check modified // check modified
long lastModified = file.lastModified(); long lastModified = file.lastModified();
Set<String> modStringSet = request.getHeader("If-Modified-Since"); Set<String> modStringSet = request.getHeader("If-Modified-Since");
if (!modStringSet.isEmpty()){ if (!modStringSet.isEmpty()){
@ -154,7 +157,7 @@ private HttpResponse generateResponse(HttpRequest request) {
if (since + 1000 >= lastModified){ if (since + 1000 >= lastModified){
return new HttpResponse(HttpStatusCode.NOT_MODIFIED); return new HttpResponse(HttpStatusCode.NOT_MODIFIED);
} }
} catch (IllegalArgumentException e){} } catch (IllegalArgumentException ignored){}
} }
//check ETag //check ETag
@ -174,7 +177,7 @@ private HttpResponse generateResponse(HttpRequest request) {
response.addHeader("Cache-Control", "max-age=" + TimeUnit.HOURS.toSeconds(1)); response.addHeader("Cache-Control", "max-age=" + TimeUnit.HOURS.toSeconds(1));
//add content type header //add content type header
String filetype = file.getName().toString(); String filetype = file.getName();
if (filetype.endsWith(".gz")) filetype = filetype.substring(0, filetype.length() - 3); if (filetype.endsWith(".gz")) filetype = filetype.substring(0, filetype.length() - 3);
int pointIndex = filetype.lastIndexOf('.'); int pointIndex = filetype.lastIndexOf('.');
if (pointIndex >= 0) filetype = filetype.substring(pointIndex + 1); if (pointIndex >= 0) filetype = filetype.substring(pointIndex + 1);