Skip to content

kekyo/Fluorite

Repository files navigation

Fluorite

Fluorite

Fluorite - Simplest and fully-customizable RPC standalone infrastructure on .NET

Project Status: WIP – Initial development is in progress, but there has not yet been a stable, usable release suitable for the public.

NuGet

Package main devel Description
Fluorite NuGet Fluorite MyGet Fluorite Meta-package (Provides automated generating static proxy / Experimental)
Fluorite.Dynamic NuGet Fluorite.Dynamic MyGet Fluorite.Dynamic Meta-package (Provides automated generating dynamic proxy)

Sub packages

Package main devel Description
Fluorite.Core NuGet Fluorite.Core MyGet Fluorite.Core Independed core engine
Fluorite.Serializer NuGet Fluorite.Serializer MyGet Fluorite.Serializer Default serializer implementation (Newtonsoft.Json)
Fluorite.Transport NuGet Fluorite.Transport MyGet Fluorite.Transport Default transport implementation (System.Net.WebSockets)
Fluorite.Build NuGet Fluorite.Build MyGet Fluorite.Build Automated static proxy generator at building time

CI

main devel
Fluorite CI build (main) Fluorite CI build (devel)

What is this ?

An implementation of bi-directional/complementary/asynchronous RPC (remote procedure call) controller with customizable serializer/transport interface.

This is simple diagram for user side Fluorite architecture:

Fluorite Diagram

Fluorite doesn't have any depending other large libraries (For example: ASP.NET). You can easy integrate flexible RPC messaging system on your own application.

Features

  • Can do truly bi-directional operation between server and client.
    • Complementary interface.
    • With overlapping calls and will do non-blocking (if transport has capability).
  • Fully asynchronous sending/receiving operation with ValueTask<T> type.
  • RPC form on custom interface definition.
    • You can choose static proxy (at building time) or dynamic proxy (at runtime).
    • Or you can attach your own custom proxy generator.
  • Can attach your own custom serializer likes Json or another form by simple interface.
    • Json, Bson, XML, Message Pack, Protocol Buffer and etc...
    • Will make simpler logical message format.
  • Can attach your own custom transport likes WebSocket or another protocol by simple interface.
    • WebSocket, TCP/UDP direct, Pipe, IPC, MQ, Persistence data and etc...

Getting started

Default configuration (meta-package) is applied with:

  • Will use serializer Newtonsoft.Json.
  • Will use transport System.Net.WebSockets.
    • Client side: ClientWebSocket.
    • Server side: WebSocket on HttpListener.

NOTE: When running on Windows platform, you have to configure HttpListener/HTTP.SYS related setup protocol on server side. For example:

  1. netsh http add urlacl url=http://+:4649/ user=everyone
  2. netsh advfirewall firewall add rule name="Fluorite HTTP" dir=in action=allow
  3. netsh advfirewall firewall set rule name="Fluorite HTTP" new program=system profile=private protocol=tcp localport=4649

Custom RPC interface definition

We can use independent platform such as netstandard2.0 or likes.

// nuget install Fluorite
// (or Fluorite.Build using static proxy generator)
using Fluorite;

// You can use custom types each arguments/return value, constraint depending used serializer.
// (Default serializer is Newtonsoft.Json)
public sealed class Item
{
    public int Id;
    public string Label;
    public int Price;
}

// Require inherits `IHost` and method returns ValueTask<T> or ValueTask.
public interface IPartsShop : IHost
{
    ValueTask<Item[]> GetItemsAsync(string category, int max);
    ValueTask PurchaseAsync(params int[] itemIds);
}

Client side

// nuget install Fluorite
using Fluorite;

// Initialize
Nest.Factory.Initialize();

// Connect to server with default WebSocket/Json transport.
// (Optional: You can register client side expose object at last arguments same as server side)
var nest = await Nest.Factory.ConnectAsync("server.example.com", 4649, false);
try
{
    // Get transparent proxy instance from the client nest.
    var partsShop = nest.GetPeer<IPartsShop>();

    // Manipulate via transparent proxy.
    var items = await shop.GetItemsAsync("FPGA", 100);
    await shop.PurchaseAsync(items[3].Id);
}
finally
{
    // Shutdown client nest
    await nest.ShutdownAsync();
}

Server side

// nuget install Fluorite
using Fluorite;

// The exposed interface implementer.
internal sealed class PartsShop : IPartsShop
{
    public async ValueTask<Item[]> GetItemsAsync(string category, int max)
    {
        // ...
    }

    public async ValueTask PurchaseAsync(int[] itemIds)
    {
        // ...
    }
}

// Initialize
Nest.Factory.Initialize();

// Start default WebSocket/Json server (at background)
// with expose objects at last arguments.
var nest = await Nest.Factory.StartServer(4649, false, new PartsShop());
try
{
    // ...
}
finally
{
    // Shutdown server nest
    await nest.ShutdownAsync();
}

Transparent proxy

Fluorite requires transparent proxy implementation. You can choice few implementation ways:

  • Automatic static proxy generator:
    • Fluorite.Build (or Fluorite) package will generate automatically static transparent proxy class on your project.
    • It's easy way and recommended.
    • AOT environment friendly and fastest at runtime.
    • But some .NET development environemnt couldn't use. Unity (Game engine) and others have unique way to build environment.
  • Dynamic proxy generator:
    • Fluorite.Dynamic package will generate automatically dynamic transparent proxy class at on demand (runtime).
    • Stable mostly environments.
    • It's better performance, will use CIL/MSIL generator.
    • Maybe couldn't use on AOT platform.
  • Self implements static proxy:
    • Flexible and customizable.
    • You dodge referring meta-package Fluorite or Fluorite.Dynamic. Will make decrease dependency only referring Fluorite.Core.
    • Tired :)

Automatic static proxy generator (Fluorite.Build)

You don't need anything. It's an example result:

// It's automatically generated, you can see it by ILSpy and others.
internal sealed class IPartsShopProxy__ :
    GeneratedProxyBase, IPartsShop
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ValueTask<Item[]> GetItemsAsync(string category, int max)
    {
        return base.InvokeAsync<Item[]>("ExamplePartsShop.IPartsShop.GetItems", new object[2] { category, max });
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ValueTask PurchaseAsync(int[] itemIds)
    {
        return base.InvokeAsync("ExamplePartsShop.IPartsShop.Purchase", new object[1] { itemIds });
    }
}

Automatic dynamic proxy generator (use Fluorite.Dynamic)

Self implements static proxy

You have to implement likes:

// Hand coded static proxy class.
internal sealed class PartsShopProxy :
    StaticProxyBase, IPartsShop
{
    public ValueTask<Item[]> GetItemsAsync(string category, int max)
    {
        return base.InvokeAsync<IPartsShop, Item[]>("GetItems", category, max);
    }

    public ValueTask PurchaseAsync(int[] itemIds)
    {
        // HACK: Requires cast expression because will convert implicitly param array.
        return base.InvokeAsync<IPartsShop>("Purchase", (object)itemIds);
    }
}

// Register proxy class.
Nest.Register<IPartsShop, PartsShopProxy>();

Advanced topic

Customize with your own serializer

TODO:

Customize with your own transport

TODO:

Customize with your own proxy factory

TODO:


License

Apache-v2

History

  • 0.6.0:
    • Reconstruct transport architecture overall transferring data with Stream asynchronously.
    • Reconstruct transport architecture enabling truly full-duplex on WebSocket.
  • 0.5.0:
    • Implemented static proxy generator at running build time (Fluorite.Build)
  • 0.2.0:
    • Passed full-duplex, bi-directional tests.
  • 0.1.0:
    • Initial version.