]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/http/server/StaticHttpHandler.java
Prepare next development cycle
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / http / server / StaticHttpHandler.java
1 package org.argeo.cms.http.server;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.OutputStream;
6 import java.io.UncheckedIOException;
7 import java.lang.System.Logger;
8 import java.lang.System.Logger.Level;
9 import java.net.FileNameMap;
10 import java.net.InetAddress;
11 import java.net.InetSocketAddress;
12 import java.net.MalformedURLException;
13 import java.net.URI;
14 import java.net.URL;
15 import java.net.URLConnection;
16 import java.nio.file.Files;
17 import java.nio.file.Path;
18 import java.nio.file.Paths;
19 import java.util.Map;
20 import java.util.NavigableMap;
21 import java.util.TreeMap;
22
23 import org.argeo.cms.acr.CmsContent;
24 import org.argeo.cms.http.HttpHeader;
25 import org.argeo.cms.http.HttpStatus;
26 import org.argeo.cms.util.StreamUtils;
27
28 import com.sun.net.httpserver.HttpExchange;
29 import com.sun.net.httpserver.HttpHandler;
30 import com.sun.net.httpserver.HttpServer;
31
32 /** A simple {@link HttpHandler} which just serves or proxy resources. */
33 public class StaticHttpHandler implements HttpHandler {
34 private final static Logger logger = System.getLogger(StaticHttpHandler.class.getName());
35
36 private static FileNameMap fileNameMap = URLConnection.getFileNameMap();
37
38 private NavigableMap<String, Object> binds = new TreeMap<>();
39
40 @Override
41 public void handle(HttpExchange exchange) throws IOException {
42 try {
43 String path = HttpServerUtils.subPath(exchange);
44 Map.Entry<String, Object> bindEntry = findBind(path);
45 boolean isRoot = "/".equals(bindEntry.getKey());
46
47 String relPath = isRoot ? path.substring(bindEntry.getKey().length())
48 : path.substring(bindEntry.getKey().length() + 1);
49 process(bindEntry.getValue(), exchange, relPath);
50 } catch (Exception e) {
51 logger.log(Level.ERROR, exchange.getRequestURI().toString(), e);
52 }
53 }
54
55 public void addBind(String path, Object bind) {
56 if (binds.containsKey(path))
57 throw new IllegalStateException("Path '" + path + "' is already bound");
58 Object bindToUse = checkBindSupport(bind);
59 binds.put(path, bindToUse);
60 }
61
62 protected Map.Entry<String, Object> findBind(String path) {
63 Map.Entry<String, Object> entry = binds.floorEntry(path);
64 if (entry == null)
65 return null;
66 String mountPath = entry.getKey();
67 if (!path.startsWith(mountPath)) {
68 // FIXME make it more robust and find when there is no content provider
69 String[] parent = CmsContent.getParentPath(path);
70 return findBind(parent[0]);
71 }
72 return entry;
73 }
74
75 protected void process(Object bind, HttpExchange httpExchange, String relativePath) throws IOException {
76 OutputStream out = null;
77
78 try {
79 String contentType = fileNameMap.getContentTypeFor(relativePath);
80 if (contentType != null)
81 httpExchange.getResponseHeaders().set(HttpHeader.CONTENT_TYPE.getHeaderName(), contentType);
82
83 if (bind instanceof Path bindPath) {
84 Path path = bindPath.resolve(relativePath);
85 if (!Files.exists(path)) {
86 httpExchange.sendResponseHeaders(HttpStatus.NOT_FOUND.getCode(), -1);
87 return;
88 }
89 long size = Files.size(path);
90 httpExchange.sendResponseHeaders(HttpStatus.OK.getCode(), size);
91 out = httpExchange.getResponseBody();
92 Files.copy(path, out);
93 } else if (bind instanceof URL bindUrl) {
94 URL url = new URL(bindUrl.toString() + relativePath);
95 URLConnection urlConnection;
96 try {
97 urlConnection = url.openConnection();
98 urlConnection.connect();
99 } catch (IOException e) {
100 httpExchange.sendResponseHeaders(HttpStatus.NOT_FOUND.getCode(), -1);
101 return;
102 }
103 // TODO check other headers?
104 // TODO use Proxy?
105 String contentLengthStr = urlConnection.getHeaderField(HttpHeader.CONTENT_LENGTH.getHeaderName());
106 httpExchange.sendResponseHeaders(HttpStatus.OK.getCode(),
107 contentLengthStr != null ? Long.parseLong(contentLengthStr) : 0);
108 try (InputStream in = urlConnection.getInputStream()) {
109 out = httpExchange.getResponseBody();
110 StreamUtils.copy(in, out);
111 } finally {
112 }
113 }
114 // make sure everything is flushed
115 httpExchange.getResponseBody().flush();
116 } catch (RuntimeException e) {
117 try {
118 httpExchange.sendResponseHeaders(HttpStatus.INTERNAL_SERVER_ERROR.getCode(), -1);
119 } catch (IOException e1) {
120 // silent
121 }
122 throw e;
123 } finally {
124 if (out != null) {
125 try {
126 out.close();
127 } catch (IOException e) {
128 throw e;
129 }
130 }
131 }
132 }
133
134 /**
135 * Checks whether this bind type is supported. This can be overridden in order
136 * to ass new bind type.
137 *
138 * @see #process(Object, HttpExchange, String) for overriding the actual
139 * implementation.
140 *
141 * @param bind the bind to check
142 * @return the bind object to actually use (an URI will have been converted to
143 * URL)
144 * @throws UnsupportedOperationException if this bind type is not supported
145 */
146 protected Object checkBindSupport(Object bind) throws UnsupportedOperationException {
147 if (bind instanceof Path)
148 return bind;
149 if (bind instanceof URL)
150 return bind;
151 if (bind instanceof URI uri) {
152 try {
153 return uri.toURL();
154 } catch (MalformedURLException e) {
155 throw new UnsupportedOperationException("URI " + uri + " cannot be connverted to URL.", e);
156 }
157 }
158 // TODO string as a path within the server?
159 throw new UnsupportedOperationException("Bind " + bind + " type " + bind.getClass() + " is not supported.");
160 }
161
162 public static void main(String... args) {
163 try {
164 HttpServer httpServer = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 6060), 0);
165
166 StaticHttpHandler staticHttpHandler = new StaticHttpHandler();
167 staticHttpHandler.addBind("/", Paths.get("/home/mbaudier/dev/workspaces/test-node-js/test-static"));
168 staticHttpHandler.addBind("/js",
169 Paths.get("/home/mbaudier/dev/workspaces/test-node-js/test-static/node_modules"));
170
171 httpServer.createContext("/", staticHttpHandler);
172 httpServer.start();
173 } catch (IOException e) {
174 throw new UncheckedIOException(e);
175 }
176 }
177 }