/******************************************************************************* * Copyright (c) 2013 Ziver * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. ******************************************************************************/ package zutil.net.http; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; import zutil.log.LogUtil; import zutil.net.threaded.ThreadedTCPNetworkServer; import zutil.net.threaded.ThreadedTCPNetworkServerThread; /** * A simple web server that handles both cookies and * sessions for all the clients * * @author Ziver */ public class HttpServer extends ThreadedTCPNetworkServer{ private static final Logger logger = LogUtil.getLogger(); public static final String SERVER_VERSION = "Ziver HttpServer 1.0"; public static final int COOKIE_TTL = 200; public static final int SESSION_TTL = 10*60*1000; // in milliseconds private Map pages; private HttpPage defaultPage; private Map> sessions; private int nextSessionId; /** * Creates a new instance of the sever * * @param port The port that the server should listen to */ public HttpServer(int port){ this(port, null, null); } /** * Creates a new instance of the sever * * @param port The port that the server should listen to * @param keyStore If this is not null then the server will use SSL connection with this keyStore file path * @param keyStorePass If this is not null then the server will use a SSL connection with the given certificate */ public HttpServer(int port, File keyStore, String keyStorePass){ super( port, keyStore, keyStorePass ); pages = new ConcurrentHashMap(); sessions = new ConcurrentHashMap>(); nextSessionId = 0; Timer timer = new Timer(); timer.schedule(new GarbageCollector(), 10000, SESSION_TTL / 2); logger.info("HTTP"+(keyStore==null?"":"S")+" Server ready!"); } /** * This class acts as an garbage collector that * removes old sessions from the session HashMap * * @author Ziver */ private class GarbageCollector extends TimerTask { public void run(){ Object[] keys = sessions.keySet().toArray(); for(Object key : keys){ Map client_session = sessions.get(key); // Check if session is still valid if((Long)client_session.get("ttl") < System.currentTimeMillis()){ sessions.remove(key); logger.fine("Removing Session: "+key); } } } } /** * Add a HttpPage to a specific URL * * @param name The URL or name of the page * @param page The page itself */ public void setPage(String name, HttpPage page){ if(name.charAt(0) != '/') name = "/"+name; pages.put(name, page); } /** * This is a default page that will be shown * if there is no other matching page, * * @param page The HttpPage that will be shown */ public void setDefaultPage(HttpPage page){ defaultPage = page; } protected ThreadedTCPNetworkServerThread getThreadInstance( Socket s ){ try { return new HttpServerThread( s ); } catch (IOException e) { logger.log(Level.SEVERE, "Could not start new Thread", e); } return null; } /** * Internal class that handles all the requests * * @author Ziver * */ protected class HttpServerThread implements ThreadedTCPNetworkServerThread{ private HttpPrintStream out; private BufferedReader in; private Socket socket; public HttpServerThread(Socket socket) throws IOException{ out = new HttpPrintStream(socket.getOutputStream()); in = new BufferedReader(new InputStreamReader(socket.getInputStream())); this.socket = socket; logger.fine("New Connection: " + socket.getInetAddress().getHostName()); } public void run(){ String tmp = null; HashMap cookie = new HashMap(); HashMap request = new HashMap(); //**************************** REQUEST ********************************* try { logger.finer("Receiving Http Request"); HttpHeaderParser parser = new HttpHeaderParser(in); //logger.finest(parser.toString()); request = parser.getURLAttributes(); cookie = parser.getCookies(); //******* Read in the post data if available if( parser.getHeader("Content-Length")!=null ){ // Reads the post data size tmp = parser.getHeader("Content-Length"); int post_data_length = Integer.parseInt( tmp ); // read the data StringBuffer tmpb = new StringBuffer(); // read the data for(int i=0; i 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") ); // Check if session is still valid if( (Long)client_session.get("ttl") < System.currentTimeMillis() ){ int session_id = (Integer)client_session.get("session_id"); client_session = Collections.synchronizedMap(new HashMap()); client_session.put( "session_id", session_id); sessions.put( ""+session_id, client_session); } // renew the session TTL client_session.put( "ttl", ttl_time ); } else{ client_session = Collections.synchronizedMap(new HashMap()); client_session.put( "session_id", nextSessionId ); client_session.put( "ttl", ttl_time ); sessions.put( ""+nextSessionId, client_session ); ++nextSessionId; } // Debug if(logger.isLoggable(Level.FINEST)){ logger.finest( " page_url: "+parser.getRequestURL() ); logger.finest( " client_session: "+client_session ); logger.finest( " cookie: "+cookie ); logger.finest( " request: "+request ); } //**************************** RESPONSE ************************************ logger.finer("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") ); if( !parser.getRequestURL().isEmpty() && pages.containsKey(parser.getRequestURL()) ){ pages.get(parser.getRequestURL()).respond(out, parser, client_session, cookie, request); } else if( defaultPage != null ){ defaultPage.respond(out, parser, client_session, cookie, request); } else{ out.setStatusCode( 404 ); out.println( "404 Page Not Found: "+parser.getRequestURL() ); logger.warning("Page not defined: " + parser.getRequestURL()); } //******************************************************************************** } catch (Exception e) { logger.log(Level.WARNING, "500 Internal Server Error", e); if(!out.isHeaderSent()) out.setStatusCode( 500 ); if(e.getMessage() != null) out.println( "500 Internal Server Error: "+e.getMessage() ); else if(e.getCause() != null){ out.println( "500 Internal Server Error: "+e.getCause().getMessage() ); } else{ out.println( "500 Internal Server Error: "+e); } } try{ logger.fine("Connection Closed."); out.close(); in.close(); socket.close(); } catch( Exception e ) { logger.log(Level.WARNING, "Could not close connection", e); } } } }