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 |
--------------------------------------------------------------------------------