diff --git a/AS1024.NetworkQuality.Server.NetCore31/AS1024.NetworkQuality.Server.NetCore31.csproj b/AS1024.NetworkQuality.Server.NetCore31/AS1024.NetworkQuality.Server.NetCore31.csproj
new file mode 100644
index 0000000..1262eb3
--- /dev/null
+++ b/AS1024.NetworkQuality.Server.NetCore31/AS1024.NetworkQuality.Server.NetCore31.csproj
@@ -0,0 +1,12 @@
+
+
+
+ netcoreapp3.1
+
+
+
+
+
+
+
+
diff --git a/AS1024.NetworkQuality.Server.NetCore31/Controllers/NetworkQualityController.cs b/AS1024.NetworkQuality.Server.NetCore31/Controllers/NetworkQualityController.cs
new file mode 100644
index 0000000..1adbb5a
--- /dev/null
+++ b/AS1024.NetworkQuality.Server.NetCore31/Controllers/NetworkQualityController.cs
@@ -0,0 +1,87 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using System;
+using System.IO;
+using System.Text.Json;
+using System.Threading.Tasks;
+using AS1024.NetworkQuality.Server;
+
+namespace YourNamespace.Controllers
+{
+ [ApiController]
+ [Route("api/v1")]
+ public class NetworkQualityController : ControllerBase
+ {
+ private readonly ILogger _logger;
+ private readonly JsonSerializerOptions _jsonOptions;
+
+ public NetworkQualityController(ILogger logger)
+ {
+ _logger = logger;
+ _jsonOptions = new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = new SnakeCaseNamingPolicy(), // Use snake_case naming policy
+ PropertyNameCaseInsensitive = true // Optional: case insensitive deserialization
+ };
+ }
+
+ // GET: /api/v1/config
+ [HttpGet("config")]
+ public IActionResult GetConfig()
+ {
+ var config = new ConfigResponse
+ {
+ Urls = new Urls
+ {
+ SmallDownloadUrl = Url.Action("Small", "NetworkQuality", null, Request.Scheme),
+ LargeDownloadUrl = Url.Action("Large", "NetworkQuality", null, Request.Scheme),
+ UploadUrl = Url.Action("Upload", "NetworkQuality", null, Request.Scheme)
+ }
+ };
+
+ var json = JsonSerializer.Serialize(config, _jsonOptions); // Serialize with snake_case
+ return Content(json, "application/json");
+ }
+
+ // GET: /api/v1/small
+ [HttpGet("small")]
+ public IActionResult Small()
+ {
+ // Return 1 byte (content is irrelevant)
+ Response.ContentType = "application/octet-stream";
+ return File(new byte[1], "application/octet-stream");
+ }
+
+ // GET: /api/v1/large
+ [HttpGet("large")]
+ public IActionResult Large()
+ {
+ try
+ {
+ var largeStream = new LargeStreamResult();
+ return new FileStreamResult(largeStream, "application/octet-stream");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "An error occurred while writing the large file content.");
+ return StatusCode(500, JsonSerializer.Serialize(new { error = "An unexpected error occurred." }, _jsonOptions));
+ }
+ }
+
+ // POST: /api/v1/upload
+ [HttpPost("upload")]
+ public async Task Upload()
+ {
+ try
+ {
+ await Request.Body.CopyToAsync(Stream.Null);
+ return Ok();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "An unexpected error occurred.");
+ return StatusCode(500, JsonSerializer.Serialize(new { error = "An unexpected error occurred." }, _jsonOptions));
+ }
+ }
+ }
+}
diff --git a/AS1024.NetworkQuality.Server.NetCore31/Models/ResponseModel.cs b/AS1024.NetworkQuality.Server.NetCore31/Models/ResponseModel.cs
new file mode 100644
index 0000000..d913d23
--- /dev/null
+++ b/AS1024.NetworkQuality.Server.NetCore31/Models/ResponseModel.cs
@@ -0,0 +1,18 @@
+namespace AS1024.NetworkQuality.Server
+{
+ public class ConfigResponse
+ {
+ public int Version => 1;
+ public Urls Urls { get; set; }
+ }
+
+ public class Urls
+ {
+ public string SmallHttpsDownloadUrl => SmallDownloadUrl;
+ public string LargeHttpsDownloadUrl => LargeDownloadUrl;
+ public string HttpsUploadUrl => UploadUrl;
+ public string SmallDownloadUrl { get; set; }
+ public string LargeDownloadUrl { get; set; }
+ public string UploadUrl { get; set; }
+ }
+}
diff --git a/AS1024.NetworkQuality.Server.NetCore31/Program.cs b/AS1024.NetworkQuality.Server.NetCore31/Program.cs
new file mode 100644
index 0000000..d152040
--- /dev/null
+++ b/AS1024.NetworkQuality.Server.NetCore31/Program.cs
@@ -0,0 +1,31 @@
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace AS1024.NetworkQuality.Server.NetCore31
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ CreateHostBuilder(args).Build().Run();
+ }
+
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder.UseStartup();
+ webBuilder.UseKestrel(options =>
+ {
+ options.Limits.MaxRequestBodySize = long.MaxValue;
+ options.Limits.MaxRequestBufferSize = long.MaxValue;
+ });
+ });
+ }
+}
diff --git a/AS1024.NetworkQuality.Server.NetCore31/Properties/launchSettings.json b/AS1024.NetworkQuality.Server.NetCore31/Properties/launchSettings.json
new file mode 100644
index 0000000..f40b3b3
--- /dev/null
+++ b/AS1024.NetworkQuality.Server.NetCore31/Properties/launchSettings.json
@@ -0,0 +1,30 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:13105",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "AS1024.NetworkQuality.Server.NetCore31": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "",
+ "applicationUrl": "https://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/AS1024.NetworkQuality.Server.NetCore31/Simulators/LargeStreamResult.cs b/AS1024.NetworkQuality.Server.NetCore31/Simulators/LargeStreamResult.cs
new file mode 100644
index 0000000..0c171d4
--- /dev/null
+++ b/AS1024.NetworkQuality.Server.NetCore31/Simulators/LargeStreamResult.cs
@@ -0,0 +1,47 @@
+using System;
+using System.IO;
+
+internal class LargeStreamResult : Stream
+{
+ private long _position = 0;
+ private const long _length = 8L * 1024L * 1024L * 1024L; // 8 GB
+
+ public override bool CanRead => true;
+ public override bool CanSeek => true;
+ public override bool CanWrite => false;
+ public override long Length => _length;
+ public override long Position { get => _position; set => _position = value; }
+
+ public override void Flush() { }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ int bytesToRead = (int)Math.Min(count, _length - _position);
+ if (bytesToRead <= 0)
+ return 0;
+
+ _position += bytesToRead;
+ return bytesToRead;
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ switch (origin)
+ {
+ case SeekOrigin.Begin:
+ _position = offset;
+ break;
+ case SeekOrigin.Current:
+ _position += offset;
+ break;
+ case SeekOrigin.End:
+ _position = _length + offset;
+ break;
+ }
+ return _position;
+ }
+
+ public override void SetLength(long value) { }
+
+ public override void Write(byte[] buffer, int offset, int count) { }
+}
diff --git a/AS1024.NetworkQuality.Server.NetCore31/Startup.cs b/AS1024.NetworkQuality.Server.NetCore31/Startup.cs
new file mode 100644
index 0000000..829f628
--- /dev/null
+++ b/AS1024.NetworkQuality.Server.NetCore31/Startup.cs
@@ -0,0 +1,48 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace AS1024.NetworkQuality.Server.NetCore31
+{
+ public class Startup
+ {
+ public Startup(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public IConfiguration Configuration { get; }
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddControllers();
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+
+ app.UseRouting();
+
+ app.UseAuthorization();
+
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapControllers();
+ });
+ }
+ }
+}
diff --git a/AS1024.NetworkQuality.Server.NetCore31/Utils/SnakeCaseNamingPolicy.cs b/AS1024.NetworkQuality.Server.NetCore31/Utils/SnakeCaseNamingPolicy.cs
new file mode 100644
index 0000000..f0529f4
--- /dev/null
+++ b/AS1024.NetworkQuality.Server.NetCore31/Utils/SnakeCaseNamingPolicy.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Text.Json;
+
+public class SnakeCaseNamingPolicy : JsonNamingPolicy
+{
+ public override string ConvertName(string name)
+ {
+ return string.IsNullOrEmpty(name) ? name : ToSnakeCase(name);
+ }
+
+ private string ToSnakeCase(string name)
+ {
+ var stringBuilder = new System.Text.StringBuilder();
+ for (var i = 0; i < name.Length; i++)
+ {
+ if (char.IsUpper(name[i]))
+ {
+ if (i > 0)
+ {
+ stringBuilder.Append('_');
+ }
+ stringBuilder.Append(char.ToLowerInvariant(name[i]));
+ }
+ else
+ {
+ stringBuilder.Append(name[i]);
+ }
+ }
+ return stringBuilder.ToString();
+ }
+}
diff --git a/AS1024.NetworkQuality.Server.NetCore31/appsettings.Development.json b/AS1024.NetworkQuality.Server.NetCore31/appsettings.Development.json
new file mode 100644
index 0000000..8983e0f
--- /dev/null
+++ b/AS1024.NetworkQuality.Server.NetCore31/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ }
+}
diff --git a/AS1024.NetworkQuality.Server.NetCore31/appsettings.json b/AS1024.NetworkQuality.Server.NetCore31/appsettings.json
new file mode 100644
index 0000000..d9d9a9b
--- /dev/null
+++ b/AS1024.NetworkQuality.Server.NetCore31/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/AS1024.NetworkQuality.Server.sln b/AS1024.NetworkQuality.Server.sln
index e2a3f85..c67eaba 100644
--- a/AS1024.NetworkQuality.Server.sln
+++ b/AS1024.NetworkQuality.Server.sln
@@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34408.163
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AS1024.NetworkQuality.Server", "AS1024.NetworkQuality.Server\AS1024.NetworkQuality.Server.csproj", "{71ABE0E0-226A-46E1-8CC9-76CDD7F5506F}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AS1024.NetworkQuality.Server", "AS1024.NetworkQuality.Server\AS1024.NetworkQuality.Server.csproj", "{71ABE0E0-226A-46E1-8CC9-76CDD7F5506F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AS1024.NetworkQuality.Server.NetCore31", "AS1024.NetworkQuality.Server.NetCore31\AS1024.NetworkQuality.Server.NetCore31.csproj", "{FAC57638-C437-40D0-9A58-45054385FC78}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -15,6 +17,10 @@ Global
{71ABE0E0-226A-46E1-8CC9-76CDD7F5506F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{71ABE0E0-226A-46E1-8CC9-76CDD7F5506F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{71ABE0E0-226A-46E1-8CC9-76CDD7F5506F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FAC57638-C437-40D0-9A58-45054385FC78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FAC57638-C437-40D0-9A58-45054385FC78}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FAC57638-C437-40D0-9A58-45054385FC78}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FAC57638-C437-40D0-9A58-45054385FC78}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE