Commonize into PathUtils; On Linux/BSD try to use xdg-open/x-www-browser if Java...
[SquirrelJME.git] / buildSrc / src / main / java / cc / squirreljme / plugin / util / SimpleHTTPServer.java
blobff802dc612ce03443b8dc49f98305fc299cdd802
1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
3 // SquirrelJME
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the GNU General Public License v3+, or later.
7 // See license.mkd for licensing and copyright information.
8 // ---------------------------------------------------------------------------
10 package cc.squirreljme.plugin.util;
12 import java.io.ByteArrayOutputStream;
13 import java.io.Closeable;
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.io.InterruptedIOException;
17 import java.io.OutputStream;
18 import java.net.InetAddress;
19 import java.net.ServerSocket;
20 import java.net.Socket;
21 import java.util.Base64;
22 import java.util.function.BiFunction;
24 /**
25 * This implements a basic HTTP server.
27 * @param <S> Session type, if used at all.
28 * @since 2020/06/26
30 public class SimpleHTTPServer<S>
31 implements Closeable
33 /** Connection backlog. */
34 private static final int _BACKLOG =
35 64;
37 /** The hostname. */
38 public final String hostname;
40 /** The service port. */
41 public final int port;
43 /** The session used. */
44 protected final S session;
46 /** The socket to listen on. */
47 private final ServerSocket _socket;
49 /**
50 * Initializes the simple server.
52 * @param __session The session to use, may be {@code null}.
53 * @throws IOException If the server could not be opened.
54 * @since 2020/06/26
56 @SuppressWarnings("resource")
57 public SimpleHTTPServer(S __session)
58 throws IOException
60 // Open server, use a random port
61 ServerSocket socket;
62 this._socket = (socket = new ServerSocket(0,
63 SimpleHTTPServer._BACKLOG, InetAddress.getLoopbackAddress()));
65 // Set timeout for any connections accordingly to not hang forever
66 socket.setSoTimeout(5_000);
68 // Where do we connect?
69 this.hostname = this._socket.getInetAddress().getHostName();
70 this.port = this._socket.getLocalPort();
72 // Store session
73 this.session = __session;
76 /**
77 * {@inheritDoc}
78 * @since 2020/06/26
80 @Override
81 public final void close()
82 throws IOException
84 this._socket.close();
87 /**
88 * Handles the next HTTP call.
90 * @param __handler The handler, this may return {@code null} which returns
91 * {@code false} from this method.
92 * @return If {@code __handler} returned {@code null} then {@code false}
93 * will be returned, otherwise {@code true}.
94 * @throws IOException On read/write errors.
95 * @throws NullPointerException On null arguments.
96 * @throws SimpleHTTPProtocolException On protocol errors.
97 * @since 2020/06/26
99 public final boolean next(
100 BiFunction<S, SimpleHTTPRequest, SimpleHTTPResponse> __handler)
101 throws IOException, NullPointerException, SimpleHTTPProtocolException
103 if (__handler == null)
104 throw new NullPointerException("NARG");
106 // Wait for a client request
107 try (Socket client = this._socket.accept();
108 InputStream in = client.getInputStream())
110 // Do not delay TCP at all, this is very short lived
111 client.setTcpNoDelay(true);
113 // Read in the request
114 SimpleHTTPRequest request = SimpleHTTPRequest.parse(in);
116 // Handle the request, always do our own favorite icon
117 SimpleHTTPResponse response;
118 if ("/favicon.ico".equals(request.path.getPath()))
119 response = SimpleHTTPServer.__responseFavIcon();
120 else
121 response = __handler.apply(this.session, request);
123 // Stop handling? Do not just close the connection as that can
124 // seem a bit rude
125 boolean stopHandling = (response == null);
126 if (stopHandling)
127 response = SimpleHTTPServer.__responseEnd();
129 // Send back to the client
130 try (OutputStream out = client.getOutputStream())
132 response.writeTo(out);
134 // Flush
135 out.flush();
138 // Stop the loop now?
139 if (stopHandling)
140 return false;
143 // Could not read, so ignore and try again later
144 catch (InterruptedIOException ignored)
146 return true;
149 // Request was handled and one was returned
150 return true;
154 * Returns the final server response.
156 * @return The response.
157 * @since 2020/06/27
159 private static SimpleHTTPResponse __responseEnd()
161 SimpleHTTPResponseBuilder response = new SimpleHTTPResponseBuilder();
163 response.status(SimpleHTTPStatus.OK);
164 response.addHeader("Content-Type",
165 "text/html");
166 response.spliceResources(SimpleHTTPResponse.class,
167 "end.html", null);
169 return response.build();
173 * Returns the server's icon.
175 * @return The response.
176 * @since 2020/06/27
178 private static SimpleHTTPResponse __responseFavIcon()
180 SimpleHTTPResponseBuilder response = new SimpleHTTPResponseBuilder();
182 response.status(SimpleHTTPStatus.OK);
183 response.addHeader("Content-Type",
184 "image/vnd.microsoft.icon");
186 // Read in the icon
187 try (InputStream in = SimpleHTTPResponse.class.getResourceAsStream(
188 "favicon.ico.base64");
189 ByteArrayOutputStream baos = new ByteArrayOutputStream())
191 // Copy the data
192 byte[] buf = new byte[4096];
193 for (;;)
195 int rc = in.read(buf);
197 if (rc < 0)
198 break;
200 baos.write(buf, 0, rc);
203 // Decode it
204 response.body(Base64.getMimeDecoder().decode(baos.toByteArray()));
207 // Failed to decode
208 catch (IOException|NullPointerException e)
210 throw new RuntimeException("Could not read favicon", e);
213 return response.build();