001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2016-2018 Michael N. Lipp
004 * 
005 * This program is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU Affero General Public License as published by 
007 * the Free Software Foundation; either version 3 of the License, or 
008 * (at your option) any later version.
009 * 
010 * This program is distributed in the hope that it will be useful, but 
011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 
013 * for more details.
014 * 
015 * You should have received a copy of the GNU Affero General Public License along 
016 * with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jgrapes.http.events;
020
021import java.net.URI;
022import java.net.URISyntaxException;
023import java.net.URL;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Optional;
027import java.util.Stack;
028import java.util.function.BiConsumer;
029import java.util.stream.Collectors;
030import org.jdrupes.httpcodec.protocols.http.HttpConstants.HttpProtocol;
031import org.jdrupes.httpcodec.protocols.http.HttpRequest;
032import org.jgrapes.core.Channel;
033import org.jgrapes.core.CompletionEvent;
034import org.jgrapes.core.Components;
035import org.jgrapes.http.ResourcePattern;
036import org.jgrapes.http.ResourcePattern.PathSpliterator;
037import org.jgrapes.net.SocketIOChannel;
038
039/**
040 * The base class for all HTTP requests such as {@link Request.In.Get},
041 * {@link Request.In.Post} etc.
042 *
043 * @param <R> the generic type
044 */
045public class Request<R> extends MessageReceived<R> {
046
047    /**
048     * @param channels
049     */
050    protected Request(Channel... channels) {
051        super(channels);
052    }
053
054    /**
055     * The base class for all incoming HTTP requests. Incoming
056     * request flow downstream and are served by the framework. 
057     * 
058     * A result of `true` indicates that the request has been processed, 
059     * i.e. a response has been sent or will sent.
060     */
061    @SuppressWarnings("PMD.ShortClassName")
062    public static class In extends Request<Boolean> {
063
064        @SuppressWarnings("PMD.AvoidFieldNameMatchingTypeName")
065        private final HttpRequest request;
066        private final int matchLevels;
067        private final MatchValue matchValue;
068        private final URI resourceUri;
069        private URI uri;
070
071        /**
072         * Creates a new request event with the associated {@link Completed}
073         * event.
074         * 
075         * @param protocol the protocol as reported by {@link #requestUri()}
076         * @param request the request data
077         * @param matchLevels the number of elements from the request path
078         * to use in the match value (see {@link #matchValue})
079         * @param channels the channels associated with this event
080         * @throws URISyntaxException 
081         */
082        @SuppressWarnings({ "PMD.UselessParentheses",
083            "PMD.ConstructorCallsOverridableMethod" })
084        public In(String protocol, HttpRequest request,
085                int matchLevels, Channel... channels)
086                throws URISyntaxException {
087            super(channels);
088            new Completed(this);
089            this.request = request;
090            this.matchLevels = matchLevels;
091
092            // Do any required request specific processing of the original
093            // request URI
094            URI requestUri = effectiveRequestUri(protocol, request);
095
096            // Clean the request URI's path, keeping the segments for matchValue
097            List<String> segs = pathToSegs(requestUri);
098            requestUri = new URI(requestUri.getScheme(),
099                requestUri.getUserInfo(), requestUri.getHost(),
100                requestUri.getPort(),
101                "/" + segs.stream().collect(Collectors.joining("/")),
102                requestUri.getQuery(), null);
103            setRequestUri(requestUri);
104
105            // The URI for handler selection ignores user info and query
106            resourceUri = new URI(requestUri.getScheme(), null,
107                requestUri.getHost(), requestUri.getPort(),
108                requestUri.getPath(), null, null);
109            @SuppressWarnings("PMD.InefficientStringBuffering")
110            StringBuilder matchPath = new StringBuilder("/" + segs.stream()
111                .limit(matchLevels).collect(Collectors.joining("/")));
112            if (segs.size() > matchLevels) {
113                if (!matchPath.toString().endsWith("/")) {
114                    matchPath.append('/');
115                }
116                matchPath.append('…');
117            }
118            matchValue = new MatchValue(getClass(),
119                (requestUri.getScheme() == null ? ""
120                    : (requestUri.getScheme() + "://"))
121                    + (requestUri.getHost() == null ? ""
122                        : (requestUri.getHost()
123                            + (requestUri.getPort() == -1 ? ""
124                                : (":" + requestUri.getPort()))))
125                    + matchPath);
126        }
127
128        @SuppressWarnings("PMD.AvoidLiteralsInIfCondition")
129        private List<String> pathToSegs(URI requestUri)
130                throws URISyntaxException {
131            Iterator<String> origSegs = PathSpliterator.stream(
132                requestUri.getPath()).iterator();
133            // Path must not be empty and must be absolute
134            if (!origSegs.hasNext() || !origSegs.next().isEmpty()) {
135                throw new URISyntaxException(
136                    requestUri().getPath(), "Must be absolute");
137            }
138            // Remove dot segments and check for "...//..."
139            @SuppressWarnings({ "PMD.LooseCoupling",
140                "PMD.ReplaceVectorWithList" })
141            Stack<String> segs = new Stack<>();
142            while (origSegs.hasNext()) {
143                if (!segs.isEmpty() && segs.peek().isEmpty()) {
144                    // Empty segment followed by more means "//"
145                    segs.clear();
146                }
147                String seg = origSegs.next();
148                if (".".equals(seg)) {
149                    continue;
150                }
151                if ("..".equals(seg)) {
152                    if (!segs.isEmpty()) {
153                        segs.pop();
154                    }
155                    continue;
156                }
157                segs.push(seg);
158            }
159            return segs;
160        }
161
162        /**
163         * Builds the URI that represents this request. The default
164         * implementation checks that request URI in the HTTP request
165         * is directed at this server as specified in the "Host"-header
166         * and adds the protocol, host and port if not specified 
167         * in the request URI. 
168         *
169         * @param protocol the protocol
170         * @param request the request
171         * @return the URI
172         * @throws URISyntaxException if the request is not acceptable
173         */
174        protected URI effectiveRequestUri(String protocol, HttpRequest request)
175                throws URISyntaxException {
176            URI serverUri = new URI(protocol, null,
177                request.host(), request.port(), "/", null, null);
178            URI origRequest = request.requestUri();
179            URI result = serverUri.resolve(new URI(null, null, null, -1,
180                origRequest.getPath(), origRequest.getQuery(), null));
181            if (!result.getScheme().equals(protocol)
182                || !result.getHost().equals(request.host())
183                || result.getPort() != request.port()) {
184                throw new URISyntaxException(origRequest.toString(),
185                    "Scheme, host or port not allowed");
186            }
187            return result;
188        }
189
190        /**
191         * Creates the appropriate derived request event type from
192         * a given {@link HttpRequest}.
193         * 
194         * @param request the HTTP request
195         * @param secure whether the request was received over a secure channel
196         * @param matchLevels the match levels
197         * @return the request event
198         * @throws URISyntaxException 
199         */
200        @SuppressWarnings("PMD.AvoidDuplicateLiterals")
201        public static In fromHttpRequest(
202                HttpRequest request, boolean secure, int matchLevels)
203                throws URISyntaxException {
204            switch (request.method()) {
205            case "OPTIONS":
206                return new Options(request, secure, matchLevels);
207            case "GET":
208                return new Get(request, secure, matchLevels);
209            case "HEAD":
210                return new Head(request, secure, matchLevels);
211            case "POST":
212                return new Post(request, secure, matchLevels);
213            case "PUT":
214                return new Put(request, secure, matchLevels);
215            case "DELETE":
216                return new Delete(request, secure, matchLevels);
217            case "TRACE":
218                return new Trace(request, secure, matchLevels);
219            case "CONNECT":
220                return new Connect(request, secure, matchLevels);
221            default:
222                return new In(secure ? "https" : "http", request, matchLevels);
223            }
224        }
225
226        /**
227         * Sets the request URI.
228         *
229         * @param uri the new request URI
230         */
231        protected final void setRequestUri(URI uri) {
232            this.uri = uri;
233        }
234
235        /**
236         * Returns an absolute URI of the request. For incoming requests, the 
237         * URI is built from the information provided by the decoder.
238         * 
239         * @return the URI
240         */
241        public final URI requestUri() {
242            return uri;
243        }
244
245        /**
246         * Returns the "raw" request as provided by the HTTP decoder.
247         * 
248         * @return the request
249         */
250        public HttpRequest httpRequest() {
251            return request;
252        }
253
254        /**
255         * The match value consists of the event class and a URI.
256         * The URI is similar to the request URI but its path elements
257         * are shortened as specified in the constructor.
258         * 
259         * The match value is used as key in a map that speeds up
260         * the lookup of handlers. Having the complete URI in the match
261         * value would inflate this map.
262         *
263         * @return the object
264         * @see org.jgrapes.core.Event#defaultCriterion()
265         */
266        @Override
267        public Object defaultCriterion() {
268            return matchValue;
269        }
270
271        /*
272         * (non-Javadoc)
273         * 
274         * @see org.jgrapes.core.Event#isMatchedBy(java.lang.Object)
275         */
276        @Override
277        public boolean isEligibleFor(Object value) {
278            if (!(value instanceof MatchValue)) {
279                return super.isEligibleFor(value);
280            }
281            MatchValue mval = (MatchValue) value;
282            if (!mval.type.isAssignableFrom(matchValue.type)) {
283                return false;
284            }
285            if (mval.resource instanceof ResourcePattern) {
286                return ((ResourcePattern) mval.resource).matches(resourceUri,
287                    matchLevels) >= 0;
288            }
289            return mval.resource.equals(matchValue.resource);
290        }
291
292        /*
293         * (non-Javadoc)
294         * 
295         * @see java.lang.Object#toString()
296         */
297        @Override
298        @SuppressWarnings("PMD.AvoidLiteralsInIfCondition")
299        public String toString() {
300            StringBuilder builder = new StringBuilder(50);
301            builder.append(Components.objectName(this))
302                .append(" [\"");
303            String path = Optional.ofNullable(request.requestUri().getPath())
304                .orElse("");
305            if (path.length() > 15) {
306                builder.append("...")
307                    .append(path.substring(path.length() - 12));
308            } else {
309                builder.append(path);
310            }
311            builder.append('\"');
312            if (channels().length > 0) {
313                builder.append(", channels=");
314                builder.append(Channel.toString(channels()));
315            }
316            builder.append(']');
317            return builder.toString();
318        }
319
320        /**
321         * Creates the match value.
322         *
323         * @param type the type
324         * @param resource the resource
325         * @return the object
326         */
327        public static Object createMatchValue(
328                Class<?> type, ResourcePattern resource) {
329            return new MatchValue(type, resource);
330        }
331
332        /**
333         * Represents a match value.
334         */
335        private static class MatchValue {
336            private final Class<?> type;
337            private final Object resource;
338
339            /**
340             * @param type
341             * @param resource
342             */
343            public MatchValue(Class<?> type, Object resource) {
344                super();
345                this.type = type;
346                this.resource = resource;
347            }
348
349            /*
350             * (non-Javadoc)
351             * 
352             * @see java.lang.Object#hashCode()
353             */
354            @Override
355            @SuppressWarnings("PMD.DataflowAnomalyAnalysis")
356            public int hashCode() {
357                @SuppressWarnings("PMD.AvoidFinalLocalVariable")
358                final int prime = 31;
359                int result = 1;
360                result = prime * result
361                    + ((resource == null) ? 0 : resource.hashCode());
362                result
363                    = prime * result + ((type == null) ? 0 : type.hashCode());
364                return result;
365            }
366
367            /*
368             * (non-Javadoc)
369             * 
370             * @see java.lang.Object#equals(java.lang.Object)
371             */
372            @Override
373            public boolean equals(Object obj) {
374                if (this == obj) {
375                    return true;
376                }
377                if (obj == null) {
378                    return false;
379                }
380                if (getClass() != obj.getClass()) {
381                    return false;
382                }
383                MatchValue other = (MatchValue) obj;
384                if (resource == null) {
385                    if (other.resource != null) {
386                        return false;
387                    }
388                } else if (!resource.equals(other.resource)) {
389                    return false;
390                }
391                if (type == null) {
392                    if (other.type != null) {
393                        return false;
394                    }
395                } else if (!type.equals(other.type)) {
396                    return false;
397                }
398                return true;
399            }
400
401        }
402
403        /**
404         * The associated completion event.
405         */
406        public static class Completed extends CompletionEvent<In> {
407
408            /**
409             * Instantiates a new event.
410             *
411             * @param monitoredEvent the monitored event
412             * @param channels the channels
413             */
414            public Completed(In monitoredEvent, Channel... channels) {
415                super(monitoredEvent, channels);
416            }
417        }
418
419        /**
420         * Represents a HTTP CONNECT request.
421         */
422        public static class Connect extends In {
423
424            /**
425             * Create a new event.
426             * 
427             * @param request the request data
428             * @param secure indicates whether the request was received on a
429             * secure channel
430             * @param matchLevels the number of elements from the request path
431             * to use in the match value
432             * @param channels the channels on which the event is to be 
433             * fired (optional)
434             * @throws URISyntaxException 
435             */
436            public Connect(HttpRequest request, boolean secure, int matchLevels,
437                    Channel... channels) throws URISyntaxException {
438                super(secure ? "https" : "http", request, matchLevels,
439                    channels);
440            }
441
442            /**
443             * Builds the URI that represents this request. This
444             * implementation returns the request URI without
445             * path and query component. 
446             *
447             * @param protocol the protocol
448             * @param request the request
449             * @return the uri
450             * @throws URISyntaxException the URI syntax exception
451             * @see "[RFC 7230, Section 5.5](https://datatracker.ietf.org/doc/html/rfc7230#section-5.5)"
452             */
453            @Override
454            protected URI effectiveRequestUri(String protocol,
455                    HttpRequest request) throws URISyntaxException {
456                URI req = request.requestUri();
457                return new URI(req.getScheme(), req.getUserInfo(),
458                    req.getHost(), req.getPort(), null, null, null);
459            }
460        }
461
462        /**
463         * The Class Delete.
464         */
465        public static class Delete extends In {
466
467            /**
468            * Create a new event.
469            * 
470            * @param request the request data
471            * @param secure indicates whether the request was received on a
472            * secure channel
473            * @param matchLevels the number of elements from the request path
474            * to use in the match value
475            * @param channels the channels on which the event is to be 
476            * fired (optional)
477             * @throws URISyntaxException 
478            */
479            public Delete(HttpRequest request, boolean secure, int matchLevels,
480                    Channel... channels) throws URISyntaxException {
481                super(secure ? "https" : "http", request, matchLevels,
482                    channels);
483            }
484
485        }
486
487        /**
488         * Represents a HTTP GET request.
489         */
490        public static class Get extends In {
491
492            /**
493             * Create a new event.
494             * 
495             * @param request the request data
496             * @param secure indicates whether the request was received on a
497             * secure channel
498             * @param matchLevels the number of elements from the request path
499             * to use in the match value
500             * @param channels the channels on which the event is to be 
501             * fired (optional)
502             * @throws URISyntaxException 
503             */
504            public Get(HttpRequest request, boolean secure, int matchLevels,
505                    Channel... channels) throws URISyntaxException {
506                super(secure ? "https" : "http", request, matchLevels,
507                    channels);
508            }
509        }
510
511        /**
512         * Represents a HTTP HEAD request.
513         */
514        public static class Head extends In {
515
516            /**
517             * Create a new event.
518             * 
519             * @param request the request data
520             * @param secure indicates whether the request was received on a
521             * secure channel
522             * @param matchLevels the number of elements from the request path
523             * to use in the match value
524             * @param channels the channels on which the event is to be 
525             * fired (optional)
526             * @throws URISyntaxException 
527             */
528            public Head(HttpRequest request, boolean secure, int matchLevels,
529                    Channel... channels) throws URISyntaxException {
530                super(secure ? "https" : "http", request, matchLevels,
531                    channels);
532            }
533        }
534
535        /**
536         * Represents a HTTP OPTIONS request.
537         */
538        public static class Options extends In {
539
540            /**
541             * Create a new event.
542             * 
543             * @param request the request data
544             * @param secure indicates whether the request was received on a
545             * secure channel
546             * @param matchLevels the number of elements from the request path
547             * to use in the match value
548             * @param channels the channels on which the event is to be 
549             * fired (optional)
550             * @throws URISyntaxException 
551             */
552            public Options(HttpRequest request, boolean secure, int matchLevels,
553                    Channel... channels) throws URISyntaxException {
554                super(secure ? "https" : "http", request, matchLevels,
555                    channels);
556            }
557        }
558
559        /**
560         * Represents a HTTP POST request.
561         */
562        public static class Post extends In {
563
564            /**
565             * Create a new event.
566             * 
567             * @param request the request data
568             * @param secure indicates whether the request was received on a
569             * secure channel
570             * @param matchLevels the number of elements from the request path
571             * to use in the match value
572             * @param channels the channels on which the event is to be 
573             * fired (optional)
574             * @throws URISyntaxException 
575             */
576            public Post(HttpRequest request, boolean secure, int matchLevels,
577                    Channel... channels) throws URISyntaxException {
578                super(secure ? "https" : "http", request, matchLevels,
579                    channels);
580            }
581
582        }
583
584        /**
585         * Represents a HTTP PUT request.
586         */
587        public static class Put extends In {
588
589            /**
590             * Create a new event.
591             * 
592             * @param request the request data
593             * @param secure indicates whether the request was received on a
594             * secure channel
595             * @param matchLevels the number of elements from the request path
596             * to use in the match value
597             * @param channels the channels on which the event is to be 
598             * fired (optional)
599             * @throws URISyntaxException 
600             */
601            public Put(HttpRequest request, boolean secure, int matchLevels,
602                    Channel... channels) throws URISyntaxException {
603                super(secure ? "https" : "http", request, matchLevels,
604                    channels);
605            }
606        }
607
608        /**
609         * Represents a HTTP TRACE request.
610         */
611        public static class Trace extends In {
612
613            /**
614             * Create a new event.
615             * 
616             * @param request the request data
617             * @param secure indicates whether the request was received on a
618             * secure channel
619             * @param matchLevels the number of elements from the request path
620             * to use in the match value
621             * @param channels the channels on which the event is to be 
622             * fired (optional)
623             * @throws URISyntaxException 
624             */
625            public Trace(HttpRequest request, boolean secure, int matchLevels,
626                    Channel... channels) throws URISyntaxException {
627                super(secure ? "https" : "http", request, matchLevels,
628                    channels);
629            }
630        }
631    }
632
633    /**
634     * The base class for all outgoing HTTP requests. Outgoing
635     * request flow upstream and are served externally. 
636     * 
637     * A result of `true` indicates that the request has been processed, 
638     * i.e. a response has been sent or will sent.
639     */
640    @SuppressWarnings("PMD.ShortClassName")
641    public static class Out extends Request<Void> {
642
643        private final HttpRequest request;
644        private BiConsumer<Request.Out, SocketIOChannel> connectedCallback;
645
646        /**
647         * Instantiates a new request.
648         *
649         * @param method the method
650         * @param url the url
651         */
652        public Out(String method, URL url) {
653            try {
654                request = new HttpRequest(method, url.toURI(),
655                    HttpProtocol.HTTP_1_1, false);
656            } catch (URISyntaxException e) {
657                // This should not happen because every valid URL can be
658                // converted to a URI.
659                throw new IllegalArgumentException(e);
660            }
661        }
662
663        /**
664         * Sets a "connected callback". When the {@link Out} event is
665         * created, the network connection is not yet known. Some
666         * header fields' values, however, need e.g. the port information
667         * from the connection. Therefore a callback may be set which is
668         * invoked when the connection has been obtained that will be used
669         * to send the request.
670         *
671         * @param connectedCallback the connected callback
672         * @return the out
673         */
674        public Out setConnectedCallback(
675                BiConsumer<Request.Out, SocketIOChannel> connectedCallback) {
676            this.connectedCallback = connectedCallback;
677            return this;
678        }
679
680        /**
681         * Returns the connected callback.
682         *
683         * @return the connected callback, if set
684         */
685        public Optional<BiConsumer<Request.Out, SocketIOChannel>>
686                connectedCallback() {
687            return Optional.ofNullable(connectedCallback);
688        }
689
690        /**
691         * The HTTP request that will be sent by the event.
692         *
693         * @return the http request
694         */
695        public HttpRequest httpRequest() {
696            return request;
697        }
698
699        /**
700         * Returns an absolute URI of the request.
701         * 
702         * @return the URI
703         */
704        public URI requestUri() {
705            return request.requestUri();
706        }
707
708        /*
709         * (non-Javadoc)
710         * 
711         * @see java.lang.Object#toString()
712         */
713        @Override
714        public String toString() {
715            StringBuilder builder = new StringBuilder();
716            builder.append(Components.objectName(this))
717                .append(" [").append(request.toString());
718            if (channels().length > 0) {
719                builder.append(", channels=");
720                builder.append(Channel.toString(channels()));
721            }
722            builder.append(']');
723            return builder.toString();
724        }
725
726        /**
727         * Represents a HTTP CONNECT request.
728         */
729        public static class Connect extends Out {
730
731            /**
732             * Instantiates a new request.
733             *
734             * @param uri the uri
735             */
736            public Connect(URL uri) {
737                super("CONNECT", uri);
738            }
739        }
740
741        /**
742         * Represents a HTTP DELETE request.
743         */
744        public static class Delete extends Out {
745
746            /**
747             * Instantiates a new request.
748             *
749             * @param uri the uri
750             */
751            public Delete(URL uri) {
752                super("DELETE", uri);
753            }
754        }
755
756        /**
757         * Represents a HTTP GET request.
758         */
759        public static class Get extends Out {
760
761            /**
762             * Instantiates a new request.
763             *
764             * @param uri the uri
765             */
766            public Get(URL uri) {
767                super("GET", uri);
768            }
769        }
770
771        /**
772         * Represents a HTTP HEAD request.
773         */
774        public static class Head extends Out {
775
776            /**
777             * Instantiates a new request.
778             *
779             * @param uri the uri
780             */
781            public Head(URL uri) {
782                super("HEAD", uri);
783            }
784        }
785
786        /**
787         * Represents a HTTP OPTIONS request.
788         */
789        public static class Options extends Out {
790
791            /**
792             * Instantiates a new request.
793             *
794             * @param uri the uri
795             */
796            public Options(URL uri) {
797                super("OPTIONS", uri);
798            }
799        }
800
801        /**
802         * Represents a HTTP POST request.
803         */
804        public static class Post extends Out {
805
806            /**
807             * Instantiates a new request.
808             *
809             * @param uri the uri
810             */
811            public Post(URL uri) {
812                super("POST", uri);
813            }
814        }
815
816        /**
817         * Represents a HTTP PUT request.
818         */
819        public static class Put extends Out {
820
821            /**
822             * Instantiates a new request.
823             *
824             * @param uri the uri
825             */
826            public Put(URL uri) {
827                super("PUT", uri);
828            }
829        }
830
831        /**
832         * Represents a HTTP TRACE request.
833         */
834        public static class Trace extends Out {
835
836            /**
837             * Instantiates a new request.
838             *
839             * @param uri the uri
840             */
841            public Trace(URL uri) {
842                super("TRACE", uri);
843            }
844        }
845    }
846}