aboutsummaryrefslogtreecommitdiff
path: root/src/uk/org/ury/server
diff options
context:
space:
mode:
Diffstat (limited to 'src/uk/org/ury/server')
-rw-r--r--src/uk/org/ury/server/RequestHandler.java44
-rw-r--r--src/uk/org/ury/server/Server.java432
-rw-r--r--src/uk/org/ury/server/ServerProtocol.java37
-rw-r--r--src/uk/org/ury/server/ServerRequestHandler.java96
-rw-r--r--src/uk/org/ury/server/exceptions/HandleFailureException.java46
-rw-r--r--src/uk/org/ury/server/protocol/Directive.java69
6 files changed, 724 insertions, 0 deletions
diff --git a/src/uk/org/ury/server/RequestHandler.java b/src/uk/org/ury/server/RequestHandler.java
new file mode 100644
index 0000000..a8f434d
--- /dev/null
+++ b/src/uk/org/ury/server/RequestHandler.java
@@ -0,0 +1,44 @@
+package uk.org.ury.server;
+
+import java.util.List;
+import java.util.Map;
+
+import uk.org.ury.server.exceptions.HandleFailureException;
+
+
+/**
+ * Interface for classes that can handle requests addressed to their
+ * class name from the main server.
+ *
+ * For an example of how to implement a RequestHandler, see
+ * ServerRequestHandler.
+ *
+ * @author Matt Windsor
+ */
+
+public interface RequestHandler
+{
+ /**
+ * Handle a server GET request (that is, a request for data
+ * output).
+ *
+ * @param parameters A key-value map of parameters supplied with the
+ * server request. Typically, the "function"
+ * parameter will detail the function that the
+ * request handler is expected to perform.
+ *
+ * @param server The server from which the request originated.
+ * This will be able to provide the handler with
+ * pooled resources, for example the database.
+ *
+ * @return A list of lines to return in the body of the
+ * server's response to the client.
+ *
+ * @throws HandleFailureException if the handler cannot
+ * handle the request.
+ */
+
+ public List<String>
+ handleGetRequest (Map<String, String> parameters, Server server)
+ throws HandleFailureException;
+}
diff --git a/src/uk/org/ury/server/Server.java b/src/uk/org/ury/server/Server.java
new file mode 100644
index 0000000..8a25916
--- /dev/null
+++ b/src/uk/org/ury/server/Server.java
@@ -0,0 +1,432 @@
+/**
+ *
+ */
+package uk.org.ury.server;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+
+import java.net.MalformedURLException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URL;
+import java.net.URLDecoder;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+
+import uk.org.ury.config.ConfigReader;
+import uk.org.ury.database.DatabaseDriver;
+import uk.org.ury.database.UserClass;
+import uk.org.ury.database.exceptions.ConnectionFailureException;
+import uk.org.ury.database.exceptions.MissingCredentialsException;
+import uk.org.ury.server.exceptions.HandleFailureException;
+
+/**
+ * The unified URY server, accepting requests over HTTP.
+ *
+ * @author Matt Windsor
+ */
+
+public class Server
+{
+
+ private ServerSocket serverSocket;
+
+ private static final String HTTP_VERSION = "HTTP/1.1";
+ private static final String SERVER_VERSION = "SLUT 0.0";
+ private static final String DOCTYPE =
+ "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\""
+ + "\"http://www.w3.org/TR/html4/strict.dtd\">";
+ private static final String INDEX_HTML =
+ "\n<html>"
+ + "\n <head>"
+ + "\n <title>" + SERVER_VERSION + "</title>"
+ + "\n </head>"
+ + "\n <body>"
+ + "\n <h1>Welcome to the " + SERVER_VERSION + " server</h1>"
+ + "\n <p>This server exposes a class-based API for accessing"
+ + "\n the internals of the " + SERVER_VERSION + " system.</p>"
+ + "\n <p>See the documentation for details.</p>"
+ + "\n </body>"
+ + "\n</html>";
+
+
+ /**
+ * The main method, which serves to create a server.
+ *
+ * @param args The argument vector.
+ */
+
+ public static void
+ main (String[] args)
+ {
+ Server srv = new Server ();
+ srv.run ();
+ }
+
+
+ /**
+ * Run the server.
+ */
+
+ private void
+ run ()
+ {
+ try
+ {
+ serverSocket = new ServerSocket (8000);
+ }
+ catch (IOException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace ();
+ }
+
+ Socket clientSocket = null;
+
+ while (true)
+ {
+ System.out.println ("Accepting connections... bring 'em on!");
+
+ try
+ {
+ clientSocket = serverSocket.accept ();
+ }
+ catch (IOException e)
+ {
+ System.out.println ("SLUT: Accept failed on port 8000. I'm bailing.");
+ System.exit (-1);
+ }
+
+ try
+ {
+ doConnection (clientSocket);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace ();
+ }
+ finally
+ {
+ try
+ {
+ clientSocket.close ();
+ }
+ catch (IOException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+
+ public void
+ doConnection (Socket clientSocket)
+ throws IOException
+ {
+ PrintWriter out = new PrintWriter (clientSocket.getOutputStream(), true);
+ BufferedReader in = new BufferedReader (new InputStreamReader (
+ clientSocket.getInputStream()));
+ String inputLine;
+
+ //initiate conversation with client
+
+ List<String> buffer = new ArrayList<String> ();
+
+
+ for (inputLine = in.readLine (); inputLine != null; inputLine = in.readLine ())
+ {
+ if (inputLine.equals (""))
+ break;
+
+ buffer.add (inputLine);
+
+ if (inputLine.startsWith ("Expect:")
+ && inputLine.split (":")[1].startsWith ("100-continue"))
+ out.print ("HTTP/1.1 100 Continue\n\r\n");
+
+ out.flush ();
+ }
+
+ processBuffer (buffer, out);
+
+ out.flush ();
+ out.close ();
+ in.close ();
+
+ System.out.println ("Just finished with this one...");
+ }
+
+
+ public void
+ processBuffer (List<String> buffer, PrintWriter out)
+ {
+ String requestStart = buffer.get (0);
+
+ System.out.println (requestStart);
+
+ if (requestStart.startsWith ("GET"))
+ {
+ System.out.println ("That was a GET...");
+ handleGet (buffer, out);
+ }
+ else
+ {
+ System.out.println ("Uh-oh! I don't know what to do!");
+
+ out.println (HTTP_VERSION + " 501 Not Implemented");
+ out.println ("Connection: close");
+ out.print ("\r\n");
+ }
+ }
+
+ public void
+ handleGet (List<String> buffer, PrintWriter out)
+ {
+ String[] getsplit = buffer.get (0).split (" ");
+ String path = getsplit[1];
+
+ if (path.equals ("/index.html")
+ || path.equals ("/"))
+ {
+ // Someone's trying to get the index page!
+ // Humour them.
+
+ out.println (HTTP_VERSION + " 200 OK");
+ out.println ("Connection: close");
+ out.print ("\r\n");
+
+ out.println (DOCTYPE + INDEX_HTML);
+ }
+ else
+ {
+ // Convert this into a URL and fan out the various parts of it.
+
+ URL pathURL = null;
+
+ try
+ {
+ pathURL = new URL ("http://localhost" + path);
+ }
+ catch (MalformedURLException e1)
+ {
+ serveError (400, "Malformed URL.", out);
+ }
+
+
+ String className = "uk.org.ury" + pathURL.getPath ().replace ('/', '.');
+ System.out.println (className);
+ Class<?> newClass = null;
+
+
+ try
+ {
+ newClass = Class.forName (className);
+ }
+ catch (ClassNotFoundException e)
+ {
+ serveError (404, "Class " + className + " not found.", out);
+ return;
+ }
+
+ if (RequestHandler.class.isAssignableFrom (newClass))
+ {
+ String queryString = pathURL.getQuery ();
+ Map<String, String> parameters;
+ try
+ {
+ parameters = parseQueryString (queryString);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ serveError (500, "URL decode failure for class "
+ + className
+ + " (" + e.getMessage () + ").", out);
+ return;
+ }
+
+ List<String> response;
+
+ try
+ {
+ RequestHandler srh = ((RequestHandler) newClass.newInstance ());
+ response = srh.handleGetRequest (parameters, this);
+ }
+ catch (InstantiationException e)
+ {
+ serveError (500, "Instantiation exception for class "
+ + className
+ + " (" + e.getMessage () + ").", out);
+ return;
+ }
+ catch (IllegalAccessException e)
+ {
+ serveError (500, "Illegal access exception for class "
+ + className
+ + " (" + e.getMessage () + ").", out);
+ return;
+ }
+ catch (HandleFailureException e)
+ {
+ serveError (500, "Failed to handle request for class "
+ + className
+ + " (" + e.getMessage () + ").", out);
+ return;
+ }
+
+ // If we made it this far, the response is A-OK.
+
+ out.println (HTTP_VERSION + " 200 OK");
+ out.println ("Content-Type: text/plain");
+ out.print ("\r\n");
+
+ out.println ("START");
+
+ for (String line : response)
+ out.println (line);
+
+ out.println ("END");
+
+ out.flush ();
+ }
+ else
+ {
+ serveError (404, "Class " + className + " does not handle requests.", out);
+ return;
+ }
+ }
+ }
+
+
+ /**
+ * Serve a HTTP plain-text error.
+ *
+ * @param code HTTP status code to use.
+ * @param reason The reason to display to the client.
+ * @param out The output stream.
+ */
+
+ private void
+ serveError (int code, String reason, PrintWriter out)
+ {
+ String errorStatus = "";
+
+ switch (code)
+ {
+ case 400:
+ errorStatus = "400 Bad Request";
+ break;
+ case 404:
+ errorStatus = "404 Not Found";
+ break;
+ default:
+ errorStatus = "500 Internal Server Error";
+ break;
+ }
+
+ out.println (HTTP_VERSION + " " + errorStatus);
+ out.println ("Content-Type: text/plain");
+ out.println ("Connection: close");
+ out.print ("\r\n");
+
+ out.println ("ERROR: " + reason);
+ out.flush ();
+ }
+
+
+ /**
+ * Parse a query string, populating a key-value map of the
+ * URL-unescaped results.
+ *
+ * @param query The query string to parse.
+ *
+ * @return A map associating parameter keys and values.
+ *
+ * @throws UnsupportedEncodingException if the URL decoder
+ * fails.
+ */
+
+ public Map<String, String>
+ parseQueryString (String query)
+ throws UnsupportedEncodingException
+ {
+ Map<String, String> params = new HashMap<String, String> ();
+
+ // At least one parameter
+ if (query != null
+ && query.endsWith ("&") == false)
+ {
+ String[] qsplit = {query};
+
+ // More than one parameter - split the query.
+ if (query.contains ("&"))
+ qsplit = query.split ("&");
+
+
+ for (String param : qsplit)
+ {
+ // Has a value
+ if (param.contains ("=")
+ && param.endsWith ("=") == false)
+ {
+ String[] paramsplit = param.split ("=");
+ params.put (URLDecoder.decode (paramsplit[0], "UTF-8"),
+ URLDecoder.decode (paramsplit[1], "UTF-8"));
+ }
+ // Doesn't have a value
+ else if (param.contains ("=") == false)
+ {
+ params.put (URLDecoder.decode (param, "UTF-8"), null);
+ }
+ }
+ }
+
+ return params;
+ }
+
+
+ /**
+ * Get a database connection using the given user class.
+ *
+ * @param userClass The user class to get a connection for.
+ *
+ * @return a database connection, which may or may not
+ * have been created on this call.
+ *
+ * @throw MissingCredentialsException if the credentials
+ * for the given userclass are missing.
+ *
+ * @throw ConnectionFailureException if the connection
+ * failed.
+ */
+
+ public DatabaseDriver
+ getDatabaseConnection (UserClass userClass)
+ throws MissingCredentialsException, ConnectionFailureException
+ {
+ // TODO: Singleton
+
+ ConfigReader config = new ConfigReader ("res/conf.xml");
+
+ return new DatabaseDriver (config, UserClass.READ_ONLY);
+ }
+
+
+ /**
+ * @return the version string of the server.
+ */
+
+ public String
+ getVersion ()
+ {
+ return SERVER_VERSION;
+ }
+}
diff --git a/src/uk/org/ury/server/ServerProtocol.java b/src/uk/org/ury/server/ServerProtocol.java
new file mode 100644
index 0000000..92d2955
--- /dev/null
+++ b/src/uk/org/ury/server/ServerProtocol.java
@@ -0,0 +1,37 @@
+package uk.org.ury.server;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The BAPS server protocol (a minimal implementation of HTTP 1.1) handler.
+ *
+ * @author Matt Windsor
+ *
+ */
+
+public class ServerProtocol
+{
+ public static final String GET_HEADER = "HTTP/1.1 200 OK\n";
+
+ public List<String> buffer;
+
+
+ public
+ ServerProtocol ()
+ {
+ buffer = new ArrayList<String> ();
+ }
+
+ public String
+ processInput (String string)
+ {
+ if (string.equals (""))
+ {
+ System.out.println ("Bingo!");
+ return "HTTP/1.1 200 OK\nConnection: Close\n\r\n<html><head></head><body>poo</body></html>\n\r\n";
+ }
+ return "";
+ }
+
+}
diff --git a/src/uk/org/ury/server/ServerRequestHandler.java b/src/uk/org/ury/server/ServerRequestHandler.java
new file mode 100644
index 0000000..f67f927
--- /dev/null
+++ b/src/uk/org/ury/server/ServerRequestHandler.java
@@ -0,0 +1,96 @@
+/**
+ *
+ */
+package uk.org.ury.server;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import uk.org.ury.server.Server;
+import uk.org.ury.server.RequestHandler;
+import uk.org.ury.server.exceptions.HandleFailureException;
+
+
+/**
+ * A request handler for server queries.
+ *
+ * @author Matt Windsor
+ */
+
+public class ServerRequestHandler implements RequestHandler
+{
+ /**
+ * Handle a server GET request (that is, a request for data
+ * output).
+ *
+ * @param parameters A key-value map of parameters supplied with
+ * the server request. Typically, the function
+ * parameter will detail the function that the
+ * request handler is expected to perform.
+ *
+ * @param server The server from which the request originated.
+ * This will be able to provide the handler with
+ * pooled resources, for example the database.
+ *
+ * @return A list of lines to return in the body of the
+ * server's response to the client.
+ *
+ * @throws HandleFailureException if the handler cannot
+ * handle the request.
+ */
+
+ @Override
+ public List<String>
+ handleGetRequest (Map<String, String> parameters, Server server)
+ throws HandleFailureException
+ {
+ List<String> response = new ArrayList<String> ();
+
+ if (parameters.containsKey ("function"))
+ {
+ String function = parameters.get ("function");
+
+ if (function.equals ("info"))
+ {
+ getInfo (response, server);
+ }
+ else if (function.equals ("help"))
+ {
+ response.add ("INFO: Available functions:");
+ response.add ("INFO: info - Get server information.");
+ }
+ else if (function.equals ("test"))
+ response.add ("INFO: Test succeeded.");
+ else
+ throw new HandleFailureException ("Unknown function: "
+ + function + ". (Try 'function=help'.)");
+
+ }
+ else
+ throw new HandleFailureException ("No function provided. (Try 'function=help'.)");
+
+ return response;
+ }
+
+
+ /**
+ * Retrieve information about the server.
+ *
+ * @param response The response list to populate.
+ *
+ * @param server The server providing database resources.
+ *
+ * @throws HandleFailureException if an error occurs
+ * that thwarts the handling of the request.
+ */
+
+ private void
+ getInfo (List<String> response, Server server)
+ throws HandleFailureException
+ {
+ response.add ("INFO: University Radio York BAPS Replacement");
+ response.add ("INFO: Server version is " + server.getVersion ());
+ }
+
+}
diff --git a/src/uk/org/ury/server/exceptions/HandleFailureException.java b/src/uk/org/ury/server/exceptions/HandleFailureException.java
new file mode 100644
index 0000000..063cc52
--- /dev/null
+++ b/src/uk/org/ury/server/exceptions/HandleFailureException.java
@@ -0,0 +1,46 @@
+/**
+ *
+ */
+package uk.org.ury.server.exceptions;
+
+/**
+ * Generic exception thrown when a server request handler fails to
+ * handle a request.
+ *
+ * @author Matt Windsor
+ */
+
+public class HandleFailureException extends Exception
+{
+
+ /**
+ * Change this! ---v
+ */
+
+ private static final long serialVersionUID = -397479334359858162L;
+
+
+ /**
+ * Construct a new HandleFailureException with a
+ * default reason.
+ */
+
+ public
+ HandleFailureException ()
+ {
+ super ("Server request handler failed to handle the request.");
+ }
+
+
+ /**
+ * Construct a new HandleFailureException.
+ *
+ * @param reason The explanation for the exception.
+ */
+
+ public
+ HandleFailureException (String reason)
+ {
+ super (reason);
+ }
+}
diff --git a/src/uk/org/ury/server/protocol/Directive.java b/src/uk/org/ury/server/protocol/Directive.java
new file mode 100644
index 0000000..0d7a162
--- /dev/null
+++ b/src/uk/org/ury/server/protocol/Directive.java
@@ -0,0 +1,69 @@
+/**
+ *
+ */
+package uk.org.ury.server.protocol;
+
+/**
+ * Directives supported by the protocol.
+ *
+ * @author Matt Windsor
+ */
+
+public enum Directive
+ {
+ // ID String representation Singleton?
+
+ /** Directive marking the start of an item block. */
+ ITEM_START ("ITEM-START" , false),
+
+ /** Directive marking a property inside an item block. */
+ ITEM_PROPERTY ("PROP" , false),
+
+ /** Directive marking the end of an item block. */
+ ITEM_END ("ITEM-END" , false);
+
+
+
+ private String strRep; // String representation
+ private boolean isSingleton; // Is a singleton?
+
+
+ /**
+ * Construct a new Directive.
+ *
+ * @param strRep The string representation of the Directive.
+ *
+ * @param isSingleton If true, then the Directive accepts no
+ * properties. If false, then the Directive
+ * must be provided with at least one.
+ */
+
+ private
+ Directive (String strRep, boolean isSingleton)
+ {
+ this.strRep = strRep;
+ this.isSingleton = isSingleton;
+ }
+
+
+ /**
+ * @return the string representation.
+ */
+
+ public String
+ toString ()
+ {
+ return strRep;
+ }
+
+
+ /**
+ * @return true if the directive has no properties.
+ */
+
+ public Boolean
+ isSingleton ()
+ {
+ return isSingleton;
+ }
+ }