Implement basic HTTP file server (IPC GUI binding), #733

I don't even want to know how many RFCs this thing violates. "It works!" 😅

Hopefully we'll improve it if needed.
This commit is contained in:
JustArchi
2018-01-28 03:23:58 +01:00
parent 1deb5e0d0a
commit 37c8096e27
3 changed files with 65 additions and 13 deletions

View File

@@ -87,6 +87,9 @@
<None Update="config\minimal.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Include="www\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -36,6 +36,15 @@ namespace ArchiSteamFarm {
internal static class IPC {
internal static bool IsRunning => IsHandlingRequests || IsListening;
private static readonly Dictionary<string, string> MimeTypes = new Dictionary<string, string>(6) {
{ ".css", "text/css" },
{ ".js", "application/javascript" },
{ ".html", "text/html" },
{ ".ico", "image/x-icon" },
{ ".jpg", "image/jpeg" },
{ ".png", "image/png" }
};
private static bool IsListening {
get {
try {
@@ -468,26 +477,44 @@ namespace ArchiSteamFarm {
return false;
}
if (request.Url.Segments.Length < 2) {
return await HandleMainPage(request, response).ConfigureAwait(false);
if ((request.Url.Segments.Length >= 2) && request.Url.Segments[1].Equals("Api/")) {
return await HandleApi(request, response, request.Url.Segments, 2).ConfigureAwait(false);
}
switch (request.Url.Segments[1]) {
case "Api/":
return await HandleApi(request, response, request.Url.Segments, 2).ConfigureAwait(false);
default:
return false;
}
return await HandleFile(request, response, request.Url.AbsolutePath).ConfigureAwait(false);
}
private static async Task<bool> HandleMainPage(HttpListenerRequest request, HttpListenerResponse response) {
if ((request == null) || (response == null)) {
ASF.ArchiLogger.LogNullError(nameof(request) + " || " + nameof(response));
private static async Task<bool> HandleFile(HttpListenerRequest request, HttpListenerResponse response, string absolutePath) {
if ((request == null) || (response == null) || string.IsNullOrEmpty(absolutePath)) {
ASF.ArchiLogger.LogNullError(nameof(request) + " || " + nameof(response) + " || " + nameof(absolutePath));
return false;
}
// In the future we'll probably have some friendly admin panel here, for now this is 501
await ResponseStatusCode(request, response, HttpStatusCode.NotImplemented).ConfigureAwait(false);
switch (request.HttpMethod) {
case HttpMethods.Get:
return await HandleFileGet(request, response, absolutePath).ConfigureAwait(false);
default:
await ResponseStatusCode(request, response, HttpStatusCode.MethodNotAllowed).ConfigureAwait(false);
return true;
}
}
private static async Task<bool> HandleFileGet(HttpListenerRequest request, HttpListenerResponse response, string absolutePath) {
if ((request == null) || (response == null) || string.IsNullOrEmpty(absolutePath)) {
ASF.ArchiLogger.LogNullError(nameof(request) + " || " + nameof(response) + " || " + nameof(absolutePath));
return false;
}
string filePath = SharedInfo.WebsiteDirectory + Path.DirectorySeparatorChar + absolutePath.Replace("/", Path.DirectorySeparatorChar.ToString());
if (Directory.Exists(filePath)) {
filePath = Path.Combine(filePath, "index.html");
}
if (!File.Exists(filePath)) {
return false;
}
await ResponseFile(request, response, filePath).ConfigureAwait(false);
return true;
}
@@ -599,6 +626,27 @@ namespace ArchiSteamFarm {
}
}
private static async Task ResponseFile(HttpListenerRequest request, HttpListenerResponse response, string filePath) {
if ((request == null) || (response == null) || string.IsNullOrEmpty(filePath)) {
ASF.ArchiLogger.LogNullError(nameof(request) + " || " + nameof(response) + " || " + nameof(filePath));
return;
}
try {
response.ContentType = MimeTypes.TryGetValue(Path.GetExtension(filePath), out string mimeType) ? mimeType : "application/octet-stream";
byte[] content = await File.ReadAllBytesAsync(filePath).ConfigureAwait(false);
await ResponseBase(request, response, content).ConfigureAwait(false);
} catch (FileNotFoundException) {
await ResponseStatusCode(request, response, HttpStatusCode.NotFound).ConfigureAwait(false);
} catch (ObjectDisposedException) {
// Ignored, request is no longer valid
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
await ResponseStatusCode(request, response, HttpStatusCode.ServiceUnavailable).ConfigureAwait(false);
}
}
private static async Task ResponseJson(HttpListenerRequest request, HttpListenerResponse response, string json, HttpStatusCode statusCode = HttpStatusCode.OK) {
if ((request == null) || (response == null) || string.IsNullOrEmpty(json)) {
ASF.ArchiLogger.LogNullError(nameof(request) + " || " + nameof(response) + " || " + nameof(json));

View File

@@ -52,6 +52,7 @@ namespace ArchiSteamFarm {
internal const string LogFile = "log.txt";
internal const string StatisticsServer = "asf.justarchi.net";
internal const string UpdateDirectory = "_old";
internal const string WebsiteDirectory = "www";
private const string SourceVariant = "source";