Skip to content

Commit fb6acef

Browse files
committed
Update dependencies and improve documentation.
1 parent a555b0f commit fb6acef

24 files changed

+322
-165
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ jobs:
4646
if-no-files-found: error
4747

4848
- name: Test
49-
#run: dotnet test Yllibed.HttpServer.sln /p:Configuration=Release /p:CollectCoverage=true /p:CoverletOutputFormat=opencover --no-build
50-
run: dotnet test Yllibed.HttpServer.sln /p:Configuration=Release --no-build
49+
run: dotnet test Yllibed.HttpServer.slnx /p:Configuration=Release --no-build
5150

5251
publish:
5352
if: startsWith(github.ref, 'refs/heads/master')

Directory.Build.props

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,40 @@
1414
<RepositoryUrl>https://github.com/carldebilly/Yllibed.HttpServer</RepositoryUrl>
1515
<RepositoryType>git</RepositoryType>
1616
<PackageLicenseExpression>MIT</PackageLicenseExpression>
17+
<PublishRepositoryUrl>true</PublishRepositoryUrl>
18+
<PackageProjectUrl>https://github.com/carldebilly/Yllibed.HttpServer</PackageProjectUrl>
19+
<PackageIcon Condition="Exists('Yllibed.png')">Yllibed.png</PackageIcon>
20+
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
21+
<PackageTags>http server lightweight self-contained sse iot desktop tools diagnostics</PackageTags>
22+
<IncludeSymbols>true</IncludeSymbols>
23+
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
1724

1825
<!-- General build properties -->
1926
<Nullable>enable</Nullable>
2027
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
2128
<DebugType>portable</DebugType>
2229
<DebugSymbols>true</DebugSymbols>
2330
<LangVersion>12</LangVersion>
31+
<Deterministic>true</Deterministic>
32+
<ContinuousIntegrationBuild Condition="'$(CI)'!=''">true</ContinuousIntegrationBuild>
33+
34+
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
35+
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
2436
</PropertyGroup>
37+
38+
<ItemGroup>
39+
<None Include="Yllibed-small.png" Pack="true" PackagePath="/" Condition="Exists('Yllibed-small.png')" />
40+
</ItemGroup>
2541

2642
<ItemGroup>
27-
<PackageReference Include="Nerdbank.GitVersioning" Version="3.6.146" PrivateAssets="all" />
28-
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0">
43+
<PackageReference Include="Nerdbank.GitVersioning" PrivateAssets="all" />
44+
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers">
2945
<PrivateAssets>all</PrivateAssets>
3046
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
3147
</PackageReference>
32-
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
33-
<PackageReference Include="DotNet.ReproducibleBuilds" Version="1.2.25" PrivateAssets="All" />
34-
<PackageReference Include="Meziantou.Analyzer" Version="2.0.180">
48+
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All"/>
49+
<PackageReference Include="DotNet.ReproducibleBuilds" PrivateAssets="All" />
50+
<PackageReference Include="Meziantou.Analyzer">
3551
<PrivateAssets>all</PrivateAssets>
3652
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
3753
</PackageReference>

Directory.Packages.props

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project>
2+
<ItemGroup>
3+
<PackageVersion Include="AwesomeAssertions" Version="9.1.0" />
4+
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
5+
<PackageVersion Include="coverlet.msbuild" Version="6.0.4" />
6+
<PackageVersion Include="DotNet.ReproducibleBuilds" Version="1.2.25" />
7+
<PackageVersion Include="Meziantou.Analyzer" Version="2.0.215" />
8+
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0" />
9+
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.8" />
10+
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
11+
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.7.115" />
12+
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
13+
<PackageVersion Include="PolySharp" Version="1.15.0" />
14+
<PackageVersion Include="System.Collections.Immutable" Version="9.0.8" />
15+
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="9.0.8" />
16+
<PackageVersion Include="System.ValueTuple" Version="4.6.1" />
17+
</ItemGroup>
18+
</Project>

README.md

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
# Yllibed HttpServer
22

3-
This is a versatile http server designed to be used in mobile/UWP applications and any applications which need to expose a simple web server.
3+
![Yllibed logo](Yllibed-small.png)
44

5-
## Packages and NuGet Statistics
5+
A small, self-contained HTTP server for desktop, mobile, and embedded apps that need to expose a simple web endpoint.
66

