1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
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
;
25 * This implements a basic HTTP server.
27 * @param <S> Session type, if used at all.
30 public class SimpleHTTPServer
<S
>
33 /** Connection backlog. */
34 private static final int _BACKLOG
=
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
;
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.
56 @SuppressWarnings("resource")
57 public SimpleHTTPServer(S __session
)
60 // Open server, use a random port
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();
73 this.session
= __session
;
81 public final void close()
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.
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();
121 response
= __handler
.apply(this.session
, request
);
123 // Stop handling? Do not just close the connection as that can
125 boolean stopHandling
= (response
== null);
127 response
= SimpleHTTPServer
.__responseEnd();
129 // Send back to the client
130 try (OutputStream out
= client
.getOutputStream())
132 response
.writeTo(out
);
138 // Stop the loop now?
143 // Could not read, so ignore and try again later
144 catch (InterruptedIOException ignored
)
149 // Request was handled and one was returned
154 * Returns the final server response.
156 * @return The response.
159 private static SimpleHTTPResponse
__responseEnd()
161 SimpleHTTPResponseBuilder response
= new SimpleHTTPResponseBuilder();
163 response
.status(SimpleHTTPStatus
.OK
);
164 response
.addHeader("Content-Type",
166 response
.spliceResources(SimpleHTTPResponse
.class,
169 return response
.build();
173 * Returns the server's icon.
175 * @return The response.
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");
187 try (InputStream in
= SimpleHTTPResponse
.class.getResourceAsStream(
188 "favicon.ico.base64");
189 ByteArrayOutputStream baos
= new ByteArrayOutputStream())
192 byte[] buf
= new byte[4096];
195 int rc
= in
.read(buf
);
200 baos
.write(buf
, 0, rc
);
204 response
.body(Base64
.getMimeDecoder().decode(baos
.toByteArray()));
208 catch (IOException
|NullPointerException e
)
210 throw new RuntimeException("Could not read favicon", e
);
213 return response
.build();