1 /* 2 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 import java.io.*; 27 import java.nio.file.*; 28 import java.nio.file.attribute.*; 29 import java.net.*; 30 import java.text.SimpleDateFormat; 31 import java.util.*; 32 import java.util.concurrent.*; 33 import com.sun.net.httpserver.*; 34 35 import static java.lang.System.out; 36 import static java.lang.System.err; 37 import static java.net.HttpURLConnection.*; 38 39 40 public class TrivialWebServer { 41 42 private boolean debug = System.getenv("TWS_DEBUG") != null; 43 44 private final PrintStream log; 45 private final Handler handler; 46 47 private TrivialWebServer(Path rpath, PrintStream l) { 48 log = l; 49 handler = new Handler(rpath); 50 } 51 52 private void dump(String t, Headers hs) { 53 log.format("%s headers%n", t); 54 for (Map.Entry<String,List<String>> e : hs.entrySet()) { 55 log.format(" %s : %s%n", e.getKey(), e.getValue()); 56 } 57 } 58 59 private static final SimpleDateFormat HTTP_DATE; 60 61 static { 62 HTTP_DATE = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", 63 Locale.US); 64 HTTP_DATE.setTimeZone(TimeZone.getTimeZone("GMT")); 65 } 66 67 private class Handler 68 implements HttpHandler 69 { 70 71 private URI root; 72 private URI BARE_ROOT = URI.create("/"); 73 74 Handler(Path rpath) { 75 root = rpath.toAbsolutePath().toUri().normalize(); 76 if (debug) 77 log.format("root %s%n", root); 78 } 79 80 private void notFound(HttpExchange hx, URI hxu) 81 throws IOException 82 { 83 byte[] err 84 = ("<b>Not found: " + hxu + "</b>").getBytes("ASCII"); 85 hx.sendResponseHeaders(HTTP_NOT_FOUND, err.length); 86 OutputStream os = hx.getResponseBody(); 87 os.write(err); 88 } 89 90 private String etag(Object ob) { 91 if (ob == null) 92 return null; 93 return '"' + Integer.toHexString(ob.hashCode()) + '"'; 94 } 95 96 public void handle(HttpExchange hx) throws IOException { 97 try { 98 99 URI hxu = hx.getRequestURI(); 100 URI u = root.resolve(BARE_ROOT.relativize(hxu)); 101 Path p = Paths.get(u); 102 if (debug) { 103 log.format("%s --> %s%n", hxu, p); 104 dump("req", hx.getRequestHeaders()); 105 } 106 if (!Files.exists(p)) { 107 notFound(hx, hxu); 108 return; 109 } 110 BasicFileAttributes ba 111 = Files.readAttributes(p, BasicFileAttributes.class); 112 113 // Directory -> index.html 114 // 115 if (ba.isDirectory()) { 116 String us = hxu.toString(); 117 if (!us.endsWith("/")) { 118 Headers ahs = hx.getResponseHeaders(); 119 ahs.put("Location", Arrays.asList(us + "/")); 120 hx.sendResponseHeaders(HTTP_MOVED_PERM, -1); 121 return; 122 } 123 p = p.resolve("index.html"); 124 if (!Files.exists(p)) { 125 notFound(hx, hxu); 126 return; 127 } 128 ba = Files.readAttributes(p, BasicFileAttributes.class); 129 } 130 if (debug) 131 log.format("%s --> %s%n", hxu, p); 132 133 // Check Last-Modified/ETag headers 134 // 135 long mtime = ba.lastModifiedTime().toMillis(); 136 String etag = etag(ba.fileKey()); 137 Headers rhs = hx.getRequestHeaders(); 138 String rmtime = rhs.getFirst("If-Modified-Since"); 139 boolean condget = false; 140 boolean sendit = false; 141 if (rmtime != null) { 142 condget = true; 143 long rmt = HTTP_DATE.parse(rmtime).getTime(); 144 sendit = mtime > rmt; 145 } 146 String retag = rhs.getFirst("If-None-Match"); 147 boolean tagChanged = true; 148 if (retag != null) { 149 condget = true; 150 sendit = sendit || !retag.equals(etag); 151 } 152 if (condget && !sendit) { 153 hx.sendResponseHeaders(HTTP_NOT_MODIFIED, -1); 154 return; 155 } 156 157 // Send content 158 // 159 Headers ahs = hx.getResponseHeaders(); 160 ahs.set("Content-Type", "application/octet-stream"); 161 ahs.set("Last-Modified", 162 HTTP_DATE.format(new Date(mtime))); 163 if (etag != null) 164 ahs.set("ETag", etag); 165 if (debug) 166 dump("ans", hx.getResponseHeaders()); 167 hx.sendResponseHeaders(HTTP_OK, ba.size()); 168 Files.copy(p, hx.getResponseBody()); 169 170 } catch (Exception x) { 171 x.printStackTrace(out); 172 } finally { 173 hx.close(); 174 } 175 } 176 177 } 178 179 private HttpServer server = null; 180 181 private void bind(int port) throws IOException { 182 server = HttpServer.create(new InetSocketAddress(port), 10); 183 server.createContext("/", handler); 184 server.setExecutor(Executors.newCachedThreadPool()); 185 } 186 187 private void start() throws IOException { 188 server.start(); 189 } 190 191 public void stop() throws IOException { 192 server.stop(0); 193 ((ExecutorService)server.getExecutor()).shutdown(); 194 } 195 196 public int port() { 197 if (server == null) 198 throw new IllegalStateException(); 199 return server.getAddress().getPort(); 200 } 201 202 public static TrivialWebServer create(Path root, PrintStream log) 203 throws IOException 204 { 205 TrivialWebServer tws = new TrivialWebServer(root, log); 206 Random r = new Random(); 207 for (;;) { 208 int p = r.nextInt((1 << 16) - 1024) + 1024; 209 try { 210 tws.bind(p); 211 break; 212 } catch (BindException x) { 213 continue; 214 } 215 } 216 tws.start(); 217 return tws; 218 } 219 220 public static TrivialWebServer create(Path root, int port, PrintStream log) 221 throws IOException 222 { 223 if (port == -1) 224 return create(root, log); 225 TrivialWebServer tws = new TrivialWebServer(root, log); 226 tws.bind(port); 227 tws.start(); 228 return tws; 229 } 230 231 public static void main(String[] args) throws IOException { 232 int port = 8081; 233 Path root = Paths.get("."); 234 Iterator<String> ai = Arrays.asList(args).iterator(); 235 while (ai.hasNext()) { 236 String a = ai.next(); 237 if (a.matches("\\d+")) { 238 port = Integer.parseInt(a); 239 continue; 240 } 241 if (a.equals("-r")) { 242 port = -1; 243 continue; 244 } 245 root = Paths.get(a); 246 } 247 TrivialWebServer tws 248 = TrivialWebServer.create(root, port, System.out); 249 System.out.format("Serving %s on port %d%n", root, tws.port()); 250 } 251 252 }