aboutsummaryrefslogtreecommitdiff
path: root/src/uk/org/ury/server/Server.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/uk/org/ury/server/Server.java')
-rw-r--r--src/uk/org/ury/server/Server.java432
1 files changed, 432 insertions, 0 deletions
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;
+ }
+}