Play Framework 无法读取 Cloud Foundry 中的环境变量
Play Framework unable to read environment variable in Cloud Foundry
我正在尝试读取 application.conf
中 PostgreSQL 服务的连接 URL 环境变量,如下所示:
db.default.driver="org.postgresql.Driver"
db.default.url=${?cloud.services.postgresql.connection.url}
我的VCAP_SERVICES
如下
{
"postgresql": [
{
"binding_name": null,
"credentials": {
"dbname": "sample-db",
"end_points": [
{
"host": "x.x.x.x",
"network_id": "SF",
"port": "44980"
}
],
"hostname": "x.x.x.x",
"password": "sample-password",
"port": "44980",
"ports": {
"5432/tcp": "44980"
},
"uri": "postgres://sample-user:sample-password@x.x.x.x:44980/sample-db",
"username": "sample-user"
},
"instance_name": "postgresql",
"label": "postgresql",
"name": "postgresql",
"plan": "v9.6-dev",
"provider": null,
"syslog_drain_url": null,
"tags": [
"postgresql",
"relational"
],
"volume_mounts": []
}
]
}
我正在关注 this article。
但是数据库不会配置并且根是 Configuration error[jdbcUrl is required with driverClassName.]
。下面是完整的异常转储。
play.api.Configuration$$anon: Configuration error[Cannot initialize to database [default]]
at play.api.Configuration$.configError(Configuration.scala:155)
at play.api.Configuration.reportError(Configuration.scala:394)
at play.api.db.DefaultDBApi.$anonfun$initialize(DefaultDBApi.scala:76)
at scala.collection.immutable.List.foreach(List.scala:333)
at play.api.db.DefaultDBApi.initialize(DefaultDBApi.scala:68)
at play.api.db.DBApiProvider.get$lzycompute(DBModule.scala:92)
at play.api.db.DBApiProvider.get(DBModule.scala:77)
at play.api.db.DBApiProvider.get(DBModule.scala:59)
at com.google.inject.internal.ProviderInternalFactory.provision(ProviderInternalFactory.java:85)
at com.google.inject.internal.BoundProviderFactory.provision(BoundProviderFactory.java:77)
at com.google.inject.internal.ProviderInternalFactory.circularGet(ProviderInternalFactory.java:59)
at com.google.inject.internal.BoundProviderFactory.get(BoundProviderFactory.java:61)
at com.google.inject.internal.SingleFieldInjector.inject(SingleFieldInjector.java:52)
at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:147)
at com.google.inject.internal.MembersInjectorImpl.injectAndNotify(MembersInjectorImpl.java:101)
at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:71)
at com.google.inject.internal.InjectorImpl.injectMembers(InjectorImpl.java:1055)
at com.google.inject.util.Providers$GuicifiedProviderWithDependencies.initialize(Providers.java:154)
at com.google.inject.util.Providers$GuicifiedProviderWithDependencies$$FastClassByGuice$a7177aa.invoke(<generated>)
at com.google.inject.internal.SingleMethodInjector.invoke(SingleMethodInjector.java:51)
at com.google.inject.internal.SingleMethodInjector.inject(SingleMethodInjector.java:85)
at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:147)
at com.google.inject.internal.MembersInjectorImpl.injectAndNotify(MembersInjectorImpl.java:101)
at com.google.inject.internal.Initializer$InjectableReference.get(Initializer.java:245)
at com.google.inject.internal.Initializer.injectAll(Initializer.java:140)
at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:178)
at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:111)
at com.google.inject.Guice.createInjector(Guice.java:87)
at com.google.inject.Guice.createInjector(Guice.java:78)
at play.api.inject.guice.GuiceBuilder.injector(GuiceInjectorBuilder.scala:200)
at play.inject.guice.GuiceBuilder.injector(GuiceBuilder.java:211)
at play.inject.guice.GuiceApplicationBuilder.build(GuiceApplicationBuilder.java:121)
at play.inject.guice.GuiceApplicationLoader.load(GuiceApplicationLoader.java:32)
at play.api.ApplicationLoader$JavaApplicationLoaderAdapter.load(ApplicationLoader.scala:181)
at play.core.server.DevServerStart$$anon.$anonfun$reload(DevServerStart.scala:190)
at play.utils.Threads$.withContextClassLoader(Threads.scala:22)
at play.core.server.DevServerStart$$anon.reload(DevServerStart.scala:182)
at play.core.server.DevServerStart$$anon.get(DevServerStart.scala:142)
at play.core.server.AkkaHttpServer.handleRequest(AkkaHttpServer.scala:301)
at play.core.server.AkkaHttpServer.$anonfun$createServerBinding(AkkaHttpServer.scala:191)
at akka.stream.impl.fusing.MapAsync$$anon.onPush(Ops.scala:1285)
at akka.stream.impl.fusing.GraphInterpreter.processPush(GraphInterpreter.scala:541)
at akka.stream.impl.fusing.GraphInterpreter.execute(GraphInterpreter.scala:423)
at akka.stream.impl.fusing.GraphInterpreterShell.runBatch(ActorGraphInterpreter.scala:625)
at akka.stream.impl.fusing.GraphInterpreterShell$AsyncInput.execute(ActorGraphInterpreter.scala:502)
at akka.stream.impl.fusing.GraphInterpreterShell.processEvent(ActorGraphInterpreter.scala:600)
at akka.stream.impl.fusing.ActorGraphInterpreter.akka$stream$impl$fusing$ActorGraphInterpreter$$processEvent(ActorGraphInterpreter.scala:769)
at akka.stream.impl.fusing.ActorGraphInterpreter$$anonfun$receive.applyOrElse(ActorGraphInterpreter.scala:784)
at akka.actor.Actor.aroundReceive(Actor.scala:535)
at akka.actor.Actor.aroundReceive$(Actor.scala:533)
at akka.stream.impl.fusing.ActorGraphInterpreter.aroundReceive(ActorGraphInterpreter.scala:691)
at akka.actor.ActorCell.receiveMessage(ActorCell.scala:575)
at akka.actor.ActorCell.invoke(ActorCell.scala:545)
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:270)
at akka.dispatch.Mailbox.run(Mailbox.scala:231)
at akka.dispatch.Mailbox.exec(Mailbox.scala:243)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: play.api.Configuration$$anon: Configuration error[jdbcUrl is required with driverClassName.]
at play.api.Configuration$.configError(Configuration.scala:155)
at play.api.Configuration.reportError(Configuration.scala:394)
at play.api.db.HikariCPConnectionPool.create(HikariCPModule.scala:70)
at play.api.db.PooledDatabase.createDataSource(Databases.scala:249)
at play.api.db.DefaultDatabase.dataSource$lzycompute(Databases.scala:141)
at play.api.db.DefaultDatabase.dataSource(Databases.scala:139)
at play.api.db.DefaultDBApi.$anonfun$initialize(DefaultDBApi.scala:72)
... 57 common frames omitted
Caused by: java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.
at com.zaxxer.hikari.HikariConfig.validate(HikariConfig.java:1000)
at play.api.db.HikariCPConfig.toHikariConfig(HikariCPModule.scala:140)
at play.api.db.HikariCPConnectionPool.$anonfun$create(HikariCPModule.scala:57)
at scala.util.Try$.apply(Try.scala:210)
at play.api.db.HikariCPConnectionPool.create(HikariCPModule.scala:54)
... 61 common frames omitted
使用 Play Framework 2.8。
看起来 Cloud Foundry 对 Play Framework 的支持自 Play 2.5 以来已经中断。 Play Framework 中 Cloud Foundry 的 Java Buildpack removed support for it which in turn invalidates the corresponding documentation 指的是前缀为 {?cloud.services....}
.
的配置键
我自己编写代码来解析 VCAP_SERVICES
并将其插入应用程序加载器,following the documentation to create your own application loader:
package com.example.config;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import play.ApplicationLoader;
import play.inject.guice.GuiceApplicationBuilder;
import play.inject.guice.GuiceApplicationLoader;
public class MyApplicationLoader extends GuiceApplicationLoader {
private static final Logger LOGGER = Logger.getLogger(MyApplicationLoader.class.getCanonicalName());
@Override
public GuiceApplicationBuilder builder(ApplicationLoader.Context context) {
// https://www.programcreek.com/scala/play.api.Configuration
// https://www.playframework.com/documentation/2.8.x/JavaDependencyInjection#Advanced:-Extending-the-GuiceApplicationLoader
Config cloudConfig = parseCloudFoundryEnvironmentConfig(context);
return initialBuilder
.in(context.environment())
.loadConfig(cloudConfig.withFallback(context.initialConfig()))
.overrides(overrides(context));
}
static Config parseCloudFoundryEnvironmentConfig(ApplicationLoader.Context context) {
final ObjectMapper objectMapper = new ObjectMapper();
final HashMap<String,Object> configOutput = new HashMap<>();
final String vcap_services_str = System.getenv("VCAP_SERVICES");
if(vcap_services_str != null) {
try {
JsonNode rootNode = objectMapper.readTree(vcap_services_str);
/// ... parse VCAP_SERVICES and initialize the "db...." Play configuration into `configOutput`
} catch(IOException ex) {
LOGGER.log(Level.SEVERE, ex, () -> MessageFormat.format("Unable to parse VCAP_SERVICES content: {0}", vcap_services_str));
}
} else {
LOGGER.info("VCAP_SERVICES not defined");
}
Config configResult = ConfigFactory.parseMap(configOutput, "Environment Variables");
return configResult;
}
}
然后我通过在 conf
文件夹下的 reference.conf
配置文件中创建一个条目来激活这个新的应用程序加载器。
play.application.loader=com.example.config.MyApplicationLoader
我正在尝试读取 application.conf
中 PostgreSQL 服务的连接 URL 环境变量,如下所示:
db.default.driver="org.postgresql.Driver"
db.default.url=${?cloud.services.postgresql.connection.url}
我的VCAP_SERVICES
如下
{
"postgresql": [
{
"binding_name": null,
"credentials": {
"dbname": "sample-db",
"end_points": [
{
"host": "x.x.x.x",
"network_id": "SF",
"port": "44980"
}
],
"hostname": "x.x.x.x",
"password": "sample-password",
"port": "44980",
"ports": {
"5432/tcp": "44980"
},
"uri": "postgres://sample-user:sample-password@x.x.x.x:44980/sample-db",
"username": "sample-user"
},
"instance_name": "postgresql",
"label": "postgresql",
"name": "postgresql",
"plan": "v9.6-dev",
"provider": null,
"syslog_drain_url": null,
"tags": [
"postgresql",
"relational"
],
"volume_mounts": []
}
]
}
我正在关注 this article。
但是数据库不会配置并且根是 Configuration error[jdbcUrl is required with driverClassName.]
。下面是完整的异常转储。
play.api.Configuration$$anon: Configuration error[Cannot initialize to database [default]]
at play.api.Configuration$.configError(Configuration.scala:155)
at play.api.Configuration.reportError(Configuration.scala:394)
at play.api.db.DefaultDBApi.$anonfun$initialize(DefaultDBApi.scala:76)
at scala.collection.immutable.List.foreach(List.scala:333)
at play.api.db.DefaultDBApi.initialize(DefaultDBApi.scala:68)
at play.api.db.DBApiProvider.get$lzycompute(DBModule.scala:92)
at play.api.db.DBApiProvider.get(DBModule.scala:77)
at play.api.db.DBApiProvider.get(DBModule.scala:59)
at com.google.inject.internal.ProviderInternalFactory.provision(ProviderInternalFactory.java:85)
at com.google.inject.internal.BoundProviderFactory.provision(BoundProviderFactory.java:77)
at com.google.inject.internal.ProviderInternalFactory.circularGet(ProviderInternalFactory.java:59)
at com.google.inject.internal.BoundProviderFactory.get(BoundProviderFactory.java:61)
at com.google.inject.internal.SingleFieldInjector.inject(SingleFieldInjector.java:52)
at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:147)
at com.google.inject.internal.MembersInjectorImpl.injectAndNotify(MembersInjectorImpl.java:101)
at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:71)
at com.google.inject.internal.InjectorImpl.injectMembers(InjectorImpl.java:1055)
at com.google.inject.util.Providers$GuicifiedProviderWithDependencies.initialize(Providers.java:154)
at com.google.inject.util.Providers$GuicifiedProviderWithDependencies$$FastClassByGuice$a7177aa.invoke(<generated>)
at com.google.inject.internal.SingleMethodInjector.invoke(SingleMethodInjector.java:51)
at com.google.inject.internal.SingleMethodInjector.inject(SingleMethodInjector.java:85)
at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:147)
at com.google.inject.internal.MembersInjectorImpl.injectAndNotify(MembersInjectorImpl.java:101)
at com.google.inject.internal.Initializer$InjectableReference.get(Initializer.java:245)
at com.google.inject.internal.Initializer.injectAll(Initializer.java:140)
at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:178)
at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:111)
at com.google.inject.Guice.createInjector(Guice.java:87)
at com.google.inject.Guice.createInjector(Guice.java:78)
at play.api.inject.guice.GuiceBuilder.injector(GuiceInjectorBuilder.scala:200)
at play.inject.guice.GuiceBuilder.injector(GuiceBuilder.java:211)
at play.inject.guice.GuiceApplicationBuilder.build(GuiceApplicationBuilder.java:121)
at play.inject.guice.GuiceApplicationLoader.load(GuiceApplicationLoader.java:32)
at play.api.ApplicationLoader$JavaApplicationLoaderAdapter.load(ApplicationLoader.scala:181)
at play.core.server.DevServerStart$$anon.$anonfun$reload(DevServerStart.scala:190)
at play.utils.Threads$.withContextClassLoader(Threads.scala:22)
at play.core.server.DevServerStart$$anon.reload(DevServerStart.scala:182)
at play.core.server.DevServerStart$$anon.get(DevServerStart.scala:142)
at play.core.server.AkkaHttpServer.handleRequest(AkkaHttpServer.scala:301)
at play.core.server.AkkaHttpServer.$anonfun$createServerBinding(AkkaHttpServer.scala:191)
at akka.stream.impl.fusing.MapAsync$$anon.onPush(Ops.scala:1285)
at akka.stream.impl.fusing.GraphInterpreter.processPush(GraphInterpreter.scala:541)
at akka.stream.impl.fusing.GraphInterpreter.execute(GraphInterpreter.scala:423)
at akka.stream.impl.fusing.GraphInterpreterShell.runBatch(ActorGraphInterpreter.scala:625)
at akka.stream.impl.fusing.GraphInterpreterShell$AsyncInput.execute(ActorGraphInterpreter.scala:502)
at akka.stream.impl.fusing.GraphInterpreterShell.processEvent(ActorGraphInterpreter.scala:600)
at akka.stream.impl.fusing.ActorGraphInterpreter.akka$stream$impl$fusing$ActorGraphInterpreter$$processEvent(ActorGraphInterpreter.scala:769)
at akka.stream.impl.fusing.ActorGraphInterpreter$$anonfun$receive.applyOrElse(ActorGraphInterpreter.scala:784)
at akka.actor.Actor.aroundReceive(Actor.scala:535)
at akka.actor.Actor.aroundReceive$(Actor.scala:533)
at akka.stream.impl.fusing.ActorGraphInterpreter.aroundReceive(ActorGraphInterpreter.scala:691)
at akka.actor.ActorCell.receiveMessage(ActorCell.scala:575)
at akka.actor.ActorCell.invoke(ActorCell.scala:545)
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:270)
at akka.dispatch.Mailbox.run(Mailbox.scala:231)
at akka.dispatch.Mailbox.exec(Mailbox.scala:243)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: play.api.Configuration$$anon: Configuration error[jdbcUrl is required with driverClassName.]
at play.api.Configuration$.configError(Configuration.scala:155)
at play.api.Configuration.reportError(Configuration.scala:394)
at play.api.db.HikariCPConnectionPool.create(HikariCPModule.scala:70)
at play.api.db.PooledDatabase.createDataSource(Databases.scala:249)
at play.api.db.DefaultDatabase.dataSource$lzycompute(Databases.scala:141)
at play.api.db.DefaultDatabase.dataSource(Databases.scala:139)
at play.api.db.DefaultDBApi.$anonfun$initialize(DefaultDBApi.scala:72)
... 57 common frames omitted
Caused by: java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.
at com.zaxxer.hikari.HikariConfig.validate(HikariConfig.java:1000)
at play.api.db.HikariCPConfig.toHikariConfig(HikariCPModule.scala:140)
at play.api.db.HikariCPConnectionPool.$anonfun$create(HikariCPModule.scala:57)
at scala.util.Try$.apply(Try.scala:210)
at play.api.db.HikariCPConnectionPool.create(HikariCPModule.scala:54)
... 61 common frames omitted
使用 Play Framework 2.8。
看起来 Cloud Foundry 对 Play Framework 的支持自 Play 2.5 以来已经中断。 Play Framework 中 Cloud Foundry 的 Java Buildpack removed support for it which in turn invalidates the corresponding documentation 指的是前缀为 {?cloud.services....}
.
我自己编写代码来解析 VCAP_SERVICES
并将其插入应用程序加载器,following the documentation to create your own application loader:
package com.example.config;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import play.ApplicationLoader;
import play.inject.guice.GuiceApplicationBuilder;
import play.inject.guice.GuiceApplicationLoader;
public class MyApplicationLoader extends GuiceApplicationLoader {
private static final Logger LOGGER = Logger.getLogger(MyApplicationLoader.class.getCanonicalName());
@Override
public GuiceApplicationBuilder builder(ApplicationLoader.Context context) {
// https://www.programcreek.com/scala/play.api.Configuration
// https://www.playframework.com/documentation/2.8.x/JavaDependencyInjection#Advanced:-Extending-the-GuiceApplicationLoader
Config cloudConfig = parseCloudFoundryEnvironmentConfig(context);
return initialBuilder
.in(context.environment())
.loadConfig(cloudConfig.withFallback(context.initialConfig()))
.overrides(overrides(context));
}
static Config parseCloudFoundryEnvironmentConfig(ApplicationLoader.Context context) {
final ObjectMapper objectMapper = new ObjectMapper();
final HashMap<String,Object> configOutput = new HashMap<>();
final String vcap_services_str = System.getenv("VCAP_SERVICES");
if(vcap_services_str != null) {
try {
JsonNode rootNode = objectMapper.readTree(vcap_services_str);
/// ... parse VCAP_SERVICES and initialize the "db...." Play configuration into `configOutput`
} catch(IOException ex) {
LOGGER.log(Level.SEVERE, ex, () -> MessageFormat.format("Unable to parse VCAP_SERVICES content: {0}", vcap_services_str));
}
} else {
LOGGER.info("VCAP_SERVICES not defined");
}
Config configResult = ConfigFactory.parseMap(configOutput, "Environment Variables");
return configResult;
}
}
然后我通过在 conf
文件夹下的 reference.conf
配置文件中创建一个条目来激活这个新的应用程序加载器。
play.application.loader=com.example.config.MyApplicationLoader