1 package org
.argeo
.cms
.http
.server
;
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
;
15 import java
.net
.URLConnection
;
16 import java
.nio
.file
.Files
;
17 import java
.nio
.file
.Path
;
18 import java
.nio
.file
.Paths
;
20 import java
.util
.NavigableMap
;
21 import java
.util
.TreeMap
;
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
;
28 import com
.sun
.net
.httpserver
.HttpExchange
;
29 import com
.sun
.net
.httpserver
.HttpHandler
;
30 import com
.sun
.net
.httpserver
.HttpServer
;
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());
36 private static FileNameMap fileNameMap
= URLConnection
.getFileNameMap();
38 private NavigableMap
<String
, Object
> binds
= new TreeMap
<>();
41 public void handle(HttpExchange exchange
) throws IOException
{
43 String path
= HttpServerUtils
.subPath(exchange
);
44 Map
.Entry
<String
, Object
> bindEntry
= findBind(path
);
45 boolean isRoot
= "/".equals(bindEntry
.getKey());
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
);
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
);
62 protected Map
.Entry
<String
, Object
> findBind(String path
) {
63 Map
.Entry
<String
, Object
> entry
= binds
.floorEntry(path
);
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]);
75 protected void process(Object bind
, HttpExchange httpExchange
, String relativePath
) throws IOException
{
76 OutputStream out
= null;
79 String contentType
= fileNameMap
.getContentTypeFor(relativePath
);
80 if (contentType
!= null)
81 httpExchange
.getResponseHeaders().set(HttpHeader
.CONTENT_TYPE
.getHeaderName(), contentType
);
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);
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
;
97 urlConnection
= url
.openConnection();
98 urlConnection
.connect();
99 } catch (IOException e
) {
100 httpExchange
.sendResponseHeaders(HttpStatus
.NOT_FOUND
.getCode(), -1);
103 // TODO check other headers?
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
);
114 // make sure everything is flushed
115 httpExchange
.getResponseBody().flush();
116 } catch (RuntimeException e
) {
118 httpExchange
.sendResponseHeaders(HttpStatus
.INTERNAL_SERVER_ERROR
.getCode(), -1);
119 } catch (IOException e1
) {
127 } catch (IOException e
) {
135 * Checks whether this bind type is supported. This can be overridden in order
136 * to ass new bind type.
138 * @see #process(Object, HttpExchange, String) for overriding the actual
141 * @param bind the bind to check
142 * @return the bind object to actually use (an URI will have been converted to
144 * @throws UnsupportedOperationException if this bind type is not supported
146 protected Object
checkBindSupport(Object bind
) throws UnsupportedOperationException
{
147 if (bind
instanceof Path
)
149 if (bind
instanceof URL
)
151 if (bind
instanceof URI uri
) {
154 } catch (MalformedURLException e
) {
155 throw new UnsupportedOperationException("URI " + uri
+ " cannot be connverted to URL.", e
);
158 // TODO string as a path within the server?
159 throw new UnsupportedOperationException("Bind " + bind
+ " type " + bind
.getClass() + " is not supported.");
162 public static void main(String
... args
) {
164 HttpServer httpServer
= HttpServer
.create(new InetSocketAddress(InetAddress
.getLoopbackAddress(), 6060), 0);
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"));
171 httpServer
.createContext("/", staticHttpHandler
);
173 } catch (IOException e
) {
174 throw new UncheckedIOException(e
);