├── .github └── workflows │ └── dotnet.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Directory.Build.props ├── LICENSE ├── README.md ├── appveyor.yml ├── src ├── Vlingo.Xoom.Http.Tests │ ├── BodyTest.cs │ ├── ChunkedBodyTest.cs │ ├── Content │ │ ├── css │ │ │ └── styles.css │ │ ├── index.html │ │ ├── js │ │ │ └── vuetify.js │ │ └── views │ │ │ ├── About.vue │ │ │ └── test 2 │ │ │ └── index.html │ ├── ContentEncodingMethodTest.cs │ ├── ContentEncodingTest.cs │ ├── ContentTypeTest.cs │ ├── Converter.cs │ ├── ExtendedCharactersFixture.cs │ ├── FiltersTest.cs │ ├── Media │ │ ├── AcceptMediaTypeTest.cs │ │ ├── ContentMediaTypeTest.cs │ │ ├── MediaTypeParserTest.cs │ │ └── ResponseContentMediaTypeSelectorTest.cs │ ├── MethodExtensionsTest.cs │ ├── QueryParametersParserTest.cs │ ├── RequestHeaderTest.cs │ ├── RequestParserTest.cs │ ├── RequestTest.cs │ ├── Resource │ │ ├── ActionTest.cs │ │ ├── ClientTest.cs │ │ ├── ConfigurationResourceTest.cs │ │ ├── ConfigurationTest.cs │ │ ├── DefaultTextPlainMapperTest.cs │ │ ├── DynamicResourceDispatcherTest.cs │ │ ├── DynamicResourceHandlerTest.cs │ │ ├── FailResource.cs │ │ ├── Feed │ │ │ ├── EventsFeedProducerActor.cs │ │ │ └── FeedResourceTest.cs │ │ ├── FluentTestResource.cs │ │ ├── MediaTypeMapperTest.cs │ │ ├── MockCompletesEventuallyResponse.cs │ │ ├── ParameterResolverTest.cs │ │ ├── RequestHandler0Test.cs │ │ ├── RequestHandler1Test.cs │ │ ├── RequestHandler2Test.cs │ │ ├── RequestHandler3Test.cs │ │ ├── RequestHandler4Test.cs │ │ ├── RequestHandler5Test.cs │ │ ├── RequestHandler6Test.cs │ │ ├── RequestHandlerTest.cs │ │ ├── RequestHandlerTestBase.cs │ │ ├── ResourceBuilderTest.cs │ │ ├── ResourceDispatcherGeneratorTest.cs │ │ ├── ResourceFailureTest.cs │ │ ├── ResourceTestFixtures.cs │ │ ├── ResourcesTest.cs │ │ ├── SecureClientTest.cs │ │ ├── ServerBootstrap.cs │ │ ├── ServerCORSTest.cs │ │ ├── ServerTest.cs │ │ ├── SinglePageApplicationResourceTest.cs │ │ ├── Sse │ │ │ ├── MessageEventTest.cs │ │ │ ├── MockRequestResponseContext.cs │ │ │ ├── MockResponseSenderChannel.cs │ │ │ ├── MockSseStreamResource.cs │ │ │ ├── SseClientTest.cs │ │ │ ├── SseEventTest.cs │ │ │ ├── SseFeedTest.cs │ │ │ ├── SseStreamResourceTest.cs │ │ │ └── SseSubscriberTest.cs │ │ ├── StaticFilesResourceTest.cs │ │ ├── TestDispatcher.cs │ │ ├── TestMapper.cs │ │ ├── TestResponseChannelConsumer.cs │ │ └── TestResponseConsumer.cs │ ├── ResponseParserTest.cs │ ├── ResponseTest.cs │ ├── Sample │ │ └── User │ │ │ ├── AllSseFeedActor.cs │ │ │ ├── ContactData.cs │ │ │ ├── Model │ │ │ ├── Contact.cs │ │ │ ├── IProfile.cs │ │ │ ├── IUser.cs │ │ │ ├── Name.cs │ │ │ ├── ProfileActor.cs │ │ │ ├── ProfileRepository.cs │ │ │ ├── ProfileState.cs │ │ │ ├── ProfileStateFactory.cs │ │ │ ├── UserActor.cs │ │ │ ├── UserRepository.cs │ │ │ ├── UserState.cs │ │ │ ├── UserStateFactory.cs │ │ │ └── User__Proxy.cs │ │ │ ├── NameData.cs │ │ │ ├── ProfileData.cs │ │ │ ├── ProfileDataMapper.cs │ │ │ ├── ProfileResource.cs │ │ │ ├── ProfileResourceFluent.cs │ │ │ ├── Serialization │ │ │ └── UserDataConverter.cs │ │ │ ├── UserData.cs │ │ │ ├── UserDataMapper.cs │ │ │ ├── UserResource.cs │ │ │ └── UserResourceFluent.cs │ ├── ToSpecParserTest.cs │ ├── VersionTest.cs │ ├── Vlingo.Xoom.Http.Tests.csproj │ ├── vlingo-actors.json │ └── vlingo-http.json ├── Vlingo.Xoom.Http.sln ├── Vlingo.Xoom.Http.sln.licenseheader └── Vlingo.Xoom.Http │ ├── BinaryBody.cs │ ├── Body.cs │ ├── CORSResponseFilter.cs │ ├── ChunkedBody.cs │ ├── ContentEncoding.cs │ ├── ContentEncodingMethod.cs │ ├── ContentPacket.cs │ ├── ContentType.cs │ ├── Context.cs │ ├── Filter.cs │ ├── Filters.cs │ ├── Header.cs │ ├── Headers.cs │ ├── Media │ ├── ContentMediaType.cs │ ├── MediaTypeDescriptor.cs │ ├── MediaTypeParser.cs │ └── ResponseMediaTypeSelector.cs │ ├── Method.cs │ ├── PlainBody.cs │ ├── QueryParameters.cs │ ├── Request.cs │ ├── RequestData.cs │ ├── RequestFilter.cs │ ├── RequestHeader.cs │ ├── RequestParser.cs │ ├── Resource │ ├── AbstractDispatcherPool.cs │ ├── Action.cs │ ├── Actions.cs │ ├── AgentDispatcherPool.cs │ ├── Client.cs │ ├── ClientConsumerCommons.cs │ ├── ClientConsumerWorkerActor.cs │ ├── ClientConsumer__Proxy.cs │ ├── ClientCorrelatingRequesterConsumerActor.cs │ ├── Configuration.cs │ ├── ConfigurationResource.cs │ ├── Content.cs │ ├── DefaultErrorHandler.cs │ ├── DefaultJsonMapper.cs │ ├── DefaultMediaTypeMapper.cs │ ├── DefaultTextPlainMapper.cs │ ├── DispatcherActor.cs │ ├── Dispatcher__Proxy.cs │ ├── DynamicResource.cs │ ├── DynamicResourceHandler.cs │ ├── EmbeddedResourceLoader.cs │ ├── Feed │ │ ├── FeedProducer__Proxy.cs │ │ ├── FeedProductRequest.cs │ │ ├── FeedResource.cs │ │ ├── FeedResourceDispatcher.cs │ │ └── IFeedProducer.cs │ ├── HandlerMissingException.cs │ ├── HttpProperties.cs │ ├── IClientConsumer.cs │ ├── IConfigurationResource.cs │ ├── IDispatcher.cs │ ├── IDispatcherPool.cs │ ├── IErrorHandler.cs │ ├── IMapper.cs │ ├── IRequestSender.cs │ ├── IResource.cs │ ├── IResourceRequestHandler.cs │ ├── IResponseConsumer.cs │ ├── IServer.cs │ ├── LoadBalancingClientRequestConsumerActor.cs │ ├── Loader.cs │ ├── MediaTypeMapper.cs │ ├── MediaTypeNotSupportedException.cs │ ├── ObjectResponse.cs │ ├── ParameterResolver.cs │ ├── RequestExecutor.cs │ ├── RequestHandler.cs │ ├── RequestHandler0.cs │ ├── RequestHandler1.cs │ ├── RequestHandler2.cs │ ├── RequestHandler3.cs │ ├── RequestHandler4.cs │ ├── RequestHandler5.cs │ ├── RequestHandler6.cs │ ├── RequestHandler7.cs │ ├── RequestHandler8.cs │ ├── RequestObjectExecutor.cs │ ├── RequestSenderProbeActor.cs │ ├── RequestSender__Proxy.cs │ ├── Resource.cs │ ├── ResourceBuilder.cs │ ├── ResourceDispatcherGenerator.cs │ ├── ResourceErrorProcessor.cs │ ├── ResourceHandler.cs │ ├── ResourceRequestHandlerActor.cs │ ├── ResourceRequestHandler__Proxy.cs │ ├── Resources.cs │ ├── RoundRobinClientRequestConsumerActor.cs │ ├── ServerActor.cs │ ├── Server__Proxy.cs │ ├── SinglePageApplicationConfiguration.cs │ ├── SinglePageApplicationResource.cs │ ├── Sse │ │ ├── ISseFeed.cs │ │ ├── ISsePublisher.cs │ │ ├── MessageEvent.cs │ │ ├── SseClient.cs │ │ ├── SseEvent.cs │ │ ├── SseFeed__Proxy.cs │ │ ├── SsePublisher__Proxy.cs │ │ ├── SseStreamResource.cs │ │ ├── SseStreamResourceDispatcher.cs │ │ └── SseSubscriber.cs │ ├── StaticFilesResource.cs │ ├── StaticFilesResourceDispatcher.cs │ └── TypeLoader.cs │ ├── Response.cs │ ├── ResponseFilter.cs │ ├── ResponseHeader.cs │ ├── ResponseParser.cs │ ├── UrlFactory.cs │ ├── Version.cs │ └── Vlingo.Xoom.Http.csproj └── vlingo-64x64.png /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: .NET 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: [ ubuntu-latest, windows-latest, macOS-latest ] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Setup .NET 6.0.x 20 | id: setup-dotnet6 21 | uses: actions/setup-dotnet@v1 22 | with: 23 | dotnet-version: 6.0.x 24 | - name: Restore dependencies 25 | id: restore-deps 26 | run: dotnet restore ./src/Vlingo.Xoom.Http.sln 27 | - name: Build 28 | id: build 29 | run: dotnet build ./src/Vlingo.Xoom.Http.sln --no-restore 30 | - name: Test 31 | id: test 32 | run: dotnet test ./src/Vlingo.Xoom.Http.Tests/Vlingo.Xoom.Http.Tests.csproj --no-build --verbosity normal 33 | - name: slack - GitHub Actions Slack integration 34 | uses: act10ns/slack@v1.2.2 35 | env: 36 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 37 | with: 38 | status: ${{ job.status }} 39 | steps: ${{ toJson(steps) }} 40 | if: always() -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute to the xoom-net-http 2 | 3 | #### **Did you find a bug?** 4 | 5 | * Make sure the bug was not already reported here: [Issues](https://github.com/vlingo-net/xoom-net-http/issues). 6 | 7 | * If nonexisting, open a new issue for the problem: [Open New Issue](https://github.com/vlingo-net/xoom-net-http/issues/new). Always provide a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. 8 | 9 | #### **Patches and bug fixes** 10 | 11 | * Open a new GitHub pull request with the patch. 12 | 13 | * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. 14 | 15 | * It would be really nice if your followed the basic code format used prevelently. 16 | 17 | #### **Please don't reformat existing code** 18 | 19 | * Just because you don't like a given code style doesn't mean you have the authority to change it. Cosmeic changes add zero to little value. 20 | 21 | #### **New features and enhancements** 22 | 23 | * Email your post your suggestion and provide an example implementation. 24 | 25 | * After agreement open a PR or issue. 26 | 27 | #### **Direct questions to...** 28 | 29 | * Vaughn Vernon: vaughn at kalele dot io 30 | 31 | #### **Contribute to documentation** 32 | 33 | * Vaughn Vernon: vaughn at kalele dot io 34 | 35 | Thanks for your kind assistance! :smile: 36 | 37 | Vaughn Vernon and the Vlingo .NET team 38 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.10.1 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xoom-net-http 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/1c2u6kbrpbvfjxgf/branch/master?svg=true)](https://ci.appveyor.com/project/VlingoNetOwner/xoom-net-http/branch/master) 4 | ![Build master](https://github.com/vlingo-net/xoom-net-http/workflows/.NET/badge.svg) 5 | [![NuGet](https://img.shields.io/nuget/v/Vlingo.Xoom.Http.svg)](https://www.nuget.org/packages/Vlingo.Xoom.Http) 6 | [![Gitter](https://badges.gitter.im/vlingo-platform-net/community.svg)](https://gitter.im/vlingo-platform-net/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 7 | 8 | ### Usage 9 | 10 | Reactive, scalable, and resilient HTTP servers and RESTful services running on vlingo-net/cluster and vlingo-net/actors. 11 | 12 | 1. The essential features are completed 13 | * Fully actor-based asynchronous requests and responses. 14 | * The request handling is resource based. 15 | * Requests that require message body content are auto-mapped to simple Java objects. 16 | 17 | 2. To run the Server: 18 | * [Use Server#StartWith() to start the Server actor](https://github.com/vlingo/xoom-net-http/blob/master/src/Vlingo.Xoom.Http/Resource/Server.cs) 19 | * The light-weight Server is meant to be run inside vlingo/cluster nodes the require RESTful HTTP support. 20 | 21 | 3. See the following for usage examples: 22 | * [vlingo/http properties file](https://github.com/vlingo/xoom-net-http/blob/master/src/Vlingo.Xoom.Http.Tests/Resources/vlingo-http.properties) 23 | * [The user resource sample](#) (Sample link to be provided) 24 | * [The user profile resource sample](#) (Sample link to be provided) 25 | 26 | 27 | License (See LICENSE file for full license) 28 | ------------------------------------------- 29 | Copyright © 2012-2021 VLINGO LABS. All rights reserved. 30 | 31 | This Source Code Form is subject to the terms of the 32 | Mozilla Public License, v. 2.0. If a copy of the MPL 33 | was not distributed with this file, You can obtain 34 | one at https://mozilla.org/MPL/2.0/. 35 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.10.1.{build} 2 | image: 3 | - Visual Studio 2022 4 | - Ubuntu 5 | - macOS 6 | - macOS-Monterey 7 | - macOS-Bigsur 8 | configuration: Release 9 | skip_commits: 10 | message: /.*\[ci\-skip\].*/ 11 | before_build: 12 | - dotnet restore src/Vlingo.Xoom.Http.sln 13 | build: 14 | project: src/Vlingo.Xoom.Http.sln 15 | verbosity: minimal 16 | publish_nuget: true 17 | for: 18 | - 19 | matrix: 20 | only: 21 | - image: Visual Studio 2019 22 | before_test: 23 | - netsh advfirewall set currentprofile state off 24 | after_test: 25 | - netsh advfirewall set currentprofile state on 26 | test_script: 27 | - dotnet test src/Vlingo.Xoom.Http.Tests 28 | deploy: 29 | - provider: NuGet 30 | api_key: 31 | secure: 4VJZEFZNaDrk3FJmRSmBW+wQugDoPi6DtVlsLZ+26IOo+wb0u9JlnTOTQF+NXs2s 32 | skip_symbols: true 33 | artifact: /.*\.nupkg/ 34 | on: 35 | branch: master 36 | notifications: 37 | - provider: Webhook 38 | url: https://webhooks.gitter.im/e/37621a855e91c31ab1da 39 | method: POST 40 | on_build_success: true 41 | on_build_failure: true 42 | on_build_status_changed: true 43 | -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/ChunkedBodyTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Xunit; 9 | 10 | namespace Vlingo.Xoom.Http.Tests; 11 | 12 | public class ChunkedBodyTest 13 | { 14 | private const string Chunk1 = "ABCDEFGHIJKLMNOPQRSTUVWYYZ0123"; 15 | private const string Chunk2 = "abcdefghijklmnopqrstuvwxyz012345"; 16 | 17 | [Fact] 18 | public void TestThatChunkedBodyChunks() 19 | { 20 | var body = 21 | Body 22 | .BeginChunked() 23 | .AppendChunk(Chunk1) 24 | .AppendChunk(Chunk2) 25 | .End(); 26 | 27 | Assert.Contains(AsChunk(Chunk1), body.Content); 28 | Assert.Contains(AsChunk(Chunk2), body.Content); 29 | } 30 | 31 | private string AsChunk(string content) => $"{content.Length:x8}\r\n{content}\r\n"; 32 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Content/css/styles.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-align: center; 3 | color: blue; 4 | } 5 | 6 | h2 { 7 | text-align: center; 8 | color: blue; 9 | } 10 | 11 | p { 12 | text-align: center; 13 | color: blue; 14 | } 15 | -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Content/index.html: -------------------------------------------------------------------------------- 1 | 2 | Test 3 |

Test1

4 |

Test paragraph.

5 | 6 | -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Content/js/vuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuetify from 'vuetify' 3 | import 'vuetify/dist/vuetify.min.css' 4 | 5 | Vue.use(Vuetify, { 6 | theme: { 7 | primary: "#00796B", 8 | secondary: "#B2DFDB", 9 | accent: "#009688", 10 | error: "#f44336", 11 | warning: "#ffeb3b", 12 | info: "#2196f3", 13 | success: "#4caf50" 14 | }, 15 | iconfont: 'md', 16 | }) 17 | -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Content/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Content/views/test 2/index.html: -------------------------------------------------------------------------------- 1 | 2 | Test 3 |

Test2

4 |

Test paragraph.

