From aae6c51a153ecda535653eb9faf6b03c09d3a7bc Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Thu, 23 Apr 2026 19:42:03 +0200 Subject: [PATCH] =?utf8?q?=F0=9F=9A=A7=20Make=20HTTP=20query=20multi-query?= =?utf8?q?-able?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../pterodactylus/rhynodge/queries/HttpQuery.java | 51 ++++++++++++++----- .../rhynodge/queries/HttpQueryTest.kt | 59 ++++++++++++++++++++++ 2 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 src/test/kotlin/net/pterodactylus/rhynodge/queries/HttpQueryTest.kt diff --git a/src/main/java/net/pterodactylus/rhynodge/queries/HttpQuery.java b/src/main/java/net/pterodactylus/rhynodge/queries/HttpQuery.java index c148eb0..1d8d899 100644 --- a/src/main/java/net/pterodactylus/rhynodge/queries/HttpQuery.java +++ b/src/main/java/net/pterodactylus/rhynodge/queries/HttpQuery.java @@ -18,9 +18,12 @@ package net.pterodactylus.rhynodge.queries; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; import net.pterodactylus.rhynodge.Query; import net.pterodactylus.rhynodge.State; +import net.pterodactylus.rhynodge.states.AbstractState; import net.pterodactylus.rhynodge.states.FailedState; import net.pterodactylus.rhynodge.states.HttpState; import org.apache.hc.client5.http.classic.methods.HttpGet; @@ -37,6 +40,9 @@ import org.apache.hc.core5.http.protocol.ResponseContent; import org.apache.hc.core5.ssl.SSLContexts; import org.jspecify.annotations.Nullable; +import static java.util.Arrays.asList; +import static java.util.Arrays.stream; + /** * {@link Query} that performs an HTTP GET request to a fixed uri. * @@ -44,18 +50,27 @@ import org.jspecify.annotations.Nullable; */ public class HttpQuery implements Query { - private final String uri; private final @Nullable String proxyHost; private final int proxyPort; + private List uris = new ArrayList<>(); public HttpQuery(String uri) { this(uri, null, -1); } + public HttpQuery(String uri, String... additionalUris) { + this(uri, null, -1, additionalUris); + } + public HttpQuery(String uri, @Nullable String proxyHost, int proxyPort) { - this.uri = uri; + this(uri, proxyHost, proxyPort, new String[0]); + } + + public HttpQuery(String uri, @Nullable String proxyHost, int proxyPort, String... additionalUris) { this.proxyHost = proxyHost; this.proxyPort = proxyPort; + this.uris.add(uri); + this.uris.addAll(asList(additionalUris)); } // @@ -81,22 +96,32 @@ public class HttpQuery implements Query { if ((proxyHost != null) && (proxyPort != -1)) { httpClientBuilder.setProxy(new HttpHost(proxyHost, proxyPort)); } - var get = new HttpGet(uri); + List states = new ArrayList<>(); try (var httpClient = httpClientBuilder.build()) { - /* make request. */ - get.addHeader("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.11 (KHTML, like Gecko) Ubuntu/12.04 Chromium/20.0.1132.47 Chrome/20.0.1132.47 Safari/536.11"); - var response = httpClient.execute(get); - if (response.getCode() != HttpStatus.SC_OK) { - return new FailedState(new IllegalStateException(String.format("Invalid HTTP Status: %d", response.getCode()))); - } - var entity = response.getEntity(); + for (var uri : uris) { + var get = new HttpGet(uri); + /* make request. */ + get.addHeader("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.11 (KHTML, like Gecko) Ubuntu/12.04 Chromium/20.0.1132.47 Chrome/20.0.1132.47 Safari/536.11"); + var response = httpClient.execute(get); + if (response.getCode() != HttpStatus.SC_OK) { + states.add(new FailedState(new IllegalStateException(String.format("Invalid HTTP Status: %d", response.getCode())))); + continue; + } + var entity = response.getEntity(); + + states.add(new HttpState(uri, response.getCode(), entity.getContentType(), EntityUtils.toByteArray(entity))); - /* yay, done! */ - return new HttpState(uri, response.getCode(), entity.getContentType(), EntityUtils.toByteArray(entity)); + } } catch (IOException ioe1) { - return new FailedState(ioe1); + states.add(new FailedState(ioe1)); } + + /* collapse states */ + return states.stream().reduce((state, newState) -> { + state.addState(newState); + return state; + }).get(); } } diff --git a/src/test/kotlin/net/pterodactylus/rhynodge/queries/HttpQueryTest.kt b/src/test/kotlin/net/pterodactylus/rhynodge/queries/HttpQueryTest.kt new file mode 100644 index 0000000..30c80bb --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/rhynodge/queries/HttpQueryTest.kt @@ -0,0 +1,59 @@ +package net.pterodactylus.rhynodge.queries + +import com.sun.net.httpserver.HttpExchange +import com.sun.net.httpserver.HttpServer +import java.net.InetSocketAddress +import net.pterodactylus.rhynodge.states.HttpState +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Test + +class HttpQueryTest { + + @Test + fun `http query returns content in state`() { + val query = HttpQuery("http://localhost:${httpServer.address.port}/url.test1") + val httpState = query.state() as HttpState + assertThat(httpState.protocolCode(), equalTo(200)) + assertThat(httpState.contentType(), equalTo("text/test1")) + assertThat(httpState.content(), equalTo("first-response")) + } + + @Test + fun `http query returns multiple states when multiple urls are given`() { + val query = HttpQuery("http://localhost:${httpServer.address.port}/url.test1", "http://localhost:${httpServer.address.port}/url.test2") + val httpState = query.state() as HttpState + assertThat(httpState.protocolCode(), equalTo(200)) + assertThat(httpState.contentType(), equalTo("text/test1")) + assertThat(httpState.content(), equalTo("first-response")) + val secondHttpState = httpState.additionalStates.single() as HttpState + assertThat(secondHttpState.protocolCode(), equalTo(200)) + assertThat(secondHttpState.contentType(), equalTo("text/test2")) + assertThat(secondHttpState.content(), equalTo("second-response")) + } + + private val httpServer = HttpServer.create(InetSocketAddress("localhost", 0), 0) + + init { + httpServer.createContext("/url.test1", sendResponse("text/test1", "first-response")) + httpServer.createContext("/url.test2", sendResponse("text/test2", "second-response")) + httpServer.start() + } + + @AfterEach + fun shutdownHttpServer() { + httpServer.stop(0) + } + +} + +val sendResponse = { contentType: String, response: String -> + { httpExchange: HttpExchange -> + httpExchange.responseHeaders.add("content-type", contentType) + httpExchange.sendResponseHeaders(200, response.toByteArray().size.toLong()) + httpExchange.responseBody.use { + it.write(response.toByteArray()) + } + } +} -- 2.7.4