1 /* 2 * An example of a very simple, multi-threaded HTTP server. 3 * Implementation notes are in WebServer.html, and also 4 * as comments in the source code. 5 */ 6 7 import java.io.*; 8 import java.net.*; 9 import java.util.*; 10 11 class WebServer implements HttpConstants { 12 13 /* static class data/methods */ 14 15 /* print to stdout */ 16 protected static void p(String s) { 17 System.out.println(s); 18 } 19 20 /* print to the log file */ 21 protected static void log(String s) { 22 synchronized (log) { 23 log.println(s); 24 log.flush(); 25 } 26 } 27 28 static PrintStream log = null; 29 /* our server's configuration information is stored 30 * in these properties 31 */ 32 protected static Properties props = new Properties(); 33 34 /* Where worker threads stand idle */ 35 static Vector threads = new Vector(); 36 37 /* the web server's virtual root */ 38 static File root; 39 40 /* timeout on client connections */ 41 static int timeout = 0; 42 43 /* max # worker threads */ 44 static int workers = 5; 45 46 47 /* load www-server.properties from java.home */ 48 static void loadProps() throws IOException { 49 File f = new File (System.getProperty("java.home")+File.separator+ 50 "lib"+File.separator+"www-server.properties"); 51 if (f.exists()) { 52 InputStream is =new BufferedInputStream(new 53 FileInputStream(f)); 54 props.load(is); 55 is.close(); 56 String r = props.getProperty("root"); 57 if (r != null) { 58 root = new File(r); 59 if (!root.exists()) { 60 throw new Error(root + " doesn't exist as server root"); 61 } 62 } 63 r = props.getProperty("timeout"); 64 if (r != null) { 65 timeout = Integer.parseInt(r); 66 } 67 r = props.getProperty("workers"); 68 if (r != null) { 69 workers = Integer.parseInt(r); 70 } 71 r = props.getProperty("log"); 72 if (r != null) { 73 p("opening log file: " + r); 74 log = new PrintStream(new BufferedOutputStream( 75 new FileOutputStream(r))); 76 } 77 } 78 79 /* if no properties were specified, choose defaults */ 80 if (root == null) { 81 root = new File(System.getProperty("user.dir")); 82 } 83 if (timeout <= 1000) { 84 timeout = 5000; 85 } 86 if (workers 25) { 87 workers = 5; 88 } 89 if (log == null) { 90 p("logging to stdout"); 91 log = System.out; 92 } 93 } 94 95 static void printProps() { 96 p("root="+root); 97 p("timeout="+timeout); 98 p("workers="+workers); 99 } 100 101 public static void main(String[] a) throws Exception { 102 int port = 8080; 103 if (a.length > 0) { 104 port = Integer.parseInt(a[0]); 105 } 106 loadProps(); 107 printProps(); 108 /* start worker threads */ 109 for (int i = 0; i < workers; ++i) { 110 Worker w = new Worker(); 111 (new Thread(w, "worker #"+i)).start(); 112 threads.addElement(w); 113 } 114 115 ServerSocket ss = new ServerSocket(port); 116 while (true) { 117 118 Socket s = ss.accept(); 119 120 Worker w = null; 121 synchronized (threads) { 122 if (threads.isEmpty()) { 123 Worker ws = new Worker(); 124 ws.setSocket(s); 125 (new Thread(ws, "additional worker")).start(); 126 } else { 127 w = (Worker) threads.elementAt(0); 128 threads.removeElementAt(0); 129 w.setSocket(s); 130 } 131 } 132 } 133 } 134 } 135 136 137 class Worker extends WebServer implements HttpConstants, Runnable { 138 final static int BUF_SIZE = 2048; 139 140 static final byte[] EOL = {(byte)'\r', (byte)'\n' }; 141 142 /* buffer to use for requests */ 143 byte[] buf; 144 /* Socket to client we're handling */ 145 private Socket s; 146 147 Worker() { 148 buf = new byte[2048]; 149 s = null; 150 } 151 152 synchronized void setSocket(Socket s) { 153 this.s = s; 154 notify(); 155 } 156 157 public synchronized void run() { 158 while(true) { 159 if (s == null) { 160 /* nothing to do */ 161 try { 162 wait(); 163 } catch (InterruptedException e) { 164 /* should not happen */ 165 continue; 166 } 167 } 168 try { 169 handleClient(); 170 } catch (Exception e) { 171 e.printStackTrace(); 172 } 173 /* go back in wait queue if there's fewer 174 * than numHandler connections. 175 */ 176 s = null; 177 Vector pool = WebServer.threads; 178 synchronized (pool) { 179 if (pool.size() >= WebServer.workers) { 180 /* too many threads, exit this one */ 181 return; 182 } else { 183 pool.addElement(this); 184 } 185 } 186 } 187 } 188 189 void handleClient() throws IOException { 190 InputStream is = new BufferedInputStream(s.getInputStream()); 191 PrintStream ps = new PrintStream(s.getOutputStream()); 192 /* we will only block in read for this many milliseconds 193 * before we fail with java.io.InterruptedIOException, 194 * at which point we will abandon the connection. 195 */ 196 s.setSoTimeout(WebServer.timeout); 197 198 /* zero out the buffer from last time */ 199 for (int i = 0; i 200 buf[i] = 0; 201 202 try { 203 /* We only support HTTP GET/HEAD, and don't 204 * support any fancy HTTP options, 205 * so we're only interested really in 206 * the first line. 207 */ 208 int nread = 0, r = 0; 209 210 outerloop: 211 while (nread < BUF_SIZE) { 212 r = is.read(buf, nread, BUF_SIZE - nread); 213 if (r == -1) { 214 /* EOF */ 215 return; 216 } 217 int i = nread; 218 nread += r; 219 for (; i < nread; i++) { 220 if (buf[i] == (byte)'\n' || buf[i] == (byte)'\r') { 221 break outerloop; 222 } 223 } 224 } 225 226 /* are we doing a GET or just a HEAD */ 227 boolean doingGet; 228 /* beginning of file name */ 229 int index; 230 if (buf[0] == (byte)'G' && 231 buf[1] == (byte)'E' && 232 233 buf[2] == (byte)'T' && 234 235 buf[3] == (byte)' ') { 236 doingGet = true; 237 index = 4; 238 } else if (buf[0] == (byte)'H' && 239 buf[1] == (byte)'E' && 240 241 buf[2] == (byte)'A' && 242 243 buf[3] == (byte)'D' && 244 245 buf[4] == (byte)' ') { 246 doingGet = false; 247 index = 5; 248 } else { 249 /* we don't support this method */ 250 ps.print("HTTP/1.0 " + HTTP_BAD_METHOD + 251 " unsupported method type: "); 252 ps.write(buf, 0, 5); 253 ps.write(EOL); 254 ps.flush(); 255 s.close(); 256 return; 257 } 258 259 int i = 0; 260 for (i = index; i < nread; i++) { 261 if (buf[i] == (byte)' ') { 262 break; 263 } 264 } 265 String fname = (new String(buf, 0, index, i-index)).replace('/', File.separatorChar); 266 if (fname.startsWith(File.separator)) { 267 fname = fname.substring(1); 268 } 269 File targ = new File(WebServer.root, fname); 270 if (targ.isDirectory()) { 271 File ind = new File(targ, "index.html"); 272 if (ind.exists()) { 273 targ = ind; 274 } 275 } 276 boolean OK = printHeaders(targ, ps); 277 if (doingGet) { 278 if (OK) { 279 sendFile(targ, ps); 280 } else { 281 send404(targ, ps); 282 } 283 } 284 } finally { 285 s.close(); 286 } 287 } 288 289 boolean printHeaders(File targ, PrintStream ps) throws IOException { 290 boolean ret = false; 291 int rCode = 0; 292 if (!targ.exists()) { 293 rCode = HTTP_NOT_FOUND; 294 ps.print("HTTP/1.0 " + HTTP_NOT_FOUND + " not found"); 295 ps.write(EOL); 296 ret = false; 297 } else { 298 rCode = HTTP_OK; 299 ps.print("HTTP/1.0 " + HTTP_OK+" OK"); 300 ps.write(EOL); 301 ret = true; 302 } 303 log("From " +s.getInetAddress().getHostAddress()+": GET " + 304 targ.getAbsolutePath()+"-->"+rCode); 305 ps.print("Server: Simple java"); 306 ps.write(EOL); 307 ps.print("Date: " + (new Date())); 308 ps.write(EOL); 309 if (ret) { 310 if (!targ.isDirectory()) { 311 ps.print("Content-length: "+targ.length()); 312 ps.write(EOL); 313 ps.print("Last Modified: " + (new Date(targ.lastModified()))); 314 ps.write(EOL); 315 String name = targ.getName(); 316 int ind = name.lastIndexOf('.'); 317 String ct = null; 318 if (ind > 0) { 319 ct = (String) map.get(name.substring(ind)); 320 } 321 if (ct == null) { 322 ct = "unknown/unknown"; 323 } 324 ps.print("Content-type: " + ct); 325 ps.write(EOL); 326 } else { 327 ps.print("Content-type: text/html"); 328 ps.write(EOL); 329 } 330 } 331 return ret; 332 } 333 334 void send404(File targ, PrintStream ps) throws IOException { 335 ps.write(EOL); 336 ps.write(EOL); 337 ps.println("Not Found\n\n"+ 338 "The requested resource was not found.\n"); 339 } 340 341 void sendFile(File targ, PrintStream ps) throws IOException { 342 InputStream is = null; 343 ps.write(EOL); 344 if (targ.isDirectory()) { 345 /* here, we take advantage of the fact 346 * that FileURLConnection will parse a directory 347 * listing into HTML for us. 348 */ 349 File ind = new File(targ, "index.html"); 350 if (ind.exists()) { 351 is = new FileInputStream(ind); 352 } else { 353 URL u = new URL("file", "", targ.getAbsolutePath()); 354 is = u.openStream(); 355 } 356 } else { 357 is = new FileInputStream(targ.getAbsolutePath()); 358 } 359 360 try { 361 int n; 362 while ((n = is.read(buf)) > 0) { 363 ps.write(buf, 0, n); 364 } 365 } finally { 366 is.close(); 367 } 368 } 369 370 /* mapping of file extensions to content-types */ 371 static java.util.Hashtable map = new java.util.Hashtable(); 372 373 static { 374 fillMap(); 375 } 376 static void setSuffix(String k, String v) { 377 map.put(k, v); 378 } 379 380 static void fillMap() { 381 setSuffix("", "content/unknown"); 382 setSuffix(".uu", "application/octet-stream"); 383 setSuffix(".exe", "application/octet-stream"); 384 setSuffix(".ps", "application/postscript"); 385 setSuffix(".zip", "application/zip"); 386 setSuffix(".sh", "application/x-shar"); 387 setSuffix(".tar", "application/x-tar"); 388 setSuffix(".snd", "audio/basic"); 389 setSuffix(".au", "audio/basic"); 390 setSuffix(".wav", "audio/x-wav"); 391 setSuffix(".gif", "image/gif"); 392 setSuffix(".jpg", "image/jpeg"); 393 setSuffix(".jpeg", "image/jpeg"); 394 setSuffix(".htm", "text/html"); 395 setSuffix(".html", "text/html"); 396 setSuffix(".text", "text/plain"); 397 setSuffix(".c", "text/plain"); 398 setSuffix(".cc", "text/plain"); 399 setSuffix(".c++", "text/plain"); 400 setSuffix(".h", "text/plain"); 401 setSuffix(".pl", "text/plain"); 402 setSuffix(".txt", "text/plain"); 403 setSuffix(".java", "text/plain"); 404 } 405 406 } 407 408 interface HttpConstants { 409 /** 2XX: generally "OK" */ 410 public static final int HTTP_OK = 200; 411 public static final int HTTP_CREATED = 201; 412 public static final int HTTP_ACCEPTED = 202; 413 public static final int HTTP_NOT_AUTHORITATIVE = 203; 414 public static final int HTTP_NO_CONTENT = 204; 415 public static final int HTTP_RESET = 205; 416 public static final int HTTP_PARTIAL = 206; 417 418 /** 3XX: relocation/redirect */ 419 public static final int HTTP_MULT_CHOICE = 300; 420 public static final int HTTP_MOVED_PERM = 301; 421 public static final int HTTP_MOVED_TEMP = 302; 422 public static final int HTTP_SEE_OTHER = 303; 423 public static final int HTTP_NOT_MODIFIED = 304; 424 public static final int HTTP_USE_PROXY = 305; 425 426 /** 4XX: client error */ 427 public static final int HTTP_BAD_REQUEST = 400; 428 public static final int HTTP_UNAUTHORIZED = 401; 429 public static final int HTTP_PAYMENT_REQUIRED = 402; 430 public static final int HTTP_FORBIDDEN = 403; 431 public static final int HTTP_NOT_FOUND = 404; 432 public static final int HTTP_BAD_METHOD = 405; 433 public static final int HTTP_NOT_ACCEPTABLE = 406; 434 public static final int HTTP_PROXY_AUTH = 407; 435 public static final int HTTP_CLIENT_TIMEOUT = 408; 436 public static final int HTTP_CONFLICT = 409; 437 public static final int HTTP_GONE = 410; 438 public static final int HTTP_LENGTH_REQUIRED = 411; 439 public static final int HTTP_PRECON_FAILED = 412; 440 public static final int HTTP_ENTITY_TOO_LARGE = 413; 441 public static final int HTTP_REQ_TOO_LONG = 414; 442 public static final int HTTP_UNSUPPORTED_TYPE = 415; 443 444 /** 5XX: server error */ 445 public static final int HTTP_SERVER_ERROR = 500; 446 public static final int HTTP_INTERNAL_ERROR = 501; 447 public static final int HTTP_BAD_GATEWAY = 502; 448 public static final int HTTP_UNAVAILABLE = 503; 449 public static final int HTTP_GATEWAY_TIMEOUT = 504; 450 public static final int HTTP_VERSION = 505; 451 }