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

[main] Issue 5383: ContentEncodingContext Builder and passing ContentEncodingContext instance from WebServer to Http1Connection. #5921

Merged
merged 6 commits into from
Feb 2, 2023
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
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")
Tomas-Kraus marked this conversation as resolved.
Show resolved Hide resolved
.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