-
Notifications
You must be signed in to change notification settings - Fork 30
Expand file tree
/
Copy pathExpress.java
More file actions
401 lines (337 loc) · 12.4 KB
/
Express.java
File metadata and controls
401 lines (337 loc) · 12.4 KB
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
package express;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsServer;
import express.filter.FilterImpl;
import express.filter.FilterLayerHandler;
import express.filter.FilterTask;
import express.filter.FilterWorker;
import express.http.HttpRequestHandler;
import express.http.request.Request;
import express.http.response.Response;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* @author Simon Reinisch
* Core class of java-express
*/
public class Express implements Router {
private final ConcurrentHashMap<String, HttpRequestHandler> parameterListener;
private final ConcurrentHashMap<Object, Object> locals;
private final ArrayList<FilterWorker> worker;
private final FilterLayerHandler handler;
private Executor executor;
private String hostname;
private HttpServer httpServer;
private HttpsConfigurator httpsConfigurator;
{
// Initialize
parameterListener = new ConcurrentHashMap<>();
locals = new ConcurrentHashMap<>();
worker = new ArrayList<>();
handler = new FilterLayerHandler(2);
executor = Executors.newCachedThreadPool();
}
/**
* Create an express instance and bind the server to an hostname.
* Default is "Localhost"
*
* @param hostname The host name
*/
public Express(String hostname) {
this.hostname = hostname;
}
/**
* Default, will bind the server to "localhost"
*
* @param httpsConfigurator The HttpsConfigurator for https
*/
public Express(HttpsConfigurator httpsConfigurator) {
this.httpsConfigurator = httpsConfigurator;
}
/**
* Create an express instance and bind the server to an hostname.
* Default is "Localhost"
*
* @param hostname The host name
* @param httpsConfigurator The HttpsConfigurator for https
*/
public Express(String hostname, HttpsConfigurator httpsConfigurator) {
this.hostname = hostname;
this.httpsConfigurator = httpsConfigurator;
}
/**
* Default, will bind the server to "localhost"
*/
public Express() {
}
/**
* @return True if the server uses https.
*/
public boolean isSecure() {
return httpsConfigurator != null;
}
/**
* Add a listener which will be called when an url with this parameter is called.
*
* @param param The parameter name.
* @param request An request handler.
* @return Express this express instance
*/
public Express onParam(String param, HttpRequestHandler request) {
parameterListener.put(param, request);
return this;
}
public ConcurrentHashMap<String, HttpRequestHandler> getParameterListener() {
return parameterListener;
}
/**
* Add an key-val pair to the express app, can be used
* to store data. Uses ConcurrentHashMap so it's thread save.
*
* @param key The key
* @param val The value
* @return The last value which was attached by this key, can be null.
*/
public Object set(String key, String val) {
return locals.put(key, val);
}
/**
* Returns the value which was allocated by this key.
*
* @param key The key.
* @return The value.
*/
public Object get(String key) {
return locals.get(key);
}
/**
* Set an executor service. Default is CachedThreadPool
* Can only changed if the server isn't already stardet.
*
* @param executor The new executor.
* @throws IOException If the server is currently running
*/
public void setExecutor(Executor executor) throws IOException {
if (httpServer != null) {
throw new IOException("Cannot set executor after the server has stardet!");
} else {
this.executor = executor;
}
}
/**
* Add an routing object.
*
* @param router The router.
* @return Express this express instance
*/
public Express use(ExpressRouter router) {
this.handler.combine(router.getHandler());
this.worker.addAll(router.getWorker());
return this;
}
/**
* Add an routing object with an specific root root.
*
* @param root The root path for all request to this router.
* @param router The router.
* @return Express this express instance
*/
@SuppressWarnings("unchecked")
public Express use(String root, ExpressRouter router) {
router.getHandler().forEach(fl -> fl.getFilter().forEach(layer -> {
((FilterImpl) layer).setRoot(root);
}));
this.handler.combine(router.getHandler());
this.worker.addAll(router.getWorker());
return this;
}
public Express use(HttpRequestHandler middleware) {
addMiddleware("*", "*", middleware);
return this;
}
public Express use(String context, HttpRequestHandler middleware) {
addMiddleware("*", context, middleware);
return this;
}
public Express use(String context, String requestMethod, HttpRequestHandler middleware) {
addMiddleware(requestMethod.toUpperCase(), context, middleware);
return this;
}
// Internal service to handle middleware
private void addMiddleware(String requestMethod, String context, HttpRequestHandler middleware) {
if (middleware instanceof FilterTask) {
worker.add(new FilterWorker((FilterTask) middleware));
}
handler.add(0, new FilterImpl(requestMethod, context, middleware));
}
public Express all(HttpRequestHandler request) {
handler.add(1, new FilterImpl("*", "*", request));
return this;
}
public Express all(String context, HttpRequestHandler request) {
handler.add(1, new FilterImpl("*", context, request));
return this;
}
public Express all(String context, String requestMethod, HttpRequestHandler request) {
handler.add(1, new FilterImpl(requestMethod, context, request));
return this;
}
public Express get(String context, HttpRequestHandler request) {
handler.add(1, new FilterImpl("GET", context, request));
return this;
}
public Express post(String context, HttpRequestHandler request) {
handler.add(1, new FilterImpl("POST", context, request));
return this;
}
public Express put(String context, HttpRequestHandler request) {
handler.add(1, new FilterImpl("PUT", context, request));
return this;
}
public Express delete(String context, HttpRequestHandler request) {
handler.add(1, new FilterImpl("DELETE", context, request));
return this;
}
public Express patch(String context, HttpRequestHandler request) {
handler.add(1, new FilterImpl("PATCH", context, request));
return this;
}
/**
* Binds a or multiple objects with request-handler methods on this express instance.
* With the use of the DynExpress Annotation handler can be declared with a annotation
* on a compatible method which has as parameter a Request and Response object.
*
* @param objects Object with proper request handler methods.
* @return This express instance.
*/
public Express bind(Object... objects) {
for (Object o : objects) {
// Skip null objects
if (o == null) {
continue;
}
// Find methods wich has the DynExpress annotation
Method[] methods = o.getClass().getDeclaredMethods();
for (Method method : methods) {
// Skip if annotation is not present
if (!method.isAnnotationPresent(DynExpress.class)) {
continue;
}
// Make private method accessible
if (!method.isAccessible()) {
method.setAccessible(true);
}
// Validate parameter types
Class<?>[] params = method.getParameterTypes();
if (params.length < 1 || params[0] != Request.class || params[1] != Response.class) {
StringBuilder sb = new StringBuilder();
for (Class<?> c : params) {
sb.append(c.getSimpleName());
sb.append(", ");
}
String paramString = sb.toString();
if (paramString.length() > 2) {
paramString = paramString.substring(0, paramString.length() - 2);
}
System.err.println("Skipped method with invalid parameter types found in " +
method.getName() + "(" + paramString + ") in " + o.getClass().getName() +
". Expected Request and Response.");
continue;
}
DynExpress[] annotations = method.getAnnotationsByType(DynExpress.class);
for (DynExpress dex : annotations) {
String context = dex.context();
String requestMethod = dex.method().getMethod();
// Bind to instance
handler.add(1, new FilterImpl(requestMethod, context, (req, res) -> {
try {
method.invoke(o, req, res);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}));
}
}
}
return this;
}
/**
* Start the HTTP-Server on port 80.
* This method is asynchronous so be sure to add an listener or keep it in mind!
*/
public void listen() {
listen(null, 80);
}
/**
* Start the HTTP-Server on a specific port
* This method is asynchronous so be sure to add an listener or keep it in mind!
*
* @param port The port.
*/
public void listen(int port) {
listen(null, port);
}
/**
* Start the HTTP-Server on port 80.
* This method is asynchronous so be sure to add an listener or keep it in mind!
*
* @param onStart An listener which will be fired after the server is stardet.
*/
public void listen(ExpressListener onStart) {
listen(onStart, 80);
}
/**
* Start the HTTP-Server on a specific port.
* This method is asynchronous so be sure to add an listener or keep it in mind.
*
* @param onStart An listener which will be fired after the server is stardet.
* @param port The port.
*/
public void listen(ExpressListener onStart, int port) {
new Thread(() -> {
try {
// Fire worker threads
worker.forEach(FilterWorker::start);
InetSocketAddress socketAddress = this.hostname == null ? new InetSocketAddress(port) : new InetSocketAddress(this.hostname, port);
if (httpsConfigurator != null) {
// Create https server
httpServer = HttpsServer.create(socketAddress, 0);
((HttpsServer) httpServer).setHttpsConfigurator(httpsConfigurator);
} else {
// Create http server
httpServer = HttpServer.create(socketAddress, 0);
}
// Set thread executor
httpServer.setExecutor(executor);
// Create handler for all contexts
httpServer.createContext("/", exchange -> handler.handle(exchange, this));
// Start server
httpServer.start();
// Fire listener
if (onStart != null) {
onStart.action();
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
/**
* Stop express
*/
public void stop() {
if (httpServer != null) {
// Stop http-server
httpServer.stop(0);
// Stop worker threads
worker.forEach(FilterWorker::stop);
}
}
}