Skip to content

Commit

Permalink
ElevenLabs-DotNet 2.2.0 (#41)
Browse files Browse the repository at this point in the history
- 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
- Added filesystemless overloads for uploading audio clips

ElevenLabs-DotNet-Proxy 2.2.0
- Updated implementation to include WebApplication builder

---------

Co-authored-by: Stillkill <[email protected]>
  • Loading branch information
StephenHodgson and RealStillkill authored May 6, 2024
1 parent cce4288 commit 9caa655
Show file tree
Hide file tree
Showing 13 changed files with 363 additions and 101 deletions.
92 changes: 46 additions & 46 deletions ElevenLabs-DotNet-Proxy/ElevenLabs-DotNet-Proxy.csproj
Original file line number Diff line number Diff line change
@@ -1,48 +1,48 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<SignAssembly>false</SignAssembly>
<Authors>Stephen Hodgson</Authors>
<Product>ElevenLabs-DotNet-Proxy</Product>
<Description>A simple Proxy API gateway for ElevenLabs-DotNet to make authenticated requests from a front end application without exposing your API keys.</Description>
<Copyright>2023</Copyright>
<PackageProjectUrl>https://github.com/RageAgainstThePixel/ElevenLabs-DotNet</PackageProjectUrl>
<RepositoryUrl>https://github.com/RageAgainstThePixel/ElevenLabs-DotNet</RepositoryUrl>
<PackageTags>ElevenLabs, AI, ML, API, api-proxy, proxy, gateway</PackageTags>
<Title>ElevenLabs API Proxy</Title>
<PackageId>ElevenLabs-DotNet-Proxy</PackageId>
<Version>1.0.1</Version>
<RootNamespace>ElevenLabs.Proxy</RootNamespace>
<PackageReleaseNotes>Initial Release!</PackageReleaseNotes>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageReadmeFile>Readme.md</PackageReadmeFile>
<IncludeSymbols>True</IncludeSymbols>
<PackageIcon>ElevenLabsIcon.png</PackageIcon>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ElevenLabs-DotNet\ElevenLabs-DotNet.csproj" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<None Include="..\ElevenLabs-DotNet\Assets\ElevenLabsIcon.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Include="..\LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<None Update="Readme.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<SignAssembly>false</SignAssembly>
<Authors>Stephen Hodgson</Authors>
<Product>ElevenLabs-DotNet-Proxy</Product>
<Description>A simple Proxy API gateway for ElevenLabs-DotNet to make authenticated requests from a front end application without exposing your API keys.</Description>
<Copyright>2024</Copyright>
<PackageProjectUrl>https://github.com/RageAgainstThePixel/ElevenLabs-DotNet</PackageProjectUrl>
<RepositoryUrl>https://github.com/RageAgainstThePixel/ElevenLabs-DotNet</RepositoryUrl>
<PackageTags>ElevenLabs, AI, ML, API, api-proxy, proxy, gateway</PackageTags>
<Title>ElevenLabs API Proxy</Title>
<PackageId>ElevenLabs-DotNet-Proxy</PackageId>
<Version>1.2.0</Version>
<RootNamespace>ElevenLabs.Proxy</RootNamespace>
<PackageReleaseNotes>Initial Release!</PackageReleaseNotes>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageReadmeFile>Readme.md</PackageReadmeFile>
<IncludeSymbols>True</IncludeSymbols>
<PackageIcon>ElevenLabsIcon.png</PackageIcon>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ElevenLabs-DotNet\ElevenLabs-DotNet.csproj" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<None Include="..\ElevenLabs-DotNet\Assets\ElevenLabsIcon.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Include="..\LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<None Update="Readme.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
</Project>
4 changes: 4 additions & 0 deletions ElevenLabs-DotNet-Proxy/Proxy/AbstractAuthenticationFilter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

namespace ElevenLabs.Proxy
Expand All @@ -9,5 +10,8 @@ public abstract class AbstractAuthenticationFilter : IAuthenticationFilter
{
/// <inheritdoc />
public abstract void ValidateAuthentication(IHeaderDictionary request);

/// <inheritdoc />
public abstract Task ValidateAuthenticationAsync(IHeaderDictionary request);
}
}
66 changes: 46 additions & 20 deletions ElevenLabs-DotNet-Proxy/Proxy/ElevenLabsProxyStartup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Net.Http.Headers;
Expand All @@ -26,7 +27,7 @@ public class ElevenLabsProxyStartup
private IAuthenticationFilter authenticationFilter;

