View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.core5.http.impl.routing;
29  
30  import java.util.ArrayList;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.function.BiFunction;
34  import java.util.function.Function;
35  import java.util.stream.Collectors;
36  
37  import org.apache.hc.core5.annotation.Contract;
38  import org.apache.hc.core5.annotation.Internal;
39  import org.apache.hc.core5.annotation.ThreadingBehavior;
40  import org.apache.hc.core5.http.HttpException;
41  import org.apache.hc.core5.http.HttpRequest;
42  import org.apache.hc.core5.http.HttpRequestMapper;
43  import org.apache.hc.core5.http.MisdirectedRequestException;
44  import org.apache.hc.core5.http.protocol.HttpContext;
45  import org.apache.hc.core5.http.protocol.UriPatternType;
46  import org.apache.hc.core5.net.URIAuthority;
47  import org.apache.hc.core5.util.Args;
48  
49  /**
50   * Request mapper that can route requests based on their properties to a specific request handler.
51   *
52   * @param <T> request handler type.
53   * @since 5.3
54   */
55  @Contract(threading = ThreadingBehavior.IMMUTABLE)
56  public class RequestRouter<T> implements HttpRequestMapper<T> {
57  
58      @Internal
59      public final static class Entry<T> {
60  
61          public final URIAuthority uriAuthority;
62          public final PathRoute<String, T> route;
63  
64          public Entry(final URIAuthority uriAuthority, final String pathPattern, final T handler) {
65              this.uriAuthority = uriAuthority;
66              this.route = new PathRoute<>(pathPattern, handler);
67          }
68  
69          public Entry(final String hostname, final String pathPattern, final T handler) {
70              this(new URIAuthority(hostname), pathPattern, handler);
71          }
72  
73          public Entry(final String pathPattern, final T handler) {
74              this((URIAuthority) null, pathPattern, handler);
75          }
76  
77          @Override
78          public String toString() {
79              return uriAuthority + "/" + route;
80          }
81  
82      }
83  
84      static class SingleAuthorityResolver<T> implements Function<URIAuthority, T> {
85  
86          private final URIAuthority singleAuthority;
87          private final T router;
88  
89          SingleAuthorityResolver(final URIAuthority singleAuthority, final T router) {
90              this.singleAuthority = singleAuthority;
91              this.router = router;
92          }
93  
94          @Override
95          public T apply(final URIAuthority authority) {
96              return singleAuthority.equals(authority) ? router : null;
97          }
98  
99      }
100 
101     static class NoAuthorityResolver<T> implements Function<URIAuthority, T> {
102 
103         @Override
104         public T apply(final URIAuthority authority) {
105             return null;
106         }
107 
108     }
109 
110     @Internal
111     public static <T> RequestRouter<T> create(final URIAuthority primaryAuthority,
112                                               final UriPatternType patternType,
113                                               final List<Entry<T>> handlerEntries,
114                                               final BiFunction<String, URIAuthority, URIAuthority> authorityResolver,
115                                               final HttpRequestMapper<T> downstream) {
116         final Map<URIAuthority, Function<String, T>> authorityMap = handlerEntries.stream()
117                 .collect(Collectors.groupingBy(
118                         e -> e.uriAuthority != null ? e.uriAuthority : primaryAuthority != null ? primaryAuthority : LOCAL_AUTHORITY,
119                         Collectors.mapping(e -> e.route,
120                                 Collectors.collectingAndThen(Collectors.toList(), e -> {
121                                     switch (patternType) {
122                                         case URI_PATTERN:
123                                             return UriPathRouter.bestMatch(e);
124                                         case URI_PATTERN_IN_ORDER:
125                                             return UriPathRouter.ordered(e);
126                                         case REGEX:
127                                             return UriPathRouter.regEx(e);
128                                         default:
129                                             throw new IllegalStateException("Unexpected pattern type: " + patternType);
130                                     }
131                                 }))));
132         final Function<URIAuthority, Function<String, T>> authorityFunction;
133         if (authorityMap.isEmpty()) {
134             authorityFunction = new NoAuthorityResolver<>();
135         } else if (authorityMap.size() == 1) {
136             final Map.Entry<URIAuthority, Function<String, T>> entry = authorityMap.entrySet().iterator().next();
137             authorityFunction = new SingleAuthorityResolver<>(entry.getKey(), entry.getValue());
138         } else {
139             authorityFunction = authorityMap::get;
140         }
141         return new RequestRouter<>(authorityFunction, authorityResolver, downstream);
142     }
143 
144     public static <T> Builder<T> builder(final UriPatternType patternType) {
145         return new Builder<>(patternType);
146     }
147 
148     public static <T> Builder<T> builder() {
149         return new Builder<>(UriPatternType.URI_PATTERN);
150     }
151 
152     public static final URIAuthority LOCAL_AUTHORITY = new URIAuthority("localhost");
153     public final static BiFunction<String, URIAuthority, URIAuthority> LOCAL_AUTHORITY_RESOLVER = (scheme, authority) -> LOCAL_AUTHORITY;
154     public final static BiFunction<String, URIAuthority, URIAuthority> IGNORE_PORT_AUTHORITY_RESOLVER = (scheme, authority) ->
155             authority != null && authority.getPort() != -1 ? new URIAuthority(authority.getHostName(), -1) : authority;
156 
157     private final Function<URIAuthority, Function<String, T>> authorityRouter;
158     private final BiFunction<String, URIAuthority, URIAuthority> authorityResolver;
159     private final HttpRequestMapper<T> downstream;
160 
161     RequestRouter(final Function<URIAuthority, Function<String, T>> authorityRouter,
162                   final BiFunction<String, URIAuthority, URIAuthority> authorityResolver,
163                   final HttpRequestMapper<T> downstream) {
164         this.authorityRouter = authorityRouter;
165         this.authorityResolver = authorityResolver;
166         this.downstream = downstream;
167     }
168 
169     @Override
170     public T resolve(final HttpRequest request, final HttpContext context) throws HttpException {
171         final URIAuthority authority = authorityResolver != null ?
172                 authorityResolver.apply(request.getScheme(), request.getAuthority()) : request.getAuthority();
173         final Function<String, T> pathRouter = authority != null ?
174                 authorityRouter.apply(authority) : null;
175         if (pathRouter == null) {
176             if (downstream != null) {
177                 return downstream.resolve(request, context);
178             } else {
179                 throw new MisdirectedRequestException("Not authoritative");
180             }
181         }
182         String path = request.getPath();
183         final int i = path.indexOf('?');
184         if (i != -1) {
185             path = path.substring(0, i);
186         }
187         return pathRouter.apply(path);
188     }
189 
190     public static class Builder<T> {
191 
192         private final UriPatternType patternType;
193         private final List<Entry<T>> handlerEntries;
194         private BiFunction<String, URIAuthority, URIAuthority> authorityResolver;
195         private HttpRequestMapper<T> downstream;
196 
197         Builder(final UriPatternType patternType) {
198             this.patternType = patternType != null ? patternType : UriPatternType.URI_PATTERN;
199             this.handlerEntries = new ArrayList<>();
200         }
201 
202         /**
203          * Adds a route with given authority and path pattern. Requests with the same authority and matching the path pattern
204          * will be routed for execution to the handler.
205          */
206         public Builder<T> addRoute(final URIAuthority authority, final String pathPattern, final T handler) {
207             Args.notNull(authority, "URI authority");
208             Args.notBlank(pathPattern, "URI path pattern");
209             Args.notNull(handler, "Handler");
210             this.handlerEntries.add(new Entry<>(authority, pathPattern, handler));
211             return this;
212         }
213 
214         /**
215          * Adds a route with given hostname and path pattern. Requests with the same hostname and matching the path pattern
216          * will be routed for execution to the handler.
217          */
218         public Builder<T> addRoute(final String hostname, final String pathPattern, final T handler) {
219             Args.notBlank(hostname, "Hostname");
220             Args.notBlank(pathPattern, "URI path pattern");
221             Args.notNull(handler, "Handler");
222             this.handlerEntries.add(new Entry<>(hostname, pathPattern, handler));
223             return this;
224         }
225 
226         /**
227          * Assigns custom {@link URIAuthority} resolution {@link Function} that can be used to normalize or re-write
228          * the authority specified in incoming requests prior to request routing. The function can return
229          * a new {@link URIAuthority} instance representing an identity of the service authoritative to handle
230          * the request or {@code null} if an authoritative service cannot be found or is unknown.
231          */
232         public Builder<T> resolveAuthority(final BiFunction<String, URIAuthority, URIAuthority> authorityResolver) {
233             this.authorityResolver = authorityResolver;
234             return this;
235         }
236 
237         /**
238          * Assigns a downstream request mapper that can be used as a fallback in case no authoritative service can be found
239          * to handle an incoming request. Using this method request mappers can be linked to form a chain of responsibility,
240          * with each link representing a different authority.
241          */
242         public Builder<T> downstream(final HttpRequestMapper<T> downstream) {
243             this.downstream = downstream;
244             return this;
245         }
246 
247         public RequestRouter<T> build() {
248             return RequestRouter.create(null, patternType, handlerEntries, authorityResolver, downstream);
249         }
250 
251     }
252 
253 }