Tomcat with Netty inside single application context - spring

I have a spring application using tomcat, I would like to run netty as a parallel web server and share the application context between the two, I connected the web flux starter together with the web, but only tomcat starts on the port specified in the properties.`#Configuration
class NettyConfiguration {
#Bean
fun nettyReactiveWebServerFactory(): NettyReactiveWebServerFactory {
val webServerFactory = NettyReactiveWebServerFactory()
webServerFactory.addServerCustomizers(EventLoopNettyCustomizer())
return webServerFactory
}
private class EventLoopNettyCustomizer : NettyServerCustomizer {
override fun apply(httpServer: HttpServer): HttpServer {
val parentGroup: EventLoopGroup = NioEventLoopGroup()
val childGroup: EventLoopGroup = NioEventLoopGroup()
return httpServer.port(8084)
.tcpConfiguration { tcpServer ->
tcpServer
.bootstrap { serverBootstrap ->
serverBootstrap
.group(parentGroup, childGroup)
.channel(NioServerSocketChannel::class.java)
}
}
}
}
}`

Try this one:
#Configuration
public class CustomWebServerConfig {
private final HttpHandler httpHandler;
public CustomWebServerConfig(HttpHandler httpHandler) {
this.httpHandler = httpHandler;
}
#PostConstruct
public void customPortNettyWebServer() {
var factory = new NettyReactiveWebServerFactory(8082);
var webServer = factory.getWebServer(httpHandler);
webServer.start();
}
}

Related

Spring Integration: how to unit test a poller advice

I'm trying to unit test an advice on the poller which blocks execution of the mongo channel adapter until a certain condition is met (=all messages from this batch are processed).
The flow looks as follow:
IntegrationFlows.from(MongoDb.reactiveInboundChannelAdapter(mongoDbFactory,
new Query().with(Sort.by(Sort.Direction.DESC, "modifiedDate")).limit(1))
.collectionName("metadata")
.entityClass(Metadata.class)
.expectSingleResult(true),
e -> e.poller(Pollers.fixedDelay(Duration.ofSeconds(pollingIntervalSeconds))
.advice(this.advices.waitUntilCompletedAdvice())))
.handle((p, h) -> {
this.advices.waitUntilCompletedAdvice().setWait(true);
return p;
})
.handle(doSomething())
.channel(Channels.DOCUMENT_HEADER.name())
.get();
And the following advice bean:
#Bean
public WaitUntilCompletedAdvice waitUntilCompletedAdvice() {
DynamicPeriodicTrigger trigger = new DynamicPeriodicTrigger(Duration.ofSeconds(1));
return new WaitUntilCompletedAdvice(trigger);
}
And the advice itself:
public class WaitUntilCompletedAdvice extends SimpleActiveIdleMessageSourceAdvice {
AtomicBoolean wait = new AtomicBoolean(false);
public WaitUntilCompletedAdvice(DynamicPeriodicTrigger trigger) {
super(trigger);
}
#Override
public boolean beforeReceive(MessageSource<?> source) {
if (getWait())
return false;
return true;
}
public boolean getWait() {
return wait.get();
}
public void setWait(boolean newWait) {
if (getWait() == newWait)
return;
while (true) {
if (wait.compareAndSet(!newWait, newWait)) {
return;
}
}
}
}
I'm using the following test for testing the flow:
#Test
public void testClaimPoollingAdapterFlow() throws Exception {
// given
ArgumentCaptor<Message<?>> captor = messageArgumentCaptor();
CountDownLatch receiveLatch = new CountDownLatch(1);
MessageHandler mockMessageHandler = mockMessageHandler(captor).handleNext(m -> receiveLatch.countDown());
this.mockIntegrationContext.substituteMessageHandlerFor("retrieveDocumentHeader", mockMessageHandler);
LocalDateTime modifiedDate = LocalDateTime.now();
ProcessingMetadata data = Metadata.builder()
.modifiedDate(modifiedDate)
.build();
assert !this.advices.waitUntilCompletedAdvice().getWait();
// when
itf.getInputChannel().send(new GenericMessage<>(Mono.just(data)));
// then
assertThat(receiveLatch.await(10, TimeUnit.SECONDS)).isTrue();
verify(mockMessageHandler).handleMessage(any());
assertThat(captor.getValue().getPayload()).isEqualTo(modifiedDate);
assert this.advices.waitUntilCompletedAdvice().getWait();
}
Which works fine but when I send another message to the input channel, it still processes the message without respecting the advice.
Is it intended behaviour? If so, how can I verify using unit test that the poller is really waiting for this advice?
itf.getInputChannel().send(new GenericMessage<>(Mono.just(data)));
That bypasses the poller and sends the message directly.
You can unit test the advice has been configured by calling beforeReceive() from your test
Or you can create a dummy test flow with the same advice
IntegationFlows.from(() -> "foo", e -> e.poller(...))
...
And verify that just one message is sent.
Example implementation:
#Test
public void testWaitingActivate() {
// given
this.advices.waitUntilCompletedAdvice().setWait(true);
// when
Message<ProcessingMetadata> receive = (Message<ProcessingMetadata>) testChannel.receive(3000);
// then
assertThat(receive).isNull();
}
#Test
public void testWaitingInactive() {
// given
this.advices.waitUntilCompletedAdvice().setWait(false);
// when
Message<ProcessingMetadata> receive = (Message<ProcessingMetadata>) testChannel.receive(3000);
// then
assertThat(receive).isNotNull();
}
#Configuration
#EnableIntegration
public static class Config {
#Autowired
private Advices advices;
#Bean
public PollableChannel testChannel() {
return new QueueChannel();
}
#Bean
public IntegrationFlow fakeFlow() {
this.advices.waitUntilCompletedAdvice().setWait(true);
return IntegrationFlows.from(() -> "foo", e -> e.poller(Pollers.fixedDelay(Duration.ofSeconds(1))
.advice(this.advices.waitUntilCompletedAdvice()))).channel("testChannel").get();
}
}

