• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

knowledgepixels / nanopub-query / 22871832647

09 Mar 2026 07:50PM UTC coverage: 70.256% (-0.4%) from 70.691%
22871832647

Pull #59

github

web-flow
Merge ecc13bbfe into c5531c09a
Pull Request #59: Fix api-version=latest hanging for OpenAPI links

216 of 336 branches covered (64.29%)

Branch coverage included in aggregate %.

606 of 834 relevant lines covered (72.66%)

10.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
src/main/java/com/knowledgepixels/query/MainVerticle.java
1
package com.knowledgepixels.query;
2

3
import com.github.jsonldjava.shaded.com.google.common.base.Charsets;
4
import com.knowledgepixels.query.GrlcSpec.InvalidGrlcSpecException;
5
import io.micrometer.prometheus.PrometheusMeterRegistry;
6
import io.vertx.core.AbstractVerticle;
7
import io.vertx.core.Future;
8
import io.vertx.core.Promise;
9
import io.vertx.core.buffer.Buffer;
10
import io.vertx.core.http.*;
11
import io.vertx.ext.web.Router;
12
import io.vertx.ext.web.RoutingContext;
13
import io.vertx.ext.web.handler.CorsHandler;
14
import io.vertx.ext.web.handler.StaticHandler;
15
import io.vertx.ext.web.proxy.handler.ProxyHandler;
16
import io.vertx.httpproxy.*;
17
import io.vertx.micrometer.PrometheusScrapingHandler;
18
import io.vertx.micrometer.backends.BackendRegistries;
19
import org.eclipse.rdf4j.model.Value;
20
import org.slf4j.Logger;
21
import org.slf4j.LoggerFactory;
22

23
import java.io.InputStream;
24
import java.net.URLEncoder;
25
import java.util.ArrayList;
26
import java.util.Collections;
27
import java.util.List;
28
import java.util.Scanner;
29
import java.util.concurrent.Executors;
30
import java.util.concurrent.TimeUnit;
31

32
/**
33
 * Main verticle that coordinates the incoming HTTP requests.
34
 */
