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}