Skip to content

Commit

Permalink
[main] Issue 5383: ContentEncodingContext Builder and passing Content…
Browse files Browse the repository at this point in the history
…EncodingContext instance from WebServer to Http1Connection. (#5921)

* Issue 5383: ContentEncodingContext Builder and passing ContentEncodingContext instance from WebServer to Http1Connection.
* junit test to check ContentEncodingContext initialization from server config
* Added discover-services to both connection-providers and content-encoding nodes. Added jUnits to verify config options.

Signed-off-by: Tomáš Kraus <tomas.kraus@oracle.com>
  • Loading branch information
Tomas-Kraus authored Feb 2, 2023
1 parent 0b7b1a3 commit c97e77c
Show file tree
Hide file tree
Showing 13 changed files with 311 additions and 318 deletions.
6 changes: 5 additions & 1 deletion nima/http/encoding/encoding/pom.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2022 Oracle and/or its affiliates.
Copyright (c) 2022, 2023 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 Down Expand Up @@ -28,6 +28,10 @@
<name>Helidon Níma HTTP Encoding</name>

<dependencies>
<dependency>
<groupId>io.helidon.common</groupId>
<artifactId>helidon-common-config</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.common</groupId>
<artifactId>helidon-common-http</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates.
* Copyright (c) 2022, 2023 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 @@ -16,9 +16,17 @@

package io.helidon.nima.http.encoding;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.ServiceLoader;
import java.util.Set;

import io.helidon.common.HelidonServiceLoader;
import io.helidon.common.config.Config;
import io.helidon.common.http.Headers;
import io.helidon.nima.http.encoding.spi.ContentEncodingProvider;

/**
* Content encoding support to obtain encoders and decoders.
Expand All @@ -30,7 +38,17 @@ public interface ContentEncodingContext {
* @return content encoding support
*/
static ContentEncodingContext create() {
return new ContentEncodingSupportImpl();
return builder().build();
}

/**
* Create a new encoding support and apply provided configuration.
*
* @param config configuration to use
* @return content encoding support
*/
static ContentEncodingContext create(Config config) {
return builder().config(config).build();
}

/**
Expand Down Expand Up @@ -88,4 +106,97 @@ static ContentEncodingContext create() {
* @return content encoder to use
*/
ContentEncoder encoder(Headers headers);

/**
* Builder to set up this encoding support content.
*
* @return a new builder
*/
static Builder builder() {
return new Builder();
}

/**
* Fluent API builder for {@link ContentEncodingContext}.
*/
class Builder implements io.helidon.common.Builder<Builder, ContentEncodingContext> {

private static final String IDENTITY_ENCODING = "identity";

private final HelidonServiceLoader.Builder<ContentEncodingProvider> encodingProviders
= HelidonServiceLoader.builder(ServiceLoader.load(ContentEncodingProvider.class));

/**
* Update this builder from configuration.
* <p>
* Configuration:<ul>
* <li><b>disable: true</b> - to disable content encoding support</li>
* </ul>
*
* @param config configuration to use
* @return updated builder instance
*/
public Builder config(Config config) {
config.get("discover-services").asBoolean().ifPresent(this::discoverServices);
return this;
}

/**
* Whether Java Service Loader should be used to load {@link ContentEncodingProvider}.
*
* @return updated builder
*/
public Builder discoverServices(boolean discoverServices) {
this.encodingProviders.useSystemServiceLoader(discoverServices);
return this;
}

/**
* Configure content encoding provider.
* This instance has priority over provider(s) discovered by service loader.
*
* @param encodingProvider explicit content encoding provider
* @return updated builder
*/
public Builder addEncodingProvider(ContentEncodingProvider encodingProvider) {
encodingProviders.addService(encodingProvider);
return this;
}

@Override
public ContentEncodingContext build() {
List<ContentEncodingProvider> providers = encodingProviders.build().asList();
Map<String, ContentEncoder> encoders = new HashMap<>();
Map<String, ContentDecoder> decoders = new HashMap<>();
ContentEncoder firstEncoder = null;

for (ContentEncodingProvider provider : providers) {
Set<String> ids = provider.ids();

if (provider.supportsEncoding()) {
for (String id : ids) {
ContentEncoder encoder = provider.encoder();
if (firstEncoder == null) {
firstEncoder = encoder;
}
encoders.putIfAbsent(id, encoder);
}
}

if (provider.supportsDecoding()) {
for (String id : ids) {
decoders.putIfAbsent(id, provider.decoder());
}
}

}

encoders.put(IDENTITY_ENCODING, ContentEncoder.NO_OP);
decoders.put(IDENTITY_ENCODING, ContentDecoder.NO_OP);

return new ContentEncodingSupportImpl(Map.copyOf(encoders), Map.copyOf(decoders), firstEncoder);
}

}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates.
* Copyright (c) 2022, 2023 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 @@ -18,89 +18,55 @@

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;

import io.helidon.common.HelidonServiceLoader;
import io.helidon.common.http.Headers;
import io.helidon.common.http.Http;
import io.helidon.nima.http.encoding.spi.ContentEncodingProvider;

class ContentEncodingSupportImpl implements ContentEncodingContext {
private static final String IDENTITY_ENCODING = "identity";

// todo now all static, should be in builder + instance, so we can configure per server
private static final boolean ENCODING_ENABLED;
private static final boolean DECODING_ENABLED;

private static final Map<String, ContentEncoder> ENCODERS;
private static final Map<String, ContentDecoder> DECODERS;
private static final ContentEncoder FIRST_ENCODER;

static {
List<ContentEncodingProvider> providers =
HelidonServiceLoader.create(ServiceLoader.load(ContentEncodingProvider.class))
.asList();
Map<String, ContentEncoder> encoders = new HashMap<>();
Map<String, ContentDecoder> decoders = new HashMap<>();
ContentEncoder firstEncoder = null;
for (ContentEncodingProvider provider : providers) {
Set<String> ids = provider.ids();
if (provider.supportsDecoding()) {
for (String id : ids) {
decoders.putIfAbsent(id, provider.decoder());
}
}
if (provider.supportsEncoding()) {
for (String id : ids) {
ContentEncoder encoder = provider.encoder();
if (firstEncoder == null) {
firstEncoder = encoder;
}
encoders.putIfAbsent(id, encoder);
}
}
}
encoders.put(IDENTITY_ENCODING, ContentEncoder.NO_OP);
decoders.put(IDENTITY_ENCODING, ContentDecoder.NO_OP);

FIRST_ENCODER = firstEncoder;

ENCODING_ENABLED = !encoders.isEmpty();
DECODING_ENABLED = !decoders.isEmpty();

ENCODERS = Map.copyOf(encoders);
DECODERS = Map.copyOf(decoders);
private final boolean encodingEnabled;
private final boolean decodingEnabled;
private final Map<String, ContentEncoder> encoders;
private final Map<String, ContentDecoder> decoders;
private final ContentEncoder firstEncoder;

ContentEncodingSupportImpl(Map<String, ContentEncoder> encoders,
Map<String, ContentDecoder> decoders,
ContentEncoder firstEncoder) {
this.encoders = encoders;
this.decoders = decoders;
this.encodingEnabled = !encoders.isEmpty();
this.decodingEnabled = !decoders.isEmpty();
this.firstEncoder = firstEncoder;
}

@Override
public boolean contentEncodingEnabled() {
return ENCODING_ENABLED;
return encodingEnabled;
}

@Override
public boolean contentDecodingEnabled() {
return DECODING_ENABLED;
return decodingEnabled;
}

@Override
public boolean contentEncodingSupported(String encodingId) {
return ENCODERS.get(encodingId) != null;
return encoders.get(encodingId) != null;
}

@Override
public boolean contentDecodingSupported(String encodingId) {
return DECODERS.get(encodingId) != null;
return decoders.get(encodingId) != null;
}

@Override
public ContentEncoder encoder(String encodingId) throws NoSuchElementException {
ContentEncoder encoder = ENCODERS.get(encodingId);
ContentEncoder encoder = encoders.get(encodingId);
if (encoder == null) {
throw new NoSuchElementException("Encoding for " + encodingId + " not available");
}
Expand All @@ -109,7 +75,7 @@ public ContentEncoder encoder(String encodingId) throws NoSuchElementException {

@Override
public ContentDecoder decoder(String encodingId) throws NoSuchElementException {
ContentDecoder decoder = DECODERS.get(encodingId);
ContentDecoder decoder = decoders.get(encodingId);
if (decoder == null) {
throw new NoSuchElementException("Decoding for " + encodingId + " not available");
}
Expand Down Expand Up @@ -137,10 +103,10 @@ public ContentEncoder encoder(Headers headers) {
Collections.sort(supported);
for (EncodingWithQ encodingWithQ : supported) {
if ("*".equals(encodingWithQ.encoding)) {
return FIRST_ENCODER;
return firstEncoder;
}
if (contentEncodingSupported(encodingWithQ.encoding)) {
return ENCODERS.get(encodingWithQ.encoding);
return encoders.get(encodingWithQ.encoding);
}
}

Expand Down
3 changes: 2 additions & 1 deletion nima/http/encoding/encoding/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates.
* Copyright (c) 2022, 2023 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 Down Expand Up @@ -30,6 +30,7 @@
requires static io.helidon.common.features.api;

requires io.helidon.common;
requires transitive io.helidon.common.config;
requires transitive io.helidon.common.http;

exports io.helidon.nima.http.encoding;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,16 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.ExecutorService;

import io.helidon.common.buffers.DataReader;
import io.helidon.common.buffers.DataWriter;
import io.helidon.common.socket.PeerInfo;
import org.junit.jupiter.api.Test;

import io.helidon.config.Config;
import io.helidon.nima.http2.Http2Setting;
import io.helidon.nima.webserver.ConnectionContext;
import io.helidon.nima.webserver.Router;
import io.helidon.nima.webserver.Routing;
import io.helidon.nima.webserver.spi.ServerConnectionSelector;
import io.helidon.nima.webserver.ServerContext;
import io.helidon.nima.webserver.WebServer;
import io.helidon.nima.webserver.http.DirectHandlers;

import org.junit.jupiter.api.Test;
import io.helidon.nima.webserver.spi.ServerConnectionSelector;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
Expand Down Expand Up @@ -106,6 +100,8 @@ void testProviderConfigBuilder() {
private static ConnectionContext mockContext() {
ConnectionContext ctx = mock(ConnectionContext.class);
when(ctx.router()).thenReturn(Router.empty());
when(ctx.serverContext()).thenReturn(mock(ServerContext.class));
return ctx;
}

}
6 changes: 6 additions & 0 deletions nima/webserver/webserver/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@
<artifactId>helidon-config-yaml</artifactId>
<scope>test</scope>
</dependency>
<!-- Put gzip content encoder on test classpath to see whether it's loaded or not. -->
<dependency>
<artifactId>helidon-nima-http-encoding-gzip</artifactId>
<groupId>io.helidon.nima.http.encoding</groupId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,12 @@ public Builder config(Config config) {
connConfig.get("tcp-no-delay").asBoolean().ifPresent(socketOptionsBuilder::tcpNoDelay);
});
});
// Configure content encoding
config.get("content-encoding")
.as(ContentEncodingContext::create)
.ifPresent(this::contentEncodingContext);
// Store providers config node for later usage.
providersConfig = config.get("connection-providers");

return this;
}

Expand Down Expand Up @@ -503,6 +506,7 @@ Map<String, Router> routers() {
}

List<ServerConnectionSelector> connectionProviders() {
providersConfig.get("discover-services").asBoolean().ifPresent(connectionProviders::useSystemServiceLoader);
List<ServerConnectionProvider> providers = connectionProviders.build().asList();
// Send configuration nodes to providers
return providers.stream()
Expand Down
Loading

0 comments on commit c97e77c

Please sign in to comment.