Skip to content

Commit

Permalink
Reuse VirtualHostBuilder on the same hostnamePattern
Browse files Browse the repository at this point in the history
  • Loading branch information
yejinio committed Jan 29, 2024
1 parent 1201845 commit 906334e
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 13 deletions.
24 changes: 24 additions & 0 deletions core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -1563,6 +1563,17 @@ public ServerBuilder withVirtualHost(Consumer<? super VirtualHostBuilder> custom
* @return {@link VirtualHostBuilder} for building the virtual host
*/
public VirtualHostBuilder virtualHost(String hostnamePattern) {
requireNonNull(hostnamePattern, "hostnamePattern");

final Optional<VirtualHostBuilder> vhost =
virtualHostBuilders.stream()
.filter(v -> !v.defaultVirtualHost() &&
v.equalsHostnamePattern(hostnamePattern))
.findFirst();
if (vhost.isPresent()) {
return vhost.get();
}

final VirtualHostBuilder virtualHostBuilder =
new VirtualHostBuilder(this, false).hostnamePattern(hostnamePattern);
virtualHostBuilders.add(virtualHostBuilder);
Expand All @@ -1577,6 +1588,19 @@ public VirtualHostBuilder virtualHost(String hostnamePattern) {
* @return {@link VirtualHostBuilder} for building the virtual host
*/
public VirtualHostBuilder virtualHost(String defaultHostname, String hostnamePattern) {
requireNonNull(defaultHostname, "defaultHostname");
requireNonNull(hostnamePattern, "hostnamePattern");

final Optional<VirtualHostBuilder> vhost =
virtualHostBuilders.stream()
.filter(v -> !v.defaultVirtualHost())
.filter(v -> v.equalsDefaultHostname(defaultHostname) &&
v.equalsHostnamePattern(hostnamePattern))
.findFirst();
if (vhost.isPresent()) {
return vhost.get();
}

final VirtualHostBuilder virtualHostBuilder = new VirtualHostBuilder(this, false)
.defaultHostname(defaultHostname)
.hostnamePattern(hostnamePattern);
Expand Down
16 changes: 16 additions & 0 deletions core/src/main/java/com/linecorp/armeria/server/VirtualHost.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.linecorp.armeria.server;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.util.Objects.requireNonNull;
Expand Down Expand Up @@ -205,6 +206,21 @@ static String normalizeHostnamePattern(String hostnamePattern) {
return Ascii.toLowerCase(hostnamePattern);
}

static void validateHostnamePattern(String hostnamePattern) {
final boolean validHostnamePattern;
if (hostnamePattern.charAt(0) == '*') {
validHostnamePattern =
hostnamePattern.length() >= 3 &&
hostnamePattern.charAt(1) == '.' &&
HOSTNAME_WITH_NO_PORT_PATTERN.matcher(hostnamePattern.substring(2)).matches();
} else {
validHostnamePattern = HOSTNAME_WITH_NO_PORT_PATTERN.matcher(hostnamePattern).matches();
}

checkArgument(validHostnamePattern,
"hostnamePattern: %s (expected: *.<hostname> or <hostname>)", hostnamePattern);
}

/**
* Ensure that 'hostnamePattern' matches 'defaultHostname'.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
import static com.linecorp.armeria.server.ServerSslContextUtil.validateSslContext;
import static com.linecorp.armeria.server.ServiceConfig.validateMaxRequestLength;
import static com.linecorp.armeria.server.ServiceConfig.validateRequestTimeoutMillis;
import static com.linecorp.armeria.server.VirtualHost.HOSTNAME_WITH_NO_PORT_PATTERN;
import static com.linecorp.armeria.server.VirtualHost.ensureHostnamePatternMatchesDefaultHostname;
import static com.linecorp.armeria.server.VirtualHost.normalizeDefaultHostname;
import static com.linecorp.armeria.server.VirtualHost.normalizeHostnamePattern;
import static com.linecorp.armeria.server.VirtualHost.validateHostnamePattern;
import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.isPseudoHeader;
import static java.util.Objects.requireNonNull;

Expand Down Expand Up @@ -248,18 +248,7 @@ public VirtualHostBuilder hostnamePattern(String hostnamePattern) {
hostnamePattern = hostAndPort.getHost();
}

final boolean validHostnamePattern;
if (hostnamePattern.charAt(0) == '*') {
validHostnamePattern =
hostnamePattern.length() >= 3 &&
hostnamePattern.charAt(1) == '.' &&
HOSTNAME_WITH_NO_PORT_PATTERN.matcher(hostnamePattern.substring(2)).matches();
} else {
validHostnamePattern = HOSTNAME_WITH_NO_PORT_PATTERN.matcher(hostnamePattern).matches();
}

checkArgument(validHostnamePattern,
"hostnamePattern: %s (expected: *.<hostname> or <hostname>)", hostnamePattern);
validateHostnamePattern(hostnamePattern);

this.hostnamePattern = normalizeHostnamePattern(hostnamePattern);
return this;
Expand Down Expand Up @@ -1524,6 +1513,26 @@ private SelfSignedCertificate selfSignedCertificate() throws CertificateExceptio
return selfSignedCertificate;
}

boolean equalsDefaultHostname(String defaultHostname) {
return normalizeDefaultHostname(defaultHostname).equals(this.defaultHostname);
}

boolean equalsHostnamePattern(String hostnamePattern) {
checkArgument(!hostnamePattern.isEmpty(), "hostnamePattern is empty.");

final HostAndPort hostAndPort = HostAndPort.fromString(hostnamePattern);
if (hostAndPort.hasPort()) {
if (port != hostAndPort.getPort()) {
return false;
}
hostnamePattern = hostAndPort.getHost();
}

validateHostnamePattern(hostnamePattern);

return hostnamePattern.equals(this.hostnamePattern);
}

int port() {
return port;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* Copyright 2024 LINE Corporation
*
* LINE Corporation licenses this file to you 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:
*
* https://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 com.linecorp.armeria.server;

import static org.assertj.core.api.Assertions.assertThat;

import java.net.ServerSocket;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import com.linecorp.armeria.client.ClientFactory;
import com.linecorp.armeria.client.WebClient;
import com.linecorp.armeria.common.AggregatedHttpResponse;
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.HttpStatus;
import com.linecorp.armeria.internal.testing.MockAddressResolverGroup;
import com.linecorp.armeria.testing.junit5.server.ServerExtension;

class HostnameBasedVirtualHostTest {

private static int fooHostPort;

@RegisterExtension
static ServerExtension serverWithPortMapping = new ServerExtension() {
@Override
protected void configure(ServerBuilder sb) throws Exception {

try (ServerSocket ss = new ServerSocket(0)) {
fooHostPort = ss.getLocalPort();
}

sb.http(fooHostPort)
.virtualHost("foo.com:" + fooHostPort)
.service("/foo", (ctx, req) -> HttpResponse.of("foo with port"))
.and()
.virtualHost("foo.com:" + fooHostPort)
.service("/bar", (ctx, req) -> HttpResponse.of("bar with port"))
.and()
.virtualHost("foo.bar.com")
.service("/foo-bar", (ctx, req) -> HttpResponse.of("foo bar"))
.and()
.virtualHost("def.bar.com", "*.bar.com")
.service("/foo-all", (ctx, req) -> HttpResponse.of("foo all"))
.and()
.build();
}
};

@Test
void testHostnamePattern() {
try (ClientFactory factory = ClientFactory.builder()
.addressResolverGroupFactory(
unused -> MockAddressResolverGroup.localhost())
.build()) {

final WebClient client = WebClient.builder("http://foo.com:" + fooHostPort)
.factory(factory)
.build();
AggregatedHttpResponse response = client.get("/foo").aggregate().join();
assertThat(response.contentUtf8()).isEqualTo("foo with port");

response = client.get("/bar").aggregate().join();
assertThat(response.contentUtf8()).isEqualTo("bar with port");
}
}

@Test
void testDefaultHostname() {
try (ClientFactory factory = ClientFactory.builder()
.addressResolverGroupFactory(
unused -> MockAddressResolverGroup.localhost())
.build()) {

final WebClient fooBarClient = WebClient.builder("http://foo.bar.com:" + fooHostPort)
.factory(factory)
.build();
AggregatedHttpResponse response = fooBarClient.get("/foo-bar").aggregate().join();
assertThat(response.contentUtf8()).isEqualTo("foo bar");

response = fooBarClient.get("/foo-all").aggregate().join();
assertThat(response.status()).isEqualTo(HttpStatus.NOT_FOUND);

final WebClient barBarClient = WebClient.builder("http://bar.bar.com:" + fooHostPort)
.factory(factory)
.build();

response = barBarClient.get("/foo-all").aggregate().join();
assertThat(response.contentUtf8()).isEqualTo("foo all");

response = barBarClient.get("/foo-bar").aggregate().join();
assertThat(response.status()).isEqualTo(HttpStatus.NOT_FOUND);

final WebClient defBarClient = WebClient.builder("http://def.bar.com:" + fooHostPort)
.factory(factory)
.build();

response = defBarClient.get("/foo-all").aggregate().join();
assertThat(response.contentUtf8()).isEqualTo("foo all");

response = defBarClient.get("/foo-bar").aggregate().join();
assertThat(response.status()).isEqualTo(HttpStatus.NOT_FOUND);
}
}

@Test
void shouldReturnSameInstanceForHostnameBasedVirtualHost() {
final ServerBuilder serverBuilder = Server.builder();
final VirtualHostBuilder virtualHost1 = serverBuilder.virtualHost("foo.com");
final VirtualHostBuilder virtualHost2 = serverBuilder.virtualHost("foo.com");
assertThat(virtualHost1).isSameAs(virtualHost2);
final VirtualHostBuilder virtualHost3 = serverBuilder.virtualHost("foo.com:18080");
assertThat(virtualHost2).isNotSameAs(virtualHost3);
final VirtualHostBuilder virtualHost4 = serverBuilder.virtualHost("bar.com");
assertThat(virtualHost2).isNotSameAs(virtualHost4);
}

@Test
void shouldReturnSameInstanceForHostnameBasedVirtualHostWithPort() {
final ServerBuilder serverBuilder = Server.builder();
final VirtualHostBuilder virtualHost1 = serverBuilder.virtualHost("foo.com:18080");
final VirtualHostBuilder virtualHost2 = serverBuilder.virtualHost("foo.com:18080");
assertThat(virtualHost1).isSameAs(virtualHost2);
final VirtualHostBuilder virtualHost3 = serverBuilder.virtualHost("foo.com:18081");
assertThat(virtualHost2).isNotSameAs(virtualHost3);
}

@Test
void shouldReturnSameInstanceForDefaultHostnameAndHostnameBasedVirtualHost() {
final ServerBuilder serverBuilder = Server.builder();
final VirtualHostBuilder virtualHost1 = serverBuilder.virtualHost("def.foo.com", "*.foo.com");
final VirtualHostBuilder virtualHost2 = serverBuilder.virtualHost("def.foo.com", "*.foo.com");
assertThat(virtualHost1).isSameAs(virtualHost2);
final VirtualHostBuilder virtualHost3 = serverBuilder.virtualHost("def2.foo.com", "*.foo.com");
assertThat(virtualHost2).isNotSameAs(virtualHost3);
}
}

0 comments on commit 906334e

Please sign in to comment.