How can I use the CXF HttpConduitFeature for DOSGi?

Has anyone succesfully used the CXF HttpConduitFeature for DOSGi ?
Looking at the CXF code for HttpConduitFeature.java
public class HttpConduitFeature extends DelegatingFeature<HttpConduitFeature.Portable> {
public HttpConduitFeature() {
super(new Portable());
}
public void setConduitConfig(HttpConduitConfig conduitConfig) {
delegate.setConduitConfig(conduitConfig);
}
public static class Portable implements AbstractPortableFeature {
private HttpConduitConfig conduitConfig;
#Override
public void initialize(Client client, Bus bus) {
Conduit conduit = client.getConduit();
if (conduitConfig != null && conduit instanceof HTTPConduit) {
conduitConfig.apply((HTTPConduit)conduit);
}
}
public void setConduitConfig(HttpConduitConfig conduitConfig) {
this.conduitConfig = conduitConfig;
}
}
}
And this method from the class JAXRSClientFactoryBean.java
protected void applyFeatures(AbstractClient client) {
if (getFeatures() != null) {
getFeatures().forEach(feature -> {
feature.initialize(client.getConfiguration(), getBus());
});
}
}
Which is what happens from the RsProvider-class in CXF-DOSGi, I don't understand how the initialize() from the HttpConduitFeature.Portable class will ever get called..
I tried to create my own implementation, a copy from HttpConduitFeature, but with an override of the method initialize(final InterceptorProvider interceptorProvider, final Bus bus), but then I have nothing to add the conduitConfig to. I don't see how I can make progress here.
Anyone has a better idea to add a Basic Authentication AuthorizationPolicy to my DOSGi client ? This was my attempt :
public class BasicAuthorizationIntent implements IntentsProvider {
#Override
public List<?> getIntents() {
HttpConduitConfig conduitConfig = new HttpConduitConfig();
conduitConfig.setAuthorizationPolicy(basicAuthorization());
HttpConduitFeature conduitFeature = new HttpConduitFeature();
conduitFeature.setConduitConfig(conduitConfig);
return Arrays.asList((Object) conduitFeature);
}
private AuthorizationPolicy basicAuthorization() {
AuthorizationPolicy authorizationPolicy = new AuthorizationPolicy();
authorizationPolicy.setUserName("dosgi");
authorizationPolicy.setPassword("dosgi");
authorizationPolicy.setAuthorizationType("Basic");
return authorizationPolicy;
}
}

