Abstracted the HTTP server with TCP Network classes and added an SSDP service

This commit is contained in:
Ziver Koc 2010-01-31 18:10:00 +00:00
parent b3ad292ff9
commit 45f514fc27
25 changed files with 1645 additions and 688 deletions

View file

@ -0,0 +1,323 @@
package zutil.network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import zutil.MultiPrintStream;
import zutil.network.http.HTTPHeaderParser;
import zutil.network.http.HttpPrintStream;
import zutil.network.threaded.ThreadedUDPNetworkThread;
import zutil.network.threaded.ThreadedUDPNetwork;
import zutil.wrapper.StringOutputStream;
/**
* A Server class that announces an service by the SSDP
* protocol specified at:
* http://coherence.beebits.net/chrome/site/draft-cai-ssdp-v1-03.txt
* ftp://ftp.pwg.org/pub/pwg/www/hypermail/ps/att-0188/01-psi_SSDP.pdf
*
* @author Ziver
*
* ********* Message clarification:
* ****** Incoming:
* ST: Search Target, this is object of the discovery request, (e.g., ssdp:all, etc.)
* HOST: This is the SSDP multicast address
* MAN: Description of packet type, (e.g., "ssdp:discover", )
* MX: Wait these few seconds and then send response
*
* ****** Outgoing:
* EXT: required by HTTP - not used with SSDP
* SERVER: informational
* LOCATION: This is the URL to request the QueryEndpointsInterface endpoint
* USN: advertisement UUID
* CACHE-CONTROL: max-age = seconds until advertisement expires
* NT: Notify target same as ST
* NTS: same as Man but for Notify messages
*/
public class SSDPServer extends ThreadedUDPNetwork implements ThreadedUDPNetworkThread{
public static final String SERVER_INFO = "SSDP Java Server by Ziver Koc";
public static final int DEFAULT_CACHE_TIME = 60*30; // 30 min
public static final int BUFFER_SIZE = 512;
public static final String SSDP_MULTICAST_ADDR = "239.255.255.250";
public static final int SSDP_PORT = 1900;
// instance specific values
private int cache_time;
private NotifyTimer notifyTimer = null;
/** HashMap that contains services as < SearchTargetName, Location > */
private HashMap<String, String> services;
/** A Map of all the used USN:s */
private HashMap<String, String> usn_map;
public static void main(String[] args) throws IOException{
SSDPServer ssdp = new SSDPServer();
ssdp.addService("upnp:rootdevice", "nowhere");
ssdp.start();
MultiPrintStream.out.println("SSDP Server running");
}
public SSDPServer() throws IOException{
super( null, SSDP_PORT, SSDP_MULTICAST_ADDR );
super.setThread( this );
services = new HashMap<String, String>();
usn_map = new HashMap<String, String>();
setChacheTime( DEFAULT_CACHE_TIME );
enableNotify( true );
}
/**
* Adds an service that will be announced.
*
* @param searchTarget is the ST value in SSDP
* @param location is the location of the service
*/
public void addService(String searchTarget, String location){
services.put( searchTarget, location );
}
/**
* Remove a service from being announced. This function will
* send out an byebye message to the clients that the service is down.
*
* @param searchTarget is the ST value in SSDP
*/
public void removeService(String searchTarget){
sendByeBye( searchTarget );
services.remove( searchTarget );
}
/**
* Sets the cache time that will be sent to
* the clients. If notification is enabled then an
* notification message will be sent every cache_time/2 seconds
*
* @param time is the time in seconds
*/
public void setChacheTime(int time){
cache_time = time;
if( isNotifyEnabled() ){
enableNotify(false);
enableNotify(true);
}
}
/**
* Enable or disable notification messages to clients
* every cache_time/2 seconds
*/
public void enableNotify(boolean enable){
if( enable && notifyTimer==null ){
notifyTimer = new NotifyTimer();
Timer timer = new Timer();
timer.schedule(new NotifyTimer(), 0, cache_time*1000/2);
}else if( !enable && notifyTimer!=null ){
notifyTimer.cancel();
notifyTimer = null;
}
}
/**
* @return if notification messages is enabled
*/
public boolean isNotifyEnabled(){
return notifyTimer != null;
}
/**
* Handles the incoming packets like this:
*
* ***** REQUEST:
* M-SEARCH * HTTP/1.1
* S: uuid:ijklmnop-7dec-11d0-a765-00a0c91e6bf6
* Host: 239.255.255.250:reservedSSDPport
* Man: "ssdp:discover"
* ST: ge:fridge
* MX: 3
*
* ***** RESPONSE;
* HTTP/1.1 200 OK
* S: uuid:ijklmnop-7dec-11d0-a765-00a0c91e6bf6
* Ext:
* Cache-Control: no-cache="Ext", max-age = 5000
* ST: ge:fridge
* USN: uuid:abcdefgh-7dec-11d0-a765-00a0c91e6bf6
* AL: <blender:ixl><http://foo/bar>
*
*/
public void receivedPacket(DatagramPacket packet, ThreadedUDPNetwork network) {
try {
String msg = new String( packet.getData() );
HTTPHeaderParser header = new HTTPHeaderParser( msg );
MultiPrintStream.out.println(header);
// ******* Respond
// Check that the message is an ssdp discovery message
if( header.getRequestType().equalsIgnoreCase("M-SEARCH") ){
String man = header.getHTTPAttribute("Man").replace("\"", "");
String st = header.getHTTPAttribute("ST");
// Check that its the correct URL and that its an ssdp:discover message
if( header.getRequestURL().equals("*") && man.equalsIgnoreCase("ssdp:discover") ){
// Check if the requested service exists
if( services.containsKey( st ) ){
// Generate the SSDP response
StringOutputStream response = new StringOutputStream();
HttpPrintStream http = new HttpPrintStream( response );
http.setStatusCode(200);
http.setHeader("Server", SERVER_INFO );
http.setHeader("ST", st );
http.setHeader("Location", services.get(st) );
http.setHeader("EXT", "" );
http.setHeader("Cache-Control", "max-age = "+cache_time );
http.setHeader("USN", getUSN(st) );
http.close();
MultiPrintStream.out.println("\n"+response);
byte[] data = response.toString().getBytes();
packet = new DatagramPacket(
data, data.length,
packet.getAddress(),
packet.getPort());
network.send( packet );
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* This thread is a timer task that sends an
* notification message to the network every
* cache_time/2 seconds.
*
* @author Ziver
*/
private class NotifyTimer extends TimerTask {
public void run(){
sendNotify();
}
}
/**
* Sends keepalive messages to update the cache of the clients
*/
public void sendNotify(){
for(String st : services.keySet()){
sendNotify( st );
}
}
/**
* Sends an keepalive message to update the cache of the clients
*
* @param searchTarget is the ST value of the service
*
* ********** Message ex:
* NOTIFY * HTTP/1.1
* Host: 239.255.255.250:reservedSSDPport
* NT: blenderassociation:blender
* NTS: ssdp:alive
* USN: someunique:idscheme3
* AL: <blender:ixl><http://foo/bar>
* Cache-Control: max-age = 7393
*/
public void sendNotify(String searchTarget){
try {
// Generate the SSDP response
StringOutputStream msg = new StringOutputStream();
HttpPrintStream http = new HttpPrintStream( msg, HttpPrintStream.HTTPMessageType.REQUEST );
http.setRequestType("NOTIFY");
http.setRequestURL("*");
http.setHeader("Server", SERVER_INFO );
http.setHeader("Host", SSDP_MULTICAST_ADDR+":"+SSDP_PORT );
http.setHeader("NT", searchTarget );
http.setHeader("NTS", "ssdp:alive" );
http.setHeader("Location", services.get(searchTarget) );
http.setHeader("Cache-Control", "max-age = "+cache_time );
http.setHeader("USN", getUSN(searchTarget) );
http.close();
MultiPrintStream.out.println("\n"+msg);
byte[] data = msg.toString().getBytes();
DatagramPacket packet = new DatagramPacket(
data, data.length,
InetAddress.getByName( SSDP_MULTICAST_ADDR ),
SSDP_PORT );
super.send( packet );
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Shutdown message is sent to the clients that all
* the service is shutting down.
*/
public void sendByeBye(){
for(String st : services.keySet()){
sendByeBye( st );
}
}
/**
* Shutdown message is sent to the clients that the service is shutting down
*
* @param searchTarget is the ST value of the service
*
* ********** Message ex:
* NOTIFY * HTTP/1.1
* Host: 239.255.255.250:reservedSSDPport
* NT: someunique:idscheme3
* NTS: ssdp:byebye
* USN: someunique:idscheme3
*/
public void sendByeBye(String searchTarget){
try {
// Generate the SSDP response
StringOutputStream msg = new StringOutputStream();
HttpPrintStream http = new HttpPrintStream( msg, HttpPrintStream.HTTPMessageType.REQUEST );
http.setRequestType("NOTIFY");
http.setRequestURL("*");
http.setHeader("Server", SERVER_INFO );
http.setHeader("Host", SSDP_MULTICAST_ADDR+":"+SSDP_PORT );
http.setHeader("NT", searchTarget );
http.setHeader("NTS", "ssdp:byebye" );
http.setHeader("USN", getUSN(searchTarget) );
http.close();
MultiPrintStream.out.println("\n"+msg);
byte[] data = msg.toString().getBytes();
DatagramPacket packet = new DatagramPacket(
data, data.length,
InetAddress.getByName( SSDP_MULTICAST_ADDR ),
SSDP_PORT );
super.send( packet );
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Generates an unique USN for the service
*
* @param searchTarget is the service ST name
* @return an unique string that corresponds to the service
*/
public String getUSN(String searchTarget){
if( !usn_map.containsKey( searchTarget ) ){
String usn = "uuid:" + UUID.nameUUIDFromBytes( (searchTarget+services.get(searchTarget)).getBytes() );
usn_map.put( searchTarget, usn );
return usn;
}
return usn_map.get( searchTarget );
}
}

View file

@ -0,0 +1,237 @@
package zutil.network.http;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Scanner;
import java.util.regex.Pattern;
public class HTTPHeaderParser {
// Some Cached regexes
private static final Pattern colonPattern = Pattern.compile(":");
private static final Pattern equalPattern = Pattern.compile("=");
private static final Pattern andPattern = Pattern.compile("&");
private static final Pattern semiColonPattern = Pattern.compile(";");
// HTTP info
private String type;
private String url;
private HashMap<String, String> url_attr;
private float version;
// params
private HashMap<String, String> attributes;
private HashMap<String, String> cookies;
/**
* Parses the HTTP header information from the stream
*
* @param in is the stream
* @throws IOException
*/
public HTTPHeaderParser(BufferedReader in) throws IOException{
url_attr = new HashMap<String, String>();
attributes = new HashMap<String, String>();
cookies = new HashMap<String, String>();
String tmp = null;
if( (tmp=in.readLine()) != null && !tmp.isEmpty() ){
parseStartLine( tmp );
while( (tmp=in.readLine()) != null && !tmp.isEmpty() ){
parseLine( tmp );
}
}
parseCookies();
}
/**
* Parses the HTTP header information from an String
*
* @param in is the string
*/
public HTTPHeaderParser(String in){
url_attr = new HashMap<String, String>();
attributes = new HashMap<String, String>();
cookies = new HashMap<String, String>();
Scanner sc = new Scanner(in);
sc.useDelimiter("\n");
String tmp = null;
if( sc.hasNext() && !(tmp=sc.next()).isEmpty() ){
parseStartLine( tmp );
while( sc.hasNext() && !(tmp=sc.next()).isEmpty() ){
parseLine( tmp );
}
}
parseCookies();
}
/**
* Parses the first header line and ads the values to
* the map and returns the file name and path
*
* @param header The header String
* @param map The HashMap to put the variables to
* @return The path and file name as a String
*/
protected void parseStartLine(String line){
type = (line.substring(0, line.indexOf(" "))).trim();
version = Float.parseFloat( line.substring(line.lastIndexOf("HTTP/")+5 , line.length()).trim() );
line = (line.substring(type.length()+1, line.lastIndexOf("HTTP/"))).trim();
// parse URL and attributes
if(line.indexOf('?') > -1){
url = line.substring(0, line.indexOf('?'));
line = line.substring(line.indexOf('?')+1, line.length());
parseUrlAttributes(line, url_attr);
}
else{
url = line;
}
}
/**
* Parses a String with variables from a get or post
* that was sent from a client and puts the data into a HashMap
*
* @param header is the String containing all the attributes
* @param map is the HashMap to put all the values into
*/
public static void parseUrlAttributes(String attributes, HashMap<String, String> map){
String[] tmp;
// get the variables
String[] data = andPattern.split( attributes );
for(String element : data){
tmp = equalPattern.split(element, 2);
map.put(
tmp[0].trim(), // Key
(tmp.length>1 ? tmp[1] : "").trim()); //Value
}
}
/**
* Parses the rest of the header
*
* @param line is the next line in the header
*/
protected void parseLine(String line){
String[] data = colonPattern.split( line, 2 );
attributes.put(
data[0].trim().toUpperCase(), // Key
(data.length>1 ? data[1] : "").trim()); //Value
}
/**
* Parses the attribute "Cookie" and returns a HashMap
* with the values
*
* @return a HashMap with cookie values
*/
protected void parseCookies(){
if( attributes.containsKey("COOKIE") ){
String[] tmp = semiColonPattern.split( attributes.get("COOKIE") );
String[] tmp2;
for(String cookie : tmp){
tmp2 = equalPattern.split(cookie, 2);
cookies.put(
tmp2[0].trim(), // Key
(tmp2.length>1 ? tmp2[1] : "").trim()); //Value
}
}
}
/**
* @return the HTTP message type( ex. GET,POST...)
*/
public String getRequestType(){
return type;
}
/**
* @return the HTTP version of this header
*/
public float getHTTPVersion(){
return version;
}
/**
* @return the URL that the client sent the server
*/
public String getRequestURL(){
return url;
}
/**
* Returns the URL attribute value of the given name,
* returns null if there is no such attribute
*/
public String getURLAttribute(String name){
return url_attr.get( name );
}
/**
* Returns the HTTP attribute value of the given name,
* returns null if there is no such attribute
*/
public String getHTTPAttribute(String name){
return attributes.get( name.toUpperCase() );
}
/**
* Returns the cookie value of the given name,
* returns null if there is no such attribute
*/
public String getCookie(String name){
return cookies.get( name );
}
/**
* @return athe parsed cookies
*/
public HashMap<String, String> getCookies(){
return cookies;
}
/**
* @return the parsed URL values
*/
public HashMap<String, String> getURLAttributes(){
return url_attr;
}
/**
* @return the parsed header attributes
*/
public HashMap<String, String> getAttributes(){
return attributes;
}
public String toString(){
StringBuffer tmp = new StringBuffer();
tmp.append("\nType: ");
tmp.append(type);
tmp.append("\nHTTP Version: HTTP/");
tmp.append(version);
tmp.append("\nURL: ");
tmp.append(url);
for( String key : url_attr.keySet() ){
tmp.append("\nURL Attr: ");
tmp.append(key);
tmp.append("=");
tmp.append( url_attr.get(key) );
}
for( String key : attributes.keySet() ){
tmp.append("\nHTTP Attr: ");
tmp.append(key);
tmp.append("=");
tmp.append( attributes.get(key) );
}
for( String key : cookies.keySet() ){
tmp.append("\nCookie: ");
tmp.append(key);
tmp.append("=");
tmp.append( cookies.get(key) );
}
return tmp.toString();
}
}

View file

@ -11,17 +11,51 @@ import java.util.HashMap;
* @author Ziver
*
*/
public class HttpPrintStream extends PrintStream{
private Integer status_code;
private HashMap<String, String> header;
private HashMap<String, String> cookie;
private StringBuffer buffer;
private boolean buffer_enabled;
public class HttpPrintStream extends PrintStream{
// Defines the type of message
public enum HTTPMessageType{
REQUEST,
RESPONSE
}
// This defines the type of message that will be generated
private HTTPMessageType message_type;
// The status code of the message, ONLY for response
private Integer res_status_code;
// The request type of the message ONLY for request
private String req_type;
// The requesting url ONLY for request
private String req_url;
// An Map of all the header values
private HashMap<String, String> header;
// An Map of all the cookies
private HashMap<String, String> cookie;
// The buffered header
private StringBuffer buffer;
// If the header buffering is enabled
private boolean buffer_enabled;
/**
* Creates an new instance of HttpPrintStream with
* message type of RESPONSE and buffering disabled.
*
* @param out is the OutputStream to send the message
*/
public HttpPrintStream(OutputStream out) {
this( out, HTTPMessageType.RESPONSE );
}
/**
* Creates an new instance of HttpPrintStream with
* message type buffering disabled.
*
* @param out is the OutputStream to send the message
* @param type is the type of message
*/
public HttpPrintStream(OutputStream out, HTTPMessageType type) {
super(out);
status_code = 0;
this.message_type = type;
res_status_code = 0;
header = new HashMap<String, String>();
cookie = new HashMap<String, String>();
buffer = new StringBuffer();
@ -49,9 +83,9 @@ public class HttpPrintStream extends PrintStream{
* @param value is the value of the cookie
* @throws Exception Throws exception if the header has already been sent
*/
public void setCookie(String key, String value) throws Exception{
public void setCookie(String key, String value) throws RuntimeException{
if(cookie == null)
throw new Exception("Header already sent!!!");
throw new RuntimeException("Header already sent!!!");
cookie.put(key, value);
}
@ -62,22 +96,51 @@ public class HttpPrintStream extends PrintStream{
* @param value is the value of the header
* @throws Exception Throws exception if the header has already been sent
*/
public void setHeader(String key, String value) throws Exception{
public void setHeader(String key, String value) throws RuntimeException{
if(header == null)
throw new Exception("Header already sent!!!");
throw new RuntimeException("Header already sent!!!");
header.put(key, value);
}
/**
* Sets the return status code
* Sets the status code of the message, ONLY available in HTTP RESPONSE
*
* @param code the code from 100 up to 599
* @throws Exception Throws exception if the header has already been sent
* @throws RuntimeException if the header has already been sent or the message type is wrong
*/
public void setStatusCode(int code) throws Exception{
if(status_code == null)
throw new Exception("Header already sent!!!");
status_code = code;
public void setStatusCode(int code) throws RuntimeException{
if( res_status_code == null )
throw new RuntimeException("Header already sent!!!");
if( message_type != HTTPMessageType.RESPONSE )
throw new RuntimeException("Status Code is only available in HTTP RESPONSE!!!");
res_status_code = code;
}
/**
* Sets the request type of the message, ONLY available in HTTP REQUEST
*
* @param req_type is the type of the message, e.g. GET, POST...
* @throws RuntimeException if the header has already been sent or the message type is wrong
*/
public void setRequestType(String req_type) throws RuntimeException{
if( req_type == null )
throw new RuntimeException("Header already sent!!!");
if( message_type != HTTPMessageType.REQUEST )
throw new RuntimeException("Request Message Type is only available in HTTP REQUEST!!!");
this.req_type = req_type;
}
/**
* Sets the requesting URL of the message, ONLY available in HTTP REQUEST
*
* @param req_url is the URL
* @throws RuntimeException if the header has already been sent or the message type is wrong
*/
public void setRequestURL(String req_url) throws RuntimeException{
if( req_url == null )
throw new RuntimeException("Header already sent!!!");
if( message_type != HTTPMessageType.REQUEST )
throw new RuntimeException("Request URL is only available in HTTP REQUEST!!!");
this.req_url = req_url;
}
/**
@ -102,10 +165,15 @@ public class HttpPrintStream extends PrintStream{
buffer.append(s);
}
else{
if(status_code != null){
super.print("HTTP/1.0 "+status_code+" "+getStatusString(status_code));
if(res_status_code != null){
if( message_type==HTTPMessageType.REQUEST )
super.print(req_type+" "+req_url+" HTTP/1.1");
else
super.print("HTTP/1.1 "+res_status_code+" "+getStatusString(res_status_code));
super.println();
status_code = null;
res_status_code = null;
req_type = null;
req_url = null;
}
if(header != null){
for(String key : header.keySet()){
@ -115,9 +183,20 @@ public class HttpPrintStream extends PrintStream{
header = null;
}
if(cookie != null){
for(String key : cookie.keySet()){
super.print("Set-Cookie: "+key+"="+cookie.get(key)+";");
super.println();
if( !cookie.isEmpty() ){
if( message_type==HTTPMessageType.REQUEST ){
super.print("Cookie: ");
for(String key : cookie.keySet()){
super.print(key+"="+cookie.get(key)+"; ");
}
super.println();
}
else{
for(String key : cookie.keySet()){
super.print("Set-Cookie: "+key+"="+cookie.get(key)+";");
super.println();
}
}
}
super.println();
cookie = null;
@ -136,7 +215,7 @@ public class HttpPrintStream extends PrintStream{
buffer.delete(0, buffer.length());
buffer_enabled = true;
}
else if(status_code != null || header != null || cookie != null){
else if(res_status_code != null || header != null || cookie != null){
printOrBuffer("");
}
super.flush();
@ -165,7 +244,7 @@ public class HttpPrintStream extends PrintStream{
public void print(int x){ printOrBuffer(String.valueOf(x));}
public void print(long x){ printOrBuffer(String.valueOf(x));}
public void print(Object x){ printOrBuffer(String.valueOf(x));}
/*
public void write(int b) { print((char)b);}
public void write(byte buf[], int off, int len){

View file

@ -4,22 +4,16 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Pattern;
import javax.net.ssl.SSLServerSocketFactory;
import zutil.MultiPrintStream;
import zutil.network.threaded.ThreadedTCPNetworkServer;
import zutil.network.threaded.ThreadedTCPNetworkServerThread;
/**
@ -28,7 +22,7 @@ import zutil.MultiPrintStream;
*
* @author Ziver
*/
public class HttpServer extends Thread{
public class HttpServer extends ThreadedTCPNetworkServer{
public static final boolean DEBUG = false;
public static final String SERVER_VERSION = "StaticInt HttpServer 1.0";
public static final int COOKIE_TTL = 200;
@ -36,8 +30,6 @@ public class HttpServer extends Thread{
public final String server_url;
public final int server_port;
private File keyStore;
private String keyStorePass;
private HashMap<String,HttpPage> pages;
private HttpPage defaultPage;
@ -64,10 +56,9 @@ public class HttpServer extends Thread{
* @param sslCert If this is not null then the server will use a SSL connection with the given certificate
*/
public HttpServer(String url, int port, File keyStore, String keyStorePass){
super( port, keyStore, keyStorePass );
this.server_url = url;
this.server_port = port;
this.keyStorePass = keyStorePass;
this.keyStore = keyStore;
pages = new HashMap<String,HttpPage>();
sessions = Collections.synchronizedMap(new HashMap<String,Map<String,Object>>());
@ -75,6 +66,8 @@ public class HttpServer extends Thread{
Timer timer = new Timer();
timer.schedule(new GarbageCollector(), 0, SESSION_TTL / 2);
MultiPrintStream.out.println("HTTP"+(keyStore==null?"":"S")+" Server ready!");
}
/**
@ -119,49 +112,13 @@ public class HttpServer extends Thread{
defaultPage = page;
}
public void run(){
try{
ServerSocket ss;
if(keyStorePass != null && keyStore != null){
registerCertificate(keyStore, keyStorePass);
ss = initSSL(server_port);
MultiPrintStream.out.println("Https Server Running!!!");
}
else{
ss = new ServerSocket(server_port);
MultiPrintStream.out.println("Http Server Running!!!");
}
while(true){
new HttpServerThread(ss.accept());
}
} catch (Exception e) {
e.printStackTrace();
protected ThreadedTCPNetworkServerThread getThreadInstance( Socket s ){
try {
return new HttpServerThread( s );
} catch (IOException e) {
e.printStackTrace( MultiPrintStream.out );
}
}
/**
* Initiates a SSLServerSocket
*
* @param port The port to listen to
* @return The SSLServerSocket
* @throws IOException
*/
private ServerSocket initSSL(int port) throws IOException{
SSLServerSocketFactory sslserversocketfactory =
(SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
return sslserversocketfactory.createServerSocket(port);
}
/**
* Registers the given cert file to the KeyStore
*
* @param certFile The cert file
*/
protected void registerCertificate(File keyStore, String keyStorePass) throws CertificateException, IOException, KeyStoreException, NoSuchProviderException, NoSuchAlgorithmException{
System.setProperty("javax.net.ssl.keyStore", keyStore.getAbsolutePath());
System.setProperty("javax.net.ssl.keyStorePassword", keyStorePass);
return null;
}
/**
@ -170,7 +127,7 @@ public class HttpServer extends Thread{
* @author Ziver
*
*/
class HttpServerThread extends Thread{
protected class HttpServerThread implements ThreadedTCPNetworkServerThread{
private HttpPrintStream out;
private BufferedReader in;
private Socket socket;
@ -179,16 +136,11 @@ public class HttpServer extends Thread{
out = new HttpPrintStream(socket.getOutputStream());
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
this.socket = socket;
start();
if(DEBUG)MultiPrintStream.out.println("New Connection!!! "+socket.getInetAddress().getHostName());
if(DEBUG) MultiPrintStream.out.println("New Connection!!! "+socket.getInetAddress().getHostName());
}
public void run(){
String tmp = null;
String[] tmpArray, tmpArray2;
Pattern colonPattern = Pattern.compile(":");
Pattern semiColonPattern = Pattern.compile(";");
Pattern equalPattern = Pattern.compile("=");
String page_url = "";
HashMap<String,String> client_info = new HashMap<String,String>();
@ -197,45 +149,19 @@ public class HttpServer extends Thread{
//**************************** REQUEST *********************************
try {
if(DEBUG)MultiPrintStream.out.println("Reciving Http Request!!!");
while((tmp=in.readLine()) != null && !tmp.isEmpty()){
//System.err.println(tmp);
//*********** Handling Get variables
if(tmp.startsWith("GET")){
// Gets the file URL and get values
tmp = (tmp.substring(5, tmp.indexOf("HTTP/"))).trim();
page_url = parseHttpHeader(tmp, request);
}
//********* Handling Post variable data
else if(tmp.startsWith("POST")){
// Gets the file URL and get values
tmp = (tmp.substring(6, tmp.indexOf("HTTP/"))).trim();
page_url = parseHttpHeader(tmp, request);
}
//********* Handling Cookies
else if(tmp.startsWith("Cookie")){
tmp = colonPattern.split(tmp)[1];
tmpArray = semiColonPattern.split(tmp);
for(String e : tmpArray){
tmpArray2 = equalPattern.split(e);
cookie.put(
tmpArray2[0].trim(), // Key
(tmpArray2.length>1 ? tmpArray2[1] : "").trim()); //Value
}
}
//********* Handling Client info
else{
tmpArray = colonPattern.split(tmp);
client_info.put(
tmpArray[0].trim(), // Key
(tmpArray.length>1 ? tmpArray[1] : "").trim()); //Value
}
}
if(DEBUG) MultiPrintStream.out.println("Reciving Http Request!!!");
HTTPHeaderParser parser = new HTTPHeaderParser(in);
if(DEBUG) MultiPrintStream.out.println(parser);
client_info = parser.getAttributes();
request = parser.getURLAttributes();
cookie = parser.getCookies();
//******* Read in the post data if available
if(client_info.containsKey("Content-Length")){
if( parser.getHTTPAttribute("Content-Length")!=null ){
// Reads the post data size
tmp = client_info.get("Content-Length");
tmp = parser.getHTTPAttribute("Content-Length");
int post_data_length = Integer.parseInt( tmp );
// read the data
StringBuffer tmpb = new StringBuffer();
@ -244,19 +170,20 @@ public class HttpServer extends Thread{
tmpb.append((char)in.read());
}
if(client_info.get("Content-Type").contains("application/x-www-form-urlencoded")){
tmp = parser.getHTTPAttribute("Content-Type");
if( tmp.contains("application/x-www-form-urlencoded") ){
// get the variables
parseVariables(tmpb.toString(), request);
HTTPHeaderParser.parseUrlAttributes( tmpb.toString(), request );
}
else if(client_info.get("Content-Type").contains("application/soap+xml") ||
client_info.get("Content-Type").contains("text/xml") ||
client_info.get("Content-Type").contains("text/plain")){
else if( tmp.contains("application/soap+xml" ) ||
tmp.contains("text/xml") ||
tmp.contains("text/plain") ){
// save the variables
request.put("" , tmpb.toString());
request.put( "" , tmpb.toString() );
}
else if(client_info.get("Content-Type").contains("multipart/form-data")){
else if( tmp.contains("multipart/form-data") ){
// TODO: File upload
throw new Exception("\"multipart-form-data\" Not implemented!!!");
throw new Exception( "\"multipart-form-data\" Not implemented!!!" );
}
}
@ -264,118 +191,71 @@ public class HttpServer extends Thread{
// Get the client session or create one
Map<String, Object> client_session;
long ttl_time = System.currentTimeMillis()+SESSION_TTL;
if(cookie.containsKey("session_id") && sessions.containsKey(cookie.get("session_id"))){
client_session = sessions.get(cookie.get("session_id"));
if( cookie.containsKey("session_id") && sessions.containsKey(cookie.get("session_id")) ){
client_session = sessions.get( cookie.get("session_id") );
// Check if session is still valid
if((Long)client_session.get("ttl") < System.currentTimeMillis()){
if( (Long)client_session.get("ttl") < System.currentTimeMillis() ){
int session_id = (Integer)client_session.get("session_id");
client_session = Collections.synchronizedMap(new HashMap<String, Object>());
client_session.put("session_id", session_id);
sessions.put(""+session_id, client_session);
client_session.put( "session_id", session_id);
sessions.put( ""+session_id, client_session);
}
// renew the session TTL
client_session.put("ttl", ttl_time);
client_session.put( "ttl", ttl_time );
}
else{
client_session = Collections.synchronizedMap(new HashMap<String, Object>());
client_session.put("session_id", nextSessionId);
client_session.put("ttl", ttl_time);
sessions.put(""+nextSessionId, client_session);
client_session.put( "session_id", nextSessionId );
client_session.put( "ttl", ttl_time );
sessions.put( ""+nextSessionId, client_session );
nextSessionId++;
}
// Debug
if(DEBUG){
MultiPrintStream.out.println("# page_url: "+page_url);
MultiPrintStream.out.println("# cookie: "+cookie);
MultiPrintStream.out.println("# client_session: "+client_session);
MultiPrintStream.out.println("# client_info: "+client_info);
MultiPrintStream.out.println("# request: "+request);
MultiPrintStream.out.println( "# page_url: "+page_url );
MultiPrintStream.out.println( "# cookie: "+cookie );
MultiPrintStream.out.println( "# client_session: "+client_session );
MultiPrintStream.out.println( "# client_info: "+client_info );
MultiPrintStream.out.println( "# request: "+request );
}
//**************************** RESPONSE ************************************
if(DEBUG)MultiPrintStream.out.println("Sending Http Response!!!");
if(DEBUG) MultiPrintStream.out.println("Sending Http Response!!!");
out.setStatusCode(200);
out.setHeader("Server", SERVER_VERSION);
out.setHeader("Content-Type", "text/html");
out.setCookie("session_id", ""+client_session.get("session_id"));
out.setHeader( "Server", SERVER_VERSION );
out.setHeader( "Content-Type", "text/html" );
out.setCookie( "session_id", ""+client_session.get("session_id") );
if(!page_url.isEmpty() && pages.containsKey(page_url)){
if( !page_url.isEmpty() && pages.containsKey(page_url) ){
pages.get(page_url).respond(out, client_info, client_session, cookie, request);
}
else if(defaultPage != null){
else if( defaultPage != null ){
defaultPage.respond(out, client_info, client_session, cookie, request);
}
else{
out.setStatusCode(404);
out.println("404 Page Not Found");
out.setStatusCode( 404 );
out.println( "404 Page Not Found" );
}
//********************************************************************************
} catch (Exception e) {
e.printStackTrace(MultiPrintStream.out);
e.printStackTrace( MultiPrintStream.out );
try {
out.setStatusCode(500);
out.setStatusCode( 500 );
} catch (Exception e1) {}
if(e.getMessage() != null)
out.println("500 Internal Server Error(Header: "+tmp+"): "+e.getMessage());
out.println( "500 Internal Server Error: "+e.getMessage() );
else{
out.println("500 Internal Server Error(Header: "+tmp+"): "+e.getCause().getMessage());
out.println( "500 Internal Server Error: "+e.getCause().getMessage() );
}
}
try{
if(DEBUG)MultiPrintStream.out.println("Conection Closed!!!");
if(DEBUG) MultiPrintStream.out.println("Conection Closed!!!");
out.close();
in.close();
socket.close();
} catch (Exception e) {
e.printStackTrace(MultiPrintStream.out);
}
}
}
/**
* Parses the first header line and ads the values to
* the map and returns the file name and path
*
* @param header The header String
* @param map The HashMap to put the variables to
* @return The path and file name as a String
*/
private String parseHttpHeader(String header, HashMap<String, String> map){
String page_url = "";
// cut out the page name
if(header.indexOf('?') > -1){
page_url = header.substring(0, header.indexOf('?'));
header = header.substring(header.indexOf('?')+1, header.length());
parseVariables(header, map);
}
else{
page_url = header;
}
return page_url;
}
/**
* Parses a String with variables from a get or post
* from a client and puts the data into a HashMap
*
* @param header A String with all the variables
* @param map The HashMap to put all the variables into
*/
private void parseVariables(String header, HashMap<String, String> map){
int tmp;
// get the variables
String[] data = header.split("&");
for(String element : data){
tmp = element.indexOf('=');
if(tmp > 0){
map.put(
element.substring(0, tmp ).trim(), // Key
element.substring(tmp+1, element.length() ).trim() ); //Value
}
else{
map.put(element, "");
} catch( Exception e ) {
e.printStackTrace( MultiPrintStream.out );
}
}
}

View file

@ -36,7 +36,7 @@ public abstract class NioNetwork implements Runnable {
* 2 = message debug
* 3 = selector debug
*/
public static int DEBUG = 2;
public static final int DEBUG = 2;
public static enum NetworkType {SERVER, CLIENT};
private NetworkType type;

View file

@ -0,0 +1,115 @@
package zutil.network.threaded;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateException;
import javax.net.ssl.SSLServerSocketFactory;
import zutil.MultiPrintStream;
/**
* A simple web server that handles both cookies and
* sessions for all the clients
*
* @author Ziver
*/
public abstract class ThreadedTCPNetworkServer extends Thread{
public final int port;
private File keyStore;
private String keyStorePass;
/**
* Creates a new instance of the sever
*
* @param port The port that the server should listen to
*/
public ThreadedTCPNetworkServer(int port){
this(port, null, null);
}
/**
* Creates a new instance of the sever
*
* @param port The port that the server should listen to
* @param sslCert If this is not null then the server will use SSL connection with this keyStore file path
* @param sslCert If this is not null then the server will use a SSL connection with the given certificate
*/
public ThreadedTCPNetworkServer(int port, File keyStore, String keyStorePass){
this.port = port;
this.keyStorePass = keyStorePass;
this.keyStore = keyStore;
}
public void run(){
ServerSocket ss = null;
try{
if(keyStorePass != null && keyStore != null){
registerCertificate(keyStore, keyStorePass);
ss = initSSL( port );
}
else{
ss = new ServerSocket( port );
}
while(true){
Socket s = ss.accept();
ThreadedTCPNetworkServerThread t = getThreadInstance( s );
if( t!=null )
new Thread( t ).start();
else{
MultiPrintStream.out.println("Unable to instantiate ThreadedTCPNetworkServerThread, closing connection!");
s.close();
}
}
} catch(Exception e) {
e.printStackTrace( MultiPrintStream.out );
}
if( ss!=null ){
try{
ss.close();
}catch(IOException e){ e.printStackTrace( MultiPrintStream.out ); }
}
}
/**
* This method returns an new instance of the ThreadedTCPNetworkServerThread
* that will handle the newly made connection, if an null value is returned
* then the ThreadedTCPNetworkServer will close the new connection.
*
* @param s is an new connection to an host
* @return a new instance of an thread or null
*/
protected abstract ThreadedTCPNetworkServerThread getThreadInstance( Socket s );
/**
* Initiates a SSLServerSocket
*
* @param port The port to listen to
* @return The SSLServerSocket
* @throws IOException
*/
private ServerSocket initSSL(int port) throws IOException{
SSLServerSocketFactory sslserversocketfactory =
(SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
return sslserversocketfactory.createServerSocket(port);
}
/**
* Registers the given cert file to the KeyStore
*
* @param certFile The cert file
*/
protected void registerCertificate(File keyStore, String keyStorePass) throws CertificateException, IOException, KeyStoreException, NoSuchProviderException, NoSuchAlgorithmException{
System.setProperty("javax.net.ssl.keyStore", keyStore.getAbsolutePath());
System.setProperty("javax.net.ssl.keyStorePassword", keyStorePass);
}
}

View file

@ -0,0 +1,12 @@
package zutil.network.threaded;
/**
* The class that will handle the TCP connection will incclude
* this interface
*
* @author Ziver
*
*/
public interface ThreadedTCPNetworkServerThread extends Runnable{
}

View file

@ -0,0 +1,103 @@
package zutil.network.threaded;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.SocketException;
/**
* A simple web server that handles both cookies and
* sessions for all the clients
*
* @author Ziver
*/
public class ThreadedUDPNetwork extends Thread{
public static final int BUFFER_SIZE = 512;
// Type of UDP socket
enum UDPType{
MULTICAST,
UNICAST
}
protected final UDPType type;
protected final int port;
protected DatagramSocket socket;
protected ThreadedUDPNetworkThread thread = null;
/**
* Creates a new unicast instance of the sever
*
* @param thread is the class that will handle incoming packets
* @param port is the port that the server should listen to
* @throws SocketException
*/
public ThreadedUDPNetwork(ThreadedUDPNetworkThread thread, int port) throws SocketException{
this.type = UDPType.UNICAST;
this.port = port;
setThread( thread );
socket = new DatagramSocket( port );
}
/**
* Creates a new multicast instance of the sever
*
* @param thread is the class that will handle incoming packets
* @param port is the port that the server should listen to
* @param multicast_addr is the multicast address that the server will listen on
* @throws IOException
*/
public ThreadedUDPNetwork(ThreadedUDPNetworkThread thread, int port, String multicast_addr ) throws IOException{
this.type = UDPType.MULTICAST;
this.port = port;
setThread( thread );
// init udp socket
MulticastSocket msocket = new MulticastSocket( port );
InetAddress group = InetAddress.getByName( multicast_addr );
msocket.joinGroup( group );
socket = msocket;
}
public void run(){
try{
while(true){
byte[] buf = new byte[BUFFER_SIZE];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive( packet );
if( thread!=null )
thread.receivedPacket( packet, this );
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Sends the given packet
*
* @param packet is the packet to send
* @throws IOException
*/
public synchronized void send( DatagramPacket packet ) throws IOException{
socket.send(packet);
}
/**
* Sets the thread that will handle the incoming packets
*
* @param thread is the thread
*/
public void setThread(ThreadedUDPNetworkThread thread){
this.thread = thread;
}
}

View file

@ -0,0 +1,21 @@
package zutil.network.threaded;
import java.net.DatagramPacket;
/**
* This interface is for processing received packets
* from the TNetworkUDPServer
*
* @author Ziver
*
*/
public interface ThreadedUDPNetworkThread extends Runnable{
/**
* Packet will be processed in this method
*
* @param packet is the received packet
* @param network is the network class that received the packet
*/
public void receivedPacket(DatagramPacket packet, ThreadedUDPNetwork network);
}