1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.commons.vfs2.util;
28
29 import java.io.File;
30 import java.io.IOException;
31 import java.net.InetSocketAddress;
32 import java.net.URI;
33 import java.net.URISyntaxException;
34 import java.net.URL;
35 import java.security.KeyManagementException;
36 import java.security.KeyStoreException;
37 import java.security.NoSuchAlgorithmException;
38 import java.security.UnrecoverableKeyException;
39 import java.security.cert.CertificateException;
40 import java.util.Date;
41 import java.util.Locale;
42 import java.util.concurrent.ExecutionException;
43 import java.util.concurrent.Future;
44 import java.util.concurrent.TimeUnit;
45
46 import javax.net.ssl.SSLContext;
47
48 import org.apache.hc.client5.http.utils.DateUtils;
49 import org.apache.hc.core5.http.ContentType;
50 import org.apache.hc.core5.http.EndpointDetails;
51 import org.apache.hc.core5.http.EntityDetails;
52 import org.apache.hc.core5.http.HttpException;
53 import org.apache.hc.core5.http.HttpHeaders;
54 import org.apache.hc.core5.http.HttpRequest;
55 import org.apache.hc.core5.http.HttpStatus;
56 import org.apache.hc.core5.http.Message;
57 import org.apache.hc.core5.http.MethodNotSupportedException;
58 import org.apache.hc.core5.http.ProtocolException;
59 import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap;
60 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
61 import org.apache.hc.core5.http.nio.AsyncRequestConsumer;
62 import org.apache.hc.core5.http.nio.AsyncServerRequestHandler;
63 import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers;
64 import org.apache.hc.core5.http.nio.entity.NoopEntityConsumer;
65 import org.apache.hc.core5.http.nio.ssl.BasicServerTlsStrategy;
66 import org.apache.hc.core5.http.nio.ssl.FixedPortStrategy;
67 import org.apache.hc.core5.http.nio.support.AsyncResponseBuilder;
68 import org.apache.hc.core5.http.nio.support.BasicRequestConsumer;
69 import org.apache.hc.core5.http.protocol.HttpContext;
70 import org.apache.hc.core5.http.protocol.HttpCoreContext;
71 import org.apache.hc.core5.http.protocol.HttpDateGenerator;
72 import org.apache.hc.core5.io.CloseMode;
73 import org.apache.hc.core5.reactor.IOReactorConfig;
74 import org.apache.hc.core5.reactor.IOReactorStatus;
75 import org.apache.hc.core5.reactor.ListenerEndpoint;
76 import org.apache.hc.core5.ssl.SSLContexts;
77 import org.apache.hc.core5.util.TimeValue;
78
79
80
81
82 public final class NHttpFileServer {
83
84 private static class HttpFileHandler implements AsyncServerRequestHandler<Message<HttpRequest, Void>> {
85
86 private final File docRoot;
87
88 HttpFileHandler(final File docRoot) {
89 this.docRoot = docRoot;
90 }
91
92 @Override
93 public void handle(final Message<HttpRequest, Void> message, final ResponseTrigger responseTrigger, final HttpContext context)
94 throws HttpException, IOException {
95 println("Handling " + message + " in " + context);
96 final HttpRequest request = message.getHead();
97 final String method = request.getMethod().toUpperCase(Locale.ROOT);
98 if (!method.equals("GET") && !method.equals("HEAD") && !method.equals("POST")) {
99 throw new MethodNotSupportedException(method + " method not supported");
100 }
101
102 final URI requestUri;
103 try {
104 requestUri = request.getUri();
105 } catch (final URISyntaxException ex) {
106 throw new ProtocolException(ex.getMessage(), ex);
107 }
108 final String path = requestUri.getPath();
109 final File file = new File(docRoot, path);
110 final ContentType mimeType = ContentType.TEXT_HTML;
111 if (!file.exists()) {
112
113 final String msg = "File " + file.getPath() + " not found";
114 println(msg);
115 responseTrigger.submitResponse(
116 AsyncResponseBuilder.create(HttpStatus.SC_NOT_FOUND).setEntity("<html><body><h1>" + msg + "</h1></body></html>", mimeType).build(),
117 context);
118
119 } else if (!file.canRead()) {
120 final String msg = "Cannot read file " + file.getPath();
121 println(msg);
122 responseTrigger.submitResponse(
123 AsyncResponseBuilder.create(HttpStatus.SC_FORBIDDEN).setEntity("<html><body><h1>" + msg + "</h1></body></html>", mimeType).build(),
124 context);
125
126 } else {
127
128 ContentType contentType;
129 final String fileName = file.getName().toLowerCase(Locale.ROOT);
130
131
132
133
134
135
136
137
138
139
140
141
142 contentType = ContentType.TEXT_HTML;
143 final HttpCoreContext coreContext = HttpCoreContext.adapt(context);
144 final EndpointDetails endpoint = coreContext.getEndpointDetails();
145
146 println(endpoint + " | serving file " + file.getPath());
147
148
149 responseTrigger.submitResponse(
150 AsyncResponseBuilder.create(HttpStatus.SC_OK)
151 .setEntity(file.isDirectory()
152 ? AsyncEntityProducers.create(file.toString(), contentType)
153 : AsyncEntityProducers.create(file, contentType))
154 .addHeader(HttpHeaders.LAST_MODIFIED, DateUtils.formatDate(new Date(file.lastModified())))
155 .build(), context);
156
157 }
158 }
159
160 @Override
161 public AsyncRequestConsumer<Message<HttpRequest, Void>> prepare(final HttpRequest request, final EntityDetails entityDetails, final HttpContext context)
162 throws HttpException {
163 return new BasicRequestConsumer<>(entityDetails != null ? new NoopEntityConsumer() : null);
164 }
165
166 }
167
168 public static final boolean DEBUG = Boolean.getBoolean(NHttpFileServer.class.getSimpleName() + ".debug");
169
170 public static void main(final String[] args) throws Exception {
171 if (args.length < 1) {
172 System.err.println("Please specify document root directory");
173 System.exit(1);
174 }
175
176 final File docRoot = new File(args[0]);
177 int port = 8080;
178 if (args.length >= 2) {
179 port = Integer.parseInt(args[1]);
180 }
181 start(port, docRoot, 0).awaitTermination();
182 }
183
184 static void println(final String msg) {
185 if (DEBUG) {
186 System.out.println(HttpDateGenerator.INSTANCE.getCurrentDate() + " | " + msg);
187 }
188 }
189
190 public static NHttpFileServer start(final int port, final File docRoot, final long waitMillis) throws KeyManagementException, UnrecoverableKeyException,
191 NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException, InterruptedException, ExecutionException {
192 return new NHttpFileServer(port, docRoot).start();
193 }
194
195 private final File docRoot;
196
197 private ListenerEndpoint listenerEndpoint;
198
199 private final int port;
200
201 private HttpAsyncServer server;
202
203 private NHttpFileServer(final int port, final File docRoot) {
204 this.port = port;
205 this.docRoot = docRoot;
206 }
207
208 private void awaitTermination() throws InterruptedException {
209 server.awaitShutdown(TimeValue.MAX_VALUE);
210 }
211
212 public void close() {
213 if (server.getStatus() == IOReactorStatus.ACTIVE) {
214 final CloseMode closeMode = CloseMode.GRACEFUL;
215 println("HTTP server shutting down (closeMode=" + closeMode + ")...");
216 server.close(closeMode);
217 println("HTTP server shut down.");
218 }
219 }
220
221 public int getPort() {
222 if (server == null) {
223 return port;
224 }
225 return ((InetSocketAddress) listenerEndpoint.getAddress()).getPort();
226 }
227
228 public void shutdown(final long gracePeriod, final TimeUnit timeUnit) throws InterruptedException {
229 if (server != null) {
230 server.initiateShutdown();
231 server.awaitShutdown(TimeValue.of(gracePeriod, timeUnit));
232 }
233
234 }
235
236 private NHttpFileServer start() throws KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, CertificateException,
237 IOException, InterruptedException, ExecutionException {
238 final AsyncServerBootstrap bootstrap = AsyncServerBootstrap.bootstrap();
239 SSLContext sslContext = null;
240 if (port == 8443 || port == 443) {
241
242 final URL url = NHttpFileServer.class.getResource("/test.keystore");
243 if (url == null) {
244 println("Keystore not found");
245 System.exit(1);
246 }
247 println("Loading keystore " + url);
248 sslContext = SSLContexts.custom().loadKeyMaterial(url, "nopassword".toCharArray(), "nopassword".toCharArray()).build();
249 bootstrap.setTlsStrategy(new BasicServerTlsStrategy(sslContext, new FixedPortStrategy(port)));
250 }
251
252
253 final IOReactorConfig config = IOReactorConfig.custom()
254 .setSoTimeout(15, TimeUnit.SECONDS)
255 .setTcpNoDelay(true)
256 .build();
257
258
259 server = bootstrap
260 .setExceptionCallback(Exception::printStackTrace)
261 .setIOReactorConfig(config)
262 .register("*", new HttpFileHandler(docRoot)).create();
263
264 Runtime.getRuntime().addShutdownHook(new Thread(this::close));
265
266 server.start();
267
268 final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(port));
269 listenerEndpoint = future.get();
270 println("Serving " + docRoot + " on " + listenerEndpoint.getAddress()
271 + (sslContext == null ? "" : " with " + sslContext.getProvider() + " " + sslContext.getProtocol()));
272 return this;
273 }
274
275 }