Skip to content

Commit

Permalink
Support customize encoder and media context in Nima WebServer (#5256)
Browse files Browse the repository at this point in the history
  • Loading branch information
joeylee committed Nov 1, 2022
1 parent d913bc4 commit d3c62e4
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* 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.tests.integration.server;

import io.helidon.common.http.Headers;
import io.helidon.common.http.Http;
import io.helidon.nima.http.encoding.ContentDecoder;
import io.helidon.nima.http.encoding.ContentEncoder;
import io.helidon.nima.http.encoding.ContentEncodingContext;
import io.helidon.nima.testing.junit5.webserver.ServerTest;
import io.helidon.nima.testing.junit5.webserver.SetUpRoute;
import io.helidon.nima.testing.junit5.webserver.SetUpServer;
import io.helidon.nima.webclient.http1.Http1Client;
import io.helidon.nima.webclient.http1.Http1ClientResponse;
import io.helidon.nima.webserver.WebServer;
import io.helidon.nima.webserver.http.*;
import org.junit.jupiter.api.Test;
import java.util.NoSuchElementException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;

@ServerTest
class ContentEncodingContextTest {

private static final CustomizedEncodingContext encodingContext = new CustomizedEncodingContext();

private final Http1Client client;

ContentEncodingContextTest(Http1Client socketHttpClient) {
this.client = socketHttpClient;
}

@SetUpServer
static void server(WebServer.Builder server) {
server.contentEncodingContext(encodingContext);
}

@SetUpRoute
static void routing(HttpRules rules) {
rules.get("/hello", (req, res) -> res.send("hello nima"));
}

@Test
void testCustomizeContentEncodingContext() {
try (Http1ClientResponse response = client.method(Http.Method.GET).uri("/hello").request()) {
assertThat(response.entity().as(String.class), equalTo("hello nima"));
assertThat(encodingContext.NO_ACCEPT_ENCODING_COUNT, greaterThan(0));
}
}


private static class CustomizedEncodingContext implements ContentEncodingContext {
int ACCEPT_ENCODING_COUNT = 0;

int NO_ACCEPT_ENCODING_COUNT = 0;

ContentEncodingContext contentEncodingContext = ContentEncodingContext.create();

@Override
public boolean contentEncodingEnabled() {
return contentEncodingContext.contentEncodingEnabled();
}

@Override
public boolean contentDecodingEnabled() {
return contentEncodingContext.contentDecodingEnabled();
}

@Override
public boolean contentEncodingSupported(String encodingId) {
return contentEncodingContext.contentEncodingSupported(encodingId);
}

@Override
public boolean contentDecodingSupported(String encodingId) {
return contentEncodingContext.contentDecodingSupported(encodingId);
}

@Override
public ContentEncoder encoder(String encodingId) throws NoSuchElementException {
return contentEncodingContext.encoder(encodingId);
}

@Override
public ContentDecoder decoder(String encodingId) throws NoSuchElementException {
return contentEncodingContext.decoder(encodingId);
}

@Override
public ContentEncoder encoder(Headers headers) {
if (headers.contains(Http.Header.ACCEPT_ENCODING)) {
ACCEPT_ENCODING_COUNT++;
} else {
NO_ACCEPT_ENCODING_COUNT++;
}
return contentEncodingContext.encoder(headers);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* 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.tests.integration.server;

import io.helidon.common.GenericType;
import io.helidon.common.http.Headers;
import io.helidon.common.http.Http;
import io.helidon.common.http.WritableHeaders;
import io.helidon.nima.http.media.EntityReader;
import io.helidon.nima.http.media.EntityWriter;
import io.helidon.nima.http.media.MediaContext;
import io.helidon.nima.testing.junit5.webserver.ServerTest;
import io.helidon.nima.testing.junit5.webserver.SetUpRoute;
import io.helidon.nima.testing.junit5.webserver.SetUpServer;
import io.helidon.nima.webclient.http1.Http1Client;
import io.helidon.nima.webclient.http1.Http1ClientResponse;
import io.helidon.nima.webserver.WebServer;
import io.helidon.nima.webserver.http.*;
import org.junit.jupiter.api.Test;
import java.io.OutputStream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsEqual.equalTo;

@ServerTest
class MediaContextTest {

private final Http1Client client;

public MediaContextTest(Http1Client client) {
this.client = client;
}

@SetUpServer
static void server(WebServer.Builder server) {
server.mediaContext(new CustomizedMediaContext());
}

@SetUpRoute
static void routing(HttpRules rules) {
rules.get("/hello", (req, res) -> res.send("hello nima"));
}

@Test
void testCustomizeMediaContext() {

try (Http1ClientResponse response = client.method(Http.Method.GET).uri("/hello").request()) {
String responseEntityString = response.entity().as(String.class);
assertThat(responseEntityString.length(), equalTo(5));
assertThat(responseEntityString, is("hello"));
}
}

private static class CustomizedMediaContext implements MediaContext {
private MediaContext delegated = MediaContext.create();

@Override
public <T> EntityReader<T> reader(GenericType<T> type, Headers headers) {
return delegated.reader(type, headers);
}

@Override
public <T> EntityWriter<T> writer(GenericType<T> type, Headers requestHeaders, WritableHeaders<?> responseHeaders) {
EntityWriter<T> impl = delegated.writer(type, requestHeaders, responseHeaders);

EntityWriter<T> realWriter = new EntityWriter<T>() {
@Override
public void write(GenericType<T> type, T object, OutputStream outputStream, Headers requestHeaders, WritableHeaders<?> responseHeaders) {
if (object instanceof String) {
String maxLen5 = (String) ((String) object).substring(0, 5);
impl.write(type, (T) maxLen5, outputStream, requestHeaders, responseHeaders);
} else {
impl.write(type, object, outputStream, requestHeaders, responseHeaders);
}
}

@Override
public void write(GenericType<T> type, T object, OutputStream outputStream, WritableHeaders<?> headers) {
impl.write(type, object, outputStream, headers);
}
};
return realWriter;
}

@Override
public <T> EntityReader<T> reader(GenericType<T> type, Headers requestHeaders, Headers responseHeaders) {
return delegated.reader(type, requestHeaders, responseHeaders);
}

@Override
public <T> EntityWriter<T> writer(GenericType<T> type, WritableHeaders<?> requestHeaders) {
return delegated.writer(type, requestHeaders);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import java.util.function.Consumer;

import io.helidon.common.Version;
import io.helidon.nima.http.encoding.ContentEncodingContext;
import io.helidon.nima.http.media.MediaContext;
import io.helidon.nima.webserver.http.DirectHandlers;
import io.helidon.nima.webserver.spi.ServerConnectionProvider;

Expand Down Expand Up @@ -65,6 +67,9 @@ class LoomServer implements WebServer {
defaultRouter = Router.empty();
}

MediaContext mediaContext = builder.mediaContext();
ContentEncodingContext contentEncodingContext = builder.contentEncodingContext();

for (String socketName : socketNames) {
Router router = routers.get(socketName);
if (router == null) {
Expand All @@ -84,7 +89,9 @@ class LoomServer implements WebServer {
socketName,
socketConfig,
router,
simpleHandlers));
simpleHandlers,
mediaContext,
contentEncodingContext));
}

this.listeners = Map.copyOf(listeners);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,8 @@ class ServerListener {
private final SocketOptions connectionOptions;
private final InetSocketAddress configuredAddress;

// todo now only from service loader, should be explicitly configurable (both)
private final MediaContext mediaContext = MediaContext.create();
private final ContentEncodingContext contentEncodingContext = ContentEncodingContext.create();
private final MediaContext mediaContext;
private final ContentEncodingContext contentEncodingContext;
private final LoomServer server;

private volatile boolean running;
Expand All @@ -81,7 +80,9 @@ class ServerListener {
String socketName,
ListenerConfiguration listenerConfig,
Router router,
DirectHandlers simpleHandlers) {
DirectHandlers simpleHandlers,
MediaContext mediaContext,
ContentEncodingContext contentEncodingContext) {
this.server = loomServer;
this.connectionProviders = ConnectionProviders.create(connectionProviders);
this.socketName = socketName;
Expand Down Expand Up @@ -113,6 +114,8 @@ class ServerListener {
port = 0;
}
this.configuredAddress = new InetSocketAddress(listenerConfig.address(), port);
this.mediaContext = mediaContext;
this.contentEncodingContext = contentEncodingContext;
}

int port() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

Expand All @@ -29,6 +30,8 @@
import io.helidon.config.Config;
import io.helidon.logging.common.LogConfig;
import io.helidon.nima.common.tls.Tls;
import io.helidon.nima.http.encoding.ContentEncodingContext;
import io.helidon.nima.http.media.MediaContext;
import io.helidon.nima.webserver.http.DirectHandlers;
import io.helidon.nima.webserver.http.HttpRouting;
import io.helidon.nima.webserver.spi.ServerConnectionProvider;
Expand Down Expand Up @@ -132,6 +135,9 @@ class Builder implements io.helidon.common.Builder<Builder, WebServer>, Router.R
private final HelidonServiceLoader.Builder<ServerConnectionProvider> connectionProviders =
HelidonServiceLoader.builder(ServiceLoader.load(ServerConnectionProvider.class));

private MediaContext mediaContext = MediaContext.create();
private ContentEncodingContext contentEncodingContext = ContentEncodingContext.create();

Builder(Config rootConfig) {
config(rootConfig.get("server"));
}
Expand Down Expand Up @@ -350,6 +356,38 @@ public boolean hasSocket(String socketName) {
return DEFAULT_SOCKET_NAME.equals(socketName) || socketBuilder.containsKey(socketName);
}

/**
* Configure the default {@link MediaContext}.
* This method discards all previously registered MediaContext.
* @param mediaContext media context
* @return updated instance of the builder
*/
public Builder mediaContext(MediaContext mediaContext) {
Objects.requireNonNull(mediaContext);
this.mediaContext = mediaContext;
return this;
}

/**
* Configure the default {@link ContentEncodingContext}.
* This method discards all previously registered ContentEncodingContext.
* @param contentEncodingContext content encoding context
* @return updated instance of the builder
*/
public Builder contentEncodingContext(ContentEncodingContext contentEncodingContext) {
Objects.requireNonNull(contentEncodingContext);
this.contentEncodingContext = contentEncodingContext;
return this;
}

MediaContext mediaContext() {
return this.mediaContext;
}

ContentEncodingContext contentEncodingContext() {
return this.contentEncodingContext;
}

Map<String, ListenerConfiguration.Builder> socketBuilders() {
return socketBuilder;
}
Expand Down

0 comments on commit d3c62e4

Please sign in to comment.