Signalr not working with Autofac and WebAPI + MVC + OWIN

We are trying to start our Signalr Hub with Dependency Injection using Autofac but with no result yet.
MVC and Web Apis are working fine with DI.
Here the config files.
Startup.cs
[assembly: OwinStartupAttribute(typeof(PNAME.WebUI.Startup))]
namespace PNAME.WebUI
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
// Register Routes
WebApiConfig.Register(config);
// Register dependencies
//ConfigureDependencies(app, config);
var container = DependencyConfiguration.Configure(app);
SignalRConfiguration.Configure(app, container);
MvcConfiguration.Configure(app, container);
WebApiConfiguration.Configure(app, container, config);
// Configure Authentication middleware
ConfigureAuth(app, config);
// configure Web API
app.UseWebApi(config);
}
}
}
DependencyConfiguration.cs
public static class DependencyConfiguration
{
public static IContainer Configure(IAppBuilder app)
{
var builder = new ContainerBuilder();
//Register any other components required by your code....
builder.RegisterType<MainContext>().As<IApplicationDbContext>();
builder.RegisterType<OneSignalNotificationService>().As<IPushNotificationService>();
builder.RegisterType<LogService>().As<ILogger>();
//Register SignalR Hub
builder.RegisterHubs(Assembly.GetExecutingAssembly());
//builder.RegisterType<ChatHub>().ExternallyOwned();
//Register MVC Controllers
builder.RegisterControllers(Assembly.GetExecutingAssembly());
//Register WebApi Controllers
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterAssemblyModules(Assembly.GetExecutingAssembly());
var container = builder.Build();
app.UseAutofacMiddleware(container);
return container;
}
}
WebApiConfiguration.cs
public class WebApiConfiguration
{
public static void Configure(IAppBuilder app, IContainer container, HttpConfiguration config)
{
// Set the dependency resolver for Web API.
var webApiResolver = new AutofacWebApiDependencyResolver(container);
config.DependencyResolver = webApiResolver;
//Configure Action Injection
//config.InjectInterfacesIntoActions();
app.UseAutofacWebApi(config);
}
}
SignalRConfiguration.cs
public static class SignalRConfiguration
{
public static void Configure(IAppBuilder app, IContainer container)
{
//var config = new HubConfiguration();
//config.Resolver = new AutofacDependencyResolver(container);
//config.EnableDetailedErrors = true;
//app.MapSignalR("/signalr", config);
app.Map("/signalr", map =>
{
map.UseAutofacMiddleware(container);
var hubConfiguration = new HubConfiguration
{
Resolver = new AutofacDependencyResolver(container),
EnableDetailedErrors = true
};
map.RunSignalR(hubConfiguration);
});
}
}
MvcConfiguration.cs
public class MvcConfiguration
{
public static void Configure(IAppBuilder app, IContainer container)
{
var mvcResolver = new Autofac.Integration.Mvc.AutofacDependencyResolver(container);
DependencyResolver.SetResolver(mvcResolver);
app.UseAutofacMvc();
}
}
As for the ChatHub its located in another project as class library with different namespace as below
ChatHub.cs
namespace PNAME.BotServices.NotificationCenter.SignalR
{
[HubName("ChatHub")]
public class ChatHub : Hub
{
private ILogger _logger;
//private IChatMessageServices _chatMessageServices;
private readonly ILifetimeScope _hubLifetimeScope;
public ChatHub(ILifetimeScope lifetimeScope)
{
// Create a lifetime scope for the hub.
_hubLifetimeScope = lifetimeScope.BeginLifetimeScope("AutofacWebRequest");
// Resolve dependencies from the hub lifetime scope.
_logger = _hubLifetimeScope.Resolve<ILogger>();
// _chatMessageServices = _hubLifetimeScope.Resolve<IChatMessageServices>();
}
public bool SendMessage(INotificationMessage notificationMessage)
{
//Some code here
}
public override Task OnConnected()
{
//Some code here
}
public override Task OnReconnected()
{
//Some code here
}
public async Task UpdateStatus(int messageId, int ServerId, StatusType status)
{
//Some code here
}
protected override void Dispose(bool disposing)
{
// Dispose the hub lifetime scope when the hub is disposed.
if (disposing && _hubLifetimeScope != null)
{
_hubLifetimeScope.Dispose();
}
base.Dispose(disposing);
}
}
}
Thats all the configuration.
As I said in the MVC and API controllers are working well with DI only signalr not connecting from client and failing with the below error.
Microsoft.AspNet.SignalR.Client.HttpClientException: StatusCode: 500, ReasonPhrase: 'Internal Server Error', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Transfer-Encoding: chunked
X-SourceFiles: =?UTF-8?B?RDpcUHJvamVjdHNcQk9UUmVwb1xCT1Rcc2lnbmFsclxuZWdvdGlhdGU=?=
Cache-Control: private
Date: Sat, 28 Dec 2019 20:43:45 GMT
Server: Microsoft-IIS/10.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Content-Type: text/html; charset=utf-8
}
at Microsoft.AspNet.SignalR.Client.Http.DefaultHttpClient.<>c__DisplayClass5_0.<Get>b__1(HttpResponseMessage responseMessage)
at Microsoft.AspNet.SignalR.TaskAsyncHelper.<>c__DisplayClass31_0`2.<Then>b__0(Task`1 t)
at Microsoft.AspNet.SignalR.TaskAsyncHelper.TaskRunners`2.<>c__DisplayClass3_0.<RunTask>b__0(Task`1 t)
don't know what did i miss in this configuration.
The issue was with the Hub name after changing the Hub name to "MyChatHub" every thing worked just fine with dependency injection

