mirror of https://github.com/HandBrake/HandBrake
258 lines
9.8 KiB
C#
258 lines
9.8 KiB
C#
// --------------------------------------------------------------------------------------------------------------------
|
|
// <copyright file="HttpServer.cs" company="HandBrake Project (http://handbrake.fr)">
|
|
// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License.
|
|
// </copyright>
|
|
// <summary>
|
|
// This is a service worker for the HandBrake app. It allows us to run encodes / scans in a separate process easily.
|
|
// All API's expose the ApplicationServices models as JSON.
|
|
// </summary>
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
namespace HandBrake.Worker
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Net;
|
|
using System.Text;
|
|
|
|
using HandBrake.Worker.Logging;
|
|
using HandBrake.Worker.Services.Interfaces;
|
|
|
|
public class HttpServer
|
|
{
|
|
private readonly int port;
|
|
private ITokenService tokenService;
|
|
private HttpListener httpListener;
|
|
private Dictionary<string, Func<HttpListenerRequest, string>> apiHandlers;
|
|
private int failedStart = 0;
|
|
private int count = 0;
|
|
|
|
public HttpServer(Dictionary<string, Func<HttpListenerRequest, string>> apiCalls, int port, ITokenService tokenService)
|
|
{
|
|
this.port = port;
|
|
try
|
|
{
|
|
Init(apiCalls, port, tokenService);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
ConsoleOutput.WriteLine("HandBrake Worker: Failed to Initialise: " + Environment.NewLine + e, ConsoleColor.Red);
|
|
}
|
|
}
|
|
|
|
public int Run()
|
|
{
|
|
if (this.failedStart > 0)
|
|
{
|
|
return failedStart;
|
|
}
|
|
|
|
httpListener.BeginGetContext(new AsyncCallback(ListenerCallback), this.httpListener);
|
|
return 0;
|
|
}
|
|
|
|
public void ListenerCallback(IAsyncResult result)
|
|
{
|
|
if (this.failedStart > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (this.httpListener.IsListening)
|
|
{
|
|
var context = this.httpListener.EndGetContext(result);
|
|
lock (this.httpListener)
|
|
{
|
|
this.HandleRequest(context);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This shouldn't happen, but if it does, try re-initialising the server.
|
|
StartServer(this.apiHandlers, port, this.tokenService);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (e is HttpListenerException)
|
|
{
|
|
Console.WriteLine("Worker: " + e);
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.Run();
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
try
|
|
{
|
|
if (this.httpListener != null)
|
|
{
|
|
this.httpListener.Stop();
|
|
this.httpListener.Close();
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
ConsoleOutput.WriteLine("Worker: Not able to close HttpListener: " + e);
|
|
}
|
|
finally
|
|
{
|
|
this.httpListener = null;
|
|
}
|
|
}
|
|
|
|
private void Init(Dictionary<string, Func<HttpListenerRequest, string>> apiCalls, int port, ITokenService tokenService)
|
|
{
|
|
if (!HttpListener.IsSupported)
|
|
{
|
|
string rstr = string.Format(
|
|
"Worker: Failed to Initialise: HttpListener is not supported on this computer.{0}"
|
|
+ "\r\nProcess Isolation can be disabled in \"Tools Menu -> Preferences -> Advanced -> Process Isolation\".{0}",
|
|
Environment.NewLine);
|
|
|
|
ConsoleOutput.WriteLine(rstr, ConsoleColor.Red);
|
|
throw new NotSupportedException("HttpListener not supported on this computer.");
|
|
}
|
|
|
|
this.tokenService = tokenService;
|
|
|
|
// Store the Handlers
|
|
this.apiHandlers = new Dictionary<string, Func<HttpListenerRequest, string>>(apiCalls);
|
|
|
|
if (!tokenService.IsTokenSet())
|
|
{
|
|
ConsoleOutput.WriteLine("HandBrake Worker: Token has not been initialised. API Access is limited!", ConsoleColor.Red);
|
|
Console.WriteLine(Environment.NewLine);
|
|
ConsoleOutput.WriteLine("API Information: ", ConsoleColor.Cyan);
|
|
Console.WriteLine("All calls require a 'token' in the HTTP header ");
|
|
}
|
|
|
|
StartServer(apiCalls, port, tokenService);
|
|
}
|
|
|
|
private void StartServer(Dictionary<string, Func<HttpListenerRequest, string>> apiCalls, int port, ITokenService tokenService)
|
|
{
|
|
this.count += 1;
|
|
ConsoleOutput.WriteLine("Worker: Starting Listener: " + count, ConsoleColor.White, true);
|
|
|
|
this.Stop(); // In the case that we are re-initialising.
|
|
this.httpListener = new HttpListener();
|
|
|
|
// Base URL
|
|
string url = string.Format("http://127.0.0.1:{0}/", port);
|
|
this.httpListener.Prefixes.Add(url);
|
|
|
|
// API URLS
|
|
foreach (KeyValuePair<string, Func<HttpListenerRequest, string>> api in apiCalls)
|
|
{
|
|
url = string.Format("http://127.0.0.1:{0}/{1}/", port, api.Key);
|
|
this.httpListener.Prefixes.Add(url);
|
|
|
|
if (!tokenService.IsTokenSet())
|
|
{
|
|
Console.WriteLine(" - " + url);
|
|
}
|
|
}
|
|
|
|
if (!tokenService.IsTokenSet())
|
|
{
|
|
Console.WriteLine();
|
|
}
|
|
|
|
try
|
|
{
|
|
this.httpListener.Start();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
this.failedStart = 2;
|
|
|
|
Console.WriteLine("Worker: Unable to start HTTP Server. Maybe the port {0} is in use?", port);
|
|
Console.WriteLine("Worker Exception: " + e);
|
|
}
|
|
}
|
|
|
|
private void HandleRequest(HttpListenerContext context)
|
|
{
|
|
try
|
|
{
|
|
if (context == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Stopwatch watch = Stopwatch.StartNew();
|
|
|
|
string path = context.Request.RawUrl.TrimStart('/').TrimEnd('/');
|
|
string token = context.Request.Headers.Get("token");
|
|
|
|
if (path == string.Empty)
|
|
{
|
|
string rstr = string.Format("HandBrake Worker {0}{0}"
|
|
+ "This worker runs on localhost loopback only and is not accessible to the wider network.{0}"
|
|
+ "\r\nThis feature allows processing of HandBrake jobs on a background process.{0}"
|
|
+ "\r\nThis can be enabled and disabled in \"Tools Menu -> Preferences -> Advanced -> Process Isolation\".{0}",
|
|
Environment.NewLine);
|
|
|
|
byte[] buf = Encoding.UTF8.GetBytes(rstr);
|
|
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
|
|
context.Response.ContentLength64 = buf.Length;
|
|
context.Response.OutputStream.Write(buf, 0, buf.Length);
|
|
|
|
watch.Stop();
|
|
Debug.WriteLine(string.Format(" - Processed call to: '/{0}', Took {1}ms", path, watch.ElapsedMilliseconds), ConsoleColor.White, true);
|
|
return;
|
|
}
|
|
|
|
if (!path.Equals("Version") && !tokenService.IsAuthenticated(token))
|
|
{
|
|
string rstr = string.Format("HandBrake Worker: Access Denied to '/{0}'. The token provided in the HTTP header was not valid.", path);
|
|
ConsoleOutput.WriteLine(rstr, ConsoleColor.Red, true);
|
|
byte[] buf = Encoding.UTF8.GetBytes(rstr);
|
|
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
|
|
context.Response.ContentLength64 = buf.Length;
|
|
context.Response.OutputStream.Write(buf, 0, buf.Length);
|
|
|
|
watch.Stop();
|
|
Debug.WriteLine(string.Format(" - Processed call to: '/{0}', Took {1}ms", path, watch.ElapsedMilliseconds), ConsoleColor.White, true);
|
|
return;
|
|
}
|
|
|
|
if (this.apiHandlers.TryGetValue(path, out var actionToPerform))
|
|
{
|
|
string rstr = actionToPerform(context.Request);
|
|
if (!string.IsNullOrEmpty(rstr))
|
|
{
|
|
byte[] buf = Encoding.UTF8.GetBytes(rstr);
|
|
context.Response.ContentLength64 = buf.Length;
|
|
context.Response.OutputStream.Write(buf, 0, buf.Length);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string rstr = "HandBrake Worker: The API endpoint is unknown.";
|
|
byte[] buf = Encoding.UTF8.GetBytes(rstr);
|
|
context.Response.ContentLength64 = buf.Length;
|
|
context.Response.OutputStream.Write(buf, 0, buf.Length);
|
|
}
|
|
|
|
watch.Stop();
|
|
Debug.WriteLine(string.Format(" - Processed call to: '/{0}', Took {1}ms", path, watch.ElapsedMilliseconds), ConsoleColor.White, true);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
ConsoleOutput.WriteLine("Worker: Listener Thread: " + exc);
|
|
}
|
|
finally
|
|
{
|
|
context?.Response.OutputStream.Close();
|
|
}
|
|
}
|
|
}
|
|
} |