Commit 8a497e65 by Johannes Edmeier

Enable Client for reactive web applications

parent 7e32dfcc
...@@ -70,6 +70,11 @@ ...@@ -70,6 +70,11 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId> <groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-standalone</artifactId> <artifactId>wiremock-standalone</artifactId>
<version>2.5.1</version> <version>2.5.1</version>
......
...@@ -62,6 +62,19 @@ public class SpringBootAdminClientAutoConfiguration { ...@@ -62,6 +62,19 @@ public class SpringBootAdminClientAutoConfiguration {
} }
} }
@Configuration
@ConditionalOnWebApplication(type = Type.REACTIVE)
public static class ReactiveConfiguration {
@Bean
@ConditionalOnMissingBean
public ApplicationFactory applicationFactory(InstanceProperties instance,
ManagementServerProperties management,
ServerProperties server,
EndpointPathProvider endpointPathProvider) {
return new DefaultApplicationFactory(instance, management, server, endpointPathProvider);
}
}
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public ApplicationRegistrator registrator(ClientProperties client, public ApplicationRegistrator registrator(ClientProperties client,
......
...@@ -24,11 +24,10 @@ import java.util.Map; ...@@ -24,11 +24,10 @@ import java.util.Map;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider; import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.boot.web.server.Ssl; import org.springframework.boot.web.server.Ssl;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
/** /**
...@@ -77,7 +76,7 @@ public class DefaultApplicationFactory implements ApplicationFactory { ...@@ -77,7 +76,7 @@ public class DefaultApplicationFactory implements ApplicationFactory {
String baseUrl = instance.getServiceBaseUrl(); String baseUrl = instance.getServiceBaseUrl();
if (getLocalServerPort() == null && StringUtils.isEmpty(baseUrl)) { if (getLocalServerPort() == null && StringUtils.isEmpty(baseUrl)) {
throw new IllegalStateException("service-base-url must be set when deployed to servlet-container"); throw new IllegalStateException("couldn't determine local port. Please supply service-base-url.");
} }
UriComponentsBuilder builder; UriComponentsBuilder builder;
...@@ -209,14 +208,12 @@ public class DefaultApplicationFactory implements ApplicationFactory { ...@@ -209,14 +208,12 @@ public class DefaultApplicationFactory implements ApplicationFactory {
} }
@EventListener @EventListener
public void onApplicationReady(ApplicationReadyEvent event) { public void onWebServerInitialized(WebServerInitializedEvent event) {
if (event.getApplicationContext() instanceof WebApplicationContext) { if ("server".equals(event.getServerId())) {
localServerPort = event.getApplicationContext() localServerPort = event.getWebServer().getPort();
.getEnvironment() }
.getProperty("local.server.port", Integer.class); if ("management".equals(event.getServerId())) {
localManagementPort = event.getApplicationContext() localManagementPort = event.getWebServer().getPort();
.getEnvironment()
.getProperty("local.management.port", Integer.class, localServerPort);
} }
} }
} }
/* /*
* Copyright 2014 the original author or authors. * Copyright 2014-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package de.codecentric.boot.admin.client.registration; package de.codecentric.boot.admin.client.registration;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.event.ApplicationReadyEvent;
...@@ -25,7 +24,6 @@ import org.springframework.context.event.EventListener; ...@@ -25,7 +24,6 @@ import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.TaskScheduler;
import org.springframework.web.context.WebApplicationContext;
/** /**
* Listener responsible for starting and stopping the registration task when the application is * Listener responsible for starting and stopping the registration task when the application is
...@@ -34,8 +32,7 @@ import org.springframework.web.context.WebApplicationContext; ...@@ -34,8 +32,7 @@ import org.springframework.web.context.WebApplicationContext;
* @author Johannes Edmeier * @author Johannes Edmeier
*/ */
public class RegistrationApplicationListener { public class RegistrationApplicationListener {
private static final Logger LOGGER = LoggerFactory private static final Logger LOGGER = LoggerFactory.getLogger(RegistrationApplicationListener.class);
.getLogger(RegistrationApplicationListener.class);
private final ApplicationRegistrator registrator; private final ApplicationRegistrator registrator;
private final TaskScheduler taskScheduler; private final TaskScheduler taskScheduler;
private boolean autoDeregister = false; private boolean autoDeregister = false;
...@@ -43,8 +40,7 @@ public class RegistrationApplicationListener { ...@@ -43,8 +40,7 @@ public class RegistrationApplicationListener {
private long registerPeriod = 10_000L; private long registerPeriod = 10_000L;
private volatile ScheduledFuture<?> scheduledTask; private volatile ScheduledFuture<?> scheduledTask;
public RegistrationApplicationListener(ApplicationRegistrator registrator, public RegistrationApplicationListener(ApplicationRegistrator registrator, TaskScheduler taskScheduler) {
TaskScheduler taskScheduler) {
this.registrator = registrator; this.registrator = registrator;
this.taskScheduler = taskScheduler; this.taskScheduler = taskScheduler;
} }
...@@ -52,7 +48,7 @@ public class RegistrationApplicationListener { ...@@ -52,7 +48,7 @@ public class RegistrationApplicationListener {
@EventListener @EventListener
@Order(Ordered.LOWEST_PRECEDENCE) @Order(Ordered.LOWEST_PRECEDENCE)
public void onApplicationReady(ApplicationReadyEvent event) { public void onApplicationReady(ApplicationReadyEvent event) {
if (event.getApplicationContext() instanceof WebApplicationContext && autoRegister) { if (event.getApplicationContext().getParent() == null && autoRegister) {
startRegisterTask(); startRegisterTask();
} }
} }
...@@ -60,7 +56,7 @@ public class RegistrationApplicationListener { ...@@ -60,7 +56,7 @@ public class RegistrationApplicationListener {
@EventListener @EventListener
@Order(Ordered.LOWEST_PRECEDENCE) @Order(Ordered.LOWEST_PRECEDENCE)
public void onClosedContext(ContextClosedEvent event) { public void onClosedContext(ContextClosedEvent event) {
if (event.getApplicationContext() instanceof WebApplicationContext) { if (event.getApplicationContext().getParent() == null) {
stopRegisterTask(); stopRegisterTask();
if (autoDeregister) { if (autoDeregister) {
...@@ -74,12 +70,7 @@ public class RegistrationApplicationListener { ...@@ -74,12 +70,7 @@ public class RegistrationApplicationListener {
return; return;
} }
scheduledTask = taskScheduler.scheduleAtFixedRate(new Runnable() { scheduledTask = taskScheduler.scheduleAtFixedRate(registrator::register, registerPeriod);
@Override
public void run() {
registrator.register();
}
}, registerPeriod);
LOGGER.debug("Scheduled registration task for every {}ms", registerPeriod); LOGGER.debug("Scheduled registration task for every {}ms", registerPeriod);
} }
......
/*
* Copyright 2014-2017 the original author or authors.
*
* 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 de.codecentric.boot.admin.client.config;
import wiremock.org.apache.http.HttpStatus;
import org.junit.Test;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.configureFor;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;
import static com.github.tomakehurst.wiremock.client.WireMock.moreThanOrExactly;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
public abstract class AbstractClientApplicationTest {
private WireMockServer wiremock;
public void setUp() throws Exception {
wiremock = new WireMockServer(wireMockConfig().dynamicPort());
wiremock.start();
configureFor(wiremock.port());
ResponseDefinitionBuilder response = aResponse().withStatus(HttpStatus.SC_CREATED)
.withHeader("Content-Type", "application/json")
.withHeader("Location", "http://localhost:" +
wiremock.port() +
"/instances/abcdef")
.withBody("{ \"id\" : \"abcdef\" }");
stubFor(post(urlEqualTo("/instances")).willReturn(response));
}
@Test
public void test_context() throws InterruptedException {
Thread.sleep(1000L);
String serviceHost = "http://localhost:" + getServerPort();
String managementHost = "http://localhost:" + getManagementPort();
String body = "{ \"name\" : \"Test-Client\"," + //
" \"managementUrl\" : \"" + managementHost + "/mgmt\"," + //
" \"healthUrl\" : \"" + managementHost + "/mgmt/health\"," + //
" \"serviceUrl\" : \"" + serviceHost + "/\", " + //
" \"metadata\" : {} }";
RequestPatternBuilder request = postRequestedFor(urlEqualTo("/instances")).withHeader("Content-Type",
equalTo("application/json")).withRequestBody(equalToJson(body));
verify(moreThanOrExactly(1), request);
}
public void shutdown() {
wiremock.stop();
}
protected abstract int getServerPort();
protected abstract int getManagementPort();
protected int getWirmockPort() {
return wiremock.port();
}
}
/*
* Copyright 2014-2017 the original author or authors.
*
* 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 de.codecentric.boot.admin.client.config;
import org.junit.After;
import org.junit.Before;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
public class ClientReactiveApplicationTest extends AbstractClientApplicationTest {
private ConfigurableApplicationContext instance;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
instance = SpringApplication.run(TestClientApplication.class, "--spring.main.web-application-type=reactive",
"--spring.application.name=Test-Client", "--server.port=0", "--management.context-path=/mgmt",
"--endpoints.health.enabled=true",
"--spring.boot.admin.client.url=http://localhost:" + getWirmockPort());
}
@Override
@After
public void shutdown() {
instance.close();
super.shutdown();
}
@Override
protected int getServerPort() {
return instance.getEnvironment().getProperty("local.server.port", Integer.class, 0);
}
@Override
protected int getManagementPort() {
return instance.getEnvironment().getProperty("local.management.port", Integer.class, 0);
}
@SpringBootConfiguration
@EnableAutoConfiguration(exclude = WebMvcAutoConfiguration.class)
public static class TestClientApplication {
}
}
...@@ -16,73 +16,43 @@ ...@@ -16,73 +16,43 @@
package de.codecentric.boot.admin.client.config; package de.codecentric.boot.admin.client.config;
import wiremock.org.apache.http.HttpStatus;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.configureFor;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;
import static com.github.tomakehurst.wiremock.client.WireMock.moreThanOrExactly;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
public class ClientServletApplicationTest { public class ClientServletApplicationTest extends AbstractClientApplicationTest {
private ConfigurableApplicationContext instance; private ConfigurableApplicationContext instance;
private WireMockServer wiremock;
@Override
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
wiremock = new WireMockServer(wireMockConfig().dynamicPort()); super.setUp();
wiremock.start();
configureFor(wiremock.port());
ResponseDefinitionBuilder response = aResponse().withStatus(HttpStatus.SC_CREATED)
.withHeader("Content-Type", "application/json")
.withHeader("Location", "http://localhost:" +
wiremock.port() +
"/instances/abcdef")
.withBody("{ \"id\" : \"abcdef\" }");
stubFor(post(urlEqualTo("/instances")).willReturn(response));
instance = SpringApplication.run(TestClientApplication.class, "--spring.main.web-application-type=servlet", instance = SpringApplication.run(TestClientApplication.class, "--spring.main.web-application-type=servlet",
"--spring.application.name=Test-Client", "--server.port=0", "--management.context-path=/mgmt", "--spring.application.name=Test-Client", "--server.port=0", "--management.context-path=/mgmt",
"--endpoints.health.enabled=true", "--endpoints.health.enabled=true",
"--spring.boot.admin.client.url=http://localhost:" + wiremock.port()); "--spring.boot.admin.client.url=http://localhost:" + getWirmockPort());
}
@Test
public void test_context() throws InterruptedException {
Thread.sleep(1000L);
String serviceHost = "http://localhost:" + instance.getEnvironment().getProperty("local.server.port");
String managementHost = "http://localhost:" + instance.getEnvironment().getProperty("local.management.port");
String body = "{ \"name\" : \"Test-Client\"," + //
" \"managementUrl\" : \"" + managementHost + "/mgmt\"," + //
" \"healthUrl\" : \"" + managementHost + "/mgmt/health\"," + //
" \"serviceUrl\" : \"" + serviceHost + "/\", " + //
" \"metadata\" : {} }";
RequestPatternBuilder request = postRequestedFor(urlEqualTo("/instances")).withHeader("Content-Type",
equalTo("application/json")).withRequestBody(equalToJson(body));
verify(moreThanOrExactly(1), request);
} }
@Override
@After @After
public void shutdown() { public void shutdown() {
instance.close(); instance.close();
wiremock.stop(); super.shutdown();
}
@Override
protected int getServerPort() {
return instance.getEnvironment().getProperty("local.server.port", Integer.class, 0);
}
@Override
protected int getManagementPort() {
return instance.getEnvironment().getProperty("local.management.port", Integer.class, 0);
} }
@SpringBootConfiguration @SpringBootConfiguration
......
...@@ -22,14 +22,13 @@ import java.net.InetAddress; ...@@ -22,14 +22,13 @@ import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider; import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.boot.web.server.Ssl; import org.springframework.boot.web.server.Ssl;
import org.springframework.mock.env.MockEnvironment; import org.springframework.boot.web.server.WebServer;
import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.context.ApplicationContext;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
...@@ -187,16 +186,36 @@ public class DefaultApplicationFactoryTest { ...@@ -187,16 +186,36 @@ public class DefaultApplicationFactoryTest {
private void publishApplicationReadyEvent(DefaultApplicationFactory factory, private void publishApplicationReadyEvent(DefaultApplicationFactory factory,
Integer serverport, Integer serverport,
Integer managementport) { Integer managementport) {
MockEnvironment env = new MockEnvironment(); factory.onWebServerInitialized(new TestWebServerInitializedEvent("server", serverport));
if (serverport != null) { factory.onWebServerInitialized(
env.setProperty("local.server.port", serverport.toString()); new TestWebServerInitializedEvent("management", managementport != null ? managementport : serverport));
} }
if (managementport != null) {
env.setProperty("local.management.port", managementport.toString()); private static class TestWebServerInitializedEvent extends WebServerInitializedEvent {
private final String serverId;
private final WebServer server;
private TestWebServerInitializedEvent(String serverId, int port) {
super(mock(WebServer.class));
this.serverId = serverId;
this.server = mock(WebServer.class);
when(server.getPort()).thenReturn(port);
}
@Override
public ApplicationContext getApplicationContext() {
return null;
} }
ConfigurableWebApplicationContext context = mock(ConfigurableWebApplicationContext.class); @Override
when(context.getEnvironment()).thenReturn(env); public String getServerId() {
factory.onApplicationReady(new ApplicationReadyEvent(mock(SpringApplication.class), new String[]{}, context)); return this.serverId;
} }
@Override
public WebServer getWebServer() {
return this.server;
}
}
} }
...@@ -22,14 +22,13 @@ import java.net.InetAddress; ...@@ -22,14 +22,13 @@ import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider; import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.mock.env.MockEnvironment; import org.springframework.boot.web.server.WebServer;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockServletContext; import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
...@@ -111,17 +110,35 @@ public class ServletApplicationFactoryTest { ...@@ -111,17 +110,35 @@ public class ServletApplicationFactoryTest {
private void publishApplicationReadyEvent(DefaultApplicationFactory factory, private void publishApplicationReadyEvent(DefaultApplicationFactory factory,
Integer serverport, Integer serverport,
Integer managementport) { Integer managementport) {
MockEnvironment env = new MockEnvironment(); factory.onWebServerInitialized(new TestWebServerInitializedEvent("server", serverport));
if (serverport != null) { factory.onWebServerInitialized(
env.setProperty("local.server.port", serverport.toString()); new TestWebServerInitializedEvent("management", managementport != null ? managementport : serverport));
} }
if (managementport != null) {
env.setProperty("local.management.port", managementport.toString()); private static class TestWebServerInitializedEvent extends WebServerInitializedEvent {
private final String serverId;
private final WebServer server;
private TestWebServerInitializedEvent(String serverId, int port) {
super(mock(WebServer.class));
this.serverId = serverId;
this.server = mock(WebServer.class);
when(server.getPort()).thenReturn(port);
}
@Override
public ApplicationContext getApplicationContext() {
return null;
} }
ConfigurableWebApplicationContext context = mock(ConfigurableWebApplicationContext.class); @Override
when(context.getEnvironment()).thenReturn(env); public String getServerId() {
factory.onApplicationReady(new ApplicationReadyEvent(mock(SpringApplication.class), new String[]{}, context)); return this.serverId;
} }
@Override
public WebServer getWebServer() {
return this.server;
}
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment