Skip to content

Commit 823ee61

Browse files
committed
HTTPD performance improvements
1 parent 94cb2a9 commit 823ee61

20 files changed

Lines changed: 367 additions & 188 deletions

src/main/java/dev/plex/HTTPDModule.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.eclipse.jetty.ee10.servlet.ServletHandler;
2020
import org.eclipse.jetty.ee10.servlet.ServletHolder;
2121
import org.eclipse.jetty.server.*;
22+
import org.eclipse.jetty.util.thread.QueuedThreadPool;
2223

2324
import java.io.File;
2425
import java.util.EnumSet;
@@ -66,18 +67,29 @@ public void enable()
6667

6768
serverThread = new Thread(() ->
6869
{
69-
Server server = new Server();
70+
int maxThreads = moduleConfig.getInt("server.threads.max", 16);
71+
int minThreads = Math.min(moduleConfig.getInt("server.threads.min", 2), maxThreads);
72+
int idleTimeout = moduleConfig.getInt("server.threads.idle-timeout-ms", 30_000);
73+
QueuedThreadPool pool = new QueuedThreadPool(maxThreads, minThreads, idleTimeout);
74+
pool.setName("Plex-HTTPD");
75+
pool.setDaemon(true);
76+
77+
Server server = new Server(pool);
7078
ServletHandler servletHandler = new ServletHandler();
7179

7280
context = new ServletContextHandler(ServletContextHandler.SESSIONS);
7381
context.setHandler(servletHandler);
7482
context.setContextPath("/");
7583
HttpConfiguration configuration = new HttpConfiguration();
7684
configuration.addCustomizer(new ForwardedRequestCustomizer());
85+
configuration.setRequestHeaderSize(moduleConfig.getInt("server.limits.request-header-bytes", 8 * 1024));
86+
configuration.setSendServerVersion(false);
7787
HttpConnectionFactory factory = new HttpConnectionFactory(configuration);
7888
ServerConnector connector = new ServerConnector(server, factory);
7989
connector.setHost(moduleConfig.getString("server.bind-address"));
8090
connector.setPort(moduleConfig.getInt("server.port"));
91+
connector.setIdleTimeout(moduleConfig.getLong("server.limits.idle-timeout-ms", 15_000L));
92+
connector.setAcceptQueueSize(moduleConfig.getInt("server.limits.accept-queue", 32));
8193

8294
context.addFilter(new FilterHolder(new RateLimitFilter()), "/*", EnumSet.of(DispatcherType.REQUEST));
8395

src/main/java/dev/plex/request/AbstractServlet.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.io.InputStreamReader;
1717
import java.lang.reflect.InvocationTargetException;
1818
import java.lang.reflect.Method;
19+
import java.net.URLEncoder;
1920
import java.nio.charset.StandardCharsets;
2021
import java.text.CharacterIterator;
2122
import java.text.StringCharacterIterator;
@@ -153,7 +154,20 @@ public static AuthenticatedUser currentStaff(HttpServletRequest request)
153154

154155
public static String signInPrompt(String action)
155156
{
156-
return "You must <a class=\"text-primary underline\" href=\"/oauth2/login\">sign in</a> as staff " + action + ".";
157+
return signInPrompt(null, action);
158+
}
159+
160+
public static String signInPrompt(HttpServletRequest request, String action)
161+
{
162+
String href = "/oauth2/login";
163+
if (request != null)
164+
{
165+
String path = getRequestPath(request);
166+
String query = request.getQueryString();
167+
String returnTo = query == null || query.isEmpty() ? path : path + "?" + query;
168+
href = href + "?return_to=" + URLEncoder.encode(returnTo, StandardCharsets.UTF_8);
169+
}
170+
return "You must <a class=\"text-primary underline\" href=\"" + href + "\">sign in</a> as staff " + action + ".";
157171
}
158172

159173
public static String readFile(InputStream filename)

src/main/java/dev/plex/request/SchematicUploadServlet.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
3030
AuthenticatedUser user = AbstractServlet.currentStaff(request);
3131
if (user == null)
3232
{
33-
response.getWriter().println(schematicUploadBadHTML(AbstractServlet.signInPrompt("to upload schematics")));
33+
response.getWriter().println(schematicUploadBadHTML(AbstractServlet.signInPrompt(request, "to upload schematics")));
3434
return;
3535
}
3636
File worldeditFolder = HTTPDModule.getWorldeditFolder();

src/main/java/dev/plex/request/impl/AuthenticationEndpoint.java

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,14 @@
1414
import org.json.JSONObject;
1515

1616
import java.io.IOException;
17+
import java.net.URLDecoder;
18+
import java.net.URLEncoder;
19+
import java.nio.charset.StandardCharsets;
1720

1821
public class AuthenticationEndpoint extends AbstractServlet
1922
{
23+
private static final String RETURN_TO_COOKIE = "plex_return_to";
24+
2025
@GetMapping(endpoint = "/oauth2/login")
2126
public String login(HttpServletRequest request, HttpServletResponse response) throws IOException
2227
{
@@ -26,6 +31,20 @@ public String login(HttpServletRequest request, HttpServletResponse response) th
2631
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Authentication is not enabled.");
2732
return null;
2833
}
34+
35+
String returnTo = sanitizeReturnTo(request.getParameter("return_to"));
36+
String cookieValue = returnTo == null ? "" : URLEncoder.encode(returnTo, StandardCharsets.UTF_8);
37+
Cookie returnCookie = new Cookie(RETURN_TO_COOKIE, cookieValue);
38+
returnCookie.setHttpOnly(true);
39+
returnCookie.setPath("/");
40+
returnCookie.setMaxAge(returnTo == null ? 0 : 600);
41+
returnCookie.setAttribute("SameSite", "Lax");
42+
if (request.isSecure() || "https".equalsIgnoreCase(request.getHeader("X-Forwarded-Proto")))
43+
{
44+
returnCookie.setSecure(true);
45+
}
46+
response.addCookie(returnCookie);
47+
2948
response.sendRedirect(provider.buildAuthorizeUrl(request));
3049
return null;
3150
}
@@ -54,7 +73,12 @@ public String callback(HttpServletRequest request, HttpServletResponse response)
5473
+ "<p>" + escape(e.getMessage()) + "</p>"
5574
+ "<p><a href=\"/oauth2/login\">Try again</a></p>";
5675
}
57-
response.sendRedirect("/");
76+
77+
String raw = readCookie(request, RETURN_TO_COOKIE);
78+
String decoded = raw == null || raw.isEmpty() ? null : URLDecoder.decode(raw, StandardCharsets.UTF_8);
79+
String target = sanitizeReturnTo(decoded);
80+
clearReturnToCookie(request, response);
81+
response.sendRedirect(target == null ? "/" : target);
5882
return null;
5983
}
6084

