package tv.mineinthebox.simpleserver;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import tv.mineinthebox.simpleserver.events.SimpleServerEvent;
import tv.mineinthebox.simpleserver.events.manager.ServerEvent;
import tv.mineinthebox.simpleserver.events.manager.ServerListener;
public class SimpleServer implements Runnable, Server {
private final int port;
private final String servername;
private volatile ServerSocket server;
private volatile Thread thread;
private volatile Set<Tag> tags = new HashSet<Tag>();
private volatile SortedList<ServerListener> listeners = new SortedList<ServerListener>(new Comparator<ServerListener>() {
@Override
public int compare(ServerListener o1, ServerListener o2) {
Class<?> a = o1.getClass();
Class<?> b = o2.getClass();
int ap = 0;
int bp = 0;
for(Method method : a.getMethods()) {
if(method.isAnnotationPresent(ServerEvent.class)) {
ap += method.getAnnotation(ServerEvent.class).priority().getPriorityLevel();
}
}
for(Method method : b.getMethods()) {
if(method.isAnnotationPresent(ServerEvent.class)) {
bp += method.getAnnotation(ServerEvent.class).priority().getPriorityLevel();
}
}
return Integer.valueOf(ap).compareTo(bp);
}
});
/**
* creates a server object
*
* @param port the port number
* @param servername the servername
*/
public SimpleServer(int port, String servername) {
this.port = port;
this.servername = servername;
}
/**
* attempt to start the server under a async operation
*
* @throws Exception as the server cannot be started due a port is blocked or when the port is already in use
*/
public void startServer() throws Exception {
if(thread == null) {
this.server = new ServerSocket(port);
this.thread = new Thread(this);
thread.start();
}
}
/**
* attempt to shutdown the server
*
* @throws IOException when the thread was interrupted
*/
@SuppressWarnings("deprecation")
public void stopServer() throws IOException {
if(thread != null) {
this.server.close();
this.server = null;
this.thread.stop();
this.thread = null;
tags.clear();
listeners.clear();
}
}
@Override
public boolean isRunning() {
return (this.server != null && this.thread != null);
}
/**
* registers a listener to fire the events
*
* @param listener - the class implementing the listener
*/
public void addListener(ServerListener listener) {
listeners.add(listener);
}
/**
* removes a current listener
*
* @param listener - the class implementing the listener
*/
public void removeListener(ServerListener listener) {
listeners.remove(listener);
}
/**
* adds a template tag to the server instance
*
* @param tag the tag representing a dynamic or abstract replacement of a piece of content
*/
public void addTemplateTag(Tag tag) {
tags.add(tag);
}
/**
* removes a template tag from the server instance
*
* @param tag the tag representing a dynamic or abstract replacement of a piece of content
*/
public void removeTemplateTag(Tag tag) {
tags.remove(tag);
}
/**
* returns a list of template tags for this instance
*
* @return Set<Tag>
*/
public Set<Tag> getTemplateTags() {
return Collections.unmodifiableSet(tags);
}
private SimpleServerEvent callEvent(SimpleServerEvent event) {
for(ServerListener listener : listeners) {
Class<?> clazz = listener.getClass();
Method[] methods = clazz.getMethods();
for(Method method : methods) {
if(method.isAnnotationPresent(ServerEvent.class)) {
try {
method.invoke(listener, event);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
return event;
}
@Override
public void run() {
while(true) {
try {
Socket socket = server.accept();
DataOutputStream response = new DataOutputStream(socket.getOutputStream());
BufferedReader read = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = "";
String[] headerdata = new String[5];
int postindex = -1;
//read data from the clients header in a limited way to push it later inside HttpClient
while((line = read.readLine()) != null && (line.length() != 0)) {
if(line.startsWith("GET")) {
headerdata[0] = new String(line);
} else if(line.startsWith("POST")) {
headerdata[0] = new String(line);
} else if(line.startsWith("Host:")) {
headerdata[1] = new String(line);
} else if(line.startsWith("User-Agent:")) {
headerdata[2] = new String(line);
} else if(line.startsWith("Accept-Language:")) {
headerdata[3] = new String(line);
} else if(line.indexOf("Content-Length:") > -1) {
postindex = new Integer(line.substring(line.indexOf("Content-Length:") + 16, line.length())).intValue();
}
}
if(postindex > 0) {
char[] array = new char[postindex];
read.read(array, 0, postindex);
headerdata[4] = new String(array);
}
boolean isValidHeader = isValidHeader(headerdata);
if(isValidHeader) {
HttpClient client = new HttpClient(headerdata);
SimpleServerEvent event = null;
if(client.getUrl().isEmpty() || client.getUrl().equalsIgnoreCase("/")) {
event = new SimpleServerEvent(client, this, MimeType.MIME_HTML, socket.getInetAddress());
} else if(client.getUrl().endsWith(".html") || client.getUrl().endsWith(".htm") || client.getUrl().endsWith(".php")) {
event = new SimpleServerEvent(client, this, MimeType.MIME_HTML, socket.getInetAddress());
} else if(client.getUrl().endsWith(".jpg") || client.getUrl().endsWith(".gif") || client.getUrl().endsWith(".png")) {
event = new SimpleServerEvent(client, this, MimeType.MIME_JPEG, socket.getInetAddress());
} else if(client.getUrl().endsWith(".json")) {
event = new SimpleServerEvent(client, this, MimeType.MIME_JSON, socket.getInetAddress());
} else if(client.getUrl().endsWith(".xml")) {
event = new SimpleServerEvent(client, this, MimeType.MIME_XML, socket.getInetAddress());
} else {
event = new SimpleServerEvent(client, this, MimeType.MIME_PLAIN, socket.getInetAddress());
}
callEvent(event);
if(event.isCancelled() || !event.hasContents()) {
response.write("HTTP/1.1 404 Not Found\r\n".getBytes());
response.flush();
} else {
response.write("HTTP/1.1 200 OK\r\n".getBytes());
response.write(("Content-Type: " + event.getMimeType().getMimeType() + "\r\n").getBytes());
response.write(("Content-Length: " + event.getContents().length + "\n\r").getBytes());
response.write("\n".getBytes());
response.flush();
response.write(event.getContents());
response.flush();
}
}
response.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
private boolean isValidHeader(String[] data) {
if(data[0] == null) {
return false;
} else if(data[1] == null) {
return false;
} else if(data[2] == null) {
return false;
} else if(data[3] == null) {
return false;
}
return true;
}
/**
* returns the name of the server
*
* @return String
*/
@Override
public String getName() {
return this.servername;
}
/**
* returns the port
*
* @return int
*/
@Override
public int getPort() {
return port;
}
}