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

knowledgepixels / nanopub-query / 17120526436

21 Aug 2025 07:44AM UTC coverage: 49.971% (-0.09%) from 50.058%
17120526436

push

github

tkuhn
Define timeouts via environment variables, and some refactoring

240 of 490 branches covered (48.98%)

Branch coverage included in aggregate %.

629 of 1249 relevant lines covered (50.36%)

2.49 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 java.io.InputStream;
4
import java.net.URLEncoder;
5
import java.util.ArrayList;
6
import java.util.Collections;
7
import java.util.List;
8
import java.util.Map.Entry;
9
import java.util.Scanner;
10
import java.util.concurrent.Executors;
11
import java.util.concurrent.TimeUnit;
12

13
import org.eclipse.rdf4j.model.Value;
14

15
import com.github.jsonldjava.shaded.com.google.common.base.Charsets;
16

17
import io.micrometer.prometheus.PrometheusMeterRegistry;
18
import io.vertx.core.AbstractVerticle;
19
import io.vertx.core.Future;
20
import io.vertx.core.MultiMap;
21
import io.vertx.core.Promise;
22
import io.vertx.core.http.HttpClient;
23
import io.vertx.core.http.HttpClientOptions;
24
import io.vertx.core.http.HttpMethod;
25
import io.vertx.core.http.HttpServer;
26
import io.vertx.core.http.HttpServerResponse;
27
import io.vertx.core.http.PoolOptions;
28
import io.vertx.ext.web.Router;
29
import io.vertx.ext.web.RoutingContext;
30
import io.vertx.ext.web.handler.CorsHandler;
31
import io.vertx.ext.web.handler.StaticHandler;
32
import io.vertx.ext.web.proxy.handler.ProxyHandler;
33
import io.vertx.httpproxy.HttpProxy;
34
import io.vertx.httpproxy.ProxyContext;
35
import io.vertx.httpproxy.ProxyInterceptor;
36
import io.vertx.httpproxy.ProxyRequest;
37
import io.vertx.httpproxy.ProxyResponse;
38
import io.vertx.micrometer.PrometheusScrapingHandler;
39
import io.vertx.micrometer.backends.BackendRegistries;
40

41
/**
42
 * Main verticle that coordinates the incoming HTTP requests.
43
 */
44
public class MainVerticle extends AbstractVerticle {
×
45

46
    private static String css = null;
×
47

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

66
        HttpServer proxyServer = vertx.createHttpServer();
×
67
        Router proxyRouter = Router.router(vertx);
×
68
        proxyRouter.route().handler(CorsHandler.create().addRelativeOrigin(".*"));
×
69

70
        // Metrics
71
        final var metricsHttpServer = vertx.createHttpServer();
×
72
        final var metricsRouter = Router.router(vertx);
×
73
        metricsHttpServer.requestHandler(metricsRouter).listen(9394);
×
74

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

86
        rdf4jProxy.addInterceptor(new ProxyInterceptor() {
×
87

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

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

114
        });
115
        // ----------
116

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

302
        proxyRouter.route(HttpMethod.GET, "/grlc-spec/*").handler(req -> {
×
303
            GrlcSpecPage gsp = new GrlcSpecPage(req.normalizedPath(), req.queryParams());
×
304
            String spec = gsp.getSpec();
×
305
            if (spec == null) {
×
306
                req.response().setStatusCode(404).end("query definition not found / not valid");
×
307
            } else {
308
                req.response().putHeader("content-type", "text/yaml").end(spec);
×
309
            }
310
        });
×
311

312
        proxyRouter.route(HttpMethod.GET, "/openapi/spec/*").handler(req -> {
×
313
            OpenApiSpecPage osp = new OpenApiSpecPage(req.normalizedPath(), req.queryParams());
×
314
            String spec = osp.getSpec();
×
315
            if (spec == null) {
×
316
                req.response().setStatusCode(404).end("query definition not found / not valid");
×
317
            } else {
318
                req.response().putHeader("content-type", "text/yaml").end(spec);
×
319
            }
320
        });
×
321

322
        proxyRouter.route("/openapi/*").handler(StaticHandler.create("com/knowledgepixels/query/swagger"));
×
323

324
        HttpProxy grlcProxy = HttpProxy.reverseProxy(httpClient);
×
325
        grlcProxy.origin(80, "grlc");
×
326
        grlcProxy.addInterceptor(new ProxyInterceptor() {
×
327

328
            @Override
329
            public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
330
                final String apiPattern = "^/api/(RA[a-zA-Z0-9-_]{43})/([a-zA-Z0-9-_]+)([?].*)?$";
×
331
                if (context.request().getURI().matches(apiPattern)) {
×
332
                    String artifactCode = context.request().getURI().replaceFirst(apiPattern, "$1");
×
333
                    String queryName = context.request().getURI().replaceFirst(apiPattern, "$2");
×
334
                    String grlcUrlParams = "";
×
335
                    String grlcSpecUrlParams = "";
×
336
                    MultiMap pm = context.request().proxiedRequest().params();
×
337
                    for (Entry<String, String> e : pm) {
×
338
                        if (e.getKey().equals("api-version")) {
×
339
                            grlcSpecUrlParams += "&" + e.getKey() + "=" + URLEncoder.encode(e.getValue(), Charsets.UTF_8);
×
340
                        } else {
341
                            grlcUrlParams += "&" + e.getKey() + "=" + URLEncoder.encode(e.getValue(), Charsets.UTF_8);
×
342
                        }
343
                    }
×
344
                    String url = "/api-url/" + queryName +
×
345
                            "?specUrl=" + URLEncoder.encode(GrlcSpecPage.nanopubQueryUrl + "grlc-spec/" + artifactCode + "/?" +
×
346
                            grlcSpecUrlParams, Charsets.UTF_8) + grlcUrlParams;
347
                    context.request().setURI(url);
×
348
                }
349
                return context.sendRequest();
×
350
            }
351

352
            @Override
353
            public Future<Void> handleProxyResponse(ProxyContext context) {
354
                // To avoid double entries:
355
                context.response().headers().remove("Access-Control-Allow-Origin");
×
356
                return context.sendResponse();
×
357
            }
358

359
        });
