it = Arrays.asList(pds).iterator();
+ while (it.hasNext()) {
+ var pd = it.next();
+
+ if (PROPERTIES_TO_IGNORE.contains(pd.getName())) {
+ continue;
+ }
+
+ buffer.append("\"");
+ buffer.append(pd.getDisplayName());
+ buffer.append("\"");
+ buffer.append(":");
+ buffer.append("\"");
+ buffer.append(pd.getReadMethod().invoke(contents).toString());
+ buffer.append("\"");
+
+ if (it.hasNext()) {
+ buffer.append(", ");
+ }
+ }
+
+
+ } catch (IntrospectionException | InvocationTargetException | IllegalAccessException e) {
+ throw new IllegalArgumentException(e);
+ }
+
+ buffer.append("}");
+ return buffer.toString();
+ }
+}
diff --git a/jujube-core/src/main/java/org/ophion/jujube/internal/util/TieredOutputStream.java b/jujube-core/src/main/java/org/ophion/jujube/internal/util/TieredOutputStream.java
index b665ec5..07e1d61 100644
--- a/jujube-core/src/main/java/org/ophion/jujube/internal/util/TieredOutputStream.java
+++ b/jujube-core/src/main/java/org/ophion/jujube/internal/util/TieredOutputStream.java
@@ -144,7 +144,7 @@ public String getContentAsText(Charset cs) throws IOException {
}
/**
- * Returns the path of the underlying file backing this stream. If this stream has not yet spilled out to its file
+ * Returns the path of the underlying file backing this stream. If this stream has not yet spilled over to its file
* based tier, we perform a full dump first.
* For a more efficient way to iterate over this buffer see @{link getContentAsStream}.
*
@@ -209,4 +209,8 @@ private void writeBufferToChannel() throws IOException {
channel.force(true);
}
}
+
+ public DataSize getLimit() {
+ return limit;
+ }
}
diff --git a/jujube-core/src/main/java/org/ophion/jujube/response/HttpResponses.java b/jujube-core/src/main/java/org/ophion/jujube/response/HttpResponses.java
new file mode 100644
index 0000000..b7a1c4a
--- /dev/null
+++ b/jujube-core/src/main/java/org/ophion/jujube/response/HttpResponses.java
@@ -0,0 +1,19 @@
+package org.ophion.jujube.response;
+
+import org.apache.hc.core5.http.HttpStatus;
+
+public class HttpResponses {
+ public static JujubeHttpResponse ok() {
+ return new JujubeHttpResponse();
+ }
+
+ public static JujubeHttpResponse ok(String message) {
+ return new JujubeHttpResponse(message);
+ }
+
+ public static JujubeHttpResponse badRequest(String message) {
+ var r = new JujubeHttpResponse(HttpStatus.SC_BAD_REQUEST);
+ r.setContent(message);
+ return r;
+ }
+}
diff --git a/jujube-core/src/test/java/org/ophion/jujube/IntegrationTest.java b/jujube-core/src/test/java/org/ophion/jujube/IntegrationTest.java
index b99b07e..4229058 100644
--- a/jujube-core/src/test/java/org/ophion/jujube/IntegrationTest.java
+++ b/jujube-core/src/test/java/org/ophion/jujube/IntegrationTest.java
@@ -26,6 +26,7 @@
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
+import java.time.Duration;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@@ -71,6 +72,8 @@ void setUp() throws UnknownHostException, URISyntaxException {
.setSelectInterval(TimeValue.of(100, TimeUnit.MILLISECONDS))
.build()
);
+ // for testing purposes we want to shut down as quickly as possible
+ config.getServerConfig().setShutDownDelay(Duration.ZERO);
server = new Jujube(config);
endpoint = URIBuilder.localhost().setPort(config.getServerConfig().getListenPort()).setScheme("https").build();
}
diff --git a/jujube-core/src/test/java/org/ophion/jujube/MultiPartPostTest.java b/jujube-core/src/test/java/org/ophion/jujube/MultiPartPostTest.java
index 8eb47ba..def66cd 100644
--- a/jujube-core/src/test/java/org/ophion/jujube/MultiPartPostTest.java
+++ b/jujube-core/src/test/java/org/ophion/jujube/MultiPartPostTest.java
@@ -15,6 +15,7 @@
import java.io.IOException;
import java.nio.file.Files;
+import java.util.concurrent.atomic.AtomicInteger;
class MultiPartPostTest extends IntegrationTest {
private static final Logger LOG = Loggers.build();
@@ -53,11 +54,13 @@ void shouldHandleMultiPartFormPosts() throws IOException {
@Test
void shouldStreamLargeFiles() throws IOException {
var size = DataSize.megabytes(10);
+ AtomicInteger counter = new AtomicInteger();
config.route("/post", ctx -> {
try {
var file = (FileParameter) ctx.getParameter("file", ParameterSource.FORM).orElseThrow();
long bytes = Files.size(file.asPath());
Assertions.assertEquals(size.toBytes(), bytes);
+ counter.incrementAndGet();
} catch (IOException e) {
throw new IllegalStateException(e);
}
@@ -80,5 +83,7 @@ void shouldStreamLargeFiles() throws IOException {
Assertions.assertEquals(200, response.getCode());
return true;
});
+
+ Assertions.assertEquals(1, counter.get());
}
}
diff --git a/jujube-core/src/test/java/org/ophion/jujube/SuspiciousBehaviorTest.java b/jujube-core/src/test/java/org/ophion/jujube/SuspiciousBehaviorTest.java
index a9dc8b9..c8d4a9b 100644
--- a/jujube-core/src/test/java/org/ophion/jujube/SuspiciousBehaviorTest.java
+++ b/jujube-core/src/test/java/org/ophion/jujube/SuspiciousBehaviorTest.java
@@ -61,10 +61,8 @@ void shouldLimitPostSize() throws IOException {
@Test
void shouldLimitPostSizeByContentLength() throws IOException {
config.route("/post", ctx -> {
- Assertions.assertFalse(ctx.getParameter("file", ParameterSource.FORM).isPresent());
- var response = new JujubeHttpResponse("w00t");
- response.setCode(200);
- return response;
+ Assertions.fail("request should not be processed");
+ return null;
});
config.getServerConfig().setRequestEntityLimit(DataSize.bytes(0));
diff --git a/jujube-core/src/test/java/org/ophion/jujube/internal/util/JSONTest.java b/jujube-core/src/test/java/org/ophion/jujube/internal/util/JSONTest.java
new file mode 100644
index 0000000..7d57413
--- /dev/null
+++ b/jujube-core/src/test/java/org/ophion/jujube/internal/util/JSONTest.java
@@ -0,0 +1,49 @@
+package org.ophion.jujube.internal.util;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+
+class JSONTest {
+ @Test
+ void shouldConvertToJson() {
+ var json = JSON.stringify(Map.of("name", "Bob", "color", "blue"));
+ Assertions.assertEquals("abc", json);
+ }
+
+ @Test
+ void shouldConvertBeansToJson() {
+ var json = JSON.stringify(new Human("Bob", 42, "secret"));
+ Assertions.assertEquals("{\"age\":\"42\", \"name\":\"Bob\"}", json);
+
+ }
+
+ private static class Human {
+ private String name;
+ private int age;
+ private String internal;
+
+ public Human(String name, int age, String internal) {
+ this.name = name;
+ this.age = age;
+ this.internal = internal;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+ }
+}
diff --git a/jujube-example/src/main/java/org/ophion/jujube/example/resources/ChecksumResource.java b/jujube-example/src/main/java/org/ophion/jujube/example/resources/ChecksumResource.java
index 94a9663..4581451 100644
--- a/jujube-example/src/main/java/org/ophion/jujube/example/resources/ChecksumResource.java
+++ b/jujube-example/src/main/java/org/ophion/jujube/example/resources/ChecksumResource.java
@@ -7,8 +7,8 @@
import org.ophion.jujube.context.JujubeHttpContext;
import org.ophion.jujube.context.ParameterSource;
import org.ophion.jujube.http.HttpConstraints;
-import org.ophion.jujube.http.HttpResponses;
import org.ophion.jujube.response.ClientError;
+import org.ophion.jujube.response.HttpResponses;
import org.ophion.jujube.response.JujubeHttpResponse;
import java.io.BufferedInputStream;
@@ -16,26 +16,46 @@
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.Security;
+import java.util.Arrays;
+import java.util.stream.Collectors;
/**
* Sample resource that calculates the checksum of a file.
+ *
+ * $ curl -F "file=@/Users/rafael/Downloads/output.pdf" -vk https://localhost:8080/checksum/
*/
public class ChecksumResource {
public JujubeHttpResponse post(JujubeHttpContext ctx) throws NoSuchAlgorithmException, IOException {
HttpConstraints.onlyAllowMediaType(ContentType.MULTIPART_FORM_DATA, ctx);
- HttpConstraints.onlyAllowMethod(Method.POST, ctx);
+
+ if (Method.GET.isSame(ctx.getRequest().getMethod())) {
+ var availableHashes = Arrays.stream(Security.getProviders())
+ .flatMap(provider -> provider.getServices().stream())
+ .filter(s -> MessageDigest.class.getSimpleName().equals(s.getType()))
+ .map(Provider.Service::getAlgorithm)
+ .collect(Collectors.joining(","));
+
+ var resp = String.format("Checksum calculating resource, please send us a file, and an optional hash to use. Available hashes: %s",
+ availableHashes);
+ return HttpResponses.ok(resp);
+ }
var file = (FileParameter) ctx.getParameter("file", ParameterSource.FORM)
.orElseThrow(() -> new ClientError("Oops, you must supply a file argument"));
- var hash = ctx.getParameter("file", ParameterSource.FORM);
+ var hash = ctx.getParameter("hash", ParameterSource.FORM);
+ var digest = MessageDigest.getInstance("SHA-256");
- var digest = MessageDigest.getInstance("sha256");
+ try {
+ if (hash.isPresent()) {
+ digest = MessageDigest.getInstance(hash.get().asText());
+ }
- if (hash.isPresent()) {
- digest = MessageDigest.getInstance(hash.get().asText());
+ } catch (NoSuchAlgorithmException ex) {
+ return HttpResponses.badRequest(String.format("error: %s \n", ex.getMessage()));
}
-
try (var ins = new BufferedInputStream(Files.newInputStream(file.asPath()))) {
while (ins.available() > 0) {
digest.update((byte) ins.read());
@@ -43,6 +63,8 @@ public JujubeHttpResponse post(JujubeHttpContext ctx) throws NoSuchAlgorithmExce
}
var checksum = Hex.encodeHexString(digest.digest());
- return HttpResponses.ok(String.format("checksum: %s", checksum));
+ return HttpResponses.ok(String.format("checksum:%s \n", checksum));
+
+
}
}
diff --git a/jujube-example/src/main/java/org/ophion/jujube/example/resources/EchoResource.java b/jujube-example/src/main/java/org/ophion/jujube/example/resources/EchoResource.java
index 1de9bf3..4b8cdc2 100644
--- a/jujube-example/src/main/java/org/ophion/jujube/example/resources/EchoResource.java
+++ b/jujube-example/src/main/java/org/ophion/jujube/example/resources/EchoResource.java
@@ -16,9 +16,9 @@ public JujubeHttpResponse hello(JujubeHttpContext ctx) {
}
var param = ctx.getParameter("name", ParameterSource.FORM)
- .orElseThrow(() -> new ClientError("visitor param is required"));
+ .orElseThrow(() -> new ClientError("name param is required"));
- return new JujubeHttpResponse("Well, hello there:" + param.asText());
+ return new JujubeHttpResponse(String.format("Well, hello there: %s!\n", param.asText()));
}
public JujubeHttpResponse notFound(JujubeHttpContext ctx) {
diff --git a/pom.xml b/pom.xml
index 3f415ad..777ad67 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,6 +18,7 @@
UTF-8
3.8.1
3.0.0-M4
+ 5.0