How to have multiple http configurations with akka-http - scala

With akka-http, you can provide typesafe config as described here which is put in application.conf. so a minified config can look like following:
akka.http {
client {
connecting-timeout = 10s
}
host-connection-pool {
max-connections = 4
max-open-requests = 32
}
}
My question is if I have to call different external services in the app, I create different pool to those. How do I change these config(max-connections, max-open-requests) for these different pools calling different external services.

One solution I have found so far for this is, overwriting the connectionPoolSettings and passing it when creating http pool:
Http().superPool[RequestTracker](
settings = ConnectionPoolSettings(httpActorSystem).withMaxOpenRequests(1).withMaxConnections(1)
)(httpMat)
Here I can provide appropriate config for maxOpenRequests and maxConnectionsas par my requirement.

Related

How do I overwrite config settings from an included file

I have defined some akka remote settings in my application.conf:
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
warn-about-java-serializer-usage = false
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "myhost"
port = 2561
maximum-frame-size = 256000b
}
}
}
But then I have another program that needs to access other configuration settings from my application.conf. But I need to ignore the akka config settings. So I've tried the following for the second program:
include "application"
akka {}
But the akka settings from application.conf are still being applied. I know this because I get a bind exception on the akka port eventhough there should be no remote akka in my second app
What is the best way for me clear/ignore the akka config settings from my application.conf?
Let's say you want to override the akka.remote.netty.tcp.port in your another.conf, you simply
include "application.conf"
akka.remote.netty.tcp.port = 2562
It will override the netty tcp port while leaving the rest unchanged and inherited
After some experimentation, the best bet IMO is to factor out your application.conf so that your common settings are in their own conf files.
So you might put the common settings in a common-settings.conf and then in your Akka application you would have
include "common-settings"
akka {
// Akka settings here
}
And the other modules that need common-settings can just, in their application.conf:
include "common-settings"
This may work better with a multi-module build. If they're the same, e.g., sbt module, then you'll probably replace the canonical application.conf with akka-application.conf and other-application.conf and point your ActorSystem setup code to akka-application.conf instead of application.conf (which probably shouldn't exist in this scenario, as you'd want bare ConfigFactory.load() calls to fail very quickly (the alternative here is to have different programs fighting over who owns application.conf).
The issue you are facing is actually by design. From the HOCON first page documentation:
Duplicate keys are allowed; later values override earlier, except for object-valued keys where the two objects are merged recursively
Therefore, when you add in your second file an akka {} it is just being merged, and not overwritten.
As I can see it, you have 2 options.
Copying the configuration, and override all properties with the one you actually want. That means, that in the second program, you can add:
akka {
actor {
provider = "new value"
warn-about-java-serializer-usage = false
}
remote {
enabled-transports = ["completely new values"]
netty.tcp {
hostname = "etc..."
port = 2561
maximum-frame-size = 256000b
}
}
}
The other option, which I like less, is to overwrite the object called akka. For doing that, you need to assign to it something that is not an object. Otherwise it will just be merged. For instance, if you add the the second program akka=4, so it will completely remove all of the other values. But! In this case, you have your program to deal with those properties to be missing. That means, that somewhere in your code you will have to write something like (don't forget that config throws on missing):
Try(config.getString("akka.actor.provider")).getOrElse(Do something here)
You have to do that because now akka is a string, and you cannot look into that as an object.

Authenticate with ECE ElasticSearch Sink from Apache Fink (Scala code)

Compiler error when using example provided in Flink documentation. The Flink documentation provides sample Scala code to set the REST client factory parameters when talking to Elasticsearch, https://ci.apache.org/projects/flink/flink-docs-stable/dev/connectors/elasticsearch.html.
When trying out this code i get a compiler error in IntelliJ which says "Cannot resolve symbol restClientBuilder".
I found the following SO which is EXACTLY my problem except that it is in Java and i am doing this in Scala.
Apache Flink (v1.6.0) authenticate Elasticsearch Sink (v6.4)
I tried copy pasting the solution code provided in the above SO into IntelliJ, the auto-converted code also has compiler errors.
// provide a RestClientFactory for custom configuration on the internally created REST client
// i only show the setMaxRetryTimeoutMillis for illustration purposes, the actual code will use HTTP cutom callback
esSinkBuilder.setRestClientFactory(
restClientBuilder -> {
restClientBuilder.setMaxRetryTimeoutMillis(10)
}
)
Then i tried (auto generated Java to Scala code by IntelliJ)
// provide a RestClientFactory for custom configuration on the internally created REST client// provide a RestClientFactory for custom configuration on the internally created REST client
import org.apache.http.auth.AuthScope
import org.apache.http.auth.UsernamePasswordCredentials
import org.apache.http.client.CredentialsProvider
import org.apache.http.impl.client.BasicCredentialsProvider
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder
import org.elasticsearch.client.RestClientBuilder
// provide a RestClientFactory for custom configuration on the internally created REST client// provide a RestClientFactory for custom configuration on the internally created REST client
esSinkBuilder.setRestClientFactory((restClientBuilder) => {
def foo(restClientBuilder) = restClientBuilder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
override def customizeHttpClient(httpClientBuilder: HttpAsyncClientBuilder): HttpAsyncClientBuilder = { // elasticsearch username and password
val credentialsProvider = new BasicCredentialsProvider
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(es_user, es_password))
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)
}
})
foo(restClientBuilder)
})
The original code snippet produces the error "cannot resolve RestClientFactory" and then Java to Scala shows several other errors.
So basically i need to find a Scala version of the solution described in Apache Flink (v1.6.0) authenticate Elasticsearch Sink (v6.4)
Update 1: I was able to make some progress with some help from IntelliJ. The following code compiles and runs but there is another problem.
esSinkBuilder.setRestClientFactory(
new RestClientFactory {
override def configureRestClientBuilder(restClientBuilder: RestClientBuilder): Unit = {
restClientBuilder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
override def customizeHttpClient(httpClientBuilder: HttpAsyncClientBuilder): HttpAsyncClientBuilder = {
// elasticsearch username and password
val credentialsProvider = new BasicCredentialsProvider
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(es_user, es_password))
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)
httpClientBuilder.setSSLContext(trustfulSslContext)
}
})
}
}
The problem is that i am not sure if i should be doing a new of the RestClientFactory object. What happens is that the application connects to the elasticsearch cluster but then discovers that the SSL CERT is not valid, so i had to put the trustfullSslContext (as described here https://gist.github.com/iRevive/4a3c7cb96374da5da80d4538f3da17cb), this got me past the SSL issue but now the ES REST Client does a ping test and the ping fails, it throws an exception and the app shutsdown. I am suspecting that the ping fails because of the SSL error and maybe it is not using the trustfulSslContext i setup as part of new RestClientFactory and this makes me suspect that i should not have done the new, there should be a simple way to update the existing RestclientFactory object and basically this is all happening because of my lack of Scala knowledge.
Happy to report that this is resolved. The code i posted in Update 1 is correct. The ping to ECE was not working for two reasons:
The certificate needs to include the complete chain including the root CA, the intermediate CA and the cert for the ECE. This helped get rid of the whole trustfulSslContext stuff.
The ECE was sitting behind an ha-proxy and the proxy did the mapping for the hostname in the HTTP request to the actual deployment cluster name in ECE. this mapping logic did not take into account that the Java REST High Level client uses the org.apache.httphost class which creates the hostname as hostname:port_number even when the port number is 443. Since it did not find the mapping because of the 443 therefore the ECE returned a 404 error instead of 200 ok (only way to find this was to look at unencrypted packets at the ha-proxy). Once the mapping logic in ha-proxy was fixed, the mapping was found and the pings are now successfull.

Setting DNS lookup's TimeToLive in Scala Play

I am trying to set the TimeToLive setting for DNS Lookup in my Scala-Play application. I use Play 2.5.9 and Scala 2.11.8 and follow the AWS guide. I tried the following ways:
in application.conf
// Set DNS lookup time-to-live to one minute
networkaddress.cache.ttl=1
networkaddress.cache.negative.ttl=1
in AppModule or EagerSingleton (the code would be similar)
class AppModule() extends AbstractModule {
Security.setProperty("networkaddress.cache.ttl", "1")
Security.setProperty("networkaddress.cache.negative.ttl", "1")
...
}
passing as environment variable:
sbt -Dsun.net.inetaddr.ttl=1 clean run
I have the following piece of test code in the application:
for (i <- 1 to 25) {
System.out.println(java.net.InetAddress.getByName("google.com").getHostAddress())
Thread.sleep(1000)
}
This always prints the same IP address, e.g. 216.58.212.206. To me it looks like none of the approaches specified above have any effect. However, maybe I am testing something else and not actually the value of TTL. Therefore, I have two questions:
what is the correct way to pass a security variable into a Play application?
how to test it?
To change the settings for DNS cache via java.security.Security you have to provide a custom application loader.
package modules
class ApplicationLoader extends GuiceApplicationLoader {
override protected def builder(context: Context): GuiceApplicationBuilder = {
java.security.Security.setProperty("networkaddress.cache.ttl", "1")
super.builder(context)
}
}
When you build this application loader you can enable it in your application.conf
play.application.loader = "modules.ApplicationLoader"
after that you could use your code above and check if the DNS cache is behaving like you set it up. But keep in mind that your system is accessing a DNS server which is caching itself so you wont see change then.
If you want to be sure that you get different addresses for google.com you should use an authority name server like ns1.google.com
If you want to write a test on that you could maybe write a test which requests the address and then waits for the specified amount of time until it resolves again. But with a DNS system out of your control like google.com this could be a problem, if you hit a DNS server with caching.
If you want to write such a check you could do it with
#RunWith(classOf[JUnitRunner])
class DnsTests extends FlatSpec with Matchers {
"DNS Cache ttl" should "refresh after 1 second"
in new WithApplicationLoader(new modules.ApplicationLoader) {
// put your test code here
}
}
As you can see you can put the custom application loader in the context of the application starting behind your test.

Camel Http4 2.12.2: "httpClientConfigurer" cannot be inferred from endpointUri

I'm using scala akka-camel with http4 component (2.12.2 version).
I'm creating a Camel producer with endpoint:
def endpointUri = "https4://host-path" +
"?bridgeEndpoint=true" +
"&httpClientConfigurer=#configurer" +
"&clientConnectionManager=#manager"
where configurer is an HttpClientConfigurer registered in Camel context registry (the same principle applies to manager).
When I'm sending a CamelMessage to that endpoint I can see at akka logs this:
DEBUG o.a.c.component.http4.HttpComponent - Creating endpoint uri https4://host-path?bridgeEndpoint=true&httpClientConfigurer=#configurer&clientConnectionManager=#manager
DEBUG o.a.camel.util.IntrospectionSupport - Configured property: clientConnectionManager on bean: Endpoint["https4://host-path?bridgeEndpoint=true&httpClientConfigurer=#configurer&clientConnectionManager=#manager"] with value: org.apache.http.impl.conn.PoolingClientConnectionManager#3da3d36f
DEBUG o.a.camel.util.IntrospectionSupport - Configured property: bridgeEndpoint on bean: Endpoint["https4://host-path?bridgeEndpoint=true&httpClientConfigurer=#configurer&clientConnectionManager=#manager"] with value: true
INFO o.a.c.component.http4.HttpComponent - Registering SSL scheme https on port 443
INFO o.a.c.component.http4.HttpComponent - Registering SSL scheme https4 on port 443
So httpClientConfigurer is not configured and I don't know why it's ignoring this parameter. I've been looking for any related issue at Apache Camel issue tracker but I have found nothing similar.
Any idea?
Thanks in advance.
Finally, it's resolved. I haven't used the none of clientConnectionManager or httpClientConfigurer. I've used SSLContextParams and a trait called TlsConfigurer that is meant to be mixed-in with a Producer.
I want to use different X509 certificates, so, as Camel suggests:
Important: Only one instance of org.apache.camel.util.jsse.SSLContextParameters is supported per HttpComponent. If you need to use 2 or more different instances, you need to define a new HttpComponent per instance you need.
Therefore, TlsConfigurer configure method must be able to get a http4 component instance from camel context and then apply the SSLContextParams and add the modified instance as a new component to camel context.
This is how it looks:
import org.apache.camel.component.http4.HttpComponent
import org.apache.camel.util.jsse._
trait TlsConfigurer {
self: {val camel: akka.camel.Camel} =>
def configure(
componentName: String,
keyStorePath:String,
trustStorePath:String,
password: String) {
val ksp = new KeyStoreParameters
ksp.setResource(keystorePath)
ksp.setPassword(password)
val kmp = new KeyManagersParameters
kmp.setKeyStore(ksp)
kmp.setKeyPassword(password)
val scp = new SSLContextParameters
scp.setKeyManagers(kmp)
val httpComponent =
camel.context.getComponent("http4",classOf[HttpComponent])
httpComponent.setSslContextParameters(scp)
camel.context.addComponent(componentName, httpComponent)
}
}
This way I can create two different end-points: http-client1://... and http-client2://... and manage their certificates in a separate way.
httpClientConfigurer is not set to the HttpEndpoint by using IntrospectionSupport, so you don't see the debug log.
I think we about to find out the configurer is called when you add some log in the customer configurer.

Why is triggerJob disabled in Quartz's JMX?

I have successfully configured our app to export Quartz's MBeans into JMX and can view everything in JConsole. I can run the majority of the scheduler operations.
The one I really want to run is 'triggerJob', but that is showing up in JConsole as greyed-out/disabled so I can't run it.
I've scanned the commits that added the JMX code to Quartz but can't see any differences between triggerJob and the other operations that are enabled.
Anyone have a clue what's going on?
EDIT - explanation found
A different StackOverflow issue describes what's going on: Why are some methods on the JConsole disabled
triggerJob (and two other operations) take non-primitive parameters, these complex parameters cannot be provided in JConsole.
I am not clear if the MBean provider might provide a custom editor for JConsole (or simlar), but at least I have my answer.
Thank you for your explanation. I have successfully triggered a job remotely through JMX using the following Groovy code:
def callParams = new Object[3]
callParams[0] = 'com.test.project.TestJob'
callParams[1] = 'DEFAULT_JOB_GROUP'
callParams[2] = new HashMap()
def callSignature = new String[3]
callSignature[0] = 'java.lang.String'
callSignature[1] = 'java.lang.String'
callSignature[2] = 'java.util.Map'
// server is an instance of MBeanServerConnection
server.invoke('triggerJob', callParams, callSignature)