Skip to content

Commit

Permalink
thrift URIs
Browse files Browse the repository at this point in the history
  • Loading branch information
Jens-G committed Apr 22, 2024
1 parent 49a5928 commit b993466
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 66 deletions.
108 changes: 93 additions & 15 deletions lib/netstd/Tests/Thrift.Tests/UriFactory/TUriFactoryTests.cs
Expand Up @@ -18,11 +18,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Thrift.Transport;
using Thrift.Transport.Client;

namespace Thrift.Tests.UriFactory
{
Expand All @@ -31,42 +29,122 @@ public class TUriFactoryTests
{
private enum BuiltinProtocols { binary, compact, json };
private enum BuiltinTransports { http, namedpipes, socket, tlssocket, file, memory };
private enum BuiltinLayered { framed, buffered };
private enum BuiltinProtocolDecorators { none, mplex };
private enum BuiltinLayeredTransports { none, framed, buffered };

private readonly string InputFile = Path.GetTempFileName();
private readonly string OutputFile = Path.GetTempFileName();

private static HashSet<BuiltinTransports> GetAllSocketTransports()
{
// these are known to slow down the test
return new HashSet<BuiltinTransports> {
BuiltinTransports.socket,
BuiltinTransports.tlssocket,
};
}

[TestMethod]
public void TFactory_Can_Parse_And_Construct_All_Builtin_Types()
public void TFactory_Can_Parse_And_Construct_Complex_ThriftUris()
{
var exclude = GetAllSocketTransports();
var transports = new HashSet<BuiltinTransports>();
foreach (var trans in Enum.GetValues<BuiltinTransports>())
if (!exclude.Contains(trans))
transports.Add(trans);

InternalTestMethodImplementation(
Enum.GetValues<BuiltinProtocols>(),
transports,
Enum.GetValues<BuiltinProtocolDecorators>(),
Enum.GetValues<BuiltinLayeredTransports>()
);
}

[TestMethod]
public void TFactory_Can_Parse_And_Construct_Socket_Transports()
{
var transports = GetAllSocketTransports();

// minimize iterations, as these are known to slow down the test
InternalTestMethodImplementation(
[BuiltinProtocols.binary],
transports,
[BuiltinProtocolDecorators.none],
[BuiltinLayeredTransports.none]
);
}

private void InternalTestMethodImplementation(IEnumerable<BuiltinProtocols> protocols, IEnumerable<BuiltinTransports> transports, IEnumerable<BuiltinProtocolDecorators> decorators, IEnumerable<BuiltinLayeredTransports> layeredTransports)
{
// those must not be empty to have at least one test run
Assert.IsTrue(protocols.Count() > 0);
Assert.IsTrue(transports.Count() > 0);

// "none" should always be included and should be first
Assert.IsTrue(decorators.FirstOrDefault() == BuiltinProtocolDecorators.none);
Assert.IsTrue(layeredTransports.FirstOrDefault() == BuiltinLayeredTransports.none);

// thrift://protocol/transport/layer/layer?data
foreach (var proto in Enum.GetValues<BuiltinProtocols>())
var numTests = 0;
foreach (var proto in protocols)
{
foreach (var trans in Enum.GetValues<BuiltinTransports>())
foreach (var trans in transports)
{
var iTest = 0;
while (InitializeTransportSpecificArgs(trans, iTest++, out var connection))
{

// test basic combination first
var sData = MakeQueryString(connection);
var sUri = TThriftUri.THRIFT_URI_SCHEME + proto + "/" + trans;
TestUri(sUri + sData);

// layers can be stacked upon each other, so lets do exactly that - just to test it
foreach (var layer in Enum.GetValues<BuiltinLayered>())
// decorators can be stacked upon each other, so lets do exactly that - just to test it
var sDecorator = string.Empty;
foreach (var decorator in decorators)
{
sUri += "/" + layer;
TestUri(sUri + sData);
sDecorator += MakeDecoratorString(decorator);

// layers can be stacked upon each other, so lets do exactly that - just to test it
var sLayer = string.Empty;
foreach (var layer in layeredTransports)
{
sLayer += MakeLayeredTransportString(layer);

// build final URI
TestUri(sUri + sDecorator + sLayer +sData);
numTests++;
}
}
}
}
}

// we had at least one test?
Assert.IsTrue(numTests > 0);

File.Delete(InputFile);
File.Delete(OutputFile);
}

private string MakeLayeredTransportString(BuiltinLayeredTransports layer)
{
return layer switch
{
BuiltinLayeredTransports.none => string.Empty,
_ => "/" + layer.ToString()
};
}

private static string MakeDecoratorString(BuiltinProtocolDecorators protdeco)
{
return protdeco switch
{
BuiltinProtocolDecorators.none => string.Empty,
BuiltinProtocolDecorators.mplex => "/" + protdeco.ToString() + ":" + Uri.EscapeDataString("Rather\0Obscure\tMplex\nService/Name"),
_ => "/" + protdeco.ToString(),
};
}

private bool InitializeTransportSpecificArgs(BuiltinTransports trans, int test, out Dictionary<string, string> connection)
{
if (test > 64) // prevent against endless loops
Expand Down Expand Up @@ -148,7 +226,7 @@ private static string MakeQueryString(Dictionary<string, string> data)

private static void TestUri(string sUri)
{
var parsed = new TThriftUri( sUri);
var parsed = new TThriftUri(sUri);
Assert.AreEqual(sUri, parsed.ToString());

try
Expand All @@ -165,7 +243,7 @@ private static void TestUri(string sUri)
proto?.Dispose();
}
}
catch(System.Security.Cryptography.CryptographicException)
catch (System.Security.Cryptography.CryptographicException)
{
// that may happen, but is not relevant here
}
Expand Down
12 changes: 12 additions & 0 deletions lib/netstd/Thrift/Protocol/TMultiplexedProtocol.cs
Expand Up @@ -15,9 +15,12 @@
// specific language governing permissions and limitations
// under the License.

using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Thrift.Protocol.Entities;
using Thrift.Transport;

#pragma warning disable IDE0079 // net20 - unneeded suppression
#pragma warning disable IDE0290 // net8 - primary CTOR
Expand Down Expand Up @@ -90,5 +93,14 @@ public override async Task WriteMessageBeginAsync(TMessage message, Cancellation
break;
}
}

internal class Factory : TProtocolDecoratorFactory
{
public override TProtocol GetProtocol(TProtocol proto, Dictionary<string, string> arguments)
{
var sService = arguments?.FirstOrDefault().Key ?? string.Empty;
return new TMultiplexedProtocol(proto, sService);
}
}
}
}
28 changes: 28 additions & 0 deletions lib/netstd/Thrift/Protocol/TProtocolDecoratorFactory.cs
@@ -0,0 +1,28 @@
// Licensed to the Apache Software Foundation(ASF) under one
// or more contributor license agreements.See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.


