Skip to content

Commit cfbe3d5

Browse files
sdesaiLULAjeremydmiller
authored andcommitted
Add HTTP transport support with client and server integration
1 parent a1d84ad commit cfbe3d5

File tree

5 files changed

+248
-26
lines changed

5 files changed

+248
-26
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using Microsoft.AspNetCore.Builder;
2+
using Microsoft.AspNetCore.Http;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Wolverine;
5+
using Wolverine.Http;
6+
using Wolverine.Http.Transport;
7+
8+
var builder = WebApplication.CreateBuilder(args);
9+
const string httpNamedClient = "https://extenal/";
10+
builder.UseWolverine(opts =>
11+
{
12+
var transport = new HttpTransport();
13+
opts.Transports.Add(transport);
14+
// Publish all messages to the external http endpoint using the named http client
15+
// The .SendInline() method fails, so do not use it here
16+
opts.PublishAllMessages().ToHttpEndpoint(httpNamedClient);
17+
});
18+
builder.Services.AddWolverineHttp();
19+
// Configure the named http client to point to the external wolverine server
20+
builder.Services.AddHttpClient(
21+
httpNamedClient,
22+
client =>
23+
{
24+
client.BaseAddress = new Uri("https://where-ever-you want-message-to-go/");
25+
//client.DefaultRequestHeaders.Add("Authorization", $"Bearer eyJ***");
26+
});
27+
// To handle the messages over HTTP, send them to https://where-your-app-with-message-handlers/_wolverine/batch/queue
28+
// Register the WolverineHttpTransportClient for sending messages over HTTP
29+
builder.Services.AddScoped<WolverineHttpTransportClient>();
30+
var app = builder.Build();
31+
app.MapWolverineEndpoints();
32+
app.MapPost(
33+
"/test-command", async (TestCommand command, IMessageBus bus) =>
34+
{
35+
await bus.SendAsync(command);
36+
return Results.Ok();
37+
});
38+
app.MapWolverineHttpTransportEndpoints();
39+
40+
app.Run();
41+
42+
public record TestCommand(string message);
43+
44+
public class TestCommandHandler
45+
{
46+
public TestCommand1 Handle(TestCommand command)
47+
{
48+
Console.WriteLine(
49+
$"Handled command with message: {command.message}");
50+
return new TestCommand1(command.message + "x");
51+
}
52+
}
53+
54+
public record TestCommand1(string message);
55+
56+
public class TestCommand1Handler(IMessageContext messageBus)
57+
{
58+
public void Handle(TestCommand1 command)
59+
{
60+
Console.WriteLine($"Handled TestCommand1 with message: {command.message}");
61+
}
62+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using JasperFx.Core;
2+
using Shouldly;
3+
using Wolverine.Configuration;
4+
using Wolverine.Http.Transport;
5+
using Wolverine.Runtime;
6+
using NSubstitute;
7+
using Xunit;
8+
9+
namespace Wolverine.Http.Tests.Transport;
10+
11+
public class HttpTransportTests
12+
{
13+
[Fact]
14+
public void protocol_is_https()
15+
{
16+
var transport = new HttpTransport();
17+
transport.Protocol.ShouldBe("https");
18+
}
19+
20+
[Fact]
21+
public void can_get_endpoint_by_uri()
22+
{
23+
var transport = new HttpTransport();
24+
var uri = "https://localhost:5500".ToUri();
25+
var endpoint = transport.EndpointFor(uri.ToString());
26+
27+
endpoint.Uri.ShouldBe(uri);
28+
endpoint.Role.ShouldBe(EndpointRole.Application);
29+
}
30+
31+
[Fact]
32+
public void endpoints_are_cached()
33+
{
34+
var transport = new HttpTransport();
35+
var uri = "https://localhost:5500".ToUri();
36+
var endpoint1 = transport.EndpointFor(uri.ToString());
37+
var endpoint2 = transport.EndpointFor(uri.ToString());
38+
39+
endpoint1.ShouldBeSameAs(endpoint2);
40+
}
41+
42+
[Fact]
43+
public async Task initialize_compiles_all_endpoints()
44+
{
45+
var transport = new HttpTransport();
46+
var uri1 = "https://localhost:5501".ToUri();
47+
var uri2 = "https://localhost:5502".ToUri();
48+
49+
var endpoint1 = transport.EndpointFor(uri1.ToString());
50+
var endpoint2 = transport.EndpointFor(uri2.ToString());
51+
52+
var runtime = NSubstitute.Substitute.For<IWolverineRuntime>();
53+
runtime.Options.Returns(new WolverineOptions());
54+
55+
await transport.InitializeAsync(runtime);
56+
}
57+
58+
[Fact]
59+
public void can_find_endpoint_by_uri()
60+
{
61+
var transport = new HttpTransport();
62+
var uri = "https://localhost:5503".ToUri();
63+
var endpoint = transport.EndpointFor(uri.ToString());
64+
65+
var found = transport.TryGetEndpoint(uri);
66+
found.ShouldBeSameAs(endpoint);
67+
}
68+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using System.Net;
2+
using System.Net.Http.Headers;
3+
using NSubstitute;
4+
using Shouldly;
5+
using Wolverine.Http.Transport;
6+
using Wolverine.Runtime.Serialization;
7+
using Wolverine.Transports;
8+
using Xunit;
9+
10+
namespace Wolverine.Http.Tests.Transport;
11+
12+
public class WolverineHttpTransportClientTests
13+
{
14+
private readonly IHttpClientFactory _clientFactory;
15+
private readonly MockHttpMessageHandler _handler;
16+
private readonly HttpClient _httpClient;
17+
private readonly WolverineHttpTransportClient _client;
18+
19+
public WolverineHttpTransportClientTests()
20+
{
21+
_clientFactory = Substitute.For<IHttpClientFactory>();
22+
_handler = new MockHttpMessageHandler();
23+
_httpClient = new HttpClient(_handler);
24+
_client = new WolverineHttpTransportClient(_clientFactory);
25+
}
26+
27+
[Fact]
28+
public async Task send_envelope_async()
29+
{
30+
var uri = "https://localhost:5001/messages";
31+
_clientFactory.CreateClient(uri).Returns(_httpClient);
32+
33+
var envelope = new Envelope
34+
{
35+
Data = new byte[] { 1, 2, 3 },
36+
Destination = new Uri(uri)
37+
};
38+
39+
await _client.SendAsync(uri, envelope);
40+
41+
_handler.LastRequest.ShouldNotBeNull();
42+
_handler.LastRequest.Method.ShouldBe(HttpMethod.Post);
43+
_handler.LastRequest.RequestUri.ToString().ShouldBe(uri);
44+
_handler.LastRequest.Content.Headers.ContentType.MediaType.ShouldBe(HttpTransportExecutor.EnvelopeContentType);
45+
46+
var expectedData = EnvelopeSerializer.Serialize(envelope);
47+
_handler.LastContent.ShouldBe(expectedData);
48+
}
49+
50+
[Fact]
51+
public async Task send_batch_async()
52+
{
53+
var uri = "https://localhost:5001/messages/batch";
54+
_httpClient.BaseAddress = new Uri("https://target-url");
55+
_clientFactory.CreateClient(uri).Returns(_httpClient);
56+
57+
var envelopes = new[]
58+
{
59+
new Envelope { Data = new byte[] { 1 } },
60+
new Envelope { Data = new byte[] { 2 } }
61+
};
62+
63+
var batch = new OutgoingMessageBatch(new Uri(uri), envelopes);
64+
65+
await _client.SendBatchAsync(uri, batch);
66+
67+
_handler.LastRequest.ShouldNotBeNull();
68+
_handler.LastRequest.Method.ShouldBe(HttpMethod.Post);
69+
_handler.LastRequest.RequestUri.ToString().ShouldBe("https://target-url/");
70+
_handler.LastRequest.Content.Headers.ContentType.MediaType.ShouldBe(HttpTransportExecutor.EnvelopeBatchContentType);
71+
72+
var expectedData = EnvelopeSerializer.Serialize(envelopes);
73+
_handler.LastContent.ShouldBe(expectedData);
74+
}
75+
}
76+
77+
public class MockHttpMessageHandler : HttpMessageHandler
78+
{
79+
public HttpRequestMessage LastRequest { get; private set; }
80+
public byte[] LastContent { get; private set; }
81+
82+
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
83+
{
84+
LastRequest = request;
85+
if (request.Content != null)
86+
{
87+
LastContent = await request.Content.ReadAsByteArrayAsync(cancellationToken);
88+
}
89+
return new HttpResponseMessage(HttpStatusCode.OK);
90+
}
91+
}
Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,42 @@
11
using JasperFx.Core;
2-
using JasperFx.Resources;
32
using Wolverine.Configuration;
43
using Wolverine.Runtime;
54
using Wolverine.Transports;
65

76
namespace Wolverine.Http.Transport;
87

9-
internal class HttpTransport : ITransport
8+
public class HttpTransport : TransportBase<HttpEndpoint>
109
{
11-
private readonly Cache<Uri, HttpEndpoint> _endpoints
12-
= new(uri => new HttpEndpoint(uri, EndpointRole.Application));
13-
14-
public string Protocol { get; } = "http";
15-
public string Name { get; } = "Http Transport";
16-
public Endpoint? ReplyEndpoint()
17-
{
18-
throw new NotImplementedException();
19-
}
10+
private readonly LightweightCache<Uri, HttpEndpoint> _endpoints
11+
= new(uri => new HttpEndpoint(uri, EndpointRole.Application){OutboundUri = uri.ToString()});
2012

21-
public Endpoint GetOrCreateEndpoint(Uri uri)
13+
public HttpTransport() : base("https", "HTTP Transport")
2214
{
23-
throw new NotImplementedException();
2415
}
2516

26-
public Endpoint? TryGetEndpoint(Uri uri)
17+
protected override IEnumerable<HttpEndpoint> endpoints()
2718
{
28-
throw new NotImplementedException();
19+
return _endpoints;
2920
}
3021

31-
public IEnumerable<Endpoint> Endpoints()
22+
protected override HttpEndpoint findEndpointByUri(Uri uri)
3223
{
33-
throw new NotImplementedException();
24+
return _endpoints[uri];
3425
}
3526

36-
public async ValueTask InitializeAsync(IWolverineRuntime runtime)
27+
public override ValueTask InitializeAsync(IWolverineRuntime runtime)
3728
{
38-
throw new NotImplementedException();
39-
}
29+
foreach (var endpoint in _endpoints)
30+
{
31+
endpoint.Compile(runtime);
32+
}
4033

41-
public bool TryBuildStatefulResource(IWolverineRuntime runtime, out IStatefulResource? resource)
42-
{
43-
throw new NotImplementedException();
34+
return ValueTask.CompletedTask;
4435
}
4536

4637
public HttpEndpoint EndpointFor(string url)
4738
{
48-
throw new NotImplementedException();
39+
var uri = new Uri(url);
40+
return _endpoints[uri];
4941
}
5042
}

src/Http/Wolverine.Http/Transport/WolverineHttpTransportClient.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,21 @@
44

55
namespace Wolverine.Http.Transport;
66

7-
internal class WolverineHttpTransportClient : HttpClient
7+
public class WolverineHttpTransportClient(IHttpClientFactory clientFactory)
88
{
99
public async Task SendBatchAsync(string uri, OutgoingMessageBatch batch)
1010
{
11+
var client = clientFactory.CreateClient(uri);
1112
var content = new ByteArrayContent(EnvelopeSerializer.Serialize(batch.Messages));
1213
content.Headers.ContentType = new MediaTypeHeaderValue(HttpTransportExecutor.EnvelopeBatchContentType);
13-
await PostAsync(uri, content);
14+
await client.PostAsync(client.BaseAddress, content);
15+
}
16+
17+
public async Task SendAsync(string uri, Envelope envelope)
18+
{
19+
var client = clientFactory.CreateClient(uri);
20+
var content = new ByteArrayContent(EnvelopeSerializer.Serialize(envelope));
21+
content.Headers.ContentType = new MediaTypeHeaderValue(HttpTransportExecutor.EnvelopeContentType);
22+
await client.PostAsync(uri, content);
1423
}
1524
}

0 commit comments

Comments
 (0)