Another checkpoint commit, just fleshing out some of the boilerplate code for request handling

This commit is contained in:
skogaby 2019-01-03 13:24:16 -06:00
parent 6494bbb50b
commit 4d504ce04e
4 changed files with 105 additions and 13 deletions

View File

@ -15,7 +15,6 @@ repositories {
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
compile group: 'commons-io', name: 'commons-io', version: '2.6'
compile group: 'com.sparkjava', name: 'spark-core', version: '2.7.2'
compile group: 'com.google.guava', name: 'guava', version: '23.5-jre'
}

View File

@ -1,12 +1,20 @@
package com.buttongames.butterfly.http;
import com.buttongames.butterfly.compression.Lz77;
import com.buttongames.butterfly.encryption.Rc4;
import com.buttongames.butterfly.http.exception.InvalidRequestMethodException;
import com.buttongames.butterfly.http.exception.InvalidRequestModelException;
import com.buttongames.butterfly.http.exception.InvalidRequestModuleException;
import com.buttongames.butterfly.http.exception.MismatchedRequestUriException;
import com.buttongames.butterfly.http.handlers.ServicesRequestHandler;
import com.buttongames.butterfly.xml.BinaryXmlUtils;
import com.google.common.collect.ImmutableSet;
import spark.Request;
import spark.utils.StringUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import static spark.Spark.exception;
import static spark.Spark.halt;
@ -17,12 +25,34 @@ import static spark.Spark.threadPool;
/**
* The main HTTP server. This class is responsible for the top-level handling of incoming
* requests, then delegates the responsbility to the appropriate handler.
* requests, then delegates the responsibility to the appropriate handler.
* @author skogaby (skogabyskogaby@gmail.com)
*/
public class ButterflyHttpServer {
/**
* Name of the HTTP header that contains the crypto key, if present.
*/
private static final String CRYPT_KEY_HEADER = "X-Eamuse-Info";
/**
* Name of the HTTP header that says whether the packet is compressed.
*/
private static final String COMPRESSION_HEADER = "X-Compress";
/**
* Value for the X-Compress header to indicate the packet is compressed.
*/
private static final String LZ77_COMPRESSION = "lz77";
/**
* Static set of all the models this server supports.
*/
private static final ImmutableSet<String> SUPPORTED_MODELS;
/**
* Static set of all the modules this server supports.
*/
private static final ImmutableSet<String> SUPPORTED_MODULES;
// Do a static setup of our supported models, modules, etc.
@ -32,10 +62,16 @@ public class ButterflyHttpServer {
SUPPORTED_MODULES = ImmutableSet.of("services");
}
/**
* Constructor.
*/
public ButterflyHttpServer() {
}
/**
* Configures the routes on our server and begins listening.
*/
public void startServer() {
// configure the server properties
int maxThreads = 20;
@ -78,6 +114,8 @@ public class ButterflyHttpServer {
((exception, request, response) -> halt(400, "Invalid request model.")));
exception(InvalidRequestModuleException.class,
((exception, request, response) -> halt(400, "Invalid request module.")));
exception(MismatchedRequestUriException.class,
(((exception, request, response) -> halt(400, "Request URI does not match request body"))));
}
/**
@ -87,29 +125,47 @@ public class ButterflyHttpServer {
* @param request The request to validate and unpack
* @return A string representing the plaintext version of the packet, in XML format.
*/
private String validateAndUnpackRequest(Request request) {
private String validateAndUnpackRequest(Request request) throws GeneralSecurityException, IOException {
final String requestUriModel = request.queryParams("model");
final String requestUriModule = request.queryParams("module");
final String requestUriMethod = request.queryParams("method");
// validate the model is supported
// 1) validate the model is supported
if (!SUPPORTED_MODELS.contains(requestUriModel)) {
throw new InvalidRequestModelException();
}
// validate the module is supported
// 2) validate the module is supported
if (!SUPPORTED_MODULES.contains(requestUriModule)) {
throw new InvalidRequestModuleException();
}
// validate that the request URI matches the request body
// TODO: Implement
if (false) {
throw new MismatchedRequestUriException();
// 3) validate that the request URI matches the request body;
final String encryptionKey = request.headers(CRYPT_KEY_HEADER);
final String compressionScheme = request.headers(COMPRESSION_HEADER);
byte[] reqBody = request.bodyAsBytes();
// decrypt the request if it's encrypted
if (!StringUtils.isBlank(encryptionKey)) {
reqBody = Rc4.decrypt(reqBody, encryptionKey);
}
// return the request body
// decompress the request if it's compressed
if (!StringUtils.isBlank(compressionScheme) &&
compressionScheme.equals(LZ77_COMPRESSION)) {
reqBody = Lz77.decompress(reqBody);
}
// convert the body to plaintext XML if it's binary XML
if (BinaryXmlUtils.isBinaryXML(reqBody)) {
reqBody = BinaryXmlUtils.binaryToXml(reqBody);
}
// read the request body into an XML document and check its properties
// to verify it matches the request URI
// TODO: Implement
return "";
// 4) return the XML document
return new String(reqBody, StandardCharsets.UTF_8);
}
}

View File

@ -1,6 +1,6 @@
package com.buttongames.butterfly.util;
import org.apache.commons.io.IOUtils;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.io.InputStream;
@ -51,7 +51,7 @@ public class CollectionUtils {
* @throws IOException
*/
public static byte[] readInputStream(final InputStream is) throws IOException {
return IOUtils.toByteArray(is);
return ByteStreams.toByteArray(is);
}
}

View File

@ -5,6 +5,7 @@ import com.buttongames.butterfly.util.CollectionUtils;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* Class to help translating between binary and plaintext XML. Right now it's a very dumb
@ -16,6 +17,18 @@ import java.io.IOException;
*/
public class BinaryXmlUtils {
/**
* First bytes of a plaintext XML response, so we know if an array needs to be converted
* to/from binary XML.
*/
private static final byte[] XML_PREFIX = "<?xml".getBytes(StandardCharsets.UTF_8);
/**
* Converts the input to plaintext XML from binary.
* @param input
* @return
* @throws IOException
*/
public static byte[] binaryToXml(final byte[] input) throws IOException {
final String tmpPath = System.getProperty("user.home") + "\\tmpkbin";
final DataOutputStream dos = new DataOutputStream(new FileOutputStream(tmpPath));
@ -29,7 +42,31 @@ public class BinaryXmlUtils {
return output;
}
/**
* Converts the input to binary XML from plaintext XML.
* @param input
* @return
* @throws IOException
*/
public static byte[] xmlToBinary(final byte[] input) throws IOException {
return binaryToXml(input);
}
/**
* Says whether or not the input is binary XML.
* @param input
* @return
*/
public static boolean isBinaryXML(final byte[] input) {
boolean isBinary = false;
for (int i = 0; i < XML_PREFIX.length; i++) {
if (input[i] != XML_PREFIX[i]) {
isBinary = true;
break;
}
}
return isBinary;
}
}