001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2014 ForgeRock AS. 015 */ 016 017package org.forgerock.openig.doc; 018 019import org.glassfish.grizzly.http.Method; 020import org.glassfish.grizzly.http.server.HttpHandler; 021import org.glassfish.grizzly.http.server.HttpServer; 022import org.glassfish.grizzly.http.server.NetworkListener; 023import org.glassfish.grizzly.http.server.Request; 024import org.glassfish.grizzly.http.server.Response; 025 026import java.io.IOException; 027import java.io.InputStream; 028import java.util.Properties; 029import java.util.Scanner; 030import java.util.logging.Level; 031import java.util.logging.Logger; 032 033/** 034 * Simple servlet allowing user-agents to get a home page, 035 * and to post form-based login to access a protected profile page. 036 */ 037public final class SampleServer { 038 039 private static final String EOL = System.getProperty("line.separator"); 040 private static final Logger LOGGER = Logger.getLogger(SampleServer.class.getName()); 041 042 /** 043 * Start an HTTP server. 044 * 045 * @param args Optionally specify a free port number. Default: 8081. 046 */ 047 public static void main(String[] args) { 048 final String usage = "Specify an optional port number. Default: 8081."; 049 int port = 8081; 050 051 if (args.length > 1) { 052 System.out.println(usage); 053 System.exit(-1); 054 } 055 056 if (args.length == 1) { 057 port = Integer.parseInt(args[0]); 058 } 059 060 LOGGER.setLevel(Level.INFO); 061 runServer(port); 062 } 063 064 /** 065 * Run the HTTP server, listening on the chosen port. 066 * <p> 067 * On HTTP GET the server returns a home page with a login form. 068 * <p> 069 * On HTTP PUT with valid credentials, the server returns a profile page. 070 * 071 * @param port Port on which the server listens 072 */ 073 static void runServer(int port) { 074 start(port, true); 075 } 076 077 /** 078 * Run the HTTP server, listening on the chosen port. 079 * <p> 080 * Use stop() to shut the server down. 081 * 082 * @param port Port on which the server listens 083 * @return The HttpServer that is running if letRun is true 084 */ 085 static HttpServer start(final int port) { 086 return start(port, false); 087 } 088 089 /** 090 * Run the HTTP server, listening on the chosen port. 091 * 092 * @param port Port on which the server listens 093 * @param waitForCtrlC If true, only stop the server when the user enters Ctrl+C 094 * @return The HttpServer that is running if letRun is true 095 */ 096 static HttpServer start(final int port, final boolean waitForCtrlC) { 097 098 final HttpServer httpServer = new HttpServer(); 099 final NetworkListener networkListener = 100 new NetworkListener("sample-server", "0.0.0.0", port); 101 httpServer.addListener(networkListener); 102 httpServer.getServerConfiguration().addHttpHandler(new SampleHandler()); 103 104 if (waitForCtrlC) { 105 Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { 106 @Override 107 public void run() { 108 httpServer.shutdownNow(); 109 } 110 }, "shutDownHook")); 111 } 112 113 try { 114 LOGGER.info("Starting HTTP server on port " + port); 115 httpServer.start(); 116 if (waitForCtrlC) { 117 LOGGER.info("Press Ctrl+C to stop the server."); 118 Thread.currentThread().join(); 119 } 120 } catch (Exception e) { 121 LOGGER.info(e.getMessage()); 122 } 123 124 return httpServer; 125 } 126 127 /** 128 * Stop the HTTP Server started with waitForCtrlC set to false. 129 * 130 * @param httpServer The server to stop 131 */ 132 static void stop(final HttpServer httpServer) { 133 httpServer.shutdownNow(); 134 } 135 136 /** 137 * Handler for HTTP GET and HTTP PUT requests. 138 */ 139 static class SampleHandler extends HttpHandler { 140 141 @Override 142 public void service(Request request, Response response) throws Exception { 143 if (Method.GET == request.getMethod()) { 144 String homePage = getResourceAsString("/home.html"); 145 146 response.setContentType("text/html"); 147 response.setStatus(200, "OK"); 148 response.setContentLength(homePage.length()); 149 response.getWriter().write(homePage); 150 } 151 152 if (Method.POST == request.getMethod()) { 153 154 final String username = request.getParameter("username"); 155 final String password = request.getParameter("password"); 156 157 if (username == null || password == null) { 158 final String authRequired = "Authorization Required"; 159 response.setStatus(401, authRequired); 160 response.setContentLength(authRequired.length() + EOL.length()); 161 response.getWriter().write(authRequired + EOL); 162 return; 163 } 164 165 if (credentialsAreValid(username, password)) { 166 167 // Replace profile page placeholders and respond. 168 final StringBuilder headers = new StringBuilder(); 169 for (String name : request.getHeaderNames()) { 170 for (String header : request.getHeaders(name)) { 171 headers.append(name) 172 .append(": ") 173 .append(header) 174 .append("<br>"); 175 } 176 } 177 178 String profilePage = getResourceAsString("/profile.html") 179 .replaceAll(EOL, "####") 180 .replaceAll("USERNAME", username) 181 .replace("METHOD", request.getMethod().getMethodString()) 182 .replace("REQUEST_URI", request.getDecodedRequestURI()) 183 .replace("HEADERS", headers.toString()) 184 .replaceAll("####", EOL); 185 186 response.setContentType("text/html"); 187 response.setStatus(200, "OK"); 188 response.setContentLength(profilePage.length()); 189 response.getWriter().write(profilePage); 190 191 } else { 192 final String forbidden = "Forbidden"; 193 response.setStatus(403, forbidden); 194 response.setContentLength(forbidden.length() + EOL.length()); 195 response.getWriter().write(forbidden + EOL); 196 } 197 } 198 } 199 } 200 201 /** 202 * Check whether username and password credentials are valid. 203 * 204 * @param username A username such as {@code demo} 205 * @param password A password such as {@code changeit} 206 * 207 * @return True if the username matches the password in credentials.properties 208 * @throws java.io.IOException Could not read credentials.properties 209 */ 210 static synchronized boolean credentialsAreValid( 211 final String username, final String password) 212 throws IOException { 213 214 boolean result = false; 215 216 Properties credentials = new Properties(); 217 InputStream in = SampleHandler.class.getResourceAsStream("/credentials.properties"); 218 credentials.load(in); 219 220 final String pwd = credentials.getProperty(username); 221 if (pwd != null) { 222 result = pwd.equals(password); 223 } 224 225 in.close(); 226 227 return result; 228 } 229 230 /** 231 * Read the contents of a resource file into a string. 232 * 233 * @param resource Path to resource file 234 * @return String holding the content of the resource file 235 */ 236 static synchronized String getResourceAsString(final String resource) { 237 238 StringBuilder content = new StringBuilder(); 239 InputStream inputStream = SampleHandler.class.getResourceAsStream(resource); 240 241 Scanner scanner = null; 242 try { 243 scanner = new Scanner(inputStream); 244 while (scanner.hasNextLine()) { 245 content.append(scanner.nextLine()).append(EOL); 246 } 247 } finally { 248 if (scanner != null) { 249 scanner.close(); 250 } 251 } 252 253 return content.toString(); 254 } 255 256 /** 257 * Not used. 258 */ 259 private SampleServer() { 260 } 261}