Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java guide #83

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions content-docs/guides/guide-shared/_preface.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
We will be creating a chat application with a server and a client.

The chat client will have the following functionality:
- Private messages between users
- Joining and sending messages to channels
- Uploading/Downloading files
- Getting server and client statistics (e.g. number of channels)

Since the emphasis is on showcasing as much RSocket functionality as possible, some examples may be either a bit contrived, or
be possible to implement in a different way using RSocket. This is left as an exercise to the reader.
4 changes: 4 additions & 0 deletions content-docs/guides/guide-shared/_routing.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
In the previous step we added a single request-response handler. In order to allow more than one functionality to use this handler,
(e.g. login, messages, join/leave chanel) they need to be distinguished from each other. To achieve this, each request to the server will
be identified by a [route](https://github.com/rsocket/rsocket/blob/master/Extensions/Routing.md). This is similar to paths in an HTTP URL where
each URL may handle one of the HTTP methods (eg. GET, POST).
1 change: 1 addition & 0 deletions content-docs/guides/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ In this section you will find guides related to working with and consuming the v

- [`rsocket-js`](./rsocket-js/index.mdx)
- [`rsocket-py`](./rsocket-py/index.mdx)
- [`rsocket-java`](./rsocket-java/index.mdx)
11 changes: 11 additions & 0 deletions content-docs/guides/rsocket-java/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
slug: /guides/rsocket-java
title: rsocket-java
sidebar_label: Introduction
---

The java `rsocket` package implements the 1.0 version of the [RSocket protocol](/about/protocol).

## Guides

See [Tutorial](/guides/rsocket-java/tutorial) for a step by step construction of an application.
127 changes: 127 additions & 0 deletions content-docs/guides/rsocket-java/tutorial/00-base.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
---
slug: /guides/rsocket-java/tutorial/base
title: Getting started
sidebar_label: Getting started
---

## Application structure

In this step we will set up a minimal code required for both the server and the client.

The application will be composed of:
- Server side
- Client side
- Shared code

See resulting code on [GitHub](https://github.com/rsocket/rsocket-java/tree/master/examples/tutorial/step0)

## Server side

We will set up a simple server to accept connections and respond to the client sending the user's name.
The server will listen on TCP port 6565.

Below is the code for the <b>ServerApplication</b> class:

```java
package io.rsocket.guide;

import io.rsocket.Payload;
import io.rsocket.RSocket;
import io.rsocket.SocketAcceptor;
import io.rsocket.core.RSocketServer;
import io.rsocket.transport.netty.server.TcpServerTransport;
import io.rsocket.util.DefaultPayload;
import reactor.core.publisher.Mono;

public class ServerApplication {

public static void main(String[] args) {
final var transport = TcpServerTransport.create(6565);

final SocketAcceptor socketAcceptor = (setup, sendingSocket) -> Mono.just(new RSocket() {
public Mono<Payload> requestResponse(Payload payload) {
return Mono.just(DefaultPayload.create("Welcome to chat, " + payload.getDataUtf8()));
}
});

RSocketServer.create()
.acceptor(socketAcceptor)
.bind(transport)
.block()
.onClose()
.block();
}
}
```

*Lines 22-27* start an RSocket TCP server listening on localhost:6565.

The 2 parameters passed are:
- transport : An instance of a supported connection method. In this case it is at instance of `TcpServerTransport` created in *Line 14*.
- socketAcceptor: A callable which returns an `RSocket` instance wrapped in a `Mono`. This will be used to respond to the client's requests.

*Lines 16-20* Define the `RSocket` service with a single `requestResponse` endpoint at *Lines *17-19*.

The `requestResponse` method receives a single argument containing the payload.
It is an instance of a `Payload` class which contains the data and metadata of the request. The data property is assumed to contain
a UTF-8 encoded string of the username, so is retrieved using `getDataUtf8`.

*Line 18* Takes the username from the `Payload` instance's data and returns it to the client with a "welcome" message.

A response is created using helper methods:
- `DefaultPayload::create` : This creates a payload which is the standard object which wraps all data transferred over RSocket. In our case, only the data property is set.
- `Mono::just` : All RSocket responses must be in the form of streams, either a `Flux` or a `Mono`.

In the example, only the `requestResponse` method of `RSocket` is overridden. In this class, we can override the methods
which handle the 4 RSocket request types:
- `requestResponse`
- `requestStream`
- `requestChannel`
- `fireAndForget`

Check the `RSocket` for other methods which can be implemented.

Next we will look at a simple client which connects to this server.

## Client side

The client will connect to the server, send a single *response* request and disconnect.

Below is the code for the <b>ClientApplication</b> class:

```java
package io.rsocket.guide;

import io.rsocket.core.RSocketConnector;
import io.rsocket.transport.netty.client.TcpClientTransport;
import io.rsocket.util.DefaultPayload;

import java.time.Duration;

public class ClientApplication {

public static void main(String[] args) {
final var transport = TcpClientTransport.create("localhost", 6565);

final var rSocket = RSocketConnector.create()
.connect(transport)
.block();

final var payload = DefaultPayload.create("George");

rSocket.requestResponse(payload)
.doOnNext(response -> System.out.println(response.getDataUtf8()))
.block(Duration.ofMinutes(1));
}
}
```

*Line 12* instantiates a TCP connection to localhost on port 6565, similar to the one in `ServerApplication`.

*Lines 14-16* instantiates an `RSocket` client.

*Line 18* Wraps the username "George" which the client will send to the server in a `Payload` using the `DefaultPayload.create` factory method

Finally, *Line 20* sends the request to the server and prints (*Line 21*) the received response.

Since RSocket is reactive, and we want to wait for the request to finish before quitting, a call to `block(Duration.ofMinutes(1))` is added to block for 1 minute.
144 changes: 144 additions & 0 deletions content-docs/guides/rsocket-java/tutorial/01-request_routing.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
---
slug: /guides/rsocket-java/tutorial/request_routing
title: Request routing
sidebar_label: Request routing
---

import Routing from '../../guide-shared/_routing.mdx'

<Routing/>

See resulting code on [GitHub](https://github.com/rsocket/rsocket-java/tree/master/examples/tutorial/step1)

## Server side

We will modify the example from the previous step into a routed request response.

### Routing request handler

Below is the modified code for instantiating `SocketAcceptor`:

```java
final SocketAcceptor socketAcceptor = (setup, sendingSocket) -> Mono.just(new RSocket() {
public Mono<Payload> requestResponse(Payload payload) {
final var route = requireRoute(payload);

switch (route) {
case "login":
return Mono.just(DefaultPayload.create("Welcome to chat, " + payload.getDataUtf8()));
}

throw new RuntimeException("Unknown requestResponse route " + route);
}

private String requireRoute(Payload payload) {
final var metadata = payload.sliceMetadata();
final CompositeMetadata compositeMetadata = new CompositeMetadata(metadata, false);

for (CompositeMetadata.Entry metadatum : compositeMetadata) {
if (Objects.requireNonNull(metadatum.getMimeType())
.equals(WellKnownMimeType.MESSAGE_RSOCKET_ROUTING.getString())) {
return new RoutingMetadata(metadatum.getContent()).iterator().next();
}
}

throw new IllegalStateException();
}
});
```

The `requestResponse` method in *Lines 2-11* is modified to first parse the route from the `Payload` metadata, using the `requireRoute` helper method.
For now there is only a single case, the "login" route, which returns the same response as in the previous section of this guide.

*Line 10* raises an exception if no known route is supplied.

The `requireRoute` method parses the `Payload` metadata using the `CompositeMetadata` class. If any of the metadata items is of routing type, its value is returned.
If no routing metadata is found (*Line 24*) an exception is thrown.

## Client side

Let's modify the client side to call this new routed request. For readability and maintainability, we will create a `Client`
which will wrap the RSocket client and provide the methods for interacting with the server.

### Client class

Below is the complete code for the new `Client` class:

```java
package io.rsocket.guide;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
import io.rsocket.Payload;
import io.rsocket.RSocket;
import io.rsocket.metadata.CompositeMetadataCodec;
import io.rsocket.metadata.TaggingMetadataCodec;
import io.rsocket.metadata.WellKnownMimeType;
import io.rsocket.util.DefaultPayload;
import reactor.core.publisher.Mono;

import java.util.List;

public class Client {

private final RSocket rSocket;

public Client(RSocket rSocket) {
this.rSocket = rSocket;
}

public Mono<Payload> login(String username) {
final Payload payload = DefaultPayload.create(
Unpooled.wrappedBuffer(username.getBytes()),
route("login")
);
return rSocket.requestResponse(payload);
}

private static CompositeByteBuf route(String route) {
final var metadata = ByteBufAllocator.DEFAULT.compositeBuffer();

CompositeMetadataCodec.encodeAndAddMetadata(
metadata,
ByteBufAllocator.DEFAULT,
WellKnownMimeType.MESSAGE_RSOCKET_ROUTING,
TaggingMetadataCodec.createTaggingContent(ByteBufAllocator.DEFAULT, List.of(route))
);

return metadata;
}
}
```

*Lines 17-45* define our new `Client` which will encapsulate the methods used to interact with the chat server.

*Lines 25-31* define a `login` method. It uses the `route` helper method defined later in the class to create the routing metadata, which is added to the `Payload`.
This ensures the payload is routed to the method registered on the server side in the previous step.

The `route` method defined in *Lines 33-44*, creates a composite metadata item (*Line 34*) and adds the route metadata to it (*Lines 36-41*).

### Test the new functionality

Let's modify the `ClientApplication` class to test our new `Client`:

```java
final var rSocket = RSocketConnector.create()
.metadataMimeType(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString())
.connect(transport)
.block();

final var client = new Client(rSocket);

client.login("George")
.doOnNext(response -> System.out.println(response.getDataUtf8()))
.block(Duration.ofMinutes(10));
```

The `RSocket` instantiation is modified, and in *Line 2* sets the `metadataMimeType` type to be COMPOSITE_METADATA.
This is required for multiple elements in the `Payload` metadata, which includes the routing information.

*Lines 6* instantiates a `Client`, passing it the `RSocket`

*Lines 8-10* call the `login` method, and prints the response.
Loading