How to start multiple boot apps for end-to-end tests?

I'd like to write end-to-end tests to validate two boot apps work well together with various profiles.
What already works:
create a third maven module (e2e) for end-to-end tests, in addition to the two tested apps (authorization-server and resource-server)
write tests using TestResTemplate
Test work fine if I start authorization-server and resource-server manually.
What I now want to do is automate the tested boot apps startup and shutdown with the right profiles for each test.
I tried:
adding maven dependencies to tested apps in e2e module
using SpringApplication in new threads for each app to start
But I face miss-configuration issues as all resources and dependencies end in the same shared classpath...
Is there a way to sort this out?
I'm also considering starting two separate java -jar ... processes, but then, how to ensure tested apps fat-jars are built before 2e2 unit-tests run?
Current app start/shutdown code sample which fails as soon as I had maven dependency to second app to start:
private Service startAuthorizationServer(boolean isJwtActive) throws InterruptedException {
return new Service(
AuthorizationServer.class,
isJwtActive ? new String[]{ "jwt" } : new String[]{} );
}
private static final class Service {
private ConfigurableApplicationContext context;
private final Thread thread;
public Service(Class<?> appClass, String... profiles) throws InterruptedException {
thread = new Thread(() -> {
SpringApplication app = new SpringApplicationBuilder(appClass).profiles(profiles).build();
context = app.run();
});
thread.setDaemon(false);
thread.start();
while (context == null || !context.isRunning()) {
Thread.sleep(1000);
};
}
#PreDestroy
public void stop() {
if (context != null) {
SpringApplication.exit(context);
}
if (thread != null) {
thread.interrupt();
}
}
}
I think your case, running the two applications via a docker compose can be a good idea.
This article shows how you can set up some integration tests using a docker compose image: https://blog.codecentric.de/en/2017/03/writing-integration-tests-docker-compose-junit/
Also, take a look at this post from Martin Fowler: https://martinfowler.com/articles/microservice-testing/
I got things working with second solution:
end-to-end tests projects has no other maven dependency than what is required to run spring-tests with TestRestClient
test config initialises environment, running mvn packageon required modules in separate processes
test cases run (re)start apps with chosen profiles in separate java -jar ... processes
Here is the helper class I wrote for this (taken from there):
class ActuatorApp {
private final int port;
private final String actuatorEndpoint;
private final File jarFile;
private final TestRestTemplate actuatorClient;
private Process process;
private ActuatorApp(File jarFile, int port, TestRestTemplate actuatorClient) {
this.port = port;
this.actuatorEndpoint = getBaseUri() + "actuator/";
this.actuatorClient = actuatorClient;
this.jarFile = jarFile;
Assert.isTrue(jarFile.exists(), jarFile.getAbsolutePath() + " does not exist");
}
public void start(List<String> profiles, List<String> additionalArgs) throws InterruptedException, IOException {
if (isUp()) {
stop();
}
this.process = Runtime.getRuntime().exec(appStartCmd(jarFile, profiles, additionalArgs));
Executors.newSingleThreadExecutor().submit(new ProcessStdOutPrinter(process));
for (int i = 0; i < 10 && !isUp(); ++i) {
Thread.sleep(5000);
}
}
public void start(String... profiles) throws InterruptedException, IOException {
this.start(Arrays.asList(profiles), List.of());
}
public void stop() throws InterruptedException {
if (isUp()) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
headers.setAccept(List.of(MediaType.APPLICATION_JSON_UTF8));
actuatorClient.postForEntity(actuatorEndpoint + "shutdown", new HttpEntity<>(headers), Object.class);
Thread.sleep(5000);
}
if (process != null) {
process.destroy();
}
}
private String[] appStartCmd(File jarFile, List<String> profiles, List<String> additionalArgs) {
final List<String> cmd = new ArrayList<>(
List.of(
"java",
"-jar",
jarFile.getAbsolutePath(),
"--server.port=" + port,
"--management.endpoint.heath.enabled=true",
"--management.endpoint.shutdown.enabled=true",
"--management.endpoints.web.exposure.include=*",
"--management.endpoints.web.base-path=/actuator"));
if (profiles.size() > 0) {
cmd.add("--spring.profiles.active=" + profiles.stream().collect(Collectors.joining(",")));
}
if (additionalArgs != null) {
cmd.addAll(additionalArgs);
}
return cmd.toArray(new String[0]);
}
private boolean isUp() {
try {
final ResponseEntity<HealthResponse> response =
actuatorClient.getForEntity(actuatorEndpoint + "health", HealthResponse.class);
return response.getStatusCode().is2xxSuccessful() && response.getBody().getStatus().equals("UP");
} catch (ResourceAccessException e) {
return false;
}
}
public static Builder builder(String moduleName, String moduleVersion) {
return new Builder(moduleName, moduleVersion);
}
/**
* Configure and build a spring-boot app
*
* #author Ch4mp
*
*/
public static class Builder {
private String moduleParentDirectory = "..";
private final String moduleName;
private final String moduleVersion;
private int port = SocketUtils.findAvailableTcpPort(8080);
private String actuatorClientId = "actuator";
private String actuatorClientSecret = "secret";
public Builder(String moduleName, String moduleVersion) {
this.moduleName = moduleName;
this.moduleVersion = moduleVersion;
}
public Builder moduleParentDirectory(String moduleParentDirectory) {
this.moduleParentDirectory = moduleParentDirectory;
return this;
}
public Builder port(int port) {
this.port = port;
return this;
}
public Builder actuatorClientId(String actuatorClientId) {
this.actuatorClientId = actuatorClientId;
return this;
}
public Builder actuatorClientSecret(String actuatorClientSecret) {
this.actuatorClientSecret = actuatorClientSecret;
return this;
}
/**
* Ensures the app module is found and packaged
* #return app ready to be started
* #throws IOException if module packaging throws one
* #throws InterruptedException if module packaging throws one
*/
public ActuatorApp build() throws IOException, InterruptedException {
final File moduleDir = new File(moduleParentDirectory, moduleName);
packageModule(moduleDir);
final File jarFile = new File(new File(moduleDir, "target"), moduleName + "-" + moduleVersion + ".jar");
return new ActuatorApp(jarFile, port, new TestRestTemplate(actuatorClientId, actuatorClientSecret));
}
private void packageModule(File moduleDir) throws IOException, InterruptedException {
Assert.isTrue(moduleDir.exists(), "could not find module. " + moduleDir + " does not exist.");
String[] cmd = new File(moduleDir, "pom.xml").exists() ?
new String[] { "mvn", "-DskipTests=true", "package" } :
new String[] { "./gradlew", "bootJar" };
Process mvnProcess = new ProcessBuilder().directory(moduleDir).command(cmd).start();
Executors.newSingleThreadExecutor().submit(new ProcessStdOutPrinter(mvnProcess));
Assert.isTrue(mvnProcess.waitFor() == 0, "module packaging exited with error status.");
}
}
private static class ProcessStdOutPrinter implements Runnable {
private InputStream inputStream;
public ProcessStdOutPrinter(Process process) {
this.inputStream = process.getInputStream();
}
#Override
public void run() {
new BufferedReader(new InputStreamReader(inputStream)).lines().forEach(System.out::println);
}
}
public String getBaseUri() {
return "https://localhost:" + port;
}
}

