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

Move DirectHandler to HTTP common and refactor reactive and Nima #4782

Merged
merged 1 commit into from
Aug 26, 2022
Merged
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates.
* Copyright (c) 2021, 2022 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,82 +14,117 @@
* limitations under the License.
*/

/*
* Copyright (c) 2022 Oracle and/or its affiliates.
*
* 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.helidon.nima.webserver.http;
package io.helidon.common.http;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import io.helidon.common.buffers.BufferData;
import io.helidon.common.http.HeadersServerResponse;
import io.helidon.common.http.Http;
import io.helidon.common.http.Http.HeaderName;

/**
* A handler that is invoked when a response is sent outside of router.
* See {@link SimpleHandler.EventType} to see which types
* A handler that is invoked when a response is sent outside of routing.
* See {@link DirectHandler.EventType} to see which types
* of events are covered by this handler.
* Direct handlers can be used both with blocking and reactive servers in Helidon.
*/
@FunctionalInterface
public interface SimpleHandler {
public interface DirectHandler {
/**
* Default handler will HTML encode the message (if any),
* use the default status code for the event type, and copy all headers configured.
*
*
* @return default direct handler
*/
static DirectHandler defaultHandler() {
return DirectHandlerDefault.INSTANCE;
}

/**
* Handler of responses that bypass router.
* <p>
* This method should be used to return custom status, header and possible entity.
* If there is a need to handle more details, please redirect the client to a proper endpoint to handle them.
*
* @param request request as received with as much known information as possible
* @param eventType type of the event
* @param request request as received with as much known information as possible
* @param eventType type of the event
* @param defaultStatus default status expected to be returned
* @param responseHeaders headers to be added to response
* @param thrown throwable caught as part of processing with possible additional details about the reason of failure
* @param thrown throwable caught as part of processing with possible additional details about the reason of failure
* @return response to use to return to original request
*/
default SimpleResponse handle(SimpleRequest request,
EventType eventType,
Http.Status defaultStatus,
HeadersServerResponse responseHeaders,
Throwable thrown) {
default TransportResponse handle(TransportRequest request,
EventType eventType,
Http.Status defaultStatus,
HeadersServerResponse responseHeaders,
Throwable thrown) {
return handle(request, eventType, defaultStatus, responseHeaders, thrown.getMessage());
}

/**
* Handler of responses that bypass router.
* Handler of responses that bypass routing.
* <p>
* This method should be used to return custom status, header and possible entity.
* If there is a need to handle more details, please redirect the client to a proper endpoint to handle them.
*
* @param request request as received with as much known information as possible
* @param eventType type of the event
* @param request request as received with as much known information as possible
* @param eventType type of the event
* @param defaultStatus default status expected to be returned
* @param responseHeaders headers to be added to response
* @param message informative message for cases that are not triggered by an exception, by default this will be called
* @param message informative message for cases that are not triggered by an exception, by default this will be called
* also
* for exceptional cases with the exception message
* @return response to use to return to original request
*/
SimpleResponse handle(SimpleRequest request,
EventType eventType,
Http.Status defaultStatus,
HeadersServerResponse responseHeaders,
String message);
TransportResponse handle(TransportRequest request,
EventType eventType,
Http.Status defaultStatus,
HeadersServerResponse responseHeaders,
String message);

/**
* Request information.
* Note that the information may not be according to specification, as this marks a bad request (by definition).
*/
interface TransportRequest {
/**
* Create an empty transport request.
* This is usually used when an error occurs before we could parse request information.
*
* @return empty transport request
*/
static TransportRequest empty() {
return DirectHandlerEmptyRequest.INSTANCE;
}

/**
* Protocol version (either from actual request, or guessed).
*
* @return protocol version
*/
String protocolVersion();

/**
* HTTP method.
*
* @return method
*/
String method();

/**
* Requested path, if found in request.
*
* @return uri or an empty string
*/
String path();

/**
* Headers, if found in request.
*
* @return headers or an empty map
*/
HeadersServerRequest headers();
}

/**
* Types of events that can be triggered outside of router
Expand Down Expand Up @@ -149,82 +184,19 @@ public boolean keepAlive() {
}
}

/**
* Request information.
* Note that the information may not be according to specification, as this marks a bad request (by definition).
*/
interface SimpleRequest {
/**
* Empty request, for cases where no information is available.
*
* @return empty simple request
*/
static SimpleRequest empty() {
return new SimpleRequest() {
@Override
public String protocolVersion() {
return "";
}

@Override
public String method() {
return "";
}

@Override
public String path() {
return "";
}

@Override
public Map<String, List<String>> headers() {
return Map.of();
}
};
}

/**
* Protocol version (either from actual request, or guessed).
*
* @return protocol version
*/
String protocolVersion();

/**
* HTTP method.
*
* @return method
*/
String method();

/**
* Requested URI, if found in request.
*
* @return uri or an empty string
*/
String path();

/**
* Headers, if found in request.
*
* @return headers or an empty map
*/
Map<String, List<String>> headers();
}

/**
* Response to correctly reply to the original client.
*/
class SimpleResponse {
class TransportResponse {
private final Http.Status status;
private final byte[] message;
private final HeadersServerResponse headers;
private final byte[] entity;
private final boolean keepAlive;

private SimpleResponse(Builder builder) {
private TransportResponse(Builder builder) {
this.status = builder.status;
this.message = builder.message;
this.headers = builder.headers;
this.entity = builder.entity;
this.keepAlive = builder.keepAlive;
}

Expand Down Expand Up @@ -260,8 +232,8 @@ public HeadersServerResponse headers() {
*
* @return mesage bytes or empty if no message is configured
*/
public Optional<byte[]> message() {
return Optional.ofNullable(message);
public Optional<byte[]> entity() {
return Optional.ofNullable(entity);
}

/**
Expand All @@ -274,20 +246,20 @@ public boolean keepAlive() {
}

/**
* Fluent API builder for {@link SimpleHandler.SimpleResponse}.
* Fluent API builder for {@link DirectHandler.TransportResponse}.
*/
public static class Builder implements io.helidon.common.Builder<Builder, SimpleResponse> {
private Http.Status status = Http.Status.OK_200;
private byte[] message = BufferData.EMPTY_BYTES;
public static class Builder implements io.helidon.common.Builder<Builder, TransportResponse> {
private Http.Status status = Http.Status.BAD_REQUEST_400;
private byte[] entity;
private HeadersServerResponse headers = HeadersServerResponse.create();
private boolean keepAlive = true;

private Builder() {
}

@Override
public SimpleResponse build() {
return new SimpleResponse(this);
public TransportResponse build() {
return new TransportResponse(this);
}

/**
Expand All @@ -313,64 +285,73 @@ public Builder headers(HeadersServerResponse headers) {
}

/**
* Configure keep alive.
* Set a header (if exists, it would be replaced).
* Keep alive header is ignored, please use {@link #keepAlive(boolean)}.
*
* @param keepAlive whether to keep alive
* @param name name of the header
* @param values value of the header
* @return updated builder
*/
public Builder keepAlive(boolean keepAlive) {
this.keepAlive = keepAlive;
public Builder header(Http.HeaderName name, String... values) {
this.headers.set(name, List.of(values));
return this;
}

/**
* Custom entity. Uses the content, encodes it for HTML, reads it as {@code UTF-8}, configures
* {@code Content-Length} header, configures {@code Content-Type} header to {@code text/plain}.
* <p>
* Use {@link #message(byte[])} for custom encoding.
* Set a header (if exists, it would be replaced).
* Keep alive header is ignored, please use {@link #keepAlive(boolean)}.
*
* @param message response entity
* @param header header value
* @return updated builder
*/
public Builder message(String message) {
this.headers.setIfAbsent(Http.HeaderValues.CONTENT_TYPE_TEXT_PLAIN);
return message(message.getBytes(StandardCharsets.UTF_8));
public Builder header(Http.HeaderValue header) {
this.headers.add(header);
return this;
}

/**
* Custom entity. Uses the content, configures
* {@code Content-Length} header.
* <p>
* Use {@link #message(String)} for simple text messages.
* Configure keep alive.
*
* @param entity response entity
* @param keepAlive whether to keep alive
* @return updated builder
*/
public Builder message(byte[] entity) {
this.message = entity;
public Builder keepAlive(boolean keepAlive) {
this.keepAlive = keepAlive;
return this;
}

/**
* Configure an additional header.
* Custom entity. Uses the content, reads it as {@code UTF-8}, configures
* {@code Content-Length} header, configures {@code Content-Type} header to {@code text/plain}.
* <p>
* Use {@link #entity(byte[])} for custom encoding.
* <p>
* Note that this method does not do any escaping (such as HTML encoding), make sure the entity is safe.
*
* @param headerName header name
* @param headerValue header value
* @param entity response entity
* @return updated builder
*/
public Builder header(HeaderName headerName, String headerValue) {
this.headers.add(headerName.withValue(headerValue));
return this;
public Builder entity(String entity) {
this.headers.setIfAbsent(Http.HeaderValues.CONTENT_TYPE_TEXT_PLAIN);
return entity(entity.getBytes(StandardCharsets.UTF_8));
}

/**
* Configure an additional header.
* Custom entity. Uses the content, configures
* {@code Content-Length} header.
* <p>
* Use {@link #entity(String)} for simple text messages.
*
* @param header header value
* @param entity response entity
* @return updated builder
*/
public Builder header(Http.HeaderValue header) {
this.headers.add(header);
public Builder entity(byte[] entity) {
this.entity = Arrays.copyOf(entity, entity.length);
if (this.entity.length == 0) {
this.headers.remove(Http.Header.CONTENT_LENGTH);
} else {
header(Http.Header.CONTENT_LENGTH, String.valueOf(entity.length));
}
return this;
}
}
Expand Down
Loading