diff --git a/core/src/main/java/io/undertow/server/handlers/ReasonPhraseHandler.java b/core/src/main/java/io/undertow/server/handlers/ReasonPhraseHandler.java
new file mode 100644
index 0000000000..7ea9655a9f
--- /dev/null
+++ b/core/src/main/java/io/undertow/server/handlers/ReasonPhraseHandler.java
@@ -0,0 +1,66 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2023 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * 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.undertow.server.handlers;
+
+import io.undertow.UndertowLogger;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+
+/**
+ * A handler which simply sets a response code.
+ *
+ * @author Bartosz Baranowski
+ */
+public final class ReasonPhraseHandler implements HttpHandler {
+
+ private static final boolean debugEnabled;
+
+ static {
+ debugEnabled = UndertowLogger.PREDICATE_LOGGER.isDebugEnabled();
+ }
+
+ private final String reasonPhrase;
+
+ private final HttpHandler next;
+ /**
+ * Construct a new instance.
+ *
+ * @param reasonPhrase the reason phrase to be set in status line
+ */
+ public ReasonPhraseHandler(final HttpHandler next, final String reasonPhrase) {
+ this.next = next;
+ this.reasonPhrase = reasonPhrase;
+ }
+
+ @Override
+ public void handleRequest(final HttpServerExchange exchange) throws Exception {
+ exchange.setReasonPhrase(reasonPhrase);
+ if(debugEnabled) {
+ UndertowLogger.PREDICATE_LOGGER.debugf("Reason phrase set to [%s] for %s.", this.reasonPhrase, exchange);
+ }
+ if(next != null) {
+ next.handleRequest(exchange);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "reason-phrase( " + this.reasonPhrase + " )";
+ }
+}
diff --git a/core/src/main/java/io/undertow/server/handlers/ResponseCodeHandler.java b/core/src/main/java/io/undertow/server/handlers/ResponseCodeHandler.java
index e4b4a20591..19f43354ad 100644
--- a/core/src/main/java/io/undertow/server/handlers/ResponseCodeHandler.java
+++ b/core/src/main/java/io/undertow/server/handlers/ResponseCodeHandler.java
@@ -64,13 +64,36 @@ public final class ResponseCodeHandler implements HttpHandler {
private final int responseCode;
+ private HttpHandler next;
/**
* Construct a new instance.
*
* @param responseCode the response code to set
+ * @param next next handler
*/
- public ResponseCodeHandler(final int responseCode) {
+ public ResponseCodeHandler(final HttpHandler next, final int responseCode) {
+ assert responseCode > 99;
+ assert responseCode < 600;
this.responseCode = responseCode;
+ this.next = next;
+ }
+
+ /**
+ * Construct a new instance.
+ *
+ * @param responseCode the response code to set
+ * @param next next handler
+ */
+ public ResponseCodeHandler(final int responseCode) {
+ this(null,responseCode);
+ }
+
+ public HttpHandler getNext() {
+ return next;
+ }
+
+ public void setNext(HttpHandler next) {
+ this.next = next;
}
@Override
@@ -79,6 +102,9 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
if(debugEnabled) {
UndertowLogger.PREDICATE_LOGGER.debugf("Response code set to [%s] for %s.", responseCode, exchange);
}
+ if(next != null) {
+ next.handleRequest(exchange);
+ }
}
@Override
diff --git a/core/src/main/java/io/undertow/server/handlers/ResponseHandler.java b/core/src/main/java/io/undertow/server/handlers/ResponseHandler.java
new file mode 100644
index 0000000000..1222818bf1
--- /dev/null
+++ b/core/src/main/java/io/undertow/server/handlers/ResponseHandler.java
@@ -0,0 +1,108 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2023 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * 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.undertow.server.handlers;
+
+import io.undertow.UndertowLogger;
+import io.undertow.server.BlockingHttpExchange;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.HeaderMap;
+import io.undertow.util.Headers;
+
+/**
+ * Class which handles set operations for response: code, reason phrase and potentially body and type. Status code is required
+ * parameter.
+ * The response handler allows to set response body as well.
+ * response(code=404, reason='dont like it') is roughly equivalent to reason-phrase('dont like it');response-code(404)"
+ *
+ * @author Bartosz Baranowski
+ */
+public class ResponseHandler implements HttpHandler {
+
+ private static final String DEFAULT_BODY_TYPE = "text/html";
+ private static final boolean debugEnabled;
+
+ static {
+ debugEnabled = UndertowLogger.PREDICATE_LOGGER.isDebugEnabled();
+ }
+
+ private final String body;
+ private final String type;
+ private final int code;
+ private final String reason;
+ private HttpHandler chained;
+
+ // TODO: review parsing/execution rules. For some reason without next, this particular handler does not ignore
+ // trailing handlers.
+ public ResponseHandler(final int code, final String reason) {
+ this(code, reason, null, null);
+ }
+
+ public ResponseHandler(final int code, final String reason, final String body) {
+ this(code, reason, body, DEFAULT_BODY_TYPE);
+ }
+
+ public ResponseHandler(final int code, final String reason, final String body, final String type) {
+ this.body = body;
+ this.type = type;
+ // toString only
+ this.code = code;
+ this.reason = reason;
+ if (reason != null) {
+ this.chained = new ReasonPhraseHandler(null, reason);
+ }
+ this.chained = new ResponseCodeHandler(this.chained, code);
+ }
+
+ @Override
+ public void handleRequest(HttpServerExchange exchange) throws Exception {
+ this.chained.handleRequest(exchange);
+ if (this.body != null) {
+ final byte[] bodyBytes = this.body.getBytes("UTF-8");
+ final HeaderMap responseHeaders = exchange.getResponseHeaders();
+ if (responseHeaders.contains(Headers.CONTENT_LENGTH) || responseHeaders.contains(Headers.CONTENT_TYPE) || exchange.isBlocking()) {
+ // TODO: explode
+ }
+ responseHeaders.add(Headers.CONTENT_TYPE, this.type);
+ responseHeaders.add(Headers.CONTENT_LENGTH, bodyBytes.length);
+ final BlockingHttpExchange blockingHttpExchange = exchange.startBlocking();
+ if (exchange.isInIoThread()) {
+ exchange.dispatch(new HttpHandler() {
+
+ @Override
+ public void handleRequest(HttpServerExchange exchange) throws Exception {
+ exchange.getOutputStream().write(bodyBytes);
+ }
+ });
+ } else {
+ exchange.getOutputStream().write(bodyBytes);
+ }
+
+ if (debugEnabled) {
+ UndertowLogger.PREDICATE_LOGGER.debugf("Respons body set to \n[%s]\nfor %s.", this.body, exchange);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "response( code='" + code + "'" + ((this.reason != null) ? ", reason='" + this.reason + "'" : "") + ""
+ + ((this.body != null) ? ", type='" + this.type + "', body='" + this.body + "'" : "") + " )";
+ }
+
+}
diff --git a/core/src/main/java/io/undertow/server/handlers/builder/ReasonPhraseHandlerBuilder.java b/core/src/main/java/io/undertow/server/handlers/builder/ReasonPhraseHandlerBuilder.java
new file mode 100644
index 0000000000..b0ea220131
--- /dev/null
+++ b/core/src/main/java/io/undertow/server/handlers/builder/ReasonPhraseHandlerBuilder.java
@@ -0,0 +1,68 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2023 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * 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.undertow.server.handlers.builder;
+
+import io.undertow.server.HandlerWrapper;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.handlers.ReasonPhraseHandler;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Bartosz Baranowski
+ */
+public class ReasonPhraseHandlerBuilder implements HandlerBuilder {
+ @Override
+ public String name() {
+ return "reason-phrase";
+ }
+
+ @Override
+ public Map> parameters() {
+ Map> parameters = new HashMap<>();
+ parameters.put("value", String.class);
+ return parameters;
+ }
+
+ @Override
+ public Set requiredParameters() {
+ final Set req = new HashSet<>();
+ req.add("value");
+ return req;
+ }
+
+ @Override
+ public String defaultParameter() {
+ return "value";
+ }
+
+ @Override
+ public HandlerWrapper build(final Map config) {
+ final String value = (String) config.get("value");
+ return new HandlerWrapper() {
+ @Override
+ public HttpHandler wrap(HttpHandler handler) {
+ return new ReasonPhraseHandler(handler, value);
+ }
+ };
+ }
+}
diff --git a/core/src/main/java/io/undertow/server/handlers/builder/ResponseCodeHandlerBuilder.java b/core/src/main/java/io/undertow/server/handlers/builder/ResponseCodeHandlerBuilder.java
index ac6631cba2..1ae2628a8a 100644
--- a/core/src/main/java/io/undertow/server/handlers/builder/ResponseCodeHandlerBuilder.java
+++ b/core/src/main/java/io/undertow/server/handlers/builder/ResponseCodeHandlerBuilder.java
@@ -61,7 +61,7 @@ public HandlerWrapper build(final Map config) {
return new HandlerWrapper() {
@Override
public HttpHandler wrap(HttpHandler handler) {
- return new ResponseCodeHandler(value);
+ return new ResponseCodeHandler(handler, value);
}
};
}
diff --git a/core/src/main/java/io/undertow/server/handlers/builder/ResponseHandlerBuilder.java b/core/src/main/java/io/undertow/server/handlers/builder/ResponseHandlerBuilder.java
new file mode 100644
index 0000000000..111f31d9f2
--- /dev/null
+++ b/core/src/main/java/io/undertow/server/handlers/builder/ResponseHandlerBuilder.java
@@ -0,0 +1,83 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2023 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * 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.undertow.server.handlers.builder;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import io.undertow.server.HandlerWrapper;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.handlers.ResponseHandler;
+
+/**
+ * @author Bartosz Baranowski
+ */
+public class ResponseHandlerBuilder implements HandlerBuilder {
+ @Override
+ public String name() {
+ return "response";
+ }
+
+ @Override
+ public Map> parameters() {
+ Map> parameters = new HashMap<>();
+ parameters.put("code", Integer.class);
+ parameters.put("reason", String.class);
+ parameters.put("type", String.class);
+ parameters.put("body", String.class);
+ return parameters;
+ }
+
+ @Override
+ public Set requiredParameters() {
+ final Set req = new HashSet<>();
+ req.add("code");
+ return req;
+ }
+
+ @Override
+ public String defaultParameter() {
+ // default parameter - name(paramValue) not supported
+ return null;
+ }
+
+ @Override
+ public HandlerWrapper build(final Map config) {
+ final Integer code = (Integer) config.get("code");
+ final String reason = (String) config.get("reason");
+ final String type = (String) config.get("type");
+ final String body = (String) config.get("body");
+ return new HandlerWrapper() {
+ @Override
+ public HttpHandler wrap(HttpHandler handler) {
+ if (body == null) {
+ return new ResponseHandler(code, reason);
+ } else {
+ if (type == null) {
+ return new ResponseHandler(code, reason, body);
+ } else {
+ return new ResponseHandler(code, reason, body, type);
+ }
+ }
+ }
+ };
+ }
+}
diff --git a/core/src/main/resources/META-INF/services/io.undertow.server.handlers.builder.HandlerBuilder b/core/src/main/resources/META-INF/services/io.undertow.server.handlers.builder.HandlerBuilder
index 0c2affdbe4..70f45c59c5 100644
--- a/core/src/main/resources/META-INF/services/io.undertow.server.handlers.builder.HandlerBuilder
+++ b/core/src/main/resources/META-INF/services/io.undertow.server.handlers.builder.HandlerBuilder
@@ -43,3 +43,5 @@ io.undertow.server.handlers.HttpContinueAcceptingHandler$Builder
io.undertow.server.handlers.form.EagerFormParsingHandler$Builder
io.undertow.server.handlers.SameSiteCookieHandler$Builder
io.undertow.server.handlers.SetErrorHandler$Builder
+io.undertow.server.handlers.builder.ReasonPhraseHandlerBuilder
+io.undertow.server.handlers.builder.ResponseHandlerBuilder
diff --git a/core/src/test/java/io/undertow/predicate/PredicateParsingTestCase.java b/core/src/test/java/io/undertow/predicate/PredicateParsingTestCase.java
index a64be6a14f..5d8cb1f01b 100644
--- a/core/src/test/java/io/undertow/predicate/PredicateParsingTestCase.java
+++ b/core/src/test/java/io/undertow/predicate/PredicateParsingTestCase.java
@@ -141,4 +141,5 @@ private void expect(String string, boolean result1, boolean result2) {
throw new RuntimeException("String " + string, ex);
}
}
+
}
diff --git a/core/src/test/java/io/undertow/server/handlers/PredicatedHandlersTestCase.java b/core/src/test/java/io/undertow/server/handlers/PredicatedHandlersTestCase.java
index 7ded4cd9d2..4cc438e617 100644
--- a/core/src/test/java/io/undertow/server/handlers/PredicatedHandlersTestCase.java
+++ b/core/src/test/java/io/undertow/server/handlers/PredicatedHandlersTestCase.java
@@ -25,8 +25,11 @@
import io.undertow.server.handlers.builder.PredicatedHandlersParser;
import io.undertow.testutils.DefaultServer;
import io.undertow.testutils.HttpClientUtils;
+import io.undertow.testutils.ProxyIgnore;
import io.undertow.testutils.TestHttpClient;
import io.undertow.util.StatusCodes;
+
+import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.junit.Assert;
@@ -159,4 +162,100 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {
handler.handleRequest(e);
Assert.assertEquals("foo'bar",e.getResponseHeaders().get("test-header").getFirst());
}
+
+ @Test @ProxyIgnore
+ public void testReasonPhrase() throws IOException {
+ DefaultServer.setRootHandler(
+ Handlers.predicates(
+
+ PredicatedHandlersParser.parse(
+ "path('/test') -> reason-phrase('test-my-patience');response-code(480)", getClass().getClassLoader()), new HttpHandler() {
+ @Override
+ public void handleRequest(HttpServerExchange exchange) throws Exception {
+ exchange.getResponseSender().send(exchange.getRelativePath());
+ }
+ }));
+ TestHttpClient client = new TestHttpClient();
+ try {
+ HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/test");
+
+ HttpResponse result = client.execute(get);
+ Assert.assertEquals("test-my-patience", result.getStatusLine().getReasonPhrase());
+ Assert.assertEquals(480, result.getStatusLine().getStatusCode());
+
+ } finally {
+ client.getConnectionManager().shutdown();
+ }
+
+ }
+
+ @Test @ProxyIgnore
+ public void testDefaultResponse() throws IOException {
+ DefaultServer.setRootHandler(
+ Handlers.predicates(
+ PredicatedHandlersParser.parse(
+ "path('/test') -> response(code='208', reason='test-my-patience', body='Dont-Touch')", getClass().getClassLoader()), new HttpHandler() {
+ @Override
+ public void handleRequest(HttpServerExchange exchange) throws Exception {
+ exchange.getResponseSender().send(exchange.getRelativePath());
+ }
+ }));
+ TestHttpClient client = new TestHttpClient();
+ try {
+ HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/test");
+
+ HttpResponse result = client.execute(get);
+ Assert.assertEquals("test-my-patience", result.getStatusLine().getReasonPhrase());
+ Assert.assertEquals(208, result.getStatusLine().getStatusCode());
+ final String body = HttpClientUtils.readResponse(result);
+ Assert.assertEquals("Dont-Touch", body);
+ Header[] hdrs = result.getHeaders("Content-Length");
+ Assert.assertNotNull(hdrs);
+ Assert.assertEquals(1, hdrs.length);
+ Assert.assertNotNull(hdrs[0]);
+ Assert.assertEquals(hdrs[0].getValue(), ""+"Dont-Touch".length());
+ hdrs = result.getHeaders("Content-Type");
+ Assert.assertNotNull(hdrs);
+ Assert.assertEquals(1, hdrs.length);
+ Assert.assertNotNull(hdrs[0]);
+ Assert.assertEquals(hdrs[0].getValue(), "text/html");
+ } finally {
+ client.getConnectionManager().shutdown();
+ }
+ }
+
+ @Test @ProxyIgnore
+ public void testCustomTypeResponse() throws IOException {
+ DefaultServer.setRootHandler(
+ Handlers.predicates(
+ PredicatedHandlersParser.parse(
+ "path('/test') -> response(code='208', reason='test-my-patience', body='Dont-Touch', type='text/plain')", getClass().getClassLoader()), new HttpHandler() {
+ @Override
+ public void handleRequest(HttpServerExchange exchange) throws Exception {
+ exchange.getResponseSender().send(exchange.getRelativePath());
+ }
+ }));
+ TestHttpClient client = new TestHttpClient();
+ try {
+ HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/test");
+
+ HttpResponse result = client.execute(get);
+ Assert.assertEquals("test-my-patience", result.getStatusLine().getReasonPhrase());
+ Assert.assertEquals(208, result.getStatusLine().getStatusCode());
+ final String body = HttpClientUtils.readResponse(result);
+ Assert.assertEquals("Dont-Touch", body);
+ Header[] hdrs = result.getHeaders("Content-Length");
+ Assert.assertNotNull(hdrs);
+ Assert.assertEquals(1, hdrs.length);
+ Assert.assertNotNull(hdrs[0]);
+ Assert.assertEquals(hdrs[0].getValue(), ""+"Dont-Touch".length());
+ hdrs = result.getHeaders("Content-Type");
+ Assert.assertNotNull(hdrs);
+ Assert.assertEquals(1, hdrs.length);
+ Assert.assertNotNull(hdrs[0]);
+ Assert.assertEquals(hdrs[0].getValue(), "text/plain");
+ } finally {
+ client.getConnectionManager().shutdown();
+ }
+ }
}