7-
| Package | Downloads | Stable Version | Pre-release Version |
8-
|-------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
9-
| [**HttpServer**](https://www.nuget.org/packages/Yllibed.HttpServer/) | ![Downloads](https://img.shields.io/nuget/dt/Yllibed.HttpServer?label=Downloads) | ![Stable](https://img.shields.io/nuget/v/Yllibed.HttpServer?label=Stable&labelColor=blue) | ![Pre-release](https://img.shields.io/nuget/vpre/Yllibed.HttpServer?label=Pre-release&labelColor=yellow) |
10-
| [**HttpServer.Json**](https://www.nuget.org/packages/Yllibed.HttpServer.Json/) | ![Downloads](https://img.shields.io/nuget/dt/Yllibed.HttpServer.Json?label=Downloads) | ![Stable](https://img.shields.io/nuget/v/Yllibed.HttpServer.Json?label=Stable&labelColor=blue) | ![Pre-release](https://img.shields.io/nuget/vpre/Yllibed.HttpServer.Json?label=Pre-release&labelColor=yellow) |
7+
- Lightweight, no ASP.NET dependency
8+
- Great for OAuth2 redirect URIs, diagnostics, and local tooling
9+
- IPv4/IPv6, HTTP/1.1, custom handlers, static files, and SSE
1110

12-
## Quick start-up
11+
---
12+
13+
## Packages and NuGet
14+
15+
| Package | Downloads | Stable | Pre-release |
16+
|---|---|---|---|
17+
| [Yllibed.HttpServer](https://www.nuget.org/packages/Yllibed.HttpServer/) | ![Downloads](https://img.shields.io/nuget/dt/Yllibed.HttpServer?label=downloads) | ![Stable](https://img.shields.io/nuget/v/Yllibed.HttpServer?label=stable) | ![Pre-release](https://img.shields.io/nuget/vpre/Yllibed.HttpServer?label=pre-release) |
18+
| [Yllibed.HttpServer.Json](https://www.nuget.org/packages/Yllibed.HttpServer.Json/) | ![Downloads](https://img.shields.io/nuget/dt/Yllibed.HttpServer.Json?label=downloads) | ![Stable](https://img.shields.io/nuget/v/Yllibed.HttpServer.Json?label=stable) | ![Pre-release](https://img.shields.io/nuget/vpre/Yllibed.HttpServer.Json?label=pre-release) |
19+
20+
---
21+
22+
## Quick start
1323

1424
1. First install nuget package:
1525
```shell
@@ -49,36 +59,44 @@ This is a versatile http server designed to be used in mobile/UWP applications a
4959
```
5060

5161
## What it is
52-
* Simple web server which can be extended using custom code
53-
* No dependencies on ASP.NET or other frameworks, self-contained
62+
63+
- Simple web server that can be extended with custom code
64+
- No dependencies on ASP.NET or other frameworks; fully self-contained
65+
- Intended for small apps and utilities (e.g., OAuth2 redirect URL from an external browser)
5466

5567
## What it is not
56-
* This HTTP server is not designed for performance or high capacity
57-
* It's perfect for small applications, or small need, like to act as _return url_ for OAuth2 authentication using external browser.
68+
69+
- NOT designed for high performance or high concurrency
70+
- NOT appropriate for public-facing web services
71+
- NOT a full-featured web framework (no MVC, no Razor, no routing, etc.)
72+
- NOT a replacement for ASP.NET Core or Kestrel
5873

5974
## Features
60-
* Simple, lightweight, self-contained HTTP server
61-
* Supports IPv4 and IPv6
62-
* Supports HTTP 1.1 (limited: no keep-alive, no chunked encoding)
63-
* Supports GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE, PATCH - even custom methods
64-
* Supports static files
65-
* Supports custom headers
66-
* Supports custom status codes
67-
* Supports custom content types
68-
* Supports custom content encodings
69-
* Supports dependency injection and configuration via `IOptions<ServerOptions>`
70-
* Configurable bind addresses and hostnames for IPv4/IPv6
71-
* Supports dynamic port assignment
75+
76+
- Simple, lightweight, self-contained HTTP server
77+
- Supports IPv4 and IPv6
78+
- Supports HTTP 1.1 (limited: no keep-alive, no chunked encoding)
79+
- Allows any HTTP method (GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE, PATCH, custom). Handlers decide how to handle them.
80+
- Simple static responses via StaticHandler (no built-in file/directory serving)
81+
- Supports custom headers
82+
- Supports custom status codes
83+
- Supports custom content types
84+
- Arbitrary response headers (incl. Content-Encoding); no automatic compression/encoding
85+
- Supports dependency injection and configuration via `IOptions<ServerOptions>`
86+
- Configurable bind addresses and hostnames for IPv4/IPv6
87+
- Supports dynamic port assignment
7288

7389
## Common use cases
74-
* Return URL for OAuth2 authentication using external browser
75-
* Remote diagnostics/monitoring on your app
76-
* Building a headless Windows IoT app (for SSDP discovery or simply end-user configuration)
77-
* Any other use case where you need to expose a simple web server
90+
91+
- Return URL for OAuth2 authentication using external browser
92+
- Remote diagnostics/monitoring on your app
93+
- Building a headless Windows IoT app (for SSDP discovery or simply end-user configuration)
94+
- Any other use case where you need to expose a simple web server
7895

7996
## Limitations
80-
* There is no support for HTTP 2.0+ (yet) or WebSockets
81-
* There is no support for HTTPS (TLS)
97+
98+
- There is no support for HTTP/2+ (yet) or WebSockets
99+
- There is no support for HTTPS (TLS)
82100

83101
## Security and Intended Use (No TLS)
84102
This server uses plain HTTP with no transport encryption. It is primarily intended for:
@@ -312,11 +330,11 @@ public sealed class SseDemoHandler : IHttpHandler
312330

313331
// Usage during startup
314332
var server = new Server();
315-
var ssePath = new RelativePathHandler("/");
333+
var ssePath = new RelativePathHandler("/updates");
316334
ssePath.RegisterHandler(new SseDemoHandler());
317335
server.RegisterHandler(ssePath);
318336
var (uri4, _) = server.Start();
319-
Console.WriteLine($"SSE endpoint: {uri4}/sse");
337+
Console.WriteLine($"SSE endpoint: {uri4}/updates/sse");
320338
```
321339

322340
SseHandler convenience base class:
@@ -348,14 +366,14 @@ Client-side (browser):
348366
```
349367

350368
Notes:
351-
- Heartbeats: send a comment frame (": keepalive\n\n") every 15–30s to prevent proxy timeouts.
369+
- Heartbeats: send a comment frame (`: keepalive\n\n`) every 15–30s to prevent proxy timeouts.
352370
- Long-running streams: handle CancellationToken to stop cleanly when the client disconnects.
353371
- Browser connection limits: most browsers cap concurrent HTTP connections per hostname (often 6–15). Without HTTP/2 multiplexing, a single client cannot keep many SSE connections in parallel; this server is not intended for a large number of per-client connections.
354372
- Public exposure: there is no TLS; prefer localhost or internal networks, or place behind a TLS-terminating reverse proxy.
355373

356374

357375
### SSE Spec and Interop Notes
358-
- Accept negotiation: If a client sends an Accept header that explicitly excludes SSE (text/event-stream), the default SseHandler will reply 406 Not Acceptable. The following values are considered acceptable: text/event-stream, text/*, or */*. If no Accept header is present, requests are accepted. You can override this behavior by overriding ShouldHandle in your handler.
376+
- Accept negotiation: If a client sends an Accept header that explicitly excludes SSE (text/event-stream), the default SseHandler will reply 406 Not Acceptable. The following values are considered acceptable: text/event-stream, text/*, or */*. If no Accept header is present, requests are accepted. You can override this behavior by overriding ValidateHeaders in your handler (ShouldHandle is for method/path filtering).
359377
- Last-Event-ID: When a client reconnects, browsers may send a Last-Event-ID header. It is exposed via ISseSession.LastEventId so you can resume from the last delivered event. Set the id parameter in SendEventAsync to help clients keep position.
360378
- Heartbeats: You can configure periodic comment frames via SseOptions.HeartBeatInterval; this keeps intermediaries from timing out idle connections.
361379
- Framing: The server uses CRLF (\r\n) in headers and LF (\n) in the SSE body as recommended by typical SSE implementations. Data payloads are normalized to LF before framing each data: line. Each event ends with a blank line.

Yllibed-small.png

5.65 KB
Loading
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
global using AwesomeAssertions;
2+
global using Microsoft.VisualStudio.TestTools.UnitTesting;
13
global using System;
24
global using System.Net;
35
global using System.Net.Http;
46
global using System.Threading;
5-
global using FluentAssertions;
6-
global using Microsoft.VisualStudio.TestTools.UnitTesting;
77
global using Yllibed.HttpServer.Sse;

Yllibed.HttpServer.Json.Tests/Yllibed.HttpServer.Json.Tests.csproj

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,24 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="MSTest.Sdk/3.10.4">
22
<PropertyGroup>
3-
<TargetFramework>net9.0</TargetFramework>
3+
<TargetFrameworks>net9.0</TargetFrameworks>
44
<IsTestProject>true</IsTestProject>
5-
<GenerateProgramFile>false</GenerateProgramFile>
6-
<IsPublishable>False</IsPublishable>
7-
<IsPackable>false</IsPackable>
85
<ImplicitUsings>enable</ImplicitUsings>
96
</PropertyGroup>
107

118
<ItemGroup>
12-
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="9.0.0" />
13-
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
14-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
15-
<PackageReference Include="MSTest.TestAdapter" Version="3.6.3" />
16-
<PackageReference Include="MSTest.TestFramework" Version="3.6.3" />
17-
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="9.0.0" />
18-
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
19-
<PackageReference Include="FluentAssertions" Version="6.12.2" />
20-
<PackageReference Include="coverlet.msbuild" Version="6.0.2">
9+
<PackageReference Include="AwesomeAssertions" />
10+
<PackageReference Include="coverlet.collector">
2111
<PrivateAssets>all</PrivateAssets>
2212
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2313
</PackageReference>
24-
<PackageReference Include="coverlet.collector" Version="6.0.2">
14+
<PackageReference Include="coverlet.msbuild">
2515
<PrivateAssets>all</PrivateAssets>
2616
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2717
</PackageReference>
18+
<PackageReference Include="Microsoft.Extensions.Logging" />
19+
<PackageReference Include="System.Diagnostics.DiagnosticSource" />
20+
<PackageReference Include="System.ValueTuple" />
21+
<PackageReference Update="Microsoft.NET.Test.Sdk" VersionOverride="17.14.1" />
2822
</ItemGroup>
2923

3024
<ItemGroup>

Yllibed.HttpServer.Json/README.md

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,92 @@
1-
# Yllibed Http Server - JSON extension
1+
# Yllibed Http Server JSON Adapter
22

3-
This is a simple extension to the Yllibed.HttpServer which allows to serve JSON content, using Newtonsoft's Json.NET library.
3+
Helpers for building JSON endpoints and JSON SSE (Server‑Sent Events) with Yllibed.HttpServer, powered by Newtonsoft.Json.
4+
5+
## What it provides
6+
- JsonHandlerBase<TResult>: base class to implement JSON endpoints quickly (sets content-type, serializes response, handles errors)
7+
- Query string parsing helper built‑in to the base class (multi‑value aware)
8+
- JSON serialization using Newtonsoft.Json
9+
- SSE helpers: SendJsonAsync and SendJsonEventAsync to push compact JSON over text/event-stream
10+
11+
## Quick start – JSON endpoint
12+
Implement a small handler that returns an object. The adapter serializes it to application/json and writes the proper status code.
13+
14+
```csharp
15+
using Yllibed.HttpServer;
16+
using Yllibed.HttpServer.Json;
17+
18+
public sealed class MyResult
19+
{
20+
public string? A { get; set; }
21+
public string? B { get; set; }
22+
}
23+
24+
public sealed class MyHandler : JsonHandlerBase<MyResult>
25+
{
26+
public MyHandler() : base("GET", "/api/echo") { }
27+
28+
protected override async Task<(MyResult result, ushort statusCode)> ProcessRequest(
29+
CancellationToken ct,
30+
string relativePath,
31+
IDictionary<string, string[]> query)
32+
{
33+
await Task.Yield();
34+
query.TryGetValue("a", out var a);
35+
query.TryGetValue("b", out var b);
36+
return (new MyResult { A = a?.FirstOrDefault(), B = b?.FirstOrDefault() }, 200);
37+
}
38+
}
39+
40+
var server = new Server();
41+
server.RegisterHandler(new MyHandler());
42+
var (uri4, _) = server.Start();
43+
Console.WriteLine(uri4 + "api/echo?a=1&b=2");
44+
```
45+
46+
Response body:
47+
```json
48+
{
49+
"A": "1",
50+
"B": "2"
51+
}
52+
```
53+
54+
## Quick start – JSON over SSE
55+
Send JSON directly as SSE data using the extension methods.
56+
57+
```csharp
58+
using Yllibed.HttpServer.Sse;
59+
using Yllibed.HttpServer.Json;
60+
61+
public sealed class PricesSse : SseHandler
62+
{
63+
protected override bool ShouldHandle(IHttpServerRequest req, string path)
64+
=> base.ShouldHandle(req, path) && path == "/prices";
65+
66+
protected override async Task HandleSseSession(ISseSession sse, CancellationToken ct)
67+
{
68+
await sse.SendJsonEventAsync("tick", new { Bid = 1.2345, Ask = 1.2347 }, id: "1", ct: ct);
69+
}
70+
}
71+
```
72+
73+
The JSON is serialized compact (no whitespace) and placed in the SSE data field.
74+
75+
## Behavior and details
76+
- Content type: application/json for JsonHandlerBase responses
77+
- Serializer: Newtonsoft.Json with Formatting.Indented for HTTP responses; Formatting.None for SSE
78+
- Errors in your handler are caught and a 500 text/plain response is emitted. Log output uses Microsoft.Extensions.Logging via the server’s logger
79+
- Paths can be passed with or without leading slash; base class normalizes them
80+
- Method matching is case‑insensitive
81+
82+
## When to use
83+
- Build small JSON APIs without bringing a full web framework
84+
- Add real‑time JSON updates over SSE easily
85+
86+
## Package info
87+
- Package: Yllibed.HttpServer.Json
88+
- Depends on: Yllibed.HttpServer, Newtonsoft.Json
89+
- Targets: see solution TargetFrameworks
90+
91+
## See also
92+
For server setup, routing helpers, SSE basics, and DI, check the main project README: ../Yllibed.HttpServer/README.md or the repository root README.
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3+
<PackageId>Yllibed.HttpServer.Json</PackageId>
4+
<Title>Yllibed HttpServer Json Adapter</Title>
35
<Product>Yllibed HttpServer Json Adapter</Product>
4-
<Description>Json adapter for Yllibed Versatile Http Server using Newtownsoft JSON.NET</Description>
5-
6+
<Description>JSON adapter for Yllibed.HttpServer using Newtonsoft.Json</Description>
7+
<PackageTags>yllibed httpserver json newtonsoft adapter</PackageTags>
68
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
79
<PackageReadmeFile>README.md</PackageReadmeFile>
810
</PropertyGroup>
911

1012
<ItemGroup>
1113
<ProjectReference Include="..\Yllibed.HttpServer\Yllibed.HttpServer.csproj" />
12-
1314
<None Include="README.md" Pack="true" PackagePath="/" />
1415
</ItemGroup>
1516

1617
<ItemGroup>
17-
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
18+
<PackageReference Include="Newtonsoft.Json" />
1819
</ItemGroup>
1920

20-
</Project>
21+
</Project>

Yllibed.HttpServer.Tests/AcceptHeaderHelperFixture.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
using FluentAssertions;
2-
using Microsoft.VisualStudio.TestTools.UnitTesting;
3-
using Yllibed.HttpServer;
4-
using Yllibed.HttpServer.Extensions;
5-
61
namespace Yllibed.HttpServer.Tests;
72

83
[TestClass]
@@ -32,7 +27,7 @@ private sealed class FakeRequest : IHttpServerRequest
3227
public void SetStreamingResponse(string contentType, Func<TextWriter, CancellationToken, Task> writer, uint resultCode = 200, string resultText = "OK", IReadOnlyDictionary<string, IReadOnlyCollection<string>>? headers = null) => throw new NotSupportedException();
3328
}
3429

35-
[DataTestMethod]
30+
[TestMethod]
3631
// No Accept header means no constraint
3732
[DataRow(null, "text/html", true)]
3833
[DataRow("", "text/html", true)]

0 commit comments

Comments
 (0)