Implement the ConfigServlet

Most of the server relevant options can now be changed in the
webinterface.
This commit is contained in:
0xboobface 2020-06-11 19:57:55 +02:00
parent 44fd340323
commit 28e6313d41
4 changed files with 105 additions and 28 deletions

View File

@ -1,5 +1,7 @@
package ctbrec.recorder.server; package ctbrec.recorder.server;
import static javax.servlet.http.HttpServletResponse.*;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
@ -7,13 +9,19 @@ import java.security.NoSuchAlgorithmException;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.Hmac; import ctbrec.Hmac;
public abstract class AbstractCtbrecServlet extends HttpServlet { public abstract class AbstractCtbrecServlet extends HttpServlet {
boolean checkAuthentication(HttpServletRequest req, String body) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException { private static final Logger LOG = LoggerFactory.getLogger(AbstractCtbrecServlet.class);
boolean checkAuthentication(HttpServletRequest req, String body) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
boolean authenticated = false; boolean authenticated = false;
if(Config.getInstance().getSettings().key != null) { if(Config.getInstance().getSettings().key != null) {
String reqParamHmac = req.getParameter("hmac"); String reqParamHmac = req.getParameter("hmac");
@ -48,4 +56,14 @@ public abstract class AbstractCtbrecServlet extends HttpServlet {
} }
return body.toString().trim(); return body.toString().trim();
} }
void sendResponse(HttpServletResponse resp, int httpStatus, String message) {
try {
resp.setStatus(httpStatus);
resp.getWriter().print(message);
} catch (IOException e) {
LOG.error("Couldn't write response", e);
resp.setStatus(SC_INTERNAL_SERVER_ERROR);
}
}
} }

View File

