/**
*
*/
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 org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.ParseException;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
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.BadRequestException;
import uk.org.ury.server.exceptions.HandleFailureException;
import uk.org.ury.server.exceptions.HandlerNotFoundException;
import uk.org.ury.server.exceptions.HandlerSetupFailureException;
import uk.org.ury.server.exceptions.HandlingException;
import uk.org.ury.server.exceptions.NotAHandlerException;
/**
* The unified URY server, accepting requests over HTTP.
*
* @author Matt Windsor
*/
public class Server
{
private ServerSocket serverSocket;
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);
HttpResponse response;
if (requestStart.startsWith ("GET"))
{
System.out.println ("That was a GET...");
try
{
response = handleGet (buffer);
}
catch (HandlerNotFoundException e)
{
// TODO: log
response = serveError (HttpStatus.SC_NOT_FOUND,
e.getMessage ());
}
catch (BadRequestException e)
{
// TODO: log
response = serveError (HttpStatus.SC_BAD_REQUEST,
e.getMessage ());
}
catch (HandlingException e)
{
response = serveError (HttpStatus.SC_INTERNAL_SERVER_ERROR,
e.getMessage ());
}
}
else
{
System.out.println ("Uh-oh! I don't know what to do!");
response = serveError (HttpStatus.SC_NOT_IMPLEMENTED,
"Feature not implemented yet.");
}
// Now send the response.
for (Header h : response.getAllHeaders ())
{
out.println (h);
}
try
{
out.print (EntityUtils.toString (response.getEntity ()));
}
catch (ParseException e)
{
// TODO Auto-generated catch block
e.printStackTrace ();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace ();
}
}
/**
* Handle a HTTP GET request.
*
* @param buffer The HTTP request as a list of strings.
*
* @return The HTTP response.
*
* @throws HandlerNotFoundException if the client requested
* a request handler that could not be found on the
* class path.
*
* @throws HandlerSetupFailureException if the handler was
* found but could not be set up (eg does not
* implement appropriate interface or cannot be
* instantiated).
*
* @throws HandleFailureException if an appropriate handler
* was contacted, but it failed to process the
* request.
*
* @throws BadRequestException if the request was malformed
* or invalid.
*
* @throws NotAHandlerException if the class requested to
* handle the request is not a handler.
*/
public HttpResponse
handleGet (List buffer)
throws HandlerNotFoundException, HandlerSetupFailureException,
HandleFailureException, BadRequestException, NotAHandlerException
{
HttpResponse response = null;
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.
response = new BasicHttpResponse (HttpVersion.HTTP_1_1,
HttpStatus.SC_OK,
"OK");
StringEntity entity = null;
try
{
entity = new StringEntity (DOCTYPE + INDEX_HTML);
}
catch (UnsupportedEncodingException e)
{
throw new HandlerSetupFailureException ("(Index page)", e);
}
response.setEntity (entity);
}
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 e)
{
throw new BadRequestException (e);
}
String className = "uk.org.ury" + pathURL.getPath ().replace ('/', '.');
System.out.println (className);
Class> newClass = null;
try
{
newClass = Class.forName (className);
}
catch (ClassNotFoundException e)
{
throw new HandlerNotFoundException (className, e);
}
// Check for error (response set) here.
if (response == null
&& RequestHandler.class.isAssignableFrom (newClass))
{
String queryString = pathURL.getQuery ();
Map parameters;
try
{
parameters = parseQueryString (queryString);
}
catch (UnsupportedEncodingException e)
{
throw new HandlerSetupFailureException (className, e);
}
List content;
try
{
RequestHandler srh = ((RequestHandler) newClass.newInstance ());
content = srh.handleGetRequest (parameters, this);
}
catch (InstantiationException e)
{
throw new HandlerSetupFailureException (className, e);
}
catch (IllegalAccessException e)
{
throw new HandlerSetupFailureException (className, e);
}
// Everything seems OK, so make the response.
response = new BasicHttpResponse (HttpVersion.HTTP_1_1,
HttpStatus.SC_OK,
"OK");
String entityString = "";
entityString += "START" + "\r\n";
for (String line : content)
entityString += line + "\r\n";
entityString += "END";
StringEntity entity = null;
try
{
entity = new StringEntity (entityString);
}
catch (UnsupportedEncodingException e)
{
throw new HandlerSetupFailureException (className, e);
}
entity.setContentType (HTTP.PLAIN_TEXT_TYPE);
response.setEntity (entity);
}
else
throw new NotAHandlerException (className);
}
return response;
}
/**
* Serve a HTTP plain-text error as a HTTP response.
*
* @param code HTTP status code to use.
* @param reason The reason to display to the client.
*
* @return the HTTP response for the error.
*/
private HttpResponse
serveError (int code, String reason)
{
// Get the reason string to put in the error response.
// TODO: standards?
String statusReason = "";
switch (code)
{
case HttpStatus.SC_BAD_REQUEST:
statusReason = "Bad Request";
break;
case HttpStatus.SC_NOT_FOUND:
statusReason = "Not Found";
break;
default:
case HttpStatus.SC_INTERNAL_SERVER_ERROR:
statusReason = "Internal Server Error";
break;
}
HttpResponse response = new BasicHttpResponse (HttpVersion.HTTP_1_1,
code, statusReason);
StringEntity entity = null;
try
{
entity = new StringEntity ("ERROR: " + reason);
}
catch (UnsupportedEncodingException e)
{
// TODO Auto-generated catch block
e.printStackTrace ();
}
if (entity != null)
{
entity.setContentType (HTTP.PLAIN_TEXT_TYPE);
response.setEntity (entity);
}
return response;
}
/**
* 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;
}
}