@@ -20,5 +20,11 @@ | |||||
<None Update="appsettings.json"> | <None Update="appsettings.json"> | ||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | <CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||||
</None> | </None> | ||||
<None Update="wwwroot\demo\demo.m3u8"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
<None Update="wwwroot\demo\demo.ts"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
</ItemGroup> | </ItemGroup> | ||||
</Project> | </Project> |
@@ -0,0 +1 @@ | |||||
m3u8 demo |
@@ -0,0 +1 @@ | |||||
ts demo |
@@ -29,6 +29,10 @@ namespace JT1078.Gateway.Configurations | |||||
/// Udp 60s检查一次 | /// Udp 60s检查一次 | ||||
/// </summary> | /// </summary> | ||||
public int UdpReceiveTimeoutCheckTimeSeconds { get; set; } = 60; | public int UdpReceiveTimeoutCheckTimeSeconds { get; set; } = 60; | ||||
/// <summary> | |||||
/// Hls根目录 | |||||
/// </summary> | |||||
public string HlsRootDirectory { get; set; } = "wwwroot"; | |||||
public JT1078Configuration Value => this; | public JT1078Configuration Value => this; | ||||
} | } | ||||
@@ -19,6 +19,7 @@ namespace JT1078.Gateway.Extensions | |||||
context.Response.ContentLength64 = b.Length; | context.Response.ContentLength64 = b.Length; | ||||
var output = context.Response.OutputStream; | var output = context.Response.OutputStream; | ||||
await output.WriteAsync(b, 0, b.Length); | await output.WriteAsync(b, 0, b.Length); | ||||
context.Response.OutputStream.Close(); | |||||
context.Response.Close(); | context.Response.Close(); | ||||
} | } | ||||
@@ -30,6 +31,7 @@ namespace JT1078.Gateway.Extensions | |||||
context.Response.ContentLength64 = b.Length; | context.Response.ContentLength64 = b.Length; | ||||
var output = context.Response.OutputStream; | var output = context.Response.OutputStream; | ||||
await output.WriteAsync(b, 0, b.Length); | await output.WriteAsync(b, 0, b.Length); | ||||
context.Response.OutputStream.Close(); | |||||
context.Response.Close(); | context.Response.Close(); | ||||
} | } | ||||
@@ -37,6 +39,7 @@ namespace JT1078.Gateway.Extensions | |||||
{ | { | ||||
context.Response.StatusCode = (int)HttpStatusCode.NotFound; | context.Response.StatusCode = (int)HttpStatusCode.NotFound; | ||||
context.Response.KeepAlive = false; | context.Response.KeepAlive = false; | ||||
context.Response.OutputStream.Close(); | |||||
context.Response.Close(); | context.Response.Close(); | ||||
} | } | ||||
@@ -48,6 +51,7 @@ namespace JT1078.Gateway.Extensions | |||||
context.Response.ContentLength64 = b.Length; | context.Response.ContentLength64 = b.Length; | ||||
var output = context.Response.OutputStream; | var output = context.Response.OutputStream; | ||||
await output.WriteAsync(b, 0, b.Length); | await output.WriteAsync(b, 0, b.Length); | ||||
context.Response.OutputStream.Close(); | |||||
context.Response.Close(); | context.Response.Close(); | ||||
} | } | ||||
@@ -72,6 +76,7 @@ namespace JT1078.Gateway.Extensions | |||||
context.Context.Response.ContentLength64 = b.Length; | context.Context.Response.ContentLength64 = b.Length; | ||||
var output = context.Context.Response.OutputStream; | var output = context.Context.Response.OutputStream; | ||||
await output.WriteAsync(b, 0, b.Length); | await output.WriteAsync(b, 0, b.Length); | ||||
context.Context.Response.OutputStream.Close(); | |||||
context.Context.Response.Close(); | context.Context.Response.Close(); | ||||
} | } | ||||
@@ -0,0 +1,29 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Net.Http; | |||||
using System.Text; | |||||
namespace JT1078.Gateway | |||||
{ | |||||
/// <summary> | |||||
/// 协调器客户端 | |||||
/// </summary> | |||||
public class JT1078CoordinatorHttpClient | |||||
{ | |||||
private HttpClient httpClient; | |||||
public JT1078CoordinatorHttpClient(HttpClient httpClient) | |||||
{ | |||||
this.httpClient = httpClient; | |||||
} | |||||
/// <summary> | |||||
/// 发送心跳至协调器中 | |||||
/// </summary> | |||||
/// <param name="content"></param> | |||||
public async void Heartbeat(string content) | |||||
{ | |||||
await httpClient.PostAsync("/heartbeat", new StringContent(content)); | |||||
} | |||||
} | |||||
} |
@@ -88,13 +88,65 @@ namespace JT1078.Gateway | |||||
return Task.CompletedTask; | return Task.CompletedTask; | ||||
} | } | ||||
private const string m3u8Mime = "application/x-mpegURL"; | |||||
private const string tsMime = "video/MP2T"; | |||||
private async ValueTask ProcessRequestAsync(HttpListenerContext context, IPrincipal principal) | private async ValueTask ProcessRequestAsync(HttpListenerContext context, IPrincipal principal) | ||||
{ | { | ||||
if(context.Request.RawUrl.StartsWith("/favicon.ico")) | if(context.Request.RawUrl.StartsWith("/favicon.ico")) | ||||
{ | { | ||||
context.Http404(); | context.Http404(); | ||||
return; | |||||
} | |||||
var queryStringIndex = context.Request.RawUrl.IndexOf("?"); | |||||
string url = ""; | |||||
if (queryStringIndex > 0) | |||||
{ | |||||
url = context.Request.RawUrl.Substring(1, queryStringIndex-1); | |||||
} | |||||
else | |||||
{ | |||||
url = context.Request.RawUrl; | |||||
} | |||||
if (url.EndsWith(".m3u8") || url.EndsWith(".ts")) | |||||
{ | |||||
string filename = Path.GetFileName(url); | |||||
string filepath = Path.Combine(Configuration.HlsRootDirectory, Path.GetFileNameWithoutExtension(filename), filename); | |||||
if (!File.Exists(filepath)) | |||||
{ | |||||
context.Http404(); | |||||
return; | |||||
} | |||||
try | |||||
{ | |||||
using (FileStream sr = new FileStream(filepath, FileMode.Open)) | |||||
{ | |||||
context.Response.ContentLength64 = sr.Length; | |||||
await sr.CopyToAsync(context.Response.OutputStream); | |||||
} | |||||
string ext = Path.GetExtension(filename); | |||||
if (ext == ".m3u8") | |||||
{ | |||||
context.Response.ContentType = m3u8Mime; | |||||
} | |||||
else if (ext == ".ts") | |||||
{ | |||||
context.Response.ContentType = tsMime; | |||||
} | |||||
context.Response.StatusCode = (int)HttpStatusCode.OK; | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
Logger.LogError(ex, $"{context.Request.RawUrl}"); | |||||
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; | |||||
} | |||||
finally | |||||
{ | |||||
context.Response.OutputStream.Close(); | |||||
context.Response.Close(); | |||||
} | |||||
return; | |||||
} | } | ||||
//todo:.m3u8 .ts | |||||
if (Logger.IsEnabled(LogLevel.Trace)) | if (Logger.IsEnabled(LogLevel.Trace)) | ||||
{ | { | ||||
Logger.LogTrace($"[http RequestTraceIdentifier]:{context.Request.RequestTraceIdentifier.ToString()}-{context.Request.RemoteEndPoint.ToString()}"); | Logger.LogTrace($"[http RequestTraceIdentifier]:{context.Request.RequestTraceIdentifier.ToString()}-{context.Request.RemoteEndPoint.ToString()}"); | ||||