Vertx http post client runs forever

I have the following Vertx Route setup:
router.post("/api/apple/")
.handler(e -> {
e.response()
.putHeader("content-type", "application/json")
.setStatusCode(200)
.end("hello");
})
.failureHandler(ctx -> {
LOG.error("Error: "+ ctx.response().getStatusMessage());
ctx.response().end();
});
vertx.createHttpServer().requestHandler(router::accept)
.listen(config().getInteger("http.port", 8081), result -> {
if (result.succeeded()) {
LOG.info("result succeeded in my start method");
future.complete();
} else {
LOG.error("result failed");
future.fail(result.cause());
}
});
When I call this from my Java test client:
Async async = context.async();
io.vertx.core.http.HttpClient client = vertx.createHttpClient();
HttpClientRequest request = client.post(8081, "localhost", "/api/apple/", response -> {
async.complete();
LOG.info("Some callback {}",response.statusCode());
});
String body = "{'username':'www','password':'www'}";
request.putHeader("content-length", "1000");
request.putHeader("content-type", "application/x-www-form-urlencoded");
request.write(body);
request.end();
The client keeps running and then the client times out. Seems like it is not able to find the endpoint on localhost:8081/api/apple
You didn't deploy your verticle defining routes in the test scope. Here is a working snippet:
public class HttpServerVerticleTest extends VertxTestRunner {
private WebClient webClient;
private HttpServerVerticle httpServer;
private int port;
#Before
public void setUp(TestContext context) throws IOException {
port = 8081;
httpServer = new HttpServerVerticle(); // the verticle where your routes are registered
// NOTICE HERE
vertx.deployVerticle(httpServer, yourdeploymentOptions, context.asyncAssertSuccess());
webClient = WebClient.wrap(vertx.createHttpClient());
}
#After
public void tearDown(TestContext testContext) {
webClient.close();
vertx.close(testContext.asyncAssertSuccess());
}
#Test
public void test_my_post_method(TestContext testContext) {
Async http = testContext.async();
String body = "{'username':'www','password':'www'}";
webClient.post(port, "localhost", "/api/apple/")
//.putHeader("Authorization", JWT_TOKEN)
.putHeader("content-length", "1000");
.putHeader("content-type", "application/x-www-form-urlencoded");
.sendJson(Buffer.buffer(body.getBytes()), requestResponse -> {
if (requestResponse.succeeded()) {
testContext.assertTrue(requestResponse.result().statusCode() == HttpResponseStatus.OK.code());
testContext.assertTrue(requestResponse.result().body().getString().equals("hello"));
} else {
testContext.fail(requestResponse.cause());
}
http.complete();
});
}
}

Resources