@ -3,6 +3,7 @@ package ctbrec.recorder.server;
import static javax.servlet.http.HttpServletResponse.*; import static javax.servlet.http.HttpServletResponse.*;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@ -11,6 +12,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -25,10 +27,7 @@ public class ConfigServlet extends AbstractCtbrecServlet {
private Settings settings; private Settings settings;
public enum DataType { public enum DataType {
STRING, STRING, BOOLEAN, INTEGER, LONG, DOUBLE
BOOLEAN,
INTEGER,
DOUBLE
} }
public ConfigServlet(Config config) { public ConfigServlet(Config config) {
@ -42,7 +41,7 @@ public class ConfigServlet extends AbstractCtbrecServlet {
boolean authenticated = checkAuthentication(req, body(req)); boolean authenticated = checkAuthentication(req, body(req));
if (!authenticated) { if (!authenticated) {
resp.setStatus(SC_UNAUTHORIZED); resp.setStatus(SC_UNAUTHORIZED);
String response = "{\"status\": \"error\", \"msg\": \"HMAC does not match\"}"; String response = "HMAC does not match";
resp.getWriter().write(response); resp.getWriter().write(response);
return; return;
} }
@ -52,13 +51,13 @@ public class ConfigServlet extends AbstractCtbrecServlet {
addParameter("ffmpegFileSuffix", "File Suffix", DataType.STRING, settings.ffmpegFileSuffix, json); addParameter("ffmpegFileSuffix", "File Suffix", DataType.STRING, settings.ffmpegFileSuffix, json);
addParameter("ffmpegMergedDownloadArgs", "FFmpeg Parameters", DataType.STRING, settings.ffmpegMergedDownloadArgs, json); addParameter("ffmpegMergedDownloadArgs", "FFmpeg Parameters", DataType.STRING, settings.ffmpegMergedDownloadArgs, json);
addParameter("httpPort", "HTTP port", DataType.INTEGER, settings.httpPort, json); addParameter("httpPort", "HTTP port", DataType.INTEGER, settings.httpPort, json);
addParameter("httpsPort", "HTTPS port", DataType.INTEGER, settings.httpSecurePort, json); addParameter("httpSecurePort", "HTTPS port", DataType.INTEGER, settings.httpSecurePort, json);
addParameter("httpUserAgent", "User-Agent", DataType.STRING, settings.httpUserAgent, json); addParameter("httpUserAgent", "User-Agent", DataType.STRING, settings.httpUserAgent, json);
addParameter("httpUserAgentMobile", "Mobile User-Agent", DataType.STRING, settings.httpUserAgentMobile, json); addParameter("httpUserAgentMobile", "Mobile User-Agent", DataType.STRING, settings.httpUserAgentMobile, json);
addParameter("generatePlaylist", "Generate Playlist", DataType.BOOLEAN, settings.generatePlaylist, json); addParameter("generatePlaylist", "Generate Playlist", DataType.BOOLEAN, settings.generatePlaylist, json);
addParameter("maximumResolution", "Maximum Resolution", DataType.INTEGER, settings.maximumResolution, json); addParameter("maximumResolution", "Maximum Resolution", DataType.INTEGER, settings.maximumResolution, json);
addParameter("minimumLengthInSeconds", "Minimum Length (secs)", DataType.INTEGER, settings.minimumLengthInSeconds, json); addParameter("minimumLengthInSeconds", "Minimum Length (secs)", DataType.INTEGER, settings.minimumLengthInSeconds, json);
addParameter("minimumSpaceLeftInBytes", "Leave Space On Device (GiB)", DataType.INTEGER, settings.minimumSpaceLeftInBytes, json); addParameter("minimumSpaceLeftInBytes", "Leave Space On Device (GiB)", DataType.LONG, settings.minimumSpaceLeftInBytes, json);
addParameter("onlineCheckIntervalInSecs", "Online Check Interval (secs)", DataType.INTEGER, settings.onlineCheckIntervalInSecs, json); addParameter("onlineCheckIntervalInSecs", "Online Check Interval (secs)", DataType.INTEGER, settings.onlineCheckIntervalInSecs, json);
addParameter("postProcessing", "Post-Processing", DataType.STRING, settings.postProcessing, json); addParameter("postProcessing", "Post-Processing", DataType.STRING, settings.postProcessing, json);
addParameter("postProcessingThreads", "Post-Processing Threads", DataType.INTEGER, settings.postProcessingThreads, json); addParameter("postProcessingThreads", "Post-Processing Threads", DataType.INTEGER, settings.postProcessingThreads, json);
@ -89,25 +88,81 @@ public class ConfigServlet extends AbstractCtbrecServlet {
} }
@Override @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
try { try {
boolean authenticated = checkAuthentication(req, body(req)); resp.setContentType("application/json");
String body = body(req);
boolean authenticated = checkAuthentication(req, body);
if (!authenticated) { if (!authenticated) {
resp.setStatus(SC_UNAUTHORIZED); String response = "HMAC does not match";
String response = "{\"status\": \"error\", \"msg\": \"HMAC does not match\"}"; sendResponse(resp, SC_UNAUTHORIZED, response);
resp.getWriter().write(response);
return; return;
} }
String postBody = body(req); JSONArray json = new JSONArray(body);
LOG.info("POST {}", postBody); changeConfig(json);
//JSONObject json = new JSONObject(postBody); sendResponse(resp, SC_OK, "{\"status\": \"success\"}");
resp.setStatus(SC_OK); } catch (InvalidKeyException | NoSuchAlgorithmException e) {
resp.setContentType("application/json"); LOG.error("Couldn't authenticate request", e);
resp.getWriter().print("{\"status\": \"success\"}"); sendResponse(resp, SC_INTERNAL_SERVER_ERROR, "Couldn't authenticate request");
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e) { } catch (JSONException e) {
resp.setStatus(SC_INTERNAL_SERVER_ERROR); LOG.error("Couldn't parse request", e);
sendResponse(resp, SC_BAD_REQUEST, "JSON array expected");
} catch (IOException e) {
LOG.error("Couldn't read request", e);
sendResponse(resp, SC_INTERNAL_SERVER_ERROR, "Couldn't read request");
} catch (ConfigWriteException e) {
LOG.error("Couldn't write config", e);
sendResponse(resp, SC_INTERNAL_SERVER_ERROR, "Couldn't write config: " + e.getLocalizedMessage());
} }
}
private void changeConfig(JSONArray json) throws ConfigWriteException {
try {
for (int i = 0; i < json.length(); i++) {
JSONObject property = json.getJSONObject(i);
String key = property.optString("key");
DataType type = DataType.valueOf(property.optString("type"));
Object value = property.get("value");
Object typeCorrectedValue = correctType(type, value);
LOG.debug("{}: {}", key, value);
Field field = Settings.class.getField(key);
field.set(settings, typeCorrectedValue);
}
config.save();
} catch (Exception e) {
throw new ConfigWriteException(e);
}
}
private Object correctType(DataType type, Object value) {
Object corrected = value;
if (value == null) {
return null;
}
switch (type) {
case INTEGER:
corrected = Integer.parseInt(value.toString());
break;
case LONG:
corrected = Long.parseLong(value.toString());
break;
case BOOLEAN:
corrected = Boolean.parseBoolean(value.toString());
break;
case DOUBLE:
corrected = Double.parseDouble(value.toString());
break;
default:
break;
}
return corrected;
}
private static class ConfigWriteException extends Exception {
public ConfigWriteException(Exception e) {
super(e);
}
} }
} }

