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.DateFormat; 31 import java.text.SimpleDateFormat; 32 import java.util.*; 33 import java.util.concurrent.*; 34 import com.sun.net.httpserver.*; 35 36 import static java.lang.System.out; 37 import static java.net.HttpURLConnection.*; 38 import static java.util.concurrent.TimeUnit.*; 39 40 41 public class TrivialWebServer { 42 43 private boolean debug = System.getenv("TWS_DEBUG") != null; 44 45 private final PrintStream log; 46 private final Handler handler; 47 48 private TrivialWebServer(Path rpath, PrintStream l) { 49 log = l; 50 handler = new Handler(rpath); 51 } 52 53 private void dump(String t, Headers hs) { 54 log.format("%s headers%n", t); 55 for (Map.Entry<String,List<String>> e : hs.entrySet()) { 56 log.format(" %s : %s%n", e.getKey(), e.getValue()); 57 } 58 } 59 60 private static final String pattern = "EEE, dd MMM yyyy HH:mm:ss zzz"; 61 private static final TimeZone gmtTZ = TimeZone.getTimeZone("GMT"); 62 private static final ThreadLocal<DateFormat> HTTP_DATE = 63 new ThreadLocal<DateFormat>() { 64 @Override protected DateFormat initialValue() { 65 DateFormat df = new SimpleDateFormat(pattern, Locale.US); 66 df.setTimeZone(gmtTZ); 67 return df; 68 } 69 }; 70 71 private class Handler 72 implements HttpHandler 73 { 74 75 private URI root; 76 private URI BARE_ROOT = URI.create("/"); 77 78 Handler(Path rpath) { 79 root = rpath.toAbsolutePath().toUri().normalize(); 80 if (debug) 81 log.format("root %s%n", root); 82 } 83 84 private void notFound(HttpExchange hx, URI hxu) 85 throws IOException 86 { 87 if (debug) 88 log.format("HTTP_NOT_FOUND"); 89 byte[] err 90 = ("<b>Not found: " + hxu + "</b>").getBytes("ASCII"); 91 hx.sendResponseHeaders(HTTP_NOT_FOUND, err.length); 92 OutputStream os = hx.getResponseBody(); 93 os.write(err); 94 } 95 96 private String etag(Object ob) { 97 if (ob == null) 98 return null; 99 return '"' + Integer.toHexString(ob.hashCode()) + '"'; 100 } 101 102 public void handle(HttpExchange hx) throws IOException { 103 try { 104 105 URI hxu = hx.getRequestURI(); 106 URI u = root.resolve(BARE_ROOT.relativize(hxu)); 107 Path p = Paths.get(u); 108 if (debug) { 109 log.format("%s %s --> %s%n", hx.getRequestMethod(), hxu, p); 110 dump("req", hx.getRequestHeaders()); 111 } 112 if (!Files.exists(p)) { 113 notFound(hx, hxu); 114 return; 115 } 116 BasicFileAttributes ba 117 = Files.readAttributes(p, BasicFileAttributes.class); 118 119 // Directory -> index.html 120 // 121 if (ba.isDirectory()) { 122 String us = hxu.toString(); 123 if (!us.endsWith("/")) { 124 Headers ahs = hx.getResponseHeaders(); 125 ahs.put("Location", Arrays.asList(us + "/")); 126 if (debug) 127 dump("HTTP_MOVED_PERM", ahs); 128 hx.sendResponseHeaders(HTTP_MOVED_PERM, -1); 129 return; 130 } 131 p = p.resolve("index.html"); 132 if (!Files.exists(p)) { 133 notFound(hx, hxu); 134 return; 135 } 136 ba = Files.readAttributes(p, BasicFileAttributes.class); 137 } 138 if (debug) 139 log.format("%s --> %s%n", hxu, p); 140 141 // Check Last-Modified/ETag headers 142 // 143 DateFormat df = HTTP_DATE.get(); 144 long mtime = ba.lastModifiedTime().to(TimeUnit.SECONDS); 145 String etag = etag(ba.fileKey()); 146 Headers rhs = hx.getRequestHeaders(); 147 String rmtime = rhs.getFirst("If-Modified-Since"); 148 boolean condget = false; 149 boolean sendit = false; 150 if (rmtime != null) { 151 condget = true; 152 long rmt = SECONDS.convert(df.parse(rmtime).getTime(), 153 MILLISECONDS); 154 sendit = mtime > rmt; 155 } 156 String retag = rhs.getFirst("If-None-Match"); 157 if (retag != null) { 158 condget = true; 159 sendit = sendit || !retag.equals(etag); 160 } 161 if (condget && !sendit) { 162 if (debug) 163 out.format("HTTP_NOT_MODIFIED%n"); 164 hx.sendResponseHeaders(HTTP_NOT_MODIFIED, -1); 165 return; 166 } 167 168 // Send content 169 // 170 Headers ahs = hx.getResponseHeaders(); 171 ahs.set("Content-Type", "application/octet-stream"); 172 ahs.set("Last-Modified", 173 df.format(new Date(MILLISECONDS.convert(mtime, SECONDS)))); 174 if (etag != null) 175 ahs.set("ETag", etag); 176 if (debug) 177 dump("HTTP_OK", ahs); 178 if ("HEAD".equalsIgnoreCase(hx.getRequestMethod())) { 179 // HEAD: manually set the c-l and write no response body 180 ahs.set("Content-length", Long.toString(ba.size())); 181 hx.sendResponseHeaders(HTTP_OK, -1); 182 } else { 183 hx.sendResponseHeaders(HTTP_OK, ba.size()); 184 Files.copy(p, hx.getResponseBody()); 185 } 186 } catch (Exception x) { 187 x.printStackTrace(out); 188 } finally { 189 hx.close(); 190 } 191 } 192 193 } 194 195 private HttpServer server = null; 196 197 private void bind(int port) throws IOException { 198 server = HttpServer.create(new InetSocketAddress(port), 10); 199 server.createContext("/", handler); 200 server.setExecutor(Executors.newCachedThreadPool()); 201 } 202 203 private void start() throws IOException { 204 server.start(); 205 } 206 207 public void stop() throws IOException { 208 server.stop(0); 209 ((ExecutorService)server.getExecutor()).shutdown(); 210 } 211 212 public int port() { 213 if (server == null) 214 throw new IllegalStateException(); 215 return server.getAddress().getPort(); 216 } 217 218 public static TrivialWebServer create(Path root, PrintStream log) 219 throws IOException 220 { 221 TrivialWebServer tws = new TrivialWebServer(root, log); 222 Random r = new Random(); 223 for (;;) { 224 int p = r.nextInt((1 << 16) - 1024) + 1024; 225 try { 226 tws.bind(p); 227 break; 228 } catch (BindException x) { 229 continue; 230 } 231 } 232 tws.start(); 233 return tws; 234 } 235 236 public static TrivialWebServer create(Path root, int port, PrintStream log) 237 throws IOException 238 { 239 if (port == -1) 240 return create(root, log); 241 TrivialWebServer tws = new TrivialWebServer(root, log); 242 tws.bind(port); 243 tws.start(); 244 return tws; 245 } 246 247 public static void main(String[] args) throws IOException { 248 int port = 8081; 249 Path root = Paths.get("."); 250 Iterator<String> ai = Arrays.asList(args).iterator(); 251 while (ai.hasNext()) { 252 String a = ai.next(); 253 if (a.matches("\\d+")) { 254 port = Integer.parseInt(a); 255 continue; 256 } 257 if (a.equals("-r")) { 258 port = -1; 259 continue; 260 } 261 root = Paths.get(a); 262 } 263 TrivialWebServer tws 264 = TrivialWebServer.create(root, port, System.out); 265 System.out.format("Serving %s on port %d%n", root, tws.port()); 266 } 267 268 }