diff --git a/examples/webserver/streaming/README.md b/examples/webserver/streaming/README.md
index 85085d11..a23e9cd3 100644
--- a/examples/webserver/streaming/README.md
+++ b/examples/webserver/streaming/README.md
@@ -14,6 +14,6 @@ java -jar target/helidon-examples-webserver-streaming.jar
Upload a file and download it back with `curl`:
```bash
-curl --data-binary "@target/classes/large-file.bin" http://localhost:8080/upload
+curl --data-binary "@large-file.bin" http://localhost:8080/upload
curl http://localhost:8080/download
```
diff --git a/examples/webserver/streaming/src/main/resources/large-file.bin b/examples/webserver/streaming/large-file.bin
similarity index 100%
rename from examples/webserver/streaming/src/main/resources/large-file.bin
rename to examples/webserver/streaming/large-file.bin
diff --git a/examples/webserver/streaming/pom.xml b/examples/webserver/streaming/pom.xml
index 79d0cb55..94e0e2a0 100644
--- a/examples/webserver/streaming/pom.xml
+++ b/examples/webserver/streaming/pom.xml
@@ -57,8 +57,13 @@
test
- io.helidon.webserver
- helidon-webserver-test-support
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
+
+ io.helidon.webclient
+ helidon-webclient
test
diff --git a/examples/webserver/streaming/src/main/java/io/helidon/webserver/examples/streaming/Main.java b/examples/webserver/streaming/src/main/java/io/helidon/webserver/examples/streaming/Main.java
index 669e9188..66feb40c 100644
--- a/examples/webserver/streaming/src/main/java/io/helidon/webserver/examples/streaming/Main.java
+++ b/examples/webserver/streaming/src/main/java/io/helidon/webserver/examples/streaming/Main.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,8 +24,6 @@
*/
public class Main {
- static final String LARGE_FILE_RESOURCE = "/large-file.bin";
-
private Main() {
}
@@ -58,4 +56,5 @@ public static void main(String[] args) {
System.out.println("Streaming service is down")
);
}
-}
+
+ }
diff --git a/examples/webserver/streaming/src/main/java/io/helidon/webserver/examples/streaming/ServerFileReader.java b/examples/webserver/streaming/src/main/java/io/helidon/webserver/examples/streaming/ServerFileReader.java
deleted file mode 100644
index 87632096..00000000
--- a/examples/webserver/streaming/src/main/java/io/helidon/webserver/examples/streaming/ServerFileReader.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (c) 2018, 2023 Oracle and/or its affiliates.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.helidon.webserver.examples.streaming;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.concurrent.Flow;
-import java.util.logging.Logger;
-
-import io.helidon.common.http.DataChunk;
-
-/**
- * Class ServerFileReader. Reads a file using NIO and produces data chunks for a
- * {@code Subscriber} to process.
- */
-public class ServerFileReader implements Flow.Publisher {
- private static final Logger LOGGER = Logger.getLogger(ServerFileReader.class.getName());
-
- static final int BUFFER_SIZE = 4096;
-
- private final Path path;
-
- ServerFileReader(Path path) {
- this.path = path;
- }
-
- @Override
- public void subscribe(Flow.Subscriber super DataChunk> s) {
- FileChannel channel;
- ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
-
- try {
- channel = FileChannel.open(path, StandardOpenOption.READ);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
-
- s.onSubscribe(new Flow.Subscription() {
- @Override
- public void request(long n) {
- try {
- while (n > 0) {
- int bytes = channel.read(buffer);
- if (bytes < 0) {
- s.onComplete();
- channel.close();
- return;
- }
- if (bytes > 0) {
- LOGGER.info(buffer.toString());
- buffer.flip();
- s.onNext(DataChunk.create(buffer));
- n--;
- }
- buffer.rewind();
- }
- } catch (IOException e) {
- s.onError(e);
- }
- }
-
- @Override
- public void cancel() {
- try {
- channel.close();
- } catch (IOException e) {
- LOGGER.info(e.getMessage());
- }
- }
- });
- }
-}
diff --git a/examples/webserver/streaming/src/main/java/io/helidon/webserver/examples/streaming/ServerFileWriter.java b/examples/webserver/streaming/src/main/java/io/helidon/webserver/examples/streaming/ServerFileWriter.java
deleted file mode 100644
index ef3f6b03..00000000
--- a/examples/webserver/streaming/src/main/java/io/helidon/webserver/examples/streaming/ServerFileWriter.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (c) 2018, 2023 Oracle and/or its affiliates.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.helidon.webserver.examples.streaming;
-
-import java.io.IOException;
-import java.nio.channels.FileChannel;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.concurrent.Flow;
-import java.util.logging.Logger;
-
-import io.helidon.common.http.DataChunk;
-import io.helidon.webserver.ServerResponse;
-
-/**
- * Class ServerFileWriter. Process data chunks from a {@code Producer} and
- * writes them to a temporary file using NIO. For simplicity, this {@code
- * Subscriber} requests an unbounded number of chunks on its subscription.
- */
-public class ServerFileWriter implements Flow.Subscriber {
- private static final Logger LOGGER = Logger.getLogger(ServerFileWriter.class.getName());
-
- private final FileChannel channel;
-
- private final ServerResponse response;
-
- ServerFileWriter(ServerResponse response) {
- this.response = response;
- try {
- Path tempFilePath = Files.createTempFile("large-file", ".tmp");
- channel = FileChannel.open(tempFilePath, StandardOpenOption.WRITE);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public void onSubscribe(Flow.Subscription subscription) {
- subscription.request(Long.MAX_VALUE);
- }
-
- @Override
- public void onNext(DataChunk chunk) {
- try {
- channel.write(chunk.data());
- LOGGER.info(chunk.data().toString() + " " + Thread.currentThread());
- chunk.release();
- } catch (IOException e) {
- LOGGER.info(e.getMessage());
- }
- }
-
- @Override
- public void onError(Throwable throwable) {
- throwable.printStackTrace();
- }
-
- @Override
- public void onComplete() {
- try {
- channel.close();
- response.send("DONE");
- } catch (IOException e) {
- LOGGER.info(e.getMessage());
- }
- }
-}
diff --git a/examples/webserver/streaming/src/main/java/io/helidon/webserver/examples/streaming/StreamingService.java b/examples/webserver/streaming/src/main/java/io/helidon/webserver/examples/streaming/StreamingService.java
index a860564e..d50da35d 100644
--- a/examples/webserver/streaming/src/main/java/io/helidon/webserver/examples/streaming/StreamingService.java
+++ b/examples/webserver/streaming/src/main/java/io/helidon/webserver/examples/streaming/StreamingService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,26 +18,22 @@
import java.io.IOException;
import java.io.UncheckedIOException;
-import java.net.URISyntaxException;
-import java.net.URL;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Arrays;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger;
import io.helidon.common.configurable.ScheduledThreadPoolSupplier;
import io.helidon.common.http.DataChunk;
+import io.helidon.common.http.Http;
import io.helidon.common.reactive.IoMulti;
import io.helidon.webserver.Routing;
import io.helidon.webserver.ServerRequest;
import io.helidon.webserver.ServerResponse;
import io.helidon.webserver.Service;
-import static io.helidon.webserver.examples.streaming.Main.LARGE_FILE_RESOURCE;
-
/**
* StreamingService class. Uses a {@code Subscriber} and a
* {@code Publisher} for uploading and downloading files.
@@ -45,18 +41,9 @@
public class StreamingService implements Service {
private static final Logger LOGGER = Logger.getLogger(StreamingService.class.getName());
private final ScheduledExecutorService executor = ScheduledThreadPoolSupplier.create().get();
- private final Path filePath;
+ private volatile Path filePath;
StreamingService() {
- URL resource = getClass().getResource(LARGE_FILE_RESOURCE);
- if (resource == null) {
- throw new IllegalStateException("Resource not found: " + LARGE_FILE_RESOURCE);
- }
- try {
- filePath = Paths.get(resource.toURI());
- } catch (URISyntaxException e) {
- throw new RuntimeException(e);
- }
}
@Override
@@ -68,23 +55,32 @@ public void update(Routing.Rules routingRules) {
private void upload(ServerRequest request, ServerResponse response) {
LOGGER.info("Entering upload ... " + Thread.currentThread());
Path tempFilePath = createTempFile("large-file", ".tmp");
+ filePath = tempFilePath;
+ LOGGER.info("Storing upload as " + tempFilePath);
request.content()
.map(DataChunk::data)
.flatMapIterable(Arrays::asList)
.to(IoMulti.writeToFile(tempFilePath)
.executor(executor)
- .build());
+ .build())
+ .thenRun(() -> response.status(Http.Status.OK_200).send());
LOGGER.info("Exiting upload ...");
}
private void download(ServerRequest request, ServerResponse response) {
LOGGER.info("Entering download ..." + Thread.currentThread());
+ if (filePath == null) {
+ LOGGER.warning("No file to download.");
+ response.status(Http.Status.BAD_REQUEST_400).send("No file to download. Please upload file first.");
+ return;
+ }
long length = filePath.toFile().length();
response.headers().contentLength(length);
response.send(IoMulti.multiFromByteChannelBuilder(newByteChannel(filePath))
.executor(executor)
- .build());
- LOGGER.info("Exiting download ...");
+ .build()
+ .map(DataChunk::create));
+ LOGGER.info("Exiting download. Returning " + length + " bytes...");
}
@SuppressWarnings("SameParameterValue")
diff --git a/examples/webserver/streaming/src/test/java/io/helidon/webserver/examples/streaming/MainTest.java b/examples/webserver/streaming/src/test/java/io/helidon/webserver/examples/streaming/MainTest.java
new file mode 100644
index 00000000..d813c956
--- /dev/null
+++ b/examples/webserver/streaming/src/test/java/io/helidon/webserver/examples/streaming/MainTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.webserver.examples.streaming;
+
+import io.helidon.common.reactive.Single;
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+import io.helidon.webclient.WebClient;
+import io.helidon.webclient.WebClientResponse;
+import io.helidon.webserver.WebServer;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static io.helidon.common.http.Http.Status;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.TestMethodOrder;
+
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+class MainTest {
+
+ private static WebServer webServer;
+ private static WebClient webClient;
+ private static int publicPort;
+
+ private final String TEST_DATA_1 = "Test Data 1";
+ private final String TEST_DATA_2 = "Test Data 2";
+
+ @BeforeAll
+ public static void startTheServer() {
+ webServer = WebServer.builder(Main.createRouting()).build().start().await();
+ webClient = WebClient.builder().build();
+ publicPort = webServer.port();
+ }
+
+ @Test
+ @Order(0)
+ void testBadRequest() throws Exception {
+ webClient.get()
+ .uri("http://localhost:" + publicPort)
+ .path("/download")
+ .request()
+ .thenAccept(response -> assertThat(response.status(), is(Status.BAD_REQUEST_400)))
+ .toCompletableFuture()
+ .get();
+ }
+
+ @Test
+ @Order(1)
+ void testUpload1() throws Exception {
+ WebClientResponse response = webClient.post()
+ .uri("http://localhost:" + publicPort)
+ .path("/upload")
+ .submit(TEST_DATA_1)
+ .toCompletableFuture()
+ .get();
+ assertThat(response.status(), is(Status.OK_200));
+ }
+
+ @Test
+ @Order(2)
+ void testDownload1() throws Exception {
+ WebClientResponse response = webClient.get()
+ .uri("http://localhost:" + publicPort)
+ .path("/download")
+ .request()
+ .toCompletableFuture()
+ .get();
+ assertThat(response.status(), is(Status.OK_200));
+ assertThat(response.content().as(String.class).get(), is (TEST_DATA_1));
+ }
+
+ @Test
+ @Order(3)
+ void testUpload2() throws Exception {
+ WebClientResponse response = webClient.post()
+ .uri("http://localhost:" + publicPort)
+ .path("/upload")
+ .submit(TEST_DATA_2)
+ .toCompletableFuture()
+ .get();
+ assertThat(response.status(), is(Status.OK_200));
+ }
+
+ @Test
+ @Order(4)
+ void testDownload2() throws Exception {
+ WebClientResponse response = webClient.get()
+ .uri("http://localhost:" + publicPort)
+ .path("/download")
+ .request()
+ .toCompletableFuture()
+ .get();
+ assertThat(response.status(), is(Status.OK_200));
+ assertThat(response.content().as(String.class).get(), is (TEST_DATA_2));
+ }
+}
\ No newline at end of file