1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.waarp.gateway.kernel.rest;
21
22 import com.fasterxml.jackson.databind.JsonNode;
23 import com.fasterxml.jackson.databind.node.ObjectNode;
24 import io.netty.buffer.ByteBuf;
25 import io.netty.buffer.Unpooled;
26 import io.netty.channel.ChannelFuture;
27 import io.netty.channel.ChannelFutureListener;
28 import io.netty.channel.ChannelHandlerContext;
29 import io.netty.channel.SimpleChannelInboundHandler;
30 import io.netty.channel.group.ChannelGroup;
31 import io.netty.handler.codec.http.DefaultFullHttpResponse;
32 import io.netty.handler.codec.http.FullHttpRequest;
33 import io.netty.handler.codec.http.FullHttpResponse;
34 import io.netty.handler.codec.http.HttpContent;
35 import io.netty.handler.codec.http.HttpHeaderNames;
36 import io.netty.handler.codec.http.HttpHeaderValues;
37 import io.netty.handler.codec.http.HttpMethod;
38 import io.netty.handler.codec.http.HttpObject;
39 import io.netty.handler.codec.http.HttpRequest;
40 import io.netty.handler.codec.http.HttpResponseStatus;
41 import io.netty.handler.codec.http.HttpUtil;
42 import io.netty.handler.codec.http.HttpVersion;
43 import io.netty.handler.codec.http.LastHttpContent;
44 import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
45 import io.netty.handler.codec.http.multipart.Attribute;
46 import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
47 import io.netty.handler.codec.http.multipart.DiskAttribute;
48 import io.netty.handler.codec.http.multipart.DiskFileUpload;
49 import io.netty.handler.codec.http.multipart.FileUpload;
50 import io.netty.handler.codec.http.multipart.HttpDataFactory;
51 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
52 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
53 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
54 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException;
55 import io.netty.handler.codec.http.multipart.InterfaceHttpData;
56 import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
57 import org.waarp.common.crypto.ssl.WaarpSslUtility;
58 import org.waarp.common.database.DbSession;
59 import org.waarp.common.exception.CryptoException;
60 import org.waarp.common.json.JsonHandler;
61 import org.waarp.common.logging.WaarpLogger;
62 import org.waarp.common.logging.WaarpLoggerFactory;
63 import org.waarp.common.utility.WaarpNettyUtil;
64 import org.waarp.common.utility.WaarpStringUtils;
65 import org.waarp.gateway.kernel.database.DbConstantGateway;
66 import org.waarp.gateway.kernel.exception.HttpForbiddenRequestException;
67 import org.waarp.gateway.kernel.exception.HttpIncorrectRequestException;
68 import org.waarp.gateway.kernel.exception.HttpInvalidAuthenticationException;
69 import org.waarp.gateway.kernel.exception.HttpMethodNotAllowedRequestException;
70 import org.waarp.gateway.kernel.exception.HttpNotFoundRequestException;
71
72 import java.io.File;
73 import java.io.IOException;
74 import java.nio.channels.ClosedChannelException;
75 import java.util.HashMap;
76 import java.util.Iterator;
77 import java.util.List;
78 import java.util.Map.Entry;
79
80
81
82
83 public abstract class HttpRestHandler
84 extends SimpleChannelInboundHandler<HttpObject> {
85
86
87
88 private static final WaarpLogger logger =
89 WaarpLoggerFactory.getLogger(HttpRestHandler.class);
90
91
92
93
94
95
96
97 public enum METHOD {
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112 GET(HttpMethod.GET),
113
114
115
116
117
118
119 PUT(HttpMethod.PUT),
120
121
122
123
124
125
126
127
128 POST(HttpMethod.POST),
129
130
131
132
133
134
135 DELETE(HttpMethod.DELETE),
136
137
138
139
140
141
142
143
144
145
146
147
148 OPTIONS(HttpMethod.OPTIONS),
149
150
151
152
153
154
155 HEAD(HttpMethod.HEAD),
156
157
158
159
160
161
162
163
164 PATCH(HttpMethod.PATCH),
165
166
167
168
169
170
171
172 TRACE(HttpMethod.TRACE),
173
174
175
176
177
178
179
180
181 CONNECT(HttpMethod.CONNECT);
182
183 public final HttpMethod method;
184
185 METHOD(final HttpMethod method) {
186 this.method = method;
187 }
188 }
189
190 public static final HttpDataFactory factory =
191 new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);
192
193
194 public static String TempPath = "J:/GG/ARK/TMP";
195
196 public static ChannelGroup group;
197
198
199
200
201
202
203
204
205
206 public static void initialize(final String tempPath) {
207 TempPath = tempPath;
208 final File file = new File(tempPath);
209 file.mkdirs();
210 DiskFileUpload.deleteOnExitTemporaryFile = true;
211
212
213 DiskFileUpload.baseDirectory = TempPath;
214
215 DiskAttribute.deleteOnExitTemporaryFile = true;
216
217 DiskAttribute.baseDirectory = TempPath;
218 }
219
220 public static final RestConfiguration defaultConfiguration =
221 new RestConfiguration();
222
223 public HashMap<String, RestMethodHandler> restHashMap;
224 public final RestConfiguration restConfiguration;
225 protected final RootOptionsRestMethodHandler rootHandler;
226
227 protected HttpPostRequestDecoder decoder;
228 protected HttpResponseStatus status = HttpResponseStatus.OK;
229
230 protected HttpRequest request;
231 protected RestMethodHandler handler;
232
233 protected DbSession dbSession;
234
235 private boolean willClose;
236
237
238
239
240 protected RestArgument arguments;
241
242
243
244 protected RestArgument response;
245
246
247
248 protected Object jsonObject;
249
250
251
252 protected ByteBuf cumulativeBody;
253
254 protected HttpRestHandler(final RestConfiguration config) {
255 restConfiguration = config;
256 rootHandler = new RootOptionsRestMethodHandler(config);
257 }
258
259 protected static class HttpCleanChannelFutureListener
260 implements ChannelFutureListener {
261 protected final HttpRestHandler handler;
262
263
264
265
266 public HttpCleanChannelFutureListener(final HttpRestHandler handler) {
267 this.handler = handler;
268 }
269
270 @Override
271 public final void operationComplete(final ChannelFuture future) {
272 handler.clean();
273 }
274 }
275
276 @Override
277 public final void channelActive(final ChannelHandlerContext ctx)
278 throws Exception {
279 if (group != null) {
280 group.add(ctx.channel());
281 }
282 super.channelActive(ctx);
283 }
284
285
286
287
288
289
290 protected final void clean() {
291 if (arguments != null) {
292 arguments.clean();
293 arguments = null;
294 }
295 if (response != null) {
296 response.clean();
297 response = null;
298 }
299 if (decoder != null) {
300 decoder.cleanFiles();
301 decoder = null;
302 }
303 handler = null;
304 cumulativeBody = null;
305 jsonObject = null;
306 if (dbSession != null) {
307 dbSession.enUseConnectionNoDisconnect();
308 dbSession = null;
309 }
310 }
311
312
313
314
315
316
317 protected final void initialize() {
318
319 clean();
320 status = HttpResponseStatus.OK;
321 request = null;
322 setWillClose(false);
323 arguments = new RestArgument(JsonHandler.createObjectNode());
324 response = new RestArgument(JsonHandler.createObjectNode());
325 }
326
327
328
329
330
331 public final DbSession getDbSession() {
332 return dbSession == null? DbConstantGateway.admin.getSession() : dbSession;
333 }
334
335
336
337
338
339
340
341
342
343
344
345
346 protected abstract void checkConnection(ChannelHandlerContext ctx)
347 throws HttpInvalidAuthenticationException;
348
349
350
351
352
353
354 protected final void setCookies(final FullHttpResponse httpResponse) {
355 if (response == null) {
356 return;
357 }
358 final ObjectNode cookieON = response.getCookieArgs();
359 if (!cookieON.isMissingNode()) {
360 final Iterator<Entry<String, JsonNode>> iter = cookieON.fields();
361 while (iter.hasNext()) {
362 final Entry<String, JsonNode> entry = iter.next();
363 httpResponse.headers().add(HttpHeaderNames.SET_COOKIE,
364 ServerCookieEncoder.LAX.encode(
365 entry.getKey(),
366 entry.getValue().asText()));
367 }
368 }
369 }
370
371
372
373
374
375
376
377
378
379
380 protected final RestMethodHandler getHandler()
381 throws HttpMethodNotAllowedRequestException,
382 HttpForbiddenRequestException {
383 final METHOD method = arguments.getMethod();
384 final String uri = arguments.getBaseUri();
385 boolean restFound = false;
386 RestMethodHandler handlerNew = restHashMap.get(uri);
387 if (handlerNew != null) {
388 handlerNew.checkHandlerSessionCorrectness(this, arguments, response);
389 if (handlerNew.isMethodIncluded(method)) {
390 restFound = true;
391 }
392 }
393 if (handlerNew == null && method == METHOD.OPTIONS) {
394 handlerNew = rootHandler;
395
396 restFound = true;
397 }
398 logger.debug("{} {} {}", method, uri, restFound);
399 if (!restFound) {
400 throw new HttpMethodNotAllowedRequestException(
401 "No Method found for that URI: " + uri);
402 }
403 return handlerNew;
404 }
405
406 @Override
407 protected void channelRead0(final ChannelHandlerContext ctx,
408 final HttpObject msg) {
409 logger.debug("Msg Received");
410 try {
411 if (msg instanceof HttpRequest) {
412 initialize();
413 request = (HttpRequest) msg;
414 arguments.setRequest(request);
415 final Iterator<Entry<CharSequence, CharSequence>> iterator =
416 request.headers().iteratorCharSequence();
417 arguments.setHeaderArgs(iterator);
418 arguments.setCookieArgs(request.headers().get(HttpHeaderNames.COOKIE));
419 logger.debug("DEBUG: {}", arguments);
420 checkConnection(ctx);
421 handler = getHandler();
422 if (arguments.getMethod() == METHOD.OPTIONS) {
423 response.setFromArgument(arguments);
424 handler.optionsCommand(this, arguments, response);
425 finalizeSend(ctx);
426 return;
427 }
428 if (request instanceof FullHttpRequest) {
429 if (handler.isBodyJsonDecoded()) {
430 final ByteBuf buffer = ((FullHttpRequest) request).content();
431 jsonObject = getBodyJsonArgs(buffer);
432 } else {
433
434 createDecoder();
435
436 readAllHttpData();
437 }
438 response.setFromArgument(arguments);
439 handler.endParsingRequest(this, arguments, response, jsonObject);
440 finalizeSend(ctx);
441 return;
442 }
443
444 if (!handler.isBodyJsonDecoded()) {
445 createDecoder();
446 }
447 } else {
448
449 if (handler != null) {
450 bodyChunk(ctx, (HttpContent) msg);
451 }
452 }
453 } catch (final HttpIncorrectRequestException e1) {
454
455 if (handler != null) {
456 status =
457 handler.handleException(this, arguments, response, jsonObject, e1);
458 }
459 if (status == HttpResponseStatus.OK) {
460 status = HttpResponseStatus.BAD_REQUEST;
461 }
462 logger.warn("Error: {}", e1.getMessage(), e1);
463 if (response.getDetail().isEmpty()) {
464 response.setDetail(e1.getMessage());
465 }
466 if (handler != null) {
467 finalizeSend(ctx);
468 } else {
469 forceClosing(ctx);
470 }
471 } catch (final HttpMethodNotAllowedRequestException e1) {
472 if (handler != null) {
473 status =
474 handler.handleException(this, arguments, response, jsonObject, e1);
475 }
476 if (status == HttpResponseStatus.OK) {
477 status = HttpResponseStatus.METHOD_NOT_ALLOWED;
478 }
479 logger.warn("Error: {}", e1.getMessage());
480 if (response.getDetail().isEmpty()) {
481 response.setDetail(e1.getMessage());
482 }
483 if (handler != null) {
484 finalizeSend(ctx);
485 } else {
486 forceClosing(ctx);
487 }
488 } catch (final HttpForbiddenRequestException e1) {
489 if (handler != null) {
490 status =
491 handler.handleException(this, arguments, response, jsonObject, e1);
492 }
493 if (status == HttpResponseStatus.OK) {
494 status = HttpResponseStatus.FORBIDDEN;
495 }
496 logger.warn("Error: {}", e1.getMessage());
497 if (response.getDetail().isEmpty()) {
498 response.setDetail(e1.getMessage());
499 }
500 if (handler != null) {
501 finalizeSend(ctx);
502 } else {
503 forceClosing(ctx);
504 }
505 } catch (final HttpInvalidAuthenticationException e1) {
506 if (handler != null) {
507 status =
508 handler.handleException(this, arguments, response, jsonObject, e1);
509 }
510 if (status == HttpResponseStatus.OK) {
511 status = HttpResponseStatus.UNAUTHORIZED;
512 }
513 logger.warn("Error: {}", e1.getMessage());
514 if (response.getDetail().isEmpty()) {
515 response.setDetail(e1.getMessage());
516 }
517 if (handler != null) {
518 finalizeSend(ctx);
519 } else {
520 forceClosing(ctx);
521 }
522 } catch (final HttpNotFoundRequestException e1) {
523 if (handler != null) {
524 status =
525 handler.handleException(this, arguments, response, jsonObject, e1);
526 }
527 if (status == HttpResponseStatus.OK) {
528 status = HttpResponseStatus.NOT_FOUND;
529 }
530 logger.warn("Error: {}", e1.getMessage());
531 if (response.getDetail().isEmpty()) {
532 response.setDetail(e1.getMessage());
533 }
534 if (handler != null) {
535 finalizeSend(ctx);
536 } else {
537 forceClosing(ctx);
538 }
539 }
540 }
541
542
543
544
545
546
547 protected final void createDecoder() throws HttpIncorrectRequestException {
548 final HttpMethod method = request.method();
549 if (!method.equals(HttpMethod.HEAD)) {
550
551 request.setMethod(HttpMethod.POST);
552 }
553 try {
554 decoder = new HttpPostRequestDecoder(factory, request);
555 } catch (final ErrorDataDecoderException e1) {
556 status = HttpResponseStatus.NOT_ACCEPTABLE;
557 throw new HttpIncorrectRequestException(e1);
558 } catch (final Exception e1) {
559
560
561 status = HttpResponseStatus.NOT_ACCEPTABLE;
562 throw new HttpIncorrectRequestException(e1);
563 }
564 }
565
566
567
568
569
570
571 protected final void readAllHttpData() throws HttpIncorrectRequestException {
572 final List<InterfaceHttpData> datas;
573 try {
574 datas = decoder.getBodyHttpDatas();
575 } catch (final NotEnoughDataDecoderException e1) {
576
577 logger.warn("decoder issue: {}", e1.getMessage());
578 status = HttpResponseStatus.NOT_ACCEPTABLE;
579 throw new HttpIncorrectRequestException(e1);
580 }
581 for (final InterfaceHttpData data : datas) {
582 readHttpData(data);
583 }
584 }
585
586
587
588
589
590
591
592
593 protected final void readHttpData(final InterfaceHttpData data)
594 throws HttpIncorrectRequestException {
595 if (data.getHttpDataType() == HttpDataType.Attribute) {
596 final ObjectNode body = arguments.getBody();
597 try {
598 body.put(data.getName(), ((Attribute) data).getValue());
599 } catch (final IOException e) {
600 throw new HttpIncorrectRequestException("Bad reading", e);
601 }
602 } else if (data.getHttpDataType() == HttpDataType.FileUpload) {
603 final FileUpload fileUpload = (FileUpload) data;
604 if (fileUpload.isCompleted()) {
605 handler.getFileUpload(this, fileUpload, arguments, response);
606 } else {
607 logger.warn("File still pending but should not");
608 fileUpload.delete();
609 status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
610 throw new HttpIncorrectRequestException(
611 "File still pending but should not");
612 }
613 } else {
614 logger.warn("Unknown element: " + data);
615 }
616 }
617
618
619
620
621
622
623 protected final void forceClosing(final ChannelHandlerContext ctx) {
624 if (status == HttpResponseStatus.OK) {
625 status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
626 }
627 if (ctx.channel().isActive()) {
628 setWillClose(true);
629 final String answer =
630 "<html><body>Error " + status.reasonPhrase() + "</body></html>";
631 final FullHttpResponse httpResponse = getResponse(
632 Unpooled.wrappedBuffer(answer.getBytes(WaarpStringUtils.UTF8)));
633 httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html");
634 httpResponse.headers().set(HttpHeaderNames.REFERER, request.uri());
635 final ChannelFuture future = ctx.writeAndFlush(httpResponse);
636 logger.debug("Will close");
637 future.addListener(WaarpSslUtility.SSLCLOSE);
638 }
639 clean();
640 }
641
642
643
644
645
646
647
648 public final FullHttpResponse getResponse(final ByteBuf content) {
649
650 if (request == null) {
651 final FullHttpResponse httpResponse;
652 if (content == null) {
653 httpResponse =
654 new DefaultFullHttpResponse(HttpVersion.HTTP_1_0, status);
655 } else {
656 httpResponse =
657 new DefaultFullHttpResponse(HttpVersion.HTTP_1_0, status, content);
658 httpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH,
659 content.array().length);
660 }
661 setCookies(httpResponse);
662 setWillClose(true);
663 return httpResponse;
664 }
665 boolean keepAlive = HttpUtil.isKeepAlive(request);
666 setWillClose(isWillClose() || status != HttpResponseStatus.OK ||
667 HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(
668 request.headers().get(HttpHeaderNames.CONNECTION)) ||
669 request.protocolVersion().equals(HttpVersion.HTTP_1_0) &&
670 !keepAlive);
671 if (isWillClose()) {
672 keepAlive = false;
673 }
674
675 final FullHttpResponse httpResponse;
676 if (content != null) {
677 httpResponse =
678 new DefaultFullHttpResponse(request.protocolVersion(), status,
679 content);
680 httpResponse.headers()
681 .set(HttpHeaderNames.CONTENT_LENGTH, content.array().length);
682 } else {
683 httpResponse =
684 new DefaultFullHttpResponse(request.protocolVersion(), status);
685 }
686 if (keepAlive) {
687 httpResponse.headers()
688 .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
689 }
690 setCookies(httpResponse);
691 return httpResponse;
692 }
693
694
695
696
697
698
699
700
701
702
703
704 protected final void bodyChunk(final ChannelHandlerContext ctx,
705 final HttpContent chunk)
706 throws HttpIncorrectRequestException, HttpInvalidAuthenticationException,
707 HttpNotFoundRequestException {
708
709 if (handler.isBodyJsonDecoded()) {
710 final ByteBuf buffer = chunk.content();
711 if (cumulativeBody != null) {
712 if (buffer.isReadable()) {
713 cumulativeBody = Unpooled.wrappedBuffer(cumulativeBody, buffer);
714 }
715 } else {
716 cumulativeBody = buffer.slice();
717 }
718 } else {
719 try {
720 decoder.offer(chunk);
721 } catch (final ErrorDataDecoderException e1) {
722 status = HttpResponseStatus.NOT_ACCEPTABLE;
723 throw new HttpIncorrectRequestException(e1);
724 }
725
726
727 readHttpDataChunkByChunk();
728 }
729
730 if (chunk instanceof LastHttpContent) {
731 if (handler.isBodyJsonDecoded()) {
732 jsonObject = getBodyJsonArgs(cumulativeBody);
733 WaarpNettyUtil.release(cumulativeBody);
734 cumulativeBody = null;
735 }
736 response.setFromArgument(arguments);
737 handler.endParsingRequest(this, arguments, response, jsonObject);
738 finalizeSend(ctx);
739 }
740 }
741
742 protected final void finalizeSend(final ChannelHandlerContext ctx) {
743 final ChannelFuture future;
744 if (arguments.getMethod() == METHOD.OPTIONS) {
745 future = handler.sendOptionsResponse(this, ctx, response, status);
746 } else {
747 future = handler.sendResponse(this, ctx, arguments, response, jsonObject,
748 status);
749 }
750 if (future != null) {
751 future.addListener(WaarpSslUtility.SSLCLOSE);
752 }
753 clean();
754 logger.debug("Cleaned");
755 }
756
757
758
759
760
761
762
763
764 protected final Object getBodyJsonArgs(final ByteBuf data)
765 throws HttpIncorrectRequestException {
766 if (data == null || data.readableBytes() == 0) {
767 return null;
768 }
769 return handler.getBody(this, data, arguments, response);
770 }
771
772
773
774
775
776
777 protected final void readHttpDataChunkByChunk()
778 throws HttpIncorrectRequestException {
779 try {
780 while (decoder.hasNext()) {
781 final InterfaceHttpData data = decoder.next();
782 if (data != null) {
783
784 readHttpData(data);
785 }
786 }
787 } catch (final EndOfDataDecoderException e1) {
788
789 }
790 }
791
792 @Override
793 public final void exceptionCaught(final ChannelHandlerContext ctx,
794 final Throwable cause) {
795 if (ctx.channel().isActive()) {
796 if (cause != null && cause.getMessage() != null) {
797 logger.warn("Exception {}", cause.getMessage());
798 } else {
799 logger.warn("Exception Received", cause);
800 }
801 if (cause instanceof ClosedChannelException ||
802 cause instanceof IOException) {
803 return;
804 }
805 if (handler != null) {
806 status = handler.handleException(this, arguments, response, jsonObject,
807 (Exception) cause);
808 }
809 if (status == HttpResponseStatus.OK) {
810 status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
811 }
812 if (handler != null) {
813 finalizeSend(ctx);
814 } else {
815 forceClosing(ctx);
816 }
817 }
818 }
819
820 @Override
821 public void channelInactive(final ChannelHandlerContext ctx)
822 throws Exception {
823 super.channelInactive(ctx);
824 clean();
825 }
826
827
828
829
830 public final HttpResponseStatus getStatus() {
831 return status;
832 }
833
834
835
836
837 public final void setStatus(final HttpResponseStatus status) {
838 this.status = status;
839 }
840
841
842
843
844 public final HttpRequest getRequest() {
845 return request;
846 }
847
848
849
850
851 public final boolean isWillClose() {
852 return willClose;
853 }
854
855
856
857
858 public final void setWillClose(final boolean willClose) {
859 this.willClose = willClose;
860 }
861 }