35
@GeneratedFlagForDependentElements
36
public class MainVerticle extends AbstractVerticle {
37

38
    private static String css = null;
39

40
    private static final Logger log = LoggerFactory.getLogger(MainVerticle.class);
41

42
    /**
43
     * Start the main verticle.
44
     *
45
     * @param startPromise the promise to complete when the verticle is started
46
     * @throws Exception if an error occurs during startup
47
     */
48
    @Override
49
    public void start(Promise<Void> startPromise) throws Exception {
50
        HttpClient httpClient = vertx.createHttpClient(
51
                new HttpClientOptions()
52
                        .setConnectTimeout(Utils.getEnvInt("NANOPUB_QUERY_VERTX_CONNECT_TIMEOUT", 1000))
53
                        .setIdleTimeoutUnit(TimeUnit.SECONDS)
54
                        .setIdleTimeout(Utils.getEnvInt("NANOPUB_QUERY_VERTX_IDLE_TIMEOUT", 60))
55
                        .setReadIdleTimeout(Utils.getEnvInt("NANOPUB_QUERY_VERTX_IDLE_TIMEOUT", 60))
56
                        .setWriteIdleTimeout(Utils.getEnvInt("NANOPUB_QUERY_VERTX_IDLE_TIMEOUT", 60)),
57
                new PoolOptions().setHttp1MaxSize(200).setHttp2MaxSize(200)
58
        );
59

60
        HttpServer proxyServer = vertx.createHttpServer();
61
        Router proxyRouter = Router.router(vertx);
62
        proxyRouter.route().handler(CorsHandler.create().addRelativeOrigin(".*"));
63

64
        // Metrics
65
        final var metricsHttpServer = vertx.createHttpServer();
66
        final var metricsRouter = Router.router(vertx);
67
        metricsHttpServer.requestHandler(metricsRouter).listen(9394);
68

69
        final var metricsRegistry = (PrometheusMeterRegistry) BackendRegistries.getDefaultNow();
70
        final var collector = new MetricsCollector(metricsRegistry);
71
        metricsRouter.route("/metrics").handler(PrometheusScrapingHandler.create(metricsRegistry));
72
        // ----------
73
        // This part is only used if the redirection is not done through Nginx.
74
        // See nginx.conf and this bug report: https://github.com/eclipse-rdf4j/rdf4j/discussions/5120
75
        HttpProxy rdf4jProxy = HttpProxy.reverseProxy(httpClient);
76
        String proxy = Utils.getEnvString("RDF4J_PROXY_HOST", "rdf4j");
77
        int proxyPort = Utils.getEnvInt("RDF4J_PROXY_PORT", 8080);
78
        rdf4jProxy.origin(proxyPort, proxy);
79

80
        rdf4jProxy.addInterceptor(new ProxyInterceptor() {
×
81

82
            @Override
83
            @GeneratedFlagForDependentElements
84
            public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
85
                ProxyRequest request = context.request();
86
                request.setURI(request.getURI().replaceAll("/", "_").replaceFirst("^_repo_", "/rdf4j-server/repositories/"));
87
                // For later to try to get HTML tables out:
88
//                                if (request.headers().get("Accept") == null) {
89
//                                        request.putHeader("Accept", "text/html");
90
//                                }
91
//                                request.putHeader("Accept", "application/json");
92
                return ProxyInterceptor.super.handleProxyRequest(context);
93
            }
94

95
            @Override
96
            @GeneratedFlagForDependentElements
97
            public Future<Void> handleProxyResponse(ProxyContext context) {
98
                ProxyResponse resp = context.response();
99
                resp.putHeader("Access-Control-Allow-Origin", "*");
100
                resp.putHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
101
                // For later to try to get HTML tables out:
102
//                                String acceptHeader = context.request().headers().get("Accept");
103
//                                if (acceptHeader != null && acceptHeader.contains("text/html")) {
104
//                                        resp.putHeader("Content-Type", "text/html");
105
//                                        resp.setBody(Body.body(Buffer.buffer("<html><body><strong>test</strong></body></html>")));
106
//                                }
107
                return ProxyInterceptor.super.handleProxyResponse(context);
108
            }
109

110
        });
111
        // ----------
112

113
        proxyRouter.route(HttpMethod.GET, "/repo").handler(req -> handleRedirect(req, "/repo"));
114
        proxyRouter.route(HttpMethod.GET, "/repo/*").handler(ProxyHandler.create(rdf4jProxy));
115
        proxyRouter.route(HttpMethod.POST, "/repo/*").handler(ProxyHandler.create(rdf4jProxy));
116
        proxyRouter.route(HttpMethod.HEAD, "/repo/*").handler(ProxyHandler.create(rdf4jProxy));
117
        proxyRouter.route(HttpMethod.OPTIONS, "/repo/*").handler(ProxyHandler.create(rdf4jProxy));
118
        proxyRouter.route(HttpMethod.GET, "/tools/*").handler(req -> {
119
            final String yasguiPattern = "^/tools/([a-zA-Z0-9-_]+)(/([a-zA-Z0-9-_]+))?/yasgui\\.html$";
120
            if (req.normalizedPath().matches(yasguiPattern)) {
121
                String repo = req.normalizedPath().replaceFirst(yasguiPattern, "$1$2");
122
                req.response()
123
                        .putHeader("content-type", "text/html")
124
                        .end("<!DOCTYPE html>\n"
125
                             + "<html lang=\"en\">\n"
126
                             + "<head>\n"
127
                             + "<meta charset=\"utf-8\">\n"
128
                             + "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n"
129
                             + "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n"
130
                             + "<title>Nanopub Query SPARQL Editor for repository: " + repo + "</title>\n"
131
                             + "<link rel=\"stylesheet\" href=\"/style.css\">\n"
132
                             + "<link href='https://cdn.jsdelivr.net/yasgui/2.6.1/yasgui.min.css' rel='stylesheet' type='text/css'/>\n"
133
                             + "<style>.yasgui .endpointText {display:none !important;}</style>\n"
134
                             + "<script type=\"text/javascript\">localStorage.clear();</script>\n"
135
                             + "</head>\n"
136
                             + "<body>\n"
137
                             + "<h3>Nanopub Query SPARQL Editor for repository: " + repo + "</h3>\n"
138
                             + "<div id='yasgui'></div>\n"
139
                             + "<script src='https://cdn.jsdelivr.net/yasgui/2.6.1/yasgui.min.js'></script>\n"
140
                             + "<script type=\"text/javascript\">\n"
141
                             + "var yasgui = YASGUI(document.getElementById(\"yasgui\"), {\n"
142
                             + "  yasqe:{sparql:{endpoint:'/repo/" + repo + "'},value:'" + Utils.defaultQuery.replaceAll("\n", "\\\\n") + "'}\n"
143
                             + "});\n"
144
                             + "</script>\n"
145
                             + "</body>\n"
146
                             + "</html>");
147
            } else {
148
                req.response()
149
                        .putHeader("content-type", "text/plain")
150
                        .setStatusCode(404)
151
                        .end("not found");
152
            }
153
        });
154
        proxyRouter.route(HttpMethod.GET, "/page").handler(req -> handleRedirect(req, "/page"));
155
        proxyRouter.route(HttpMethod.GET, "/page/*").handler(req -> {
156
            final String pagePattern = "^/page/([a-zA-Z0-9-_]+)(/([a-zA-Z0-9-_]+))?$";
157
            if (req.normalizedPath().matches(pagePattern)) {
158
                String repo = req.normalizedPath().replaceFirst(pagePattern, "$1$2");
159
                req.response()
160
                        .putHeader("content-type", "text/html")
161
                        .end("<!DOCTYPE html>\n"
162
                             + "<html lang=\"en\">\n"
163
                             + "<head>\n"
164
                             + "<meta charset=\"utf-8\">\n"
165
                             + "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n"
166
                             + "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n"
167
                             + "<title>Nanopub Query repo: " + repo + "</title>\n"
168
                             + "<link rel=\"stylesheet\" href=\"/style.css\">\n"
169
                             + "</head>\n"
170
                             + "<body>\n"
171
                             + "<h3>Nanopub Query repo: " + repo + "</h3>\n"
172
                             + "<p>Endpoint: <a href=\"/repo/" + repo + "\">/repo/" + repo + "</a></p>"
173
                             + "<p>YASGUI: <a href=\"/tools/" + repo + "/yasgui.html\">/tools/" + repo + "/yasgui.hml</a></p>"
174
                             + "</body>\n"
175
                             + "</html>");
176
            } else {
177
                req.response()
178
                        .putHeader("content-type", "text/plain")
179
                        .setStatusCode(404)
180
                        .end("not found");
181
            }
182
        });
183
        proxyRouter.route(HttpMethod.GET, "/").handler(req -> {
184
            String repos = "";
185
            List<String> repoList = new ArrayList<>(TripleStore.get().getRepositoryNames());
186
            Collections.sort(repoList);
187
            for (String s : repoList) {
188
                if (s.startsWith("pubkey_") || s.startsWith("type_")) continue;
189
                repos += "<li><code><a href=\"/page/" + s + "\">" + s + "</a></code></li>";
190
            }
191
            String pinnedApisValue = Utils.getEnvString("NANOPUB_QUERY_PINNED_APIS", "");
192
            String[] pinnedApis = pinnedApisValue.split(" ");
193
            String pinnedApiLinks = "";
194
            if (!pinnedApisValue.isEmpty()) {
195
                for (String s : pinnedApis) {
196
                    pinnedApiLinks = pinnedApiLinks + "<li><a href=\"openapi/?url=spec/" + s + "%3Fapi-version=latest\">" + s.replaceFirst("^.*/", "") + "</a></li>";
197
                }
198
                pinnedApiLinks = "<p>Pinned APIs:</p>\n" +
199
                                 "<ul>\n" +
200
                                 pinnedApiLinks +
201
                                 "</ul>\n";
202
            }
203
            req.response()
204
                    .putHeader("content-type", "text/html")
205
                    .end("<!DOCTYPE html>\n"
206
                         + "<html lang='en'>\n"
207
                         + "<head>\n"
208
                         + "<title>Nanopub Query</title>\n"
209
                         + "<meta charset='utf-8'>\n"
210
                         + "<link rel=\"stylesheet\" href=\"/style.css\">\n"
211
                         + "</head>\n"
212
                         + "<body>\n"
213
                         + "<h1>Nanopub Query</h1>"
214
                         + "<p>General repos:</p>"
215
                         + "<ul>" + repos + "</ul>"
216
                         + "<p>Specific repos:</p>"
217
                         + "<ul>"
218
                         + "<li><a href=\"/pubkeys\">Pubkey Repos</a></li>"
219
                         + "<li><a href=\"/types\">Type Repos</a></li>"
220
                         + "</ul>"
221
                         + pinnedApiLinks
222
                         + "</body>\n"
223
                         + "</html>");
224
        });
225
        proxyRouter.route(HttpMethod.GET, "/pubkeys").handler(req -> {
226
            String repos = "";
227
            List<String> repoList = new ArrayList<>(TripleStore.get().getRepositoryNames());
228
            Collections.sort(repoList);
229
            for (String s : repoList) {
230
                if (!s.startsWith("pubkey_")) continue;
231
                String hash = s.replaceFirst("^([a-zA-Z0-9-]+)_([a-zA-Z0-9-_]+)$", "$2");
232
                Value hashObj = Utils.getObjectForHash(hash);
233
                String label;
234
                if (hashObj == null) {
235
                    label = "";
236
                } else {
237
                    label = " (" + Utils.getShortPubkeyName(hashObj.stringValue()) + ")";
238
                }
239
                s = s.replaceFirst("^([a-zA-Z0-9-]+)_([a-zA-Z0-9-_]+)$", "$1/$2");
240
                repos += "<li><code><a href=\"/page/" + s + "\">" + s + "</a>" + label + "</code></li>";
241
            }
242
            req.response()
243
                    .putHeader("content-type", "text/html")
244
                    .end("<!DOCTYPE html>\n"
245
                         + "<html lang='en'>\n"
246
                         + "<head>\n"
247
                         + "<title>Nanopub Query: Pubkey Repos</title>\n"
248
                         + "<meta charset='utf-8'>\n"
249
                         + "<link rel=\"stylesheet\" href=\"/style.css\">\n"
250
                         + "</head>\n"
251
                         + "<body>\n"
252
                         + "<h3>Pubkey Repos</h3>"
253
                         + "<p>Repos:</p>"
254
                         + "<ul>" + repos + "</ul>"
255
                         + "</body>\n"
256
                         + "</html>");
257
        });
258
        proxyRouter.route(HttpMethod.GET, "/types").handler(req -> {
259
            String repos = "";
260
            List<String> repoList = new ArrayList<>(TripleStore.get().getRepositoryNames());
261
            Collections.sort(repoList);
262
            for (String s : repoList) {
263
                if (!s.startsWith("type_")) continue;
264
                String hash = s.replaceFirst("^([a-zA-Z0-9-]+)_([a-zA-Z0-9-_]+)$", "$2");
265
                Value hashObj = Utils.getObjectForHash(hash);
266
                String label;
267
                if (hashObj == null) {
268
                    label = "";
269
                } else {
270
                    label = " (" + hashObj.stringValue() + ")";
271
                }
272
                s = s.replaceFirst("^([a-zA-Z0-9-]+)_([a-zA-Z0-9-_]+)$", "$1/$2");
273
                repos += "<li><code><a href=\"/page/" + s + "\">" + s + "</a>" + label + "</code></li>";
274
            }
275
            req.response()
276
                    .putHeader("content-type", "text/html")
277
                    .end("<!DOCTYPE html>\n"
278
                         + "<html lang='en'>\n"
279
                         + "<head>\n"
280
                         + "<title>Nanopub Query: Type Repos</title>\n"
281
                         + "<meta charset='utf-8'>\n"
282
                         + "<link rel=\"stylesheet\" href=\"/style.css\">\n"
283
                         + "</head>\n"
284
                         + "<body>\n"
285
                         + "<h3>Type Repos</h3>"
286
                         + "<p>Repos:</p>"
287
                         + "<ul>" + repos + "</ul>"
288
                         + "</body>\n"
289
                         + "</html>");
290
        });
291
        proxyRouter.route(HttpMethod.GET, "/style.css").handler(req -> {
292
            if (css == null) {
293
                css = getResourceAsString("style.css");
294
            }
295
            req.response().end(css);
296
        });
297

298
        // TODO This is no longer needed and can be removed at some point:
299
        proxyRouter.route(HttpMethod.GET, "/grlc-spec/*").handler(req -> {
300
            vertx.<String>executeBlocking(() -> {
301
                GrlcSpec gsp = new GrlcSpec(req.normalizedPath(), req.queryParams());
302
                return gsp.getSpec();
303
            }, false).onSuccess(spec -> {
304
                req.response().putHeader("content-type", "text/yaml").end(spec);
305
            }).onFailure(ex -> {
306
                if (ex instanceof InvalidGrlcSpecException) {
307
                    req.response().setStatusCode(400).end(ex.getMessage());
308
                } else {
309
                    req.response().setStatusCode(500).end("Unexpected error: " + ex.getMessage());
310
                }
311
            });
312
        });
313

314
        proxyRouter.route(HttpMethod.GET, "/openapi/spec/*").handler(req -> {
315
            vertx.<String>executeBlocking(() -> {
316
                OpenApiSpecPage osp = new OpenApiSpecPage(req.normalizedPath(), req.queryParams());
317
                return osp.getSpec();
318
            }, false).onSuccess(spec -> {
319
                req.response().putHeader("content-type", "text/yaml").end(spec);
320
            }).onFailure(ex -> {
321
                if (ex instanceof InvalidGrlcSpecException) {
322
                    req.response().setStatusCode(400).end("Invalid grlc API definition: " + ex.getMessage());
323
                } else {
324
                    req.response().setStatusCode(500).end("Unexpected error: " + ex.getMessage());
325
                }
326
            });
327
        });
328

329
        proxyRouter.route("/openapi/*").handler(StaticHandler.create("com/knowledgepixels/query/swagger"));
330

331
        HttpProxy grlcxProxy = HttpProxy.reverseProxy(httpClient);
332
        grlcxProxy.origin(proxyPort, proxy);
333

334
        grlcxProxy.addInterceptor(new ProxyInterceptor() {
×
335

336
            @Override
337
            @GeneratedFlagForDependentElements
338
            public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
339
                final ProxyRequest req = context.request();
340
                final String apiPattern = "^/api/(RA[a-zA-Z0-9-_]{43})/([a-zA-Z0-9-_]+)([.]csv|[.]json|[.]srx)?([?].*)?$";
341
                if (req.getURI().matches(apiPattern)) {
342
                    try {
343
                        req.setMethod(HttpMethod.POST);
344
                        if (req.getURI().matches(".*[.]csv([?].*)?$")) {
345
                            req.putHeader("Accept", "text/csv");
346
                            req.setURI(req.getURI().replaceFirst("[.]csv([?].*)?$", "$1"));
347
                        } else if (req.getURI().matches(".*[.]json([?].*)?$")) {
348
                            req.putHeader("Accept", "application/json");
349
                            req.setURI(req.getURI().replaceFirst("[.]json([?].*)?$", "$1"));
350
                        } else if (req.getURI().matches(".*[.]srx([?].*)?$")) {
351
                            req.putHeader("Accept", "application/xml");
352
                            req.setURI(req.getURI().replaceFirst("[.]srx([?].*)?$", "$1"));
353
                        }
354
                        GrlcSpec grlcSpec = new GrlcSpec(req.getURI(), req.proxiedRequest().params());
355

356
                        // Variant 1:
357
                        req.putHeader("Content-Type", "application/sparql-query");
358
                        req.setBody(Body.body(Buffer.buffer(grlcSpec.expandQuery())));
359
                        // Variant 2:
360
                        //req.putHeader("Content-Type", "application/x-www-form-urlencoded");
361
                        //req.setBody(Body.body(Buffer.buffer("query=" + URLEncoder.encode(grlcSpec.getExpandedQueryContent(), Charsets.UTF_8))));
362

363
                        req.setURI("/rdf4j-server/repositories/" + grlcSpec.getRepoName());
364
                        log.info("Forwarding apix request to /rdf4j-server/repositories/", grlcSpec.getRepoName());
365
                    } catch (InvalidGrlcSpecException ex) {
366
                        return Future.succeededFuture(context.request()
367
                                .response()
368
                                .setStatusCode(400)
369
                                .putHeader("Content-Type", "text/plain")
370
                                .setBody(Body.body(Buffer.buffer("Bad request: " + ex.getMessage()))));
371
                    } catch (Exception ex) {
372
                        return Future.succeededFuture(context.request()
373
                                .response()
374
                                .setStatusCode(500)
375
                                .putHeader("Content-Type", "text/plain")
376
                                .setBody(Body.body(Buffer.buffer("Unexpected error: " + ex.getMessage()))));
377
                    }
378
                }
379
                return ProxyInterceptor.super.handleProxyRequest(context);
380
            }
381

382
            @Override
383
            @GeneratedFlagForDependentElements
384
            public Future<Void> handleProxyResponse(ProxyContext context) {
385
                log.info("Receiving api response");
386
                ProxyResponse resp = context.response();
387
                resp.putHeader("Access-Control-Allow-Origin", "*");
388
                resp.putHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
389
                resp.putHeader("Content-Disposition", "inline");
390
                return ProxyInterceptor.super.handleProxyResponse(context);
391
            }
392

393
        });