360

361
        proxyServer.requestHandler(req -> {
×
362
            applyGlobalHeaders(req.response());
×
363
            proxyRouter.handle(req);
×
364
        });
×
365
        proxyServer.listen(9393);
×
366

367
        proxyRouter.route("/api/*").handler(ProxyHandler.create(grlcProxy));
×
368
        proxyRouter.route("/static/*").handler(ProxyHandler.create(grlcProxy));
×
369

370
        // Periodic metrics update
371
        vertx.setPeriodic(1000, id -> collector.updateMetrics());
×
372

373

374
        new Thread(() -> {
×
375
            try {
376
                var status = StatusController.get().initialize();
×
377
                System.err.println("Current state: " + status.state + ", last committed counter: " + status.loadCounter);
×
378
                if (status.state == StatusController.State.LAUNCHING || status.state == StatusController.State.LOADING_INITIAL) {
×
379
                    // Do the initial nanopublication loading
380
                    StatusController.get().setLoadingInitial(status.loadCounter);
×
381
                    // Fall back to local nanopub loading if the local files are present
382
                    if (!LocalNanopubLoader.init()) {
×
383
                        JellyNanopubLoader.loadInitial(status.loadCounter);
×
384
                    } else {
385
                        System.err.println("Local nanopublication loading finished");
×
386
                    }
387
                    StatusController.get().setReady();
×
388
                } else {
389
                    System.err.println("Initial load is already done");
×
390
                    StatusController.get().setReady();
×
391
                }
392
            } catch (Exception ex) {
×
393
                ex.printStackTrace();
×
394
                System.err.println("Initial load failed, terminating...");
×
395
                Runtime.getRuntime().exit(1);
×
396
            }
×
397

398
            // Start periodic nanopub loading
399
            System.err.println("Starting periodic nanopub loading...");
×
400
            var executor = Executors.newSingleThreadScheduledExecutor();
×
401
            executor.scheduleWithFixedDelay(
×
402
                    JellyNanopubLoader::loadUpdates,
403
                    JellyNanopubLoader.UPDATES_POLL_INTERVAL,
404
                    JellyNanopubLoader.UPDATES_POLL_INTERVAL,
405
                    TimeUnit.MILLISECONDS
406
            );
407
        }).start();
×
408

409
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
×
410
            try {
411
                System.err.println("Gracefully shutting down...");
×
412
                TripleStore.get().shutdownRepositories();
×
413
                vertx.close().toCompletionStage().toCompletableFuture().get(5, TimeUnit.SECONDS);
×
414
                System.err.println("Graceful shutdown completed");
×
415
            } catch (Exception ex) {
×
416
                System.err.println("Graceful shutdown failed");
×
417
                ex.printStackTrace();
×
418
            }
×
419
        }));
×
420
    }
×
421

422
    private String getResourceAsString(String file) {
423
        InputStream is = getClass().getClassLoader().getResourceAsStream("com/knowledgepixels/query/" + file);
×
424
        try (Scanner s = new Scanner(is).useDelimiter("\\A")) {
×
425
            String fileContent = s.hasNext() ? s.next() : "";
×
426
            return fileContent;
×
427
        }
428
    }
429

430
    private static void handleRedirect(RoutingContext req, String path) {
431
        String queryString = "";
×
432
        if (!req.queryParam("query").isEmpty())
×
433
            queryString = "?query=" + URLEncoder.encode(req.queryParam("query").get(0), Charsets.UTF_8);
×
434
        if (req.queryParam("for-type").size() == 1) {
×
435
            String type = req.queryParam("for-type").get(0);
×
436
            req.response().putHeader("location", path + "/type/" + Utils.createHash(type) + queryString);
×
437
            req.response().setStatusCode(301).end();
×
438
        } else if (req.queryParam("for-pubkey").size() == 1) {
×
439
            String type = req.queryParam("for-pubkey").get(0);
×
440
            req.response().putHeader("location", path + "/pubkey/" + Utils.createHash(type) + queryString);
×
441
            req.response().setStatusCode(301).end();
×
442
        } else if (req.queryParam("for-user").size() == 1) {
×
443
            String type = req.queryParam("for-user").get(0);
×
444
            req.response().putHeader("location", path + "/user/" + Utils.createHash(type) + queryString);
×
445
            req.response().setStatusCode(301).end();
×
446
        }
447
    }
×
448

449
    /**
450
     * Apply headers to the response that should be present for all requests.
451
     *
452
     * @param response The response to which the headers should be applied.
453
     */
454
    private static void applyGlobalHeaders(HttpServerResponse response) {
455
        response.putHeader("Nanopub-Query-Status", StatusController.get().getState().state.toString());
×
456
    }
×
457
}
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