View File

@ -11,12 +11,12 @@ function loadConfig() {
} }
}).done(function(data, textStatus, jqXHR) { }).done(function(data, textStatus, jqXHR) {
if (textStatus === 'success') { if (textStatus === 'success') {
while (observableSettingsArray().length > 0) {
observableSettingsArray.pop();
}
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
let param = data[i]; let param = data[i];
param.ko_value = ko.observable(param.value); param.ko_value = ko.observable(param.value);
param.ko_value.subscribe(function(newValue) {
console.log("The person's new name is " + newValue);
});
observableSettingsArray.push(param); observableSettingsArray.push(param);
} }
} else { } else {
@ -36,7 +36,6 @@ function loadConfig() {
function saveConfig() { function saveConfig() {
try { try {
let msg = JSON.stringify(observableSettingsArray()); let msg = JSON.stringify(observableSettingsArray());
console.log(msg);
$.ajax({ $.ajax({
type : 'POST', type : 'POST',
url : '../config', url : '../config',
@ -49,17 +48,21 @@ function saveConfig() {
data : msg data : msg
}).done(function(data, textStatus, jqXHR) { }).done(function(data, textStatus, jqXHR) {
if (textStatus === 'success') { if (textStatus === 'success') {
//$.notify('Configuration saved', 'info'); loadConfig();
$.notify('Configuration saved. You might have to restart the server.', 'info');
} else { } else {
if (console) if (console)
console.log('request failed', data); console.log('request failed', data);
$.notify('Error: ' + jqXHR.responseText, 'error');
} }
}).fail(function(jqXHR, textStatus, errorThrown) { }).fail(function(jqXHR, textStatus, errorThrown) {
if (console) if (console)
console.log(jqXHR, textStatus, errorThrown); console.log(jqXHR, textStatus, errorThrown);
$.notify(errorThrown + ': ' + jqXHR.responseText, 'error');
}); });
} catch (e) { } catch (e) {
if (console) if (console)
console.log('Unexpected error', e); console.log('Unexpected error', e);
$.notify(errorThrown + ': ' + jqXHR.responseText, 'error');
} }
} }

View File

@ -77,7 +77,7 @@
<div id="alert-container"></div> <div id="alert-container"></div>
<div class="tab-content" id="myTabContent"> <div class="tab-content" id="myTabContent">
<section id="models" class="tab-pane fade active show" role="tabpanel" aria-labelledby="models-tab"> <section id="models" class="tab-pane fade" role="tabpanel" aria-labelledby="models-tab">
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-lg-10 mx-auto"> <div class="col-lg-10 mx-auto">
@ -172,7 +172,7 @@
</div> </div>
</div> </div>
</section> </section>
<section id="configuration" class="tab-pane fade" role="tabpanel" aria-labelledby="configuration-tab"> <section id="configuration" class="tab-pane fade active show" role="tabpanel" aria-labelledby="configuration-tab">
<div class="container"> <div class="container">
<form data-bind="submit: saveConfig"> <form data-bind="submit: saveConfig">
<div class="row"> <div class="row">
@ -470,6 +470,7 @@
loadConfig(); loadConfig();
} }
}); });
loadConfig();
}); });
</script> </script>