Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions bolt-jakarta-jetty/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<description>A handy way to run Slack Bolt apps on Jakarta EE compatible Jetty HTTP server.</description>
<url>https://docs.slack.dev/tools/java-slack-sdk</url>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>com.slack.api</groupId>
Expand Down Expand Up @@ -45,27 +50,21 @@
</dependency>

<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>${jakarta.servlet-api.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>${jakarta.jetty.version}</version>
<groupId>org.eclipse.jetty.ee9</groupId>
<artifactId>jetty-ee9-servlet</artifactId>
<version>${jakarta.jetty12.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jakarta.jetty.version}</version>
<version>${jakarta.jetty12.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>${jakarta.jetty.version}</version>
<groupId>org.eclipse.jetty.ee9</groupId>
<artifactId>jetty-ee9-webapp</artifactId>
<version>${jakarta.jetty12.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@
import com.slack.api.bolt.jakarta_servlet.SlackOAuthAppServlet;
import com.slack.api.bolt.jakarta_servlet.WebEndpointServlet;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jetty.ee9.servlet.ServletContextHandler;
import org.eclipse.jetty.ee9.servlet.ServletHolder;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;

import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;

Expand All @@ -36,17 +39,69 @@ public class SlackAppServer {
// This is intentionally mutable to allow developers to register their own one
private ErrorHandler errorHandler = new ErrorHandler() {
@Override
protected void writeErrorHtml(
Request request,
Writer writer,
Charset charset,
int code,
String message,
Throwable cause) throws IOException {
if (writeMinimalError(writer, code)) {
return;
}
super.writeErrorHtml(request, writer, charset, code, message, cause);
}

@Override
protected void writeErrorJson(
Request request, PrintWriter writer, int code, String message, Throwable cause) {
if (writeMinimalError(writer, code)) {
return;
}
super.writeErrorJson(request, writer, code, message, cause);
}

@Override
protected void writeErrorPlain(
Request request, PrintWriter writer, int code, String message, Throwable cause) {
if (writeMinimalError(writer, code)) {
return;
}
super.writeErrorPlain(request, writer, code, message, cause);
}
};

private final org.eclipse.jetty.ee9.nested.ErrorHandler servletErrorHandler =
new org.eclipse.jetty.ee9.nested.ErrorHandler() {
@Override
protected void writeErrorPage(
HttpServletRequest request,
Writer writer,
int code,
String message,
boolean showStacks) throws IOException {
if (localDebug) {
super.writeErrorPage(request, writer, code, message, showStacks);
} else {
writer.write("{\"status\":\"" + code + "\"}");
if (writeMinimalError(writer, code)) {
return;
}
super.writeErrorPage(request, writer, code, message, showStacks);
}

@Override
protected void writeErrorJson(
HttpServletRequest request, PrintWriter writer, int code, String message) {
if (writeMinimalError(writer, code)) {
return;
}
super.writeErrorJson(request, writer, code, message);
}

@Override
protected void writeErrorPlain(
HttpServletRequest request, PrintWriter writer, int code, String message) {
if (writeMinimalError(writer, code)) {
return;
}
super.writeErrorPlain(request, writer, code, message);
}
};

Expand Down Expand Up @@ -119,6 +174,7 @@ public SlackAppServer(Map<String, App> pathToApp, int port) {
}
}
pathToApp.putAll(addedOnes);
handler.setErrorHandler(servletErrorHandler);
server.setHandler(handler);
server.setErrorHandler(errorHandler);
}
Expand Down Expand Up @@ -152,6 +208,22 @@ public void setErrorHandler(ErrorHandler errorHandler) {
// internal methods
// ----------------------------------------------------

private boolean writeMinimalError(Writer writer, int code) throws IOException {
if (localDebug) {
return false;
}
writer.write("{\"status\":\"" + code + "\"}");
return true;
}

private boolean writeMinimalError(PrintWriter writer, int code) {
if (localDebug) {
return false;
}
writer.write("{\"status\":\"" + code + "\"}");
return true;
}

private static Map<String, App> toApps(App app, String path) {
Map<String, App> apps = new HashMap<>();
apps.put(path, app);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package test_locally.jakarta_jetty;

import com.slack.api.bolt.App;
import com.slack.api.bolt.AppConfig;
import com.slack.api.bolt.jakarta_jetty.SlackAppServer;
import org.junit.Test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.ServerSocket;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assume.assumeThat;

public class SlackAppServerTest {

@Test
public void startsAndServesMinimalErrorResponseForEverySupportedAcceptType() throws Exception {
assumeThat(System.getenv("SLACK_APP_LOCAL_DEBUG"), is(nullValue()));

int port = findAvailablePort();
App app = new App(new AppConfig(), Collections.emptyList());
SlackAppServer server = new SlackAppServer(app, port);
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> serverFuture = executor.submit(() -> {
server.start();
return null;
});

try {
String[][] acceptTypes = {
{null, "text/html"},
{"text/html", "text/html"},
{"text/*", "text/html"},
{"*/*", "text/html"},
{"application/json", "application/json"},
{"text/json", "text/json"},
{"text/plain", "text/plain"}
};
for (String[] acceptType : acceptTypes) {
HttpURLConnection connection = awaitResponse(serverFuture, port, acceptType[0]);
try {
assertMinimalErrorResponse(connection, acceptType[0], acceptType[1]);
} finally {
connection.disconnect();
}
}
} finally {
try {
server.stop();
serverFuture.get(10, TimeUnit.SECONDS);
} finally {
executor.shutdownNow();
}
}
}

private static void assertMinimalErrorResponse(
HttpURLConnection connection, String acceptType, String expectedContentType) throws IOException {
String requestDescription = acceptType == null ? "with default Accept" : "with Accept: " + acceptType;
assertThat("status " + requestDescription, connection.getResponseCode(), is(equalTo(404)));
assertThat("Server header " + requestDescription, connection.getHeaderField("Server"), is(nullValue()));
assertThat("Content-Type " + requestDescription,
connection.getContentType(), startsWith(expectedContentType));
try (BufferedReader reader = new BufferedReader(new InputStreamReader(
connection.getErrorStream(), StandardCharsets.UTF_8))) {
assertThat("body " + requestDescription,
reader.lines().collect(Collectors.joining("\n")),
is(equalTo("{\"status\":\"404\"}")));
}
}

private static int findAvailablePort() throws IOException {
try (ServerSocket socket = new ServerSocket(0)) {
return socket.getLocalPort();
}
}

private static HttpURLConnection awaitResponse(Future<?> serverFuture, int port, String acceptType)
throws Exception {
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(10);
IOException lastFailure = null;
while (System.nanoTime() < deadline) {
if (serverFuture.isDone()) {
serverFuture.get();
throw new AssertionError("SlackAppServer stopped before accepting requests");
}

HttpURLConnection connection = (HttpURLConnection) new URL(
"http://127.0.0.1:" + port + "/missing").openConnection();
if (acceptType != null) {
connection.setRequestProperty("Accept", acceptType);
}
connection.setConnectTimeout(200);
connection.setReadTimeout(200);
try {
connection.getResponseCode();
return connection;
} catch (IOException e) {
lastFailure = e;
connection.disconnect();
Thread.sleep(10);
}
}
throw new AssertionError("SlackAppServer did not accept requests within 10 seconds", lastFailure);
}
}
4 changes: 3 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@
<delombok.output>target/generated-sources-for-javadocs</delombok.output>

<!-- optional / testing-only dependencies -->
<!-- Note that "[11.0,12.0)" does not exclude jetty-xxx 12.0.0.alpha0 -->
<!-- Jetty 11 remains the test harness for bolt-jakarta-servlet. -->
<jakarta.jetty.version>[11.0,11.1)</jakarta.jetty.version>
<!-- bolt-jakarta-jetty uses Jetty 12's Jakarta EE 9 implementation. -->
<jakarta.jetty12.version>[12.1,12.2)</jakarta.jetty12.version>
<java-jwt.version>[4.0,5.0)</java-jwt.version>
<aws.s3.version>[2.29.24,3)</aws.s3.version>
<junit.version>4.13.2</junit.version>
Expand Down
1 change: 1 addition & 0 deletions scripts/run_no_prep_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ then
elif [[ "${is_jdk_14}" != "" ]];
then
./mvnw \
-pl !bolt-jakarta-jetty \
-pl !bolt-micronaut \
$MVN_PHASES \
'-Dtest=test_locally.**.*Test' -Dsurefire.failIfNoSpecifiedTests=false ${CI_ARGS} \
Expand Down