Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[UNDERTOW-1735] add reason-phrase handler and make response-code obey… #1419

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
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 @@ -655,4 +655,7 @@ public interface UndertowMessages {
@Message(id = 210, value = "Buffer content underflow for exchange '%s', buffer '%s'")
IOException bufferUnderflow(HttpServerExchange exchange, ByteBuffer buf);

@Message(id = 211, value = "Exchange '%s' already has body or is blocking.")
IOException exhangeBlockingOrBlocking(HttpServerExchange e);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also name is wrong. I will update on monday.


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

@Override
public Map<String, Class<?>> parameters() {
Map<String, Class<?>> 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<String> requiredParameters() {
final Set<String> 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<String, Object> 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);
}
}
}
};
}
}
Loading
Loading