diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template index 43b99b6102..bba9e13148 100644 --- a/obp-api/src/main/resources/props/sample.props.template +++ b/obp-api/src/main/resources/props/sample.props.template @@ -287,6 +287,7 @@ db.url=jdbc:h2:./lift_proto.db;NON_KEYWORDS=VALUE;DB_CLOSE_ON_EXIT=FALSE ## This is needed for oauth to work. it's important to access the api over this url, e.g. ## If this is 127.0.0.1 do NOT use localhost to access it. ## (this needs to be a URL) +## IMPORTANT: hostname is also used as fallback for local_provider_name - do not change unless necessary hostname=http://127.0.0.1 # Used as the base URL for password reset and email validation links sent via email. @@ -298,6 +299,13 @@ portal_external_url=http://localhost:5174 ## To start the server, use: java -jar obp-api/target/obp-api.jar dev.port=8080 +## Bind address for http4s server (optional) +## If not set, the server will parse and use the host from the hostname property above +## Use this if you need to bind to a different address than what's in hostname +## Example: bind_address=0.0.0.0 (to listen on all interfaces) +## Example: bind_address=127.0.0.1 (to listen only on localhost) +#bind_address=127.0.0.1 + #The start of the api path (before the version) #It is *strongly* recommended not to change this - since Apps will be expecting the api at /obp/+version diff --git a/obp-api/src/main/scala/bootstrap/http4s/Http4sServer.scala b/obp-api/src/main/scala/bootstrap/http4s/Http4sServer.scala index 6298630950..e8262339ee 100644 --- a/obp-api/src/main/scala/bootstrap/http4s/Http4sServer.scala +++ b/obp-api/src/main/scala/bootstrap/http4s/Http4sServer.scala @@ -2,9 +2,8 @@ package bootstrap.http4s import cats.effect._ import code.api.util.APIUtil -import code.api.util.http4s.Http4sApp +import code.api.util.http4s.{Http4sApp, Http4sConfigUtil} import com.comcast.ip4s._ -import org.http4s.Uri import org.http4s.ember.server._ object Http4sServer extends IOApp { @@ -12,22 +11,15 @@ object Http4sServer extends IOApp { //Start OBP relevant objects and settings; this step MUST be executed first // new bootstrap.http4s.Http4sBoot().boot new bootstrap.liftweb.Boot().boot - - // Parse hostname - support both "127.0.0.1" and "http://127.0.0.1" formats - private def parseHostname(hostnameValue: String): String = { - val trimmed = hostnameValue.trim - // Try to parse as URI first - Uri.fromString(trimmed).toOption - .flatMap(_.host.map(_.renderString)) - .getOrElse(trimmed) // If not a valid URI, use as-is - } - - val host = parseHostname(code.api.Constant.HostName) + + // Get bind address: use bind_address prop if set, otherwise parse from hostname + // Note: hostname prop must remain unchanged as it may be used for local_provider_name fallback + val host = Http4sConfigUtil.parseHostname(APIUtil.getPropsValue("bind_address",code.api.Constant.HostName)) val port = APIUtil.getPropsAsIntValue("dev.port",8080) // Use shared httpApp configuration (same as tests) val httpApp = Http4sApp.httpApp - + override def run(args: List[String]): IO[ExitCode] = EmberServerBuilder .default[IO] .withHost(Host.fromString(host).get) diff --git a/obp-api/src/main/scala/code/api/util/http4s/Http4sSupport.scala b/obp-api/src/main/scala/code/api/util/http4s/Http4sSupport.scala index f9f87715b7..89cae07d3e 100644 --- a/obp-api/src/main/scala/code/api/util/http4s/Http4sSupport.scala +++ b/obp-api/src/main/scala/code/api/util/http4s/Http4sSupport.scala @@ -436,3 +436,35 @@ object ResourceDocMatcher { ) } } + +/** + * Http4s configuration utilities. + */ +object Http4sConfigUtil { + + /** + * Parse hostname from a string that may be either a plain host or a full URI. + * + * Supports both formats: + * - Plain host: "127.0.0.1" or "localhost" + * - Full URI: "http://127.0.0.1:8080" or "https://example.com" + * + * @param hostnameValue The hostname or URI string to parse + * @return The extracted hostname/IP address + * + * Examples: + * {{{ + * parseHostname("127.0.0.1") // Returns: "127.0.0.1" + * parseHostname("http://127.0.0.1:8080") // Returns: "127.0.0.1" + * parseHostname("https://api.example.com") // Returns: "api.example.com" + * parseHostname("localhost") // Returns: "localhost" + * }}} + */ + def parseHostname(hostnameValue: String): String = { + val trimmed = hostnameValue.trim + // Try to parse as URI first + Uri.fromString(trimmed).toOption + .flatMap(_.host.map(_.renderString)) + .getOrElse(trimmed) // If not a valid URI, use as-is + } +} diff --git a/obp-api/src/test/scala/code/api/util/http4s/Http4sConfigUtilTest.scala b/obp-api/src/test/scala/code/api/util/http4s/Http4sConfigUtilTest.scala new file mode 100644 index 0000000000..af0278b3d0 --- /dev/null +++ b/obp-api/src/test/scala/code/api/util/http4s/Http4sConfigUtilTest.scala @@ -0,0 +1,42 @@ +package code.api.util.http4s + +import org.scalatest.{FlatSpec, Matchers} + +class Http4sConfigUtilTest extends FlatSpec with Matchers { + + "parseHostname" should "extract hostname from plain IP address" in { + Http4sConfigUtil.parseHostname("127.0.0.1") shouldBe "127.0.0.1" + } + + it should "extract hostname from HTTP URI" in { + Http4sConfigUtil.parseHostname("http://127.0.0.1:8080") shouldBe "127.0.0.1" + } + + it should "extract hostname from HTTPS URI" in { + Http4sConfigUtil.parseHostname("https://api.example.com") shouldBe "api.example.com" + } + + it should "handle localhost" in { + Http4sConfigUtil.parseHostname("localhost") shouldBe "localhost" + } + + it should "handle URI with path" in { + Http4sConfigUtil.parseHostname("http://example.com/path") shouldBe "example.com" + } + + it should "trim whitespace" in { + Http4sConfigUtil.parseHostname(" 127.0.0.1 ") shouldBe "127.0.0.1" + } + + it should "handle URI with port" in { + Http4sConfigUtil.parseHostname("http://localhost:8080") shouldBe "localhost" + } + + it should "handle domain names" in { + Http4sConfigUtil.parseHostname("example.com") shouldBe "example.com" + } + + it should "handle full URL with protocol, port and path" in { + Http4sConfigUtil.parseHostname("https://api.example.com:443/v1/endpoint") shouldBe "api.example.com" + } +} diff --git a/release_notes.md b/release_notes.md index f6b79cb2c0..0652d39f43 100644 --- a/release_notes.md +++ b/release_notes.md @@ -3,12 +3,11 @@ ### Most recent changes at top of file ``` Date Commit Action -12/12/2025 f2e7b827 Http4s runner configuration - Added http4s.host and http4s.port to props sample template: - - http4s.host=127.0.0.1 - - http4s.port=8086 - These properties control the bind address of bootstrap.http4s.Http4sServer - when running via the obp-http4s-runner fat JAR. +27/02/2026 24035862 Http4s server bind address configuration + Added bind_address property for http4s server configuration: + - bind_address: Optional property to specify the network binding address + - Falls back to parsing hostname property if not set + - Maintains backward compatibility with existing hostname configuration 11/12/2025 3c2df942 BREAKING CHANGE: Migration from Akka to Apache Pekko™ 1.1.2 Replaced Akka 2.5.32 with Apache Pekko™ 1.1.2 to address Akka licensing changes. Updated all imports from com.typesafe.akka to org.apache.pekko.