// Copied from https://github.com/microsoft/reverse-proxy/blob/51d797986b1fea03500a1ad173d13a1176fb5552/src/ReverseProxy/Forwarder/RequestUtilities.cs#L61-L83
private static readonly HashSet<string> ExcludedHeaders = new HashSet<string>()
private static readonly HashSet<string> excludedHeaders = new()
{
HeaderNames.Connection,
HeaderNames.TransferEncoding,
Expand All @@ -49,7 +50,12 @@ public class ElevenLabsProxyStartup
#endif
};

public void ConfigureServices(IServiceCollection services) { }
/// <summary>
/// Configures the <see cref="elevenLabsClient"/> and <see cref="IAuthenticationFilter"/> services.
/// </summary>
/// <param name="services"></param>
public void ConfigureServices(IServiceCollection services)
=> SetupServices(services.BuildServiceProvider());

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
Expand All @@ -58,8 +64,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseDeveloperExceptionPage();
}

elevenLabsClient = app.ApplicationServices.GetRequiredService<ElevenLabsClient>();
authenticationFilter = app.ApplicationServices.GetRequiredService<IAuthenticationFilter>();
SetupServices(app.ApplicationServices);

app.UseHttpsRedirection();
app.UseRouting();
Expand All @@ -77,25 +82,43 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
/// <param name="args">Startup args.</param>
/// <param name="elevenLabsClient"><see cref="ElevenLabsClient"/> with configured <see cref="ElevenLabsAuthentication"/> and <see cref="ElevenLabsClientSettings"/>.</param>
public static IHost CreateDefaultHost<T>(string[] args, ElevenLabsClient elevenLabsClient) where T : class, IAuthenticationFilter
{
return Host.CreateDefaultBuilder(args)
=> Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<ElevenLabsProxyStartup>();
webBuilder.ConfigureKestrel(options =>
{
options.AllowSynchronousIO = false;
options.Limits.MinRequestBodyDataRate = null;
options.Limits.MinResponseDataRate = null;
options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(10);
options.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(2);
});
webBuilder.ConfigureKestrel(ConfigureKestrel);
})
.ConfigureServices(services =>
{
services.AddSingleton(elevenLabsClient);
services.AddSingleton<IAuthenticationFilter, T>();
}).Build();

public static WebApplication CreateWebApplication<T>(string[] args, ElevenLabsClient elevenLabsClient) where T : class, IAuthenticationFilter
{
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(ConfigureKestrel);
builder.Services.AddSingleton(elevenLabsClient);
builder.Services.AddSingleton<IAuthenticationFilter, T>();
var app = builder.Build();
var startup = new ElevenLabsProxyStartup();
startup.Configure(app, app.Environment);
return app;
}

private static void ConfigureKestrel(KestrelServerOptions options)
{
options.AllowSynchronousIO = false;
options.Limits.MinRequestBodyDataRate = null;
options.Limits.MinResponseDataRate = null;
options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(10);
options.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(2);
}

private void SetupServices(IServiceProvider serviceProvider)
{
elevenLabsClient = serviceProvider.GetRequiredService<ElevenLabsClient>();
authenticationFilter = serviceProvider.GetRequiredService<IAuthenticationFilter>();
}

private static async Task HealthEndpoint(HttpContext context)
Expand All @@ -115,31 +138,34 @@ 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}"));
var elevenLabsRequest = new HttpRequestMessage(method, uri);
using var request = new HttpRequestMessage(method, uri);

elevenLabsRequest.Content = new StreamContent(httpContext.Request.Body);
request.Content = new StreamContent(httpContext.Request.Body);

if (httpContext.Request.ContentType != null)
{
elevenLabsRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(httpContext.Request.ContentType);
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(httpContext.Request.ContentType);
}

var proxyResponse = await elevenLabsClient.Client.SendAsync(elevenLabsRequest, HttpCompletionOption.ResponseHeadersRead);
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; }
if (excludedHeaders.Contains(key)) { continue; }
httpContext.Response.Headers[key] = value.ToArray();
}

foreach (var (key, value) in proxyResponse.Content.Headers)
{
if (ExcludedHeaders.Contains(key)) { continue; }
if (excludedHeaders.Contains(key)) { continue; }
httpContext.Response.Headers[key] = value.ToArray();
}