5 | 6 | -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/ContentEncodingMethodTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Xunit; 9 | 10 | namespace Vlingo.Xoom.Http.Tests; 11 | 12 | public class ContentEncodingMethodTest 13 | { 14 | [Fact] 15 | public void MethodParseReturnsMethod() 16 | { 17 | var method = "gzip"; 18 | var result = ContentEncodingMethodHelper.Parse(method); 19 | Assert.True(result.IsPresent); 20 | Assert.Equal(ContentEncodingMethod.Gzip, result.Get()); 21 | } 22 | 23 | [Fact] 24 | public void MethodParseReturnsEmpty() 25 | { 26 | var method = "jarjar"; 27 | var result = ContentEncodingMethodHelper.Parse(method); 28 | Assert.False(result.IsPresent); 29 | } 30 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/ContentEncodingTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Xunit; 9 | 10 | namespace Vlingo.Xoom.Http.Tests; 11 | 12 | public class ContentEncodingTest 13 | { 14 | [Fact] 15 | public void CreateEncodingFrom() 16 | { 17 | var results = ContentEncoding.ParseFromHeader("gzip, br"); 18 | ContentEncodingMethod[] expectedMethods = 19 | { 20 | ContentEncodingMethod.Gzip, ContentEncodingMethod.Brotli 21 | }; 22 | 23 | Assert.Equal(expectedMethods, results.EncodingMethods); 24 | } 25 | 26 | [Fact] 27 | public void CreateEncodingSkipsUnkownEncoding() 28 | { 29 | var results = ContentEncoding.ParseFromHeader("gzip, br, foo"); 30 | ContentEncodingMethod[] expectedMethods = { 31 | ContentEncodingMethod.Gzip, ContentEncodingMethod.Brotli 32 | }; 33 | 34 | Assert.Equal(expectedMethods, results.EncodingMethods); 35 | } 36 | 37 | [Fact] 38 | public void CreateEncodingEmpty() 39 | { 40 | ContentEncoding results = ContentEncoding.ParseFromHeader(""); 41 | ContentEncodingMethod[] expectedMethods = {}; 42 | Assert.Equal(expectedMethods, results.EncodingMethods); 43 | } 44 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/ContentTypeTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Xunit; 9 | 10 | namespace Vlingo.Xoom.Http.Tests; 11 | 12 | public class ContentTypeTest 13 | { 14 | [Fact] 15 | public void TestThatContentTypeHasMediaTypeOnly() 16 | { 17 | var contentType = ContentType.Of("text/html"); 18 | 19 | Assert.NotNull(contentType); 20 | Assert.Equal("text/html", contentType.MediaType); 21 | Assert.Equal("text/html", contentType.ToString()); 22 | Assert.Equal(string.Empty, contentType.Charset); 23 | Assert.Equal(string.Empty, contentType.Boundary); 24 | } 25 | 26 | [Fact] 27 | public void TestThatContentTypeHasMediaTypeCharsetOnly() 28 | { 29 | var contentType = ContentType.Of("text/html", "charset=UTF-8"); 30 | 31 | Assert.NotNull(contentType); 32 | Assert.Equal("text/html", contentType.MediaType); 33 | Assert.Equal("charset=UTF-8", contentType.Charset); 34 | Assert.Equal("text/html; charset=UTF-8", contentType.ToString()); 35 | Assert.Equal(string.Empty, contentType.Boundary); 36 | } 37 | 38 | [Fact] 39 | public void TestThatContentTypeHasMediaTypeCharsetBoundaryOnly() 40 | { 41 | var contentType = ContentType.Of("text/html", "charset=UTF-8", "boundary=something"); 42 | 43 | Assert.NotNull(contentType); 44 | Assert.Equal("text/html", contentType.MediaType); 45 | Assert.Equal("charset=UTF-8", contentType.Charset); 46 | Assert.Equal("boundary=something", contentType.Boundary); 47 | Assert.Equal("text/html; charset=UTF-8; boundary=something", contentType.ToString()); 48 | } 49 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Converter.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.IO; 10 | using System.Text; 11 | using Xunit.Abstractions; 12 | 13 | namespace Vlingo.Xoom.Http.Tests; 14 | 15 | public class Converter : TextWriter 16 | { 17 | private readonly ITestOutputHelper _output; 18 | 19 | public Converter(ITestOutputHelper output) => _output = output; 20 | 21 | public override Encoding Encoding => Encoding.UTF8; 22 | 23 | public override void WriteLine(string message) 24 | { 25 | try 26 | { 27 | _output.WriteLine(message); 28 | } 29 | catch (InvalidOperationException e) 30 | { 31 | if (e.Message != "There is no currently active test.") 32 | { 33 | throw; 34 | } 35 | } 36 | } 37 | 38 | public override void WriteLine(string format, params object[] args) => _output.WriteLine(format, args); 39 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/ExtendedCharactersFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System.Text; 9 | 10 | namespace Vlingo.Xoom.Http.Tests; 11 | 12 | public class ExtendedCharactersFixture 13 | { 14 | public static string AsciiWithExtendedCharacters() 15 | { 16 | var builder = new StringBuilder(); 17 | 18 | var asciiBegin = 0x0020; 19 | var asciiEnd = 0x007E; 20 | 21 | for (var ascii = asciiBegin; ascii <= asciiEnd; ++ascii) 22 | { 23 | builder.Append((char) ascii); 24 | } 25 | 26 | var cyrillicBegin = 0x0409; 27 | var cyrillicEnd = 0x04FF; 28 | 29 | for (var cyrillic = cyrillicBegin; cyrillic <= cyrillicEnd; ++cyrillic) 30 | { 31 | builder.Append((char) cyrillic); 32 | } 33 | 34 | var greekCopticBegin = 0x0370; 35 | var greekCopticEnd = 0x03FF; 36 | 37 | for (var greekCoptic = greekCopticBegin; greekCoptic <= greekCopticEnd; ++greekCoptic) 38 | { 39 | builder.Append((char) greekCoptic); 40 | } 41 | 42 | return builder.ToString(); 43 | } 44 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Media/AcceptMediaTypeTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Http.Media; 9 | using Xunit; 10 | 11 | namespace Vlingo.Xoom.Http.Tests.Media; 12 | 13 | public class AcceptMediaTypeTest 14 | { 15 | [Fact] 16 | public void SpecificMimeTypeGreaterThanGeneric() 17 | { 18 | var acceptMediaType1 = new ResponseMediaTypeSelector.AcceptMediaType("application", "json"); 19 | var acceptMediaType2 = new ResponseMediaTypeSelector.AcceptMediaType("*", "*"); 20 | Assert.Equal( 1, acceptMediaType1.CompareTo(acceptMediaType2)); 21 | Assert.Equal( -1, acceptMediaType2.CompareTo(acceptMediaType1)); 22 | } 23 | 24 | [Fact] 25 | public void SpecificMimeSubTypeGreaterThanGeneric() 26 | { 27 | var acceptMediaType1 = new ResponseMediaTypeSelector.AcceptMediaType("application", "json"); 28 | var acceptMediaType2 = new ResponseMediaTypeSelector.AcceptMediaType("application", "*"); 29 | Assert.Equal( 1, acceptMediaType1.CompareTo(acceptMediaType2)); 30 | Assert.Equal( -1, acceptMediaType2.CompareTo(acceptMediaType1)); 31 | } 32 | 33 | [Fact] 34 | public void SpecificParameterGreaterThanGenericWithSameQualityFactor() 35 | { 36 | var acceptMediaType1 = new MediaTypeDescriptor.Builder( 37 | (a, b, c) => new ResponseMediaTypeSelector.AcceptMediaType(a, b, c)) 38 | .WithMimeType("application") 39 | .WithMimeSubType("xml") 40 | .WithParameter("version", "1.0") 41 | .Build(); 42 | 43 | var acceptMediaType2 = new ResponseMediaTypeSelector.AcceptMediaType("application", "json"); 44 | Assert.Equal( 1, acceptMediaType1.CompareTo(acceptMediaType2)); 45 | Assert.Equal( -1, acceptMediaType2.CompareTo(acceptMediaType1)); 46 | } 47 | 48 | [Fact] 49 | public void QualityFactorTrumpsSpecificity() 50 | { 51 | var acceptMediaType1 = new MediaTypeDescriptor.Builder( 52 | (a, b, c) => new ResponseMediaTypeSelector.AcceptMediaType(a, b, c)) 53 | .WithMimeType("text") 54 | .WithMimeSubType("*") 55 | .Build(); 56 | 57 | var acceptMediaType2 = new MediaTypeDescriptor.Builder( 58 | (a, b, c) => new ResponseMediaTypeSelector.AcceptMediaType(a, b, c)) 59 | .WithMimeType("text") 60 | .WithMimeSubType("json") 61 | .WithParameter("q", "0.8") 62 | .Build(); 63 | 64 | Assert.Equal( 1, acceptMediaType1.CompareTo(acceptMediaType2)); 65 | Assert.Equal( -1, acceptMediaType2.CompareTo(acceptMediaType1)); 66 | } 67 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Media/ContentMediaTypeTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Http.Media; 9 | using Vlingo.Xoom.Http.Resource; 10 | using Xunit; 11 | 12 | namespace Vlingo.Xoom.Http.Tests.Media; 13 | 14 | public class ContentMediaTypeTest 15 | { 16 | [Fact] 17 | public void WildCardsAreNotAllowed() 18 | { 19 | Assert.Throws(() => new ContentMediaType("application", "*")); 20 | } 21 | 22 | [Fact] 23 | public void InvalidMimeTypeNotAllowed() 24 | { 25 | Assert.Throws(() => new ContentMediaType("unknownMimeType", "foo")); 26 | } 27 | 28 | [Fact] 29 | public void BuilderCreates() 30 | { 31 | var builder = new MediaTypeDescriptor.Builder((a, b, c) => new ContentMediaType(a, b, c)); 32 | var contentMediaType = builder 33 | .WithMimeType(ContentMediaType.MimeTypes.Application.ToString().ToLower()) 34 | .WithMimeSubType("json") 35 | .Build(); 36 | 37 | Assert.Equal(ContentMediaType.Json, contentMediaType); 38 | } 39 | 40 | [Fact] 41 | public void BuiltInTypesHaveCorrectFormat() 42 | { 43 | var jsonType = new ContentMediaType("application", "json"); 44 | Assert.Equal(jsonType, ContentMediaType.Json); 45 | 46 | var xmlType = new ContentMediaType("application", "xml"); 47 | Assert.Equal(xmlType, ContentMediaType.Xml); 48 | } 49 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Media/MediaTypeParserTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System.Collections.Generic; 9 | using Vlingo.Xoom.Http.Media; 10 | using Xunit; 11 | 12 | namespace Vlingo.Xoom.Http.Tests.Media; 13 | 14 | public class MediaTypeParserTest 15 | { 16 | [Fact] 17 | public void SimpleTypeEmptyParameters() 18 | { 19 | var mediaType = Parse("application/json"); 20 | var mediaTypeExpected = new MediaTypeDescriptor.Builder( 21 | (a, b, c) => new MediaTypeTest(a, b, c)) 22 | .WithMimeType("application") 23 | .WithMimeSubType("json") 24 | .Build(); 25 | 26 | Assert.Equal(mediaTypeExpected, mediaType); 27 | } 28 | 29 | [Fact] 30 | public void ParseParameters() 31 | { 32 | var mediaTypeDescriptor = Parse("application/*;q=0.8;foo=bar"); 33 | 34 | var mediaTypeExpected = new MediaTypeDescriptor.Builder( 35 | (a, b, c) => new MediaTypeTest(a, b, c)) 36 | .WithMimeType("application") 37 | .WithMimeSubType("*") 38 | .WithParameter("q", "0.8") 39 | .WithParameter("foo", "bar") 40 | .Build(); 41 | 42 | Assert.Equal(mediaTypeExpected, mediaTypeDescriptor); 43 | Assert.Equal("application/*;q=0.8;foo=bar", mediaTypeDescriptor.ToString()); 44 | } 45 | 46 | [Fact] 47 | public void IncorrectFormatUsesEmptyStringAndDefaultQuality() 48 | { 49 | var mediaType = Parse("typeOnly"); 50 | var mediaTypeExpected = new MediaTypeDescriptor.Builder( 51 | (a, b, c) => new MediaTypeTest(a, b, c)) 52 | .WithMimeType("") 53 | .WithMimeSubType("") 54 | .Build(); 55 | 56 | Assert.Equal(mediaTypeExpected, mediaType); 57 | } 58 | 59 | private MediaTypeTest Parse(string descriptor) 60 | { 61 | return MediaTypeParser.ParseFrom(descriptor, 62 | new MediaTypeDescriptor.Builder( 63 | (a, b, c) => new MediaTypeTest(a, b, c))); 64 | } 65 | 66 | private class MediaTypeTest : MediaTypeDescriptor 67 | { 68 | public MediaTypeTest(string mimeType, string mimeSubType, IDictionary parameters) 69 | : base(mimeType, mimeSubType, parameters) 70 | { 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Media/ResponseContentMediaTypeSelectorTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Http.Media; 9 | using Xunit; 10 | 11 | namespace Vlingo.Xoom.Http.Tests.Media; 12 | 13 | public class ResponseContentMediaTypeSelectorTest 14 | { 15 | [Fact] 16 | public void Single_media_type_matches() 17 | { 18 | var specificTypeAccepted = "application/json"; 19 | var selector = new ResponseMediaTypeSelector(specificTypeAccepted); 20 | var selected = selector.SelectType(new[]{ContentMediaType.Json}); 21 | Assert.Equal(ContentMediaType.Json, selected); 22 | } 23 | 24 | [Fact] 25 | public void Wild_card_media_type_matches() 26 | { 27 | var xmlAndJsonSuperTypeAccepted = "application/*"; 28 | var selector = new ResponseMediaTypeSelector(xmlAndJsonSuperTypeAccepted); 29 | var selected = selector.SelectType(new[]{ContentMediaType.Json}); 30 | Assert.Equal(ContentMediaType.Json, selected); 31 | } 32 | 33 | [Fact] 34 | public void Generic_media_type_select_by_order_of_media_type() 35 | { 36 | var xmlAndJsonSuperTypeAccepted = "application/*"; 37 | var selector = new ResponseMediaTypeSelector(xmlAndJsonSuperTypeAccepted); 38 | var selected = selector.SelectType(new[]{ContentMediaType.Xml, ContentMediaType.Json}); 39 | Assert.Equal(ContentMediaType.Xml, selected); 40 | } 41 | 42 | [Fact] 43 | public void Specific_media_type_select_highest_ranked() 44 | { 45 | var jsonHigherPriorityXmlLowerPriorityAccepted = "application/xml;q=0.8, application/json"; 46 | var selector = new ResponseMediaTypeSelector(jsonHigherPriorityXmlLowerPriorityAccepted); 47 | var selected = selector.SelectType(new[]{ContentMediaType.Xml, ContentMediaType.Json}); 48 | Assert.Equal(ContentMediaType.Json, selected); 49 | } 50 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Resource/DefaultTextPlainMapperTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Http.Resource; 10 | using Xunit; 11 | 12 | namespace Vlingo.Xoom.Http.Tests.Resource; 13 | 14 | public class DefaultTextPlainMapperTest 15 | { 16 | [Fact] 17 | public void TestFromObjectToStringUsesToString() 18 | { 19 | var mapper = new DefaultTextPlainMapper(); 20 | Assert.Equal("toStringResult", mapper.From(new ObjectForTest())); 21 | } 22 | 23 | [Fact] 24 | public void TestDeserializationToNonStringFails() 25 | { 26 | var mapper = new DefaultTextPlainMapper(); 27 | Assert.Throws(() => mapper.From("some string", typeof(ObjectForTest))); 28 | } 29 | 30 | [Fact] 31 | public void TestDeserializationToStringSucceed() 32 | { 33 | var mapper = new DefaultTextPlainMapper(); 34 | var canBeSerialized = mapper.From("some string", typeof(string)); 35 | Assert.Equal("some string", canBeSerialized); 36 | } 37 | } 38 | 39 | internal class ObjectForTest 40 | { 41 | public override string ToString() => "toStringResult"; 42 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Resource/FailResource.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Common; 9 | using Vlingo.Xoom.Http.Resource; 10 | using Xunit.Abstractions; 11 | 12 | namespace Vlingo.Xoom.Http.Tests.Resource; 13 | 14 | public class FailResource : ResourceHandler 15 | { 16 | private readonly ITestOutputHelper _output; 17 | 18 | public FailResource(ITestOutputHelper output) => _output = output; 19 | 20 | public ICompletes Query() 21 | { 22 | _output.WriteLine("QUERY"); 23 | return Xoom.Common.Completes.WithFailure(Response.Of(ResponseStatus.BadRequest)); 24 | } 25 | 26 | public override Http.Resource.Resource Routes() 27 | => ResourceBuilder.Resource("Failure API", ResourceBuilder.Get("/fail").Handle(Query)); 28 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Resource/Feed/EventsFeedProducerActor.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System.Text; 9 | using Vlingo.Xoom.Actors; 10 | using Vlingo.Xoom.Http.Resource.Feed; 11 | 12 | namespace Vlingo.Xoom.Http.Tests.Resource.Feed; 13 | 14 | public class EventsFeedProducerActor : Actor, IFeedProducer 15 | { 16 | public void ProduceFeedFor(FeedProductRequest request) 17 | { 18 | var body = 19 | new StringBuilder() 20 | .Append(request.FeedName) 21 | .Append(":") 22 | .Append(request.FeedProductId) 23 | .Append(":"); 24 | 25 | for (var count = 1; count <= request.FeedProductElements; ++count) 26 | { 27 | body.Append(count).Append("\n"); 28 | } 29 | 30 | var response = Response.Of(ResponseStatus.Ok, body.ToString()); 31 | request.Context?.Completes.With(response); 32 | } 33 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Resource/MediaTypeMapperTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Http.Media; 10 | using Vlingo.Xoom.Http.Resource; 11 | using Xunit; 12 | 13 | namespace Vlingo.Xoom.Http.Tests.Resource; 14 | 15 | public class MediaTypeMapperTest 16 | { 17 | [Fact] 18 | public void Registered_mapper_maps_type() 19 | { 20 | var mappedToObject = new Object(); 21 | var mappedToString = "mappedToString"; 22 | 23 | var testMapper = new TestMapper(mappedToObject, mappedToString); 24 | var mediaTypeMapper = new MediaTypeMapper.Builder() 25 | .AddMapperFor(ContentMediaType.Json, testMapper) 26 | .Build(); 27 | 28 | Assert.Equal(mappedToString, mediaTypeMapper.From(new object(), ContentMediaType.Json)); 29 | } 30 | 31 | [Fact] 32 | public void Exception_thrown_for_invalid_mapper() 33 | { 34 | var mediaTypeMapper = new MediaTypeMapper.Builder().Build(); 35 | 36 | Assert.Throws(() => 37 | mediaTypeMapper.From(new object(), ContentMediaType.Json)); 38 | } 39 | 40 | [Fact] 41 | public void Parameters_do_not_affect_mapping() 42 | { 43 | var mediaTypeMapper = new MediaTypeMapper.Builder() 44 | .AddMapperFor(ContentMediaType.Json, DefaultJsonMapper.Instance) 45 | .Build(); 46 | var contentMediaType = ContentMediaType.ParseFromDescriptor("application/json; charset=UTF-8"); 47 | 48 | mediaTypeMapper.From(new object(), contentMediaType); 49 | } 50 | 51 | #pragma warning disable 8632 52 | private class TestMapper : IMapper 53 | { 54 | private readonly string _returnString; 55 | private readonly T _returnObject; 56 | 57 | public TestMapper(T mappedToObject, string mappedToString) 58 | { 59 | _returnObject = mappedToObject; 60 | _returnString = mappedToString; 61 | } 62 | 63 | public object? From(string? data, Type? type) => _returnObject; 64 | 65 | public string? From(T1 data) => _returnString; 66 | } 67 | #pragma warning restore 8632 68 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Resource/MockCompletesEventuallyResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Actors; 9 | using Vlingo.Xoom.Actors.TestKit; 10 | using Vlingo.Xoom.Common; 11 | 12 | namespace Vlingo.Xoom.Http.Tests.Resource; 13 | 14 | public class MockCompletesEventuallyResponse : ICompletesEventually 15 | { 16 | private AccessSafely _withCalls = AccessSafely.AfterCompleting(0); 17 | 18 | public AtomicReference Response { get; } = new AtomicReference(); 19 | 20 | /// 21 | /// Answer with an AccessSafely which writes nulls to "with" and reads the write count from the "completed". 22 | /// 23 | /// Number of times With(outcome) must be called before ReadFrom(...) will return. 24 | /// AccessSafely instance 25 | /// Note: Clients can replace the default lambdas with their own via readingWith/writingWith. 26 | public AccessSafely ExpectWithTimes(int n) 27 | { 28 | _withCalls = AccessSafely.AfterCompleting(n) 29 | .WritingWith("with", r => Response.Set(r)) 30 | .ReadingWith("completed", () => _withCalls.TotalWrites) 31 | .ReadingWith("response", () => Response.Get()); 32 | return _withCalls; 33 | } 34 | public void Conclude() 35 | { 36 | } 37 | 38 | public void Stop() 39 | { 40 | } 41 | 42 | public bool IsStopped => false; 43 | public void With(object outcome) 44 | { 45 | _withCalls.WriteUsing("with", (Response)outcome); 46 | } 47 | 48 | public IAddress Address => null; 49 | 50 | public override string ToString() => 51 | $"MockCompletesEventuallyResponse [response={Response}, withCalls={_withCalls}]"; 52 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Resource/RequestHandlerTestBase.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | using Vlingo.Xoom.Actors.Plugin.Logging.Console; 11 | using Vlingo.Xoom.Http.Media; 12 | using Vlingo.Xoom.Http.Resource; 13 | using Xunit; 14 | using Xunit.Abstractions; 15 | 16 | namespace Vlingo.Xoom.Http.Tests.Resource; 17 | 18 | public class RequestHandlerTestBase 19 | { 20 | protected readonly ILogger Logger; 21 | 22 | protected RequestHandlerTestBase(ITestOutputHelper output) 23 | { 24 | var converter = new Converter(output); 25 | Console.SetOut(converter); 26 | 27 | Logger = ConsoleLogger.TestInstance(); 28 | } 29 | 30 | protected void AssertResponsesAreEquals(Response expected, Response actual) => Assert.Equal(expected.ToString(), actual.ToString()); 31 | 32 | internal void AssertResolvesAreEquals(ParameterResolver expected, ParameterResolver actual) 33 | { 34 | Assert.Equal(expected.Type, actual.Type); 35 | Assert.Equal(expected.ParamClass, actual.ParamClass); 36 | } 37 | 38 | protected MediaTypeMapper DefaultMediaTypeMapperForJson() 39 | { 40 | 41 | return new MediaTypeMapper.Builder() 42 | .AddMapperFor(ContentMediaType.Json, new DefaultJsonMapper()) 43 | .Build(); 44 | } 45 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Resource/ResourceBuilderTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Common.Serialization; 9 | using Vlingo.Xoom.Http.Resource; 10 | using Vlingo.Xoom.Http.Tests.Sample.User; 11 | using Xunit; 12 | using Xunit.Abstractions; 13 | 14 | namespace Vlingo.Xoom.Http.Tests.Resource; 15 | 16 | public class ResourceBuilderTest : ResourceTestFixtures 17 | { 18 | [Fact] 19 | public void SimpleRoute() 20 | { 21 | var resource = (DynamicResource) ResourceBuilder.Resource("userResource", 22 | ResourceBuilder.Get("/helloWorld").Handle(() => Xoom.Common.Completes.WithSuccess(Response.Of(ResponseStatus.Ok, JsonSerialization.Serialized("Hello World")))), 23 | ResourceBuilder.Post("/post/{postId}") 24 | .Param() 25 | .Body() 26 | .Handle((postId, userData) => Xoom.Common.Completes.WithSuccess(Response.Of(ResponseStatus.Ok, JsonSerialization.Serialized(postId))))); 27 | 28 | Assert.NotNull(resource); 29 | Assert.Equal("userResource", resource.Name); 30 | Assert.Equal(10, resource.HandlerPoolSize); 31 | Assert.Equal(2, resource.Handlers.Count); 32 | } 33 | 34 | [Fact] 35 | public void ShouldRespondToCorrectResourceHandler() 36 | { 37 | var resource = (DynamicResource) ResourceBuilder.Resource("userResource", 38 | ResourceBuilder.Get("/customers/{customerId}/accounts/{accountId}") 39 | .Param() 40 | .Param() 41 | .Handle((customerId, accountId) => Xoom.Common.Completes.WithSuccess(Response.Of(ResponseStatus.Ok, JsonSerialization.Serialized("users")))), 42 | ResourceBuilder.Get("/customers/{customerId}/accounts/{accountId}/withdraw") 43 | .Param() 44 | .Param() 45 | .Handle((customerId, accountId) => Xoom.Common.Completes.WithSuccess(Response.Of(ResponseStatus.Ok, JsonSerialization.Serialized("user admin")))) 46 | ); 47 | 48 | var matchWithdrawResource = resource.MatchWith(Method.Get, "/customers/cd1234/accounts/ac1234/withdraw".ToMatchableUri()); 49 | var matchAccountResource = resource.MatchWith(Method.Get, "/customers/cd1234/accounts/ac1234".ToMatchableUri()); 50 | 51 | Assert.Equal("/customers/{customerId}/accounts/{accountId}/withdraw", matchWithdrawResource.Action?.Uri); 52 | Assert.Equal("/customers/{customerId}/accounts/{accountId}", matchAccountResource.Action?.Uri); 53 | } 54 | 55 | public ResourceBuilderTest(ITestOutputHelper output) : base(output) 56 | { 57 | } 58 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Resource/ResourceFailureTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | using Vlingo.Xoom.Common; 11 | using Vlingo.Xoom.Http.Resource; 12 | using Vlingo.Xoom.Wire.Message; 13 | using Vlingo.Xoom.Wire.Nodes; 14 | using Xunit; 15 | using Xunit.Abstractions; 16 | using Configuration = Vlingo.Xoom.Http.Resource.Configuration; 17 | 18 | namespace Vlingo.Xoom.Http.Tests.Resource; 19 | 20 | public class ResourceFailureTest : IDisposable 21 | { 22 | private readonly ITestOutputHelper _output; 23 | private static readonly AtomicInteger NextPort = new AtomicInteger(14000); 24 | 25 | private readonly IConsumerByteBuffer _consumerByteBuffer = BasicConsumerByteBuffer.Allocate(5, 1024); 26 | private Client _client; 27 | private int _count; 28 | private readonly int _port; 29 | private Response _response; 30 | private readonly IServer _server; 31 | private readonly World _world; 32 | 33 | [Fact] 34 | public void TestBasicFailure() 35 | { 36 | var consumer = new TestResponseConsumer(_output); 37 | var access = consumer.AfterCompleting(1); 38 | var unknown = new UnknownResponseConsumer(access, _output); 39 | 40 | var config = 41 | Client.Configuration.DefaultedExceptFor(_world.Stage, Address.From(Host.Of("localhost"), _port, AddressType.None), unknown); 42 | 43 | _client = Client.Using(config, Client.ClientConsumerType.RoundRobin, 1); 44 | 45 | var request = Request.From(ToConsumerByteBuffer("GET /fail HTTP/1.1\nHost: vlingo.io\n\n")); 46 | 47 | _count = 0; 48 | 49 | _client.RequestWith(request).AndThenConsume(response => { 50 | ++_count; 51 | _response = response; 52 | }).Await(); 53 | 54 | Assert.Equal(1, _count); 55 | 56 | Assert.NotNull(_response); 57 | } 58 | 59 | public ResourceFailureTest(ITestOutputHelper output) 60 | { 61 | _output = output; 62 | var converter = new Converter(output); 63 | Console.SetOut(converter); 64 | 65 | _world = World.StartWithDefaults("test-request-failure"); 66 | 67 | var resource = new FailResource(output); 68 | 69 | _port = NextPort.IncrementAndGet(); 70 | 71 | _server = ServerFactory.StartWith( 72 | _world.Stage, 73 | Resources.Are(resource.Routes()), 74 | Filters.None(), 75 | _port, 76 | Configuration.SizingConf.DefineConf(), 77 | Configuration.TimingConf.DefineConf()); 78 | } 79 | 80 | public void Dispose() 81 | { 82 | _client.Close(); 83 | _consumerByteBuffer?.Clear(); 84 | _server?.ShutDown(); 85 | _world?.Terminate(); 86 | } 87 | 88 | private IConsumerByteBuffer ToConsumerByteBuffer(string requestContent) 89 | { 90 | _consumerByteBuffer.Clear(); 91 | _consumerByteBuffer.Put(Converters.TextToBytes(requestContent)); 92 | _consumerByteBuffer.Flip(); 93 | return _consumerByteBuffer; 94 | } 95 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Resource/ServerBootstrap.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | using Vlingo.Xoom.Common; 11 | using Vlingo.Xoom.Http.Resource; 12 | using Vlingo.Xoom.Http.Tests.Sample.User; 13 | using Configuration = Vlingo.Xoom.Http.Resource.Configuration; 14 | 15 | namespace Vlingo.Xoom.Http.Tests.Resource; 16 | 17 | public class ServerBootstrap 18 | { 19 | private static readonly Random Random = new Random(); 20 | private static readonly AtomicInteger PortToUse = new AtomicInteger(Random.Next(32_768, 60_999)); 21 | 22 | public static ServerBootstrap Instance { get; private set; } 23 | 24 | public World World { get; } 25 | 26 | public static void Main(string[] args) 27 | { 28 | Instance = new ServerBootstrap(); 29 | } 30 | 31 | public IServer Server { get; } 32 | 33 | private ServerBootstrap() 34 | { 35 | World = World.Start("vlingo-http-server"); 36 | 37 | var userResource = new UserResourceFluent(World); 38 | var profileResource = new ProfileResourceFluent(World); 39 | var r1 = userResource.Routes(); 40 | var r2 = profileResource.Routes(); 41 | var resources = Resources.Are(r1, r2); 42 | 43 | Server = 44 | Server.StartWith( 45 | World.Stage, 46 | resources, 47 | Filters.None(), 48 | PortToUse.IncrementAndGet(), 49 | Configuration.SizingConf.DefineWith(4, 10, 100, 10240), 50 | Configuration.TimingConf.DefineWith(3, 1, 100), 51 | "arrayQueueMailbox", 52 | "arrayQueueMailbox"); 53 | 54 | AppDomain.CurrentDomain.ProcessExit += (s, e) => 55 | { 56 | if (Instance != null) 57 | { 58 | Instance.Server.Stop(); 59 | 60 | Console.WriteLine("\n"); 61 | Console.WriteLine("=============================="); 62 | Console.WriteLine("Stopping vlingo/http Server..."); 63 | Console.WriteLine("=============================="); 64 | } 65 | }; 66 | } 67 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Resource/Sse/MessageEventTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Http.Resource.Sse; 9 | using Xunit; 10 | 11 | namespace Vlingo.Xoom.Http.Tests.Resource.Sse; 12 | 13 | public class MessageEventTest 14 | { 15 | [Fact] 16 | public void TestThatMessageEventParses() 17 | { 18 | var @event = ": I like events.\nid: 1\nevent: E1\ndata: value\nretry: 2500\n\n"; 19 | var response = 20 | Response.Of( 21 | ResponseStatus.Ok, 22 | Headers.Of(ResponseHeader.WithContentType("text/event-stream")), @event); 23 | 24 | var messageEvents = MessageEvent.From(response); 25 | 26 | Assert.Single(messageEvents); 27 | 28 | var messageEvent = messageEvents[0]; 29 | 30 | Assert.Equal("I like events.", messageEvent.Comment); 31 | Assert.Equal("1", messageEvent.Id); 32 | Assert.Equal("E1", messageEvent.Event); 33 | Assert.Equal("value", messageEvent.Data); 34 | Assert.Equal(2500, messageEvent.Retry); 35 | } 36 | 37 | [Fact] 38 | public void TestThatMultipleMessageEventsParses() 39 | { 40 | var event1 = ": I like events.\nid: 1\nevent: E1\ndata: value1\nretry: 2500\n\n"; 41 | var event2 = ": I love events.\nid: 2\nevent: E2\ndata: value2\n\n"; 42 | var event3 = ": I <3 events.\nid: 3\nevent: E3\ndata: value3\n\n"; 43 | var event4 = "id\n\n"; 44 | 45 | var response = 46 | Response.Of( 47 | ResponseStatus.Ok, 48 | Headers.Of(ResponseHeader.WithContentType("text/event-stream")), 49 | event1 + event2 + event3 + event4); 50 | 51 | var messageEvents = MessageEvent.From(response); 52 | 53 | Assert.Equal(4, messageEvents.Count); 54 | 55 | var messageEvent1 = messageEvents[0]; 56 | Assert.Equal("I like events.", messageEvent1.Comment); 57 | Assert.Equal("1", messageEvent1.Id); 58 | Assert.Equal("E1", messageEvent1.Event); 59 | Assert.Equal("value1", messageEvent1.Data); 60 | Assert.Equal(2500, messageEvent1.Retry); 61 | 62 | var messageEvent2 = messageEvents[1]; 63 | Assert.Equal("I love events.", messageEvent2.Comment); 64 | Assert.Equal("2", messageEvent2.Id); 65 | Assert.Equal("E2", messageEvent2.Event); 66 | Assert.Equal("value2", messageEvent2.Data); 67 | Assert.Equal(MessageEvent.NoRetry, messageEvent2.Retry); 68 | 69 | var messageEvent3 = messageEvents[2]; 70 | Assert.Equal("I <3 events.", messageEvent3.Comment); 71 | Assert.Equal("3", messageEvent3.Id); 72 | Assert.Equal("E3", messageEvent3.Event); 73 | Assert.Equal("value3", messageEvent3.Data); 74 | Assert.Equal(MessageEvent.NoRetry, messageEvent3.Retry); 75 | 76 | var messageEvent4 = messageEvents[3]; 77 | Assert.True(messageEvent4.EndOfStream); 78 | } 79 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Resource/Sse/MockRequestResponseContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Common; 9 | using Vlingo.Xoom.Wire.Channel; 10 | 11 | namespace Vlingo.Xoom.Http.Tests.Resource.Sse; 12 | 13 | public class MockRequestResponseContext : RequestResponseContext 14 | { 15 | public MockResponseSenderChannel Channel { get; } 16 | 17 | public AtomicReference ConsumerDataRef => new AtomicReference(); 18 | public AtomicReference WhenClosingData => new AtomicReference(); 19 | 20 | public MockRequestResponseContext(MockResponseSenderChannel channel) 21 | { 22 | Channel = channel; 23 | } 24 | 25 | public override TR ConsumerData() => (TR)ConsumerDataRef.Get(); 26 | 27 | public override TR ConsumerData(TR data) 28 | { 29 | ConsumerDataRef.Set(data); 30 | return data; 31 | } 32 | 33 | public override void WhenClosing(object data) => WhenClosingData.Set(data); 34 | 35 | public override bool HasConsumerData => ConsumerDataRef.Get() != null; 36 | 37 | public override string Id => "1"; 38 | 39 | public override IResponseSenderChannel Sender => Channel; 40 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Resource/Sse/MockSseStreamResource.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Actors; 9 | using Vlingo.Xoom.Common; 10 | using Vlingo.Xoom.Http.Resource.Sse; 11 | 12 | namespace Vlingo.Xoom.Http.Tests.Resource.Sse; 13 | 14 | public class MockSseStreamResource : SseStreamResource 15 | { 16 | private readonly ICompletesEventually _completes; 17 | 18 | private Request _request; 19 | 20 | public readonly MockRequestResponseContext RequestResponseContext; 21 | 22 | public MockSseStreamResource(World world) : base(world) 23 | { 24 | _completes = world.CompletesFor(new BasicCompletes(world.Stage.Scheduler)); 25 | RequestResponseContext = new MockRequestResponseContext(new MockResponseSenderChannel()); 26 | } 27 | 28 | public void NextRequest(Request request) => _request = request; 29 | 30 | protected override ICompletesEventually Completes => _completes; 31 | 32 | public override Context Context => new Context(RequestResponseContext, _request, _completes); 33 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Resource/Sse/SseFeedTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | using Vlingo.Xoom.Actors; 11 | using Vlingo.Xoom.Http.Resource.Sse; 12 | using Vlingo.Xoom.Http.Tests.Sample.User; 13 | using Xunit; 14 | using Xunit.Abstractions; 15 | 16 | namespace Vlingo.Xoom.Http.Tests.Resource.Sse; 17 | 18 | public class SseFeedTest : IDisposable 19 | { 20 | private readonly SseClient _client; 21 | private readonly MockRequestResponseContext _context; 22 | private ISseFeed _feed; 23 | private readonly World _world; 24 | 25 | [Fact] 26 | public void TestThatFeedFeedsOneSubscriber() 27 | { 28 | var respondWithSafely = _context.Channel.ExpectRespondWith(1); 29 | 30 | _feed = _world.ActorFor(() => new AllSseFeedActor("all", 10, "1")); 31 | 32 | var subscriber = new SseSubscriber("all", _client, "ABC123", "42"); 33 | 34 | _feed.To(new List {subscriber}); 35 | 36 | Assert.Equal(1, respondWithSafely.ReadFrom("count")); 37 | 38 | Assert.Equal(1, _context.Channel.RespondWithCount.Get()); 39 | 40 | var eventsResponse = respondWithSafely.ReadFrom("eventsResponse"); 41 | 42 | var events = MessageEvent.From(eventsResponse); 43 | 44 | Assert.Equal(10, events.Count); 45 | } 46 | 47 | [Fact] 48 | public void TestThatFeedFeedsMultipleSubscribers() 49 | { 50 | _feed = _world.ActorFor(() => new AllSseFeedActor("all", 10, "1")); 51 | 52 | var subscriber1 = new SseSubscriber("all", _client, "ABC123", "41"); 53 | var subscriber2 = new SseSubscriber("all", _client, "ABC456", "42"); 54 | var subscriber3 = new SseSubscriber("all", _client, "ABC789", "43"); 55 | 56 | var respondWithSafely = _context.Channel.ExpectRespondWith(3); 57 | 58 | _feed.To(new List {subscriber1, subscriber2, subscriber3}); 59 | 60 | Assert.Equal(3, respondWithSafely.ReadFrom("count")); 61 | 62 | Assert.Equal(3, _context.Channel.RespondWithCount.Get()); 63 | } 64 | 65 | public SseFeedTest(ITestOutputHelper output) 66 | { 67 | var converter = new Converter(output); 68 | Console.SetOut(converter); 69 | 70 | _world = World.StartWithDefaults("test-feed"); 71 | Configuration.Define(); 72 | _context = new MockRequestResponseContext(new MockResponseSenderChannel()); 73 | _client = new SseClient(_context); 74 | } 75 | 76 | public void Dispose() 77 | { 78 | _client.Close(); 79 | _world.Terminate(); 80 | } 81 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Resource/Sse/SseSubscriberTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Http.Resource; 9 | using Vlingo.Xoom.Http.Resource.Sse; 10 | using Xunit; 11 | 12 | namespace Vlingo.Xoom.Http.Tests.Resource.Sse; 13 | 14 | public class SseSubscriberTest 15 | { 16 | private readonly SseClient _client; 17 | private readonly MockRequestResponseContext _context; 18 | 19 | [Fact] 20 | public void TestSubscriberPropertiesBehavior() 21 | { 22 | _context.Channel.ExpectRespondWith(1); 23 | 24 | var subscriber = new SseSubscriber("all", _client, "123ABC", "42"); 25 | 26 | Assert.NotNull(subscriber.Client); 27 | Assert.Equal(_context.Id, subscriber.Id); 28 | Assert.Equal("all", subscriber.StreamName); 29 | Assert.Equal("123ABC", subscriber.CorrelationId); 30 | Assert.Equal("42", subscriber.CurrentEventId); 31 | subscriber.CurrentEventId = "4242"; 32 | Assert.Equal("4242", subscriber.CurrentEventId); 33 | Assert.True(subscriber.IsCompatibleWith("all")); 34 | Assert.False(subscriber.IsCompatibleWith("amm")); 35 | Assert.Equal(0, _context.Channel.AbandonCount.Get()); 36 | var abandonSafely = _context.Channel.ExpectAbandon(1); 37 | subscriber.Close(); 38 | Assert.Equal(1, abandonSafely.ReadFrom("count")); 39 | Assert.Equal(1, _context.Channel.AbandonCount.Get()); 40 | } 41 | 42 | public SseSubscriberTest() 43 | { 44 | Configuration.Define(); 45 | _context = new MockRequestResponseContext(new MockResponseSenderChannel()); 46 | _client = new SseClient(_context); 47 | } 48 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Resource/TestDispatcher.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Actors; 9 | using Vlingo.Xoom.Http.Resource; 10 | using IDispatcher = Vlingo.Xoom.Http.Resource.IDispatcher; 11 | 12 | namespace Vlingo.Xoom.Http.Tests.Resource; 13 | 14 | public class TestDispatcher : IDispatcher 15 | { 16 | private readonly Resources _resources; 17 | private readonly ILogger _logger; 18 | 19 | public TestDispatcher(Resources resources, ILogger logger) 20 | { 21 | _resources = resources; 22 | _logger = logger; 23 | } 24 | 25 | 26 | public void Conclude() 27 | { 28 | } 29 | 30 | public void Stop() 31 | { 32 | } 33 | 34 | public bool IsStopped => false; 35 | 36 | public void DispatchFor(Context context) => _resources.DispatchMatching(context, _logger); 37 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Resource/TestMapper.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Common.Serialization; 10 | using Vlingo.Xoom.Http.Resource; 11 | 12 | namespace Vlingo.Xoom.Http.Tests.Resource; 13 | 14 | public class TestMapper : IMapper 15 | { 16 | public object From(string data, Type type) => JsonSerialization.Deserialized(data, type); 17 | 18 | public string From(T data) => JsonSerialization.Serialized(data); 19 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Resource/TestResponseChannelConsumer.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System.Collections.Concurrent; 9 | using Vlingo.Xoom.Actors; 10 | using Vlingo.Xoom.Actors.TestKit; 11 | using Vlingo.Xoom.Common; 12 | using Vlingo.Xoom.Wire.Channel; 13 | using Vlingo.Xoom.Wire.Message; 14 | 15 | namespace Vlingo.Xoom.Http.Tests.Resource; 16 | 17 | public class TestResponseChannelConsumer : Actor, IResponseChannelConsumer 18 | { 19 | private ResponseParser _parser; 20 | private readonly Progress _progress; 21 | 22 | public TestResponseChannelConsumer(Progress progress) => _progress = progress; 23 | 24 | public void Consume(IConsumerByteBuffer buffer) 25 | { 26 | if (_parser == null) 27 | { 28 | _parser = ResponseParser.ParserFor(buffer); 29 | } 30 | else 31 | { 32 | _parser.ParseNext(buffer); 33 | } 34 | 35 | buffer.Release(); 36 | 37 | while (_parser.HasFullResponse()) 38 | { 39 | var response = _parser.FullResponse(); 40 | _progress.ConsumeCalls.WriteUsing("consume", response); 41 | } 42 | } 43 | } 44 | 45 | public class Progress 46 | { 47 | internal AccessSafely ConsumeCalls = AccessSafely.AfterCompleting(0); 48 | 49 | public ConcurrentQueue Responses { get; } = new ConcurrentQueue(); 50 | 51 | public AtomicInteger ConsumeCount { get; } = new AtomicInteger(0); 52 | 53 | /// 54 | /// Answer with an AccessSafely which writes responses to "consume" and reads the write count from "completed". 55 | /// 56 | /// Number of times consume(response) must be called before readFrom(...) will return. 57 | /// AccessSafely 58 | /// Clients can replace the default lambdas with their own via readingWith/writingWith. 59 | public AccessSafely ExpectConsumeTimes(int n) 60 | { 61 | ConsumeCalls = AccessSafely.AfterCompleting(n); 62 | ConsumeCalls.WritingWith("consume", response => 63 | { 64 | Responses.Enqueue(response); 65 | ConsumeCount.IncrementAndGet(); 66 | }) 67 | .ReadingWith("completed", () => ConsumeCalls.TotalWrites); 68 | 69 | return ConsumeCalls; 70 | } 71 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/AllSseFeedActor.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System.Collections.Generic; 9 | using Vlingo.Xoom.Actors; 10 | using Vlingo.Xoom.Http.Resource.Sse; 11 | 12 | namespace Vlingo.Xoom.Http.Tests.Sample.User; 13 | 14 | public class AllSseFeedActor : Actor, ISseFeed 15 | { 16 | private int _retryThreshold = 3000; 17 | 18 | private readonly SseEvent.Builder _builder; 19 | private readonly int _defaultId; 20 | private readonly int _feedPayload; 21 | private string _streamName; 22 | 23 | public AllSseFeedActor(string streamName, int feedPayload, string feedDefaultId) 24 | { 25 | _streamName = streamName; 26 | _feedPayload = feedPayload; 27 | var currentStreamId = 1; 28 | _defaultId = DefaultId(feedDefaultId, currentStreamId); 29 | _builder = SseEvent.Builder.Instance; 30 | Logger.Info($"SseFeed started for stream: {streamName}"); 31 | } 32 | 33 | public void To(ICollection subscribers) 34 | { 35 | foreach (var subscriber in subscribers) 36 | { 37 | var fresh = !subscriber.HasCurrentEventId; 38 | var retry = fresh ? _retryThreshold : SseEvent.NoRetry; 39 | var startId = fresh ? _defaultId : int.Parse(subscriber.CurrentEventId!); 40 | var endId = startId + _feedPayload - 1; 41 | var (sseEvents, id) = ReadSubStream(startId, endId, retry); 42 | subscriber.Client?.Send(sseEvents); 43 | subscriber.CurrentEventId = id.ToString(); 44 | } 45 | } 46 | 47 | private int DefaultId(string feedDefaultId, int defaultDefaultId) 48 | { 49 | var maybeDefaultId = int.Parse(feedDefaultId); 50 | return maybeDefaultId <= 0 ? defaultDefaultId : maybeDefaultId; 51 | } 52 | 53 | private (IEnumerable, int) ReadSubStream(int startId, int endId, int retry) 54 | { 55 | var substream = new List(); 56 | int type = 0; 57 | int id = startId; 58 | for ( ; id <= endId; ++id) 59 | { 60 | substream.Add(_builder.Clear().WithEvent("mimeType-" + ('A' + type)).WithId(id).WithData("data-" + id).WithRetry(retry).ToEvent()); 61 | type = type > 26 ? 0 : type + 1; 62 | } 63 | 64 | return (substream, id + 1); 65 | } 66 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/ContactData.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Http.Tests.Sample.User.Model; 9 | 10 | namespace Vlingo.Xoom.Http.Tests.Sample.User; 11 | 12 | public class ContactData 13 | { 14 | public string EmailAddress { get; } 15 | public string TelephoneNumber { get; } 16 | 17 | public static ContactData From(string emailAddress, string telephoneNumber) => new ContactData(emailAddress, telephoneNumber); 18 | 19 | public static ContactData From(Contact contact) => new ContactData(contact.EmailAddress, contact.TelephoneNumber); 20 | 21 | public ContactData(string emailAddress, string telephoneNumber) 22 | { 23 | EmailAddress = emailAddress; 24 | TelephoneNumber = telephoneNumber; 25 | } 26 | 27 | public override string ToString() => $"ContactData[emailAddress={EmailAddress}, telephoneNumber={TelephoneNumber}]"; 28 | 29 | public override bool Equals(object obj) 30 | { 31 | if (this == obj) return true; 32 | if (!(obj is ContactData)) return false; 33 | var contactData = (ContactData) obj; 34 | return EmailAddress.Equals(contactData.EmailAddress) && TelephoneNumber.Equals(contactData.TelephoneNumber); 35 | } 36 | 37 | public override int GetHashCode() => 13 * EmailAddress.GetHashCode() + TelephoneNumber.GetHashCode(); 38 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/Model/Contact.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | namespace Vlingo.Xoom.Http.Tests.Sample.User.Model; 9 | 10 | public class Contact 11 | { 12 | public string EmailAddress { get; } 13 | public string TelephoneNumber { get; } 14 | 15 | public static Contact From(string emailAddress, string telephoneNumber) => new Contact(emailAddress, telephoneNumber); 16 | 17 | public Contact(string emailAddress, string telephoneNumber) 18 | { 19 | EmailAddress = emailAddress; 20 | TelephoneNumber = telephoneNumber; 21 | } 22 | 23 | public override string ToString() => $"Contact[emailAddress={EmailAddress}, telephoneNumber={TelephoneNumber}]"; 24 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/Model/IProfile.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Common; 9 | 10 | namespace Vlingo.Xoom.Http.Tests.Sample.User.Model; 11 | 12 | public interface IProfile 13 | { 14 | ICompletes WithTwitterAccount(string twitterAccount); 15 | ICompletes WithLinkedInAccount(string linkedInAccount); 16 | ICompletes WithWebSite(string website); 17 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/Model/IUser.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Common; 9 | 10 | namespace Vlingo.Xoom.Http.Tests.Sample.User.Model; 11 | 12 | public interface IUser 13 | { 14 | ICompletes WithContact(Contact contact); 15 | ICompletes WithName(Name name); 16 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/Model/Name.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | namespace Vlingo.Xoom.Http.Tests.Sample.User.Model; 9 | 10 | public class Name 11 | { 12 | public string Given { get; } 13 | public string Family { get; } 14 | 15 | public static Name From(string given, string family) => new Name(given, family); 16 | 17 | public Name(string given, string family) 18 | { 19 | Given = given; 20 | Family = family; 21 | } 22 | 23 | public override string ToString() => $"Name[given={Given}, family={Family}]"; 24 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/Model/ProfileActor.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Actors; 9 | using Vlingo.Xoom.Common; 10 | 11 | namespace Vlingo.Xoom.Http.Tests.Sample.User.Model; 12 | 13 | public class ProfileActor : Actor, IProfile 14 | { 15 | private ProfileState _state; 16 | 17 | public ProfileActor(ProfileState profileState) => _state = profileState; 18 | 19 | public ICompletes WithTwitterAccount(string twitterAccount) 20 | { 21 | _state = _state.WithTwitterAccount(twitterAccount); 22 | return Completes().With(_state); 23 | } 24 | 25 | public ICompletes WithLinkedInAccount(string linkedInAccount) 26 | { 27 | _state = _state.WithLinkedInAccount(linkedInAccount); 28 | return Completes().With(_state); 29 | } 30 | 31 | public ICompletes WithWebSite(string website) 32 | { 33 | _state = _state.WithWebSite(website); 34 | return Completes().With(_state); 35 | } 36 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/Model/ProfileRepository.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System.Collections.Generic; 9 | 10 | namespace Vlingo.Xoom.Http.Tests.Sample.User.Model; 11 | 12 | public class ProfileRepository 13 | { 14 | private static ProfileRepository _instance; 15 | 16 | private readonly Dictionary _profiles; 17 | 18 | private static volatile object _lockSync = new object(); 19 | 20 | public static ProfileRepository Instance() 21 | { 22 | lock (_lockSync) 23 | { 24 | if (_instance == null) 25 | { 26 | _instance = new ProfileRepository(); 27 | } 28 | 29 | return _instance; 30 | } 31 | } 32 | 33 | public ProfileState ProfileOf(string userId) 34 | { 35 | var profileState = _profiles[userId]; 36 | 37 | return profileState == null ? ProfileStateFactory.NonExisting() : profileState; 38 | } 39 | 40 | public void Save(ProfileState profileState) => _profiles.Add(profileState.Id, profileState); 41 | 42 | private ProfileRepository() => _profiles = new Dictionary(); 43 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/Model/ProfileState.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | namespace Vlingo.Xoom.Http.Tests.Sample.User.Model; 9 | 10 | public class ProfileState 11 | { 12 | public string Id { get; } 13 | public string LinkedInAccount { get; } 14 | public string TwitterAccount { get; } 15 | public string Website { get; } 16 | 17 | public bool DoesNotExist => Id == null; 18 | 19 | public ProfileState WithTwitterAccount(string twitterAccount) => new ProfileState(Id, twitterAccount, LinkedInAccount, Website); 20 | 21 | public ProfileState WithLinkedInAccount(string linkedInAccount) => new ProfileState(Id, TwitterAccount, linkedInAccount, Website); 22 | 23 | public ProfileState WithWebSite(string website) => new ProfileState(Id, TwitterAccount, LinkedInAccount, website); 24 | 25 | public ProfileState(string id, string twitterAccount, string linkedInAccount, string website) 26 | { 27 | Id = id; 28 | TwitterAccount = twitterAccount; 29 | LinkedInAccount = linkedInAccount; 30 | Website = website; 31 | } 32 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/Model/ProfileStateFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | namespace Vlingo.Xoom.Http.Tests.Sample.User.Model; 9 | 10 | public static class ProfileStateFactory 11 | { 12 | public static ProfileState From(string id, string twitterAccount, string linkedInAccount, string website) => 13 | new ProfileState(id, twitterAccount, linkedInAccount, website); 14 | 15 | public static ProfileState NonExisting() => new ProfileState(null, null, null, null); 16 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/Model/UserActor.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Actors; 9 | using Vlingo.Xoom.Common; 10 | 11 | namespace Vlingo.Xoom.Http.Tests.Sample.User.Model; 12 | 13 | public class UserActor : Actor, IUser 14 | { 15 | private UserState _userState; 16 | 17 | public UserActor(UserState userState) => _userState = userState; 18 | 19 | public ICompletes WithContact(Contact contact) 20 | { 21 | _userState = _userState.WithContact(contact); 22 | return Completes().With(_userState); 23 | } 24 | 25 | public ICompletes WithName(Name name) 26 | { 27 | _userState = _userState.WithName(name); 28 | return Completes().With(_userState); 29 | } 30 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/Model/UserRepository.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System.Collections.Generic; 9 | 10 | namespace Vlingo.Xoom.Http.Tests.Sample.User.Model; 11 | 12 | public class UserRepository 13 | { 14 | private static UserRepository _instance; 15 | 16 | private readonly Dictionary _users; 17 | 18 | private static volatile object _lockSync = new object(); 19 | 20 | private UserRepository() 21 | { 22 | _users = new Dictionary(); 23 | } 24 | 25 | public static UserRepository Instance() 26 | { 27 | lock (_lockSync) 28 | { 29 | if (_instance == null) 30 | { 31 | _instance = new UserRepository(); 32 | } 33 | 34 | return _instance; 35 | } 36 | } 37 | 38 | public static void Reset() => _instance = null; 39 | 40 | public UserState UserOf(string userId) 41 | { 42 | var userState = _users[userId]; 43 | return userState == null ? UserStateFactory.NonExisting() : userState; 44 | } 45 | 46 | public IEnumerable Users => _users.Values; 47 | 48 | public void Save(UserState userState) => _users[userState.Id] = userState; 49 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/Model/UserState.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Common; 9 | 10 | namespace Vlingo.Xoom.Http.Tests.Sample.User.Model; 11 | 12 | public class UserState 13 | { 14 | public static AtomicInteger NextId { get; } = new AtomicInteger(0); 15 | 16 | public string Id { get; } 17 | public Name Name { get; } 18 | public Contact Contact { get; } 19 | 20 | public bool DoesNotExist() 21 | { 22 | return Id == null; 23 | } 24 | 25 | public UserState WithContact(Contact contact) => new UserState(Id, Name, contact); 26 | 27 | public UserState WithName(Name name) => new UserState(Id, name, Contact); 28 | 29 | public override string ToString() => $"User.State[id={Id}, name={Name}, contact={Contact}]"; 30 | 31 | public UserState(string id, Name name, Contact contact) 32 | { 33 | Id = id; 34 | Name = name; 35 | Contact = contact; 36 | } 37 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/Model/UserStateFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | namespace Vlingo.Xoom.Http.Tests.Sample.User.Model; 9 | 10 | public static class UserStateFactory 11 | { 12 | public static UserState NonExisting() => new UserState(null, null, null); 13 | 14 | public static UserState From(Name name, Contact contact) => new UserState(NextId(), name, contact); 15 | 16 | public static UserState From(string id, Name name, Contact contact) => new UserState(id, name, contact); 17 | 18 | public static void ResetId() => UserState.NextId.Set(0); 19 | 20 | public static string NextId() 21 | { 22 | var id = UserState.NextId.IncrementAndGet(); 23 | return $"{id:D3}"; 24 | } 25 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/Model/User__Proxy.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | using Vlingo.Xoom.Common; 11 | 12 | namespace Vlingo.Xoom.Http.Tests.Sample.User.Model; 13 | 14 | public class User__Proxy : IUser 15 | { 16 | private const string WithContactRepresentation1 = "WithContact(Vlingo.Xoom.Http.Tests.Sample.User.Model.Contact)"; 17 | private const string WithNameRepresentation2 = "WithName(Vlingo.Xoom.Http.Tests.Sample.User.Model.Name)"; 18 | 19 | private readonly Actor _actor; 20 | private readonly IMailbox _mailbox; 21 | 22 | public User__Proxy(Actor actor, IMailbox mailbox) 23 | { 24 | _actor = actor; 25 | _mailbox = mailbox; 26 | } 27 | 28 | public ICompletes WithContact( 29 | Contact contact) 30 | { 31 | if (!_actor.IsStopped) 32 | { 33 | Action cons128873 = __ => __.WithContact(contact); 34 | var completes = new BasicCompletes(_actor.Scheduler); 35 | if (_mailbox.IsPreallocated) 36 | { 37 | _mailbox.Send(_actor, cons128873, completes, WithContactRepresentation1); 38 | } 39 | else 40 | { 41 | _mailbox.Send(new LocalMessage(_actor, 42 | cons128873, completes, WithContactRepresentation1)); 43 | } 44 | 45 | return completes; 46 | } 47 | else 48 | { 49 | _actor.DeadLetters?.FailedDelivery(new DeadLetter(_actor, WithContactRepresentation1)); 50 | } 51 | 52 | return null; 53 | } 54 | 55 | public ICompletes WithName( 56 | Name name) 57 | { 58 | if (!_actor.IsStopped) 59 | { 60 | Action cons128873 = __ => __.WithName(name); 61 | var completes = new BasicCompletes(_actor.Scheduler); 62 | if (_mailbox.IsPreallocated) 63 | { 64 | _mailbox.Send(_actor, cons128873, completes, WithNameRepresentation2); 65 | } 66 | else 67 | { 68 | _mailbox.Send(new LocalMessage(_actor, 69 | cons128873, completes, WithNameRepresentation2)); 70 | } 71 | 72 | return completes; 73 | } 74 | else 75 | { 76 | _actor.DeadLetters?.FailedDelivery(new DeadLetter(_actor, WithNameRepresentation2)); 77 | } 78 | 79 | return null; 80 | } 81 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/NameData.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Http.Tests.Sample.User.Model; 9 | 10 | namespace Vlingo.Xoom.Http.Tests.Sample.User; 11 | 12 | public class NameData 13 | { 14 | public string Given { get; } 15 | public string Family { get; } 16 | 17 | public static NameData From(string given, string family) => new NameData(given, family); 18 | 19 | public static NameData From(Name name) => new NameData(name.Given, name.Family); 20 | 21 | public NameData(string given, string family) 22 | { 23 | Given = given; 24 | Family = family; 25 | } 26 | 27 | public override string ToString() => $"NameData[given={Given}, family={Family}]"; 28 | 29 | public override bool Equals(object obj) 30 | { 31 | if (this == obj) return true; 32 | if (!(obj is NameData)) return false; 33 | var nameData = (NameData) obj; 34 | return Given.Equals(nameData.Given) && Family.Equals(nameData.Family); 35 | } 36 | 37 | public override int GetHashCode() => 13 * Given.GetHashCode() + Family.GetHashCode(); 38 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/ProfileData.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Http.Tests.Sample.User.Model; 9 | 10 | namespace Vlingo.Xoom.Http.Tests.Sample.User; 11 | 12 | public class ProfileData 13 | { 14 | public string LinkedInAccount { get; } 15 | public string TwitterAccount { get; } 16 | public string Website { get; } 17 | 18 | public static ProfileData From(ProfileState profile) => new ProfileData(profile.TwitterAccount, profile.LinkedInAccount, profile.Website); 19 | 20 | public ProfileData(string twitterAccount, string linkedInAccount, string website) 21 | { 22 | TwitterAccount = twitterAccount; 23 | LinkedInAccount = linkedInAccount; 24 | Website = website; 25 | } 26 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/ProfileDataMapper.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Http.Resource; 10 | 11 | namespace Vlingo.Xoom.Http.Tests.Sample.User; 12 | 13 | public class ProfileDataMapper : IMapper 14 | { 15 | public object From(string data, Type type) => DefaultJsonMapper.Instance.From(data, type); 16 | 17 | public string From(T data) => DefaultJsonMapper.Instance.From(data); 18 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/ProfileResource.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Actors; 9 | using Vlingo.Xoom.Common.Serialization; 10 | using Vlingo.Xoom.Http.Resource; 11 | using Vlingo.Xoom.Http.Tests.Sample.User.Model; 12 | 13 | namespace Vlingo.Xoom.Http.Tests.Sample.User; 14 | 15 | public sealed class ProfileResource : ResourceHandler 16 | { 17 | private readonly ProfileRepository _repository = ProfileRepository.Instance(); 18 | 19 | public ProfileResource(World world) => Stage = world.StageNamed("service"); 20 | 21 | public void Define(string userId, ProfileData profileData) 22 | { 23 | Stage?.ActorOf(Stage.World.AddressFactory.FindableBy(int.Parse(userId))) 24 | .AndThenConsume(profile => { 25 | var profileState = _repository.ProfileOf(userId); 26 | Completes?.With(Response.Of( 27 | ResponseStatus.Ok, 28 | Headers.Of(ResponseHeader.Of(ResponseHeader.Location, ProfileLocation(userId))), 29 | JsonSerialization.Serialized(ProfileData.From(profileState)))); 30 | }) 31 | .OtherwiseConsume(noProfile => { 32 | var profileState = 33 | ProfileStateFactory.From( 34 | userId, 35 | profileData.TwitterAccount, 36 | profileData.LinkedInAccount, 37 | profileData.Website); 38 | 39 | Stage?.ActorFor(() => new ProfileActor(profileState)); 40 | 41 | _repository.Save(profileState); 42 | Completes?.With(Response.Of(ResponseStatus.Created, JsonSerialization.Serialized(ProfileData.From(profileState)))); 43 | }); 44 | } 45 | 46 | public void Query(string userId) 47 | { 48 | var profileState = _repository.ProfileOf(userId); 49 | if (profileState.DoesNotExist) 50 | { 51 | Completes?.With(Response.Of(ResponseStatus.NotFound, ProfileLocation(userId))); 52 | } 53 | else 54 | { 55 | Completes?.With(Response.Of(ResponseStatus.Ok, JsonSerialization.Serialized(ProfileData.From(profileState)))); 56 | } 57 | } 58 | 59 | private string ProfileLocation(string userId) => $"/users/{userId}/profile"; 60 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/ProfileResourceFluent.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Actors; 9 | using Vlingo.Xoom.Common; 10 | using Vlingo.Xoom.Common.Serialization; 11 | using Vlingo.Xoom.Http.Resource; 12 | using Vlingo.Xoom.Http.Tests.Sample.User.Model; 13 | 14 | namespace Vlingo.Xoom.Http.Tests.Sample.User; 15 | 16 | public class ProfileResourceFluent : ResourceHandler 17 | { 18 | private readonly ProfileRepository _repository = ProfileRepository.Instance(); 19 | private readonly Stage _stage; 20 | 21 | public ProfileResourceFluent(World world) => _stage = world.StageNamed("service"); 22 | 23 | public ICompletes Define(string userId, ProfileData profileData) 24 | { 25 | return _stage.ActorOf(_stage.World.AddressFactory.FindableBy(int.Parse(userId))) 26 | .AndThenTo(profile => { 27 | var profileState = _repository.ProfileOf(userId); 28 | return Vlingo.Xoom.Common.Completes.WithSuccess(Response.Of(ResponseStatus.Ok, Headers.Of(ResponseHeader.Of(ResponseHeader.Location, ProfileLocation(userId))), 29 | JsonSerialization.Serialized(ProfileData.From(profileState)))); 30 | }) 31 | .Otherwise(noProfile => { 32 | var profileState = 33 | ProfileStateFactory.From( 34 | userId, 35 | profileData.TwitterAccount, 36 | profileData.LinkedInAccount, 37 | profileData.Website); 38 | 39 | Stage?.ActorFor(() => new ProfileActor(profileState)); 40 | 41 | _repository.Save(profileState); 42 | return Response.Of(ResponseStatus.Created, JsonSerialization.Serialized(ProfileData.From(profileState))); 43 | }); 44 | } 45 | 46 | public ICompletes Query(string userId) 47 | { 48 | var profileState = _repository.ProfileOf(userId); 49 | if (profileState.DoesNotExist) 50 | { 51 | return Vlingo.Xoom.Common.Completes.WithSuccess(Response.Of(ResponseStatus.NotFound, ProfileLocation(userId))); 52 | } 53 | 54 | return Vlingo.Xoom.Common.Completes.WithSuccess(Response.Of(ResponseStatus.Ok, JsonSerialization.Serialized(ProfileData.From(profileState)))); 55 | } 56 | 57 | public override Http.Resource.Resource Routes() 58 | { 59 | return ResourceBuilder.Resource("profile resource fluent api", 60 | ResourceBuilder.Put("/users/{userId}/profile") 61 | .Param() 62 | .Body() 63 | .Handle(Define), 64 | ResourceBuilder.Get("/users/{userId}/profile") 65 | .Param() 66 | .Handle(Query)); 67 | } 68 | 69 | private string ProfileLocation(string userId) => "/users/" + userId + "/profile"; 70 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/Serialization/UserDataConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Newtonsoft.Json; 10 | using Newtonsoft.Json.Linq; 11 | 12 | namespace Vlingo.Xoom.Http.Tests.Sample.User.Serialization; 13 | 14 | public class UserDataConverter : JsonConverter 15 | { 16 | public override bool CanWrite { get; } = false; 17 | 18 | public override void WriteJson(JsonWriter writer, UserData value, JsonSerializer serializer) 19 | { 20 | } 21 | 22 | public override UserData ReadJson(JsonReader reader, Type objectType, UserData existingValue, bool hasExistingValue, JsonSerializer serializer) 23 | { 24 | JObject jo = JObject.Load(reader); 25 | 26 | var id = jo["Id"].ToObject(); 27 | var nameData = jo["NameData"].ToObject(); 28 | var contactData = jo["ContactData"].ToObject(); 29 | 30 | return UserData.From(id, nameData, contactData); 31 | } 32 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/UserData.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | using Vlingo.Xoom.Http.Tests.Sample.User.Model; 11 | 12 | namespace Vlingo.Xoom.Http.Tests.Sample.User; 13 | 14 | public class UserData 15 | { 16 | public string Id { get; } 17 | public NameData NameData { get; } 18 | public ContactData ContactData { get; } 19 | 20 | public static UserData From(NameData nameData, ContactData contactData) => new UserData(nameData, contactData); 21 | 22 | public static UserData From(string id, NameData nameData, ContactData contactData) => new UserData(id, nameData, contactData); 23 | 24 | public static UserData From(UserState userState) => 25 | new UserData(userState.Id, NameData.From(userState.Name), ContactData.From(userState.Contact)); 26 | 27 | public static UserData UserAt(string location, List userData) 28 | { 29 | var index = location.LastIndexOf("/", StringComparison.Ordinal); 30 | var id = location.Substring(index + 1); 31 | return UserOf(id, userData); 32 | } 33 | 34 | public static UserData UserOf(string id, List userData) 35 | { 36 | foreach (var data in userData) 37 | { 38 | if (data.Id.Equals(id)) 39 | { 40 | return data; 41 | } 42 | } 43 | 44 | return null; 45 | } 46 | 47 | private UserData(NameData nameData, ContactData contactData) 48 | { 49 | Id = Guid.NewGuid().ToString(); 50 | NameData = nameData; 51 | ContactData = contactData; 52 | } 53 | 54 | public UserData(string id, NameData nameData, ContactData contactData) 55 | { 56 | Id = id; 57 | NameData = nameData; 58 | ContactData = contactData; 59 | } 60 | 61 | public override string ToString() => $"UserData[id={Id}, nameData={NameData}, contactData={ContactData}]"; 62 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Sample/User/UserDataMapper.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Newtonsoft.Json; 10 | using Vlingo.Xoom.Common.Serialization; 11 | using Vlingo.Xoom.Http.Resource; 12 | using Vlingo.Xoom.Http.Tests.Sample.User.Serialization; 13 | 14 | namespace Vlingo.Xoom.Http.Tests.Sample.User; 15 | 16 | public class UserDataMapper : IMapper 17 | { 18 | private readonly JsonSerializerSettings _settings; 19 | 20 | public UserDataMapper() 21 | { 22 | _settings = new JsonSerializerSettings(); 23 | _settings.Converters.Add(new UserDataConverter()); 24 | } 25 | 26 | public object From(string data, Type type) => JsonSerialization.Deserialized(data, type, _settings); 27 | 28 | public string From(T data) => JsonSerialization.Serialized(data, _settings); 29 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/VersionTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Xunit; 10 | 11 | namespace Vlingo.Xoom.Http.Tests; 12 | 13 | public class VersionTest 14 | { 15 | [Fact] 16 | public void TestVersion1Dot1() 17 | { 18 | var version = Version.From("HTTP/1.1"); 19 | 20 | Assert.True(version.IsHttp1_1()); 21 | Assert.False(version.IsHttp2_0()); 22 | } 23 | 24 | [Fact] 25 | public void TestVersion2Dot0() 26 | { 27 | var version = Version.From("HTTP/2.0"); 28 | 29 | Assert.True(version.IsHttp2_0()); 30 | Assert.False(version.IsHttp1_1()); 31 | } 32 | 33 | [Fact] 34 | public void TestUnsupportedVersion0Dot1() 35 | { 36 | Assert.Throws(() => Version.From("HTTP/0.1")); 37 | } 38 | 39 | [Fact] 40 | public void TestUnsupportedVersion2Dot1() 41 | { 42 | Assert.Throws(() => Version.From("HTTP/2.1")); 43 | } 44 | 45 | [Fact] 46 | public void TestUnsupportedGarbage() 47 | { 48 | Assert.Throws(() => Version.From("Blah/Blah")); 49 | } 50 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/Vlingo.Xoom.Http.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | latest 6 | 7 | false 8 | Vlingo.Xoom.Http.Tests.Resource.ServerBootstrap 9 | Debug;Release;Debug With Project References 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Always 37 | 38 | 39 | Always 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.Tests/vlingo-actors.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": { 3 | "name": { 4 | "queueMailbox": true, 5 | "arrayQueueMailbox": true, 6 | "consoleLogger": true, 7 | "pooledCompletes": true 8 | }, 9 | "pooledCompletes": { 10 | "classname" : "Vlingo.Xoom.Actors.Plugin.Completes.PooledCompletesPlugin", 11 | "pool" : 10, 12 | "mailbox" : "queueMailbox" 13 | }, 14 | "queueMailbox": { 15 | "classname": "Vlingo.Xoom.Actors.Plugin.Mailbox.ConcurrentQueue.ConcurrentQueueMailboxPlugin", 16 | "defaultMailbox": true, 17 | "numberOfDispatchersFactor": 1.5, 18 | "dispatcherThrottlingCount": 1 19 | }, 20 | "arrayQueueMailbox": { 21 | "classname": "Vlingo.Xoom.Actors.Plugin.Mailbox.AgronaMPSCArrayQueue.ManyToOneConcurrentArrayQueuePlugin", 22 | "defaultMailbox": false, 23 | "size": 65535, 24 | "fixedBackoff": 2, 25 | "dispatcherThrottlingCount": 1, 26 | "sendRetires": 10 27 | }, 28 | "consoleLogger": { 29 | "classname": "Vlingo.Xoom.Actors.Plugin.Logging.Console.ConsoleLoggerPlugin", 30 | "name": "vlingo-net/http", 31 | "defaultLogger": true 32 | } 33 | }, 34 | "proxy": { 35 | "generated": { 36 | "classes": { 37 | "main": "target/classes/", 38 | "test": "target/test-classes/" 39 | }, 40 | "sources": { 41 | "main": "target/generated-sources/", 42 | "test": "target/generated-test-sources/" 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.136 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vlingo.Xoom.Http", "Vlingo.Xoom.Http\Vlingo.Xoom.Http.csproj", "{B46A0736-489B-4BF7-9758-C0BBE70EA929}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vlingo.Xoom.Http.Tests", "Vlingo.Xoom.Http.Tests\Vlingo.Xoom.Http.Tests.csproj", "{444E1E58-5F05-4BF4-B1FA-C5BB5164C14C}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {B46A0736-489B-4BF7-9758-C0BBE70EA929}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {B46A0736-489B-4BF7-9758-C0BBE70EA929}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {B46A0736-489B-4BF7-9758-C0BBE70EA929}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {B46A0736-489B-4BF7-9758-C0BBE70EA929}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {444E1E58-5F05-4BF4-B1FA-C5BB5164C14C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {444E1E58-5F05-4BF4-B1FA-C5BB5164C14C}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {444E1E58-5F05-4BF4-B1FA-C5BB5164C14C}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {444E1E58-5F05-4BF4-B1FA-C5BB5164C14C}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {4E2584D9-B33D-4CB5-84B6-9D7E621FFFC1} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http.sln.licenseheader: -------------------------------------------------------------------------------- 1 | extensions: .cs 2 | // Copyright (c) 2012-2021 VLINGO LABS. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the 5 | // Mozilla Public License, v. 2.0. If a copy of the MPL 6 | // was not distributed with this file, You can obtain 7 | // one at https://mozilla.org/MPL/2.0/. 8 | -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/BinaryBody.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System.Collections; 9 | 10 | namespace Vlingo.Xoom.Http; 11 | 12 | public sealed class BinaryBody : Body 13 | { 14 | private readonly byte[] _binaryContent; 15 | 16 | public BinaryBody() => _binaryContent = new byte[0]; 17 | 18 | public BinaryBody(byte[] body) => _binaryContent = body; 19 | 20 | public override byte[] BinaryContent => _binaryContent; 21 | 22 | public override string Content => BytesToBase64(_binaryContent); 23 | 24 | public override bool HasContent => _binaryContent.Length != 0; 25 | 26 | public override string ToString() => Content; 27 | 28 | public override bool Equals(object? obj) 29 | { 30 | if (this == obj) 31 | { 32 | return true; 33 | } 34 | 35 | if (obj == null || GetType() != obj.GetType()) 36 | { 37 | return false; 38 | } 39 | 40 | var that = (BinaryBody) obj; 41 | return ((IStructuralEquatable)_binaryContent).Equals(that._binaryContent, StructuralComparisons.StructuralEqualityComparer); 42 | } 43 | 44 | private bool Equals(BinaryBody other) => _binaryContent.Equals(other._binaryContent); 45 | 46 | public override int GetHashCode() => _binaryContent.GetHashCode(); 47 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/CORSResponseFilter.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | 12 | namespace Vlingo.Xoom.Http; 13 | 14 | public class CORSResponseFilter : ResponseFilter 15 | { 16 | private const string AnyOrigin = "*"; 17 | private readonly Dictionary> _originHeaders; 18 | 19 | public CORSResponseFilter() => _originHeaders = new Dictionary>(); 20 | 21 | /// 22 | /// Register the with the such that 23 | /// when a contains a RequestHeader of type Origin, the 24 | /// will contain the . 25 | /// 26 | /// The string URI of a valid CORS origin 27 | /// The list of to set in the Responses for Origin URI 28 | public void OriginHeadersFor(string originUri, IEnumerable responseHeaders) 29 | { 30 | if (string.IsNullOrEmpty(originUri)) 31 | { 32 | throw new ArgumentNullException(nameof(originUri),"The origin URI must not be null or empty."); 33 | } 34 | 35 | var headers = responseHeaders.ToList(); 36 | if (responseHeaders == null || !headers.Any()) 37 | { 38 | throw new ArgumentNullException(nameof(responseHeaders), "The response headers must not be null or empty."); 39 | } 40 | 41 | _originHeaders.Add(originUri, headers.ToList()); 42 | } 43 | 44 | public override (Response, bool) Filter(Response response) => new ValueTuple(response, true); 45 | 46 | public override (Response, bool) Filter(Request request, Response response) 47 | { 48 | var origin = request.HeaderValueOr(RequestHeader.Origin, null!); 49 | 50 | if (origin != null) 51 | { 52 | foreach (string uri in _originHeaders.Keys) 53 | { 54 | if (uri.Equals(AnyOrigin) || uri.Equals(origin)) 55 | { 56 | response.IncludeAll(_originHeaders[uri]); 57 | break; 58 | } 59 | } 60 | } 61 | 62 | return new ValueTuple(response, true); 63 | } 64 | 65 | public override void Stop() 66 | { 67 | } 68 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/ChunkedBody.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.Text; 10 | 11 | namespace Vlingo.Xoom.Http; 12 | 13 | /// 14 | /// An HTTP response body that provides a multi-chunk format. You may create 15 | /// one chunk in each instance, or multiple chunks. 16 | /// 17 | public class ChunkedBody : Body 18 | { 19 | private readonly StringBuilder _content; 20 | 21 | /// 22 | /// Gets self after appending the as the chunk. 23 | /// 24 | /// the with content to append as the chunk 25 | /// 26 | public ChunkedBody AppendChunk(Body body) => AppendChunk(body.Content); 27 | 28 | /// 29 | /// Answer a new as a with my content. 30 | /// 31 | /// 32 | public Body AsPlainBody() => new PlainBody(Content); 33 | 34 | /// 35 | /// Gets self after appending the . 36 | /// 37 | /// The string content to append as the chunk 38 | /// 39 | public ChunkedBody AppendChunk(string chunk) 40 | { 41 | _content 42 | .Append(chunk.Length.ToString("x8")) 43 | .Append("\r\n") 44 | .Append(chunk) 45 | .Append("\r\n"); 46 | 47 | return this; 48 | } 49 | 50 | /// 51 | /// Gets self after appending the . 52 | /// 53 | /// the byte[] content to append as the chunk 54 | /// 55 | public ChunkedBody AppendChunk(byte[] chunk) => throw new NotImplementedException("Adding chunks in the form of byte[] is not yet supported"); 56 | 57 | /// 58 | /// Gets my content as string. 59 | /// 60 | public override string Content => ToString(); 61 | 62 | public override byte[] BinaryContent => System.Text.Encoding.UTF8.GetBytes(ToString()); 63 | 64 | public override bool IsComplex => true; 65 | 66 | /// 67 | /// Gets self after appending the end chunk, which is a length of 0. 68 | /// 69 | /// 70 | public ChunkedBody End() 71 | { 72 | _content.Append(0).Append("\r\n"); 73 | return this; 74 | } 75 | 76 | public override bool HasContent => _content.Length > 0; 77 | 78 | public override string ToString() => _content.ToString(); 79 | 80 | /// 81 | /// Construct my default state. 82 | /// 83 | internal ChunkedBody() => _content = new StringBuilder(); 84 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/ContentEncoding.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System.Collections; 9 | using System.Linq; 10 | 11 | namespace Vlingo.Xoom.Http; 12 | 13 | /// 14 | /// Contains the ordered list of content encodings that have been applied to a piece of 15 | /// content. To reverse any such compression, these encodings should be applied in reverse order 16 | /// 17 | public class ContentEncoding 18 | { 19 | public ContentEncodingMethod[] EncodingMethods { get; } 20 | 21 | public ContentEncoding(params ContentEncodingMethod[] encodingMethods) => EncodingMethods = encodingMethods; 22 | 23 | public ContentEncoding() => EncodingMethods = new ContentEncodingMethod[0]; 24 | 25 | public static ContentEncoding ParseFromHeader(string? headerValue) 26 | { 27 | if (!string.IsNullOrEmpty(headerValue)) 28 | { 29 | var methods = headerValue?.Split(','); 30 | var parsedMethods = methods?.Select(ContentEncodingMethodHelper.Parse) 31 | .Where(o => o.IsPresent) 32 | .Select(o => o.Get()) 33 | .ToArray(); 34 | 35 | if (parsedMethods != null) return new ContentEncoding(parsedMethods); 36 | } 37 | 38 | return new ContentEncoding(); 39 | } 40 | 41 | public static ContentEncoding None() => new ContentEncoding(); 42 | 43 | public override bool Equals(object? obj) 44 | { 45 | if (this == obj) 46 | { 47 | return true; 48 | } 49 | 50 | if (obj == null || GetType() != obj.GetType()) 51 | { 52 | return false; 53 | } 54 | 55 | var that = (ContentEncoding) obj; 56 | return ((IStructuralEquatable)EncodingMethods).Equals(that.EncodingMethods, StructuralComparisons.StructuralEqualityComparer); 57 | } 58 | 59 | protected bool Equals(ContentEncoding other) => ((IStructuralEquatable)EncodingMethods).Equals(other.EncodingMethods, StructuralComparisons.StructuralEqualityComparer); 60 | 61 | public override int GetHashCode() => EncodingMethods.GetHashCode(); 62 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/ContentEncodingMethod.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Common; 10 | 11 | namespace Vlingo.Xoom.Http; 12 | 13 | public enum ContentEncodingMethod 14 | { 15 | Gzip, 16 | Compress, 17 | Deflate, 18 | Brotli 19 | } 20 | 21 | public static class ContentEncodingMethodHelper 22 | { 23 | public static Optional Parse(string value) 24 | { 25 | if (Enum.TryParse(value.ToLower().Trim() == "br" ? "brotli" : value.Trim(), true, out ContentEncodingMethod parsed)) 26 | { 27 | return Optional.Of(parsed); 28 | } 29 | 30 | return Optional.Empty(); 31 | } 32 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/ContentPacket.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | namespace Vlingo.Xoom.Http; 9 | 10 | public class ContentPacket 11 | { 12 | public string Content { get; } 13 | public int Utf8ExtraLength { get; } 14 | 15 | public ContentPacket(string content, int utf8ExtraLength) 16 | { 17 | Content = content; 18 | Utf8ExtraLength = utf8ExtraLength; 19 | } 20 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Context.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Actors; 9 | using Vlingo.Xoom.Wire.Channel; 10 | 11 | namespace Vlingo.Xoom.Http; 12 | 13 | public class Context 14 | { 15 | private readonly ICompletesEventually _completes; 16 | 17 | public Context(RequestResponseContext? requestResponseContext, Request? request, ICompletesEventually completes) 18 | { 19 | ClientContext = requestResponseContext; 20 | Request = request; 21 | _completes = completes; 22 | } 23 | 24 | public Context(Request? request, ICompletesEventually completes) 25 | : this(null, request, completes) 26 | { 27 | } 28 | 29 | public Context(ICompletesEventually completes) 30 | : this(null, completes) 31 | { 32 | } 33 | 34 | public RequestResponseContext? ClientContext { get; } 35 | 36 | public bool HasClientContext => ClientContext != null; 37 | 38 | public Request? Request { get; } 39 | 40 | public bool HasRequest => Request != null; 41 | 42 | public override string ToString() => $"Context [completes={_completes}, request=\n{Request}]"; 43 | 44 | internal ICompletesEventually Completes => _completes; 45 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Filter.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | namespace Vlingo.Xoom.Http; 9 | 10 | /// 11 | /// Base filter supporting starting and stopping. 12 | /// 13 | public abstract class Filter 14 | { 15 | /// 16 | /// Sent when I am to be stopped. 17 | /// 18 | public abstract void Stop(); 19 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Header.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | 10 | namespace Vlingo.Xoom.Http; 11 | 12 | public class Header 13 | { 14 | public static string ValueWildcardAny { get; } = "*"; 15 | public static string ValueBr { get; } = "br"; 16 | public static string ValueClose { get; } = "close"; 17 | public static string ValueCompress { get; } = "compress"; 18 | public static string ValueDeflate { get; } = "deflate"; 19 | public static string ValueGZip { get; } = "gzip"; 20 | public static string ValueIdentity { get; } = "identity"; 21 | public static string ValueISO_8859_15 { get; } = "iso-8859-15"; 22 | public static string ValueKeepAlive { get; } = "keep-alive"; 23 | public static string ValueUTF_8 { get; } = "utf-8"; 24 | 25 | protected Header(string name, string? value) 26 | { 27 | Name = name; 28 | Value = value; 29 | } 30 | 31 | internal string Name { get; } 32 | internal string? Value { get; } 33 | 34 | public bool MatchesNameOf(Header header) 35 | => string.Equals(Name, header.Name, StringComparison.InvariantCultureIgnoreCase); 36 | 37 | public bool MatchesNameOf(string name) 38 | => string.Equals(Name, name, StringComparison.InvariantCultureIgnoreCase); 39 | 40 | public bool MatchesValueOf(Header header) 41 | => string.Equals(Value, header.Value, StringComparison.InvariantCultureIgnoreCase); 42 | 43 | public bool MatchesValueOf(string value) 44 | => string.Equals(Value, value, StringComparison.InvariantCultureIgnoreCase); 45 | 46 | public override bool Equals(object? obj) 47 | { 48 | var other = obj as Header; 49 | if (other == null || GetType() != other.GetType()) 50 | { 51 | return false; 52 | } 53 | 54 | return MatchesNameOf(other) && MatchesValueOf(other); 55 | } 56 | 57 | public override int GetHashCode() 58 | => 13 * Name.GetHashCode() + (Value != null ? Value.GetHashCode() : 0); 59 | 60 | public override string ToString() 61 | => $"{Name}: {Value}"; 62 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Media/ContentMediaType.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | using Vlingo.Xoom.Http.Resource; 11 | 12 | namespace Vlingo.Xoom.Http.Media; 13 | 14 | public class ContentMediaType : MediaTypeDescriptor 15 | { 16 | // IANA MIME Type List 17 | internal enum MimeTypes 18 | { 19 | Application, 20 | Audio, 21 | Font, 22 | Image, 23 | Model, 24 | Text, 25 | Video, 26 | Multipart, 27 | Message 28 | } 29 | 30 | public ContentMediaType(string mimeType, string mimeSubType) 31 | : base(mimeType, mimeSubType) => 32 | Validate(); 33 | 34 | public ContentMediaType(string mimeType, string mimeSubType, IDictionary parameters) 35 | : base(mimeType, mimeSubType, parameters) => 36 | Validate(); 37 | 38 | private void Validate() 39 | { 40 | if (string.Equals(MimeSubType, "*") || !Enum.TryParse(MimeType, true, out MimeTypes _)) 41 | { 42 | throw new MediaTypeNotSupportedException($"Illegal MIME type: {ToString()}"); 43 | } 44 | } 45 | 46 | public ContentMediaType ToBaseType() 47 | { 48 | if (Parameters == null || Parameters.Count == 0) 49 | { 50 | return this; 51 | } 52 | 53 | return new ContentMediaType(MimeType, MimeSubType); 54 | } 55 | 56 | public static ContentMediaType Json 57 | => new ContentMediaType(MimeTypes.Application.ToString().ToLower(), "json"); 58 | 59 | public static ContentMediaType Xml 60 | => new ContentMediaType(MimeTypes.Application.ToString().ToLower(), "xml"); 61 | 62 | public static ContentMediaType PlainText => 63 | new ContentMediaType(MimeTypes.Text.ToString(), "plain"); 64 | 65 | public static ContentMediaType BinaryContent => 66 | new ContentMediaType(MimeTypes.Application.ToString(), "octet-stream"); 67 | 68 | public static ContentMediaType CompressedZipContent() => 69 | new ContentMediaType(MimeTypes.Application.ToString(), "gzip"); 70 | 71 | public static ContentMediaType CompressedTarContent => 72 | new ContentMediaType(MimeTypes.Application.ToString(), "octet-stream"); 73 | 74 | 75 | public static ContentMediaType ParseFromDescriptor(string contentMediaTypeDescriptor) 76 | => MediaTypeParser.ParseFrom( 77 | contentMediaTypeDescriptor, 78 | new Builder((a, b, c) => new ContentMediaType(a, b, c))); 79 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Media/MediaTypeParser.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | 10 | namespace Vlingo.Xoom.Http.Media; 11 | 12 | public static class MediaTypeParser 13 | { 14 | private const int MimeTypeAndSubtypeSize = 2; 15 | private const int ParameterValueOffset = 1; 16 | private const int ParameterFieldOffset = 0; 17 | private const int ParameterAndValueSize = 2; 18 | 19 | public static T ParseFrom(string mediaTypeDescriptor, MediaTypeDescriptor.Builder builder) where T : MediaTypeDescriptor 20 | { 21 | var descriptorParts = mediaTypeDescriptor.Split(MediaTypeDescriptor.ParameterSeparator); 22 | if (descriptorParts.Length > 1) 23 | { 24 | ParseAttributes(builder, new ArraySegment(descriptorParts, 1, descriptorParts.Length-1)); 25 | } 26 | 27 | var mimeParts = descriptorParts[0].Split(MediaTypeDescriptor.MimeSubtypeSeparator); 28 | if (mimeParts.Length == MimeTypeAndSubtypeSize) 29 | { 30 | builder.WithMimeType(mimeParts[0].Trim()) 31 | .WithMimeSubType(mimeParts[1].Trim()); 32 | } 33 | 34 | return builder.Build(); 35 | } 36 | 37 | private static void ParseAttributes(MediaTypeDescriptor.Builder builder, ArraySegment parameters) where T : MediaTypeDescriptor 38 | { 39 | foreach (var parameter in parameters) 40 | { 41 | var parameterFieldAndValue = parameter.Split(MediaTypeDescriptor.ParameterAssignment); 42 | 43 | if (parameterFieldAndValue.Length == ParameterAndValueSize) 44 | { 45 | var attributeName = parameterFieldAndValue[ParameterFieldOffset]; 46 | var value = parameterFieldAndValue[ParameterValueOffset]; 47 | builder.WithParameter(attributeName, value); 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Method.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | 10 | namespace Vlingo.Xoom.Http; 11 | 12 | public enum Method 13 | { 14 | Connect, 15 | Delete, 16 | Get, 17 | Head, 18 | Options, 19 | Patch, 20 | Post, 21 | Put, 22 | Trace 23 | } 24 | 25 | public static class MethodExtensions 26 | { 27 | public static Method ToMethod(this string? methodNameText) 28 | { 29 | var name = (methodNameText ?? string.Empty).ToUpperInvariant(); 30 | switch (name) 31 | { 32 | case "CONNECT": 33 | return Method.Connect; 34 | case "DELETE": 35 | return Method.Delete; 36 | case "GET": 37 | return Method.Get; 38 | case "HEAD": 39 | return Method.Head; 40 | case "OPTIONS": 41 | return Method.Options; 42 | case "PATCH": 43 | return Method.Patch; 44 | case "POST": 45 | return Method.Post; 46 | case "PUT": 47 | return Method.Put; 48 | case "TRACE": 49 | return Method.Trace; 50 | default: 51 | throw new ArgumentException($"{ResponseStatus.MethodNotAllowed.GetDescription()}\n\n${methodNameText}"); 52 | } 53 | } 54 | 55 | public static bool IsConnect(this Method? method) => method == Method.Connect; 56 | public static bool IsConnect(this Method method) => method == Method.Connect; 57 | public static bool IsDelete(this Method? method) => method == Method.Delete; 58 | public static bool IsDelete(this Method method) => method == Method.Delete; 59 | public static bool IsGet(this Method? method) => method == Method.Get; 60 | public static bool IsGet(this Method method) => method == Method.Get; 61 | public static bool IsHead(this Method? method) => method == Method.Head; 62 | public static bool IsHead(this Method method) => method == Method.Head; 63 | public static bool IsOptions(this Method? method) => method == Method.Options; 64 | public static bool IsOptions(this Method method) => method == Method.Options; 65 | public static bool IsPatch(this Method? method) => method == Method.Patch; 66 | public static bool IsPatch(this Method method) => method == Method.Patch; 67 | public static bool IsPost(this Method? method) => method == Method.Post; 68 | public static bool IsPost(this Method method) => method == Method.Post; 69 | public static bool IsPut(this Method? method) => method == Method.Put; 70 | public static bool IsPut(this Method method) => method == Method.Put; 71 | public static bool IsTrace(this Method? method) => method == Method.Trace; 72 | public static bool IsTrace(this Method method) => method == Method.Trace; 73 | 74 | public static string Name(this Method method) => method.ToString().ToUpperInvariant(); 75 | public static string? Name(this Method? method) => method?.ToString().ToUpperInvariant(); 76 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/PlainBody.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | namespace Vlingo.Xoom.Http; 9 | 10 | public sealed class PlainBody : Body 11 | { 12 | /// 13 | /// Construct my default state. 14 | /// 15 | public PlainBody() 16 | { 17 | } 18 | 19 | /// 20 | /// Construct my default state with the as content. 21 | /// 22 | /// the string body content 23 | public PlainBody(string body) : base(body) 24 | { 25 | } 26 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/QueryParameters.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | 12 | namespace Vlingo.Xoom.Http; 13 | 14 | public class QueryParameters 15 | { 16 | private readonly IDictionary> _allParameters; 17 | 18 | public QueryParameters(string? query) => _allParameters = ParseQuery(query); 19 | 20 | public ICollection Names => _allParameters.Keys; 21 | 22 | public IReadOnlyList? ValuesOf(string name) 23 | { 24 | if (!_allParameters.ContainsKey(name)) 25 | { 26 | return null; 27 | } 28 | 29 | return new ArraySegment(_allParameters[name].ToArray()); 30 | } 31 | 32 | public bool ContainsKey(string name) => _allParameters.ContainsKey(name); 33 | 34 | private static IDictionary> ParseQuery(string? query) 35 | { 36 | if (string.IsNullOrWhiteSpace(query)) 37 | { 38 | return new Dictionary>(0); 39 | } 40 | 41 | try 42 | { 43 | var parameters = query?.Replace("?", string.Empty) .Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries); 44 | var queryParameters = new Dictionary>(parameters!.Length); 45 | 46 | foreach (var parameter in parameters) 47 | { 48 | var equalSign = parameter.IndexOf('='); 49 | var name = equalSign > 0 50 | ? Uri.UnescapeDataString(parameter.Substring(0, equalSign)) 51 | : parameter; 52 | var value = equalSign > 0 && parameter.Length > equalSign + 1 53 | ? Uri.UnescapeDataString(parameter.Substring(equalSign + 1)) 54 | : null; 55 | 56 | if (!queryParameters.ContainsKey(name)) 57 | { 58 | queryParameters[name] = new List(); 59 | } 60 | queryParameters[name].Add(value!); 61 | } 62 | 63 | return queryParameters; 64 | } 65 | catch (Exception ex) 66 | { 67 | throw new ArgumentException($"Query parameters invalid: {query}", ex); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/RequestData.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Http.Media; 9 | 10 | namespace Vlingo.Xoom.Http; 11 | 12 | public class RequestData 13 | { 14 | public Body Body { get; } 15 | public ContentMediaType MediaType { get; } 16 | public ContentEncoding ContentEncoding { get; } 17 | 18 | public RequestData(Body body, ContentMediaType mediaType, ContentEncoding contentEncoding) 19 | { 20 | Body = body; 21 | MediaType = mediaType; 22 | ContentEncoding = contentEncoding; 23 | } 24 | 25 | public override bool Equals(object? obj) 26 | { 27 | if (this == obj) 28 | { 29 | return true; 30 | } 31 | 32 | if (obj == null || GetType() != obj.GetType()) 33 | { 34 | return false; 35 | } 36 | 37 | var that = (RequestData) obj; 38 | return Body.Equals(that.Body) && MediaType.Equals(that.MediaType); 39 | } 40 | 41 | protected bool Equals(RequestData other) => 42 | Equals(Body, other.Body) && Equals(MediaType, other.MediaType); 43 | 44 | public override int GetHashCode() => 31 * Body.GetHashCode() + MediaType.GetHashCode(); 45 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/RequestFilter.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Http.Resource; 9 | 10 | namespace Vlingo.Xoom.Http; 11 | 12 | /// 13 | /// A for handling. 14 | /// 15 | public abstract class RequestFilter : Filter 16 | { 17 | /// 18 | /// Answer the to be propagated forward to the next 19 | /// or to the , and a bool indicating whether or not the 20 | /// chain should continue or be short circuited. If the bool is true, the chain 21 | /// will continue; if false, it will be short circuited. 22 | /// 23 | /// The to filter 24 | /// A pair of (Request, bool) 25 | public abstract (Request, bool) Filter(Request request); 26 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/AbstractDispatcherPool.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Actors; 9 | 10 | namespace Vlingo.Xoom.Http.Resource; 11 | 12 | /// 13 | /// Default behavior for all implementations. 14 | /// 15 | public abstract class AbstractDispatcherPool : IDispatcherPool 16 | { 17 | protected readonly IDispatcher[] DispatcherPool; 18 | 19 | protected AbstractDispatcherPool(Stage stage, Resources resources, int dispatcherPoolSize) 20 | { 21 | DispatcherPool = new IDispatcher[dispatcherPoolSize]; 22 | 23 | for (var idx = 0; idx < dispatcherPoolSize; ++idx) 24 | { 25 | DispatcherPool[idx] = Http.Resource.Dispatcher.StartWith(stage, resources); 26 | } 27 | } 28 | 29 | public void Close() 30 | { 31 | foreach (var dispatcher in DispatcherPool) 32 | { 33 | dispatcher.Stop(); 34 | } 35 | } 36 | 37 | public abstract IDispatcher Dispatcher(); 38 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/Actions.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | 12 | namespace Vlingo.Xoom.Http.Resource; 13 | 14 | public class Actions 15 | { 16 | private int _currentId; 17 | private readonly IList _actions; 18 | 19 | public static Actions CanBe(string method, string uri, string to) 20 | => new Actions(method, uri, to, null); 21 | 22 | public static Actions CanBe(string method, string uri, string to, string mapper) 23 | => new Actions(method, uri, to, mapper); 24 | 25 | public Actions Also(string method, string uri, string to) 26 | { 27 | _actions.Add(new Action(_currentId++, method, uri, to, null)); 28 | return this; 29 | } 30 | 31 | public Actions Also(string method, string uri, string to, string mapper) 32 | { 33 | _actions.Add(new Action(_currentId++, method, uri, to, mapper)); 34 | return this; 35 | } 36 | 37 | public IList ThatsAll() => new ArraySegment(_actions.ToArray()); 38 | 39 | private Actions(string method, string uri, string to, string? mapper) 40 | { 41 | _actions = new List 42 | { 43 | new Action(_currentId++, method, uri, to, mapper) 44 | }; 45 | } 46 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/AgentDispatcherPool.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Actors; 9 | using Vlingo.Xoom.Common; 10 | 11 | namespace Vlingo.Xoom.Http.Resource; 12 | 13 | public class AgentDispatcherPool : AbstractDispatcherPool 14 | { 15 | private readonly AtomicLong _dispatcherPoolIndex; 16 | private readonly long _dispatcherPoolSize; 17 | 18 | public AgentDispatcherPool(Stage stage, Resources resources, int dispatcherPoolSize) : base(stage, resources, dispatcherPoolSize) 19 | { 20 | _dispatcherPoolIndex = new AtomicLong(-1); 21 | _dispatcherPoolSize = dispatcherPoolSize; 22 | } 23 | 24 | public override IDispatcher Dispatcher() 25 | { 26 | var index = (int) (_dispatcherPoolIndex.IncrementAndGet() % _dispatcherPoolSize); 27 | return DispatcherPool[index]; 28 | } 29 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/ClientConsumerCommons.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Actors; 9 | using Vlingo.Xoom.Wire.Channel; 10 | using Vlingo.Xoom.Wire.Fdx.Bidirectional; 11 | 12 | namespace Vlingo.Xoom.Http.Resource; 13 | 14 | public static class ClientConsumerCommons 15 | { 16 | public static IClientRequestResponseChannel ClientChannel( 17 | Client.Configuration configuration, 18 | IResponseChannelConsumer consumer, 19 | ILogger logger) 20 | { 21 | if (configuration.IsSecure) 22 | { 23 | return new SecureClientRequestResponseChannel( 24 | configuration.AddressOfHost, 25 | consumer, 26 | configuration.ReadBufferPoolSize, 27 | configuration.ReadBufferSize, 28 | logger); 29 | } 30 | 31 | return new BasicClientRequestResponseChannel( 32 | configuration.AddressOfHost, 33 | consumer, 34 | configuration.ReadBufferPoolSize, 35 | configuration.ReadBufferSize, 36 | logger); 37 | } 38 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/Content.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Http.Media; 9 | 10 | namespace Vlingo.Xoom.Http.Resource; 11 | 12 | public class Content 13 | { 14 | public string Data { get; } 15 | public ContentMediaType ContentMediaType { get; } 16 | 17 | public Content(string data, ContentMediaType contentMediaType) 18 | { 19 | Data = data; 20 | ContentMediaType = contentMediaType; 21 | } 22 | 23 | public override bool Equals(object? o) 24 | { 25 | if (ReferenceEquals(this, o)) 26 | { 27 | return true; 28 | } 29 | 30 | if (o == null || GetType() != o.GetType()) 31 | { 32 | return false; 33 | } 34 | 35 | var content = (Content)o; 36 | 37 | return string.Equals(Data, content.Data) && Equals(ContentMediaType, content.ContentMediaType); 38 | } 39 | 40 | public override int GetHashCode() 41 | { 42 | unchecked 43 | { 44 | var hash = 17; 45 | hash = hash * 23 + (Data?.GetHashCode() ?? 0); 46 | hash = hash * 23 + (ContentMediaType?.GetHashCode() ?? 0); 47 | return hash; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/DefaultErrorHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | 10 | namespace Vlingo.Xoom.Http.Resource; 11 | 12 | public static class DefaultErrorHandler 13 | { 14 | private static Func Handler = ex => 15 | { 16 | if (ex is MediaTypeNotSupportedException) 17 | { 18 | return Response.Of(ResponseStatus.UnsupportedMediaType); 19 | } 20 | else if (ex is ArgumentException) 21 | { 22 | return Response.Of(ResponseStatus.BadRequest); 23 | } 24 | else 25 | { 26 | return Response.Of(ResponseStatus.InternalServerError); 27 | } 28 | }; 29 | 30 | public static IErrorHandler Instance { get; } = new ErrorHandlerImpl(Handler); 31 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/DefaultJsonMapper.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Common.Serialization; 10 | 11 | namespace Vlingo.Xoom.Http.Resource; 12 | 13 | public class DefaultJsonMapper : IMapper 14 | { 15 | public static DefaultJsonMapper Instance { get; } = new DefaultJsonMapper(); 16 | 17 | public object? From(string? data, Type? type) 18 | { 19 | if (type == typeof(string)) 20 | { 21 | return data; 22 | } 23 | 24 | return JsonSerialization.Deserialized(data!, type!); 25 | } 26 | 27 | public string From(T data) 28 | => JsonSerialization.Serialized(data); 29 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/DefaultMediaTypeMapper.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Http.Media; 9 | 10 | namespace Vlingo.Xoom.Http.Resource; 11 | 12 | public static class DefaultMediaTypeMapper 13 | { 14 | private static MediaTypeMapper BuildInstance() 15 | => new MediaTypeMapper.Builder() 16 | .AddMapperFor(ContentMediaType.Json, DefaultJsonMapper.Instance) 17 | .Build(); 18 | 19 | public static MediaTypeMapper Instance { get; } = BuildInstance(); 20 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/DefaultTextPlainMapper.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | 10 | namespace Vlingo.Xoom.Http.Resource; 11 | 12 | public class DefaultTextPlainMapper : IMapper 13 | { 14 | public static IMapper Instance => new DefaultTextPlainMapper(); 15 | 16 | public object? From(string? data, Type? type) 17 | { 18 | if (type == typeof(string)) 19 | { 20 | return data; 21 | } 22 | 23 | throw new InvalidOperationException("Cannot deserialize text into type"); 24 | } 25 | 26 | public string? From(T data) => data?.ToString(); 27 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/DispatcherActor.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Actors; 9 | 10 | namespace Vlingo.Xoom.Http.Resource; 11 | 12 | public class DispatcherActor : Actor, IDispatcher 13 | { 14 | private readonly Resources _resources; 15 | 16 | public DispatcherActor(Resources resources) 17 | { 18 | _resources = resources; 19 | AllocateHandlerPools(); 20 | } 21 | 22 | public void DispatchFor(Context context) 23 | => _resources.DispatchMatching(context, Logger); 24 | 25 | private void AllocateHandlerPools() 26 | { 27 | foreach (var resource in _resources.ResourceHandlers) 28 | { 29 | resource.AllocateHandlerPool(Stage); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/Dispatcher__Proxy.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | 11 | namespace Vlingo.Xoom.Http.Resource; 12 | 13 | public class Dispatcher__Proxy : IDispatcher 14 | { 15 | private const string RepresentationConclude0 = "Conclude()"; 16 | private const string DispatchForRepresentation1 = "DispatchFor(Vlingo.Xoom.Http.Context)"; 17 | private const string StopRepresentation2 = "Stop()"; 18 | 19 | private readonly Actor _actor; 20 | private readonly IMailbox _mailbox; 21 | 22 | public Dispatcher__Proxy(Actor actor, IMailbox mailbox) 23 | { 24 | _actor = actor; 25 | _mailbox = mailbox; 26 | } 27 | 28 | public bool IsStopped => _actor.IsStopped; 29 | 30 | public void DispatchFor(Context context) 31 | { 32 | if (!_actor.IsStopped) 33 | { 34 | Action consumer = actor => actor.DispatchFor(context); 35 | if (_mailbox.IsPreallocated) 36 | { 37 | _mailbox.Send(_actor, consumer, null, DispatchForRepresentation1); 38 | } 39 | else 40 | { 41 | _mailbox.Send(new LocalMessage(_actor, consumer, DispatchForRepresentation1)); 42 | } 43 | } 44 | else 45 | { 46 | _actor.DeadLetters?.FailedDelivery(new DeadLetter(_actor, DispatchForRepresentation1)); 47 | } 48 | } 49 | 50 | public void Stop() 51 | { 52 | if (!_actor.IsStopped) 53 | { 54 | Action consumer = actor => actor.Stop(); 55 | if (_mailbox.IsPreallocated) 56 | { 57 | _mailbox.Send(_actor, consumer, null, StopRepresentation2); 58 | } 59 | else 60 | { 61 | _mailbox.Send(new LocalMessage(_actor, consumer, StopRepresentation2)); 62 | } 63 | } 64 | else 65 | { 66 | _actor.DeadLetters?.FailedDelivery(new DeadLetter(_actor, StopRepresentation2)); 67 | } 68 | } 69 | 70 | public void Conclude() 71 | { 72 | if (!_actor.IsStopped) 73 | { 74 | Action consumer = actor => actor.Conclude(); 75 | if (_mailbox.IsPreallocated) 76 | { 77 | _mailbox.Send(_actor, consumer, null, RepresentationConclude0); 78 | } 79 | else 80 | { 81 | _mailbox.Send(new LocalMessage(_actor, consumer, RepresentationConclude0)); 82 | } 83 | } 84 | else 85 | { 86 | _actor.DeadLetters?.FailedDelivery(new DeadLetter(_actor, RepresentationConclude0)); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/EmbeddedResourceLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace Vlingo.Xoom.Http.Resource; 6 | 7 | public static class EmbeddedResourceLoader 8 | { 9 | public static Assembly LoadFromPath(string path) 10 | { 11 | var lastIndexOfPathSeparator = path.LastIndexOf('.'); 12 | var loadPath = path.Substring(0, lastIndexOfPathSeparator); 13 | var assemblies = AppDomain.CurrentDomain.GetAssemblies(); 14 | while (!string.IsNullOrWhiteSpace(loadPath)) 15 | { 16 | var assembly = assemblies.SingleOrDefault(a => a.GetName().Name == loadPath); 17 | if (assembly != null) 18 | { 19 | return assembly; 20 | } 21 | 22 | var nextIndex = loadPath.LastIndexOf('.'); 23 | if (nextIndex > 0) 24 | { 25 | loadPath = loadPath.Substring(0, nextIndex); 26 | } 27 | else 28 | { 29 | loadPath = string.Empty; 30 | } 31 | } 32 | 33 | return Assembly.GetExecutingAssembly(); 34 | } 35 | 36 | public static string CleanPath(string path) => path.Replace("%20", "_").Replace("/", "."); 37 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/Feed/FeedProducer__Proxy.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | 11 | namespace Vlingo.Xoom.Http.Resource.Feed; 12 | 13 | public class FeedProducer__Proxy : IFeedProducer 14 | { 15 | private const string ProduceFeedForRepresentation1 = 16 | "ProduceFeedFor(Vlingo.Xoom.Http.Resource.Feed.FeedProductRequest)"; 17 | 18 | private readonly Actor _actor; 19 | private readonly IMailbox _mailbox; 20 | 21 | public FeedProducer__Proxy(Actor actor, IMailbox mailbox) 22 | { 23 | _actor = actor; 24 | _mailbox = mailbox; 25 | } 26 | 27 | public void ProduceFeedFor(FeedProductRequest request) 28 | { 29 | if (!_actor.IsStopped) 30 | { 31 | Action cons1617155091 = __ => __.ProduceFeedFor(request); 32 | if (_mailbox.IsPreallocated) 33 | _mailbox.Send(_actor, cons1617155091, null, ProduceFeedForRepresentation1); 34 | else 35 | _mailbox.Send(new LocalMessage(_actor, cons1617155091, ProduceFeedForRepresentation1)); 36 | } 37 | else 38 | { 39 | _actor.DeadLetters?.FailedDelivery(new DeadLetter(_actor, ProduceFeedForRepresentation1)); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/Feed/FeedProductRequest.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | namespace Vlingo.Xoom.Http.Resource.Feed; 9 | 10 | public class FeedProductRequest 11 | { 12 | /// 13 | /// Constructs my state 14 | /// 15 | /// the Context of the original HTTP request 16 | /// The name of the feed from which the product is made 17 | /// The identity of the product to feed 18 | /// The maximum number of elements in the product 19 | public FeedProductRequest(Context? context, string feedName, string feedProductId, int feedProductElements) 20 | { 21 | Context = context; 22 | FeedName = feedName; 23 | FeedProductId = feedProductId; 24 | FeedProductElements = feedProductElements; 25 | } 26 | 27 | public Context? Context { get; } 28 | public string? FeedName { get; } 29 | public int FeedProductElements { get; } 30 | public string? FeedProductId { get; } 31 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/Feed/FeedResource.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | using Vlingo.Xoom.Actors; 11 | 12 | namespace Vlingo.Xoom.Http.Resource.Feed; 13 | 14 | /// 15 | /// Standard reusable resource for serving feeds. 16 | /// 17 | public class FeedResource : ResourceHandler 18 | { 19 | private readonly World _world; 20 | private readonly Dictionary _producers; 21 | 22 | /// 23 | /// Construct my default state. 24 | /// 25 | /// The World 26 | public FeedResource(World world) 27 | { 28 | _world = world; 29 | _producers = new Dictionary(2); 30 | } 31 | 32 | public void Feed(string feedName, string feedProductId, Type feedProducerClass, int feedProductElements) 33 | { 34 | var producer = FeedProducer(feedName, feedProducerClass); 35 | if (producer == null) 36 | { 37 | Completes?.With(Response.Of(ResponseStatus.NotFound, $"Feed '{feedName}' does not exist.")); 38 | } 39 | else 40 | { 41 | producer.ProduceFeedFor(new FeedProductRequest(Context, feedName, feedProductId, feedProductElements)); 42 | } 43 | } 44 | 45 | private IFeedProducer FeedProducer(string feedName, Type feedProducerClass) 46 | { 47 | if (!_producers.TryGetValue(feedName, out var producer)) 48 | { 49 | producer = FeedProducerFactory.Using(_world.Stage, feedProducerClass); 50 | _producers.Add(feedName, producer); 51 | } 52 | return producer; 53 | } 54 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/Feed/FeedResourceDispatcher.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | 11 | namespace Vlingo.Xoom.Http.Resource.Feed; 12 | 13 | public class FeedResourceDispatcher : ConfigurationResource 14 | { 15 | public FeedResourceDispatcher(string name, Type resourceHandlerClass, int handlerPoolSize, 16 | IList actions) : base(name, resourceHandlerClass, handlerPoolSize, actions) 17 | { 18 | } 19 | 20 | public override void DispatchToHandlerWith(Context context, Action.MappedParameters? mappedParameters) 21 | { 22 | Action consumer; 23 | 24 | try 25 | { 26 | switch (mappedParameters?.ActionId) 27 | { 28 | case 0 29 | : // GET /feeds/{feedName}/{feedItemId} feed(String feedName, String feedProductId, Class feedProducerClass, int feedProductElements) 30 | consumer = handler => handler.Feed((string) mappedParameters.Mapped[0].Value!, 31 | (string) mappedParameters.Mapped[1].Value!, (Type) mappedParameters.Mapped[2].Value!, 32 | (int) mappedParameters.Mapped[3].Value!); 33 | PooledHandler.HandleFor(context, consumer); 34 | break; 35 | } 36 | } 37 | catch (Exception e) 38 | { 39 | throw new InvalidOperationException($"Action mismatch: Request: {context.Request}Parameters: {mappedParameters}", e); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/Feed/IFeedProducer.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | 11 | namespace Vlingo.Xoom.Http.Resource.Feed; 12 | 13 | /// 14 | /// Produce a feed item for a named feed. 15 | /// 16 | public interface IFeedProducer 17 | { 18 | /// 19 | /// Produce the feed to fulfill the 20 | /// 21 | /// The FeedProductRequest holding request information 22 | void ProduceFeedFor(FeedProductRequest request); 23 | } 24 | 25 | public static class FeedProducerFactory 26 | { 27 | /// 28 | /// Answer a new 29 | /// 30 | /// The Stage in which the FeedProducer is created 31 | /// feedProducerClass 32 | /// FeedProducer 33 | public static IFeedProducer Using(Stage stage, Type feedProducerClass) 34 | => stage.ActorFor(feedProducerClass); 35 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/HandlerMissingException.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.Runtime.Serialization; 10 | 11 | namespace Vlingo.Xoom.Http.Resource; 12 | 13 | public class HandlerMissingException : Exception 14 | { 15 | public HandlerMissingException() 16 | { 17 | } 18 | 19 | public HandlerMissingException(string message) : base(message) 20 | { 21 | } 22 | 23 | public HandlerMissingException(string message, Exception innerException) : base(message, innerException) 24 | { 25 | } 26 | 27 | protected HandlerMissingException(SerializationInfo info, StreamingContext context) : base(info, context) 28 | { 29 | } 30 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/HttpProperties.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | using System.IO; 11 | using System.Linq; 12 | using Vlingo.Xoom.Common; 13 | 14 | namespace Vlingo.Xoom.Http.Resource; 15 | 16 | public sealed class HttpProperties : ConfigurationProperties 17 | { 18 | private static IDictionary _properties = new Dictionary(); 19 | 20 | private static Func Factory = () => 21 | { 22 | var props = new HttpProperties(_properties); 23 | props.Load(new FileInfo("vlingo-http.json")); 24 | return props; 25 | }; 26 | 27 | private static Lazy SingleInstance = new Lazy(Factory, true); 28 | 29 | public static HttpProperties Instance 30 | { 31 | get 32 | { 33 | if (_properties.Any()) 34 | { 35 | SingleInstance.Value.UpdateCustomProperties(_properties); 36 | _properties.Clear(); 37 | } 38 | 39 | return SingleInstance.Value; 40 | } 41 | } 42 | 43 | public void SetCustomProperties(IDictionary properties) 44 | { 45 | _properties = properties; 46 | UpdateCustomProperties(_properties); 47 | } 48 | 49 | private HttpProperties(IDictionary properties) 50 | { 51 | _properties = properties; 52 | } 53 | 54 | private void UpdateCustomProperties(IDictionary properties) 55 | { 56 | foreach (var property in properties) 57 | { 58 | SetProperty(property.Key, property.Value); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/IClientConsumer.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System.IO; 9 | using Vlingo.Xoom.Actors; 10 | using Vlingo.Xoom.Common; 11 | using Vlingo.Xoom.Wire.Channel; 12 | using Vlingo.Xoom.Wire.Fdx.Bidirectional; 13 | 14 | namespace Vlingo.Xoom.Http.Resource; 15 | 16 | /// 17 | /// The client that is a request sender and that checks for 18 | /// responses and consumes them. 19 | /// 20 | public interface IClientConsumer : IResponseChannelConsumer, IScheduled, IStoppable 21 | { 22 | ICompletes RequestWith(Request request, ICompletes completes); 23 | } 24 | 25 | internal sealed class State 26 | { 27 | public MemoryStream Buffer { get; } 28 | public IClientRequestResponseChannel Channel { get; } 29 | public Client.Configuration Configuration { get; } 30 | public ResponseParser? Parser { get; internal set; } 31 | public ICancellable Probe { get; } 32 | 33 | public State( 34 | Client.Configuration configuration, 35 | IClientRequestResponseChannel channel, 36 | ResponseParser? parser, 37 | ICancellable probe, 38 | MemoryStream buffer) 39 | { 40 | Configuration = configuration; 41 | Channel = channel; 42 | Parser = parser; 43 | Probe = probe; 44 | Buffer = buffer; 45 | } 46 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/IConfigurationResource.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | using Vlingo.Xoom.Actors; 11 | 12 | namespace Vlingo.Xoom.Http.Resource; 13 | 14 | public interface IConfigurationResource : IResource 15 | { 16 | Type ResourceHandlerClass { get; } 17 | IReadOnlyList Actions { get; } 18 | IResourceRequestHandler PooledHandler { get; } 19 | ResourceHandler ResourceHandlerInstance(Stage stage); 20 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/IDispatcher.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Actors; 9 | 10 | namespace Vlingo.Xoom.Http.Resource; 11 | 12 | public interface IDispatcher : IStoppable 13 | { 14 | void DispatchFor(Context context); 15 | } 16 | 17 | public static class Dispatcher 18 | { 19 | public static IDispatcher StartWith(Stage stage, Resources resources) 20 | => stage.ActorFor(() => new DispatcherActor(resources)); 21 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/IDispatcherPool.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | namespace Vlingo.Xoom.Http.Resource; 9 | 10 | /// 11 | /// A pool of IDispatcher instances. 12 | /// 13 | public interface IDispatcherPool 14 | { 15 | /// 16 | /// Close the IDispatcher instances of my internal pool. 17 | /// 18 | void Close(); 19 | 20 | /// 21 | /// Answer an available IDispatcher from my pool. 22 | /// 23 | /// 24 | IDispatcher Dispatcher(); 25 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/IErrorHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | 10 | namespace Vlingo.Xoom.Http.Resource; 11 | 12 | public interface IErrorHandler 13 | { 14 | Response Handle(Exception error); 15 | } 16 | 17 | public static class ErrorHandler 18 | { 19 | public static IErrorHandler HandleAllWith(ResponseStatus status) 20 | => new ErrorHandlerImpl(err => Response.Of(status)); 21 | } 22 | 23 | internal class ErrorHandlerImpl : IErrorHandler 24 | { 25 | private readonly Func _handler; 26 | 27 | public ErrorHandlerImpl(Func handler) 28 | { 29 | _handler = handler; 30 | } 31 | 32 | public Response Handle(Exception error) 33 | => _handler.Invoke(error); 34 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/IMapper.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | 10 | namespace Vlingo.Xoom.Http.Resource; 11 | 12 | public interface IMapper 13 | { 14 | object? From(string? data, Type? type); 15 | string? From(T data); 16 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/IRequestSender.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Actors; 9 | 10 | namespace Vlingo.Xoom.Http.Resource; 11 | 12 | /// 13 | /// Sends Request messages in behalf of a client. 14 | /// 15 | public interface IRequestSender : IStoppable 16 | { 17 | /// 18 | /// Sends the . 19 | /// 20 | /// The Request to send. 21 | void SendRequest(Request request); 22 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/IResource.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | 11 | namespace Vlingo.Xoom.Http.Resource; 12 | 13 | public interface IResource 14 | { 15 | string Name { get; } 16 | int HandlerPoolSize { get; } 17 | void DispatchToHandlerWith(Context context, Action.MappedParameters? mappedParameters); 18 | Action.MatchResults MatchWith(Method? method, Uri? uri); 19 | void Log(ILogger logger); 20 | void AllocateHandlerPool(Stage stage); 21 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/IResourceRequestHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | 10 | namespace Vlingo.Xoom.Http.Resource; 11 | 12 | public interface IResourceRequestHandler 13 | { 14 | void HandleFor(Context context, Action? consumer) where T : ResourceHandler; 15 | 16 | void HandleFor(Context context, Action.MappedParameters mappedParameters, RequestHandler handler); 17 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/IResponseConsumer.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | namespace Vlingo.Xoom.Http.Resource; 9 | 10 | public interface IResponseConsumer 11 | { 12 | void Consume(Response response); 13 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/LoadBalancingClientRequestConsumerActor.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | using Vlingo.Xoom.Common; 11 | using Vlingo.Xoom.Wire.Message; 12 | 13 | namespace Vlingo.Xoom.Http.Resource; 14 | 15 | /// 16 | /// Load-balancing router of IClientConsumer requests. 17 | /// 18 | public class LoadBalancingClientRequestConsumerActor : SmallestMailboxRouter, IClientConsumer 19 | { 20 | private const string ErrorMessage = "LoadBalancingClientRequestConsumerActor: Should not be reached. Message: "; 21 | 22 | public LoadBalancingClientRequestConsumerActor( 23 | Client.Configuration configuration, 24 | RouterSpecification specification) 25 | : base(specification) 26 | { 27 | } 28 | 29 | public void Consume(IConsumerByteBuffer buffer) 30 | { 31 | var message = $"{ErrorMessage} Consume()"; 32 | Logger.Error(message, new NotSupportedException(message)); 33 | } 34 | 35 | public void IntervalSignal(IScheduled scheduled, object data) 36 | { 37 | var message = $"{ErrorMessage} IntervalSignal()"; 38 | Logger.Error(message, new NotSupportedException(message)); 39 | } 40 | 41 | public ICompletes RequestWith(Request request, ICompletes completes) 42 | { 43 | DispatchCommand((a, b, c) => a.RequestWith(b, c), request, completes); 44 | return completes; 45 | } 46 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/MediaTypeMapper.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using Vlingo.Xoom.Http.Media; 12 | 13 | namespace Vlingo.Xoom.Http.Resource; 14 | 15 | public class MediaTypeMapper 16 | { 17 | private readonly IDictionary _mappersByContentType; 18 | 19 | public MediaTypeMapper(IDictionary mappersByContentType) => 20 | _mappersByContentType = mappersByContentType; 21 | 22 | public T From(string? data, ContentMediaType contentMediaType) 23 | { 24 | var baseType = contentMediaType.ToBaseType(); 25 | if (_mappersByContentType.ContainsKey(baseType)) 26 | { 27 | return (T)_mappersByContentType[baseType].From(data, typeof(T))!; 28 | } 29 | throw new MediaTypeNotSupportedException(contentMediaType.ToString()); 30 | } 31 | 32 | public string From(T data, ContentMediaType contentMediaType) 33 | { 34 | var baseType = contentMediaType.ToBaseType(); 35 | if (_mappersByContentType.ContainsKey(baseType)) 36 | { 37 | return _mappersByContentType[baseType].From(data)!; 38 | } 39 | throw new MediaTypeNotSupportedException(contentMediaType.ToString()); 40 | } 41 | 42 | public ContentMediaType[] MappedMediaTypes => _mappersByContentType.Keys.ToArray(); 43 | 44 | public class Builder 45 | { 46 | private readonly IDictionary _mappersByContentType; 47 | 48 | public Builder() => _mappersByContentType = new Dictionary(); 49 | 50 | public Builder AddMapperFor(ContentMediaType contentMediaType, IMapper mapper) 51 | { 52 | if (_mappersByContentType.ContainsKey(contentMediaType)) 53 | { 54 | throw new InvalidOperationException("Content mimeType already added"); 55 | } 56 | 57 | _mappersByContentType[contentMediaType] = mapper; 58 | 59 | return this; 60 | } 61 | 62 | public MediaTypeMapper Build() => new MediaTypeMapper(_mappersByContentType); 63 | } 64 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/MediaTypeNotSupportedException.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | 10 | namespace Vlingo.Xoom.Http.Resource; 11 | 12 | public class MediaTypeNotSupportedException : Exception 13 | { 14 | private readonly string _mediaType; 15 | 16 | public MediaTypeNotSupportedException(string mediaType) 17 | { 18 | _mediaType = mediaType; 19 | } 20 | 21 | public override string Message => $"No mapper registered for the following media mimeType: {_mediaType}"; 22 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/RequestExecutor.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | using Vlingo.Xoom.Common; 11 | 12 | namespace Vlingo.Xoom.Http.Resource; 13 | 14 | internal abstract class RequestExecutor 15 | { 16 | internal static ICompletes ExecuteRequest( 17 | Func?> executeAction, 18 | IErrorHandler errorHandler, 19 | ILogger logger) 20 | { 21 | 22 | try 23 | { 24 | return executeAction.Invoke()? 25 | .RecoverFrom(ex => ResourceErrorProcessor.ResourceHandlerError(errorHandler, logger, ex))!; 26 | } 27 | catch (Exception ex) 28 | { 29 | return Completes.WithFailure(ResourceErrorProcessor.ResourceHandlerError(errorHandler, logger, ex)); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/RequestObjectExecutor.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | using Vlingo.Xoom.Common; 11 | 12 | namespace Vlingo.Xoom.Http.Resource; 13 | 14 | internal abstract class RequestObjectExecutor 15 | { 16 | internal static ICompletes ExecuteRequest( 17 | Request request, 18 | MediaTypeMapper mediaTypeMapper, 19 | Func> executeAction, 20 | IErrorHandler errorHandler, 21 | ILogger logger) 22 | { 23 | try 24 | { 25 | return executeAction.Invoke() 26 | .AndThen(objectResponse => ToResponse(objectResponse, request, mediaTypeMapper, errorHandler, logger)); 27 | } 28 | catch (Exception ex) 29 | { 30 | return Completes.WithFailure(ResourceErrorProcessor.ResourceHandlerError(errorHandler, logger, ex)); 31 | } 32 | } 33 | 34 | internal static Response ToResponse( 35 | IObjectResponse objectResponse, 36 | Request request, 37 | MediaTypeMapper mediaTypeMapper, 38 | IErrorHandler errorHandler, 39 | ILogger logger) 40 | => Success.Of(objectResponse.ResponseFrom(request, mediaTypeMapper)) 41 | .Resolve( 42 | ex => ResourceErrorProcessor.ResourceHandlerError(errorHandler, logger, ex), 43 | response => response); 44 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/RequestSenderProbeActor.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.IO; 10 | using Vlingo.Xoom.Actors; 11 | using Vlingo.Xoom.Common; 12 | using Vlingo.Xoom.Wire.Channel; 13 | using Vlingo.Xoom.Wire.Fdx.Bidirectional; 14 | using Vlingo.Xoom.Wire.Message; 15 | 16 | namespace Vlingo.Xoom.Http.Resource; 17 | 18 | public sealed class RequestSenderProbeActor : Actor, IRequestSender, IScheduled 19 | { 20 | private readonly MemoryStream _buffer; 21 | private readonly IClientRequestResponseChannel _channel; 22 | private readonly ICancellable _cancellable; 23 | 24 | public RequestSenderProbeActor(Client.Configuration configuration, IResponseChannelConsumer consumer, string testId) 25 | { 26 | _channel = ClientConsumerCommons.ClientChannel(configuration, consumer, Logger); 27 | _cancellable = Stage.Scheduler.Schedule( 28 | SelfAs>(), 29 | null, 30 | TimeSpan.FromMilliseconds(1), 31 | TimeSpan.FromMilliseconds(configuration.ProbeInterval)); 32 | _buffer = new MemoryStream(configuration.WriteBufferSize); 33 | } 34 | 35 | public void IntervalSignal(IScheduled scheduled, object data) 36 | => _channel.ProbeChannel(); 37 | 38 | public void SendRequest(Request request) 39 | { 40 | _buffer.Clear(); 41 | var array = Converters.TextToBytes(request.ToString()); 42 | _buffer.Write(array, 0, array.Length); 43 | _buffer.Flip(); 44 | _channel.RequestWith(_buffer.ToArray()); 45 | } 46 | 47 | public override void Stop() 48 | { 49 | _cancellable.Cancel(); 50 | _channel.Close(); 51 | base.Stop(); 52 | } 53 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/RequestSender__Proxy.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | 11 | namespace Vlingo.Xoom.Http.Resource; 12 | 13 | public class RequestSender__Proxy : IRequestSender 14 | { 15 | private const string RepresentationConclude = "Conclude()"; 16 | private const string SendRequestRepresentation = "SendRequest(Request)"; 17 | private const string StopRepresentation = "Stop()"; 18 | 19 | private readonly Actor _actor; 20 | private readonly IMailbox _mailbox; 21 | 22 | public RequestSender__Proxy(Actor actor, IMailbox mailbox) 23 | { 24 | _actor = actor; 25 | _mailbox = mailbox; 26 | } 27 | 28 | public bool IsStopped => _actor.IsStopped; 29 | 30 | public void Conclude() 31 | { 32 | if (!_actor.IsStopped) 33 | { 34 | Action consumer = actor => actor.Conclude(); 35 | if (_mailbox.IsPreallocated) 36 | { 37 | _mailbox.Send(_actor, consumer, null, RepresentationConclude); 38 | } 39 | else 40 | { 41 | _mailbox.Send(new LocalMessage(_actor, consumer, RepresentationConclude)); 42 | } 43 | } 44 | else 45 | { 46 | _actor.DeadLetters?.FailedDelivery(new DeadLetter(_actor, RepresentationConclude)); 47 | } 48 | } 49 | 50 | public void SendRequest(Request request) 51 | { 52 | if (!_actor.IsStopped) 53 | { 54 | Action consumer = actor => actor.SendRequest(request); 55 | if (_mailbox.IsPreallocated) 56 | { 57 | _mailbox.Send(_actor, consumer, null, SendRequestRepresentation); 58 | } 59 | else 60 | { 61 | _mailbox.Send(new LocalMessage(_actor, consumer, SendRequestRepresentation)); 62 | } 63 | } 64 | else 65 | { 66 | _actor.DeadLetters?.FailedDelivery(new DeadLetter(_actor, SendRequestRepresentation)); 67 | } 68 | } 69 | 70 | public void Stop() 71 | { 72 | if (!_actor.IsStopped) 73 | { 74 | Action consumer = actor => actor.Stop(); 75 | if (_mailbox.IsPreallocated) 76 | { 77 | _mailbox.Send(_actor, consumer, null, StopRepresentation); 78 | } 79 | else 80 | { 81 | _mailbox.Send(new LocalMessage(_actor, consumer, StopRepresentation)); 82 | } 83 | } 84 | else 85 | { 86 | _actor.DeadLetters?.FailedDelivery(new DeadLetter(_actor, StopRepresentation)); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/Resource.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | using Vlingo.Xoom.Common; 11 | 12 | namespace Vlingo.Xoom.Http.Resource; 13 | 14 | public abstract class Resource : IResource 15 | { 16 | private readonly IResourceRequestHandler[] _handlerPool; 17 | private readonly AtomicLong _handlerPoolIndex; 18 | 19 | public Resource(string name, int handlerPoolSize) 20 | { 21 | Name = name; 22 | HandlerPoolSize = handlerPoolSize; 23 | _handlerPool = new IResourceRequestHandler[handlerPoolSize]; 24 | _handlerPoolIndex = new AtomicLong(0); 25 | } 26 | 27 | public string Name { get; } 28 | public int HandlerPoolSize { get; } 29 | 30 | public abstract void DispatchToHandlerWith(Context context, Action.MappedParameters? mappedParameters); 31 | public abstract Action.MatchResults MatchWith(Method? method, Uri? uri); 32 | public abstract void Log(ILogger logger); 33 | 34 | public abstract ResourceHandler ResourceHandlerInstance(Stage stage); 35 | 36 | public void AllocateHandlerPool(Stage stage) 37 | { 38 | for (var i = 0; i < HandlerPoolSize; ++i) 39 | { 40 | _handlerPool[i] = stage.ActorFor( 41 | () => new ResourceRequestHandlerActor(ResourceHandlerInstance(stage))); 42 | } 43 | } 44 | 45 | public IResourceRequestHandler PooledHandler 46 | { 47 | get 48 | { 49 | var index = (int)(_handlerPoolIndex.IncrementAndGet() % HandlerPoolSize); 50 | return _handlerPool[index]; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/ResourceBuilder.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System.Linq; 9 | 10 | namespace Vlingo.Xoom.Http.Resource; 11 | 12 | public static class ResourceBuilder 13 | { 14 | public static Resource Resource(string name, params RequestHandler[] requestHandlers) 15 | => Resource(name, null, 10, requestHandlers); 16 | 17 | public static Resource Resource(string name, int handlerPoolSize, params RequestHandler[] requestHandlers) 18 | => Resource(name, null, 10, requestHandlers); 19 | 20 | public static Resource Resource(string name, DynamicResourceHandler? dynamicResourceHandler, params RequestHandler[] requestHandlers) 21 | => new DynamicResource(name, dynamicResourceHandler, 10, requestHandlers.ToList()); 22 | 23 | public static Resource Resource(string name, DynamicResourceHandler? dynamicResourceHandler, int handlerPoolSize, params RequestHandler[] requestHandlers) 24 | => new DynamicResource(name, dynamicResourceHandler, handlerPoolSize, requestHandlers.ToList()); 25 | 26 | public static RequestHandler0 Get(string uri) 27 | => new RequestHandler0(Method.Get, uri); 28 | 29 | public static RequestHandler0 Post(string uri) 30 | => new RequestHandler0(Method.Post, uri); 31 | 32 | public static RequestHandler0 Put(string uri) 33 | => new RequestHandler0(Method.Put, uri); 34 | 35 | public static RequestHandler0 Delete(string uri) 36 | => new RequestHandler0(Method.Delete, uri); 37 | 38 | public static RequestHandler0 Patch(string uri) 39 | => new RequestHandler0(Method.Patch, uri); 40 | 41 | public static RequestHandler0 Head(string uri) 42 | => new RequestHandler0(Method.Head, uri); 43 | 44 | public static RequestHandler0 Options(string uri) 45 | => new RequestHandler0(Method.Options, uri); 46 | 47 | public static RequestHandler0 Trace(string uri) 48 | => new RequestHandler0(Method.Trace, uri); 49 | 50 | public static RequestHandler0 Connect(string uri) 51 | => new RequestHandler0(Method.Connect, uri); 52 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/ResourceErrorProcessor.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | 11 | namespace Vlingo.Xoom.Http.Resource; 12 | 13 | public class ResourceErrorProcessor 14 | { 15 | public static Response DefaultErrorResponse() => Response.Of(ResponseStatus.InternalServerError); 16 | 17 | public static Response ResourceHandlerError(IErrorHandler errorHandler, ILogger logger, Exception exception) 18 | { 19 | Response response; 20 | try 21 | { 22 | logger.Error("Exception thrown by Resource execution", exception); 23 | response = errorHandler != null ? 24 | errorHandler.Handle(exception) : 25 | DefaultErrorHandler.Instance.Handle(exception); 26 | } 27 | catch 28 | { 29 | logger.Error("Exception thrown by error handler when handling error", exception); 30 | response = DefaultErrorResponse(); 31 | } 32 | return response; 33 | } 34 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/ResourceHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | using Vlingo.Xoom.Common; 11 | 12 | namespace Vlingo.Xoom.Http.Resource; 13 | 14 | public abstract class ResourceHandler 15 | { 16 | private Context? _context; 17 | private Stage? _stage; 18 | 19 | public virtual Resource Routes() => throw new NotSupportedException("Undefined resource; must override."); 20 | 21 | protected ResourceHandler() { } 22 | 23 | protected virtual ICompletesEventually? Completes => Context?.Completes; 24 | 25 | public virtual Context? Context 26 | { 27 | get => _context; 28 | set => _context = value; 29 | } 30 | 31 | protected ContentType ContentType => ContentType.Of("text/plain", "us-ascii"); 32 | 33 | /// 34 | /// Answer a with the and 35 | /// with a Content-Type header per my ContentType, which may be overridden. 36 | /// 37 | /// The status of the response 38 | /// The string entity of the response 39 | /// 40 | protected Response EntityResponseOf(ResponseStatus status, string entity) => 41 | EntityResponseOf(status, Headers.Empty(), entity); 42 | 43 | /// 44 | /// Answer a with the and 45 | /// with a Content-Type header per my ContentType, which may be overridden. 46 | /// 47 | /// The status of the response 48 | /// The to which the Content-Type header is appended 49 | /// The string entity of the response 50 | /// 51 | protected Response EntityResponseOf(ResponseStatus status, Headers headers, string entity) => 52 | Response.Of(status, headers.And(ContentType.ToResponseHeader()), entity); 53 | 54 | protected internal virtual ILogger? Logger => _stage?.World.DefaultLogger; 55 | 56 | public virtual Scheduler? Scheduler => _stage?.Scheduler; 57 | 58 | public virtual Stage? Stage { get => _stage; set => _stage = value; } 59 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/ResourceRequestHandlerActor.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | 11 | namespace Vlingo.Xoom.Http.Resource; 12 | 13 | public class ResourceRequestHandlerActor : Actor, IResourceRequestHandler 14 | { 15 | private readonly ResourceHandler _resourceHandler; 16 | 17 | public ResourceRequestHandlerActor(ResourceHandler resourceHandler) 18 | { 19 | _resourceHandler = resourceHandler; 20 | } 21 | 22 | public void HandleFor(Context context, Action? consumer) where T : ResourceHandler 23 | { 24 | try 25 | { 26 | _resourceHandler.Context = context; 27 | _resourceHandler.Stage = Stage; 28 | consumer?.Invoke((T)_resourceHandler); 29 | } 30 | catch (Exception e) 31 | { 32 | Logger.Error("Error thrown by resource dispatcher", e); 33 | context.Completes.With(Response.Of(ResponseStatus.InternalServerError)); 34 | } 35 | } 36 | 37 | public void HandleFor(Context context, Action.MappedParameters mappedParameters, RequestHandler handler) 38 | { 39 | Action consumer = resource => 40 | handler 41 | .Execute(context.Request!, mappedParameters, resource.Logger!) 42 | .AndThen(outcome => RespondWith(context, outcome)) 43 | .Otherwise(failure => RespondWith(context, failure)) 44 | .RecoverFrom(exception => Response.Of(ResponseStatus.BadRequest, exception.Message)); 45 | 46 | HandleFor(context, consumer); 47 | } 48 | 49 | private Response RespondWith(Context context, Response response) 50 | { 51 | context.Completes.With(response); 52 | return response; 53 | } 54 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/ResourceRequestHandler__Proxy.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | 11 | namespace Vlingo.Xoom.Http.Resource; 12 | 13 | public class ResourceRequestHandler__Proxy : IResourceRequestHandler 14 | { 15 | private const string HandleForRepresentation1 = "HandleFor(Vlingo.Xoom.Http.Context, Action)"; 16 | private const string HandleForRepresentation2 = "HandleFor(Vlingo.Xoom.Http.Context, Action.MappedParameters, RequestHandler)"; 17 | 18 | private readonly Actor _actor; 19 | private readonly IMailbox _mailbox; 20 | 21 | public ResourceRequestHandler__Proxy(Actor actor, IMailbox mailbox) 22 | { 23 | _actor = actor; 24 | _mailbox = mailbox; 25 | } 26 | 27 | public void HandleFor(Context context, Action? consumer) 28 | where T : ResourceHandler 29 | { 30 | if (!_actor.IsStopped) 31 | { 32 | Action cons128873 = __ => 33 | __.HandleFor(context, consumer); 34 | if (_mailbox.IsPreallocated) 35 | { 36 | _mailbox.Send(_actor, cons128873, null, HandleForRepresentation1); 37 | } 38 | else 39 | { 40 | _mailbox.Send( 41 | new LocalMessage(_actor, cons128873, 42 | HandleForRepresentation1)); 43 | } 44 | } 45 | else 46 | { 47 | _actor.DeadLetters?.FailedDelivery(new DeadLetter(_actor, HandleForRepresentation1)); 48 | } 49 | } 50 | 51 | public void HandleFor(Context context, Action.MappedParameters mappedParameters, RequestHandler handler) 52 | { 53 | if (!_actor.IsStopped) 54 | { 55 | Action cons128873 = __ => __.HandleFor(context, mappedParameters, handler); 56 | if (_mailbox.IsPreallocated) 57 | { 58 | _mailbox.Send(_actor, cons128873, null, HandleForRepresentation2); 59 | } 60 | else 61 | { 62 | _mailbox.Send( 63 | new LocalMessage(_actor, cons128873, 64 | HandleForRepresentation2)); 65 | } 66 | } 67 | else 68 | { 69 | _actor.DeadLetters?.FailedDelivery(new DeadLetter(_actor, HandleForRepresentation2)); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/Resources.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Collections.ObjectModel; 11 | using Vlingo.Xoom.Actors; 12 | 13 | namespace Vlingo.Xoom.Http.Resource; 14 | 15 | public class Resources 16 | { 17 | private readonly IDictionary _namedResources; 18 | 19 | public static Resources Are(params IResource[] resources) 20 | { 21 | var all = new Resources(); 22 | foreach(var resource in resources) 23 | { 24 | all._namedResources[resource.Name] = resource; 25 | } 26 | 27 | return all; 28 | } 29 | 30 | private Resources() 31 | { 32 | _namedResources = new Dictionary(); 33 | } 34 | 35 | internal Resources(IDictionary namedResource) 36 | { 37 | _namedResources = new ReadOnlyDictionary(namedResource); 38 | } 39 | 40 | internal Resources(Resource resource) 41 | { 42 | _namedResources = new Dictionary(); 43 | _namedResources[resource.Name] = resource; 44 | } 45 | 46 | public IResource ResourceOf(string name) => _namedResources[name]; 47 | 48 | public IEnumerable ResourceHandlers => _namedResources.Values; 49 | 50 | public IDictionary NamedResources => _namedResources; 51 | 52 | public override string ToString() 53 | => $"Resources[namedResource={_namedResources}]"; 54 | 55 | internal void DispatchMatching(Context context, ILogger logger) 56 | { 57 | string message; 58 | 59 | try 60 | { 61 | foreach (var resource in _namedResources.Values) 62 | { 63 | var matchResults = resource.MatchWith(context.Request?.Method, context.Request?.Uri); 64 | if (matchResults.IsMatched) 65 | { 66 | var mappedParameters = matchResults.Action?.Map(context.Request, matchResults.Parameters); 67 | resource.DispatchToHandlerWith(context, mappedParameters); 68 | return; 69 | } 70 | } 71 | message = $"No matching resource for method {context.Request?.Method} and Uri {context.Request?.Uri}"; 72 | logger.Warn(message); 73 | } 74 | catch (Exception e) 75 | { 76 | message = $"Problem dispatching request for method {context.Request?.Method} and Uri {context.Request?.Uri} because: {e.Message}"; 77 | logger.Error(message, e); 78 | } 79 | 80 | context.Completes.With(Response.Of(ResponseStatus.NotFound, message)); 81 | } 82 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/RoundRobinClientRequestConsumerActor.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | using Vlingo.Xoom.Common; 11 | using Vlingo.Xoom.Wire.Message; 12 | 13 | namespace Vlingo.Xoom.Http.Resource; 14 | 15 | /// 16 | /// Round-robin router of requests. 17 | /// 18 | public class RoundRobinClientRequestConsumerActor : RoundRobinRouter, IClientConsumer 19 | { 20 | private const string ErrorMessage = "RoundRobinClientRequestConsumerActor: Should not be reached. Message: "; 21 | 22 | /// 23 | /// Constructs my default state. 24 | /// 25 | /// The 26 | /// The 27 | /// When the router cannot be initialized 28 | public RoundRobinClientRequestConsumerActor(Client.Configuration configuration, RouterSpecification specification) : base(specification) 29 | { 30 | } 31 | 32 | public void Consume(IConsumerByteBuffer buffer) 33 | { 34 | const string message = ErrorMessage + "Consume()"; 35 | Logger.Error(message, new InvalidOperationException(message)); 36 | } 37 | 38 | public void IntervalSignal(IScheduled scheduled, object data) 39 | { 40 | const string message = ErrorMessage + "IntervalSignal()"; 41 | Logger.Error(message, new InvalidOperationException(message)); 42 | } 43 | 44 | public ICompletes RequestWith(Request request, ICompletes completes) 45 | { 46 | DispatchCommand((consumer, r, c) => consumer.RequestWith(r, c), request, completes); 47 | return completes; 48 | } 49 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/SinglePageApplicationConfiguration.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | namespace Vlingo.Xoom.Http.Resource; 9 | 10 | public class SinglePageApplicationConfiguration 11 | { 12 | private readonly string _rootPath; 13 | private readonly string _contextPath; 14 | 15 | private SinglePageApplicationConfiguration() : this("/frontend", "/app") 16 | { 17 | } 18 | 19 | private SinglePageApplicationConfiguration(string rootPath, string contextPath) 20 | { 21 | _rootPath = rootPath; 22 | _contextPath = contextPath; 23 | } 24 | 25 | public string RootPath => _rootPath; 26 | 27 | public string ContextPath => _contextPath; 28 | 29 | public static SinglePageApplicationConfiguration Define() => new SinglePageApplicationConfiguration(); 30 | 31 | public static SinglePageApplicationConfiguration DefineWith(string rootPath, string contextPath) => 32 | new SinglePageApplicationConfiguration(rootPath, contextPath); 33 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/Sse/ISseFeed.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System.Collections.Generic; 9 | 10 | namespace Vlingo.Xoom.Http.Resource.Sse; 11 | 12 | public interface ISseFeed 13 | { 14 | void To(ICollection subscribers); 15 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/Sse/ISsePublisher.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using Vlingo.Xoom.Actors; 9 | 10 | namespace Vlingo.Xoom.Http.Resource.Sse; 11 | 12 | public interface ISsePublisher : IStoppable 13 | { 14 | void Subscribe(SseSubscriber subscriber); 15 | void Unsubscribe(SseSubscriber subscriber); 16 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/Sse/SseClient.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using Vlingo.Xoom.Wire.Channel; 12 | using Vlingo.Xoom.Wire.Message; 13 | 14 | namespace Vlingo.Xoom.Http.Resource.Sse; 15 | 16 | public class SseClient 17 | { 18 | private static readonly Headers Headers; 19 | 20 | static SseClient() 21 | { 22 | var cacheControl = ResponseHeader.Of(ResponseHeader.CacheControl, "no-cache"); 23 | var connection = ResponseHeader.Of(ResponseHeader.Connection, "keep-alive"); 24 | var contentType = ResponseHeader.Of(ResponseHeader.ContentType, "text/event-stream;charset=utf-8"); 25 | Headers = Http.Headers.Empty().And(connection).And(contentType).And(cacheControl); 26 | } 27 | 28 | private readonly StringBuilder _builder; 29 | private readonly RequestResponseContext? _context; 30 | private readonly int _maxMessageSize; 31 | 32 | public SseClient(RequestResponseContext? context) 33 | { 34 | _context = context; 35 | _builder = new StringBuilder(); 36 | _maxMessageSize = Configuration.Instance != null ? Configuration.Instance.Sizing.MaxMessageSize : 65535; 37 | 38 | SendInitialResponse(); 39 | } 40 | 41 | public void Close() => _context?.Abandon(); 42 | 43 | public string? Id => _context?.Id; 44 | 45 | public void Send(SseEvent @event) => Send(@event.Sendable()); 46 | 47 | public void Send(params SseEvent[] events) => Send(events.ToList()); 48 | 49 | public void Send(IEnumerable events) 50 | { 51 | var entity = Flatten(events); 52 | Send(entity); 53 | } 54 | 55 | private void Send(string entity) 56 | { 57 | var buffer = BasicConsumerByteBuffer.Allocate(1, _maxMessageSize); 58 | _context?.RespondWith(buffer.Put(Encoding.UTF8.GetBytes(entity)).Flip()); 59 | } 60 | 61 | private void SendInitialResponse() 62 | { 63 | try 64 | { 65 | var response = Response.Of(ResponseStatus.Ok, Headers.Copy()); 66 | var buffer = BasicConsumerByteBuffer.Allocate(1, _maxMessageSize); 67 | _context?.RespondWith(response.Into(buffer)); 68 | } 69 | catch 70 | { 71 | // it's possible that I am being used for an unsubscribe 72 | // where the client has already disconnected and this 73 | // attempt will fail; ignore it and return. 74 | } 75 | } 76 | 77 | private string Flatten(IEnumerable events) 78 | { 79 | _builder.Clear(); 80 | 81 | foreach (var @event in events) 82 | { 83 | _builder.Append(@event.Sendable()); 84 | } 85 | 86 | return _builder.ToString(); 87 | } 88 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/Sse/SseFeed__Proxy.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using Vlingo.Xoom.Actors; 10 | 11 | namespace Vlingo.Xoom.Http.Resource.Sse; 12 | 13 | public class SseFeed__Proxy : ISseFeed 14 | { 15 | private const string ToRepresentation = "To(ICollection)"; 16 | 17 | private readonly Actor _actor; 18 | private readonly IMailbox _mailbox; 19 | 20 | public SseFeed__Proxy(Actor actor, IMailbox mailbox) 21 | { 22 | _actor = actor; 23 | _mailbox = mailbox; 24 | } 25 | 26 | public void To(System.Collections.Generic.ICollection subscribers) 27 | { 28 | if (!_actor.IsStopped) 29 | { 30 | Action consumer = actor => actor.To(subscribers); 31 | if (_mailbox.IsPreallocated) 32 | { 33 | _mailbox.Send(_actor, consumer, null, ToRepresentation); 34 | } 35 | else 36 | { 37 | _mailbox.Send(new LocalMessage(_actor, consumer, ToRepresentation)); 38 | } 39 | } 40 | else 41 | { 42 | _actor.DeadLetters?.FailedDelivery(new DeadLetter(_actor, ToRepresentation)); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/Sse/SseStreamResourceDispatcher.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | 11 | namespace Vlingo.Xoom.Http.Resource.Sse; 12 | 13 | public class SseStreamResourceDispatcher : ConfigurationResource 14 | { 15 | public SseStreamResourceDispatcher( 16 | string name, 17 | Type resourceHandlerClass, 18 | int handlerPoolSize, 19 | IList actions) 20 | : base(name, resourceHandlerClass, handlerPoolSize, actions) 21 | { 22 | } 23 | 24 | public override void DispatchToHandlerWith(Context context, Action.MappedParameters? mappedParameters) 25 | { 26 | try 27 | { 28 | Action consumer; 29 | switch (mappedParameters?.ActionId) 30 | { 31 | case 0: // GET /eventstreams/{streamName} 32 | consumer = handler => handler.SubscribeToStream( 33 | (string)mappedParameters.Mapped[0].Value!, 34 | (Type)mappedParameters.Mapped[1].Value!, 35 | (int)mappedParameters.Mapped[2].Value!, 36 | (int)mappedParameters.Mapped[3].Value!, 37 | (string)mappedParameters.Mapped[4].Value!); 38 | 39 | PooledHandler.HandleFor(context, consumer); 40 | break; 41 | 42 | case 1: // DELETE /eventstreams/{streamName}/{id} 43 | consumer = handler => handler.UnsubscribeFromStream( 44 | (string)mappedParameters.Mapped[0].Value!, 45 | (string)mappedParameters.Mapped[1].Value!); 46 | 47 | PooledHandler.HandleFor(context, consumer); 48 | break; 49 | } 50 | } 51 | catch(Exception ex) 52 | { 53 | throw new ArgumentException($"Action mismatch: Request: {context.Request} Parameters: {mappedParameters}", ex); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/Sse/SseSubscriber.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System.Text; 9 | 10 | namespace Vlingo.Xoom.Http.Resource.Sse; 11 | 12 | public class SseSubscriber 13 | { 14 | public SseClient? Client { get; } 15 | public string? CorrelationId { get; } 16 | public string? CurrentEventId { get; set; } 17 | public string? StreamName { get; } 18 | 19 | public SseSubscriber(string? streamName, SseClient client, string? correlationId, string? lastEventId) 20 | { 21 | StreamName = streamName; 22 | Client = client; 23 | CorrelationId = correlationId; 24 | CurrentEventId = lastEventId; 25 | } 26 | 27 | public SseSubscriber(string streamName, SseClient client) 28 | : this(streamName, client, string.Empty, string.Empty) 29 | { 30 | } 31 | 32 | public void Close() => Client?.Close(); 33 | 34 | public bool IsCompatibleWith(string streamName) => string.Equals(StreamName, streamName); 35 | 36 | public bool HasCorrelationId => !string.IsNullOrEmpty(CorrelationId); 37 | 38 | public bool HasCurrentEventId => !string.IsNullOrEmpty(CurrentEventId); 39 | 40 | public string? Id => Client?.Id; 41 | 42 | public override string ToString() 43 | { 44 | var sb = new StringBuilder("SseSubscriber ["); 45 | sb.Append("stream='").Append(StreamName).Append('\''); 46 | if (HasCorrelationId) 47 | { 48 | sb.Append(", correlationId='").Append(CorrelationId).Append('\''); 49 | } 50 | if (HasCurrentEventId) 51 | { 52 | sb.Append(", currentEventId='").Append(CurrentEventId).Append('\''); 53 | } 54 | if (Client?.Id != null) 55 | { 56 | sb.Append(", client=").Append(Client.Id); 57 | } 58 | sb.Append(']'); 59 | return sb.ToString(); 60 | } 61 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/StaticFilesResourceDispatcher.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | 11 | namespace Vlingo.Xoom.Http.Resource; 12 | 13 | public class StaticFilesResourceDispatcher : ConfigurationResource 14 | { 15 | public StaticFilesResourceDispatcher( 16 | string name, 17 | Type resourceHandlerClass, 18 | int handlerPoolSize, 19 | IList actions) 20 | : base(name, resourceHandlerClass, handlerPoolSize, actions) 21 | { 22 | } 23 | 24 | public override void DispatchToHandlerWith(Context context, Action.MappedParameters? mappedParameters) 25 | { 26 | try 27 | { 28 | switch (mappedParameters?.ActionId) 29 | { 30 | case 0: // GET %root%{path} ServeFile(string root, string paths, string contentFilePath) 31 | if (mappedParameters.Mapped.Count == 3) 32 | { 33 | PooledHandler.HandleFor(context, handler => handler.ServeFile((string) mappedParameters.Mapped[0].Value!, (string) mappedParameters.Mapped[1].Value!, (string) mappedParameters.Mapped[2].Value!)); 34 | } 35 | else 36 | { 37 | PooledHandler.HandleFor(context, handler => handler.ServeFile(string.Empty, (string) mappedParameters.Mapped[0].Value!, (string) mappedParameters.Mapped[1].Value!)); 38 | 39 | } 40 | break; 41 | } 42 | } 43 | catch (Exception) 44 | { 45 | throw new ArgumentException($"Action mismatch: Request: {context.Request} Parameters: {mappedParameters}"); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Resource/TypeLoader.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | using System.Linq; 10 | 11 | namespace Vlingo.Xoom.Http.Resource; 12 | 13 | public static class TypeLoader 14 | { 15 | public static Type Load(string? className) 16 | { 17 | if (string.IsNullOrEmpty(className)) 18 | { 19 | throw new ArgumentNullException(nameof(className), "Cannot load type for empty type name"); 20 | } 21 | 22 | var classType = Type.GetType(className); 23 | if (classType == null) 24 | { 25 | // tires to load type with assembly name 26 | var classNameParts = className!.Split('.'); 27 | for (var i = 0; i < classNameParts.Length; i++) 28 | { 29 | var potentialAssemblyName = string.Join("." ,classNameParts.Take(i + 1)); 30 | var fullyQualifiedTypeName = $"{className}, {potentialAssemblyName}"; 31 | classType = Type.GetType(fullyQualifiedTypeName); 32 | if (classType != null) 33 | { 34 | break; 35 | } 36 | } 37 | } 38 | 39 | if (classType == null) 40 | { 41 | throw new InvalidOperationException($"Cannot load class for: {className}"); 42 | } 43 | 44 | return classType; 45 | } 46 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/ResponseFilter.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | namespace Vlingo.Xoom.Http; 9 | 10 | /// 11 | /// A for handling. 12 | /// 13 | public abstract class ResponseFilter : Filter 14 | { 15 | /// 16 | /// Answer the to be propagated forward to the next 17 | /// or as the final , and a bool indicating whether or not the 18 | /// chain should continue or be short circuited. If the bool is true, the chain 19 | /// will continue; if false, it will be short circuited. 20 | /// 21 | /// The to filter 22 | /// A pair of (Response, bool) 23 | public abstract (Response, bool) Filter(Response response); 24 | 25 | public abstract (Response, bool) Filter(Request request, Response response); 26 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/UrlFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | 10 | namespace Vlingo.Xoom.Http; 11 | 12 | public static class UrlFactory 13 | { 14 | public static Uri ToMatchableUri(this string uri) 15 | { 16 | var pathAndQuery = uri.Contains("?") 17 | ? uri.Split('?') 18 | : new []{ uri }; 19 | return pathAndQuery.Length == 1 20 | ? new UriBuilder("http", "localhost", 80, pathAndQuery[0]).Uri 21 | : new UriBuilder("http", "localhost", 80, pathAndQuery[0], $"?{pathAndQuery[1]}").Uri; 22 | } 23 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Version.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2012-2023 VLINGO LABS. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the 4 | // Mozilla Public License, v. 2.0. If a copy of the MPL 5 | // was not distributed with this file, You can obtain 6 | // one at https://mozilla.org/MPL/2.0/. 7 | 8 | using System; 9 | 10 | namespace Vlingo.Xoom.Http; 11 | 12 | public class Version 13 | { 14 | internal const string HTTP_1_1 = "HTTP/1.1"; 15 | private const string HTTP_2_0 = "HTTP/2.0"; 16 | 17 | private readonly string _version; 18 | private Version(string version) 19 | { 20 | _version = version; 21 | } 22 | 23 | public static Version Http1_1 { get; } = new Version(HTTP_1_1); 24 | 25 | public static Version Http2_0 { get; } = new Version(HTTP_2_0); 26 | 27 | public static Version From(string version) 28 | { 29 | if(string.Equals(version, HTTP_1_1, StringComparison.InvariantCultureIgnoreCase)) 30 | { 31 | return Http1_1; 32 | } 33 | else if(string.Equals(version, HTTP_2_0, StringComparison.InvariantCultureIgnoreCase)) 34 | { 35 | return Http2_0; 36 | } 37 | 38 | throw new ArgumentException($"Unsupported HTTP/version: {version}"); 39 | } 40 | 41 | public bool IsHttp1_1() => string.Equals(_version, HTTP_1_1); 42 | 43 | public bool IsHttp2_0() => string.Equals(_version, HTTP_2_0); 44 | 45 | public override string ToString() => _version; 46 | } -------------------------------------------------------------------------------- /src/Vlingo.Xoom.Http/Vlingo.Xoom.Http.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | latest 6 | enable 7 | 8 | 9 | true 10 | $(VlingoVersion) 11 | Vlingo.Xoom.Http 12 | Vlingo 13 | 14 | Reactive, scalable, and resilient HTTP servers and RESTful services running on vlingo-net/cluster and vlingo-net/actors. 15 | 16 | false 17 | LICENSE 18 | https://github.com/vlingo-net/xoom-net-http 19 | vlingo-64x64.png 20 | https://github.com/vlingo-net/xoom-net-http 21 | Debug;Release;Debug With Project References 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | <_Parameter1>$(MSBuildProjectName).Tests 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /vlingo-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vlingo-net/xoom-net-http/9b7c2264cc9d9c8754641fff896218797c2a97e6/vlingo-64x64.png --------------------------------------------------------------------------------