├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml ├── release-drafter.yml └── workflows │ ├── branch-ci.yml │ ├── pre-release-ci.yml │ └── release-ci.yml ├── .gitignore ├── .yamllint.yml ├── LICENSE.txt ├── README.md ├── pom.xml ├── requirements.txt ├── services-api ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── scalecube │ │ │ └── services │ │ │ ├── Address.java │ │ │ ├── CommunicationMode.java │ │ │ ├── HexUtil.java │ │ │ ├── MaskUtil.java │ │ │ ├── Reflect.java │ │ │ ├── RequestContext.java │ │ │ ├── ServiceCall.java │ │ │ ├── ServiceEndpoint.java │ │ │ ├── ServiceInfo.java │ │ │ ├── ServiceProvider.java │ │ │ ├── ServiceReference.java │ │ │ ├── ServiceRegistration.java │ │ │ ├── annotations │ │ │ ├── AfterConstruct.java │ │ │ ├── BeforeDestroy.java │ │ │ ├── ExecuteOn.java │ │ │ ├── Inject.java │ │ │ ├── RequestType.java │ │ │ ├── ResponseType.java │ │ │ ├── RestMethod.java │ │ │ ├── Service.java │ │ │ ├── ServiceMethod.java │ │ │ ├── Subscriber.java │ │ │ ├── Tag.java │ │ │ └── Tags.java │ │ │ ├── api │ │ │ ├── DynamicQualifier.java │ │ │ ├── ErrorData.java │ │ │ ├── Qualifier.java │ │ │ └── ServiceMessage.java │ │ │ ├── auth │ │ │ ├── AllowedRole.java │ │ │ ├── AllowedRoles.java │ │ │ ├── Authenticator.java │ │ │ ├── CredentialsSupplier.java │ │ │ ├── CredentialsSuppliers.java │ │ │ ├── Principal.java │ │ │ ├── PrincipalMapper.java │ │ │ ├── Secured.java │ │ │ ├── ServicePrincipal.java │ │ │ └── ServiceRolesProcessor.java │ │ │ ├── discovery │ │ │ └── api │ │ │ │ ├── ServiceDiscovery.java │ │ │ │ ├── ServiceDiscoveryEvent.java │ │ │ │ └── ServiceDiscoveryFactory.java │ │ │ ├── exceptions │ │ │ ├── BadRequestException.java │ │ │ ├── ConnectionClosedException.java │ │ │ ├── DefaultErrorMapper.java │ │ │ ├── ForbiddenException.java │ │ │ ├── InternalServiceException.java │ │ │ ├── MessageCodecException.java │ │ │ ├── ServiceClientErrorMapper.java │ │ │ ├── ServiceException.java │ │ │ ├── ServiceProviderErrorMapper.java │ │ │ ├── ServiceUnavailableException.java │ │ │ └── UnauthorizedException.java │ │ │ ├── gateway │ │ │ └── Gateway.java │ │ │ ├── methods │ │ │ ├── MethodInfo.java │ │ │ ├── ServiceMethodDefinition.java │ │ │ ├── ServiceMethodInvoker.java │ │ │ └── ServiceRoleDefinition.java │ │ │ ├── registry │ │ │ └── api │ │ │ │ └── ServiceRegistry.java │ │ │ ├── routing │ │ │ ├── RandomServiceRouter.java │ │ │ ├── RoundRobinServiceRouter.java │ │ │ ├── Router.java │ │ │ ├── Routers.java │ │ │ └── StaticAddressRouter.java │ │ │ └── transport │ │ │ └── api │ │ │ ├── ClientChannel.java │ │ │ ├── ClientTransport.java │ │ │ ├── DataCodec.java │ │ │ ├── HeadersCodec.java │ │ │ ├── JdkCodec.java │ │ │ ├── ServerTransport.java │ │ │ ├── ServiceMessageDataDecoder.java │ │ │ └── ServiceTransport.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── io.scalecube.services.transport.api.DataCodec │ └── test │ └── java │ └── io │ └── scalecube │ └── services │ ├── api │ └── DynamicQualifierTest.java │ ├── methods │ ├── PrincipalImpl.java │ ├── ReflectTest.java │ ├── ServiceMethodInvokerTest.java │ ├── StubService.java │ └── StubServiceImpl.java │ └── transport │ └── api │ ├── JdkCodecTest.java │ └── JdkHeadersCodecTest.java ├── services-discovery ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── scalecube │ │ └── services │ │ └── discovery │ │ └── ScalecubeServiceDiscovery.java │ └── test │ └── java │ └── io │ └── scalecube │ └── services │ └── discovery │ └── ScalecubeServiceDiscoveryTest.java ├── services-examples ├── pom.xml └── src │ └── main │ ├── java │ └── io │ │ └── scalecube │ │ └── services │ │ └── examples │ │ ├── BenchmarkService.java │ │ ├── BenchmarkServiceImpl.java │ │ ├── EchoRequest.java │ │ ├── EmptyGreetingRequest.java │ │ ├── EmptyGreetingResponse.java │ │ ├── GreetingRequest.java │ │ ├── GreetingResponse.java │ │ ├── GreetingService.java │ │ ├── GreetingServiceCancelCallback.java │ │ ├── GreetingServiceImpl.java │ │ ├── StreamRequest.java │ │ ├── codecs │ │ └── Example1.java │ │ ├── exceptions │ │ ├── ExceptionMapperExample.java │ │ ├── ServiceA.java │ │ ├── ServiceAClientErrorMapper.java │ │ ├── ServiceAException.java │ │ ├── ServiceAImpl.java │ │ ├── ServiceAProviderErrorMapper.java │ │ ├── ServiceB.java │ │ └── ServiceBImpl.java │ │ ├── helloworld │ │ ├── Example1.java │ │ ├── Example2.java │ │ ├── Example3.java │ │ └── service │ │ │ ├── BidiGreetingImpl.java │ │ │ ├── GreetingServiceImpl.java │ │ │ └── api │ │ │ ├── BidiGreetingService.java │ │ │ ├── Greeting.java │ │ │ └── GreetingsService.java │ │ └── services │ │ ├── Example1.java │ │ ├── Example2.java │ │ ├── Service1.java │ │ ├── Service1Impl.java │ │ ├── Service2.java │ │ └── Service2Impl.java │ └── resources │ └── log4j2.xml ├── services-gateway ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── scalecube │ │ └── services │ │ └── gateway │ │ ├── GatewaySession.java │ │ ├── GatewaySessionHandler.java │ │ ├── ReferenceCountUtil.java │ │ ├── client │ │ ├── GatewayClientCodec.java │ │ ├── ServiceMessageCodec.java │ │ ├── http │ │ │ ├── HttpGatewayClientCodec.java │ │ │ └── HttpGatewayClientTransport.java │ │ └── websocket │ │ │ ├── Signal.java │ │ │ ├── WebsocketGatewayClientCodec.java │ │ │ ├── WebsocketGatewayClientSession.java │ │ │ └── WebsocketGatewayClientTransport.java │ │ ├── http │ │ ├── HttpGateway.java │ │ └── HttpGatewayAcceptor.java │ │ └── websocket │ │ ├── GatewayMessages.java │ │ ├── HeartbeatService.java │ │ ├── HeartbeatServiceImpl.java │ │ ├── Signal.java │ │ ├── WebsocketContextException.java │ │ ├── WebsocketGateway.java │ │ ├── WebsocketGatewayAcceptor.java │ │ ├── WebsocketGatewaySession.java │ │ └── WebsocketServiceMessageCodec.java │ └── test │ └── java │ └── io │ └── scalecube │ └── services │ └── gateway │ ├── AuthRegistry.java │ ├── ErrorService.java │ ├── ErrorServiceImpl.java │ ├── GatewayErrorMapperImpl.java │ ├── SecuredService.java │ ├── SecuredServiceImpl.java │ ├── SomeException.java │ ├── files │ ├── ExportReportRequest.java │ ├── FileDownloadTest.java │ ├── ReportResponse.java │ ├── ReportService.java │ └── ReportServiceImpl.java │ ├── http │ ├── CorsTest.java │ ├── HttpClientConnectionTest.java │ ├── HttpGatewayTest.java │ └── HttpLocalGatewayTest.java │ ├── rest │ ├── RestGatewayTest.java │ ├── RestService.java │ ├── RestServiceImpl.java │ ├── RoutingService.java │ ├── RoutingServiceImpl.java │ ├── SomeRequest.java │ └── SomeResponse.java │ └── websocket │ ├── CancelledSubscriber.java │ ├── GatewaySessionHandlerImpl.java │ ├── ReactiveAdapter.java │ ├── ReactiveOperator.java │ ├── TestGatewaySessionHandler.java │ ├── TestInputs.java │ ├── TestService.java │ ├── TestServiceImpl.java │ ├── WebsocketClientConnectionTest.java │ ├── WebsocketClientTest.java │ ├── WebsocketGatewayAuthTest.java │ ├── WebsocketGatewayTest.java │ ├── WebsocketLocalGatewayTest.java │ ├── WebsocketServerTest.java │ └── WebsocketServiceMessageCodecTest.java ├── services-security ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── scalecube │ │ └── services │ │ └── security │ │ ├── ServiceTokenAuthenticator.java │ │ ├── ServiceTokenCredentialsSupplier.java │ │ └── VaultServiceRolesProcessor.java │ └── test │ ├── java │ └── io │ │ └── scalecube │ │ └── services │ │ └── security │ │ ├── ServiceTokenTests.java │ │ └── environment │ │ ├── IntegrationEnvironmentFixture.java │ │ └── VaultEnvironment.java │ └── resources │ └── log4j2-test.xml ├── services-testlib ├── pom.xml └── src │ └── main │ ├── java │ └── io │ │ └── scalecube │ │ └── services │ │ └── utils │ │ └── LoggingExtension.java │ └── resources │ ├── META-INF │ └── services │ │ └── org.junit.jupiter.api.extension.Extension │ ├── junit-platform.properties │ └── log4j2.xml ├── services-transport-parent ├── pom.xml ├── services-transport-jackson │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── io │ │ │ └── scalecube │ │ │ └── services │ │ │ └── transport │ │ │ └── jackson │ │ │ └── JacksonCodec.java │ │ └── resources │ │ └── META-INF │ │ └── services │ │ ├── io.scalecube.services.transport.api.DataCodec │ │ └── io.scalecube.services.transport.api.HeadersCodec └── services-transport-rsocket │ ├── pom.xml │ └── src │ └── main │ ├── java │ └── io │ │ └── scalecube │ │ └── services │ │ └── transport │ │ └── rsocket │ │ ├── DelegatedLoopResources.java │ │ ├── RSocketClientChannel.java │ │ ├── RSocketClientTransport.java │ │ ├── RSocketClientTransportFactory.java │ │ ├── RSocketImpl.java │ │ ├── RSocketServerTransport.java │ │ ├── RSocketServerTransportFactory.java │ │ ├── RSocketServiceAcceptor.java │ │ ├── RSocketServiceTransport.java │ │ ├── ReferenceCountUtil.java │ │ ├── ServiceMessageByteBufDataDecoder.java │ │ └── ServiceMessageCodec.java │ └── resources │ └── META-INF │ └── services │ └── io.scalecube.services.transport.api.ServiceMessageDataDecoder └── services ├── pom.xml └── src ├── main └── java │ └── io │ └── scalecube │ └── services │ ├── Injector.java │ ├── Microservices.java │ ├── ServiceScanner.java │ ├── files │ ├── AddFileRequest.java │ ├── FileService.java │ ├── FileServiceImpl.java │ └── FileStreamer.java │ └── registry │ └── ServiceRegistryImpl.java └── test └── java └── io └── scalecube └── services ├── AuthTest.java ├── ErrorFlowTest.java ├── ExecuteOnTest.java ├── PlaceholderQualifierTest.java ├── PrincipalImpl.java ├── ServiceCallLocalTest.java ├── ServiceCallRemoteTest.java ├── ServiceDiscoverySubscriberTest.java ├── ServiceLifecycleAnnotationsTest.java ├── ServiceLocalTest.java ├── ServiceRegistryTest.java ├── ServiceRemoteTest.java ├── ServiceScannerTest.java ├── StreamingServiceTest.java ├── TestRequests.java ├── auth └── ServiceRolesProcessorTest.java ├── registry └── ServiceRegistryImplTest.java ├── routings ├── RoutersTest.java ├── ServiceTagsExample.java └── sut │ ├── CanaryService.java │ ├── DummyRouter.java │ ├── GreetingServiceImplA.java │ ├── GreetingServiceImplB.java │ ├── RandomCollection.java │ ├── TagService.java │ └── WeightedRandomRouter.java ├── sut ├── AnnotationService.java ├── AnnotationServiceImpl.java ├── CoarseGrainedService.java ├── CoarseGrainedServiceImpl.java ├── EmptyGreetingRequest.java ├── EmptyGreetingResponse.java ├── GreetingRequest.java ├── GreetingResponse.java ├── GreetingService.java ├── GreetingServiceImpl.java ├── QuoteService.java ├── SimpleQuoteService.java └── security │ ├── CompositeSecuredService.java │ ├── CompositeSecuredServiceImpl.java │ ├── SecuredService.java │ └── SecuredServiceImpl.java └── transport └── rsocket ├── RSocketColocatedEventLoopTest.java └── RSocketServiceTransportTest.java /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | *.txt text 4 | *.sh text eol=lf 5 | *.html text eol=lf diff=html 6 | *.css text eol=lf 7 | *.js text eol=lf 8 | *.jpg -text 9 | *.pdf -text 10 | *.java text diff=java 11 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: 2 | - https://www.om2.com/ 3 | - https://exberry.io/ 4 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | template: | 2 | ## What’s Changed 3 | 4 | $CHANGES 5 | -------------------------------------------------------------------------------- /.github/workflows/branch-ci.yml: -------------------------------------------------------------------------------- 1 | name: Branch CI 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '.github/workflows/**' 7 | - '*.md' 8 | - '*.txt' 9 | branches-ignore: 10 | - 'release*' 11 | 12 | jobs: 13 | build: 14 | name: Branch CI 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/cache@v3 19 | with: 20 | path: ~/.m2/repository 21 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 22 | restore-keys: | 23 | ${{ runner.os }}-maven- 24 | - name: Set up JDK 25 | uses: actions/setup-java@v4 26 | with: 27 | java-version: 17 28 | distribution: zulu 29 | server-id: github 30 | server-username: GITHUB_ACTOR 31 | server-password: GITHUB_TOKEN 32 | - name: Maven Build 33 | run: mvn clean install -DskipTests=true -Dmaven.javadoc.skip=true -B -V 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.ORGANIZATION_TOKEN }} 36 | - name: Maven Verify 37 | run: mvn verify -B 38 | -------------------------------------------------------------------------------- /.github/workflows/pre-release-ci.yml: -------------------------------------------------------------------------------- 1 | name: Pre-release CI 2 | 3 | on: 4 | release: 5 | types: [prereleased] 6 | 7 | jobs: 8 | build: 9 | name: Pre-release CI 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/cache@v3 14 | with: 15 | path: ~/.m2/repository 16 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 17 | restore-keys: | 18 | ${{ runner.os }}-maven- 19 | - name: Set up Java for publishing to GitHub Packages 20 | uses: actions/setup-java@v4 21 | with: 22 | java-version: 17 23 | distribution: zulu 24 | server-id: github 25 | server-username: GITHUB_ACTOR 26 | server-password: GITHUB_TOKEN 27 | - name: Deploy pre-release version to GitHub Packages 28 | run: | 29 | pre_release_version=${{ github.event.release.tag_name }} 30 | echo Pre-release version $pre_release_version 31 | mvn versions:set -DnewVersion=$pre_release_version -DgenerateBackupPoms=false 32 | mvn versions:commit 33 | mvn clean deploy -Pdeploy2Github -B -V 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.ORGANIZATION_TOKEN }} 36 | - name: Set up Java for publishing to Maven Central Repository 37 | uses: actions/setup-java@v4 38 | with: 39 | java-version: 17 40 | distribution: zulu 41 | server-id: ossrh 42 | server-username: MAVEN_USERNAME 43 | server-password: MAVEN_PASSWORD 44 | gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} 45 | gpg-passphrase: MAVEN_GPG_PASSPHRASE 46 | - name: Deploy pre-release version to the Maven Central Repository 47 | run: | 48 | pre_release_version=${{ github.event.release.tag_name }} 49 | echo Pre-release version $pre_release_version 50 | mvn versions:set -DnewVersion=$pre_release_version -DgenerateBackupPoms=false 51 | mvn versions:commit 52 | mvn deploy -Pdeploy2Maven -DskipTests -B -V 53 | env: 54 | MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} 55 | MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} 56 | MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} 57 | - name: Rollback pre-release (remove tag) 58 | if: failure() 59 | run: git push origin :refs/tags/${{ github.event.release.tag_name }} 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.gitignore 3 | !.gitattributes 4 | !.github 5 | !.editorconfig 6 | !.*.yml 7 | !.env.example 8 | **/target/ 9 | *.iml 10 | **/logs/*.log 11 | *.db 12 | *.csv 13 | *.log 14 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | extends: default 2 | rules: 3 | document-start: 4 | present: false 5 | truthy: disable 6 | comments: 7 | min-spaces-from-content: 1 8 | line-length: 9 | max: 150 10 | braces: 11 | min-spaces-inside: 0 12 | max-spaces-inside: 0 13 | brackets: 14 | min-spaces-inside: 0 15 | max-spaces-inside: 0 16 | indentation: 17 | indent-sequences: consistent 18 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.32.0 2 | -------------------------------------------------------------------------------- /services-api/.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse project files 2 | .project 3 | .classpath 4 | .settings 5 | 6 | # IntelliJ IDEA project files and directories 7 | *.iml 8 | *.ipr 9 | *.iws 10 | .idea/ 11 | 12 | # Geany project file 13 | .geany 14 | 15 | # KDevelop project file and directory 16 | .kdev4/ 17 | *.kdev4 18 | 19 | # Build targets 20 | /target 21 | */target 22 | 23 | # Report directories 24 | /reports 25 | */reports 26 | 27 | # Mac-specific directory that no other operating system needs. 28 | .DS_Store 29 | 30 | # JVM crash logs 31 | hs_err_pid*.log -------------------------------------------------------------------------------- /services-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | io.scalecube 8 | scalecube-services-parent 9 | 2.13.2-SNAPSHOT 10 | 11 | 12 | scalecube-services-api 13 | 14 | 15 | 16 | io.projectreactor 17 | reactor-core 18 | 19 | 20 | org.slf4j 21 | slf4j-api 22 | 23 | 24 | 25 | io.scalecube 26 | scalecube-services-testlib 27 | ${project.version} 28 | test 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/CommunicationMode.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services; 2 | 3 | public enum CommunicationMode { 4 | 5 | /** 6 | * Corresponds to {@code Mono action(Request)} or {@code Mono action(Request)}. 7 | */ 8 | REQUEST_RESPONSE, 9 | 10 | /** Corresponds to {@code Flux action(Request)}. */ 11 | REQUEST_STREAM, 12 | 13 | /** Corresponds to {@code Flux action(Flux)}. */ 14 | REQUEST_CHANNEL; 15 | } 16 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/HexUtil.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services; 2 | 3 | public final class HexUtil { 4 | 5 | private HexUtil() { 6 | // Do not instantiate 7 | } 8 | 9 | /** 10 | * Converts bytes array to hex string. 11 | * 12 | * @param bytes bytes array 13 | * @return hex string 14 | */ 15 | public static String toHex(byte[] bytes) { 16 | StringBuilder sb = new StringBuilder(); 17 | for (byte b : bytes) { 18 | sb.append(String.format("%02x", b)); 19 | } 20 | return sb.toString(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/MaskUtil.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services; 2 | 3 | import java.util.Map; 4 | import java.util.Map.Entry; 5 | import java.util.UUID; 6 | import java.util.stream.Collectors; 7 | 8 | public final class MaskUtil { 9 | 10 | private MaskUtil() { 11 | // Do not instantiate 12 | } 13 | 14 | /** 15 | * Mask sensitive data by replacing part of string with an asterisk symbol. 16 | * 17 | * @param data sensitive data to be masked 18 | * @return masked data 19 | */ 20 | public static String mask(String data) { 21 | if (data == null || data.length() < 5) { 22 | return "*****"; 23 | } 24 | return data.replace(data.substring(2, data.length() - 2), "***"); 25 | } 26 | 27 | /** 28 | * Mask sensitive data by replacing part of string with an asterisk symbol. 29 | * 30 | * @param data sensitive data to be masked 31 | * @return masked data 32 | */ 33 | public static String mask(UUID data) { 34 | return data != null ? mask(data.toString()) : null; 35 | } 36 | 37 | /** 38 | * Mask sensitive data by replacing part of string with an asterisk symbol. 39 | * 40 | * @param map map with sensitive data to be masked 41 | * @return string representation 42 | */ 43 | public static String mask(Map map) { 44 | if (map == null || map.isEmpty()) { 45 | return String.valueOf(map); 46 | } 47 | return map.entrySet().stream() 48 | .collect(Collectors.toMap(Entry::getKey, entry -> mask(entry.getValue()))) 49 | .toString(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/ServiceProvider.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services; 2 | 3 | import java.util.Collection; 4 | 5 | @FunctionalInterface 6 | public interface ServiceProvider { 7 | 8 | Collection provide(ServiceCall call); 9 | } 10 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/annotations/AfterConstruct.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.annotations; 2 | 3 | import static java.lang.annotation.ElementType.METHOD; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * This annotation is used to mark the method which will be executed after constructing of service 12 | * and dependency injection is done.
13 | * NOTE: scalecube services doesn't support {@code javax.annotation.PostConstruct} since Java API 14 | * Specification for it has strict limitation for annotated method. 15 | */ 16 | @Documented 17 | @Retention(RUNTIME) 18 | @Target(METHOD) 19 | public @interface AfterConstruct {} 20 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/annotations/BeforeDestroy.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.annotations; 2 | 3 | import static java.lang.annotation.ElementType.METHOD; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * This annotation is used to mark the method which will be executed before shutdown of service. 12 | *
13 | * NOTE: scalecube services doesn't support {@code javax.annotation.PreDestroy} since Java API 14 | * Specification for it has strict limitation for annotated method. 15 | */ 16 | @Documented 17 | @Retention(RUNTIME) 18 | @Target(METHOD) 19 | public @interface BeforeDestroy {} 20 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/annotations/ExecuteOn.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.annotations; 2 | 3 | import static java.lang.annotation.ElementType.METHOD; 4 | import static java.lang.annotation.ElementType.TYPE; 5 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 6 | 7 | import java.lang.annotation.Documented; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.Target; 10 | 11 | /** 12 | * This annotation is used to mark that particular service method or all service methods will be 13 | * executed in the specified scheduler. 14 | */ 15 | @Documented 16 | @Target({METHOD, TYPE}) 17 | @Retention(RUNTIME) 18 | public @interface ExecuteOn { 19 | 20 | /** 21 | * Returns scheduler name. 22 | * 23 | * @return scheduler name 24 | */ 25 | String value(); 26 | } 27 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/annotations/Inject.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.annotations; 2 | 3 | import io.scalecube.services.routing.Router; 4 | import java.lang.annotation.Documented; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target(value = {ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD}) 11 | @Retention(value = RetentionPolicy.RUNTIME) 12 | @Documented 13 | public @interface Inject { 14 | 15 | /** 16 | * Router class to be used. 17 | * 18 | * @return router class 19 | */ 20 | Class router() default Router.class; 21 | } 22 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/annotations/RequestType.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) 10 | public @interface RequestType { 11 | 12 | /** 13 | * The class for the request. 14 | * 15 | * @return the request type 16 | */ 17 | Class value(); 18 | } 19 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/annotations/ResponseType.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) 10 | public @interface ResponseType { 11 | 12 | /** 13 | * The class for the response. 14 | * 15 | * @return the response type 16 | */ 17 | Class value(); 18 | } 19 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/annotations/RestMethod.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** Indicates that annotated method is a REST service method. */ 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) 11 | public @interface RestMethod { 12 | 13 | /** 14 | * Name of the HTTP method. Supported methods: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, 15 | * TRACE. 16 | * 17 | * @return http method 18 | */ 19 | String value(); 20 | } 21 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/annotations/Service.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** Indicates that annotated class is a scalecube service object. */ 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target(ElementType.TYPE) 11 | public @interface Service { 12 | 13 | /** 14 | * Name of the service. If not specified service class result will be used. 15 | * 16 | * @return service name 17 | */ 18 | String value() default ""; 19 | } 20 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/annotations/ServiceMethod.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** Indicates that annotated method is a scalecube service method. */ 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) 11 | public @interface ServiceMethod { 12 | 13 | /** 14 | * Name of the method. In case if not provided will be used the result of annotated method, but 15 | * with this annotation you can override method result so it won't depend on specific result in 16 | * the code. 17 | */ 18 | String value() default ""; 19 | } 20 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/annotations/Subscriber.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | public @interface Subscriber { 11 | 12 | /** 13 | * Event type to subscribe on. 14 | * 15 | * @return event type 16 | */ 17 | Class value() default Object.class; 18 | } 19 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/annotations/Tag.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Repeatable; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Key-value entry to provide additional information about service itself or method. This annotation 11 | * will be applied only in combination with {@link Service} or {@link ServiceMethod}. 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE}) 15 | @Repeatable(Tags.class) 16 | public @interface Tag { 17 | 18 | /** 19 | * Returns the key corresponding to this entry. 20 | * 21 | * @return key 22 | */ 23 | String key(); 24 | 25 | /** 26 | * Returns the value corresponding to this entry. 27 | * 28 | * @return value 29 | */ 30 | String value(); 31 | } 32 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/annotations/Tags.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Contains tags associated with {@link Service} or {@link ServiceMethod}. 10 | * 11 | * @see Tag 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE}) 15 | public @interface Tags { 16 | 17 | /** 18 | * Returns array of associated tags. 19 | * 20 | * @return array of tags 21 | */ 22 | Tag[] value(); 23 | } 24 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/api/ErrorData.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.api; 2 | 3 | import java.util.StringJoiner; 4 | 5 | public final class ErrorData { 6 | 7 | private int errorCode; 8 | private String errorMessage; 9 | 10 | /** 11 | * Constructor for de/serialization purpose. 12 | * 13 | * @deprecated exposed only for de/serialization purpose. 14 | */ 15 | @Deprecated 16 | public ErrorData() {} 17 | 18 | /** 19 | * Create an error data. 20 | * 21 | * @param errorCode the business error code 22 | * @param errorMessage the error message 23 | */ 24 | public ErrorData(int errorCode, String errorMessage) { 25 | this.errorCode = errorCode; 26 | this.errorMessage = errorMessage; 27 | } 28 | 29 | public int getErrorCode() { 30 | return errorCode; 31 | } 32 | 33 | public String getErrorMessage() { 34 | return errorMessage; 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return new StringJoiner(", ", ErrorData.class.getSimpleName() + "[", "]") 40 | .add("errorCode=" + errorCode) 41 | .add("errorMessage=" + errorMessage) 42 | .toString(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/api/Qualifier.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.api; 2 | 3 | /** Qualifier utility class. */ 4 | public final class Qualifier { 5 | 6 | public static final String DELIMITER = "/"; 7 | 8 | /** 9 | * Builds qualifier string out of given namespace and action. 10 | * 11 | * @param namespace qualifier namespace. 12 | * @param action qualifier action. 13 | * @return constructed qualifier. 14 | */ 15 | public static String asString(String namespace, String action) { 16 | return namespace + DELIMITER + action; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/auth/AllowedRole.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.auth; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Repeatable; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target({ElementType.METHOD, ElementType.TYPE}) 11 | @Repeatable(AllowedRoles.class) 12 | public @interface AllowedRole { 13 | 14 | /** 15 | * Role name. 16 | * 17 | * @return role name 18 | */ 19 | String name(); 20 | 21 | /** 22 | * Allowed permissions. 23 | * 24 | * @return permissions 25 | */ 26 | String[] permissions(); 27 | } 28 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/auth/AllowedRoles.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.auth; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.METHOD, ElementType.TYPE}) 10 | public @interface AllowedRoles { 11 | 12 | /** 13 | * Returns array of associated allowed roles. 14 | * 15 | * @return allowed roles 16 | */ 17 | AllowedRole[] value(); 18 | } 19 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/auth/Authenticator.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.auth; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | /** 6 | * Service authentication interface to handle authentication of clients to the service. Result of 7 | * authentication is abstract {@link Principal} with role and permissions. 8 | */ 9 | @FunctionalInterface 10 | public interface Authenticator { 11 | 12 | /** 13 | * Authenticates service clients by given credentials. 14 | * 15 | * @param credentials credentials 16 | * @return result 17 | */ 18 | Mono authenticate(byte[] credentials); 19 | } 20 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/auth/CredentialsSupplier.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.auth; 2 | 3 | import java.util.List; 4 | import reactor.core.publisher.Mono; 5 | 6 | /** 7 | * Supplier of credentials for authentication with remote service. Implementations can use {@code 8 | * byte[0]} to denote empty credentials. 9 | */ 10 | @FunctionalInterface 11 | public interface CredentialsSupplier { 12 | 13 | /** 14 | * Obtains credentials for the given service role. 15 | * 16 | * @param service logical service name 17 | * @param serviceRoles allowed roles on the service (optional) 18 | * @return credentials 19 | */ 20 | Mono credentials(String service, List serviceRoles); 21 | } 22 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/auth/CredentialsSuppliers.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.auth; 2 | 3 | import java.util.List; 4 | 5 | public class CredentialsSuppliers { 6 | 7 | private final CredentialsSupplier credentialsSupplier; 8 | 9 | public CredentialsSuppliers(CredentialsSupplier credentialsSupplier) { 10 | this.credentialsSupplier = credentialsSupplier; 11 | } 12 | 13 | public CredentialsSupplier forServiceRole(String serviceRole) { 14 | return (service, allowedRoles) -> 15 | credentialsSupplier.credentials(service, List.of(serviceRole)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/auth/Principal.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.auth; 2 | 3 | import java.util.Collection; 4 | 5 | /** 6 | * Represents an authenticated entity in the system, typically service identity or end user. This 7 | * interface provides methods to check roles and permissions for authorization purposes. 8 | */ 9 | public interface Principal { 10 | 11 | /** 12 | * Null principal, which signifies an unauthenticated entity. This principal does not have any 13 | * roles or permissions. 14 | */ 15 | Principal NULL_PRINCIPAL = 16 | new Principal() { 17 | @Override 18 | public String toString() { 19 | return "NULL_PRINCIPAL"; 20 | } 21 | }; 22 | 23 | /** 24 | * Returns the role associated with this principal. 25 | * 26 | * @return the role name, or {@code null} if no role is assigned 27 | */ 28 | default String role() { 29 | return null; 30 | } 31 | 32 | /** 33 | * Checks if this principal has the specified role. 34 | * 35 | * @param role the role to check 36 | * @return {@code true} if the principal has the specified role, {@code false} otherwise 37 | */ 38 | default boolean hasRole(String role) { 39 | return false; 40 | } 41 | 42 | /** 43 | * Returns the collection of permissions assigned to this principal. 44 | * 45 | * @return permissions, or {@code null} if no permissions are assigned 46 | */ 47 | default Collection permissions() { 48 | return null; 49 | } 50 | 51 | /** 52 | * Checks if this principal has the specified permission. 53 | * 54 | * @param permission the permission to check 55 | * @return {@code true} if the principal has the specified permission, {@code false} otherwise 56 | */ 57 | default boolean hasPermission(String permission) { 58 | return false; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/auth/PrincipalMapper.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.auth; 2 | 3 | import io.scalecube.services.RequestContext; 4 | import reactor.core.publisher.Mono; 5 | 6 | /** 7 | * Functional interface for transforming existing {@link Principal} from {@link RequestContext} to 8 | * the new {@link Principal}. This interface allows to modify or replace a current principal from 9 | * {@link RequestContext}, allowing for dynamic adjustments to authentication and authorization 10 | * logic based on the request context. 11 | * 12 | * @see Principal 13 | * @see RequestContext 14 | */ 15 | @FunctionalInterface 16 | public interface PrincipalMapper { 17 | 18 | /** 19 | * Maps current principal in the provided {@link RequestContext} to the new {@link Principal}. 20 | * 21 | * @param requestContext {@link RequestContext} containing the current principal 22 | * @return {@link Mono} emitting the new {@link Principal} after the transformation 23 | */ 24 | Mono map(RequestContext requestContext); 25 | } 26 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/auth/Secured.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.auth; 2 | 3 | import static java.lang.annotation.ElementType.METHOD; 4 | import static java.lang.annotation.ElementType.TYPE; 5 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 6 | 7 | import java.lang.annotation.Documented; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.Target; 10 | 11 | /** 12 | * This annotation is used to mark that service or service method is protected by authentication 13 | * mechanism. 14 | */ 15 | @Documented 16 | @Target({METHOD, TYPE}) 17 | @Retention(RUNTIME) 18 | public @interface Secured { 19 | 20 | /** 21 | * Returns whether service method must apply standard security check behavior. 22 | * 23 | * @return {@code true} if service must apply standard security check behavior, {@code false} 24 | * otherwise 25 | */ 26 | boolean deferSecured() default true; 27 | } 28 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/auth/ServicePrincipal.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.auth; 2 | 3 | import java.util.Collection; 4 | import java.util.Objects; 5 | import java.util.Set; 6 | import java.util.StringJoiner; 7 | 8 | /** 9 | * Service principal implementation of {@link Principal}. Provides role-based access control by 10 | * allowing checks against assigned roles and permissions. 11 | */ 12 | public class ServicePrincipal implements Principal { 13 | 14 | private final String role; 15 | private final Set permissions; 16 | 17 | /** 18 | * Constructor. 19 | * 20 | * @param role service role 21 | * @param permissions service permissions 22 | */ 23 | public ServicePrincipal(String role, Collection permissions) { 24 | this.role = role; 25 | this.permissions = permissions != null ? Set.copyOf(permissions) : null; 26 | } 27 | 28 | @Override 29 | public String role() { 30 | return role; 31 | } 32 | 33 | @Override 34 | public boolean hasRole(String role) { 35 | return Objects.equals(this.role, role); 36 | } 37 | 38 | @Override 39 | public Collection permissions() { 40 | return permissions; 41 | } 42 | 43 | @Override 44 | public boolean hasPermission(String permission) { 45 | return permissions != null && permissions.contains(permission); 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return new StringJoiner(", ", ServicePrincipal.class.getSimpleName() + "[", "]") 51 | .add("role='" + role + "'") 52 | .add("permissions=" + permissions) 53 | .toString(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/auth/ServiceRolesProcessor.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.auth; 2 | 3 | import io.scalecube.services.methods.ServiceRoleDefinition; 4 | import java.util.Collection; 5 | 6 | /** 7 | * Handler for processing of service roles which come out of registered services. Used as 8 | * post-construction step in bootstraping of services. 9 | */ 10 | @FunctionalInterface 11 | public interface ServiceRolesProcessor { 12 | 13 | /** 14 | * Function that does processing of service roles. 15 | * 16 | * @param values collection of {@link ServiceRoleDefinition} objects 17 | */ 18 | void process(Collection values); 19 | } 20 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/discovery/api/ServiceDiscovery.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.discovery.api; 2 | 3 | import io.scalecube.services.Address; 4 | import reactor.core.publisher.Flux; 5 | 6 | public interface ServiceDiscovery { 7 | 8 | /** 9 | * Return listening address. 10 | * 11 | * @return listening address, or null if {@link #start()} was not called. 12 | */ 13 | Address address(); 14 | 15 | /** 16 | * Function to subscribe and listen on service discovery stream. 17 | * 18 | * @return stream of {@code ServiceDiscoveryEvent} objects 19 | */ 20 | Flux listen(); 21 | 22 | /** Starts this instance. */ 23 | void start(); 24 | 25 | /** Stops this instance and release occupied resources. */ 26 | void shutdown(); 27 | } 28 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/discovery/api/ServiceDiscoveryEvent.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.discovery.api; 2 | 3 | import io.scalecube.services.ServiceEndpoint; 4 | import java.util.Objects; 5 | import java.util.StringJoiner; 6 | 7 | public final class ServiceDiscoveryEvent { 8 | 9 | public enum Type { 10 | ENDPOINT_ADDED, // service endpoint added 11 | ENDPOINT_LEAVING, // service endpoint is leaving 12 | ENDPOINT_REMOVED // service endpoint removed 13 | } 14 | 15 | private final Type type; 16 | private final ServiceEndpoint serviceEndpoint; 17 | 18 | /** 19 | * Constructor. 20 | * 21 | * @param type type; not null 22 | * @param serviceEndpoint service endpoint; not null 23 | */ 24 | private ServiceDiscoveryEvent(Type type, ServiceEndpoint serviceEndpoint) { 25 | this.type = Objects.requireNonNull(type, "ServiceDiscoveryEvent: type"); 26 | this.serviceEndpoint = 27 | Objects.requireNonNull(serviceEndpoint, "ServiceDiscoveryEvent: serviceEndpoint"); 28 | } 29 | 30 | public static ServiceDiscoveryEvent newEndpointAdded(ServiceEndpoint serviceEndpoint) { 31 | return new ServiceDiscoveryEvent(Type.ENDPOINT_ADDED, serviceEndpoint); 32 | } 33 | 34 | public static ServiceDiscoveryEvent newEndpointLeaving(ServiceEndpoint serviceEndpoint) { 35 | return new ServiceDiscoveryEvent(Type.ENDPOINT_LEAVING, serviceEndpoint); 36 | } 37 | 38 | public static ServiceDiscoveryEvent newEndpointRemoved(ServiceEndpoint serviceEndpoint) { 39 | return new ServiceDiscoveryEvent(Type.ENDPOINT_REMOVED, serviceEndpoint); 40 | } 41 | 42 | public Type type() { 43 | return type; 44 | } 45 | 46 | public ServiceEndpoint serviceEndpoint() { 47 | return serviceEndpoint; 48 | } 49 | 50 | public boolean isEndpointAdded() { 51 | return Type.ENDPOINT_ADDED == type; 52 | } 53 | 54 | public boolean isEndpointLeaving() { 55 | return Type.ENDPOINT_LEAVING == type; 56 | } 57 | 58 | public boolean isEndpointRemoved() { 59 | return Type.ENDPOINT_REMOVED == type; 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | return new StringJoiner(", ", ServiceDiscoveryEvent.class.getSimpleName() + "[", "]") 65 | .add("type=" + type) 66 | .add("serviceEndpoint=" + serviceEndpoint) 67 | .toString(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/discovery/api/ServiceDiscoveryFactory.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.discovery.api; 2 | 3 | import io.scalecube.services.ServiceEndpoint; 4 | 5 | @FunctionalInterface 6 | public interface ServiceDiscoveryFactory { 7 | 8 | ServiceDiscovery createServiceDiscovery(ServiceEndpoint serviceEndpoint); 9 | } 10 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/exceptions/BadRequestException.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.exceptions; 2 | 3 | public class BadRequestException extends ServiceException { 4 | 5 | public static final int ERROR_TYPE = 400; 6 | 7 | public BadRequestException(String message) { 8 | this(ERROR_TYPE, message); 9 | } 10 | 11 | public BadRequestException(int errorCode, String message) { 12 | super(errorCode, message); 13 | } 14 | 15 | public BadRequestException(Throwable cause) { 16 | super(ERROR_TYPE, cause); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/exceptions/ConnectionClosedException.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.exceptions; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public class ConnectionClosedException extends InternalServiceException { 6 | 7 | private static final Pattern GENERIC_CONNECTION_CLOSED = 8 | Pattern.compile( 9 | "^.*(?:connection.*(?:reset|closed|abort|broken)|broken.*pipe).*$", 10 | Pattern.CASE_INSENSITIVE); 11 | 12 | public ConnectionClosedException() { 13 | super("Connection closed"); 14 | } 15 | 16 | public ConnectionClosedException(Throwable cause) { 17 | super(cause); 18 | } 19 | 20 | public ConnectionClosedException(String message) { 21 | super(message); 22 | } 23 | 24 | /** 25 | * Returns {@code true} if connection has been aborted on a tcp level by verifying error message 26 | * and matching it against predefined pattern. 27 | * 28 | * @param th error 29 | * @return {@code true} if connection has been aborted on a tcp level 30 | */ 31 | public static boolean isConnectionClosed(Throwable th) { 32 | if (th instanceof ConnectionClosedException) { 33 | return true; 34 | } 35 | 36 | final String message = th != null ? th.getMessage() : null; 37 | 38 | return message != null && GENERIC_CONNECTION_CLOSED.matcher(message).matches(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/exceptions/DefaultErrorMapper.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.exceptions; 2 | 3 | import io.scalecube.services.api.ErrorData; 4 | import io.scalecube.services.api.ServiceMessage; 5 | import java.util.Optional; 6 | 7 | public final class DefaultErrorMapper 8 | implements ServiceClientErrorMapper, ServiceProviderErrorMapper { 9 | 10 | public static final DefaultErrorMapper INSTANCE = new DefaultErrorMapper(); 11 | 12 | private static final int DEFAULT_ERROR_CODE = 500; 13 | 14 | private DefaultErrorMapper() { 15 | // do not instantiate 16 | } 17 | 18 | @Override 19 | public Throwable toError(ServiceMessage message) { 20 | ErrorData data = message.data(); 21 | 22 | int errorType = message.errorType(); 23 | int errorCode = data.getErrorCode(); 24 | String errorMessage = data.getErrorMessage(); 25 | 26 | switch (errorType) { 27 | case BadRequestException.ERROR_TYPE: 28 | return new BadRequestException(errorCode, errorMessage); 29 | case UnauthorizedException.ERROR_TYPE: 30 | return new UnauthorizedException(errorCode, errorMessage); 31 | case ForbiddenException.ERROR_TYPE: 32 | return new ForbiddenException(errorCode, errorMessage); 33 | case ServiceUnavailableException.ERROR_TYPE: 34 | return new ServiceUnavailableException(errorCode, errorMessage); 35 | case InternalServiceException.ERROR_TYPE: 36 | return new InternalServiceException(errorCode, errorMessage); 37 | // Handle other types of Service Exceptions here 38 | default: 39 | return new InternalServiceException(errorCode, errorMessage); 40 | } 41 | } 42 | 43 | @Override 44 | public ServiceMessage toMessage(String qualifier, Throwable throwable) { 45 | int errorCode = DEFAULT_ERROR_CODE; 46 | int errorType = DEFAULT_ERROR_CODE; 47 | 48 | if (throwable instanceof ServiceException) { 49 | errorCode = ((ServiceException) throwable).errorCode(); 50 | if (throwable instanceof BadRequestException) { 51 | errorType = BadRequestException.ERROR_TYPE; 52 | } else if (throwable instanceof UnauthorizedException) { 53 | errorType = UnauthorizedException.ERROR_TYPE; 54 | } else if (throwable instanceof ForbiddenException) { 55 | errorType = ForbiddenException.ERROR_TYPE; 56 | } else if (throwable instanceof ServiceUnavailableException) { 57 | errorType = ServiceUnavailableException.ERROR_TYPE; 58 | } else if (throwable instanceof InternalServiceException) { 59 | errorType = InternalServiceException.ERROR_TYPE; 60 | } 61 | } 62 | 63 | String errorMessage = 64 | Optional.ofNullable(throwable.getMessage()).orElseGet(throwable::toString); 65 | 66 | return ServiceMessage.error(qualifier, errorType, errorCode, errorMessage); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/exceptions/ForbiddenException.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.exceptions; 2 | 3 | public class ForbiddenException extends ServiceException { 4 | 5 | public static final int ERROR_TYPE = 403; 6 | 7 | public ForbiddenException(int errorCode, String message) { 8 | super(errorCode, message); 9 | } 10 | 11 | public ForbiddenException(String message) { 12 | super(ERROR_TYPE, message); 13 | } 14 | 15 | public ForbiddenException(Throwable cause) { 16 | super(ERROR_TYPE, cause); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/exceptions/InternalServiceException.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.exceptions; 2 | 3 | public class InternalServiceException extends ServiceException { 4 | 5 | public static final int ERROR_TYPE = 500; 6 | 7 | public InternalServiceException(int errorCode, String message) { 8 | super(errorCode, message); 9 | } 10 | 11 | public InternalServiceException(Throwable cause) { 12 | super(ERROR_TYPE, cause); 13 | } 14 | 15 | public InternalServiceException(String message) { 16 | super(ERROR_TYPE, message); 17 | } 18 | 19 | public InternalServiceException(String message, Throwable cause) { 20 | super(ERROR_TYPE, message, cause); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/exceptions/MessageCodecException.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.exceptions; 2 | 3 | public class MessageCodecException extends InternalServiceException { 4 | 5 | public MessageCodecException(String message, Throwable cause) { 6 | super(message, cause); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/exceptions/ServiceClientErrorMapper.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.exceptions; 2 | 3 | import io.scalecube.services.api.ServiceMessage; 4 | 5 | @FunctionalInterface 6 | public interface ServiceClientErrorMapper { 7 | 8 | /** 9 | * Maps service message to an exception. 10 | * 11 | * @param message the message to map to an exception. 12 | * @return an exception mapped from qualifier and error data. 13 | */ 14 | Throwable toError(ServiceMessage message); 15 | } 16 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/exceptions/ServiceException.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.exceptions; 2 | 3 | import java.util.StringJoiner; 4 | 5 | public abstract class ServiceException extends RuntimeException { 6 | 7 | private final int errorCode; 8 | 9 | public ServiceException(int errorCode, String message) { 10 | super(message); 11 | this.errorCode = errorCode; 12 | } 13 | 14 | public ServiceException(int errorCode, Throwable cause) { 15 | super(cause.getMessage(), cause); 16 | this.errorCode = errorCode; 17 | } 18 | 19 | public ServiceException(int errorCode, String message, Throwable cause) { 20 | super(message, cause); 21 | this.errorCode = errorCode; 22 | } 23 | 24 | @Override 25 | public synchronized Throwable fillInStackTrace() { 26 | return this; 27 | } 28 | 29 | public int errorCode() { 30 | return errorCode; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return new StringJoiner(", ", getClass().getSimpleName() + "[", "]") 36 | .add("errorCode=" + errorCode) 37 | .add("errorMessage='" + getMessage() + "'") 38 | .toString(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/exceptions/ServiceProviderErrorMapper.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.exceptions; 2 | 3 | import io.scalecube.services.api.ServiceMessage; 4 | 5 | @FunctionalInterface 6 | public interface ServiceProviderErrorMapper { 7 | 8 | /** 9 | * Maps an exception to a {@link ServiceMessage}. 10 | * 11 | * @param qualifier original qualifier. 12 | * @param throwable the exception to map to a service message. 13 | * @return a service message mapped from the supplied exception. 14 | */ 15 | ServiceMessage toMessage(String qualifier, Throwable throwable); 16 | } 17 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/exceptions/ServiceUnavailableException.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.exceptions; 2 | 3 | public class ServiceUnavailableException extends ServiceException { 4 | 5 | public static final int ERROR_TYPE = 503; 6 | 7 | public ServiceUnavailableException(String message) { 8 | this(ERROR_TYPE, message); 9 | } 10 | 11 | public ServiceUnavailableException(int errorCode, String message) { 12 | super(errorCode, message); 13 | } 14 | 15 | public ServiceUnavailableException(Throwable cause) { 16 | super(ERROR_TYPE, cause); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/exceptions/UnauthorizedException.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.exceptions; 2 | 3 | public class UnauthorizedException extends ServiceException { 4 | 5 | public static final int ERROR_TYPE = 401; 6 | 7 | public UnauthorizedException(int errorCode, String message) { 8 | super(errorCode, message); 9 | } 10 | 11 | public UnauthorizedException(String message) { 12 | super(ERROR_TYPE, message); 13 | } 14 | 15 | public UnauthorizedException(Throwable cause) { 16 | super(ERROR_TYPE, cause); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/gateway/Gateway.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway; 2 | 3 | import io.scalecube.services.Address; 4 | import io.scalecube.services.ServiceCall; 5 | import io.scalecube.services.registry.api.ServiceRegistry; 6 | 7 | public interface Gateway { 8 | 9 | /** 10 | * Returns gateway id. 11 | * 12 | * @return gateway id 13 | */ 14 | String id(); 15 | 16 | /** 17 | * Returns gateway address. 18 | * 19 | * @return gateway listen address 20 | */ 21 | Address address(); 22 | 23 | /** 24 | * Starts gateway. 25 | * 26 | * @param call {@link ServiceCall} instance 27 | * @param serviceRegistry {@link ServiceRegistry} instance 28 | * @return gateway instance 29 | */ 30 | Gateway start(ServiceCall call, ServiceRegistry serviceRegistry); 31 | 32 | /** Stops gateway. */ 33 | void stop(); 34 | } 35 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/methods/ServiceRoleDefinition.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.methods; 2 | 3 | import java.util.Collection; 4 | import java.util.Set; 5 | import java.util.StringJoiner; 6 | 7 | public class ServiceRoleDefinition { 8 | 9 | private final String role; 10 | private final Set permissions; 11 | 12 | /** 13 | * Constructor. 14 | * 15 | * @param role service role 16 | * @param permissions service permissions 17 | */ 18 | public ServiceRoleDefinition(String role, Collection permissions) { 19 | this.role = role; 20 | this.permissions = permissions != null ? Set.copyOf(permissions) : null; 21 | } 22 | 23 | public String role() { 24 | return role; 25 | } 26 | 27 | public Collection permissions() { 28 | return permissions; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return new StringJoiner(", ", ServiceRoleDefinition.class.getSimpleName() + "[", "]") 34 | .add("role='" + role + "'") 35 | .add("permissions=" + permissions) 36 | .toString(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/routing/RandomServiceRouter.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.routing; 2 | 3 | import io.scalecube.services.ServiceReference; 4 | import io.scalecube.services.api.ServiceMessage; 5 | import io.scalecube.services.registry.api.ServiceRegistry; 6 | import java.util.List; 7 | import java.util.Optional; 8 | import java.util.concurrent.ThreadLocalRandom; 9 | 10 | public class RandomServiceRouter implements Router { 11 | 12 | @Override 13 | public Optional route(ServiceRegistry serviceRegistry, ServiceMessage request) { 14 | List serviceInstances = serviceRegistry.lookupService(request); 15 | if (serviceInstances.isEmpty()) { 16 | return Optional.empty(); 17 | } else if (serviceInstances.size() == 1) { 18 | return Optional.of(serviceInstances.get(0)); 19 | } else { 20 | int index = ThreadLocalRandom.current().nextInt((serviceInstances.size())); 21 | return Optional.of(serviceInstances.get(index)); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/routing/RoundRobinServiceRouter.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.routing; 2 | 3 | import io.scalecube.services.ServiceReference; 4 | import io.scalecube.services.api.ServiceMessage; 5 | import io.scalecube.services.registry.api.ServiceRegistry; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Optional; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | import java.util.concurrent.atomic.AtomicInteger; 11 | 12 | public class RoundRobinServiceRouter implements Router { 13 | 14 | private final Map counterByServiceName = new ConcurrentHashMap<>(); 15 | 16 | @Override 17 | public Optional route(ServiceRegistry serviceRegistry, ServiceMessage request) { 18 | List serviceInstances = serviceRegistry.lookupService(request); 19 | if (serviceInstances.isEmpty()) { 20 | return Optional.empty(); 21 | } else if (serviceInstances.size() == 1) { 22 | return Optional.of(serviceInstances.get(0)); 23 | } else { 24 | AtomicInteger counter = 25 | counterByServiceName.computeIfAbsent(request.qualifier(), or -> new AtomicInteger()); 26 | int index = (counter.incrementAndGet() & Integer.MAX_VALUE) % serviceInstances.size(); 27 | return Optional.of(serviceInstances.get(index)); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/routing/Router.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.routing; 2 | 3 | import io.scalecube.services.ServiceReference; 4 | import io.scalecube.services.api.ServiceMessage; 5 | import io.scalecube.services.registry.api.ServiceRegistry; 6 | import java.util.Optional; 7 | import reactor.util.annotation.Nullable; 8 | 9 | @FunctionalInterface 10 | public interface Router { 11 | 12 | /** 13 | * Returns suitable service references for a given request message. 14 | * 15 | * @param serviceRegistry service registry (optional) 16 | * @param request service message 17 | * @return service instance (optional) 18 | */ 19 | Optional route( 20 | @Nullable ServiceRegistry serviceRegistry, ServiceMessage request); 21 | } 22 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/routing/Routers.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.routing; 2 | 3 | import java.util.concurrent.ConcurrentHashMap; 4 | import reactor.core.Exceptions; 5 | 6 | public final class Routers { 7 | 8 | private static final ConcurrentHashMap, Router> routers = 9 | new ConcurrentHashMap<>(); 10 | 11 | private Routers() {} 12 | 13 | /** 14 | * Get router instance by a given router class. The class should have a default constructor. 15 | * Otherwise no router can be created 16 | * 17 | * @param routerType the type of the Router. 18 | * @return instance of the Router. 19 | */ 20 | public static Router getRouter(Class routerType) { 21 | return routers.computeIfAbsent(routerType, Routers::create); 22 | } 23 | 24 | private static Router create(Class routerType) { 25 | try { 26 | return routerType.newInstance(); 27 | } catch (Exception ex) { 28 | throw Exceptions.propagate(ex); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/transport/api/ClientChannel.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.transport.api; 2 | 3 | import io.scalecube.services.api.ServiceMessage; 4 | import java.lang.reflect.Type; 5 | import org.reactivestreams.Publisher; 6 | import reactor.core.publisher.Flux; 7 | import reactor.core.publisher.Mono; 8 | 9 | public interface ClientChannel { 10 | 11 | Mono requestResponse(ServiceMessage message, Type responseType); 12 | 13 | Flux requestStream(ServiceMessage message, Type responseType); 14 | 15 | Flux requestChannel(Publisher publisher, Type responseType); 16 | } 17 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/transport/api/ClientTransport.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.transport.api; 2 | 3 | import io.scalecube.services.ServiceReference; 4 | 5 | public interface ClientTransport extends AutoCloseable { 6 | 7 | /** 8 | * Creates {@link ClientChannel} for communication with remote service endpoint. 9 | * 10 | * @param serviceReference target serviceReference 11 | * @return {@link ClientChannel} instance 12 | */ 13 | ClientChannel create(ServiceReference serviceReference); 14 | } 15 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/transport/api/DataCodec.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.transport.api; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | import java.lang.reflect.Type; 7 | import java.util.Collection; 8 | import java.util.Map; 9 | import java.util.Objects; 10 | import java.util.ServiceLoader; 11 | import java.util.Set; 12 | import java.util.function.Function; 13 | import java.util.stream.Collectors; 14 | import java.util.stream.StreamSupport; 15 | 16 | public interface DataCodec { 17 | 18 | Map INSTANCES = 19 | StreamSupport.stream(ServiceLoader.load(DataCodec.class).spliterator(), false) 20 | .collect(Collectors.toMap(DataCodec::contentType, Function.identity())); 21 | 22 | static Collection getAllInstances() { 23 | return INSTANCES.values(); 24 | } 25 | 26 | static Set getAllContentTypes() { 27 | return getAllInstances().stream().map(DataCodec::contentType).collect(Collectors.toSet()); 28 | } 29 | 30 | /** 31 | * Returns {@link DataCodec} by given {@code contentType}. 32 | * 33 | * @param contentType contentType (required) 34 | * @return {@link DataCodec} by given {@code contentType} (or throws IllegalArgumentException is 35 | * thrown if not exist) 36 | */ 37 | static DataCodec getInstance(String contentType) { 38 | Objects.requireNonNull(contentType, "[getInstance] contentType"); 39 | DataCodec dataCodec = INSTANCES.get(contentType); 40 | Objects.requireNonNull( 41 | dataCodec, "[getInstance] dataCodec not found for '" + contentType + "'"); 42 | return dataCodec; 43 | } 44 | 45 | String contentType(); 46 | 47 | void encode(OutputStream stream, Object value) throws IOException; 48 | 49 | Object decode(InputStream stream, Type type) throws IOException; 50 | } 51 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/transport/api/HeadersCodec.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.transport.api; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | import java.util.Map; 7 | import java.util.ServiceLoader; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | import java.util.stream.StreamSupport; 10 | 11 | public interface HeadersCodec { 12 | 13 | HeadersCodec DEFAULT_INSTANCE = new JdkCodec(); 14 | 15 | Map INSTANCES = new ConcurrentHashMap<>(); 16 | 17 | static HeadersCodec getInstance(String contentType) { 18 | return INSTANCES.computeIfAbsent(contentType, HeadersCodec::loadInstance); 19 | } 20 | 21 | /** 22 | * Returns {@link HeadersCodec} by given {@code contentType}. 23 | * 24 | * @param contentType contentType (required) 25 | * @return {@link HeadersCodec} by given {@code contentType} (or throws IllegalArgumentException 26 | * is thrown if not exist) 27 | */ 28 | static HeadersCodec loadInstance(String contentType) { 29 | return StreamSupport.stream(ServiceLoader.load(HeadersCodec.class).spliterator(), false) 30 | .filter(codec -> codec.contentType().equalsIgnoreCase(contentType)) 31 | .findFirst() 32 | .orElseThrow( 33 | () -> 34 | new IllegalArgumentException( 35 | "HeadersCodec for '" + contentType + "' not configured")); 36 | } 37 | 38 | String contentType(); 39 | 40 | void encode(OutputStream stream, Map headers) throws IOException; 41 | 42 | Map decode(InputStream stream) throws IOException; 43 | } 44 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/transport/api/ServerTransport.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.transport.api; 2 | 3 | import io.scalecube.services.Address; 4 | 5 | public interface ServerTransport { 6 | 7 | /** 8 | * Returns listening server address. 9 | * 10 | * @return listening server address 11 | */ 12 | Address address(); 13 | 14 | /** 15 | * Starts this {@link ServerTransport} instance. 16 | * 17 | * @return started {@link ServerTransport} instance 18 | */ 19 | ServerTransport bind(); 20 | 21 | /** Stops this instance and release allocated resources. */ 22 | void stop(); 23 | } 24 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/transport/api/ServiceMessageDataDecoder.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.transport.api; 2 | 3 | import io.scalecube.services.api.ServiceMessage; 4 | import java.util.ServiceLoader; 5 | import java.util.function.BiFunction; 6 | import java.util.stream.StreamSupport; 7 | 8 | @FunctionalInterface 9 | public interface ServiceMessageDataDecoder 10 | extends BiFunction, ServiceMessage> { 11 | 12 | ServiceMessageDataDecoder INSTANCE = 13 | StreamSupport.stream(ServiceLoader.load(ServiceMessageDataDecoder.class).spliterator(), false) 14 | .findFirst() 15 | .orElse(null); 16 | } 17 | -------------------------------------------------------------------------------- /services-api/src/main/java/io/scalecube/services/transport/api/ServiceTransport.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.transport.api; 2 | 3 | import io.scalecube.services.registry.api.ServiceRegistry; 4 | 5 | public interface ServiceTransport { 6 | 7 | /** 8 | * Provider for {@link ClientTransport}. 9 | * 10 | * @return {@link ClientTransport} instance 11 | */ 12 | ClientTransport clientTransport(); 13 | 14 | /** 15 | * Provider for {@link ServerTransport}. 16 | * 17 | * @param serviceRegistry {@link ServiceRegistry} instance 18 | * @return {@link ServerTransport} instance 19 | */ 20 | ServerTransport serverTransport(ServiceRegistry serviceRegistry); 21 | 22 | /** 23 | * Starts {@link ServiceTransport} instance. 24 | * 25 | * @return started {@link ServiceTransport} instance 26 | */ 27 | ServiceTransport start(); 28 | 29 | /** Stops transport and release allocated transport resources. */ 30 | void stop(); 31 | } 32 | -------------------------------------------------------------------------------- /services-api/src/main/resources/META-INF/services/io.scalecube.services.transport.api.DataCodec: -------------------------------------------------------------------------------- 1 | io.scalecube.services.transport.api.JdkCodec -------------------------------------------------------------------------------- /services-api/src/test/java/io/scalecube/services/methods/PrincipalImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.methods; 2 | 3 | import io.scalecube.services.auth.Principal; 4 | import java.util.List; 5 | import java.util.Objects; 6 | import java.util.StringJoiner; 7 | 8 | public record PrincipalImpl(String role, List permissions) implements Principal { 9 | 10 | @Override 11 | public boolean hasRole(String role) { 12 | return Objects.equals(this.role, role); 13 | } 14 | 15 | @Override 16 | public boolean hasPermission(String permission) { 17 | return permissions != null && permissions.contains(permission); 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return new StringJoiner(", ", PrincipalImpl.class.getSimpleName() + "[", "]") 23 | .add("role='" + role + "'") 24 | .add("permissions=" + permissions) 25 | .toString(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /services-api/src/test/java/io/scalecube/services/methods/StubService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.methods; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import reactor.core.publisher.Flux; 6 | import reactor.core.publisher.Mono; 7 | 8 | @Service(StubService.NAMESPACE) 9 | public interface StubService { 10 | 11 | String NAMESPACE = "v1/stubService"; 12 | 13 | // Invocation methods 14 | 15 | @ServiceMethod 16 | Mono invokeOneReturnsNull(); 17 | 18 | @ServiceMethod 19 | Flux invokeManyReturnsNull(); 20 | 21 | @ServiceMethod 22 | Flux invokeBidirectionalReturnsNull(Flux request); 23 | 24 | @ServiceMethod 25 | Mono invokeOneThrowsException(); 26 | 27 | @ServiceMethod 28 | Flux invokeManyThrowsException(); 29 | 30 | @ServiceMethod 31 | Flux invokeBidirectionalThrowsException(Flux request); 32 | 33 | @ServiceMethod("hello/:foo/dynamic/:bar") 34 | Mono invokeDynamicQualifier(); 35 | 36 | // Secured methods 37 | 38 | @ServiceMethod 39 | Mono invokeWithAuthContext(); 40 | 41 | // Services secured by code in method body 42 | 43 | @ServiceMethod 44 | Mono invokeWithRoleOrPermissions(); 45 | 46 | // Services secured by annotations in method body 47 | 48 | @ServiceMethod 49 | Mono invokeWithAllowedRoleAnnotation(); 50 | } 51 | -------------------------------------------------------------------------------- /services-api/src/test/java/io/scalecube/services/transport/api/JdkCodecTest.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.transport.api; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.io.ByteArrayInputStream; 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.io.Serializable; 9 | import java.util.Arrays; 10 | import java.util.Objects; 11 | import java.util.stream.Stream; 12 | import org.junit.jupiter.params.ParameterizedTest; 13 | import org.junit.jupiter.params.provider.MethodSource; 14 | 15 | class JdkCodecTest { 16 | 17 | private final DataCodec codec = new JdkCodec(); 18 | 19 | @ParameterizedTest 20 | @MethodSource("provider") 21 | void test(Object body) throws IOException { 22 | Object decoded = writeAndRead(body); 23 | assertEquals(body, decoded); 24 | } 25 | 26 | static Stream provider() { 27 | return Stream.of("hello", Arrays.asList(1, 2, 3), new Greeting("joe")); 28 | } 29 | 30 | private Object writeAndRead(Object body) throws IOException { 31 | try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { 32 | codec.encode(os, body); 33 | byte[] bytes = os.toByteArray(); 34 | try (ByteArrayInputStream is = new ByteArrayInputStream(bytes)) { 35 | return codec.decode(is, body.getClass()); 36 | } 37 | } 38 | } 39 | 40 | static class Greeting implements Serializable { 41 | 42 | private final String name; 43 | 44 | Greeting(String name) { 45 | this.name = name; 46 | } 47 | 48 | public String getName() { 49 | return name; 50 | } 51 | 52 | @Override 53 | public boolean equals(Object o) { 54 | if (this == o) { 55 | return true; 56 | } 57 | if (o == null || getClass() != o.getClass()) { 58 | return false; 59 | } 60 | Greeting greeting = (Greeting) o; 61 | return Objects.equals(name, greeting.name); 62 | } 63 | 64 | @Override 65 | public int hashCode() { 66 | return Objects.hash(name); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /services-api/src/test/java/io/scalecube/services/transport/api/JdkHeadersCodecTest.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.transport.api; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.io.ByteArrayInputStream; 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.util.Collections; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | import java.util.stream.Stream; 12 | import org.junit.jupiter.params.ParameterizedTest; 13 | import org.junit.jupiter.params.provider.MethodSource; 14 | 15 | class JdkHeadersCodecTest { 16 | 17 | private final HeadersCodec codec = new JdkCodec(); 18 | 19 | @ParameterizedTest 20 | @MethodSource("provider") 21 | void test(Map headers) throws IOException { 22 | Map decoded = writeAndRead(headers); 23 | assertEquals(headers, decoded); 24 | } 25 | 26 | static Stream> provider() { 27 | Map sampleMap = new HashMap<>(); 28 | sampleMap.put("header", "value"); 29 | sampleMap.put("test", "3"); 30 | 31 | return Stream.of( 32 | sampleMap, 33 | Collections.singletonMap("header", String.valueOf(Integer.MAX_VALUE)), 34 | Collections.emptyMap(), 35 | Collections.singletonMap("", ""), 36 | Collections.singletonMap("header", ""), 37 | Collections.singletonMap("", "value")); 38 | } 39 | 40 | private Map writeAndRead(Map headers) throws IOException { 41 | try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { 42 | codec.encode(os, headers); 43 | byte[] bytes = os.toByteArray(); 44 | try (ByteArrayInputStream is = new ByteArrayInputStream(bytes)) { 45 | return codec.decode(is); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /services-discovery/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | io.scalecube 6 | scalecube-services-parent 7 | 2.13.2-SNAPSHOT 8 | 9 | 10 | scalecube-services-discovery 11 | 12 | 13 | 14 | io.scalecube 15 | scalecube-services-api 16 | ${project.version} 17 | 18 | 19 | io.scalecube 20 | scalecube-cluster 21 | 22 | 23 | 24 | io.scalecube 25 | scalecube-services-testlib 26 | ${project.version} 27 | test 28 | 29 | 30 | io.scalecube 31 | scalecube-codec-jackson 32 | test 33 | 34 | 35 | io.scalecube 36 | scalecube-transport-netty 37 | test 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /services-examples/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.scalecube 7 | scalecube-services-parent 8 | 2.13.2-SNAPSHOT 9 | 10 | 11 | scalecube-services-examples 12 | 13 | 14 | 15 | io.scalecube 16 | scalecube-services 17 | ${project.version} 18 | 19 | 20 | 21 | io.scalecube 22 | scalecube-services-transport-rsocket 23 | ${project.version} 24 | 25 | 26 | io.scalecube 27 | scalecube-services-transport-jackson 28 | ${project.version} 29 | 30 | 31 | 32 | io.scalecube 33 | scalecube-services-discovery 34 | ${project.version} 35 | 36 | 37 | io.scalecube 38 | scalecube-transport-netty 39 | 40 | 41 | 42 | it.unimi.dsi 43 | fastutil 44 | 8.1.1 45 | 46 | 47 | 48 | org.slf4j 49 | slf4j-api 50 | 51 | 52 | org.apache.logging.log4j 53 | log4j-slf4j-impl 54 | 55 | 56 | org.apache.logging.log4j 57 | log4j-core 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/BenchmarkService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples; 2 | 3 | import static io.scalecube.services.examples.BenchmarkService.NAMESPACE; 4 | 5 | import io.scalecube.services.annotations.RequestType; 6 | import io.scalecube.services.annotations.ResponseType; 7 | import io.scalecube.services.annotations.Service; 8 | import io.scalecube.services.annotations.ServiceMethod; 9 | import io.scalecube.services.api.ServiceMessage; 10 | import reactor.core.publisher.Flux; 11 | import reactor.core.publisher.Mono; 12 | 13 | @Service(NAMESPACE) 14 | public interface BenchmarkService { 15 | 16 | String NAMESPACE = "benchmarks"; 17 | 18 | String SERVICE_RECV_TIME = "service-recv-time"; 19 | String SERVICE_SEND_TIME = "service-send-time"; 20 | String CLIENT_RECV_TIME = "client-recv-time"; 21 | String CLIENT_SEND_TIME = "client-send-time"; 22 | 23 | @RequestType(String.class) 24 | @ResponseType(String.class) 25 | @ServiceMethod 26 | Mono requestVoid(ServiceMessage request); 27 | 28 | @RequestType(String.class) 29 | @ResponseType(String.class) 30 | @ServiceMethod 31 | Mono one(ServiceMessage message); 32 | 33 | @RequestType(String.class) 34 | @ResponseType(String.class) 35 | @ServiceMethod 36 | Mono failure(ServiceMessage message); 37 | 38 | @RequestType(String.class) 39 | @ResponseType(String.class) 40 | @ServiceMethod 41 | Flux infiniteStream(ServiceMessage message); 42 | } 43 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/BenchmarkServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples; 2 | 3 | import io.scalecube.services.api.ServiceMessage; 4 | import io.scalecube.services.api.ServiceMessage.Builder; 5 | import java.util.concurrent.Callable; 6 | import reactor.core.publisher.Flux; 7 | import reactor.core.publisher.Mono; 8 | 9 | public class BenchmarkServiceImpl implements BenchmarkService { 10 | 11 | @Override 12 | public Mono requestVoid(ServiceMessage request) { 13 | return Mono.empty(); 14 | } 15 | 16 | @Override 17 | public Mono one(ServiceMessage message) { 18 | Callable callable = 19 | () -> { 20 | long value = System.currentTimeMillis(); 21 | return ServiceMessage.from(message) 22 | .header(SERVICE_RECV_TIME, value) 23 | .header(SERVICE_SEND_TIME, value) 24 | .data("hello") 25 | .build(); 26 | }; 27 | return Mono.fromCallable(callable); 28 | } 29 | 30 | @Override 31 | public Mono failure(ServiceMessage message) { 32 | return Mono.defer(() -> Mono.error(new RuntimeException("General failure"))); 33 | } 34 | 35 | @Override 36 | public Flux infiniteStream(ServiceMessage message) { 37 | Builder builder = ServiceMessage.from(message); 38 | return Flux.range(0, Integer.MAX_VALUE) 39 | .map(i -> builder.header(SERVICE_SEND_TIME, System.currentTimeMillis()).build()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/EchoRequest.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples; 2 | 3 | public class EchoRequest { 4 | 5 | private String name; 6 | private long frequencyMillis; 7 | 8 | public String getName() { 9 | return name; 10 | } 11 | 12 | public void setName(String name) { 13 | this.name = name; 14 | } 15 | 16 | public long getFrequencyMillis() { 17 | return frequencyMillis; 18 | } 19 | 20 | public void setFrequencyMillis(long frequencyMillis) { 21 | this.frequencyMillis = frequencyMillis; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | final StringBuilder sb = new StringBuilder("EchoRequest{"); 27 | sb.append("name='").append(name).append('\''); 28 | sb.append(", frequencyMillis=").append(frequencyMillis); 29 | sb.append('}'); 30 | return sb.toString(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/EmptyGreetingRequest.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples; 2 | 3 | public class EmptyGreetingRequest {} 4 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/EmptyGreetingResponse.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples; 2 | 3 | public class EmptyGreetingResponse {} 4 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/GreetingRequest.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples; 2 | 3 | public class GreetingRequest { 4 | 5 | private String text; 6 | 7 | public GreetingRequest() {} 8 | 9 | public GreetingRequest(String text) { 10 | this.text = text; 11 | } 12 | 13 | public String getText() { 14 | return text; 15 | } 16 | 17 | public GreetingRequest setText(String text) { 18 | this.text = text; 19 | return this; 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | final StringBuilder sb = new StringBuilder("GreetingRequest{"); 25 | sb.append("text='").append(text).append('\''); 26 | sb.append('}'); 27 | return sb.toString(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/GreetingResponse.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples; 2 | 3 | public class GreetingResponse { 4 | 5 | private String text; 6 | 7 | public GreetingResponse() {} 8 | 9 | public GreetingResponse(String text) { 10 | this.text = text; 11 | } 12 | 13 | public String getText() { 14 | return text; 15 | } 16 | 17 | @Override 18 | public boolean equals(Object obj) { 19 | if (this == obj) { 20 | return true; 21 | } 22 | if (obj == null || getClass() != obj.getClass()) { 23 | return false; 24 | } 25 | 26 | GreetingResponse that = (GreetingResponse) obj; 27 | 28 | return text != null ? text.equals(that.text) : that.text == null; 29 | } 30 | 31 | @Override 32 | public int hashCode() { 33 | return text != null ? text.hashCode() : 0; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | final StringBuilder sb = new StringBuilder("GreetingResponse{"); 39 | sb.append("text='").append(text).append('\''); 40 | sb.append('}'); 41 | return sb.toString(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/GreetingService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples; 2 | 3 | import io.scalecube.services.annotations.RequestType; 4 | import io.scalecube.services.annotations.ResponseType; 5 | import io.scalecube.services.annotations.Service; 6 | import io.scalecube.services.annotations.ServiceMethod; 7 | import io.scalecube.services.api.ServiceMessage; 8 | import java.util.List; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | 12 | @Service(GreetingService.NAMESPACE) 13 | public interface GreetingService { 14 | 15 | String NAMESPACE = "greeting"; 16 | 17 | @ServiceMethod("one") 18 | Mono one(String name); 19 | 20 | @ServiceMethod("many") 21 | Flux many(String name); 22 | 23 | @ServiceMethod("manyStream") 24 | Flux manyStream(Long cnt); 25 | 26 | @ServiceMethod("failing/one") 27 | Mono failingOne(String name); 28 | 29 | @ServiceMethod("failing/many") 30 | Flux failingMany(String name); 31 | 32 | @ServiceMethod("pojo/one") 33 | Mono pojoOne(GreetingRequest request); 34 | 35 | @ServiceMethod("pojo/list") 36 | Mono> pojoList(GreetingRequest request); 37 | 38 | @ServiceMethod("pojo/many") 39 | Flux pojoMany(GreetingRequest request); 40 | 41 | @ServiceMethod("empty/one") 42 | Mono emptyOne(String name); 43 | 44 | @ServiceMethod("empty/many") 45 | Flux emptyMany(String name); 46 | 47 | @ServiceMethod("never/one") 48 | Mono neverOne(String name); 49 | 50 | @ServiceMethod("delay/one") 51 | Mono delayOne(String name); 52 | 53 | @ServiceMethod("delay/many") 54 | Flux delayMany(String name); 55 | 56 | @ServiceMethod("empty/pojo") 57 | Mono emptyGreeting(EmptyGreetingRequest request); 58 | 59 | @ServiceMethod("empty/wrappedPojo") 60 | @RequestType(EmptyGreetingRequest.class) 61 | @ResponseType(EmptyGreetingResponse.class) 62 | Mono emptyGreetingMessage(ServiceMessage request); 63 | 64 | @ServiceMethod("hello/:someVar") 65 | Mono helloDynamicQualifier(Long value); 66 | } 67 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/StreamRequest.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples; 2 | 3 | public class StreamRequest { 4 | 5 | private long intervalMillis; 6 | private int messagesPerInterval; 7 | 8 | public long getIntervalMillis() { 9 | return intervalMillis; 10 | } 11 | 12 | public StreamRequest setIntervalMillis(long intervalMillis) { 13 | this.intervalMillis = intervalMillis; 14 | return this; 15 | } 16 | 17 | public int getMessagesPerInterval() { 18 | return messagesPerInterval; 19 | } 20 | 21 | public StreamRequest setMessagesPerInterval(int messagesPerInterval) { 22 | this.messagesPerInterval = messagesPerInterval; 23 | return this; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | final StringBuilder sb = new StringBuilder("StreamRequest{"); 29 | sb.append("intervalMillis=").append(intervalMillis); 30 | sb.append(", messagesPerInterval=").append(messagesPerInterval); 31 | sb.append('}'); 32 | return sb.toString(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/codecs/Example1.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.codecs; 2 | 3 | import io.scalecube.services.Address; 4 | import io.scalecube.services.Microservices; 5 | import io.scalecube.services.Microservices.Context; 6 | import io.scalecube.services.discovery.ScalecubeServiceDiscovery; 7 | import io.scalecube.services.examples.helloworld.service.GreetingServiceImpl; 8 | import io.scalecube.services.examples.helloworld.service.api.GreetingsService; 9 | import io.scalecube.services.transport.rsocket.RSocketServiceTransport; 10 | import io.scalecube.transport.netty.websocket.WebsocketTransportFactory; 11 | 12 | public class Example1 { 13 | 14 | public static final String JSON = "application/json"; 15 | public static final String PROTOSTUFF = "application/protostuff"; 16 | public static final String OCTET_STREAM = "application/octet-stream"; 17 | 18 | /** 19 | * Start the example. 20 | * 21 | * @param args ignored 22 | */ 23 | public static void main(String[] args) { 24 | // ScaleCube Node with no members 25 | Microservices seed = 26 | Microservices.start( 27 | new Context() 28 | .discovery( 29 | serviceEndpoint -> 30 | new ScalecubeServiceDiscovery() 31 | .transport(cfg -> cfg.transportFactory(new WebsocketTransportFactory())) 32 | .options(opts -> opts.metadata(serviceEndpoint))) 33 | .transport(RSocketServiceTransport::new) 34 | // .defaultContentType(PROTOSTUFF) // set explicit default data format 35 | ); 36 | 37 | final Address seedAddress = seed.discoveryAddress(); 38 | 39 | // Construct a ScaleCube node which joins the cluster hosting the Greeting Service 40 | Microservices ms = 41 | Microservices.start( 42 | new Context() 43 | .discovery( 44 | endpoint -> 45 | new ScalecubeServiceDiscovery() 46 | .transport(cfg -> cfg.transportFactory(new WebsocketTransportFactory())) 47 | .options(opts -> opts.metadata(endpoint)) 48 | .membership(cfg -> cfg.seedMembers(seedAddress.toString()))) 49 | .transport(RSocketServiceTransport::new) 50 | .services(new GreetingServiceImpl())); 51 | 52 | seed.call() 53 | .api(GreetingsService.class) 54 | .sayHello("joe (on default dataFormat PROTOSTUFF)") 55 | .subscribe(consumer -> System.out.println(consumer.message())); 56 | 57 | seed.call() 58 | .contentType(JSON) 59 | .api(GreetingsService.class) 60 | .sayHello("alice (on JSON dataFormat)") 61 | .subscribe(consumer -> System.out.println(consumer.message())); 62 | 63 | seed.call() 64 | .contentType(OCTET_STREAM) 65 | .api(GreetingsService.class) 66 | .sayHello("bob (on java native Serializable/Externalizable dataFormat)") 67 | .subscribe(consumer -> System.out.println(consumer.message())); 68 | 69 | seed.close(); 70 | ms.close(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/exceptions/ServiceA.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.exceptions; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import reactor.core.publisher.Mono; 6 | 7 | @Service("example.serviceA") 8 | public interface ServiceA { 9 | 10 | @ServiceMethod 11 | Mono doStuff(int input); 12 | } 13 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/exceptions/ServiceAClientErrorMapper.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.exceptions; 2 | 3 | import io.scalecube.services.api.ErrorData; 4 | import io.scalecube.services.api.ServiceMessage; 5 | import io.scalecube.services.exceptions.DefaultErrorMapper; 6 | import io.scalecube.services.exceptions.ServiceClientErrorMapper; 7 | 8 | public class ServiceAClientErrorMapper implements ServiceClientErrorMapper { 9 | 10 | @Override 11 | public Throwable toError(ServiceMessage message) { 12 | ErrorData data = message.data(); 13 | 14 | if (data.getErrorCode() == 42) { 15 | // implement service mapping logic 16 | return new ServiceAException(data.getErrorMessage()); 17 | } else { 18 | // or delegate it to default mapper 19 | return DefaultErrorMapper.INSTANCE.toError(message); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/exceptions/ServiceAException.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.exceptions; 2 | 3 | public class ServiceAException extends Exception { 4 | 5 | private static final int ERROR_CODE = 42; 6 | 7 | ServiceAException(String message) { 8 | super(message); 9 | } 10 | 11 | public int code() { 12 | return ERROR_CODE; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/exceptions/ServiceAImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.exceptions; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | public class ServiceAImpl implements ServiceA { 6 | 7 | @Override 8 | public Mono doStuff(int input) { 9 | if (input == 0) { 10 | return Mono.error(new ServiceAException("Input is zero")); 11 | } 12 | 13 | return Mono.just(input); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/exceptions/ServiceAProviderErrorMapper.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.exceptions; 2 | 3 | import io.scalecube.services.api.ServiceMessage; 4 | import io.scalecube.services.exceptions.BadRequestException; 5 | import io.scalecube.services.exceptions.DefaultErrorMapper; 6 | import io.scalecube.services.exceptions.ServiceProviderErrorMapper; 7 | 8 | public class ServiceAProviderErrorMapper implements ServiceProviderErrorMapper { 9 | 10 | @Override 11 | public ServiceMessage toMessage(String qualifier, Throwable throwable) { 12 | // implement service mapping logic 13 | if (throwable instanceof ServiceAException) { 14 | ServiceAException e = (ServiceAException) throwable; 15 | return ServiceMessage.error( 16 | qualifier, BadRequestException.ERROR_TYPE, e.code(), e.getMessage()); 17 | } 18 | 19 | // or delegate it to default mapper 20 | return DefaultErrorMapper.INSTANCE.toMessage(qualifier, throwable); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/exceptions/ServiceB.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.exceptions; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import reactor.core.publisher.Mono; 6 | 7 | @Service("example.serviceB") 8 | public interface ServiceB { 9 | 10 | @ServiceMethod 11 | Mono doAnotherStuff(int input); 12 | } 13 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/exceptions/ServiceBImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.exceptions; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | public class ServiceBImpl implements ServiceB { 6 | 7 | private ServiceA serviceA; 8 | 9 | ServiceBImpl(ServiceA serviceA) { 10 | this.serviceA = serviceA; 11 | } 12 | 13 | @Override 14 | public Mono doAnotherStuff(int input) { 15 | return serviceA 16 | .doStuff(input) 17 | .doOnError( 18 | ServiceAException.class, 19 | th -> 20 | System.err.println( 21 | "Service client mapper is defined for for ServiceA, " 22 | + "so exact ServiceAException instance can be caught! -> " 23 | + th)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/helloworld/Example1.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.helloworld; 2 | 3 | import io.scalecube.services.Address; 4 | import io.scalecube.services.Microservices; 5 | import io.scalecube.services.Microservices.Context; 6 | import io.scalecube.services.discovery.ScalecubeServiceDiscovery; 7 | import io.scalecube.services.examples.helloworld.service.GreetingServiceImpl; 8 | import io.scalecube.services.examples.helloworld.service.api.GreetingsService; 9 | import io.scalecube.services.transport.rsocket.RSocketServiceTransport; 10 | import io.scalecube.transport.netty.websocket.WebsocketTransportFactory; 11 | 12 | /** 13 | * The Hello World project is a time-honored tradition in computer programming. It is a simple 14 | * exercise that gets you started when learning something new. Let’s get started with ScaleCube! 15 | * 16 | *

the example starts 2 cluster member nodes. 1. seed is a member node and holds no services of 17 | * its own. 2. The microservices variable is a member that joins seed member and 18 | * provision GreetingService instance. 19 | */ 20 | public class Example1 { 21 | 22 | /** 23 | * Start the example. 24 | * 25 | * @param args ignored 26 | */ 27 | public static void main(String[] args) { 28 | // ScaleCube Node with no members 29 | Microservices seed = 30 | Microservices.start( 31 | new Context() 32 | .discovery( 33 | serviceEndpoint -> 34 | new ScalecubeServiceDiscovery() 35 | .transport(cfg -> cfg.transportFactory(new WebsocketTransportFactory())) 36 | .options(opts -> opts.metadata(serviceEndpoint))) 37 | .transport(RSocketServiceTransport::new)); 38 | 39 | final Address seedAddress = seed.discoveryAddress(); 40 | 41 | // Construct a ScaleCube node which joins the cluster hosting the Greeting Service 42 | Microservices ms = 43 | Microservices.start( 44 | new Context() 45 | .discovery( 46 | endpoint -> 47 | new ScalecubeServiceDiscovery() 48 | .transport(cfg -> cfg.transportFactory(new WebsocketTransportFactory())) 49 | .options(opts -> opts.metadata(endpoint)) 50 | .membership(cfg -> cfg.seedMembers(seedAddress.toString()))) 51 | .transport(RSocketServiceTransport::new) 52 | .services(new GreetingServiceImpl())); 53 | 54 | // Create service proxy 55 | GreetingsService service = seed.call().api(GreetingsService.class); 56 | 57 | // Execute the services and subscribe to service events 58 | service.sayHello("joe").subscribe(consumer -> System.out.println(consumer.message())); 59 | 60 | seed.close(); 61 | ms.close(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/helloworld/Example3.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.helloworld; 2 | 3 | import io.scalecube.services.Address; 4 | import io.scalecube.services.Microservices; 5 | import io.scalecube.services.Microservices.Context; 6 | import io.scalecube.services.discovery.ScalecubeServiceDiscovery; 7 | import io.scalecube.services.examples.helloworld.service.BidiGreetingImpl; 8 | import io.scalecube.services.examples.helloworld.service.api.BidiGreetingService; 9 | import io.scalecube.services.transport.rsocket.RSocketServiceTransport; 10 | import io.scalecube.transport.netty.websocket.WebsocketTransportFactory; 11 | import reactor.core.publisher.Flux; 12 | 13 | /** 14 | * The Hello World project is a time-honored tradition in computer programming. It is a simple 15 | * exercise that gets you started when learning something new. Let’s get started with ScaleCube! 16 | * 17 | *

the example starts 2 cluster member nodes. 1. seed is a member node and holds no services of 18 | * its own. 2. The microservices variable is a member that joins seed member and 19 | * provision BidiGreetingService instance. 20 | */ 21 | public class Example3 { 22 | 23 | /** 24 | * Start the example. 25 | * 26 | * @param args ignored 27 | */ 28 | public static void main(String[] args) { 29 | // ScaleCube Node with no members 30 | Microservices seed = 31 | Microservices.start( 32 | new Context() 33 | .discovery( 34 | serviceEndpoint -> 35 | new ScalecubeServiceDiscovery() 36 | .transport(cfg -> cfg.transportFactory(new WebsocketTransportFactory())) 37 | .options(opts -> opts.metadata(serviceEndpoint))) 38 | .transport(RSocketServiceTransport::new)); 39 | 40 | final Address seedAddress = seed.discoveryAddress(); 41 | 42 | // Construct a ScaleCube node which joins the cluster hosting the Greeting Service 43 | Microservices ms = 44 | Microservices.start( 45 | new Context() 46 | .discovery( 47 | endpoint -> 48 | new ScalecubeServiceDiscovery() 49 | .transport(cfg -> cfg.transportFactory(new WebsocketTransportFactory())) 50 | .options(opts -> opts.metadata(endpoint)) 51 | .membership(cfg -> cfg.seedMembers(seedAddress.toString()))) 52 | .transport(RSocketServiceTransport::new) 53 | .services(new BidiGreetingImpl())); 54 | 55 | // Create service proxy 56 | BidiGreetingService service = seed.call().api(BidiGreetingService.class); 57 | 58 | // Execute the services and subscribe to service events 59 | service 60 | .greeting(Flux.fromArray(new String[] {"joe", "dan", "roni"})) 61 | .doOnNext(System.out::println) 62 | .blockLast(); 63 | 64 | seed.close(); 65 | ms.close(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/helloworld/service/BidiGreetingImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.helloworld.service; 2 | 3 | import io.scalecube.services.examples.helloworld.service.api.BidiGreetingService; 4 | import reactor.core.publisher.Flux; 5 | 6 | /** 7 | * Greeting is an act of communication in which human beings intentionally make their presence known 8 | * to each other, to show attention to, and to suggest a type of relationship (usually cordial) or 9 | * social status (formal or informal) between individuals or groups of people coming in contact with 10 | * each other. 11 | */ 12 | public class BidiGreetingImpl implements BidiGreetingService { 13 | 14 | /** 15 | * Call this method to be greeted by the this ScaleCube service. 16 | * 17 | * @param requestStream incoming stream of names to greet. 18 | * @return service greeting 19 | */ 20 | @Override 21 | public Flux greeting(Flux requestStream) { 22 | return requestStream.map(next -> "greeting: " + next); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/helloworld/service/GreetingServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.helloworld.service; 2 | 3 | import io.scalecube.services.examples.helloworld.service.api.Greeting; 4 | import io.scalecube.services.examples.helloworld.service.api.GreetingsService; 5 | import reactor.core.publisher.Mono; 6 | 7 | public class GreetingServiceImpl implements GreetingsService { 8 | @Override 9 | public Mono sayHello(String name) { 10 | return Mono.just(new Greeting("Nice to meet you " + name + " and welcome to ScaleCube")); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/helloworld/service/api/BidiGreetingService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.helloworld.service.api; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import reactor.core.publisher.Flux; 6 | 7 | @Service("BidiGreeting") 8 | public interface BidiGreetingService { 9 | 10 | @ServiceMethod() 11 | public Flux greeting(Flux request); 12 | } 13 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/helloworld/service/api/Greeting.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.helloworld.service.api; 2 | 3 | import java.io.Serializable; 4 | 5 | public class Greeting implements Serializable { 6 | 7 | String message; 8 | 9 | public Greeting() {} 10 | 11 | public Greeting(String message) { 12 | this.message = message; 13 | } 14 | 15 | public String message() { 16 | return message; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/helloworld/service/api/GreetingsService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.helloworld.service.api; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import reactor.core.publisher.Mono; 6 | 7 | /** 8 | * Greeting is an act of communication in which human beings intentionally make their presence known 9 | * to each other, to show attention to, and to suggest a type of relationship (usually cordial) or 10 | * social status (formal or informal) between individuals or groups of people coming in contact with 11 | * each other. 12 | */ 13 | @Service("io.scalecube.Greetings") 14 | public interface GreetingsService { 15 | /** 16 | * Call this method to be greeted by the this ScaleCube service. 17 | * 18 | * @param name name of the caller 19 | * @return service greeting 20 | */ 21 | @ServiceMethod("sayHello") 22 | Mono sayHello(String name); 23 | } 24 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/services/Example1.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.services; 2 | 3 | import io.scalecube.services.Address; 4 | import io.scalecube.services.Microservices; 5 | import io.scalecube.services.Microservices.Context; 6 | import io.scalecube.services.discovery.ScalecubeServiceDiscovery; 7 | import io.scalecube.services.transport.rsocket.RSocketServiceTransport; 8 | import io.scalecube.transport.netty.websocket.WebsocketTransportFactory; 9 | import reactor.core.scheduler.Schedulers; 10 | 11 | public class Example1 { 12 | 13 | /** 14 | * Main method. 15 | * 16 | * @param args - program arguments 17 | */ 18 | public static void main(String[] args) { 19 | Microservices gateway = 20 | Microservices.start( 21 | new Context() 22 | .discovery( 23 | serviceEndpoint -> 24 | new ScalecubeServiceDiscovery() 25 | .transport(cfg -> cfg.transportFactory(new WebsocketTransportFactory())) 26 | .options(opts -> opts.metadata(serviceEndpoint))) 27 | .transport(RSocketServiceTransport::new)); 28 | 29 | final Address gatewayAddress = gateway.discoveryAddress(); 30 | 31 | Microservices service2Node = 32 | Microservices.start( 33 | new Context() 34 | .discovery( 35 | endpoint -> 36 | new ScalecubeServiceDiscovery() 37 | .transport(cfg -> cfg.transportFactory(new WebsocketTransportFactory())) 38 | .options(opts -> opts.metadata(endpoint)) 39 | .membership(cfg -> cfg.seedMembers(gatewayAddress.toString()))) 40 | .transport(RSocketServiceTransport::new) 41 | .services(new Service2Impl())); 42 | 43 | Microservices service1Node = 44 | Microservices.start( 45 | new Context() 46 | .discovery( 47 | endpoint -> 48 | new ScalecubeServiceDiscovery() 49 | .transport(cfg -> cfg.transportFactory(new WebsocketTransportFactory())) 50 | .options(opts -> opts.metadata(endpoint)) 51 | .membership(cfg -> cfg.seedMembers(gatewayAddress.toString()))) 52 | .transport(RSocketServiceTransport::new) 53 | .services(new Service1Impl())); 54 | 55 | gateway 56 | .call() 57 | .api(Service1.class) 58 | .manyDelay(100) 59 | .publishOn(Schedulers.parallel()) 60 | .take(10) 61 | .log("receive |") 62 | .collectList() 63 | .log("complete |") 64 | .block(); 65 | 66 | gateway.close(); 67 | service1Node.close(); 68 | service2Node.close(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/services/Example2.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.services; 2 | 3 | import io.scalecube.services.Address; 4 | import io.scalecube.services.Microservices; 5 | import io.scalecube.services.Microservices.Context; 6 | import io.scalecube.services.discovery.ScalecubeServiceDiscovery; 7 | import io.scalecube.services.transport.rsocket.RSocketServiceTransport; 8 | import io.scalecube.transport.netty.websocket.WebsocketTransportFactory; 9 | import reactor.core.scheduler.Schedulers; 10 | 11 | public class Example2 { 12 | 13 | /** 14 | * Main method. 15 | * 16 | * @param args - program arguments 17 | */ 18 | public static void main(String[] args) { 19 | Microservices gateway = 20 | Microservices.start( 21 | new Context() 22 | .discovery( 23 | serviceEndpoint -> 24 | new ScalecubeServiceDiscovery() 25 | .transport(cfg -> cfg.transportFactory(new WebsocketTransportFactory())) 26 | .options(opts -> opts.metadata(serviceEndpoint))) 27 | .transport(RSocketServiceTransport::new)); 28 | 29 | final Address gatewayAddress = gateway.discoveryAddress(); 30 | 31 | Microservices service2Node = 32 | Microservices.start( 33 | new Context() 34 | .discovery( 35 | endpoint -> 36 | new ScalecubeServiceDiscovery() 37 | .transport(cfg -> cfg.transportFactory(new WebsocketTransportFactory())) 38 | .options(opts -> opts.metadata(endpoint)) 39 | .membership(cfg -> cfg.seedMembers(gatewayAddress.toString()))) 40 | .transport(RSocketServiceTransport::new) 41 | .services(new Service2Impl())); 42 | 43 | Microservices service1Node = 44 | Microservices.start( 45 | new Context() 46 | .discovery( 47 | endpoint -> 48 | new ScalecubeServiceDiscovery() 49 | .transport(cfg -> cfg.transportFactory(new WebsocketTransportFactory())) 50 | .options(opts -> opts.metadata(endpoint)) 51 | .membership(cfg -> cfg.seedMembers(gatewayAddress.toString()))) 52 | .transport(RSocketServiceTransport::new) 53 | .services(new Service1Impl())); 54 | 55 | gateway 56 | .call() 57 | .api(Service1.class) 58 | .remoteCallThenManyDelay(100) 59 | .publishOn(Schedulers.parallel()) 60 | .take(10) 61 | .log("receive |") 62 | .collectList() 63 | .log("complete |") 64 | .block(); 65 | 66 | gateway.close(); 67 | service1Node.close(); 68 | service2Node.close(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/services/Service1.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.services; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import reactor.core.publisher.Flux; 6 | 7 | @Service 8 | public interface Service1 { 9 | 10 | @ServiceMethod 11 | Flux manyDelay(long interval); 12 | 13 | @ServiceMethod 14 | Flux remoteCallThenManyDelay(long interval); 15 | } 16 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/services/Service1Impl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.services; 2 | 3 | import io.scalecube.services.annotations.Inject; 4 | import java.time.Instant; 5 | import java.time.LocalDateTime; 6 | import java.time.ZoneId; 7 | import java.util.concurrent.locks.LockSupport; 8 | import reactor.core.publisher.Flux; 9 | import reactor.core.publisher.FluxSink; 10 | import reactor.core.scheduler.Schedulers; 11 | 12 | public class Service1Impl implements Service1 { 13 | 14 | private static final long SLEEP_PERIOD_NS = 10000; 15 | 16 | @Inject private Service2 remoteService; 17 | 18 | @Override 19 | public Flux manyDelay(long interval) { 20 | return Flux.create(sink -> sink.onRequest(r -> onRequest(sink, interval))) 21 | .subscribeOn(Schedulers.parallel()) 22 | .log("manyDelay |"); 23 | } 24 | 25 | @Override 26 | public Flux remoteCallThenManyDelay(long interval) { 27 | return remoteService 28 | .oneDelay(interval) 29 | .publishOn(Schedulers.parallel()) 30 | .log("remoteCall |") 31 | .then( 32 | remoteService.oneDelay(interval).publishOn(Schedulers.parallel()).log("remoteCall2 |")) 33 | .flatMapMany( 34 | i -> 35 | Flux.create(sink -> sink.onRequest(r -> onRequest(sink, interval))) 36 | .subscribeOn(Schedulers.parallel()) 37 | .log("manyInner |")) 38 | .log("rcManyDelay |"); 39 | } 40 | 41 | private void onRequest(FluxSink sink, long interval) { 42 | long lastPublished = System.currentTimeMillis(); 43 | 44 | while (!sink.isCancelled() && sink.requestedFromDownstream() > 0) { 45 | long now = System.currentTimeMillis(); 46 | 47 | if (sink.requestedFromDownstream() > 0 && now - lastPublished > interval) { 48 | lastPublished = now; 49 | sink.next(toResponse(now)); 50 | continue; 51 | } 52 | 53 | LockSupport.parkNanos(SLEEP_PERIOD_NS); 54 | } 55 | } 56 | 57 | private String toResponse(long now) { 58 | String currentThread = Thread.currentThread().getName(); 59 | final LocalDateTime time = 60 | LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneId.systemDefault()); 61 | return "|" + currentThread + "| response: " + time; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/services/Service2.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.services; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import reactor.core.publisher.Mono; 6 | 7 | @Service 8 | public interface Service2 { 9 | 10 | @ServiceMethod 11 | Mono oneDelay(long interval); 12 | } 13 | -------------------------------------------------------------------------------- /services-examples/src/main/java/io/scalecube/services/examples/services/Service2Impl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.examples.services; 2 | 3 | import java.time.Instant; 4 | import java.time.LocalDateTime; 5 | import java.time.ZoneId; 6 | import java.util.concurrent.atomic.AtomicBoolean; 7 | import java.util.concurrent.locks.LockSupport; 8 | import reactor.core.publisher.Mono; 9 | import reactor.core.publisher.MonoSink; 10 | import reactor.core.scheduler.Schedulers; 11 | 12 | class Service2Impl implements Service2 { 13 | 14 | private static final long SLEEP_PERIOD_NS = 10000; 15 | 16 | @Override 17 | public Mono oneDelay(long interval) { 18 | return Mono.create(sink -> doWork(sink, interval)) 19 | .subscribeOn(Schedulers.parallel()) 20 | .log("oneDelay |"); 21 | } 22 | 23 | private void doWork(MonoSink sink, long interval) { 24 | AtomicBoolean isActive = new AtomicBoolean(true); 25 | sink.onCancel(() -> isActive.set(false)); 26 | sink.onDispose(() -> isActive.set(false)); 27 | 28 | long started = System.currentTimeMillis(); 29 | 30 | sink.onRequest( 31 | r -> { 32 | while (isActive.get()) { 33 | long now = System.currentTimeMillis(); 34 | 35 | if (now - started > interval) { 36 | sink.success(toResponse(now)); 37 | return; 38 | } 39 | 40 | LockSupport.parkNanos(SLEEP_PERIOD_NS); 41 | } 42 | }); 43 | } 44 | 45 | private String toResponse(long now) { 46 | String currentThread = Thread.currentThread().getName(); 47 | final LocalDateTime time = 48 | LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneId.systemDefault()); 49 | return "|" + currentThread + "| response: " + time; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /services-gateway/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.scalecube 7 | scalecube-services-parent 8 | 2.13.2-SNAPSHOT 9 | 10 | 11 | scalecube-services-gateway 12 | 13 | 14 | 15 | io.scalecube 16 | scalecube-services-api 17 | ${project.parent.version} 18 | 19 | 20 | 21 | org.slf4j 22 | slf4j-api 23 | 24 | 25 | 26 | io.projectreactor.netty 27 | reactor-netty 28 | 29 | 30 | 31 | com.fasterxml.jackson.datatype 32 | jackson-datatype-jsr310 33 | 34 | 35 | com.fasterxml.jackson.core 36 | jackson-core 37 | 38 | 39 | com.fasterxml.jackson.core 40 | jackson-databind 41 | 42 | 43 | 44 | 45 | io.scalecube 46 | scalecube-services-testlib 47 | ${project.version} 48 | test 49 | 50 | 51 | io.scalecube 52 | scalecube-services-examples 53 | ${project.parent.version} 54 | test 55 | 56 | 57 | io.scalecube 58 | scalecube-services-discovery 59 | ${project.parent.version} 60 | test 61 | 62 | 63 | io.scalecube 64 | scalecube-services-transport-rsocket 65 | ${project.parent.version} 66 | test 67 | 68 | 69 | io.scalecube 70 | scalecube-services-transport-jackson 71 | ${project.parent.version} 72 | test 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /services-gateway/src/main/java/io/scalecube/services/gateway/GatewaySession.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway; 2 | 3 | import java.util.Map; 4 | 5 | public interface GatewaySession { 6 | 7 | /** 8 | * Session id representation to be unique per client session. 9 | * 10 | * @return session id 11 | */ 12 | long sessionId(); 13 | 14 | /** 15 | * Returns headers associated with session. 16 | * 17 | * @return headers map 18 | */ 19 | Map headers(); 20 | } 21 | -------------------------------------------------------------------------------- /services-gateway/src/main/java/io/scalecube/services/gateway/ReferenceCountUtil.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway; 2 | 3 | import io.netty.util.ReferenceCounted; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | public final class ReferenceCountUtil { 8 | 9 | private static final Logger LOGGER = LoggerFactory.getLogger(ReferenceCountUtil.class); 10 | 11 | private ReferenceCountUtil() { 12 | // Do not instantiate 13 | } 14 | 15 | /** 16 | * Try to release input object iff it's instance is of {@link ReferenceCounted} type and its 17 | * refCount greater than zero. 18 | * 19 | * @return true if msg release taken place 20 | */ 21 | public static boolean safestRelease(Object msg) { 22 | try { 23 | return (msg instanceof ReferenceCounted) 24 | && ((ReferenceCounted) msg).refCnt() > 0 25 | && ((ReferenceCounted) msg).release(); 26 | } catch (Throwable t) { 27 | LOGGER.warn("Failed to release reference counted object: {}, cause: {}", msg, t.toString()); 28 | return false; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /services-gateway/src/main/java/io/scalecube/services/gateway/client/GatewayClientCodec.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.client; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.scalecube.services.api.ServiceMessage; 5 | import io.scalecube.services.exceptions.MessageCodecException; 6 | import java.lang.reflect.Type; 7 | 8 | public interface GatewayClientCodec { 9 | 10 | /** 11 | * Data decoder function. 12 | * 13 | * @param message client message. 14 | * @param dataType data type class. 15 | * @return client message object. 16 | * @throws MessageCodecException in case if data decoding fails. 17 | */ 18 | default ServiceMessage decodeData(ServiceMessage message, Type dataType) 19 | throws MessageCodecException { 20 | return ServiceMessageCodec.decodeData(message, dataType); 21 | } 22 | 23 | /** 24 | * Encodes {@link ServiceMessage}. 25 | * 26 | * @param message message to encode 27 | * @return encoded message 28 | */ 29 | ByteBuf encode(ServiceMessage message); 30 | 31 | /** 32 | * Decodes {@link ServiceMessage} object from {@link ByteBuf}. 33 | * 34 | * @param byteBuf message to decode 35 | * @return decoded message represented by {@link ServiceMessage} 36 | */ 37 | ServiceMessage decode(ByteBuf byteBuf); 38 | } 39 | -------------------------------------------------------------------------------- /services-gateway/src/main/java/io/scalecube/services/gateway/client/ServiceMessageCodec.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.client; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufInputStream; 5 | import io.scalecube.services.api.ErrorData; 6 | import io.scalecube.services.api.ServiceMessage; 7 | import io.scalecube.services.exceptions.MessageCodecException; 8 | import io.scalecube.services.transport.api.DataCodec; 9 | import java.lang.reflect.Type; 10 | 11 | public final class ServiceMessageCodec { 12 | 13 | private ServiceMessageCodec() {} 14 | 15 | /** 16 | * Decode message. 17 | * 18 | * @param message the original message (with {@link ByteBuf} data) 19 | * @param dataType the type of the data. 20 | * @return a new Service message that upon {@link ServiceMessage#data()} returns the actual data 21 | * (of type data type) 22 | * @throws MessageCodecException when decode fails 23 | */ 24 | public static ServiceMessage decodeData(ServiceMessage message, Type dataType) 25 | throws MessageCodecException { 26 | if (dataType == null 27 | || !message.hasData(ByteBuf.class) 28 | || ((ByteBuf) message.data()).readableBytes() == 0 29 | || ByteBuf.class == dataType) { 30 | return message; 31 | } 32 | 33 | Object data; 34 | Type targetType = message.isError() ? ErrorData.class : dataType; 35 | 36 | ByteBuf dataBuffer = message.data(); 37 | try (ByteBufInputStream inputStream = new ByteBufInputStream(dataBuffer, true)) { 38 | DataCodec dataCodec = DataCodec.getInstance(message.dataFormatOrDefault()); 39 | data = dataCodec.decode(inputStream, targetType); 40 | } catch (Throwable ex) { 41 | throw new MessageCodecException("Failed to decode service message data", ex); 42 | } 43 | 44 | return ServiceMessage.from(message).data(data).build(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /services-gateway/src/main/java/io/scalecube/services/gateway/client/http/HttpGatewayClientCodec.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.client.http; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufAllocator; 5 | import io.netty.buffer.ByteBufOutputStream; 6 | import io.scalecube.services.api.ServiceMessage; 7 | import io.scalecube.services.exceptions.MessageCodecException; 8 | import io.scalecube.services.gateway.ReferenceCountUtil; 9 | import io.scalecube.services.gateway.client.GatewayClientCodec; 10 | import io.scalecube.services.transport.api.DataCodec; 11 | 12 | public final class HttpGatewayClientCodec implements GatewayClientCodec { 13 | 14 | private final DataCodec dataCodec; 15 | 16 | /** 17 | * Constructor for codec which encode/decode client message to/from {@link ByteBuf}. 18 | * 19 | * @param dataCodec data message codec. 20 | */ 21 | public HttpGatewayClientCodec(DataCodec dataCodec) { 22 | this.dataCodec = dataCodec; 23 | } 24 | 25 | @Override 26 | public ByteBuf encode(ServiceMessage message) { 27 | ByteBuf content; 28 | 29 | if (message.hasData(ByteBuf.class)) { 30 | content = message.data(); 31 | } else { 32 | content = ByteBufAllocator.DEFAULT.buffer(); 33 | try { 34 | dataCodec.encode(new ByteBufOutputStream(content), message.data()); 35 | } catch (Throwable t) { 36 | ReferenceCountUtil.safestRelease(content); 37 | throw new MessageCodecException( 38 | "Failed to encode data on message q=" + message.qualifier(), t); 39 | } 40 | } 41 | 42 | return content; 43 | } 44 | 45 | @Override 46 | public ServiceMessage decode(ByteBuf encodedMessage) { 47 | return ServiceMessage.builder().data(encodedMessage).build(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /services-gateway/src/main/java/io/scalecube/services/gateway/client/websocket/Signal.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.client.websocket; 2 | 3 | public enum Signal { 4 | COMPLETE(1), 5 | ERROR(2), 6 | CANCEL(3); 7 | 8 | private final int code; 9 | 10 | Signal(int code) { 11 | this.code = code; 12 | } 13 | 14 | public int code() { 15 | return code; 16 | } 17 | 18 | public String codeAsString() { 19 | return String.valueOf(code); 20 | } 21 | 22 | /** 23 | * Return appropriate instance of {@link Signal} for given signal code. 24 | * 25 | * @param code signal code 26 | * @return signal instance 27 | */ 28 | public static Signal from(String code) { 29 | return from(Integer.parseInt(code)); 30 | } 31 | 32 | /** 33 | * Return appropriate instance of {@link Signal} for given signal code. 34 | * 35 | * @param code signal code 36 | * @return signal instance 37 | */ 38 | public static Signal from(int code) { 39 | switch (code) { 40 | case 1: 41 | return COMPLETE; 42 | case 2: 43 | return ERROR; 44 | case 3: 45 | return CANCEL; 46 | default: 47 | throw new IllegalArgumentException("Unknown signal: " + code); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /services-gateway/src/main/java/io/scalecube/services/gateway/websocket/HeartbeatService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.websocket; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import reactor.core.publisher.Mono; 6 | 7 | /** 8 | * Service interface for handling custom ping/pong service messages for websocket - service is 9 | * echoing back ping message to the client. Used (optionally) as part of {@link WebsocketGateway}. 10 | */ 11 | @Service(HeartbeatService.NAMESPACE) 12 | public interface HeartbeatService { 13 | 14 | String NAMESPACE = "v1/heartbeat"; 15 | 16 | @ServiceMethod 17 | Mono ping(long value); 18 | } 19 | -------------------------------------------------------------------------------- /services-gateway/src/main/java/io/scalecube/services/gateway/websocket/HeartbeatServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.websocket; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | public class HeartbeatServiceImpl implements HeartbeatService { 6 | 7 | @Override 8 | public Mono ping(long value) { 9 | return Mono.just(value); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /services-gateway/src/main/java/io/scalecube/services/gateway/websocket/Signal.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.websocket; 2 | 3 | public enum Signal { 4 | COMPLETE(1), 5 | ERROR(2), 6 | CANCEL(3); 7 | 8 | private final int code; 9 | 10 | Signal(int code) { 11 | this.code = code; 12 | } 13 | 14 | public int code() { 15 | return code; 16 | } 17 | 18 | /** 19 | * Return appropriate instance of {@link Signal} for given signal code. 20 | * 21 | * @param code signal code 22 | * @return signal instance 23 | */ 24 | public static Signal from(int code) { 25 | switch (code) { 26 | case 1: 27 | return COMPLETE; 28 | case 2: 29 | return ERROR; 30 | case 3: 31 | return CANCEL; 32 | default: 33 | throw new IllegalArgumentException("Unknown signal: " + code); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /services-gateway/src/main/java/io/scalecube/services/gateway/websocket/WebsocketContextException.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.websocket; 2 | 3 | import io.scalecube.services.api.ServiceMessage; 4 | import io.scalecube.services.gateway.ReferenceCountUtil; 5 | 6 | public class WebsocketContextException extends RuntimeException { 7 | 8 | private final ServiceMessage request; 9 | private final ServiceMessage response; 10 | 11 | private WebsocketContextException( 12 | Throwable cause, ServiceMessage request, ServiceMessage response) { 13 | super(cause); 14 | this.request = request; 15 | this.response = response; 16 | } 17 | 18 | public static WebsocketContextException badRequest(String errorMessage, ServiceMessage request) { 19 | return new WebsocketContextException( 20 | new io.scalecube.services.exceptions.BadRequestException(errorMessage), request, null); 21 | } 22 | 23 | public ServiceMessage request() { 24 | return request; 25 | } 26 | 27 | public ServiceMessage response() { 28 | return response; 29 | } 30 | 31 | /** 32 | * Releases request data if any. 33 | * 34 | * @return self 35 | */ 36 | public WebsocketContextException releaseRequest() { 37 | if (request != null) { 38 | ReferenceCountUtil.safestRelease(request.data()); 39 | } 40 | return this; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/AuthRegistry.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway; 2 | 3 | import io.scalecube.services.auth.Principal; 4 | import java.util.Set; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | import java.util.concurrent.ConcurrentMap; 7 | 8 | /** So called "guess username" authentication. All preconfigured users can be authenticated. */ 9 | public class AuthRegistry { 10 | 11 | public static final String SESSION_ID = "SESSION_ID"; 12 | 13 | private final Set allowedUsers; 14 | private final ConcurrentMap loggedInUsers = new ConcurrentHashMap<>(); 15 | 16 | /** 17 | * Constructor. 18 | * 19 | * @param allowedUsers preconfigured usernames that are allowed to be authenticated. 20 | */ 21 | public AuthRegistry(Set allowedUsers) { 22 | this.allowedUsers = allowedUsers; 23 | } 24 | 25 | /** 26 | * Get session auth data if exists. 27 | * 28 | * @param sessionId sessionId 29 | * @return principal by sessionId 30 | */ 31 | public Principal getAuth(long sessionId) { 32 | return loggedInUsers.get(sessionId); 33 | } 34 | 35 | /** 36 | * Add session auth data. 37 | * 38 | * @param sessionId sessionId 39 | * @param username username 40 | */ 41 | public boolean addAuth(long sessionId, String username) { 42 | if (allowedUsers.contains(username)) { 43 | loggedInUsers.putIfAbsent(sessionId, new AllowedUser(username)); 44 | return true; 45 | } 46 | return false; 47 | } 48 | 49 | /** 50 | * Remove session from registry. 51 | * 52 | * @param sessionId sessionId 53 | * @return principal, or null if not exists 54 | */ 55 | public Principal removeAuth(long sessionId) { 56 | return loggedInUsers.remove(sessionId); 57 | } 58 | 59 | public record AllowedUser(String username) implements Principal {} 60 | } 61 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/ErrorService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import reactor.core.publisher.Flux; 6 | import reactor.core.publisher.Mono; 7 | 8 | @Service 9 | public interface ErrorService { 10 | 11 | @ServiceMethod 12 | Flux manyError(); 13 | 14 | @ServiceMethod 15 | Mono oneError(); 16 | } 17 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/ErrorServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway; 2 | 3 | import reactor.core.publisher.Flux; 4 | import reactor.core.publisher.Mono; 5 | 6 | public class ErrorServiceImpl implements ErrorService { 7 | 8 | @Override 9 | public Flux manyError() { 10 | return Flux.error(new SomeException()); 11 | } 12 | 13 | @Override 14 | public Mono oneError() { 15 | return Mono.error(new SomeException()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/GatewayErrorMapperImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway; 2 | 3 | import io.scalecube.services.api.ErrorData; 4 | import io.scalecube.services.api.ServiceMessage; 5 | import io.scalecube.services.exceptions.DefaultErrorMapper; 6 | import io.scalecube.services.exceptions.ServiceClientErrorMapper; 7 | import io.scalecube.services.exceptions.ServiceProviderErrorMapper; 8 | 9 | public class GatewayErrorMapperImpl 10 | implements ServiceProviderErrorMapper, ServiceClientErrorMapper { 11 | 12 | public static final GatewayErrorMapperImpl ERROR_MAPPER = new GatewayErrorMapperImpl(); 13 | 14 | @Override 15 | public Throwable toError(ServiceMessage message) { 16 | if (SomeException.ERROR_TYPE == message.errorType()) { 17 | final ErrorData data = message.data(); 18 | if (SomeException.ERROR_CODE == data.getErrorCode()) { 19 | return new SomeException(); 20 | } 21 | } 22 | return DefaultErrorMapper.INSTANCE.toError(message); 23 | } 24 | 25 | @Override 26 | public ServiceMessage toMessage(String qualifier, Throwable throwable) { 27 | if (throwable instanceof SomeException) { 28 | final int errorCode = ((SomeException) throwable).errorCode(); 29 | final int errorType = SomeException.ERROR_TYPE; 30 | final String errorMessage = throwable.getMessage(); 31 | return ServiceMessage.error(qualifier, errorType, errorCode, errorMessage); 32 | } 33 | return DefaultErrorMapper.INSTANCE.toMessage(qualifier, throwable); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/SecuredService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway; 2 | 3 | import static io.scalecube.services.gateway.SecuredService.NAMESPACE; 4 | 5 | import io.scalecube.services.annotations.RequestType; 6 | import io.scalecube.services.annotations.Service; 7 | import io.scalecube.services.annotations.ServiceMethod; 8 | import io.scalecube.services.api.ServiceMessage; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | 12 | @Service(NAMESPACE) 13 | public interface SecuredService { 14 | 15 | String NAMESPACE = "gw.auth"; 16 | 17 | @ServiceMethod 18 | @RequestType(String.class) 19 | Mono createSession(ServiceMessage request); 20 | 21 | @ServiceMethod 22 | Mono requestOne(String request); 23 | 24 | @ServiceMethod 25 | Flux requestMany(Integer request); 26 | } 27 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/SecuredServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway; 2 | 3 | import io.scalecube.services.RequestContext; 4 | import io.scalecube.services.api.ServiceMessage; 5 | import io.scalecube.services.auth.Secured; 6 | import io.scalecube.services.exceptions.BadRequestException; 7 | import io.scalecube.services.exceptions.ForbiddenException; 8 | import io.scalecube.services.exceptions.UnauthorizedException; 9 | import io.scalecube.services.gateway.AuthRegistry.AllowedUser; 10 | import java.util.stream.IntStream; 11 | import reactor.core.publisher.Flux; 12 | import reactor.core.publisher.Mono; 13 | 14 | public class SecuredServiceImpl implements SecuredService { 15 | 16 | private final AuthRegistry authRegistry; 17 | 18 | public SecuredServiceImpl(AuthRegistry authRegistry) { 19 | this.authRegistry = authRegistry; 20 | } 21 | 22 | @Override 23 | public Mono createSession(ServiceMessage request) { 24 | String sessionId = request.header(AuthRegistry.SESSION_ID); 25 | if (sessionId == null) { 26 | throw new BadRequestException("sessionId is not present in request"); 27 | } 28 | String req = request.data(); 29 | if (!authRegistry.addAuth(Long.parseLong(sessionId), req)) { 30 | throw new UnauthorizedException("Authentication failed"); 31 | } 32 | return Mono.just(req); 33 | } 34 | 35 | @Secured 36 | @Override 37 | public Mono requestOne(String req) { 38 | return RequestContext.deferContextual() 39 | .map( 40 | context -> { 41 | if (!context.hasPrincipal()) { 42 | throw new ForbiddenException("Insufficient permissions"); 43 | } 44 | final var principal = (AllowedUser) context.principal(); 45 | return principal.username() + "@" + req; 46 | }); 47 | } 48 | 49 | @Secured 50 | @Override 51 | public Flux requestMany(Integer times) { 52 | return RequestContext.deferContextual() 53 | .flatMapMany( 54 | context -> { 55 | if (!context.hasPrincipal()) { 56 | throw new ForbiddenException("Insufficient permissions"); 57 | } 58 | if (times <= 0) { 59 | return Flux.empty(); 60 | } 61 | return Flux.fromStream(IntStream.range(0, times).mapToObj(String::valueOf)); 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/SomeException.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway; 2 | 3 | import io.scalecube.services.exceptions.ServiceException; 4 | 5 | public class SomeException extends ServiceException { 6 | 7 | public static final int ERROR_TYPE = 4020; 8 | public static final int ERROR_CODE = 42; 9 | public static final String ERROR_MESSAGE = "smth happened"; 10 | 11 | public SomeException() { 12 | super(ERROR_CODE, ERROR_MESSAGE); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/files/ExportReportRequest.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.files; 2 | 3 | import java.time.Duration; 4 | import java.util.StringJoiner; 5 | 6 | public class ExportReportRequest { 7 | 8 | private Integer ttl; 9 | private Integer numOfLines; 10 | 11 | public Integer ttl() { 12 | return ttl; 13 | } 14 | 15 | public ExportReportRequest ttl(Integer ttl) { 16 | this.ttl = ttl; 17 | return this; 18 | } 19 | 20 | public Duration duration() { 21 | return ttl() != null ? Duration.ofMillis(ttl()) : null; 22 | } 23 | 24 | public Integer numOfLines() { 25 | return numOfLines; 26 | } 27 | 28 | public ExportReportRequest numOfLines(Integer numOfLines) { 29 | this.numOfLines = numOfLines; 30 | return this; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return new StringJoiner(", ", ExportReportRequest.class.getSimpleName() + "[", "]") 36 | .add("ttl=" + ttl) 37 | .add("numOfLines=" + numOfLines) 38 | .toString(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/files/ReportResponse.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.files; 2 | 3 | import java.util.StringJoiner; 4 | 5 | public class ReportResponse { 6 | 7 | private String reportPath; 8 | 9 | public String reportPath() { 10 | return reportPath; 11 | } 12 | 13 | public ReportResponse reportPath(String reportPath) { 14 | this.reportPath = reportPath; 15 | return this; 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return new StringJoiner(", ", ReportResponse.class.getSimpleName() + "[", "]") 21 | .add("reportPath='" + reportPath + "'") 22 | .toString(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/files/ReportService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.files; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import reactor.core.publisher.Mono; 6 | 7 | @Service("v1/api") 8 | public interface ReportService { 9 | 10 | @ServiceMethod 11 | Mono exportReport(ExportReportRequest request); 12 | 13 | @ServiceMethod 14 | Mono exportReportWrongFile(); 15 | } 16 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/files/ReportServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.files; 2 | 3 | import io.scalecube.services.annotations.AfterConstruct; 4 | import io.scalecube.services.files.AddFileRequest; 5 | import io.scalecube.services.files.FileService; 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.util.stream.IntStream; 11 | import reactor.core.publisher.Mono; 12 | 13 | public class ReportServiceImpl implements ReportService { 14 | 15 | private FileService fileService; 16 | 17 | @AfterConstruct 18 | private void conclude(FileService fileService) { 19 | this.fileService = fileService; 20 | } 21 | 22 | @Override 23 | public Mono exportReport(ExportReportRequest request) { 24 | return Mono.defer( 25 | () -> { 26 | try { 27 | // Generate file under correct baseDir (java.io.tmpdir) 28 | final var numOfLines = request.numOfLines() != null ? request.numOfLines() : 10000; 29 | final var file = generateFile(Files.createTempFile("export_report_", null), numOfLines); 30 | return fileService 31 | .addFile(new AddFileRequest(file, request.duration())) 32 | .map(s -> new ReportResponse().reportPath(s)); 33 | } catch (Exception ex) { 34 | throw new RuntimeException(ex); 35 | } 36 | }); 37 | } 38 | 39 | @Override 40 | public Mono exportReportWrongFile() { 41 | // Try create file under wrong baseDir ("target") 42 | final var file = Path.of("target", "export_report_" + System.nanoTime()).toFile(); 43 | return fileService 44 | .addFile(new AddFileRequest(file)) 45 | .map(s -> new ReportResponse().reportPath(s)); 46 | } 47 | 48 | private static File generateFile(final Path file, final int numOfLines) throws IOException { 49 | final var list = 50 | IntStream.range(0, numOfLines) 51 | .mapToObj(i -> "export report @ " + System.nanoTime()) 52 | .toList(); 53 | Files.write(file, list); 54 | return file.toFile(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/rest/RestService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.rest; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import reactor.core.publisher.Mono; 6 | 7 | @Service("v1/restService") 8 | public interface RestService { 9 | 10 | @ServiceMethod("options/:foo") 11 | Mono options(); 12 | 13 | @ServiceMethod("get/:foo") 14 | Mono get(); 15 | 16 | @ServiceMethod("head/:foo") 17 | Mono head(); 18 | 19 | @ServiceMethod("post/:foo") 20 | Mono post(SomeRequest request); 21 | 22 | @ServiceMethod("put/:foo") 23 | Mono put(SomeRequest request); 24 | 25 | @ServiceMethod("patch/:foo") 26 | Mono patch(SomeRequest request); 27 | 28 | @ServiceMethod("delete/:foo") 29 | Mono delete(SomeRequest request); 30 | 31 | @ServiceMethod("trace/:foo") 32 | Mono trace(); 33 | } 34 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/rest/RoutingService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.rest; 2 | 3 | import io.scalecube.services.annotations.RestMethod; 4 | import io.scalecube.services.annotations.Service; 5 | import io.scalecube.services.annotations.ServiceMethod; 6 | import reactor.core.publisher.Mono; 7 | 8 | @Service("v1/routingService") 9 | public interface RoutingService { 10 | 11 | @RestMethod("GET") 12 | @ServiceMethod("find/:foo") 13 | Mono find(); 14 | 15 | @RestMethod("POST") 16 | @ServiceMethod("update/:foo") 17 | Mono update(SomeRequest request); 18 | } 19 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/rest/RoutingServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.rest; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | 7 | import io.scalecube.services.RequestContext; 8 | import reactor.core.publisher.Mono; 9 | 10 | public class RoutingServiceImpl implements RoutingService { 11 | 12 | @Override 13 | public Mono find() { 14 | return RequestContext.deferContextual() 15 | .map( 16 | context -> { 17 | final var foo = context.pathVar("foo"); 18 | assertNotNull(foo); 19 | assertNotNull(context.headers()); 20 | assertTrue(context.headers().size() > 0); 21 | assertEquals("GET", context.requestMethod()); 22 | return new SomeResponse().name(foo); 23 | }); 24 | } 25 | 26 | @Override 27 | public Mono update(SomeRequest request) { 28 | return RequestContext.deferContextual() 29 | .map( 30 | context -> { 31 | assertNotNull(context.pathVar("foo")); 32 | assertNotNull(context.headers()); 33 | assertTrue(context.headers().size() > 0); 34 | assertEquals("POST", context.requestMethod()); 35 | return new SomeResponse().name(request.name()); 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/rest/SomeRequest.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.rest; 2 | 3 | import java.util.StringJoiner; 4 | 5 | public class SomeRequest { 6 | 7 | private String name; 8 | 9 | public String name() { 10 | return name; 11 | } 12 | 13 | public SomeRequest name(String name) { 14 | this.name = name; 15 | return this; 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return new StringJoiner(", ", SomeRequest.class.getSimpleName() + "[", "]") 21 | .add("name='" + name + "'") 22 | .toString(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/rest/SomeResponse.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.rest; 2 | 3 | import java.util.StringJoiner; 4 | 5 | public class SomeResponse { 6 | 7 | private String name; 8 | 9 | public String name() { 10 | return name; 11 | } 12 | 13 | public SomeResponse name(String name) { 14 | this.name = name; 15 | return this; 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return new StringJoiner(", ", SomeResponse.class.getSimpleName() + "[", "]") 21 | .add("name='" + name + "'") 22 | .toString(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/websocket/CancelledSubscriber.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.websocket; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import reactor.core.CoreSubscriber; 6 | 7 | public class CancelledSubscriber implements CoreSubscriber { 8 | 9 | private static final Logger LOGGER = LoggerFactory.getLogger(CancelledSubscriber.class); 10 | 11 | public static final CancelledSubscriber INSTANCE = new CancelledSubscriber(); 12 | 13 | private CancelledSubscriber() { 14 | // Do not instantiate 15 | } 16 | 17 | @Override 18 | public void onSubscribe(org.reactivestreams.Subscription s) { 19 | // no-op 20 | } 21 | 22 | @Override 23 | public void onNext(Object o) { 24 | LOGGER.warn("Received ({}) which will be dropped immediately due cancelled aeron inbound", o); 25 | } 26 | 27 | @Override 28 | public void onError(Throwable t) { 29 | // no-op 30 | } 31 | 32 | @Override 33 | public void onComplete() { 34 | // no-op 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/websocket/GatewaySessionHandlerImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.websocket; 2 | 3 | import static io.scalecube.services.auth.Principal.NULL_PRINCIPAL; 4 | 5 | import io.netty.buffer.ByteBuf; 6 | import io.scalecube.services.RequestContext; 7 | import io.scalecube.services.api.ServiceMessage; 8 | import io.scalecube.services.gateway.AuthRegistry; 9 | import io.scalecube.services.gateway.GatewaySession; 10 | import io.scalecube.services.gateway.GatewaySessionHandler; 11 | import reactor.util.context.Context; 12 | 13 | public class GatewaySessionHandlerImpl implements GatewaySessionHandler { 14 | 15 | private final AuthRegistry authRegistry; 16 | 17 | public GatewaySessionHandlerImpl(AuthRegistry authRegistry) { 18 | this.authRegistry = authRegistry; 19 | } 20 | 21 | @Override 22 | public Context onRequest(GatewaySession session, ByteBuf byteBuf, Context context) { 23 | final var principal = authRegistry.getAuth(session.sessionId()); 24 | return new RequestContext().principal(principal != null ? principal : NULL_PRINCIPAL); 25 | } 26 | 27 | @Override 28 | public ServiceMessage mapMessage( 29 | GatewaySession session, ServiceMessage message, Context context) { 30 | return ServiceMessage.from(message) 31 | .header(AuthRegistry.SESSION_ID, session.sessionId()) 32 | .build(); 33 | } 34 | 35 | @Override 36 | public void onSessionClose(GatewaySession session) { 37 | authRegistry.removeAuth(session.sessionId()); 38 | LOGGER.info("Session removed: {}", session); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/websocket/ReactiveOperator.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.websocket; 2 | 3 | import reactor.core.Disposable; 4 | 5 | public interface ReactiveOperator extends Disposable { 6 | 7 | void dispose(Throwable throwable); 8 | 9 | void lastError(Throwable throwable); 10 | 11 | Throwable lastError(); 12 | 13 | void tryNext(Object fragment); 14 | 15 | boolean isFastPath(); 16 | 17 | void commitProduced(); 18 | 19 | long incrementProduced(); 20 | 21 | long requested(long limit); 22 | } 23 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/websocket/TestGatewaySessionHandler.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.websocket; 2 | 3 | import io.scalecube.services.api.ServiceMessage; 4 | import io.scalecube.services.gateway.GatewaySession; 5 | import io.scalecube.services.gateway.GatewaySessionHandler; 6 | import java.util.concurrent.CountDownLatch; 7 | import java.util.concurrent.atomic.AtomicReference; 8 | import reactor.util.context.Context; 9 | 10 | public class TestGatewaySessionHandler implements GatewaySessionHandler { 11 | 12 | public final CountDownLatch msgLatch = new CountDownLatch(1); 13 | public final CountDownLatch connLatch = new CountDownLatch(1); 14 | public final CountDownLatch disconnLatch = new CountDownLatch(1); 15 | private final AtomicReference lastSession = new AtomicReference<>(); 16 | 17 | @Override 18 | public ServiceMessage mapMessage(GatewaySession s, ServiceMessage req, Context context) { 19 | msgLatch.countDown(); 20 | return req; 21 | } 22 | 23 | @Override 24 | public void onSessionOpen(GatewaySession s) { 25 | connLatch.countDown(); 26 | lastSession.set(s); 27 | } 28 | 29 | @Override 30 | public void onSessionClose(GatewaySession s) { 31 | disconnLatch.countDown(); 32 | } 33 | 34 | public GatewaySession lastSession() { 35 | return lastSession.get(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/websocket/TestInputs.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.websocket; 2 | 3 | public interface TestInputs { 4 | 5 | long SID = 42L; 6 | int I = 423; 7 | int SIG = 422; 8 | String Q = "/test/test"; 9 | 10 | String NO_DATA = 11 | "{" 12 | + " \"q\":\"" 13 | + Q 14 | + "\"," 15 | + " \"sid\":" 16 | + SID 17 | + "," 18 | + " \"sig\":" 19 | + SIG 20 | + "," 21 | + " \"i\":" 22 | + I 23 | + "}"; 24 | 25 | String STRING_DATA_PATTERN_Q_SIG_SID_D = 26 | "{" + "\"q\":\"%s\"," + "\"sig\":%d," + "\"sid\":%d," + "\"d\":%s" + "}"; 27 | 28 | String STRING_DATA_PATTERN_D_SIG_SID_Q = 29 | "{" + "\"d\": %s," + "\"sig\":%d," + "\"sid\": %d," + "\"q\":\"%s\"" + "}"; 30 | 31 | class Entity { 32 | private String text; 33 | private Integer number; 34 | private Boolean check; 35 | 36 | Entity() {} 37 | 38 | public Entity(String text, Integer number, Boolean check) { 39 | this.text = text; 40 | this.number = number; 41 | this.check = check; 42 | } 43 | 44 | public String text() { 45 | return text; 46 | } 47 | 48 | public Integer number() { 49 | return number; 50 | } 51 | 52 | public Boolean check() { 53 | return check; 54 | } 55 | 56 | @Override 57 | public boolean equals(Object o) { 58 | if (this == o) { 59 | return true; 60 | } 61 | if (o == null || getClass() != o.getClass()) { 62 | return false; 63 | } 64 | 65 | Entity entity = (Entity) o; 66 | 67 | if (text != null ? !text.equals(entity.text) : entity.text != null) { 68 | return false; 69 | } 70 | if (number != null ? !number.equals(entity.number) : entity.number != null) { 71 | return false; 72 | } 73 | return check != null ? check.equals(entity.check) : entity.check == null; 74 | } 75 | 76 | @Override 77 | public int hashCode() { 78 | int result = text != null ? text.hashCode() : 0; 79 | result = 31 * result + (number != null ? number.hashCode() : 0); 80 | result = 31 * result + (check != null ? check.hashCode() : 0); 81 | return result; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/websocket/TestService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.websocket; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import reactor.core.publisher.Flux; 6 | import reactor.core.publisher.Mono; 7 | 8 | @Service 9 | public interface TestService { 10 | 11 | @ServiceMethod("manyNever") 12 | Flux manyNever(); 13 | 14 | @ServiceMethod 15 | Mono one(String one); 16 | 17 | @ServiceMethod 18 | Mono oneErr(String one); 19 | } 20 | -------------------------------------------------------------------------------- /services-gateway/src/test/java/io/scalecube/services/gateway/websocket/TestServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.gateway.websocket; 2 | 3 | import io.scalecube.services.exceptions.ForbiddenException; 4 | import reactor.core.publisher.Flux; 5 | import reactor.core.publisher.Mono; 6 | 7 | public class TestServiceImpl implements TestService { 8 | 9 | private final Runnable onClose; 10 | 11 | public TestServiceImpl(Runnable onClose) { 12 | this.onClose = onClose; 13 | } 14 | 15 | @Override 16 | public Flux manyNever() { 17 | return Flux.never().log(">>>").doOnCancel(onClose); 18 | } 19 | 20 | @Override 21 | public Mono one(String one) { 22 | return Mono.just(one); 23 | } 24 | 25 | @Override 26 | public Mono oneErr(String one) { 27 | throw new ForbiddenException("forbidden"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /services-security/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.scalecube 7 | scalecube-services-parent 8 | 2.13.2-SNAPSHOT 9 | 10 | 11 | scalecube-services-security 12 | 13 | 14 | 15 | io.scalecube 16 | scalecube-services-api 17 | ${project.version} 18 | 19 | 20 | io.scalecube 21 | scalecube-security-tokens 22 | 23 | 24 | io.scalecube 25 | scalecube-security-vault 26 | 27 | 28 | 29 | io.scalecube 30 | scalecube-services-testlib 31 | ${project.version} 32 | test 33 | 34 | 35 | io.scalecube 36 | scalecube-security-tests 37 | ${scalecube-security.version} 38 | test 39 | 40 | 41 | org.testcontainers 42 | vault 43 | ${testcontainers.version} 44 | test 45 | 46 | 47 | com.bettercloud 48 | vault-java-driver 49 | ${vault-java-driver.version} 50 | test 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /services-security/src/main/java/io/scalecube/services/security/ServiceTokenAuthenticator.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.security; 2 | 3 | import io.scalecube.security.tokens.jwt.JwtToken; 4 | import io.scalecube.security.tokens.jwt.JwtTokenResolver; 5 | import io.scalecube.security.tokens.jwt.JwtUnavailableException; 6 | import io.scalecube.services.auth.Authenticator; 7 | import io.scalecube.services.auth.Principal; 8 | import io.scalecube.services.auth.ServicePrincipal; 9 | import java.time.Duration; 10 | import java.util.Arrays; 11 | import java.util.stream.Collectors; 12 | import reactor.core.publisher.Mono; 13 | import reactor.util.retry.Retry; 14 | 15 | /** 16 | * Service authenticator based on JWT token. Being used to verify service identity that establishing 17 | * session to the system. JWT token must have claims: {@code role} and {@code permissions}. 18 | */ 19 | public class ServiceTokenAuthenticator implements Authenticator { 20 | 21 | private final JwtTokenResolver tokenResolver; 22 | private final Retry retryStrategy; 23 | 24 | /** 25 | * Constructor with defaults. 26 | * 27 | * @param tokenResolver token resolver 28 | */ 29 | public ServiceTokenAuthenticator(JwtTokenResolver tokenResolver) { 30 | this(tokenResolver, 5, Duration.ofSeconds(3)); 31 | } 32 | 33 | /** 34 | * Constructor. 35 | * 36 | * @param tokenResolver token resolver 37 | * @param retryMaxAttempts max number of retry attempts 38 | * @param retryFixedDelay delay between retry attempts 39 | */ 40 | public ServiceTokenAuthenticator( 41 | JwtTokenResolver tokenResolver, int retryMaxAttempts, Duration retryFixedDelay) { 42 | this.tokenResolver = tokenResolver; 43 | this.retryStrategy = 44 | Retry.fixedDelay(retryMaxAttempts, retryFixedDelay) 45 | .filter(ex -> ex instanceof JwtUnavailableException); 46 | } 47 | 48 | @Override 49 | public Mono authenticate(byte[] credentials) { 50 | return Mono.fromFuture(tokenResolver.resolve(new String(credentials))) 51 | .retryWhen(retryStrategy) 52 | .map(JwtToken::payload) 53 | .map( 54 | payload -> { 55 | final var role = (String) payload.get("role"); 56 | if (role == null) { 57 | throw new IllegalArgumentException("Wrong token: role claim is missing"); 58 | } 59 | 60 | final var permissionsClaim = (String) payload.get("permissions"); 61 | if (permissionsClaim == null) { 62 | throw new IllegalArgumentException("Wrong token: permissions claim is missing"); 63 | } 64 | 65 | final var permissions = 66 | Arrays.stream(permissionsClaim.split(",")) 67 | .map(String::trim) 68 | .filter(s -> !s.isBlank()) 69 | .collect(Collectors.toSet()); 70 | 71 | return new ServicePrincipal(role, permissions); 72 | }); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /services-security/src/main/java/io/scalecube/services/security/ServiceTokenCredentialsSupplier.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.security; 2 | 3 | import io.scalecube.security.vault.VaultServiceTokenSupplier; 4 | import io.scalecube.services.auth.CredentialsSupplier; 5 | import io.scalecube.services.exceptions.ForbiddenException; 6 | import java.util.Collection; 7 | import java.util.Collections; 8 | import java.util.List; 9 | import java.util.Objects; 10 | import java.util.concurrent.CompletableFuture; 11 | import java.util.function.Supplier; 12 | import reactor.core.publisher.Mono; 13 | 14 | public class ServiceTokenCredentialsSupplier implements CredentialsSupplier { 15 | 16 | private final String environment; 17 | private final String vaultAddress; 18 | private final Supplier> vaultTokenSupplier; 19 | private final Collection allowedRoles; 20 | 21 | /** 22 | * Constructor. 23 | * 24 | * @param environment logical environment name 25 | * @param vaultAddress vaultAddress 26 | * @param vaultTokenSupplier vaultTokenSupplier 27 | * @param allowedRoles allowedRoles (optional) 28 | */ 29 | public ServiceTokenCredentialsSupplier( 30 | String environment, 31 | String vaultAddress, 32 | Supplier> vaultTokenSupplier, 33 | Collection allowedRoles) { 34 | this.environment = Objects.requireNonNull(environment, "environment"); 35 | this.vaultAddress = Objects.requireNonNull(vaultAddress, "vaultAddress"); 36 | this.vaultTokenSupplier = Objects.requireNonNull(vaultTokenSupplier, "vaultTokenSupplier"); 37 | this.allowedRoles = allowedRoles; 38 | } 39 | 40 | @Override 41 | public Mono credentials(String service, List serviceRoles) { 42 | return Mono.defer( 43 | () -> { 44 | if (serviceRoles == null || serviceRoles.isEmpty()) { 45 | return Mono.just(new byte[0]); 46 | } 47 | 48 | String serviceRole = null; 49 | 50 | if (allowedRoles == null || allowedRoles.isEmpty()) { 51 | serviceRole = serviceRoles.get(0); 52 | } else { 53 | for (var allowedRole : allowedRoles) { 54 | if (serviceRoles.contains(allowedRole)) { 55 | serviceRole = allowedRole; 56 | } 57 | } 58 | } 59 | 60 | if (serviceRole == null) { 61 | throw new ForbiddenException("Insufficient permissions"); 62 | } 63 | 64 | return Mono.fromFuture( 65 | VaultServiceTokenSupplier.builder() 66 | .vaultAddress(vaultAddress) 67 | .serviceRole(serviceRole) 68 | .vaultTokenSupplier(vaultTokenSupplier) 69 | .serviceTokenNameBuilder( 70 | (role, tags) -> String.join(".", environment, service, role)) 71 | .build() 72 | .getToken(Collections.emptyMap())) 73 | .map(String::getBytes); 74 | }); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /services-security/src/main/java/io/scalecube/services/security/VaultServiceRolesProcessor.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.security; 2 | 3 | import io.scalecube.security.vault.VaultServiceRolesInstaller; 4 | import io.scalecube.security.vault.VaultServiceRolesInstaller.ServiceRoles; 5 | import io.scalecube.security.vault.VaultServiceRolesInstaller.ServiceRoles.Role; 6 | import io.scalecube.services.auth.ServiceRolesProcessor; 7 | import io.scalecube.services.methods.ServiceRoleDefinition; 8 | import java.util.ArrayList; 9 | import java.util.Collection; 10 | import java.util.List; 11 | import java.util.Objects; 12 | import java.util.concurrent.CompletableFuture; 13 | import java.util.function.Supplier; 14 | 15 | public class VaultServiceRolesProcessor implements ServiceRolesProcessor { 16 | 17 | private final String environment; 18 | private final String service; 19 | private final String vaultAddress; 20 | private final Supplier> vaultTokenSupplier; 21 | 22 | /** 23 | * Constructor. 24 | * 25 | * @param environment logical environment name 26 | * @param service logical service name 27 | * @param vaultAddress vaultAddress 28 | * @param vaultTokenSupplier vaultTokenSupplier 29 | */ 30 | public VaultServiceRolesProcessor( 31 | String environment, 32 | String service, 33 | String vaultAddress, 34 | Supplier> vaultTokenSupplier) { 35 | this.environment = Objects.requireNonNull(environment, "environment"); 36 | this.service = Objects.requireNonNull(service, "service"); 37 | this.vaultAddress = Objects.requireNonNull(vaultAddress, "vaultAddress"); 38 | this.vaultTokenSupplier = Objects.requireNonNull(vaultTokenSupplier, "vaultTokenSupplier"); 39 | } 40 | 41 | @Override 42 | public void process(Collection values) { 43 | VaultServiceRolesInstaller.builder() 44 | .vaultAddress(vaultAddress) 45 | .vaultTokenSupplier(vaultTokenSupplier) 46 | .serviceRolesSources(List.of(() -> toServiceRoles(values))) 47 | .keyNameSupplier(() -> String.join(".", environment, "identity-key")) 48 | .roleNameBuilder(role -> String.join(".", environment, service, role)) 49 | .build() 50 | .install(); 51 | } 52 | 53 | private static ServiceRoles toServiceRoles(Collection values) { 54 | return new ServiceRoles() 55 | .roles( 56 | values.stream() 57 | .map( 58 | roleDefinition -> { 59 | final var role = new Role(); 60 | role.role(roleDefinition.role()); 61 | role.permissions(new ArrayList<>(roleDefinition.permissions())); 62 | return role; 63 | }) 64 | .toList()); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /services-security/src/test/java/io/scalecube/services/security/environment/IntegrationEnvironmentFixture.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.security.environment; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.function.Supplier; 6 | import org.junit.jupiter.api.extension.BeforeAllCallback; 7 | import org.junit.jupiter.api.extension.ExtensionContext; 8 | import org.junit.jupiter.api.extension.ExtensionContext.Namespace; 9 | import org.junit.jupiter.api.extension.ParameterContext; 10 | import org.junit.jupiter.api.extension.ParameterResolutionException; 11 | import org.junit.jupiter.api.extension.ParameterResolver; 12 | 13 | public class IntegrationEnvironmentFixture 14 | implements BeforeAllCallback, ExtensionContext.Store.CloseableResource, ParameterResolver { 15 | 16 | private static final Map, Supplier> PARAMETERS_TO_RESOLVE = new HashMap<>(); 17 | 18 | private static VaultEnvironment vaultEnvironment; 19 | 20 | @Override 21 | public void beforeAll(ExtensionContext context) { 22 | context 23 | .getRoot() 24 | .getStore(Namespace.GLOBAL) 25 | .getOrComputeIfAbsent( 26 | this.getClass(), 27 | key -> { 28 | vaultEnvironment = VaultEnvironment.start(); 29 | return this; 30 | }); 31 | 32 | PARAMETERS_TO_RESOLVE.put(VaultEnvironment.class, () -> vaultEnvironment); 33 | } 34 | 35 | @Override 36 | public void close() { 37 | if (vaultEnvironment != null) { 38 | vaultEnvironment.close(); 39 | } 40 | } 41 | 42 | @Override 43 | public boolean supportsParameter( 44 | ParameterContext parameterContext, ExtensionContext extensionContext) 45 | throws ParameterResolutionException { 46 | Class type = parameterContext.getParameter().getType(); 47 | return PARAMETERS_TO_RESOLVE.keySet().stream().anyMatch(type::isAssignableFrom); 48 | } 49 | 50 | @Override 51 | public Object resolveParameter( 52 | ParameterContext parameterContext, ExtensionContext extensionContext) 53 | throws ParameterResolutionException { 54 | Class type = parameterContext.getParameter().getType(); 55 | return PARAMETERS_TO_RESOLVE.get(type).get(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /services-security/src/test/resources/log4j2-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %level{length=1} %d{ISO8601} %c{1.} %m [%t]%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /services-testlib/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.scalecube 7 | scalecube-services-parent 8 | 2.13.2-SNAPSHOT 9 | 10 | 11 | scalecube-services-testlib 12 | 13 | 14 | 15 | org.junit.jupiter 16 | junit-jupiter-api 17 | ${junit-jupiter.version} 18 | 19 | 20 | org.junit.jupiter 21 | junit-jupiter-engine 22 | ${junit-jupiter.version} 23 | 24 | 25 | org.junit.jupiter 26 | junit-jupiter-params 27 | ${junit-jupiter.version} 28 | 29 | 30 | org.mockito 31 | mockito-junit-jupiter 32 | ${mockito-junit.version} 33 | 34 | 35 | org.junit.jupiter 36 | junit-jupiter-api 37 | 38 | 39 | 40 | 41 | org.hamcrest 42 | hamcrest-all 43 | ${hamcrest.version} 44 | 45 | 46 | io.projectreactor 47 | reactor-test 48 | 49 | 50 | org.slf4j 51 | slf4j-api 52 | 53 | 54 | org.apache.logging.log4j 55 | log4j-slf4j-impl 56 | 57 | 58 | org.apache.logging.log4j 59 | log4j-core 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /services-testlib/src/main/java/io/scalecube/services/utils/LoggingExtension.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.utils; 2 | 3 | import java.lang.reflect.Method; 4 | import org.junit.jupiter.api.extension.AfterAllCallback; 5 | import org.junit.jupiter.api.extension.AfterEachCallback; 6 | import org.junit.jupiter.api.extension.BeforeAllCallback; 7 | import org.junit.jupiter.api.extension.BeforeEachCallback; 8 | import org.junit.jupiter.api.extension.ExtensionContext; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | public class LoggingExtension 13 | implements AfterEachCallback, BeforeEachCallback, AfterAllCallback, BeforeAllCallback { 14 | 15 | private static final Logger LOGGER = LoggerFactory.getLogger(LoggingExtension.class); 16 | 17 | @Override 18 | public void beforeAll(ExtensionContext context) { 19 | LOGGER.info( 20 | "***** Setup: " + context.getTestClass().map(Class::getSimpleName).orElse("") + " *****"); 21 | } 22 | 23 | @Override 24 | public void afterEach(ExtensionContext context) { 25 | LOGGER.info( 26 | "***** Test finished: " 27 | + context.getTestClass().map(Class::getSimpleName).orElse("") 28 | + "." 29 | + context.getTestMethod().map(Method::getName).orElse("") 30 | + "." 31 | + context.getDisplayName() 32 | + " *****"); 33 | } 34 | 35 | @Override 36 | public void beforeEach(ExtensionContext context) { 37 | LOGGER.info( 38 | "***** Test started: " 39 | + context.getTestClass().map(Class::getSimpleName).orElse("") 40 | + "." 41 | + context.getTestMethod().map(Method::getName).orElse("") 42 | + "." 43 | + context.getDisplayName() 44 | + " *****"); 45 | } 46 | 47 | @Override 48 | public void afterAll(ExtensionContext context) { 49 | LOGGER.info( 50 | "***** TearDown: " 51 | + context.getTestClass().map(Class::getSimpleName).orElse("") 52 | + " *****"); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /services-testlib/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension: -------------------------------------------------------------------------------- 1 | io.scalecube.services.utils.LoggingExtension 2 | -------------------------------------------------------------------------------- /services-testlib/src/main/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | junit.jupiter.extensions.autodetection.enabled=true 2 | -------------------------------------------------------------------------------- /services-testlib/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %level{length=1} %date{MMdd-HHmm:ss,SSS} %logger{1.} %message [%thread]%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /services-transport-parent/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.scalecube 7 | scalecube-services-parent 8 | 2.13.2-SNAPSHOT 9 | 10 | 11 | scalecube-services-transport-parent 12 | pom 13 | 14 | 15 | services-transport-jackson 16 | services-transport-rsocket 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /services-transport-parent/services-transport-jackson/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | io.scalecube 6 | scalecube-services-transport-parent 7 | 2.13.2-SNAPSHOT 8 | 9 | 10 | scalecube-services-transport-jackson 11 | 12 | 13 | 14 | io.scalecube 15 | scalecube-services-api 16 | ${project.version} 17 | 18 | 19 | 20 | com.fasterxml.jackson.core 21 | jackson-core 22 | 23 | 24 | com.fasterxml.jackson.core 25 | jackson-databind 26 | 27 | 28 | com.fasterxml.jackson.datatype 29 | jackson-datatype-jsr310 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /services-transport-parent/services-transport-jackson/src/main/java/io/scalecube/services/transport/jackson/JacksonCodec.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.transport.jackson; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.fasterxml.jackson.annotation.JsonInclude; 5 | import com.fasterxml.jackson.annotation.PropertyAccessor; 6 | import com.fasterxml.jackson.databind.DeserializationFeature; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import com.fasterxml.jackson.databind.SerializationFeature; 9 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 10 | import io.scalecube.services.transport.api.DataCodec; 11 | import io.scalecube.services.transport.api.HeadersCodec; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.io.OutputStream; 15 | import java.lang.reflect.Type; 16 | import java.util.Collections; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | public final class JacksonCodec implements DataCodec, HeadersCodec { 21 | 22 | public static final String CONTENT_TYPE = "application/json"; 23 | 24 | private final ObjectMapper mapper; 25 | 26 | public JacksonCodec() { 27 | this(initMapper()); 28 | } 29 | 30 | public JacksonCodec(ObjectMapper mapper) { 31 | this.mapper = mapper; 32 | } 33 | 34 | @Override 35 | public String contentType() { 36 | return CONTENT_TYPE; 37 | } 38 | 39 | @Override 40 | public void encode(OutputStream stream, Map headers) throws IOException { 41 | mapper.writeValue(stream, headers); 42 | } 43 | 44 | @Override 45 | public void encode(OutputStream stream, Object value) throws IOException { 46 | mapper.writeValue(stream, value); 47 | } 48 | 49 | @Override 50 | public Map decode(InputStream stream) throws IOException { 51 | //noinspection unchecked 52 | return stream.available() == 0 53 | ? Collections.emptyMap() 54 | : mapper.readValue(stream, HashMap.class); 55 | } 56 | 57 | @Override 58 | public Object decode(InputStream stream, Type type) throws IOException { 59 | return mapper.readValue(stream, mapper.getTypeFactory().constructType(type)); 60 | } 61 | 62 | private static ObjectMapper initMapper() { 63 | ObjectMapper mapper = new ObjectMapper(); 64 | mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 65 | mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); 66 | mapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true); 67 | mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); 68 | mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 69 | mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 70 | mapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true); 71 | mapper.registerModule(new JavaTimeModule()); 72 | return mapper; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /services-transport-parent/services-transport-jackson/src/main/resources/META-INF/services/io.scalecube.services.transport.api.DataCodec: -------------------------------------------------------------------------------- 1 | io.scalecube.services.transport.jackson.JacksonCodec -------------------------------------------------------------------------------- /services-transport-parent/services-transport-jackson/src/main/resources/META-INF/services/io.scalecube.services.transport.api.HeadersCodec: -------------------------------------------------------------------------------- 1 | io.scalecube.services.transport.jackson.JacksonCodec -------------------------------------------------------------------------------- /services-transport-parent/services-transport-rsocket/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | io.scalecube 6 | scalecube-services-transport-parent 7 | 2.13.2-SNAPSHOT 8 | 9 | 10 | scalecube-services-transport-rsocket 11 | 12 | 13 | 14 | io.scalecube 15 | scalecube-services-api 16 | ${project.version} 17 | 18 | 19 | io.rsocket 20 | rsocket-core 21 | 22 | 23 | io.rsocket 24 | rsocket-transport-netty 25 | 26 | 27 | io.projectreactor.netty 28 | reactor-netty 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /services-transport-parent/services-transport-rsocket/src/main/java/io/scalecube/services/transport/rsocket/RSocketClientChannel.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.transport.rsocket; 2 | 3 | import io.rsocket.Payload; 4 | import io.rsocket.RSocket; 5 | import io.rsocket.util.ByteBufPayload; 6 | import io.scalecube.services.api.ServiceMessage; 7 | import io.scalecube.services.exceptions.ConnectionClosedException; 8 | import io.scalecube.services.transport.api.ClientChannel; 9 | import java.lang.reflect.Type; 10 | import org.reactivestreams.Publisher; 11 | import reactor.core.publisher.Flux; 12 | import reactor.core.publisher.Mono; 13 | import reactor.netty.channel.AbortedException; 14 | 15 | public class RSocketClientChannel implements ClientChannel { 16 | 17 | private final Mono rsocket; 18 | private final ServiceMessageCodec messageCodec; 19 | 20 | public RSocketClientChannel(Mono rsocket, ServiceMessageCodec codec) { 21 | this.rsocket = rsocket; 22 | this.messageCodec = codec; 23 | } 24 | 25 | @Override 26 | public Mono requestResponse(ServiceMessage message, Type responseType) { 27 | return rsocket 28 | .flatMap(rsocket -> rsocket.requestResponse(toPayload(message))) 29 | .map(this::toMessage) 30 | .map(msg -> ServiceMessageCodec.decodeData(msg, responseType)) 31 | .onErrorMap(RSocketClientChannel::mapConnectionAborted); 32 | } 33 | 34 | @Override 35 | public Flux requestStream(ServiceMessage message, Type responseType) { 36 | return rsocket 37 | .flatMapMany(rsocket -> rsocket.requestStream(toPayload(message))) 38 | .map(this::toMessage) 39 | .map(msg -> ServiceMessageCodec.decodeData(msg, responseType)) 40 | .onErrorMap(RSocketClientChannel::mapConnectionAborted); 41 | } 42 | 43 | @Override 44 | public Flux requestChannel( 45 | Publisher publisher, Type responseType) { 46 | return rsocket 47 | .flatMapMany(rsocket -> rsocket.requestChannel(Flux.from(publisher).map(this::toPayload))) 48 | .map(this::toMessage) 49 | .map(msg -> ServiceMessageCodec.decodeData(msg, responseType)) 50 | .onErrorMap(RSocketClientChannel::mapConnectionAborted); 51 | } 52 | 53 | private Payload toPayload(ServiceMessage request) { 54 | return messageCodec.encodeAndTransform(request, ByteBufPayload::create); 55 | } 56 | 57 | private ServiceMessage toMessage(Payload payload) { 58 | try { 59 | return messageCodec.decode(payload.sliceData().retain(), payload.sliceMetadata().retain()); 60 | } finally { 61 | payload.release(); 62 | } 63 | } 64 | 65 | private static Throwable mapConnectionAborted(Throwable t) { 66 | return AbortedException.isConnectionReset(t) || ConnectionClosedException.isConnectionClosed(t) 67 | ? new ConnectionClosedException(t) 68 | : t; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /services-transport-parent/services-transport-rsocket/src/main/java/io/scalecube/services/transport/rsocket/RSocketServerTransportFactory.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.transport.rsocket; 2 | 3 | import io.netty.channel.ChannelOption; 4 | import io.rsocket.transport.ServerTransport; 5 | import io.rsocket.transport.netty.server.CloseableChannel; 6 | import io.rsocket.transport.netty.server.TcpServerTransport; 7 | import io.rsocket.transport.netty.server.WebsocketServerTransport; 8 | import java.net.InetSocketAddress; 9 | import java.util.function.Function; 10 | import reactor.netty.http.server.HttpServer; 11 | import reactor.netty.resources.LoopResources; 12 | import reactor.netty.tcp.TcpServer; 13 | 14 | public interface RSocketServerTransportFactory { 15 | 16 | /** 17 | * Returns default rsocket tcp server transport factory (shall listen on port {@code 0}). 18 | * 19 | * @see TcpServerTransport 20 | * @return factory function for {@link RSocketServerTransportFactory} 21 | */ 22 | static Function tcp() { 23 | return tcp(0); 24 | } 25 | 26 | /** 27 | * Returns default rsocket tcp server transport factory. 28 | * 29 | * @param port port 30 | * @see TcpServerTransport 31 | * @return factory function for {@link RSocketServerTransportFactory} 32 | */ 33 | static Function tcp(int port) { 34 | return (LoopResources loopResources) -> 35 | () -> 36 | TcpServerTransport.create( 37 | TcpServer.create() 38 | .runOn(loopResources) 39 | .bindAddress(() -> new InetSocketAddress(port)) 40 | .childOption(ChannelOption.TCP_NODELAY, true) 41 | .childOption(ChannelOption.SO_KEEPALIVE, true) 42 | .childOption(ChannelOption.SO_REUSEADDR, true)); 43 | } 44 | 45 | /** 46 | * Returns default rsocket websocket server transport factory (shall listen on port {@code 0}). 47 | * 48 | * @see WebsocketServerTransport 49 | * @return factory function for {@link RSocketServerTransportFactory} 50 | */ 51 | static Function websocket() { 52 | return websocket(0); 53 | } 54 | 55 | /** 56 | * Returns default rsocket websocket server transport factory. 57 | * 58 | * @param port port 59 | * @see WebsocketServerTransport 60 | * @return factory function for {@link RSocketServerTransportFactory} 61 | */ 62 | static Function websocket(int port) { 63 | return loopResources -> 64 | () -> 65 | WebsocketServerTransport.create( 66 | HttpServer.create() 67 | .runOn(loopResources) 68 | .bindAddress(() -> new InetSocketAddress(port)) 69 | .childOption(ChannelOption.TCP_NODELAY, true) 70 | .childOption(ChannelOption.SO_KEEPALIVE, true) 71 | .childOption(ChannelOption.SO_REUSEADDR, true)); 72 | } 73 | 74 | ServerTransport serverTransport(); 75 | } 76 | -------------------------------------------------------------------------------- /services-transport-parent/services-transport-rsocket/src/main/java/io/scalecube/services/transport/rsocket/RSocketServiceAcceptor.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.transport.rsocket; 2 | 3 | import static io.scalecube.services.auth.Principal.NULL_PRINCIPAL; 4 | 5 | import io.netty.buffer.ByteBuf; 6 | import io.rsocket.ConnectionSetupPayload; 7 | import io.rsocket.RSocket; 8 | import io.rsocket.SocketAcceptor; 9 | import io.scalecube.services.auth.Authenticator; 10 | import io.scalecube.services.auth.Principal; 11 | import io.scalecube.services.exceptions.UnauthorizedException; 12 | import io.scalecube.services.registry.api.ServiceRegistry; 13 | import io.scalecube.services.transport.api.DataCodec; 14 | import io.scalecube.services.transport.api.HeadersCodec; 15 | import java.util.Collection; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | import reactor.core.publisher.Mono; 19 | 20 | public class RSocketServiceAcceptor implements SocketAcceptor { 21 | 22 | private static final Logger LOGGER = LoggerFactory.getLogger(RSocketServiceAcceptor.class); 23 | 24 | private final HeadersCodec headersCodec; 25 | private final Collection dataCodecs; 26 | private final Authenticator authenticator; 27 | private final ServiceRegistry serviceRegistry; 28 | 29 | public RSocketServiceAcceptor( 30 | HeadersCodec headersCodec, 31 | Collection dataCodecs, 32 | Authenticator authenticator, 33 | ServiceRegistry serviceRegistry) { 34 | this.headersCodec = headersCodec; 35 | this.dataCodecs = dataCodecs; 36 | this.authenticator = authenticator; 37 | this.serviceRegistry = serviceRegistry; 38 | } 39 | 40 | @Override 41 | public Mono accept(ConnectionSetupPayload setupPayload, RSocket rsocket) { 42 | return Mono.defer(() -> authenticate(setupPayload.data())).map(this::newRSocket); 43 | } 44 | 45 | private Mono authenticate(ByteBuf connectionSetup) { 46 | if (authenticator == null || !connectionSetup.isReadable()) { 47 | return Mono.just(NULL_PRINCIPAL); 48 | } 49 | 50 | final var credentials = new byte[connectionSetup.readableBytes()]; 51 | connectionSetup.getBytes(connectionSetup.readerIndex(), credentials); 52 | 53 | return Mono.defer(() -> authenticator.authenticate(credentials)) 54 | .switchIfEmpty(Mono.just(NULL_PRINCIPAL)) 55 | .doOnSuccess(principal -> LOGGER.debug("Authenticated successfully: {}", principal)) 56 | .doOnError(ex -> LOGGER.error("Authentication failed", ex)) 57 | .onErrorMap(ex -> new UnauthorizedException("Authentication failed")); 58 | } 59 | 60 | private RSocket newRSocket(Principal principal) { 61 | return new RSocketImpl( 62 | principal, new ServiceMessageCodec(headersCodec, dataCodecs), serviceRegistry); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /services-transport-parent/services-transport-rsocket/src/main/java/io/scalecube/services/transport/rsocket/ReferenceCountUtil.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.transport.rsocket; 2 | 3 | import io.netty.util.ReferenceCounted; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | public final class ReferenceCountUtil { 8 | 9 | private static final Logger LOGGER = LoggerFactory.getLogger(ReferenceCountUtil.class); 10 | 11 | private ReferenceCountUtil() { 12 | // Do not instantiate 13 | } 14 | 15 | /** 16 | * Try to release input object iff it's instance is of {@link ReferenceCounted} type and its 17 | * refCount greater than zero. 18 | * 19 | * @return true if msg release taken place 20 | */ 21 | public static boolean safestRelease(Object msg) { 22 | try { 23 | return (msg instanceof ReferenceCounted) 24 | && ((ReferenceCounted) msg).refCnt() > 0 25 | && ((ReferenceCounted) msg).release(); 26 | } catch (Throwable t) { 27 | LOGGER.warn("Failed to release reference counted object: {}, cause: {}", msg, t.toString()); 28 | return false; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /services-transport-parent/services-transport-rsocket/src/main/java/io/scalecube/services/transport/rsocket/ServiceMessageByteBufDataDecoder.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.transport.rsocket; 2 | 3 | import io.scalecube.services.api.ServiceMessage; 4 | import io.scalecube.services.transport.api.ServiceMessageDataDecoder; 5 | 6 | public class ServiceMessageByteBufDataDecoder implements ServiceMessageDataDecoder { 7 | 8 | @Override 9 | public ServiceMessage apply(ServiceMessage message, Class dataType) { 10 | return ServiceMessageCodec.decodeData(message, dataType); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /services-transport-parent/services-transport-rsocket/src/main/resources/META-INF/services/io.scalecube.services.transport.api.ServiceMessageDataDecoder: -------------------------------------------------------------------------------- 1 | io.scalecube.services.transport.rsocket.ServiceMessageByteBufDataDecoder 2 | -------------------------------------------------------------------------------- /services/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | scalecube-services-parent 7 | io.scalecube 8 | 2.13.2-SNAPSHOT 9 | 10 | 11 | scalecube-services 12 | 13 | 14 | 15 | io.scalecube 16 | scalecube-services-api 17 | ${project.version} 18 | 19 | 20 | 21 | io.scalecube 22 | scalecube-services-discovery 23 | ${project.version} 24 | test 25 | 26 | 27 | io.scalecube 28 | scalecube-services-testlib 29 | ${project.version} 30 | test 31 | 32 | 33 | io.scalecube 34 | scalecube-services-transport-rsocket 35 | ${project.version} 36 | test 37 | 38 | 39 | io.scalecube 40 | scalecube-services-transport-jackson 41 | ${project.version} 42 | test 43 | 44 | 45 | io.scalecube 46 | scalecube-codec-jackson 47 | test 48 | 49 | 50 | io.scalecube 51 | scalecube-transport-netty 52 | test 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /services/src/main/java/io/scalecube/services/files/AddFileRequest.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.files; 2 | 3 | import java.io.File; 4 | import java.time.Duration; 5 | import java.util.StringJoiner; 6 | 7 | public class AddFileRequest { 8 | 9 | private final File file; 10 | private final Duration ttl; 11 | 12 | public AddFileRequest(File file) { 13 | this(file, null); 14 | } 15 | 16 | public AddFileRequest(File file, Duration ttl) { 17 | this.file = file; 18 | this.ttl = ttl; 19 | } 20 | 21 | public File file() { 22 | return file; 23 | } 24 | 25 | public Duration ttl() { 26 | return ttl; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return new StringJoiner(", ", AddFileRequest.class.getSimpleName() + "[", "]") 32 | .add("file=" + file) 33 | .add("ttl=" + ttl) 34 | .toString(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /services/src/main/java/io/scalecube/services/files/FileService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.files; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import reactor.core.publisher.Mono; 6 | 7 | /** 8 | * Service interface for adding files locally, those added files will be accessible by {@link 9 | * FileStreamer}. Typical usage: client defines an app service with injected {@link FileService}, 10 | * client generates a file in the app service, then calls {@link #addFile(AddFileRequest)}, then 11 | * returns result (file path qualifier) all the way back to the caller of app service. On the caller 12 | * side file path qualifier gets combined with http-gateway address, and then url for file streaming 13 | * is ready. Then caller side has time to download a file until file gets expired. 14 | */ 15 | @Service 16 | public interface FileService { 17 | 18 | /** 19 | * Adding file and returning path qualifier for the added file. {@link AddFileRequest} must 20 | * contain {@code file} that exists, that is not directory, and must have valid path, another 21 | * parameter - {@code ttl} (optional) represents time after which file will be deleted. Returned 22 | * file path qualifier comes as: {@code v1/scalecube.endpoints/${microservices:id}/files/:name}, 23 | * for example: {@code 24 | * v1/scalecube.endpoints/19e3afc1-fa46-4a55-8a41-1bdf4afc8a5b/files/report_03_01_2025.txt} 25 | * 26 | * @param request request 27 | * @return async result with path qualifier 28 | */ 29 | @ServiceMethod 30 | Mono addFile(AddFileRequest request); 31 | } 32 | -------------------------------------------------------------------------------- /services/src/main/java/io/scalecube/services/files/FileStreamer.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.files; 2 | 3 | import io.scalecube.services.annotations.RestMethod; 4 | import io.scalecube.services.annotations.Service; 5 | import io.scalecube.services.annotations.ServiceMethod; 6 | import io.scalecube.services.annotations.Tag; 7 | import reactor.core.publisher.Flux; 8 | 9 | /** 10 | * System service interface for streaming files after they have been added locally with {@link 11 | * FileService#addFile(AddFileRequest)}. NOTE: this is system service interface, clients are not 12 | * supposed to inject it into their app services and call it directly. 13 | */ 14 | @Service(FileStreamer.NAMESPACE) 15 | public interface FileStreamer { 16 | 17 | String NAMESPACE = "v1/endpoints"; 18 | 19 | @Tag(key = "Content-Type", value = "application/file") 20 | @RestMethod("GET") 21 | @ServiceMethod("${microservices:id}/files/:name") 22 | Flux streamFile(); 23 | } 24 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/PrincipalImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services; 2 | 3 | import io.scalecube.services.auth.Principal; 4 | import java.util.List; 5 | import java.util.Objects; 6 | import java.util.StringJoiner; 7 | 8 | public record PrincipalImpl(String role, List permissions) implements Principal { 9 | 10 | @Override 11 | public boolean hasRole(String role) { 12 | return Objects.equals(this.role, role); 13 | } 14 | 15 | @Override 16 | public boolean hasPermission(String permission) { 17 | return permissions != null && permissions.contains(permission); 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return new StringJoiner(", ", PrincipalImpl.class.getSimpleName() + "[", "]") 23 | .add("role='" + role + "'") 24 | .add("permissions=" + permissions) 25 | .toString(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/ServiceDiscoverySubscriberTest.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertNotNull; 4 | import static org.mockito.Mockito.spy; 5 | import static org.mockito.Mockito.verifyNoInteractions; 6 | 7 | import io.scalecube.services.Microservices.Context; 8 | import io.scalecube.services.annotations.Subscriber; 9 | import io.scalecube.services.discovery.api.ServiceDiscoveryEvent; 10 | import java.util.concurrent.atomic.AtomicReference; 11 | import org.junit.jupiter.api.Test; 12 | import org.reactivestreams.Subscription; 13 | import reactor.core.publisher.BaseSubscriber; 14 | 15 | public class ServiceDiscoverySubscriberTest { 16 | 17 | @Test 18 | void testRegisterNonDiscoveryCoreSubscriber() { 19 | final NonDiscoverySubscriber1 discoverySubscriber1 = spy(new NonDiscoverySubscriber1()); 20 | final NonDiscoverySubscriber2 discoverySubscriber2 = spy(new NonDiscoverySubscriber2()); 21 | 22 | Microservices.start(new Context().services(discoverySubscriber1, discoverySubscriber2)); 23 | 24 | verifyNoInteractions(discoverySubscriber1, discoverySubscriber2); 25 | } 26 | 27 | @Test 28 | void testRegisterNotMatchingTypeDiscoveryCoreSubscriber() { 29 | final NotMatchingTypeDiscoverySubscriber discoverySubscriber = 30 | spy(new NotMatchingTypeDiscoverySubscriber()); 31 | 32 | Microservices.start(new Context().services(discoverySubscriber)); 33 | 34 | verifyNoInteractions(discoverySubscriber); 35 | } 36 | 37 | @Test 38 | void testRegisterDiscoveryCoreSubscriber() { 39 | final AtomicReference subscriptionReference = new AtomicReference<>(); 40 | final NormalDiscoverySubscriber normalDiscoverySubscriber = 41 | new NormalDiscoverySubscriber(subscriptionReference); 42 | 43 | Microservices.start(new Context().services(normalDiscoverySubscriber)); 44 | 45 | assertNotNull(subscriptionReference.get(), "subscription"); 46 | } 47 | 48 | private static class SomeType {} 49 | 50 | private static class NonDiscoverySubscriber1 extends BaseSubscriber {} 51 | 52 | private static class NonDiscoverySubscriber2 extends BaseSubscriber {} 53 | 54 | @Subscriber 55 | private static class NotMatchingTypeDiscoverySubscriber 56 | extends BaseSubscriber {} 57 | 58 | @Subscriber(ServiceDiscoveryEvent.class) 59 | private static class NormalDiscoverySubscriber extends BaseSubscriber { 60 | 61 | private final AtomicReference subscriptionReference; 62 | 63 | private NormalDiscoverySubscriber(AtomicReference subscriptionReference) { 64 | this.subscriptionReference = subscriptionReference; 65 | } 66 | 67 | @Override 68 | protected void hookOnSubscribe(Subscription subscription) { 69 | subscriptionReference.set(subscription); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/routings/sut/CanaryService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.routings.sut; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import io.scalecube.services.sut.GreetingRequest; 6 | import io.scalecube.services.sut.GreetingResponse; 7 | import reactor.core.publisher.Mono; 8 | 9 | @Service 10 | public interface CanaryService { 11 | 12 | @ServiceMethod 13 | Mono greeting(GreetingRequest request); 14 | } 15 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/routings/sut/DummyRouter.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.routings.sut; 2 | 3 | import io.scalecube.services.ServiceReference; 4 | import io.scalecube.services.api.ServiceMessage; 5 | import io.scalecube.services.registry.api.ServiceRegistry; 6 | import io.scalecube.services.routing.Router; 7 | import java.util.Objects; 8 | import java.util.Optional; 9 | 10 | public class DummyRouter implements Router { 11 | private Object empty; 12 | 13 | public DummyRouter() { 14 | Objects.requireNonNull(this.empty); 15 | } 16 | 17 | @Override 18 | public Optional route(ServiceRegistry serviceRegistry, ServiceMessage request) { 19 | return Optional.empty(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/routings/sut/GreetingServiceImplA.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.routings.sut; 2 | 3 | import io.scalecube.services.sut.GreetingRequest; 4 | import io.scalecube.services.sut.GreetingResponse; 5 | import reactor.core.publisher.Mono; 6 | 7 | public final class GreetingServiceImplA implements CanaryService { 8 | 9 | @Override 10 | public Mono greeting(GreetingRequest name) { 11 | return Mono.just(new GreetingResponse("SERVICE_A_TALKING - hello to: " + name)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/routings/sut/GreetingServiceImplB.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.routings.sut; 2 | 3 | import io.scalecube.services.sut.GreetingRequest; 4 | import io.scalecube.services.sut.GreetingResponse; 5 | import reactor.core.publisher.Mono; 6 | 7 | public final class GreetingServiceImplB implements CanaryService { 8 | 9 | @Override 10 | public Mono greeting(GreetingRequest name) { 11 | return Mono.just(new GreetingResponse("SERVICE_B_TALKING - hello to: " + name)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/routings/sut/RandomCollection.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.routings.sut; 2 | 3 | import java.util.NavigableMap; 4 | import java.util.TreeMap; 5 | import java.util.concurrent.ThreadLocalRandom; 6 | 7 | public class RandomCollection { 8 | private final NavigableMap map = new TreeMap<>(); 9 | private double total = 0; 10 | 11 | /** 12 | * Add weighted result. 13 | * 14 | * @param weight weight 15 | * @param result result 16 | */ 17 | public void add(double weight, E result) { 18 | if (weight > 0 && !map.containsValue(result)) { 19 | map.put(total += weight, result); 20 | } 21 | } 22 | 23 | public E next() { 24 | double value = ThreadLocalRandom.current().nextDouble() * total; 25 | return map.ceilingEntry(value).getValue(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/routings/sut/TagService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.routings.sut; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import io.scalecube.services.annotations.Tag; 6 | import reactor.core.publisher.Flux; 7 | 8 | @Service 9 | @Tag(key = "tagA", value = "a") 10 | @Tag(key = "tagB", value = "b") 11 | @FunctionalInterface 12 | public interface TagService { 13 | 14 | @ServiceMethod 15 | @Tag(key = "methodTagA", value = "a") 16 | Flux upperCase(Flux input); 17 | } 18 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/routings/sut/WeightedRandomRouter.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.routings.sut; 2 | 3 | import io.scalecube.services.ServiceReference; 4 | import io.scalecube.services.api.ServiceMessage; 5 | import io.scalecube.services.registry.api.ServiceRegistry; 6 | import io.scalecube.services.routing.Router; 7 | import java.util.Optional; 8 | 9 | public class WeightedRandomRouter implements Router { 10 | 11 | @Override 12 | public Optional route(ServiceRegistry serviceRegistry, ServiceMessage request) { 13 | RandomCollection weightedRandom = new RandomCollection<>(); 14 | serviceRegistry 15 | .lookupService(request) 16 | .forEach(sr -> weightedRandom.add(Double.valueOf(sr.tags().get("Weight")), sr)); 17 | return Optional.of(weightedRandom.next()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/sut/AnnotationService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.sut; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import io.scalecube.services.discovery.api.ServiceDiscoveryEvent; 6 | import reactor.core.publisher.Flux; 7 | 8 | @Service(AnnotationService.SERVICE_NAME) 9 | public interface AnnotationService { 10 | 11 | String SERVICE_NAME = "annotations"; 12 | 13 | @ServiceMethod 14 | Flux serviceDiscoveryEventTypes(); 15 | } 16 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/sut/AnnotationServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.sut; 2 | 3 | import io.scalecube.services.Microservices; 4 | import io.scalecube.services.annotations.AfterConstruct; 5 | import io.scalecube.services.discovery.api.ServiceDiscoveryEvent; 6 | import reactor.core.publisher.Flux; 7 | import reactor.core.publisher.Sinks; 8 | 9 | public class AnnotationServiceImpl implements AnnotationService { 10 | 11 | private Sinks.Many serviceDiscoveryEvents; 12 | 13 | @AfterConstruct 14 | void init(Microservices microservices) { 15 | this.serviceDiscoveryEvents = Sinks.many().replay().all(); 16 | microservices 17 | .listenDiscovery() 18 | .subscribe( 19 | serviceDiscoveryEvents::tryEmitNext, 20 | serviceDiscoveryEvents::tryEmitError, 21 | serviceDiscoveryEvents::tryEmitComplete); 22 | } 23 | 24 | @Override 25 | public Flux serviceDiscoveryEventTypes() { 26 | return serviceDiscoveryEvents.asFlux().onBackpressureBuffer().map(ServiceDiscoveryEvent::type); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/sut/CoarseGrainedService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.sut; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import reactor.core.publisher.Mono; 6 | 7 | @Service 8 | public interface CoarseGrainedService { 9 | 10 | @ServiceMethod 11 | Mono callGreeting(String name); 12 | 13 | @ServiceMethod 14 | Mono callGreetingTimeout(String request); 15 | 16 | @ServiceMethod 17 | Mono callGreetingWithDispatcher(String request); 18 | } 19 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/sut/CoarseGrainedServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.sut; 2 | 3 | import io.scalecube.services.Microservices; 4 | import io.scalecube.services.annotations.Inject; 5 | import io.scalecube.services.api.ServiceMessage; 6 | import java.time.Duration; 7 | import reactor.core.publisher.Mono; 8 | 9 | public class CoarseGrainedServiceImpl implements CoarseGrainedService { 10 | 11 | @Inject public GreetingService greetingServiceTimeout; 12 | 13 | @Inject private GreetingService greetingService; 14 | 15 | @Inject private Microservices microservices; 16 | 17 | @Override 18 | public Mono callGreeting(String name) { 19 | return this.greetingService.greeting(name); 20 | } 21 | 22 | @Override 23 | public Mono callGreetingTimeout(String request) { 24 | System.out.println("callGreetingTimeout: " + request); 25 | return Mono.from( 26 | this.greetingServiceTimeout.greetingRequestTimeout( 27 | new GreetingRequest(request, Duration.ofSeconds(3)))) 28 | .timeout(Duration.ofSeconds(1)) 29 | .map(GreetingResponse::result); 30 | } 31 | 32 | @Override 33 | public Mono callGreetingWithDispatcher(String request) { 34 | return microservices 35 | .call() 36 | .requestOne( 37 | ServiceMessage.builder() 38 | .qualifier(GreetingService.SERVICE_NAME, "greeting") 39 | .data("joe") 40 | .build()) 41 | .map(ServiceMessage::data); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/sut/EmptyGreetingRequest.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.sut; 2 | 3 | public class EmptyGreetingRequest {} 4 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/sut/EmptyGreetingResponse.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.sut; 2 | 3 | public class EmptyGreetingResponse {} 4 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/sut/GreetingRequest.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.sut; 2 | 3 | import java.time.Duration; 4 | import java.util.StringJoiner; 5 | 6 | public final class GreetingRequest { 7 | 8 | private String name; 9 | private Duration duration; 10 | 11 | public GreetingRequest() {} 12 | 13 | public GreetingRequest(String name) { 14 | this.name = name; 15 | this.duration = null; 16 | } 17 | 18 | public GreetingRequest(String name, Duration duration) { 19 | this.name = name; 20 | this.duration = duration; 21 | } 22 | 23 | public String name() { 24 | return name; 25 | } 26 | 27 | public GreetingRequest name(String name) { 28 | this.name = name; 29 | return this; 30 | } 31 | 32 | public Duration duration() { 33 | return duration; 34 | } 35 | 36 | public GreetingRequest duration(Duration duration) { 37 | this.duration = duration; 38 | return this; 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return new StringJoiner(", ", GreetingRequest.class.getSimpleName() + "[", "]") 44 | .add("name='" + name + "'") 45 | .add("duration=" + duration) 46 | .toString(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/sut/GreetingResponse.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.sut; 2 | 3 | import java.util.StringJoiner; 4 | 5 | public final class GreetingResponse { 6 | 7 | private String result; 8 | private String sender; 9 | 10 | public GreetingResponse() {} 11 | 12 | public GreetingResponse(String result) { 13 | this.result = result; 14 | this.sender = null; 15 | } 16 | 17 | public GreetingResponse(String result, String sender) { 18 | this.result = result; 19 | this.sender = sender; 20 | } 21 | 22 | public String result() { 23 | return result; 24 | } 25 | 26 | public GreetingResponse result(String result) { 27 | this.result = result; 28 | return this; 29 | } 30 | 31 | public String sender() { 32 | return sender; 33 | } 34 | 35 | public GreetingResponse sender(String sender) { 36 | this.sender = sender; 37 | return this; 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return new StringJoiner(", ", GreetingResponse.class.getSimpleName() + "[", "]") 43 | .add("result='" + result + "'") 44 | .add("sender='" + sender + "'") 45 | .toString(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/sut/QuoteService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.sut; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import reactor.core.publisher.Flux; 6 | import reactor.core.publisher.Mono; 7 | 8 | @Service(QuoteService.NAME) 9 | public interface QuoteService { 10 | 11 | String NAME = "quote-service"; 12 | 13 | @ServiceMethod 14 | Flux quotes(); 15 | 16 | @ServiceMethod 17 | Flux snapshot(int size); 18 | 19 | @ServiceMethod 20 | Mono justOne(); 21 | 22 | @ServiceMethod 23 | Flux scheduled(int interval); 24 | 25 | @ServiceMethod 26 | Mono justNever(); 27 | 28 | @ServiceMethod 29 | Flux justManyNever(); 30 | 31 | @ServiceMethod 32 | Flux onlyOneAndThenNever(); 33 | } 34 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/sut/SimpleQuoteService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.sut; 2 | 3 | import java.time.Duration; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | import java.util.stream.IntStream; 6 | import reactor.core.publisher.Flux; 7 | import reactor.core.publisher.Mono; 8 | 9 | public class SimpleQuoteService implements QuoteService { 10 | 11 | final AtomicInteger counter = new AtomicInteger(1); 12 | 13 | public SimpleQuoteService() {} 14 | 15 | @Override 16 | public Mono justOne() { 17 | return Mono.just("1"); 18 | } 19 | 20 | @Override 21 | public Flux scheduled(int interval) { 22 | return Flux.interval(Duration.ofMillis(100)).map(s -> "quote : " + counter.incrementAndGet()); 23 | } 24 | 25 | @Override 26 | public Flux quotes() { 27 | return Flux.interval(Duration.ofMillis(100)).map(s -> "quote : " + counter.incrementAndGet()); 28 | } 29 | 30 | @Override 31 | public Flux snapshot(int size) { 32 | return Flux.fromStream(IntStream.range(0, size).boxed().map(i -> "tick:" + i)); 33 | } 34 | 35 | @Override 36 | public Mono justNever() { 37 | return Mono.never(); 38 | } 39 | 40 | @Override 41 | public Flux justManyNever() { 42 | return Flux.never(); 43 | } 44 | 45 | @Override 46 | public Flux onlyOneAndThenNever() { 47 | return Flux.merge(Mono.just("only first"), Mono.never()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/sut/security/CompositeSecuredService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.sut.security; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import reactor.core.publisher.Mono; 6 | 7 | @Service("compositeSecured") 8 | public interface CompositeSecuredService { 9 | 10 | // Services secured by code in method body 11 | 12 | @ServiceMethod 13 | Mono helloComposite(); 14 | } 15 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/sut/security/CompositeSecuredServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.sut.security; 2 | 3 | import io.scalecube.services.RequestContext; 4 | import io.scalecube.services.auth.Principal; 5 | import io.scalecube.services.auth.Secured; 6 | import io.scalecube.services.exceptions.ForbiddenException; 7 | import java.util.ArrayList; 8 | import java.util.Collection; 9 | import java.util.List; 10 | import java.util.StringJoiner; 11 | import reactor.core.publisher.Mono; 12 | 13 | @Secured 14 | public class CompositeSecuredServiceImpl implements CompositeSecuredService { 15 | 16 | // Services secured by code in method body 17 | 18 | @Secured 19 | @Override 20 | public Mono helloComposite() { 21 | return RequestContext.deferContextual() 22 | .doOnNext( 23 | context -> { 24 | if (!context.hasPrincipal()) { 25 | throw new ForbiddenException("Insufficient permissions"); 26 | } 27 | 28 | final var principal = context.principal(); 29 | final var permissions = principal.permissions(); 30 | 31 | if (!permissions.contains("helloComposite")) { 32 | throw new ForbiddenException("Insufficient permissions"); 33 | } 34 | }) 35 | .then(); 36 | } 37 | 38 | public static class CompositePrincipalImpl implements Principal { 39 | 40 | private final Principal principal; 41 | private final List permissions = new ArrayList<>(); 42 | 43 | public CompositePrincipalImpl(Principal principal, List permissions) { 44 | this.principal = principal; 45 | if (principal.permissions() != null) { 46 | this.permissions.addAll(principal.permissions()); 47 | } 48 | if (permissions != null) { 49 | this.permissions.addAll(permissions); 50 | } 51 | } 52 | 53 | @Override 54 | public String role() { 55 | return principal.role(); 56 | } 57 | 58 | @Override 59 | public boolean hasRole(String role) { 60 | return principal.hasRole(role); 61 | } 62 | 63 | @Override 64 | public Collection permissions() { 65 | return permissions; 66 | } 67 | 68 | @Override 69 | public boolean hasPermission(String permission) { 70 | return permissions.contains(permission); 71 | } 72 | 73 | @Override 74 | public String toString() { 75 | return new StringJoiner(", ", CompositePrincipalImpl.class.getSimpleName() + "[", "]") 76 | .add("principal=" + principal) 77 | .add("permissions=" + permissions) 78 | .toString(); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/sut/security/SecuredService.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.sut.security; 2 | 3 | import io.scalecube.services.annotations.Service; 4 | import io.scalecube.services.annotations.ServiceMethod; 5 | import reactor.core.publisher.Mono; 6 | 7 | @Service("secured") 8 | public interface SecuredService { 9 | 10 | // Services secured by code in method body 11 | 12 | @ServiceMethod 13 | Mono invokeWithRoleOrPermissions(); 14 | 15 | // Services secured by annotations in method body 16 | 17 | @ServiceMethod 18 | Mono readWithAllowedRoleAnnotation(); 19 | 20 | @ServiceMethod 21 | Mono writeWithAllowedRoleAnnotation(); 22 | } 23 | -------------------------------------------------------------------------------- /services/src/test/java/io/scalecube/services/sut/security/SecuredServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.services.sut.security; 2 | 3 | import io.scalecube.services.RequestContext; 4 | import io.scalecube.services.auth.AllowedRole; 5 | import io.scalecube.services.auth.Secured; 6 | import io.scalecube.services.exceptions.ForbiddenException; 7 | import reactor.core.publisher.Mono; 8 | 9 | @Secured 10 | public class SecuredServiceImpl implements SecuredService { 11 | 12 | // Services secured by code in method body 13 | 14 | @Secured 15 | @Override 16 | public Mono invokeWithRoleOrPermissions() { 17 | return RequestContext.deferContextual() 18 | .doOnNext( 19 | context -> { 20 | if (!context.hasPrincipal()) { 21 | throw new ForbiddenException("Insufficient permissions"); 22 | } 23 | 24 | final var principal = context.principal(); 25 | final var role = principal.role(); 26 | final var permissions = principal.permissions(); 27 | 28 | if (role == null && permissions == null) { 29 | throw new ForbiddenException("Insufficient permissions"); 30 | } 31 | if (role != null && !role.equals("invoker") && !role.equals("caller")) { 32 | throw new ForbiddenException("Insufficient permissions"); 33 | } 34 | if (permissions != null && !permissions.contains("invoke")) { 35 | throw new ForbiddenException("Insufficient permissions"); 36 | } 37 | }) 38 | .then(); 39 | } 40 | 41 | // Services secured by annotations in method body 42 | 43 | @Secured 44 | @AllowedRole( 45 | name = "admin", 46 | permissions = {"read"}) 47 | @Override 48 | public Mono readWithAllowedRoleAnnotation() { 49 | return Mono.empty(); 50 | } 51 | 52 | @Secured 53 | @AllowedRole( 54 | name = "admin", 55 | permissions = {"write"}) 56 | @Override 57 | public Mono writeWithAllowedRoleAnnotation() { 58 | return Mono.empty(); 59 | } 60 | } 61 | --------------------------------------------------------------------------------