diff --git a/ballerina-tests/http-advanced-tests/tests/http_default_listener_test.bal b/ballerina-tests/http-advanced-tests/tests/http_default_listener_test.bal new file mode 100644 index 000000000..cfc2b6ce9 --- /dev/null +++ b/ballerina-tests/http-advanced-tests/tests/http_default_listener_test.bal @@ -0,0 +1,55 @@ +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org). +// +// WSO2 LLC. licenses this file to you 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. + +import ballerina/http; +import ballerina/http_test_common.service1 as _; +import ballerina/http_test_common.service2 as _; +import ballerina/test; + +listener http:Listener defaultListener = http:getDefaultListener(); + +service /api/v3 on defaultListener { + + resource function get greeting() returns string { + return "Hello, World from service 3!"; + } +} + +service /api/v4 on defaultListener { + + resource function get greeting() returns string { + return "Hello, World from service 4!"; + } +} + +listener http:Listener defaultListenerNew = http:getDefaultListener(); + +service /api/v5 on defaultListenerNew { + + resource function get greeting() returns string { + return "Hello, World from service 5!"; + } +} + +final http:Client defaultListenerClient = check new(string `localhost:${http:defaultListenerPort}/api`); + +@test:Config {} +function testDefaultListener() returns error? { + foreach int i in 1...5 { + string response = check defaultListenerClient->/[string `v${i}`]/greeting; + test:assertEquals(response, string `Hello, World from service ${i}!`); + } +} diff --git a/ballerina-tests/http-advanced-tests/tests/multipart_response.bal b/ballerina-tests/http-advanced-tests/tests/multipart_response.bal index f5ab7d3d2..d5cf69655 100644 --- a/ballerina-tests/http-advanced-tests/tests/multipart_response.bal +++ b/ballerina-tests/http-advanced-tests/tests/multipart_response.bal @@ -20,8 +20,8 @@ import ballerina/mime; import ballerina/test; import ballerina/http_test_common as common; -listener http:Listener mockEP2 = new (9091, httpVersion = http:HTTP_1_1); -final http:Client multipartRespClient = check new ("http://localhost:9091", httpVersion = http:HTTP_1_1); +listener http:Listener mockEP2 = new (multipartResponseTestPort, httpVersion = http:HTTP_1_1); +final http:Client multipartRespClient = check new (string `http://localhost:${multipartResponseTestPort}`, httpVersion = http:HTTP_1_1); service /multipart on mockEP2 { resource function get encode_out_response(http:Caller caller, http:Request request) returns error? { diff --git a/ballerina-tests/http-advanced-tests/tests/test_service_ports.bal b/ballerina-tests/http-advanced-tests/tests/test_service_ports.bal index d2de8fd3e..3b1c6954d 100644 --- a/ballerina-tests/http-advanced-tests/tests/test_service_ports.bal +++ b/ballerina-tests/http-advanced-tests/tests/test_service_ports.bal @@ -36,9 +36,10 @@ const int trailingHeaderTestPort2 = 9527; const int corsConfigTestPort = 9013; const int multipartRequestTestPort = 9018; +const int multipartResponseTestPort = 9019; const int serviceMediaTypeSubtypePrefixPort = 9579; -const int statusCodeErrorUseCasePort = 9090; +const int statusCodeErrorUseCasePort = 9089; const int statusCodeErrorPort = 9092; const int identicalCookiePort = 9093; diff --git a/ballerina-tests/http-security-tests/tests/Config.toml b/ballerina-tests/http-security-tests/tests/Config.toml index d8275b017..ca115e8fc 100644 --- a/ballerina-tests/http-security-tests/tests/Config.toml +++ b/ballerina-tests/http-security-tests/tests/Config.toml @@ -11,3 +11,13 @@ scopes=["read"] [[ballerina.auth.users]] username="eve" password="123" + +[ballerina.http] +defaultListenerPort = 8080 + +[ballerina.http.defaultListenerConfig] +httpVersion = "1.1" + +[ballerina.http.defaultListenerConfig.secureSocket.key] +path = "../resources/certsandkeys/ballerinaKeystore.p12" +password = "ballerina" diff --git a/ballerina-tests/http-security-tests/tests/http_default_listener_test.bal b/ballerina-tests/http-security-tests/tests/http_default_listener_test.bal new file mode 100644 index 000000000..484ab1642 --- /dev/null +++ b/ballerina-tests/http-security-tests/tests/http_default_listener_test.bal @@ -0,0 +1,64 @@ +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org). +// +// WSO2 LLC. licenses this file to you 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. + +import ballerina/http; +import ballerina/http_test_common as common; +import ballerina/http_test_common.service1 as _; +import ballerina/http_test_common.service2 as _; +import ballerina/test; + +listener http:Listener defaultListener = http:getDefaultListener(); + +service /api/v3 on defaultListener { + + resource function get greeting() returns string { + return "Hello, World from service 3!"; + } +} + +service /api/v4 on defaultListener { + + resource function get greeting() returns string { + return "Hello, World from service 4!"; + } +} + +listener http:Listener defaultListenerNew = http:getDefaultListener(); + +service /api/v5 on defaultListenerNew { + + resource function get greeting() returns string { + return "Hello, World from service 5!"; + } +} + +final http:Client defaultListenerClient = check new(string `localhost:${http:defaultListenerPort}/api`, + secureSocket = { + cert: { + path: common:TRUSTSTORE_PATH, + password: "ballerina" + } + }, + httpVersion = "1.1" +); + +@test:Config {} +function testDefaultListenerWithConfiguration() returns error? { + foreach int i in 1...5 { + string response = check defaultListenerClient->/[string `v${i}`]/greeting; + test:assertEquals(response, string `Hello, World from service ${i}!`); + } +} diff --git a/ballerina-tests/http-test-common/modules/service1/README.md b/ballerina-tests/http-test-common/modules/service1/README.md new file mode 100644 index 000000000..7718cedcc --- /dev/null +++ b/ballerina-tests/http-test-common/modules/service1/README.md @@ -0,0 +1,3 @@ +## Module overview + +This module provides a mock HTTP service - service1. diff --git a/ballerina-tests/http-test-common/modules/service1/service.bal b/ballerina-tests/http-test-common/modules/service1/service.bal new file mode 100644 index 000000000..434c175da --- /dev/null +++ b/ballerina-tests/http-test-common/modules/service1/service.bal @@ -0,0 +1,26 @@ +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org). +// +// WSO2 LLC. licenses this file to you 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. + +import ballerina/http; + +listener http:Listener defaultListener = http:getDefaultListener(); + +service /api/v1 on defaultListener { + + resource function get greeting() returns string { + return "Hello, World from service 1!"; + } +} diff --git a/ballerina-tests/http-test-common/modules/service2/README.md b/ballerina-tests/http-test-common/modules/service2/README.md new file mode 100644 index 000000000..a0bce3c1a --- /dev/null +++ b/ballerina-tests/http-test-common/modules/service2/README.md @@ -0,0 +1,3 @@ +## Module overview + +This module provides a mock HTTP service - service2. diff --git a/ballerina-tests/http-test-common/modules/service2/service.bal b/ballerina-tests/http-test-common/modules/service2/service.bal new file mode 100644 index 000000000..992413832 --- /dev/null +++ b/ballerina-tests/http-test-common/modules/service2/service.bal @@ -0,0 +1,26 @@ +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org). +// +// WSO2 LLC. licenses this file to you 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. + +import ballerina/http; + +listener http:Listener defaultListener = http:getDefaultListener(); + +service /api/v2 on defaultListener { + + resource function get greeting() returns string { + return "Hello, World from service 2!"; + } +} diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 687c6fbe3..c0394352d 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -27,13 +27,13 @@ path = "../native/build/libs/http-native-2.13.0-SNAPSHOT.jar" groupId = "io.ballerina.stdlib" artifactId = "mime-native" version = "2.11.0" -path = "./lib/mime-native-2.11.0-20241218-125100-e28a03b.jar" +path = "./lib/mime-native-2.11.0-20250127-182500-46c9896.jar" [[platform.java21.dependency]] groupId = "io.ballerina.stdlib" artifactId = "constraint-native" version = "1.6.0" -path = "./lib/constraint-native-1.6.0-20241218-112400-cd313f2.jar" +path = "./lib/constraint-native-1.6.0-20250127-170900-48ad9ae.jar" [[platform.java21.dependency]] groupId = "io.netty" diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml index 51c847c86..73f2ab76f 100644 --- a/ballerina/CompilerPlugin.toml +++ b/ballerina/CompilerPlugin.toml @@ -6,4 +6,4 @@ class = "io.ballerina.stdlib.http.compiler.HttpCompilerPlugin" path = "../compiler-plugin/build/libs/http-compiler-plugin-2.13.0-SNAPSHOT.jar" [[dependency]] -path = "../compiler-plugin/build/libs/ballerina-to-openapi-2.2.0-20241126-081700-8e808fd.jar" +path = "../compiler-plugin/build/libs/ballerina-to-openapi-2.2.0-20250127-223930-4c87b7d.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 4ffdf4673..262a05fdb 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.11.0-20241218-101200-109f6cc7" +distribution-version = "2201.11.0-20250127-101700-a4b67fe5" [[package]] org = "ballerina" diff --git a/ballerina/http_default_listener.bal b/ballerina/http_default_listener.bal new file mode 100644 index 000000000..977c3e77f --- /dev/null +++ b/ballerina/http_default_listener.bal @@ -0,0 +1,54 @@ +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org). +// +// WSO2 LLC. licenses this file to you 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. + +isolated Listener? defaultListener = (); + +# Default HTTP listener port used by the HTTP Default Listener. +# The default value is 9090. +public configurable int defaultListenerPort = 9090; + +# Default HTTP listener configuration used by the HTTP Default Listener. +public configurable ListenerConfiguration defaultListenerConfig = {}; + +# Returns the default HTTP listener. If the default listener is not already created, a new +# listener will be created with the port and configuration. An error will be returned if +# the listener creation fails. +# +# The default listener configuration can be changed in the `Config.toml` file. Example: +# ```toml +# [ballerina.http] +# defaultListenerPort = 8080 +# +# [ballerina.http.defaultListenerConfig] +# httpVersion = "1.1" +# +# [ballerina.http.defaultListenerConfig.secureSocket.key] +# path = "resources/certs/key.pem" +# password = "password" +# ``` +# +# + return - The default HTTP listener or an error if the listener creation fails. +public isolated function getDefaultListener() returns Listener|ListenerError { + lock { + Listener? tempListener = defaultListener; + if tempListener is Listener { + return tempListener; + } + Listener 'listener = check new Listener(defaultListenerPort, defaultListenerConfig); + defaultListener = 'listener; + return 'listener; + } +} diff --git a/changelog.md b/changelog.md index 8d9fc3c94..6cba7d2e4 100644 --- a/changelog.md +++ b/changelog.md @@ -35,6 +35,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - [Add connection eviction feature to handle connections that receive GO_AWAY from the client](https://github.com/ballerina-platform/ballerina-library/issues/6734) - [Enhance the configurability of Ballerina access logging by introducing multiple configuration options.](https://github.com/ballerina-platform/ballerina-library/issues/6111) - [Introduce HTTP service contract object type](https://github.com/ballerina-platform/ballerina-library/issues/6378) +- [Add default HTTP listener](https://github.com/ballerina-platform/ballerina-library/issues/7514) ### Fixed diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 320f09d9e..efffbe5ea 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -23,6 +23,7 @@ The conforming implementation of the specification is released and included in t * 2.1. [Listener](#21-listener) * 2.1.1. [Automatically starting the service](#211-automatically-starting-the-service) * 2.1.2. [Programmatically starting the service](#212-programmatically-starting-the-service) + * 2.1.3. [Default listener](#213-default-listener) * 2.2. [Service](#22-service) * 2.2.1. [Service type](#221-service-type) * 2.2.2. [Service-base-path](#222-service-base-path) @@ -212,6 +213,40 @@ http:Service s = service object { }; ``` +#### 2.1.3. Default listener + +The default listener can be created by calling the `getDefaultListener()` method. Once the default listener is created, +the subsequent calls to the `getDefaultListener()` method will return the same listener object. With this approach, +the user can attach multiple services to the same listener and configure the listener as required. The default listener +port is 9090. + +```ballerina +import ballerina/http; + +listener http:Listener httpListener = http:getDefaultListener(); + +service /api/v1 on httpListener { + + resource function get greeting() returns string { + return "Hello, World from Service 1!"; + } +} +``` + +The port and listener configuration of the default listener can be changed in the `Config.toml` as follows: + +```toml +[ballerina.http] +defaultListenerPort = 8080 + +[ballerina.http.defaultListenerConfig] +httpVersion = "1.1" + +[ballerina.http.defaultListenerConfig.secureSocket.key] +path = "resources/certs/ballerinaKeystore.p12" +password = "ballerina" +``` + ### 2.2. Service Service is a collection of resources functions, which are the network entry points of a ballerina program. In addition to that a service can contain public and private functions which can be accessed by calling with `self`.