using System.Collections.Generic;

namespace Thrift.Protocol
{
// ReSharper disable once InconsistentNaming
public abstract class TProtocolDecoratorFactory
{
public abstract TProtocol GetProtocol(TProtocol proto, Dictionary<string, string> arguments);
}
}
93 changes: 72 additions & 21 deletions lib/netstd/Thrift/TFactory.cs
Expand Up @@ -17,20 +17,40 @@

using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Reflection.Emit;
using System.Xml.Linq;
using Thrift.Protocol;
using Thrift.Transport;
using Thrift.Transport.Client;
using Thrift.Transport.Server;
using static System.Net.WebRequestMethods;

#pragma warning disable IDE0028 // collection init is net8 only

namespace Thrift
{
public static class TFactory
{
private class LayerFactory
{
// its either one or the other, can't be both
public readonly TProtocolDecoratorFactory ProtocolDecorator;
public readonly TTransportFactory LayeredTransport;

public LayerFactory(TProtocolDecoratorFactory factory)
{
ProtocolDecorator = factory;
}

public LayerFactory(TTransportFactory factory)
{
LayeredTransport = factory;
}
}


private static readonly Dictionary<string, TProtocolFactory> RegisteredProtocols = new Dictionary<string, TProtocolFactory>();
private static readonly Dictionary<string, TTransportFactory> RegisteredLayeredTransports = new Dictionary<string, TTransportFactory>();
private static readonly Dictionary<string, TEndpointTransportFactory> RegisteredEndpointTransports = new Dictionary<string, TEndpointTransportFactory>();
private static readonly Dictionary<string, LayerFactory> RegisteredLayers = new Dictionary<string, LayerFactory>();


static TFactory()
{
Expand All @@ -39,6 +59,9 @@ static TFactory()
Register("compact", new TCompactProtocol.Factory());
Register("json", new TJsonProtocol.Factory());

// protocol decorators
Register("mplex", new TMultiplexedProtocol.Factory());

// layered transports
Register("framed", new TFramedTransport.Factory());
Register("buffered", new TBufferedTransport.Factory());
Expand All @@ -56,20 +79,30 @@ static TFactory()

public static void Register(string name, TProtocolFactory factory)
{
// throws intentionally if name is already used
lock (RegisteredProtocols)
RegisteredProtocols.Add(name, factory); // throws intentionally if name is already used
RegisteredProtocols.Add(name, factory);
}

public static void Register(string name, TEndpointTransportFactory factory)
{
// throws intentionally if name is already used
lock (RegisteredEndpointTransports)
RegisteredEndpointTransports.Add(name, factory);
}

public static void Register(string name, TTransportFactory factory)
{
lock (RegisteredLayeredTransports)
RegisteredLayeredTransports.Add(name, factory); // throws intentionally if name is already used
// throws intentionally if name is already used
lock (RegisteredLayers)
RegisteredLayers.Add(name, new LayerFactory(factory));
}

public static void Register(string name, TEndpointTransportFactory factory)
public static void Register(string name, TProtocolDecoratorFactory factory)
{
lock (RegisteredEndpointTransports)
RegisteredEndpointTransports.Add(name, factory); // throws intentionally if name is already used
// throws intentionally if name is already used
lock (RegisteredLayers)
RegisteredLayers.Add(name, new LayerFactory(factory));
}


Expand All @@ -82,31 +115,49 @@ public static TProtocol ConstructClientProtocolTransportStack(string sThriftUri,

public static TProtocol ConstructClientProtocolTransportStack(TThriftUri uri, TConfiguration config, out TTransport transport)
{
transport = CreateEndpointTransport(uri.EndpointTransport, config, uri.QueryData);
foreach (var layer in uri.LayeredTransports)
transport = CreateLayeredTransport(layer, transport);
return CreateProtocol(uri.Protocol, transport);
transport = CreateEndpointTransport(uri.Transport, config, uri.QueryData);
foreach (var layer in uri.Layers)
transport = AddLayeredTransport(transport, layer.Key);

var protocol = CreateProtocol(uri.Protocol, transport);
foreach (var layer in uri.Layers)
protocol = AddProtocolDecorator(protocol, layer.Key, layer.Value);

return protocol;
}

private static TEndpointTransport CreateEndpointTransport(string name, TConfiguration config, Dictionary<string, string> args)
{
if (RegisteredEndpointTransports.TryGetValue(name, out var factory))
return factory.GetTransport(config, args);
throw new TApplicationException(TApplicationException.ExceptionType.Unknown, "Endpoint transport '" + name + "' not registered");
}

private static TTransport CreateLayeredTransport(string name, TTransport transport)
{
if (RegisteredLayeredTransports.TryGetValue(name, out var factory))
return factory.GetTransport(transport);
throw new TApplicationException(TApplicationException.ExceptionType.Unknown, "layered transport '" + name + "' not registered");
throw new TApplicationException(TApplicationException.ExceptionType.Unknown, "Endpoint transport '" + name + "' not registered");
}

private static TProtocol CreateProtocol(string name, TTransport transport)
{
if (RegisteredProtocols.TryGetValue(name, out var factory))
return factory.GetProtocol(transport);

throw new TApplicationException(TApplicationException.ExceptionType.Unknown, "Protocol '" + name + "' not registered");
}

private static TProtocol AddProtocolDecorator(TProtocol protocol, string name, Dictionary<string, string> args)
{
if (!RegisteredLayers.TryGetValue(name, out var factory))
throw new TApplicationException(TApplicationException.ExceptionType.Unknown, "Thrift protocol/transport layer '" + name + "' not registered");
if (factory.ProtocolDecorator != null)
return factory.ProtocolDecorator.GetProtocol(protocol, args);
return protocol;
}

private static TTransport AddLayeredTransport(TTransport transport, string name)
{
if (!RegisteredLayers.TryGetValue(name, out var factory))
throw new TApplicationException(TApplicationException.ExceptionType.Unknown, "Thrift protocol/transport layer '" + name + "' not registered");
if (factory.LayeredTransport != null)
return factory.LayeredTransport.GetTransport(transport);
return transport;
}
}
}

0 comments on commit b993466

Please sign in to comment.