/** * */ 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 = ""; private static final String INDEX_HTML = "\n" + "\n " + "\n " + SERVER_VERSION + "" + "\n " + "\n " + "\n

Welcome to the " + SERVER_VERSION + " server

" + "\n

This server exposes a class-based API for accessing" + "\n the internals of the " + SERVER_VERSION + " system.

" + "\n

See the documentation for details.

" + "\n " + "\n"; /** * 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 buffer = new ArrayList (); 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 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 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 parameters; try { parameters = parseQueryString (queryString); } catch (UnsupportedEncodingException e) { serveError (500, "URL decode failure for class " + className + " (" + e.getMessage () + ").", out); return; } List 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 parseQueryString (String query) throws UnsupportedEncodingException { Map params = new HashMap (); // 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; } }