Skip to content

Commit bd4e4e2

Browse files
Refactor Server CORS handlers and update picoserve to support method routing
- Refactored `Server.scala` to use the `.OPTIONS()` handler builder method from `picoserve` and deduplicated the `OPTIONS` CORS response logic into a common `getOptionsResponse` helper. - Fixed `org.limium.picoserve.Server` Java code to group handlers by path so that multiple HTTP methods (e.g. `POST` and `OPTIONS`) can be correctly routed to their respective processors for the same path. Tests now pass. Co-authored-by: hrj <345879+hrj@users.noreply.github.com>
1 parent 70850e2 commit bd4e4e2

3 files changed

Lines changed: 59 additions & 49 deletions

File tree

plugins/wordpress/librecaptcha.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ public function render_captcha() {
242242
}
243243

244244
$captcha_id = esc_attr( $data['id'] );
245-
$media_url = esc_url( $server_url . '/v2/media?id=' . $captcha_id );
245+
$media_url = esc_url( $server_url . '/v1/media?id=' . $captcha_id );
246246

247247
?>
248248
<div class="librecaptcha-container" style="margin-bottom: 15px;">
@@ -373,7 +373,7 @@ public function settings_page() {
373373
var data = responseJson.data;
374374
idInput.value = data.id;
375375
var img = document.createElement('img');
376-
img.src = data.server_url + '/v2/media?id=' + data.id;
376+
img.src = data.server_url + '/v1/media?id=' + data.id;
377377
img.alt = 'Test CAPTCHA';
378378
img.style.maxWidth = '100%';
379379
img.onload = function() {
@@ -382,7 +382,7 @@ public function settings_page() {
382382
captchaArea.style.display = 'block';
383383
};
384384
img.onerror = function() {
385-
statusEl.innerText = 'Error: Failed to load image from /v2/media';
385+
statusEl.innerText = 'Error: Failed to load image from /v1/media';
386386
statusEl.style.color = 'red';
387387
};
388388
imageContainer.appendChild(img);

src/main/java/org/limium/picoserve/Server.java

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -139,26 +139,50 @@ public Server(
139139
throws IOException {
140140
this.server = HttpServer.create(addr, backlog);
141141
this.server.setExecutor(executor);
142+
143+
// Group handlers by path to combine their allowed methods
144+
final java.util.Map<String, java.util.List<Handler>> handlersByPath = new java.util.HashMap<>();
142145
for (final var handler : handlers) {
143-
// System.out.println("Registering handler for " + handler.path);
146+
handlersByPath.computeIfAbsent(handler.path, k -> new java.util.ArrayList<>()).add(handler);
147+
}
148+
149+
for (final var entry : handlersByPath.entrySet()) {
150+
final String path = entry.getKey();
151+
final java.util.List<Handler> pathHandlers = entry.getValue();
152+
// System.out.println("Registering handler for " + path);
144153
this.server.createContext(
145-
handler.path,
154+
path,
146155
new HttpHandler() {
147156
public void handle(final HttpExchange exchange) {
148157
final var method = exchange.getRequestMethod();
149-
final Response errorResponse = checkMethods(handler.methods, method);
150-
try (final var os = exchange.getResponseBody()) {
151-
Response response;
152-
if (errorResponse != null) {
153-
response = errorResponse;
154-
} else {
158+
159+
Handler matchingHandler = null;
160+
for (Handler h : pathHandlers) {
161+
if (h.methods.length == 0 || java.util.Arrays.asList(h.methods).contains(method)) {
162+
matchingHandler = h;
163+
break;
164+
}
165+
}
166+
167+
Response response;
168+
if (matchingHandler == null) {
169+
// Collect all allowed methods
170+
java.util.List<String> allowedMethods = new java.util.ArrayList<>();
171+
for (Handler h : pathHandlers) {
172+
allowedMethods.addAll(java.util.Arrays.asList(h.methods));
173+
}
174+
java.util.Map<String, java.util.List<String>> allowHeader = new java.util.HashMap<>();
175+
allowHeader.put("Allow", java.util.Collections.singletonList(String.join(", ", allowedMethods)));
176+
response = new StringResponse(405, "Method Not Allowed", allowHeader);
177+
} else {
155178
try {
156-
response = handler.processor.process(new Request(exchange));
179+
response = matchingHandler.processor.process(new Request(exchange));
157180
} catch (final Exception e) {
158181
e.printStackTrace();
159182
response = new StringResponse(500, "Error: " + e);
160183
}
161-
}
184+
}
185+
try (final var os = exchange.getResponseBody()) {
162186
final var headersToSend = response.getResponseHeaders();
163187
if (headersToSend != null) {
164188
final var responseHeaders = exchange.getResponseHeaders();

src/main/scala/lc/server/Server.scala

Lines changed: 22 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -41,23 +41,25 @@ class Server(
4141
false
4242
}
4343

44+
private def getOptionsResponse(): StringResponse = {
45+
val optionsHeaderMap = new java.util.HashMap[String, java.util.List[String]]()
46+
if (corsHeader.nonEmpty) {
47+
optionsHeaderMap.put("Access-Control-Allow-Origin", List(corsHeader).asJava)
48+
}
49+
optionsHeaderMap.put("Access-Control-Allow-Methods", List("POST, GET, OPTIONS").asJava)
50+
optionsHeaderMap.put("Access-Control-Allow-Headers", List("Content-Type, Auth").asJava)
51+
new StringResponse(200, "", optionsHeaderMap)
52+
}
53+
4454
val serverBuilder: ServerBuilder = picoserve.Server
4555
.builder()
4656
.address(new InetSocketAddress(address, port))
4757
.backlog(32)
48-
.handle(new picoserve.Server.Handler(
58+
.OPTIONS("/v2/captcha", (_) => getOptionsResponse())
59+
.POST(
4960
"/v2/captcha",
50-
"POST,OPTIONS",
5161
(request) => {
52-
if (request.getMethod() == "OPTIONS") {
53-
val optionsHeaderMap = new java.util.HashMap[String, java.util.List[String]]()
54-
if (corsHeader.nonEmpty) {
55-
optionsHeaderMap.put("Access-Control-Allow-Origin", List(corsHeader).asJava)
56-
}
57-
optionsHeaderMap.put("Access-Control-Allow-Methods", List("POST, GET, OPTIONS").asJava)
58-
optionsHeaderMap.put("Access-Control-Allow-Headers", List("Content-Type, Auth").asJava)
59-
new StringResponse(200, "", optionsHeaderMap)
60-
} else if (!checkAuth(request)) {
62+
if (!checkAuth(request)) {
6163
new StringResponse(401, "Unauthorized", headerMap)
6264
} else {
6365
val bodyStr = request.getBodyString().trim.replaceAll("\u0000", "")
@@ -71,20 +73,12 @@ class Server(
7173
}
7274
}
7375
}
74-
))
75-
.handle(new picoserve.Server.Handler(
76+
)
77+
.OPTIONS("/v2/media", (_) => getOptionsResponse())
78+
.GET(
7679
"/v2/media",
77-
"GET,OPTIONS",
7880
(request) => {
79-
if (request.getMethod() == "OPTIONS") {
80-
val optionsHeaderMap = new java.util.HashMap[String, java.util.List[String]]()
81-
if (corsHeader.nonEmpty) {
82-
optionsHeaderMap.put("Access-Control-Allow-Origin", List(corsHeader).asJava)
83-
}
84-
optionsHeaderMap.put("Access-Control-Allow-Methods", List("POST, GET, OPTIONS").asJava)
85-
optionsHeaderMap.put("Access-Control-Allow-Headers", List("Content-Type, Auth").asJava)
86-
new StringResponse(200, "", optionsHeaderMap)
87-
} else if (!checkAuth(request)) {
81+
if (!checkAuth(request)) {
8882
new StringResponse(401, "Unauthorized", headerMap)
8983
} else {
9084
val params = request.getQueryParams()
@@ -98,20 +92,12 @@ class Server(
9892
getResponse(result, headerMap)
9993
}
10094
}
101-
))
102-
.handle(new picoserve.Server.Handler(
95+
)
96+
.OPTIONS("/v2/answer", (_) => getOptionsResponse())
97+
.POST(
10398
"/v2/answer",
104-
"POST,OPTIONS",
10599
(request) => {
106-
if (request.getMethod() == "OPTIONS") {
107-
val optionsHeaderMap = new java.util.HashMap[String, java.util.List[String]]()
108-
if (corsHeader.nonEmpty) {
109-
optionsHeaderMap.put("Access-Control-Allow-Origin", List(corsHeader).asJava)
110-
}
111-
optionsHeaderMap.put("Access-Control-Allow-Methods", List("POST, GET, OPTIONS").asJava)
112-
optionsHeaderMap.put("Access-Control-Allow-Headers", List("Content-Type, Auth").asJava)
113-
new StringResponse(200, "", optionsHeaderMap)
114-
} else if (!checkAuth(request)) {
100+
if (!checkAuth(request)) {
115101
new StringResponse(401, "Unauthorized", headerMap)
116102
} else {
117103
val bodyStr = request.getBodyString().trim.replaceAll("\u0000", "")
@@ -125,7 +111,7 @@ class Server(
125111
}
126112
}
127113
}
128-
))
114+
)
129115
if (playgroundEnabled) {
130116
val htmlHeaderMap = Map("Content-Type" -> List("text/html").asJava).asJava
131117
serverBuilder.GET(

0 commit comments

Comments
 (0)