@@ -103,19 +127,51 @@ public String me(HttpServletRequest request, HttpServletResponse response)
103127
}
104128

105129
private static String readSessionCookie(HttpServletRequest request)
130+
{
131+
return readCookie(request, OAuth2Provider.SESSION_COOKIE);
132+
}
133+
134+
private static String readCookie(HttpServletRequest request, String name)
106135
{
107136
Cookie[] cookies = request.getCookies();
108137
if (cookies == null) return null;
109138
for (Cookie cookie : cookies)
110139
{
111-
if (OAuth2Provider.SESSION_COOKIE.equals(cookie.getName()))
140+
if (name.equals(cookie.getName()))
112141
{
113142
return cookie.getValue();
114143
}
115144
}
116145
return null;
117146
}
118147

148+
private void clearReturnToCookie(HttpServletRequest request, HttpServletResponse response)
149+
{
150+
Cookie clear = new Cookie(RETURN_TO_COOKIE, "");
151+
clear.setHttpOnly(true);
152+
clear.setPath("/");
153+
clear.setMaxAge(0);
154+
clear.setAttribute("SameSite", "Lax");
155+
if (request.isSecure() || "https".equalsIgnoreCase(request.getHeader("X-Forwarded-Proto")))
156+
{
157+
clear.setSecure(true);
158+
}
159+
response.addCookie(clear);
160+
}
161+
162+
private static String sanitizeReturnTo(String value)
163+
{
164+
if (value == null || value.isEmpty()) return null;
165+
if (!value.startsWith("/")) return null;
166+
if (value.startsWith("//") || value.startsWith("/\\")) return null;
167+
for (int i = 0; i < value.length(); i++)
168+
{
169+
char c = value.charAt(i);
170+
if (c == '\n' || c == '\r' || c == '\\') return null;
171+
}
172+
return value;
173+
}
174+
119175
private static String escape(String s)
120176
{
121177
if (s == null) return "";

src/main/java/dev/plex/request/impl/CommandsEndpoint.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ private static String renderSection(String plugin, List<Command> commands)
7777
<svg class="size-4 text-muted-foreground transition-transform group-open:rotate-90" aria-hidden="true"><use href="#i-arrow-right"/></svg>
7878
%s
7979
</span>
80-
<span class="font-mono text-[11px] uppercase tracking-wider text-muted-foreground">
80+
<span class="text-sm text-muted-foreground">
8181
%d %s
8282
</span>
8383
</summary>
@@ -113,11 +113,11 @@ private static String renderCard(Command c)
113113
%s
114114
</header>
115115
%s
116-
<dl class="mt-3 grid grid-cols-[max-content_1fr] gap-x-3 gap-y-1.5 border-t border-border/60 pt-3 font-mono text-[11px]">
117-
<dt class="text-muted-foreground uppercase tracking-wider">usage</dt>
118-
<dd class="text-foreground/80 break-all">%s</dd>
119-
<dt class="text-muted-foreground uppercase tracking-wider">perm</dt>
120-
<dd class="text-foreground/80 break-all">%s</dd>
116+
<dl class="mt-3 grid grid-cols-[max-content_1fr] gap-x-3 gap-y-1.5 border-t border-border/60 pt-3 text-xs">
117+
<dt class="text-muted-foreground">usage</dt>
118+
<dd class="font-mono text-foreground/80 break-all">%s</dd>
119+
<dt class="text-muted-foreground">perm</dt>
120+
<dd class="font-mono text-foreground/80 break-all">%s</dd>
121121
</dl>
122122
</article>
123123
""".formatted(searchBlob, name, aliasMarkup, descMarkup, usage, permission);

src/main/java/dev/plex/request/impl/IndefBansEndpoint.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public String getBans(HttpServletRequest request, HttpServletResponse response)
1616
AuthenticatedUser user = currentStaff(request);
1717
if (user == null)
1818
{
19-
return indefbansHTML(signInPrompt("to view this page"));
19+
return indefbansHTML(signInPrompt(request, "to view this page"));
2020
}
2121

2222
response.setHeader("content-type", "application/json");

src/main/java/dev/plex/request/impl/IndefBansUIEndpoint.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public String getBans(HttpServletRequest request, HttpServletResponse response)
1919
AuthenticatedUser viewer = currentStaff(request);
2020
if (viewer == null)
2121
{
22-
return errorHTML(signInPrompt("to view this page"));
22+
return errorHTML(signInPrompt(request, "to view this page"));
2323
}
2424

2525
List<IndefiniteBan> bans = Plex.get().getPunishmentManager().getIndefiniteBans();
@@ -66,24 +66,24 @@ private static String renderCard(IndefiniteBan ban)
6666
StringBuilder rows = new StringBuilder();
6767
if (!ban.getUsernames().isEmpty())
6868
{
69-
rows.append(renderRow("users", "text-foreground/90 break-all", ban.getUsernames().stream().map(IndefBansUIEndpoint::escapeHtml).toList()));
69+
rows.append(renderRow("Users", "text-foreground/90 break-all", ban.getUsernames().stream().map(IndefBansUIEndpoint::escapeHtml).toList()));
7070
}
7171
if (!ban.getUuids().isEmpty())
7272
{
73-
rows.append(renderRow("uuids", "text-foreground/55 break-all", ban.getUuids().stream().map(UUID::toString).toList()));
73+
rows.append(renderRow("UUIDs", "font-mono text-foreground/55 break-all", ban.getUuids().stream().map(UUID::toString).toList()));
7474
}
7575
if (!ban.getIps().isEmpty())
7676
{
77-
rows.append(renderRow("ips", "text-warning break-all", ban.getIps().stream().map(IndefBansUIEndpoint::escapeHtml).toList()));
77+
rows.append(renderRow("IPs", "font-mono text-warning break-all", ban.getIps().stream().map(IndefBansUIEndpoint::escapeHtml).toList()));
7878
}
7979

8080
return """
8181
<article class="ring-card rounded-2xl bg-card p-5">
8282
<header class="flex flex-wrap items-baseline justify-between gap-3">
8383
<p class="text-sm">%s</p>
84-
<span class="font-mono text-[11px] uppercase tracking-wider text-muted-foreground">%d %s</span>
84+
<span class="text-xs text-muted-foreground">%d %s</span>
8585
</header>
86-
<dl class="mt-4 grid grid-cols-[max-content_1fr] gap-x-4 gap-y-2 border-t border-border/60 pt-3 font-mono text-[11px]">
86+
<dl class="mt-4 grid grid-cols-[max-content_1fr] gap-x-4 gap-y-2 border-t border-border/60 pt-3 text-xs">
8787
%s
8888
</dl>
8989
</article>
@@ -98,7 +98,7 @@ private static String renderRow(String label, String valueClasses, List<String>
9898
items.append("<span>").append(value).append("</span>");
9999
}
100100
return """
101-
<dt class="text-muted-foreground uppercase tracking-wider">%s</dt>
101+
<dt class="text-muted-foreground">%s</dt>
102102
<dd class="flex flex-wrap gap-x-3 gap-y-1 %s">%s</dd>
103103
""".formatted(label, valueClasses, items);
104104
}

0 commit comments

Comments
 (0)