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

Protobuf support #360

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 7 additions & 2 deletions API/Protocol/ContentType.cs
Expand Up @@ -201,8 +201,12 @@ public enum ContentType
/// <summary>
/// Url encoded form data.
/// </summary>
ApplicationWwwFormUrlEncoded
ApplicationWwwFormUrlEncoded,

/// <summary>
/// A Protobuf message.
/// </summary>
ApplicationProtobuf
}

#endregion
Expand Down Expand Up @@ -276,7 +280,8 @@ public class FlexibleContentType
{ ContentType.ImageScalableVectorGraphicsXml, "image/svg+xml" },
{ ContentType.ImageScalableVectorGraphicsCompressed, "image/svgz" },
{ ContentType.ApplicationJson, "application/json" },
{ ContentType.ApplicationWwwFormUrlEncoded, "application/x-www-form-urlencoded" }
{ ContentType.ApplicationWwwFormUrlEncoded, "application/x-www-form-urlencoded" },
{ ContentType.ApplicationProtobuf, "application/protobuf" }
};

private static readonly Dictionary<string, ContentType> MAPPING_REVERSE = MAPPING.ToDictionary(x => x.Value, x => x.Key);
Expand Down
9 changes: 8 additions & 1 deletion GenHTTP.sln
Expand Up @@ -100,6 +100,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GenHTTP.Modules.Pages", "Mo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GenHTTP.Modules.Functional", "Modules\Functional\GenHTTP.Modules.Functional.csproj", "{2A2BCF94-BBDF-49A7-8B87-931E665507F0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenHTTP.Modules.Protobuf", "Modules\Protobuf\GenHTTP.Modules.Protobuf.csproj", "{0D9957DE-8FE2-44A1-B9D7-EADD1DBDBD6E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -246,6 +248,10 @@ Global
{2A2BCF94-BBDF-49A7-8B87-931E665507F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2A2BCF94-BBDF-49A7-8B87-931E665507F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A2BCF94-BBDF-49A7-8B87-931E665507F0}.Release|Any CPU.Build.0 = Release|Any CPU
{0D9957DE-8FE2-44A1-B9D7-EADD1DBDBD6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0D9957DE-8FE2-44A1-B9D7-EADD1DBDBD6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D9957DE-8FE2-44A1-B9D7-EADD1DBDBD6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D9957DE-8FE2-44A1-B9D7-EADD1DBDBD6E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -288,9 +294,10 @@ Global
{4F2BDA0D-D206-4787-83E6-593F7673C1AB} = {23B23225-275E-4F52-8B29-6F44C85B6ACE}
{69D6CA16-1332-41B6-8994-6AD943915F25} = {23B23225-275E-4F52-8B29-6F44C85B6ACE}
{2A2BCF94-BBDF-49A7-8B87-931E665507F0} = {23B23225-275E-4F52-8B29-6F44C85B6ACE}
{0D9957DE-8FE2-44A1-B9D7-EADD1DBDBD6E} = {23B23225-275E-4F52-8B29-6F44C85B6ACE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
LessCompiler = 2603124e-1287-4d61-9540-6ac3efad4eb9
SolutionGuid = {9C67B3AF-0BF6-4E21-8C39-3F74CFCF9632}
LessCompiler = 2603124e-1287-4d61-9540-6ac3efad4eb9
EndGlobalSection
EndGlobal
56 changes: 56 additions & 0 deletions Modules/Protobuf/GenHTTP.Modules.Protobuf.csproj
@@ -0,0 +1,56 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>

<TargetFrameworks>net6.0;net7.0</TargetFrameworks>

<LangVersion>10.0</LangVersion>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>

<AssemblyVersion>7.0.0.0</AssemblyVersion>
<FileVersion>7.0.0.0</FileVersion>
<Version>7.0.0</Version>

<Authors>Andreas Nägeli</Authors>
Kaliumhexacyanoferrat marked this conversation as resolved.
Show resolved Hide resolved
<Company />

<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageProjectUrl>https://genhttp.org/</PackageProjectUrl>

<Description>Allows to read and write responses in protobuf data format</Description>
<PackageTags>HTTP Webserver C# Module Serialization Conversion Protobuf</PackageTags>

<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>

<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>CS1591,CS1587,CS1572,CS1573</NoWarn>

<PackageIcon>icon.png</PackageIcon>

</PropertyGroup>

<ItemGroup>

<None Include="..\..\LICENSE" Pack="true" PackagePath="\" />
<None Include="..\..\Resources\icon.png" Pack="true" PackagePath="\" />

</ItemGroup>

<ItemGroup>
<PackageReference Include="protobuf-net" Version="3.1.26" />
</ItemGroup>

<ItemGroup>

<ProjectReference Include="..\..\API\GenHTTP.Api.csproj" />

<ProjectReference Include="..\Basics\GenHTTP.Modules.Basics.csproj" />

<ProjectReference Include="..\Conversion\GenHTTP.Modules.Conversion.csproj" />

</ItemGroup>

</Project>
43 changes: 43 additions & 0 deletions Modules/Protobuf/Providers/ProtobufContent.cs
@@ -0,0 +1,43 @@
using GenHTTP.Api.Protocol;
using ProtoBuf;
using System.IO;
using System.Threading.Tasks;

namespace GenHTTP.Modules.Protobuf.Providers
{
public sealed class ProtobufContent : IResponseContent
{
#region Get-/Setters

public ulong? Length => null;

private object Data { get; }

#endregion

#region Initialization

public ProtobufContent(object data)
{
Data = data;
}

#endregion

#region Functionality

public ValueTask<ulong?> CalculateChecksumAsync()
{
return new ValueTask<ulong?>((ulong)Data.GetHashCode());
}

public ValueTask WriteAsync(Stream target, uint bufferSize)
{
Serializer.Serialize(target, Data);

return new ValueTask();
}

#endregion
}
}
29 changes: 29 additions & 0 deletions Modules/Protobuf/Providers/ProtobufFormat.cs
@@ -0,0 +1,29 @@
using GenHTTP.Api.Protocol;
using GenHTTP.Modules.Basics;
using GenHTTP.Modules.Conversion.Providers;
using ProtoBuf;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading.Tasks;

namespace GenHTTP.Modules.Protobuf.Providers
{
public sealed class ProtobufFormat : ISerializationFormat
{
public ValueTask<object?> DeserializeAsync(Stream stream, [DynamicallyAccessedMembers((DynamicallyAccessedMemberTypes)(-1))] Type type)
{
object deserializedObject = Serializer.Deserialize(type, stream);
return new ValueTask<object?>(deserializedObject);
}

public ValueTask<IResponseBuilder> SerializeAsync(IRequest request, object response)
{
var result = request.Respond()
.Content(new ProtobufContent(response))
.Type(ContentType.ApplicationProtobuf);

return new ValueTask<IResponseBuilder>(result);
}
}
}
14 changes: 14 additions & 0 deletions Modules/Protobuf/SerializationExtensions.cs
@@ -0,0 +1,14 @@
using GenHTTP.Api.Protocol;
using GenHTTP.Modules.Conversion.Providers;
using GenHTTP.Modules.Protobuf.Providers;

namespace GenHTTP.Modules.Protobuf
{
public static class SerializationExtensions
{
public static SerializationBuilder AddProtobuf(this SerializationBuilder serializationBuilder)
{
return serializationBuilder.Add(ContentType.ApplicationProtobuf, new ProtobufFormat());
}
}
}
5 changes: 4 additions & 1 deletion Testing/GenHTTP.Testing.Acceptance.csproj
Expand Up @@ -47,6 +47,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

<PackageReference Include="protobuf-net" Version="3.1.26" />

</ItemGroup>

<ItemGroup>
Expand Down Expand Up @@ -84,7 +86,8 @@
<ProjectReference Include="..\Modules\Caching\GenHTTP.Modules.Caching.csproj" />
<ProjectReference Include="..\Modules\ServerCaching\GenHTTP.Modules.ServerCaching.csproj" />
<ProjectReference Include="..\Modules\AutoReload\GenHTTP.Modules.AutoReload.csproj" />
<ProjectReference Include="..\Modules\Functional\GenHTTP.Modules.Functional.csproj" />
<ProjectReference Include="..\Modules\Functional\GenHTTP.Modules.Functional.csproj" />
<ProjectReference Include="..\Modules\Protobuf\GenHTTP.Modules.Protobuf.csproj" />

</ItemGroup>

Expand Down
163 changes: 163 additions & 0 deletions Testing/Modules/ProtobufTests.cs
@@ -0,0 +1,163 @@
using GenHTTP.Api.Protocol;
using GenHTTP.Modules.Conversion;
using GenHTTP.Modules.Layouting;
using GenHTTP.Modules.Protobuf;
using GenHTTP.Modules.Reflection;
using GenHTTP.Modules.Webservices;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using static System.Runtime.InteropServices.JavaScript.JSType;

namespace GenHTTP.Testing.Acceptance.Modules
{
[TestClass]
public sealed class ProtobufTests
{
#region Supporting structures

[ProtoContract]
public sealed class TestEntity
{
[ProtoMember(1)]
public int ID { get; set; }

[ProtoMember(2)]
public string? Name { get; set; }

[ProtoMember(3)]
public double? Nullable { get; set; }

}
public sealed class TestResource
{

[ResourceMethod]
public TestEntity? GetEntity()
{

TestEntity entity = new TestEntity()
{
ID = 1,
Name = "test1"
};

return entity;
}

[ResourceMethod(RequestMethod.POST)]
public TestEntity PostEntity(TestEntity entity)
{
return entity;
}

}


#endregion


#region Tests
[TestMethod]

public async Task TestGetEntityAsProtobuf()

{
TestEntity? result = null;
await WithResponse(string.Empty, HttpMethod.Get, null, "application/protobuf", "application/protobuf", async r =>
{
result = Serializer.Deserialize<TestEntity>(await r.Content.ReadAsStreamAsync());

});

Assert.AreEqual(1, result!.ID);
Assert.AreEqual("test1", result!.Name);
}

[TestMethod]
public async Task TestPostEntityAsProtobuf()

{
TestEntity entity = new TestEntity()
{
ID = 2,
Name = "test2",
Nullable = null
};

byte[] encodedEntity;
using (MemoryStream memoryStream = new MemoryStream())
{
Serializer.Serialize(memoryStream, entity);
encodedEntity = memoryStream.ToArray();
}

TestEntity? result = null;
await WithResponse(string.Empty, HttpMethod.Post, encodedEntity, "application/protobuf", "application/protobuf", async r =>
{
result = Serializer.Deserialize<TestEntity>(await r.Content.ReadAsStreamAsync());

});

Assert.IsNotNull(result);
Assert.AreEqual(entity.ID, result!.ID);
Assert.AreEqual(entity.Name, result!.Name);
Assert.IsNull(result!.Nullable);

}

#endregion

#region Helpers

private async Task WithResponse(string uri, HttpMethod method, byte[]? body, string? contentType, string? accept, Func<HttpResponseMessage, Task> logic)
{
using var service = GetService();

var request = service.GetRequest($"/t/{uri}");

request.Method = method;

if (accept is not null)
{
request.Headers.Add("Accept", accept);
}

if (body is not null)
{
if (contentType is not null)
{
request.Content = new ByteArrayContent(body);
request.Content.Headers.ContentType = new(contentType);
}
else
{
request.Content = new ByteArrayContent(body);
request.Content.Headers.ContentType = null;
}
}

using var response = await service.GetResponse(request);

await logic(response);
}

private static TestRunner GetService()
{
var service = ServiceResource.From<TestResource>()
.Formats(Serialization.Default().AddProtobuf())
.Injectors(Injection.Default());

return TestRunner.Run(Layout.Create().Add("t", service));
}

#endregion

}
}