Skip to content

Commit

Permalink
[UNDERTOW-1735] add reason-phrase handler and make response-code obey…
Browse files Browse the repository at this point in the history
… doc contract of one-by-one execution
  • Loading branch information
baranowb committed Mar 27, 2024
1 parent ddb4aee commit a9b28f1
Show file tree
Hide file tree
Showing 10 changed files with 459 additions and 2 deletions.
3 changes: 3 additions & 0 deletions core/src/main/java/io/undertow/UndertowMessages.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLPeerUnverifiedException;

import io.undertow.server.HttpServerExchange;
import io.undertow.server.RequestTooBigException;
import io.undertow.server.handlers.form.MultiPartParserDefinition;
import io.undertow.util.UrlDecodeException;
Expand Down Expand Up @@ -647,4 +648,6 @@ public interface UndertowMessages {
@Message(id = 208, value = "Failed to allocate resource")
IOException failedToAllocateResource();

@Message(id = 209, value = "Exchange '%s' already has body or is blocking.")
IOException exhangeBlockingOrBlocking(final HttpServerExchange e);
}
Original file line number Diff line number Diff line change
@@ -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 <a href="mailto:[email protected]">Bartosz Baranowski</a>
*/
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 + " )";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
109 changes: 109 additions & 0 deletions core/src/main/java/io/undertow/server/handlers/ResponseHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* 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.UndertowMessages;
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.<br>
* The response handler allows to set response body as well. <br>
* response(code=404, reason='dont like it') is roughly equivalent to reason-phrase('dont like it');response-code(404)"<br>
*
* @author <a href="mailto:[email protected]">Bartosz Baranowski</a>
*/
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: need user feedback
throw UndertowMessages.MESSAGES.exhangeBlockingOrBlocking(exchange);
}
responseHeaders.add(Headers.CONTENT_TYPE, this.type);
responseHeaders.add(Headers.CONTENT_LENGTH, bodyBytes.length);
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 + "'" : "") + " )";
}

}
Original file line number Diff line number Diff line change
@@ -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 <a href="mailto:[email protected]">Bartosz Baranowski</a>
*/
public class ReasonPhraseHandlerBuilder implements HandlerBuilder {
@Override
public String name() {
return "reason-phrase";
}

@Override
public Map<String, Class<?>> parameters() {
Map<String, Class<?>> parameters = new HashMap<>();
parameters.put("value", String.class);
return parameters;
}

@Override
public Set<String> requiredParameters() {
final Set<String> req = new HashSet<>();
req.add("value");
return req;
}

@Override
public String defaultParameter() {
return "value";
}

@Override
public HandlerWrapper build(final Map<String, Object> config) {
final String value = (String) config.get("value");
return new HandlerWrapper() {
@Override
public HttpHandler wrap(HttpHandler handler) {
return new ReasonPhraseHandler(handler, value);
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public HandlerWrapper build(final Map<String, Object> config) {
return new HandlerWrapper() {
@Override
public HttpHandler wrap(HttpHandler handler) {
return new ResponseCodeHandler(value);
return new ResponseCodeHandler(handler, value);
}
};
}
Expand Down
Loading

0 comments on commit a9b28f1

Please sign in to comment.