1 module hunt.http.routing.impl.RouterManagerImpl;
2 
3 import hunt.http.routing.impl.ContentTypePreciseMatcher;
4 import hunt.http.routing.impl.ContentTypePatternMatcher;
5 import hunt.http.routing.impl.AcceptHeaderMatcher;
6 import hunt.http.routing.impl.HttpMethodMatcher;
7 import hunt.http.routing.impl.PatternPathMatcher;
8 import hunt.http.routing.impl.PrecisePathMatcher;
9 import hunt.http.routing.impl.ParameterPathMatcher;
10 import hunt.http.routing.impl.RegexPathMatcher;
11 import hunt.http.routing.impl.RouterImpl;
12 import hunt.http.routing.impl.RoutingContextImpl;
13 import hunt.http.routing.handler.ErrorResponseHandler;
14 
15 import hunt.http.HttpHeader;
16 import hunt.http.HttpMethod;
17 import hunt.http.HttpRequest;
18 import hunt.http.HttpStatus;
19 
20 import hunt.http.server.HttpServerContext;
21 import hunt.http.routing.Matcher;
22 import hunt.http.routing.Router;
23 import hunt.http.routing.RouterManager;
24 import hunt.http.routing.RoutingContext;
25 
26 import hunt.collection;
27 import hunt.Exceptions;
28 import hunt.logging;
29 
30 /**
31  * 
32  */
33 class RouterManagerImpl : RouterManager {
34 
35     private int idGenerator;
36     private Map!(Matcher.MatchType, List!(Matcher)) matcherMap;
37     private Matcher precisePathMather;
38     private Matcher patternPathMatcher;
39     private Matcher regexPathMatcher;
40     private Matcher parameterPathMatcher;
41     private Matcher httpMethodMatcher;
42     // private Matcher contentTypePreciseMatcher;
43     // private Matcher contentTypePatternMatcher;
44     // private Matcher acceptHeaderMatcher;
45 
46     private Router _code405Router;
47 
48     this() {
49         _code405Router = handleStatus405();
50         matcherMap = new HashMap!(Matcher.MatchType, List!(Matcher))();
51         precisePathMather = new PrecisePathMatcher();
52         patternPathMatcher = new PatternPathMatcher();
53         parameterPathMatcher = new ParameterPathMatcher();
54         regexPathMatcher = new RegexPathMatcher();
55         ArrayList!Matcher al = new ArrayList!Matcher([precisePathMather, patternPathMatcher, 
56             parameterPathMatcher, regexPathMatcher]);
57         matcherMap.put(Matcher.MatchType.PATH, al);
58 
59         httpMethodMatcher = new HttpMethodMatcher();
60         matcherMap.put(Matcher.MatchType.METHOD, Collections.singletonList!(Matcher)(httpMethodMatcher));
61 
62         // contentTypePreciseMatcher = new ContentTypePreciseMatcher();
63         // contentTypePatternMatcher = new ContentTypePatternMatcher();
64         // al = new ArrayList!Matcher([contentTypePreciseMatcher, contentTypePatternMatcher]);
65         // matcherMap.put(Matcher.MatchType.CONTENT_TYPE, al);
66 
67         // acceptHeaderMatcher = new AcceptHeaderMatcher();
68         // matcherMap.put(Matcher.MatchType.ACCEPT, Collections.singletonList!(Matcher)(acceptHeaderMatcher));
69     }
70 
71     Matcher getHttpMethodMatcher() {
72         return httpMethodMatcher;
73     }
74 
75     Matcher getPrecisePathMather() {
76         return precisePathMather;
77     }
78 
79     Matcher getPatternPathMatcher() {
80         return patternPathMatcher;
81     }
82 
83     Matcher getRegexPathMatcher() {
84         return regexPathMatcher;
85     }
86 
87     Matcher getParameterPathMatcher() {
88         return parameterPathMatcher;
89     }
90 
91     // Matcher getContentTypePreciseMatcher() {
92     //     return contentTypePreciseMatcher;
93     // }
94 
95     // Matcher getContentTypePatternMatcher() {
96     //     return contentTypePatternMatcher;
97     // }
98 
99     // Matcher getAcceptHeaderMatcher() {
100     //     return acceptHeaderMatcher;
101     // }
102 
103     override NavigableSet!RouterMatchResult findRouter(string method, 
104             string path, string contentType, string accept) {
105 
106         // warningf("method: %s, path: %s, accept: %s", method, path, accept);
107 
108         Map!(Router, Set!(Matcher.MatchType)) routerMatchTypes = new HashMap!(Router, Set!(Matcher.MatchType))();
109         Map!(Router, Map!(string, string)) routerParameters = new HashMap!(Router, Map!(string, string))();
110 
111         findRouter(method, Matcher.MatchType.METHOD, routerMatchTypes, routerParameters);
112         findRouter(path, Matcher.MatchType.PATH, routerMatchTypes, routerParameters);
113         // findRouter(contentType, Matcher.MatchType.CONTENT_TYPE, routerMatchTypes, routerParameters);
114         // findRouter(accept, Matcher.MatchType.ACCEPT, routerMatchTypes, routerParameters);
115 
116         NavigableSet!(RouterMatchResult) ret = new TreeSet!(RouterMatchResult)();
117         foreach(Router key, Set!(Matcher.MatchType) value; routerMatchTypes) {
118             // tracef("checking route id: %d", key.getId());
119             if(!key.isEnable()) continue;
120 
121             // infof("route: %s", (cast(Object)key).toString());
122             // tracef("target MatchTypes: %s", value);
123 
124             Set!(Matcher.MatchType) matchTypes = key.getMatchTypes();
125             if(matchTypes == value) {
126                 ret.add(new RouterMatchResult(key, routerParameters.get(key), value));
127                 // TODO: Tasks pending completion -@zhangxueping at 2020-08-03T16:50:45+08:00
128                 // 
129             // } else if(matchTypes.contains(Matcher.MatchType.METHOD) && matchTypes.contains(Matcher.MatchType.PATH)) {
130             //     // 405 Method Not Allowed
131             //     ret.add(new RouterMatchResult(_code405Router, null, value));
132             }
133         }
134         return ret;
135     }
136 
137     private void findRouter(string value, Matcher.MatchType matchType,
138                             Map!(Router, Set!(Matcher.MatchType)) routerMatchTypes,
139                             Map!(Router, Map!(string, string)) routerParameters) {
140 
141         List!(Matcher) matchers = matcherMap.get(matchType);
142         int matchersSize = matchers.size();
143 
144         // FIXME: Needing refactor or cleanup -@zhangxueping at 2020-03-31T17:33:01+08:00
145         // https://forum.dlang.org/post/exknjzbuooofyulgeaen@forum.dlang.org
146         for(int i=0; i<matchersSize; i++) {
147             Matcher m = matchers.get(i);
148 
149             // tracef("%d => %s", i, value);
150         
151         // foreach(Matcher m; matchers) {
152             MatchResult mr = m.match(value);
153             if(mr is null) continue;
154 
155 
156             Set!(Router) routers = mr.getRouters();
157             foreach(Router router; routers) {
158 
159             // warningf("found router %d for %s", router.getId(), value);
160 
161                 routerMatchTypes.computeIfAbsent(router, k => new HashSet!(Matcher.MatchType)())
162                                 .add(mr.getMatchType());
163                 Map!(Router, Map!(string, string)) parameters = mr.getParameters();
164 
165                 if (parameters !is null && !parameters.isEmpty()) {
166                     routerParameters.computeIfAbsent(router, k => new HashMap!(string, string)())
167                                     .putAll(parameters.get(router));
168                 }
169             }
170 
171         }
172     }
173 
174     // 405 Method Not Allowed
175     private Router handleStatus405() {
176         import hunt.http.routing.handler.Error405ResponseHandler;
177         Router r = new RouterImpl(idGenerator + 10, this);
178         // FIXME: Needing refactor or cleanup -@zhangxueping at 2020-08-03T16:35:05+08:00
179         // 
180         // r.handler(new Error405ResponseHandler());
181         
182         r.handler((context) {
183             renderErrorPage(context, HttpStatus.METHOD_NOT_ALLOWED_405, null);
184         });
185         return r;
186     }
187 
188     override Router register() {
189         return new RouterImpl(idGenerator++, this);
190     }
191 
192     override Router register(int id) {
193         return new RouterImpl(id, this);
194     }
195 
196     override void accept(HttpServerContext context) {
197         // TODO: Tasks pending completion -@zhangxueping at 2020-07-31T15:26:19+08:00
198         // Refactor this
199         HttpRequest request = context.httpRequest();
200         NavigableSet!(RouterMatchResult) routers = findRouter(
201                 request.getMethod(),
202                 request.getURI().getPath(),
203                 request.getFields().get(HttpHeader.CONTENT_TYPE),
204                 request.getFields().get(HttpHeader.ACCEPT));
205         RoutingContext routingContext = new RoutingContextImpl(context, routers);
206         routingContext.next();
207     }
208 }