import java.lang.*; import java.lang.reflect.Array; import java.net.*; import java.io.*; import java.util.*; import java.text.*; public class HTTPServer extends Thread { Socket _s; private static Properties serverConfig; private static Properties mimeConfig; public HTTPServer(Socket s) { _s = s; } public void run() { try{ InetAddress clientAddr = _s.getInetAddress(); System.out.println("Connection accepted from: " + clientAddr.getHostAddress()); //-----INSTANTIATE SERVER, DOCUMENT, AND CGI ROOTS String serverRoot = serverConfig.getProperty("server.root"); String docRoot = serverConfig.getProperty("doc.root"); String cgiRoot = serverConfig.getProperty("cgi.root"); //-----CREATE INPUT AND OUTPUT STREAMS PrintStream out = new PrintStream( _s.getOutputStream()); InputStream in = new BufferedInputStream( _s.getInputStream()); //-----CREATE GMT DATE STRING Date localDate = new Date(); // CREATE DATE OBJECT REPRESENTING LOCAL DATE long ms = localDate.getTime(); // GET LOCAL DATE AS OFFSET FROM "January 1, 1970, 00:00:00 GMT" IN ms long GMTOffset = (5*60*60*1000); ms = ms + GMTOffset; // ADD 5 HOURS TO COMPENSATE FOR EST OFFSET FROM GMT Date d = new Date(ms); // CREATE NEW DATE BASED ON CORRECTED GMT TIME SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss"); String GMTStr = sdf.format(d) + " GMT"; //-----START MAIN HTTP SERVER CODE boolean requestServiced = false; // CHECKS WHETHER THE REQUEST WAS SUCCESSFULLY SERVICED byte[] rawRequest = new byte[1024]; // THE RAW, UNALTERED REQUEST in.read(rawRequest); String strRequest = new String(rawRequest); String strMethod = ""; String strURL = ""; String strVersion = ""; String strNoImp = "PUT;TRACE;OPTIONS;DELETE"; String strSupported = "GET;POST;HEAD"; //-----TOKENIZE THE ENTIRE REQUEST, LINE BY LINE StringTokenizer tRequest= new StringTokenizer(strRequest, "\n"); //-----TOKENIZE THE FIRST LINE OF THE REQUEST StringTokenizer tStartLine = new StringTokenizer(tRequest.nextToken()); //-----EXTRACT THE TOKENS FROM THE START LINE if(tStartLine.hasMoreTokens()) strMethod = tStartLine.nextToken(); if(tStartLine.hasMoreTokens()) strURL = tStartLine.nextToken(); if(tStartLine.hasMoreTokens()) strVersion = tStartLine.nextToken(); if(strVersion.equals("HTTP/1.0") || strVersion.equals("")){ if(strSupported.indexOf(strMethod) != -1){ // THE METHOD IS SUPPORTED if(strURL.indexOf("/cgi-bin/") == 0){ // THE URL POINTS TO AN ENTITY IN THE cgi-bin DIRECTORY boolean fileExists = false; try{ String cgiName = ""; String[] env = new String[30]; for(int i = 0; i < 30; env[i] = "", i++); env[0] = "REQUEST_METHOD=" + strMethod; env[1] = "SERVER_PROTOCOL=HTTP/1.0"; env[2] = "HTTP_" + env[0]; env[3] = "HTTP_" + env[1]; // EXECUTE THE CGI Process cgiProc = null; if(strMethod.equals("GET")){ // REQUEST METHOD WAS A GET, GET THE QUERYSTRING cgiName = serverRoot + cgiRoot; if(strURL.indexOf("?") != -1){ cgiName = cgiName + strURL.substring(strURL.indexOf("/", 1) + 1, strURL.indexOf("?")); } else{ cgiName = cgiName + strURL.substring(strURL.indexOf("/", 1) + 1); } File cgiFile = new File(cgiName); if(cgiFile.exists()) fileExists = true; else throw new FileNotFoundException(); env[4] = "QUERY_STRING=" + strURL.substring(strURL.indexOf("?") + 1); env[5] = "HTTP_" + env[2]; cgiProc = Runtime.getRuntime().exec(cgiName, env); } else{ // REQUEST METHOD WAS A POST, GET ALL THE HEADERS cgiName = serverRoot + cgiRoot + strURL.substring(strURL.lastIndexOf("/") + 1); String header = strRequest.substring(0, strRequest.indexOf("\n\n")); String body = strRequest.substring(strRequest.indexOf("\n\n")).trim(); StringTokenizer tHeader = new StringTokenizer(header, "\n"); tHeader.nextToken(); boolean hasContentLength = false; int contentLength = 0; int envIndex = 4; while(tHeader.hasMoreTokens() && envIndex < 30){ // cToken IS THE CURRENT HEADER, WE LOOP THROUGH EACH HEADER AND CHECK // WHICH HEADER IT IS. SEPERATE THE HEADER TYPE FROM THE HEADER ARGS. String cToken = tHeader.nextToken(); int seperator = cToken.indexOf(":"); if(seperator != -1){ // SEPERATE THE HEADER NAME FROM THE HEADER VALUE // CAPITALIZE THE HEADER AND REPLACE "-" W/ "_" String hName = cToken.substring(0,seperator).toUpperCase().replace('-','_'); String hArgs = cToken.substring(seperator + 1).trim(); if(hName.equals("CONTENT_LENGTH")){ // ON EACH HEADER, CHECK IF CONTENT_LENGTH IS PRESENT, IF SO, STORE IT // IF NOT FOUND, THROW EXCEPTION hasContentLength = true; contentLength = Integer.parseInt(hArgs); } env[envIndex] = hName + "=" + hArgs; envIndex++; env[envIndex] = "HTTP_" + env[envIndex - 1]; envIndex++; } } if(!hasContentLength) throw new MalformedURLException(); File cgiFile = new File(cgiName); if(cgiFile.exists()) fileExists = true; else throw new FileNotFoundException(); // REDUCE THE BODY TO THE CONTENT LENGTH body = body.substring(0, contentLength); cgiProc = Runtime.getRuntime().exec(cgiName, env); OutputStream cgiOS = cgiProc.getOutputStream(); // WRITE REQUEST BODY TO OUTPUT STREAM THEN CLOSE THE STREAM cgiOS.write(body.getBytes()); cgiOS.flush(); cgiOS.close(); } // THE FILE EXISTS, WE ASSUME IT EXECUTED cgiProc.waitFor(); InputStream cgiIS = cgiProc.getInputStream(); int iterations = 1; while(true){ byte[] returnBytes = new byte[512]; int bytesRead = cgiIS.read(returnBytes); if(bytesRead > 0 && iterations == 1 && strVersion.equals("HTTP/1.0")){ out.println("HTTP/1.0 200 OK"); out.println("Date: " + GMTStr); out.println("Last-Modified: " + GMTStr); } if(bytesRead < 0) break; else out.write(returnBytes); iterations++; } if(iterations == 1 && strVersion.equals("HTTP/1.0")){ out.println("HTTP/1.0 500 Internal Server Error"); out.println("Date: " + GMTStr); out.println("Last-Modified: " + GMTStr); out.println("Content-Length: 0"); } requestServiced = true; } catch(InterruptedException e){ // PROCESS INTERRUPTED, 500? if(strVersion.equals("HTTP/1.0")){ out.println("HTTP/1.0 500 Internal Server Error"); } } catch(FileNotFoundException e){ // FILE DOES NOT EXIST if(strVersion.equals("HTTP/1.0")){ out.println("HTTP/1.0 404 Not Found"); } } catch(MalformedURLException e){ // NO CONTENT LENGTH, BAD REQUEST if(strVersion.equals("HTTP/1.0")){ out.println("HTTP/1.0 400 Bad Request"); } } catch(IOException e){ // NO EXECUTE PERMISSIONS if(strVersion.equals("HTTP/1.0")){ out.println("HTTP/1.0 403 Forbidden"); } } catch(StringIndexOutOfBoundsException e){ // THIS HAPPENS WHEN REQUEST IS A POST FROM THE BROWSWER. CANNOT FIND "\n\n" if(strVersion.equals("HTTP/1.0")){ out.println("HTTP/1.0 500 Internal Server Error"); } } } else{ // THE URL POINTS TO AN ENTITY IN THE document DIRECTORY // IF THE ENTITY IS A .cgi OR A .pl IN THE document DIRECTORY, RETURN 404, NOT FOUND String rescLoc = serverRoot + docRoot + strURL.substring(1); try{ // TRY TO LOCATE THE FILE IN THE document ROOT, OPEN IT, AND SEND IT TO CLIENT if(strURL.substring(strURL.lastIndexOf(".")).equals(".cgi")){ throw new FileNotFoundException(); } File rescFile = new File(rescLoc); byte[] outputBuffer = new byte[(int)rescFile.length()]; InputStream fis = new FileInputStream(rescFile); fis.read(outputBuffer); // FILE LOCATED, GENERATE 200 OK RESPONSE IF HTTP/1.0 HEADER AND RETURN // HEADERS: DATE, LAST-MODIFIED, CONTENT-TYPE if(strVersion.equals("HTTP/1.0")){ // PREPROCESS LAST MODIFIED DATE AND CONTENT TYPE Date lastMod = new Date(rescFile.lastModified() + GMTOffset); SimpleDateFormat lastModSdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss"); String lastModStr = lastModSdf.format(lastMod)+" GMT"; String fileType = strURL.substring(strURL.lastIndexOf(".") + 1); fileType = mimeConfig.getProperty(fileType); if(fileType == null) fileType = "Unknown"; // RETURN HEADERS out.println("HTTP/1.0 200 OK"); out.println("Date: " + GMTStr); out.println("Last-Modified: " + lastModStr); out.println("Content-Type: " + fileType); out.println("Content-Length: " + (int)rescFile.length()); out.println(""); } if(!(strMethod.equals("HEAD"))){ // ONLY RETURN THE ENTITY BODY IF THE REQUEST IS NOT HEAD out.write(outputBuffer, 0, Array.getLength(outputBuffer)); } requestServiced = true; } catch(FileNotFoundException e){ // FILE DOES NOT EXIST if(strVersion.equals("HTTP/1.0")){ out.println("HTTP/1.0 404 Not Found"); } } catch(NullPointerException e){ // FILE DOES NOT EXIST if(strVersion.equals("HTTP/1.0")){ out.println("HTTP/1.0 404 Not Found"); } } catch(SecurityException e){ // FILE ACCESS DENIED if(strVersion.equals("HTTP/1.0")){ out.println("HTTP/1.0 403 Forbidden"); } } } } else if(strNoImp.indexOf(strMethod) != -1){ // THE METHOD IS NOT IMPLEMENTED ON THIS SERVER if(strVersion.equals("HTTP/1.0")){ out.println("HTTP/1.0 501 Not Implemented"); } } else{ // THIS IS A BAD REQUEST, RESPOND ONLY IF IT'S VERSION 1.0 if(strVersion.equals("HTTP/1.0")){ out.println("HTTP/1.0 400 Bad Request"); } } } else{ // THIS IS ERROR 505: "HTTP Version Not Supported" out.println("HTTP/1.0 505 HTTP Version Not Supported"); } // RETURN THE REST OF THE HEADER IF IT'S VERSION 1.0 OR HIGHER if(!(strVersion.equals("")) && requestServiced == false){ out.println("Date: " + GMTStr); out.println("Last-Modified: " + GMTStr); out.println("Content-Length: 0"); out.println(""); } //-----END MAIN HTTP SERVER CODE in.close(); out.close(); System.out.println(" Closing connection to: " + clientAddr.getHostAddress()); _s.close(); } catch(IOException e){ System.out.println(e.getMessage()); } } public static void main(String[] args) { try{ ServerSocket ss = new ServerSocket(Integer.parseInt(args[0])); // LOAD THE CONFIGURATION FILES INTO RESPECTIVE PROPERTIES serverConfig = new Properties(); mimeConfig = new Properties(); serverConfig.load(new FileInputStream("server.conf")); mimeConfig.load(new FileInputStream("mime.conf")); System.out.println("Server started. Waiting for connections."); while(true) { Socket s = ss.accept(); HTTPServer server = new HTTPServer(s); server.start(); } } catch(NumberFormatException e){ System.out.println("Invalid number format for port_number."); System.out.println("Usage: java HTTPServer "); } catch(IOException e){ System.out.println(e.getMessage()); } } }