394
        proxyRouter.route(HttpMethod.GET, "/api/*").handler(ProxyHandler.create(grlcxProxy));
395

396
        proxyServer.requestHandler(req -> {
397
            applyGlobalHeaders(req.response());
398
            proxyRouter.handle(req);
399
        });
400
        proxyServer.listen(9393);
401

402
        // Periodic metrics update
403
        vertx.setPeriodic(1000, id -> collector.updateMetrics());
404

405

406
        new Thread(() -> {
407
            try {
408
                var status = StatusController.get().initialize();
409
                log.info("Current state: {}, last committed counter: {}", status.state, status.loadCounter);
410
                if (status.state == StatusController.State.LAUNCHING || status.state == StatusController.State.LOADING_INITIAL) {
411
                    // Do the initial nanopublication loading
412
                    StatusController.get().setLoadingInitial(status.loadCounter);
413
                    // Fall back to local nanopub loading if the local files are present
414
                    if (!LocalNanopubLoader.init()) {
415
                        JellyNanopubLoader.loadInitial(status.loadCounter);
416
                    } else {
417
                        log.info("Local nanopublication loading finished");
418
                    }
419
                    StatusController.get().setReady();
420
                } else {
421
                    log.info("Initial load is already done");
422
                    StatusController.get().setReady();
423
                }
424
            } catch (Exception ex) {
425
                log.info("Initial load failed, terminating...", ex);
426
                Runtime.getRuntime().exit(1);
427
            }
428

429
            // Start periodic nanopub loading
430
            log.info("Starting periodic nanopub loading...");
431
            var executor = Executors.newSingleThreadScheduledExecutor();
432
            executor.scheduleWithFixedDelay(
433
                    JellyNanopubLoader::loadUpdates,
434
                    JellyNanopubLoader.UPDATES_POLL_INTERVAL,
435
                    JellyNanopubLoader.UPDATES_POLL_INTERVAL,
436
                    TimeUnit.MILLISECONDS
437
            );
438
        }).start();