Expand Down
11 changes: 10 additions & 1 deletion ElevenLabs-DotNet-Proxy/Proxy/IAuthenticationFilter.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Security.Authentication;
using Microsoft.AspNetCore.Http;
using System.Security.Authentication;
using System.Threading.Tasks;

namespace ElevenLabs.Proxy
{
Expand All @@ -17,5 +18,13 @@ public interface IAuthenticationFilter
/// <param name="request"></param>
/// <exception cref="AuthenticationException"></exception>
void ValidateAuthentication(IHeaderDictionary request);

/// <summary>
/// Checks the headers for your user issued token.
/// If it's not valid, then throw <see cref="AuthenticationException"/>.
/// </summary>
/// <param name="request"></param>
/// <exception cref="AuthenticationException"></exception>
Task ValidateAuthenticationAsync(IHeaderDictionary request);
}
}
22 changes: 17 additions & 5 deletions ElevenLabs-DotNet-Proxy/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ In this example, we demonstrate how to set up and use `ElevenLabsProxyStartup` i
- Powershell install: `Install-Package ElevenLabs-DotNet-Proxy`
- Manually editing .csproj: `<PackageReference Include="ElevenLabs-DotNet-Proxy" />`
3. Create a new class that inherits from `AbstractAuthenticationFilter` and override the `ValidateAuthentication` method. This will implement the `IAuthenticationFilter` that you will use to check user session token against your internal server.
4. In `Program.cs`, create a new proxy web application by calling `ElevenLabsProxyStartup.CreateDefaultHost` method, passing your custom `AuthenticationFilter` as a type argument.
4. In `Program.cs`, create a new proxy web application by calling `ElevenLabsProxyStartup.CreateWebApplication` method, passing your custom `AuthenticationFilter` as a type argument.
5. Create `ElevenLabsAuthentication` and `ElevenLabsClientSettings` as you would normally with your API keys, org id, or Azure settings.

```csharp
Expand All @@ -63,7 +63,19 @@ public partial class Program
{
// You will need to implement your own class to properly test
// custom issued tokens you've setup for your end users.
if (!request["xi-api-key"].ToString().Contains(userToken))
if (!request["xi-api-key"].ToString().Contains(TestUserToken))
{
throw new AuthenticationException("User is not authorized");
}
}

public override async Task ValidateAuthenticationAsync(IHeaderDictionary request)
{
await Task.CompletedTask; // remote resource call
// You will need to implement your own class to properly test
// custom issued tokens you've setup for your end users.
if (!request["xi-api-key"].ToString().Contains(TestUserToken))
{
throw new AuthenticationException("User is not authorized");
}
Expand All @@ -72,9 +84,9 @@ public partial class Program

public static void Main(string[] args)
{
var client = new ElevenLabsClient();
var proxy = ElevenLabsProxyStartup.CreateDefaultHost<AuthenticationFilter>(args, client);
proxy.Run();
var auth = ElevenLabsAuthentication.LoadFromEnv();
var client = new ElevenLabsClient(auth);
ElevenLabsProxyStartup.CreateWebApplication<AuthenticationFilter>(args, client).Run();
}
}
```
Expand Down
17 changes: 14 additions & 3 deletions ElevenLabs-DotNet-Tests-Proxy/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

using ElevenLabs.Proxy;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using System.Security.Authentication;
using System.Threading.Tasks;

namespace ElevenLabs.Tests.Proxy
{
Expand All @@ -27,14 +27,25 @@ public override void ValidateAuthentication(IHeaderDictionary request)
throw new AuthenticationException("User is not authorized");
}
}

public override async Task ValidateAuthenticationAsync(IHeaderDictionary request)
{
await Task.CompletedTask; // remote resource call

// You will need to implement your own class to properly test
// custom issued tokens you've setup for your end users.
if (!request["xi-api-key"].ToString().Contains(TestUserToken))
{
throw new AuthenticationException("User is not authorized");
}
}
}

public static void Main(string[] args)
{
var auth = ElevenLabsAuthentication.LoadFromEnv();
var client = new ElevenLabsClient(auth);
var proxy = ElevenLabsProxyStartup.CreateDefaultHost<AuthenticationFilter>(args, client);
proxy.Run();
ElevenLabsProxyStartup.CreateWebApplication<AuthenticationFilter>(args, client).Run();
}
}
}
Loading

0 comments on commit 9caa655

Please sign in to comment.