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
83 public class NHttpFileServer {
84
85 private static class HttpFileHandler implements AsyncServerRequestHandler<Message<HttpRequest, Void>> {
86
87 private final File docRoot;
88
89 HttpFileHandler(final File docRoot) {
90 this.docRoot = docRoot;
91 }
92
93 @Override
94 public void handle(final Message<HttpRequest, Void> message, final ResponseTrigger responseTrigger,
95 final HttpContext context) throws HttpException, IOException {
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(AsyncResponseBuilder.create(HttpStatus.SC_NOT_FOUND)
116 .setEntity("<html><body><h1>" + msg + "</h1></body></html>", mimeType).build(), context);
117
118 } else if (!file.canRead()) {
119 final String msg = "Cannot read file " + file.getPath();
120 println(msg);
121 responseTrigger.submitResponse(AsyncResponseBuilder.create(HttpStatus.SC_FORBIDDEN)
122 .setEntity("<html><body><h1>" + msg + "</h1></body></html>", mimeType).build(), context);
123
124 } else {
125
126 ContentType contentType;
127 final String filename = file.getName().toLowerCase(Locale.ROOT);
128
129
130
131
132
133
134
135
136
137
138
139
140 contentType = ContentType.TEXT_HTML;
141 final HttpCoreContext coreContext = HttpCoreContext.adapt(context);
142 final EndpointDetails endpoint = coreContext.getEndpointDetails();
143
144 println(endpoint + " | serving file " + file.getPath());
145
146
147 responseTrigger.submitResponse(
148 AsyncResponseBuilder.create(HttpStatus.SC_OK)
149 .setEntity(file.isDirectory()
150 ? AsyncEntityProducers.create(file.toString(), contentType)
151 : AsyncEntityProducers.create(file, contentType))
152 .addHeader(HttpHeaders.LAST_MODIFIED, DateUtils.formatDate(new Date(file.lastModified())))
153 .build(), context);
154
155 }
156 }
157
158 @Override
159 public AsyncRequestConsumer<Message<HttpRequest, Void>> prepare(final HttpRequest request,
160 final EntityDetails entityDetails, final HttpContext context) throws HttpException {
161 return new BasicRequestConsumer<>(entityDetails != null ? new NoopEntityConsumer() : null);
162 }
163
164 }
165
166 public static boolean DEBUG = Boolean.getBoolean(NHttpFileServer.class.getSimpleName() + ".debug");
167
168 public static void main(final String[] args) throws Exception {
169 if (args.length < 1) {
170 System.err.println("Please specify document root directory");
171 System.exit(1);
172 }
173
174 final File docRoot = new File(args[0]);
175 int port = 8080;
176 if (args.length >= 2) {
177 port = Integer.parseInt(args[1]);
178 }
179 new NHttpFileServer(port, docRoot).start().awaitTermination();
180 }
181
182 static final void println(final String msg) {
183 if (DEBUG) {
184 System.out.println(HttpDateGenerator.INSTANCE.getCurrentDate() + " | " + msg);
185 }
186 }
187
188 public static NHttpFileServer start(final int port, final File docRoot, final long waitMillis)
189 throws KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException,
190 CertificateException, IOException, InterruptedException, ExecutionException {
191 return new NHttpFileServer(port, docRoot).start();
192 }
193
194 private final File docRoot;
195 private ListenerEndpoint listenerEndpoint;
196 private final int port;
197 private HttpAsyncServer server;
198
199 private NHttpFileServer(final int port, final File docRoot) {
200 this.port = port;
201 this.docRoot = docRoot;
202 }
203
204 private void awaitTermination() throws InterruptedException {
205 server.awaitShutdown(TimeValue.MAX_VALUE);
206 }
207
208 public void close() {
209 if (server.getStatus() == IOReactorStatus.ACTIVE) {
210 final CloseMode closeMode = CloseMode.GRACEFUL;
211 println("HTTP server shutting down (closeMode=" + closeMode + ")...");
212 server.close(closeMode);
213 println("HTTP server shut down.");
214 }
215 }
216
217 public int getPort() {
218 if (server == null) {
219 return port;
220 }
221 return ((InetSocketAddress) listenerEndpoint.getAddress()).getPort();
222 }
223
224 public void shutdown(final long gracePeriod, final TimeUnit timeUnit) throws InterruptedException {
225 if (server != null) {
226 server.initiateShutdown();
227 server.awaitShutdown(TimeValue.of(gracePeriod, timeUnit));
228 }
229
230 }
231
232 private NHttpFileServer start() throws KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException,
233 KeyStoreException, CertificateException, IOException, InterruptedException, ExecutionException {
234 final AsyncServerBootstrap bootstrap = AsyncServerBootstrap.bootstrap();
235 SSLContext sslContext = null;
236 if (port == 8443 || port == 443) {
237
238 final URL url = NHttpFileServer.class.getResource("/test.keystore");
239 if (url == null) {
240 println("Keystore not found");
241 System.exit(1);
242 }
243 println("Loading keystore " + url);
244 sslContext = SSLContexts.custom()
245 .loadKeyMaterial(url, "nopassword".toCharArray(), "nopassword".toCharArray()).build();
246 bootstrap.setTlsStrategy(new BasicServerTlsStrategy(sslContext, new FixedPortStrategy(port)));
247 }
248
249
250 final IOReactorConfig config = IOReactorConfig.custom()
251 .setSoTimeout(15, TimeUnit.SECONDS)
252 .setTcpNoDelay(true)
253 .build();
254
255
256 server = bootstrap.setIOReactorConfig(config).register("*", new HttpFileHandler(docRoot)).create();
257
258 Runtime.getRuntime().addShutdownHook(new Thread() {
259 @Override
260 public void run() {
261 close();
262 }
263
264 });
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 }