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

Support @LocalArmeriaPort, @LocalArmeriaPorts #2649

Closed
wants to merge 11 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ public enum SessionProtocol {
/**
* <a href="https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt">PROXY protocol</a> - v1 or v2.
*/
PROXY("proxy", false, false, 0);
PROXY("proxy", false, false, 0),
/**
* None.
*/
NONE("none", false, false, -1);

private static final Set<SessionProtocol> HTTP_VALUES = Sets.immutableEnumSet(HTTP, H1C, H2C);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright 2020 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.internal.spring;

import static com.google.common.collect.ImmutableList.toImmutableList;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;

import com.linecorp.armeria.server.Server;
import com.linecorp.armeria.spring.ArmeriaServerStartedEvent;

/**
* {@link ApplicationContextInitializer} that sets {@link Environment} properties for the
* ports that Armeria {@link Server}s are actually listening on. The properties
* {@code "local.armeria.port"}, {@code "local.armeria.ports"} can be injected directly into tests using
* {@link Value @Value} or obtained via the {@link Environment}.
* Properties are automatically propagated up to any parent context.
*/
public class ArmeriaServerPortInfoApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.addApplicationListener(new Listener(applicationContext));
}

private static class Listener implements ApplicationListener<ArmeriaServerStartedEvent> {

// The following naming conventions are referenced here.
// https://github.com/spring-projects/spring-boot/blob/2.2.x/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/context/ServerPortInfoApplicationContextInitializer.java.
private static final String LOCAL_ARMERIA_PORT = "local.armeria.port";
private static final String LOCAL_ARMERIA_PORTS = "local.armeria.ports";
trustin marked this conversation as resolved.
Show resolved Hide resolved

private final ConfigurableApplicationContext applicationContext;

Listener(ConfigurableApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}

@Override
public void onApplicationEvent(ArmeriaServerStartedEvent event) {
final Server server = event.getSource();
setPortProperty(applicationContext, server.activeLocalPort());
setPortProperty(applicationContext, server.activePorts().values().stream()
.map(p -> p.localAddress().getPort())
.collect(toImmutableList()));
}

private void setPortProperty(ApplicationContext context, int port) {
if (context instanceof ConfigurableApplicationContext) {
setPortProperty(((ConfigurableApplicationContext) context).getEnvironment(), port);
}
if (context.getParent() != null) {
setPortProperty(context.getParent(), port);
}
}

private void setPortProperty(ConfigurableEnvironment environment, int port) {
final MutablePropertySources sources = environment.getPropertySources();
PropertySource<?> source = sources.get("server.ports");
if (source == null) {
source = new MapPropertySource("server.ports", new HashMap<>());
sources.addFirst(source);
}
setPortProperty(port, source);
}

@SuppressWarnings("unchecked")
private static void setPortProperty(int port, PropertySource<?> source) {
((Map<String, Object>) source.getSource()).put(LOCAL_ARMERIA_PORT, port);
}

private void setPortProperty(ApplicationContext context, List<Integer> ports) {
if (context instanceof ConfigurableApplicationContext) {
setPortProperty(((ConfigurableApplicationContext) context).getEnvironment(), ports);
}
if (context.getParent() != null) {
setPortProperty(context.getParent(), ports);
}
}

private void setPortProperty(ConfigurableEnvironment environment, List<Integer> ports) {
final MutablePropertySources sources = environment.getPropertySources();
PropertySource<?> source = sources.get("server.ports");
if (source == null) {
source = new MapPropertySource("server.ports", new HashMap<>());
sources.addFirst(source);
}
setPortProperty(ports, source);
}

@SuppressWarnings("unchecked")
private static void setPortProperty(List<Integer> ports, PropertySource<?> source) {
((Map<String, Object>) source.getSource()).put(LOCAL_ARMERIA_PORTS, ports);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright 2020 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.internal.spring;

import java.lang.annotation.Annotation;

import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.SimpleAutowireCandidateResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;

import com.linecorp.armeria.common.SessionProtocol;
import com.linecorp.armeria.server.Server;
import com.linecorp.armeria.spring.LocalArmeriaPort;

public class LocalArmeriaPortAnnotationAutowireCandidateResolver extends SimpleAutowireCandidateResolver {

private final Class<? extends Annotation> localArmeriaPortAnnotationType = LocalArmeriaPort.class;

@Nullable
private Server server;

public void setServer(@Nullable Server server) {
this.server = server;
}

/**
* Determine whether the given dependency declares a value annotation.
* @see LocalArmeriaPort
*/
@Override
@Nullable
public Object getSuggestedValue(DependencyDescriptor descriptor) {
Object value = findValue(descriptor.getAnnotations());
if (value == null) {
final MethodParameter methodParam = descriptor.getMethodParameter();
if (methodParam != null) {
value = findValue(methodParam.getMethodAnnotations());
}
}
return value;
}

/**
* Determine a suggested value from any of the given candidate annotations.
*/
@Nullable
protected Object findValue(Annotation[] annotationsToSearch) {
if (annotationsToSearch.length > 0) { // qualifier annotations have to be local
final AnnotationAttributes attr = AnnotatedElementUtils.getMergedAnnotationAttributes(
AnnotatedElementUtils.forAnnotations(annotationsToSearch), localArmeriaPortAnnotationType);
if (attr != null) {
return extractValue(attr);
}
}
return null;
}

/**
* Extract the value attribute from the given annotation.
* @since 4.3
*/
@Nullable
protected Object extractValue(AnnotationAttributes attr) {
assert server != null;
final SessionProtocol value = (SessionProtocol) attr.get(AnnotationUtils.VALUE);
if (value == null) {
throw new IllegalStateException("LocalArmeriaPort annotation must have a value attribute");
}
if (value == SessionProtocol.NONE) {
return server.activeLocalPort();
}

try {
return server.activeLocalPort(value);
} catch (IllegalStateException e) {
// ignored
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2020 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.internal.spring;

import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.core.Ordered;

import com.linecorp.armeria.spring.LocalArmeriaPort;

/**
* TBD.
*/
public class LocalArmeriaPortAnnotationBeanPostProcessor extends AutowiredAnnotationBeanPostProcessor {

private int order = Ordered.LOWEST_PRECEDENCE - 2;

public LocalArmeriaPortAnnotationBeanPostProcessor() {
setAutowiredAnnotationType(LocalArmeriaPort.class);
}

@Override
public void setOrder(int order) {
this.order = order;
}

@Override
public int getOrder() {
return order;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

Expand Down Expand Up @@ -79,7 +80,8 @@ public Server armeriaServer(
Optional<List<GrpcServiceRegistrationBean>> grpcServiceRegistrationBeans,
Optional<List<HttpServiceRegistrationBean>> httpServiceRegistrationBeans,
Optional<List<AnnotatedServiceRegistrationBean>> annotatedServiceRegistrationBeans,
Optional<List<DocServiceConfigurator>> docServiceConfigurators)
Optional<List<DocServiceConfigurator>> docServiceConfigurators,
ApplicationEventPublisher eventPublisher)
throws InterruptedException {

if (!armeriaServerConfigurators.isPresent() &&
Expand Down Expand Up @@ -157,6 +159,8 @@ public Server armeriaServer(
}
return result;
}).join();

eventPublisher.publishEvent(new ArmeriaServerStartedEvent(server));
ikhoon marked this conversation as resolved.
Show resolved Hide resolved
logger.info("Armeria server started at ports: {}", server.activePorts());
return server;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2020 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.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;

import com.google.common.base.MoreObjects;

import com.linecorp.armeria.server.Server;

/**
* An {@link ApplicationEvent} that is published after an {@link ApplicationContext} is refreshed
* and the {@link Server} is ready. Useful for obtaining the local port of a running server.
*/
public final class ArmeriaServerStartedEvent extends ApplicationEvent {

/**
* Creates a new instance.
*/
public ArmeriaServerStartedEvent(Server server) {
super(server);
}

/**
* Returns the {@link Server}.
*/
@Override
public Server getSource() {
return (Server) super.getSource();
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("source", getSource())
.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2020 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.spring;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import com.linecorp.armeria.common.SessionProtocol;

/**
* Specifies an active local port of an Armeria server.
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LocalArmeriaPort {

/**
* TBD.
*/
SessionProtocol value() default SessionProtocol.NONE;
}
Loading