439

440
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
441
            try {
442
                log.info("Gracefully shutting down...");
443
                TripleStore.get().shutdownRepositories();
444
                vertx.close().toCompletionStage().toCompletableFuture().get(5, TimeUnit.SECONDS);
445
                log.info("Graceful shutdown completed");
446
            } catch (Exception ex) {
447
                log.info("Graceful shutdown failed", ex);
448
            }
449
        }));
450
    }
451

452
    private String getResourceAsString(String file) {
453
        InputStream is = getClass().getClassLoader().getResourceAsStream("com/knowledgepixels/query/" + file);
454
        try (Scanner s = new Scanner(is).useDelimiter("\\A")) {
455
            String fileContent = s.hasNext() ? s.next() : "";
456
            return fileContent;
457
        }
458
    }
459

460
    private static void handleRedirect(RoutingContext req, String path) {
461
        String queryString = "";
462
        if (!req.queryParam("query").isEmpty())
463
            queryString = "?query=" + URLEncoder.encode(req.queryParam("query").getFirst(), Charsets.UTF_8);
464
        if (req.queryParam("for-type").size() == 1) {
465
            String type = req.queryParam("for-type").getFirst();
466
            req.response().putHeader("location", path + "/type/" + Utils.createHash(type) + queryString);
467
            req.response().setStatusCode(301).end();
468
        } else if (req.queryParam("for-pubkey").size() == 1) {
469
            String type = req.queryParam("for-pubkey").getFirst();
470
            req.response().putHeader("location", path + "/pubkey/" + Utils.createHash(type) + queryString);
471
            req.response().setStatusCode(301).end();
472
        } else if (req.queryParam("for-user").size() == 1) {
473
            String type = req.queryParam("for-user").getFirst();
474
            req.response().putHeader("location", path + "/user/" + Utils.createHash(type) + queryString);
475
            req.response().setStatusCode(301).end();
476
        }
477
    }
478

479
    /**
480
     * Apply headers to the response that should be present for all requests.
481
     *
482
     * @param response The response to which the headers should be applied.
483
     */
484
    private static void applyGlobalHeaders(HttpServerResponse response) {
485
        response.putHeader("Nanopub-Query-Status", StatusController.get().getState().state.toString());
486
    }
487
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc