diff --git a/ElevenLabs-DotNet-Proxy/ElevenLabs-DotNet-Proxy.csproj b/ElevenLabs-DotNet-Proxy/ElevenLabs-DotNet-Proxy.csproj
index 87f53ab..5d2c963 100644
--- a/ElevenLabs-DotNet-Proxy/ElevenLabs-DotNet-Proxy.csproj
+++ b/ElevenLabs-DotNet-Proxy/ElevenLabs-DotNet-Proxy.csproj
@@ -15,9 +15,12 @@
ElevenLabs, AI, ML, API, api-proxy, proxy, gateway
ElevenLabs API Proxy
ElevenLabs-DotNet-Proxy
- 1.2.0
+ 2.2.1
ElevenLabs.Proxy
- Initial Release!
+ Version 2.2.1
+- Refactor with modern WebApplication builder
+- Added ElevenLabs.Proxy.EndpointRouteBuilder
+
True
false
Readme.md
diff --git a/ElevenLabs-DotNet-Proxy/Proxy/ElevenLabsProxyStartup.cs b/ElevenLabs-DotNet-Proxy/Proxy/ElevenLabsProxyStartup.cs
index 12a14e4..0b2a68e 100644
--- a/ElevenLabs-DotNet-Proxy/Proxy/ElevenLabsProxyStartup.cs
+++ b/ElevenLabs-DotNet-Proxy/Proxy/ElevenLabsProxyStartup.cs
@@ -6,15 +6,8 @@
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
-using Microsoft.Net.Http.Headers;
using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Net.Http;
-using System.Security.Authentication;
using System.Threading.Tasks;
-using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue;
namespace ElevenLabs.Proxy
{
@@ -26,37 +19,18 @@ public class ElevenLabsProxyStartup
private ElevenLabsClient elevenLabsClient;
private IAuthenticationFilter authenticationFilter;
- // Copied from https://github.com/microsoft/reverse-proxy/blob/51d797986b1fea03500a1ad173d13a1176fb5552/src/ReverseProxy/Forwarder/RequestUtilities.cs#L61-L83
- private static readonly HashSet excludedHeaders = new()
- {
- HeaderNames.Connection,
- HeaderNames.TransferEncoding,
- HeaderNames.KeepAlive,
- HeaderNames.Upgrade,
- "Proxy-Connection",
- "Proxy-Authenticate",
- "Proxy-Authentication-Info",
- "Proxy-Authorization",
- "Proxy-Features",
- "Proxy-Instruction",
- "Security-Scheme",
- "ALPN",
- "Close",
- HeaderNames.TE,
-#if NET
- HeaderNames.AltSvc,
-#else
- "Alt-Svc",
-#endif
- };
-
///
- /// Configures the and services.
+ /// Configures the and services.
///
///
public void ConfigureServices(IServiceCollection services)
=> SetupServices(services.BuildServiceProvider());
+ ///
+ /// Configures the to handle requests and forward them to OpenAI API.
+ ///
+ /// .
+ /// .
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
@@ -71,7 +45,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/health", HealthEndpoint);
- endpoints.Map($"{elevenLabsClient.ElevenLabsClientSettings.BaseRequest}{{**endpoint}}", HandleRequest);
+ endpoints.MapElevenLabsEndpoints(elevenLabsClient, authenticationFilter);
});
}
@@ -94,6 +68,12 @@ public static IHost CreateDefaultHost(string[] args, ElevenLabsClient elevenL
services.AddSingleton();
}).Build();
+ ///
+ /// Creates a new that acts as a proxy web api for OpenAI.
+ ///
+ /// type to use to validate your custom issued tokens.
+ /// Startup args.
+ /// with configured and .
public static WebApplication CreateWebApplication(string[] args, ElevenLabsClient elevenLabsClient) where T : class, IAuthenticationFilter
{
var builder = WebApplication.CreateBuilder(args);
@@ -130,75 +110,5 @@ private static async Task HealthEndpoint(HttpContext context)
const string content = "OK";
await context.Response.WriteAsync(content);
}
-
- ///
- /// Handles incoming requests, validates authentication, and forwards the request to ElevenLabs API
- ///
- private async Task HandleRequest(HttpContext httpContext, string endpoint)
- {
- try
- {
- // ReSharper disable once MethodHasAsyncOverload
- // just in case either method is implemented we call it twice.
- authenticationFilter.ValidateAuthentication(httpContext.Request.Headers);
- await authenticationFilter.ValidateAuthenticationAsync(httpContext.Request.Headers);
-
- var method = new HttpMethod(httpContext.Request.Method);
- var uri = new Uri(string.Format(elevenLabsClient.ElevenLabsClientSettings.BaseRequestUrlFormat, $"{endpoint}{httpContext.Request.QueryString}"));
- using var request = new HttpRequestMessage(method, uri);
-
- request.Content = new StreamContent(httpContext.Request.Body);
-
- if (httpContext.Request.ContentType != null)
- {
- request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(httpContext.Request.ContentType);
- }
-
- var proxyResponse = await elevenLabsClient.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
- httpContext.Response.StatusCode = (int)proxyResponse.StatusCode;
-
- foreach (var (key, value) in proxyResponse.Headers)
- {
- if (excludedHeaders.Contains(key)) { continue; }
- httpContext.Response.Headers[key] = value.ToArray();
- }
-
- foreach (var (key, value) in proxyResponse.Content.Headers)
- {
- if (excludedHeaders.Contains(key)) { continue; }
- httpContext.Response.Headers[key] = value.ToArray();
- }
-
- httpContext.Response.ContentType = proxyResponse.Content.Headers.ContentType?.ToString() ?? string.Empty;
- const string streamingContent = "text/event-stream";
-
- if (httpContext.Response.ContentType.Equals(streamingContent))
- {
- var stream = await proxyResponse.Content.ReadAsStreamAsync();
- await WriteServerStreamEventsAsync(httpContext, stream);
- }
- else
- {
- await proxyResponse.Content.CopyToAsync(httpContext.Response.Body);
- }
- }
- catch (AuthenticationException authenticationException)
- {
- httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
- await httpContext.Response.WriteAsync(authenticationException.Message);
- }
- catch (Exception e)
- {
- httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
- await httpContext.Response.WriteAsync(e.Message);
- }
- }
-
- private static async Task WriteServerStreamEventsAsync(HttpContext httpContext, Stream contentStream)
- {
- var responseStream = httpContext.Response.Body;
- await contentStream.CopyToAsync(responseStream, httpContext.RequestAborted);
- await responseStream.FlushAsync(httpContext.RequestAborted);
- }
}
}
diff --git a/ElevenLabs-DotNet-Proxy/Proxy/EndpointRouteBuilder.cs b/ElevenLabs-DotNet-Proxy/Proxy/EndpointRouteBuilder.cs
new file mode 100644
index 0000000..181f68d
--- /dev/null
+++ b/ElevenLabs-DotNet-Proxy/Proxy/EndpointRouteBuilder.cs
@@ -0,0 +1,129 @@
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Net.Http.Headers;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Security.Authentication;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+namespace ElevenLabs.Proxy
+{
+ public static class EndpointRouteBuilder
+ {
+ // Copied from https://github.com/microsoft/reverse-proxy/blob/51d797986b1fea03500a1ad173d13a1176fb5552/src/ReverseProxy/Forwarder/RequestUtilities.cs#L61-L83
+ private static readonly HashSet excludedHeaders = new()
+ {
+ HeaderNames.Connection,
+ HeaderNames.TransferEncoding,
+ HeaderNames.KeepAlive,
+ HeaderNames.Upgrade,
+ "Proxy-Connection",
+ "Proxy-Authenticate",
+ "Proxy-Authentication-Info",
+ "Proxy-Authorization",
+ "Proxy-Features",
+ "Proxy-Instruction",
+ "Security-Scheme",
+ "ALPN",
+ "Close",
+ "Set-Cookie",
+ HeaderNames.TE,
+#if NET
+ HeaderNames.AltSvc,
+#else
+ "Alt-Svc",
+#endif
+ };
+
+ public static void MapElevenLabsEndpoints(this IEndpointRouteBuilder endpoints,
+ ElevenLabsClient elevenLabsClient, IAuthenticationFilter authenticationFilter)
+ {
+ endpoints.Map($"{elevenLabsClient.ElevenLabsClientSettings.BaseRequest}{{**endpoint}}", HandleRequest);
+
+ async Task HandleRequest(HttpContext httpContext, string endpoint)
+ {
+ try
+ {
+ // ReSharper disable once MethodHasAsyncOverload
+ // just in case either method is implemented we call it twice.
+ authenticationFilter.ValidateAuthentication(httpContext.Request.Headers);
+ await authenticationFilter.ValidateAuthenticationAsync(httpContext.Request.Headers);
+
+ var method = new HttpMethod(httpContext.Request.Method);
+ var uri = new Uri(string.Format(
+ elevenLabsClient.ElevenLabsClientSettings.BaseRequestUrlFormat,
+ $"{endpoint}{httpContext.Request.QueryString}"
+ ));
+ using var request = new HttpRequestMessage(method, uri);
+ request.Content = new StreamContent(httpContext.Request.Body);
+
+ if (httpContext.Request.ContentType != null)
+ {
+ request.Content.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse(httpContext.Request.ContentType);
+ }
+
+ var proxyResponse = await elevenLabsClient.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
+ httpContext.Response.StatusCode = (int)proxyResponse.StatusCode;
+
+ foreach (var (key, value) in proxyResponse.Headers)
+ {
+ if (excludedHeaders.Contains(key))
+ {
+ continue;
+ }
+
+ httpContext.Response.Headers[key] = value.ToArray();
+ }
+
+ foreach (var (key, value) in proxyResponse.Content.Headers)
+ {
+ if (excludedHeaders.Contains(key))
+ {
+ continue;
+ }
+
+ httpContext.Response.Headers[key] = value.ToArray();
+ }
+
+ httpContext.Response.ContentType = proxyResponse.Content.Headers.ContentType?.ToString() ?? string.Empty;
+ const string streamingContent = "text/event-stream";
+
+ if (httpContext.Response.ContentType.Equals(streamingContent))
+ {
+ var stream = await proxyResponse.Content.ReadAsStreamAsync();
+ await WriteServerStreamEventsAsync(httpContext, stream);
+ }
+ else
+ {
+ await proxyResponse.Content.CopyToAsync(httpContext.Response.Body);
+ }
+ }
+ catch (AuthenticationException authenticationException)
+ {
+ httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
+ await httpContext.Response.WriteAsync(authenticationException.Message);
+ }
+ catch (Exception e)
+ {
+ httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
+ var response = JsonSerializer.Serialize(new { error = new { e.Message, e.StackTrace } });
+ await httpContext.Response.WriteAsync(response);
+ }
+
+ static async Task WriteServerStreamEventsAsync(HttpContext httpContext, Stream contentStream)
+ {
+ var responseStream = httpContext.Response.Body;
+ await contentStream.CopyToAsync(responseStream, httpContext.RequestAborted);
+ await responseStream.FlushAsync(httpContext.RequestAborted);
+ }
+ }
+ }
+ }
+}
diff --git a/ElevenLabs-DotNet-Tests/Test_Fixture_02_VoicesEndpoint.cs b/ElevenLabs-DotNet-Tests/Test_Fixture_02_VoicesEndpoint.cs
index c577290..9d0f4e4 100644
--- a/ElevenLabs-DotNet-Tests/Test_Fixture_02_VoicesEndpoint.cs
+++ b/ElevenLabs-DotNet-Tests/Test_Fixture_02_VoicesEndpoint.cs
@@ -43,7 +43,7 @@ public async Task Test_03_GetVoice()
var results = await ElevenLabsClient.VoicesEndpoint.GetAllVoicesAsync();
Assert.NotNull(results);
Assert.IsNotEmpty(results);
- var voiceToGet = results.OrderBy(voice => voice.Name).FirstOrDefault();
+ var voiceToGet = results.MinBy(voice => voice.Name);
var result = await ElevenLabsClient.VoicesEndpoint.GetVoiceAsync(voiceToGet);
Assert.NotNull(result);
Console.WriteLine($"{result.Id} | {result.Name} | {result.PreviewUrl}");
@@ -94,7 +94,7 @@ public async Task Test_06_AddVoiceFromByteArray()
{ "accent", "american" }
};
var clipPath = Path.GetFullPath("../../../Assets/test_sample_01.ogg");
- byte[] clipData = await File.ReadAllBytesAsync(clipPath);
+ var clipData = await File.ReadAllBytesAsync(clipPath);
var result = await ElevenLabsClient.VoicesEndpoint.AddVoiceAsync("Test Voice", new[] { clipData }, testLabels);
Assert.NotNull(result);
Console.WriteLine($"{result.Name}");
@@ -112,13 +112,11 @@ public async Task Test_07_AddVoiceFromStream()
};
var clipPath = Path.GetFullPath("../../../Assets/test_sample_01.ogg");
- using (FileStream fs = File.OpenRead(clipPath))
- {
- var result = await ElevenLabsClient.VoicesEndpoint.AddVoiceAsync("Test Voice", new[] { fs }, testLabels);
- Assert.NotNull(result);
- Console.WriteLine($"{result.Name}");
- Assert.IsNotEmpty(result.Samples);
- }
+ await using var fs = File.OpenRead(clipPath);
+ var result = await ElevenLabsClient.VoicesEndpoint.AddVoiceAsync("Test Voice", new[] { fs }, testLabels);
+ Assert.NotNull(result);
+ Console.WriteLine($"{result.Name}");
+ Assert.IsNotEmpty(result.Samples);
}
[Test]
diff --git a/ElevenLabs-DotNet/ElevenLabs-DotNet.csproj b/ElevenLabs-DotNet/ElevenLabs-DotNet.csproj
index fb235ad..6c7771d 100644
--- a/ElevenLabs-DotNet/ElevenLabs-DotNet.csproj
+++ b/ElevenLabs-DotNet/ElevenLabs-DotNet.csproj
@@ -14,8 +14,10 @@
RageAgainstThePixel
2024
ElevenLabs, AI, ML, Voice, TTS
- 2.2.0
- Version 2.2.0
+ 2.2.1
+ Version 2.2.1
+- Misc formatting changes
+Version 2.2.0
- Changed ElevenLabsClient to be IDisposable
- The ElevenLabsClient must now be disposed if you do not pass your own HttpClient
- Updated ElevenLabsClientSettings to accept custom domains
diff --git a/ElevenLabs-DotNet/Voices/VoicesEndpoint.cs b/ElevenLabs-DotNet/Voices/VoicesEndpoint.cs
index acdd203..252aab8 100644
--- a/ElevenLabs-DotNet/Voices/VoicesEndpoint.cs
+++ b/ElevenLabs-DotNet/Voices/VoicesEndpoint.cs
@@ -42,9 +42,7 @@ public VoicesEndpoint(ElevenLabsClient client) : base(client) { }
///
/// of s.
public Task> GetAllVoicesAsync(CancellationToken cancellationToken = default)
- {
- return GetAllVoicesAsync(true, cancellationToken);
- }
+ => GetAllVoicesAsync(true, cancellationToken);
///
/// Gets a list of all available voices for a user.