001/**
002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.jboot.components.gateway;
017
018import io.jboot.core.spi.JbootSpiLoader;
019import io.jboot.utils.ArrayUtil;
020import io.jboot.utils.ClassUtil;
021import io.jboot.utils.StrUtil;
022
023import javax.servlet.http.HttpServletRequest;
024import java.io.Serializable;
025import java.util.*;
026
027/**
028 * @author michael yang (fuhai999@gmail.com)
029 * @Date: 2020/3/22
030 */
031public class JbootGatewayConfig implements Serializable {
032
033    public static final String DEFAULT_PROXY_CONTENT_TYPE = "text/html;charset=utf-8";
034    public static final GatewayInterceptor[] EMPTY_GATEWAY_INTERCEPTOR_ARRAY = new GatewayInterceptor[0];
035
036    private String name;
037    private Set<String> uri;
038
039
040    // 是否启用健康检查
041    private boolean uriHealthCheckEnable;
042
043    // URI 健康检查路径,要求服务 statusCode = 200
044    // 当配置 uriHealthCheckPath 后,健康检查的 url 地址为 uri + uriHealthCheckPath
045    private String uriHealthCheckPath;
046
047    // 是否启用
048    private boolean enable = false;
049
050    // 是否启用 sentinel 限流
051    private boolean sentinelEnable = false;
052    // sentinel 被限流后跳转地址
053    private String sentinelBlockPage;
054    // sentinel 被渲染的json内容,如果配置 sentinelBlockPage,则 sentinelBlockJsonMap 配置无效
055    private Map<String, String> sentinelBlockJsonMap;
056
057    // 设置 http 代理的参数
058    private int proxyReadTimeout = 10000; //10s
059    private int proxyConnectTimeout = 5000; //5s
060    private int proxyRetries = 2; //2 times
061    private String proxyContentType = DEFAULT_PROXY_CONTENT_TYPE;
062
063
064    private String[] pathEquals;
065    private String[] pathContains;
066    private String[] pathStartsWith;
067    private String[] pathEndsWith;
068
069
070    private String[] hostEquals;
071    private String[] hostContains;
072    private String[] hostStartsWith;
073    private String[] hostEndsWith;
074
075
076    private Map<String, String> queryEquals;
077    private String[] queryContains;
078
079    //拦截器配置,一般可以用于对请求进行 鉴权 等处理
080    private String[] interceptors;
081    private String loadBalanceStrategy;
082
083//    暂时不支持 cookie
084//    private Map<String, String> cookieEquals;
085//    private String[] cookieContains;
086
087    //不健康的 URI 地址
088    private Set<String> unHealthUris = Collections.synchronizedSet(new HashSet<>());
089
090
091    public String getName() {
092        return name;
093    }
094
095    public void setName(String name) {
096        this.name = name;
097    }
098
099
100    public Set<String> getUri() {
101        return uri;
102    }
103
104    public void setUri(Set<String> uri) {
105        this.uri = uri;
106    }
107
108    //服务发现的 URI 列表
109    private Set<String> discoveryUris;
110
111    //健康的 URI 缓存
112    private String[] healthUris;
113
114    //健康 URI 是否有变化的标识
115    private boolean healthUriChanged = true;
116
117    public String[] getHealthUris() {
118        if (healthUriChanged) {
119            synchronized (this) {
120                if (healthUriChanged) {
121                    if ((uri == null || uri.isEmpty()) && (discoveryUris == null || discoveryUris.isEmpty())) {
122                        healthUris = null;
123                    } else {
124                        HashSet<String> healthUriSet = new HashSet<>();
125                        if (uri != null && !uri.isEmpty()) {
126                            healthUriSet.addAll(uri);
127                        }
128
129                        if (discoveryUris != null && !discoveryUris.isEmpty()) {
130                            healthUriSet.addAll(discoveryUris);
131                        }
132
133                        if (!unHealthUris.isEmpty()) {
134                            healthUriSet.removeAll(unHealthUris);
135                        }
136                        healthUris = healthUriSet.isEmpty() ? null : healthUriSet.toArray(new String[healthUriSet.size()]);
137
138                    }
139                    healthUriChanged = false;
140                }
141            }
142        }
143        return healthUris;
144    }
145
146    public boolean isUriHealthCheckEnable() {
147        return uriHealthCheckEnable;
148    }
149
150    public void setUriHealthCheckEnable(boolean uriHealthCheckEnable) {
151        this.uriHealthCheckEnable = uriHealthCheckEnable;
152    }
153
154    public String getUriHealthCheckPath() {
155        return uriHealthCheckPath;
156    }
157
158    public void setUriHealthCheckPath(String uriHealthCheckPath) {
159        this.uriHealthCheckPath = uriHealthCheckPath;
160    }
161
162
163    public boolean isEnable() {
164        return enable;
165    }
166
167    public void setEnable(boolean enable) {
168        this.enable = enable;
169    }
170
171    public boolean isSentinelEnable() {
172        return sentinelEnable;
173    }
174
175    public void setSentinelEnable(boolean sentinelEnable) {
176        this.sentinelEnable = sentinelEnable;
177    }
178
179    public String getSentinelBlockPage() {
180        return sentinelBlockPage;
181    }
182
183    public void setSentinelBlockPage(String sentinelBlockPage) {
184        this.sentinelBlockPage = sentinelBlockPage;
185    }
186
187    public Map<String, String> getSentinelBlockJsonMap() {
188        return sentinelBlockJsonMap;
189    }
190
191    public void setSentinelBlockJsonMap(Map<String, String> sentinelBlockJsonMap) {
192        this.sentinelBlockJsonMap = sentinelBlockJsonMap;
193    }
194
195    public int getProxyReadTimeout() {
196        return proxyReadTimeout;
197    }
198
199    public void setProxyReadTimeout(int proxyReadTimeout) {
200        this.proxyReadTimeout = proxyReadTimeout;
201    }
202
203    public int getProxyConnectTimeout() {
204        return proxyConnectTimeout;
205    }
206
207    public void setProxyConnectTimeout(int proxyConnectTimeout) {
208        this.proxyConnectTimeout = proxyConnectTimeout;
209    }
210
211    public int getProxyRetries() {
212        return proxyRetries;
213    }
214
215    public void setProxyRetries(int proxyRetries) {
216        this.proxyRetries = proxyRetries;
217    }
218
219    public String getProxyContentType() {
220        return proxyContentType;
221    }
222
223    public void setProxyContentType(String proxyContentType) {
224        this.proxyContentType = proxyContentType;
225    }
226
227    public String[] getPathEquals() {
228        return pathEquals;
229    }
230
231    public void setPathEquals(String[] pathEquals) {
232        this.pathEquals = pathEquals;
233    }
234
235    public String[] getPathContains() {
236        return pathContains;
237    }
238
239    public void setPathContains(String[] pathContains) {
240        this.pathContains = pathContains;
241    }
242
243    public String[] getPathStartsWith() {
244        return pathStartsWith;
245    }
246
247    public void setPathStartsWith(String[] pathStartsWith) {
248        this.pathStartsWith = pathStartsWith;
249    }
250
251    public String[] getPathEndsWith() {
252        return pathEndsWith;
253    }
254
255    public void setPathEndsWith(String[] pathEndsWith) {
256        this.pathEndsWith = pathEndsWith;
257    }
258
259    public String[] getHostEquals() {
260        return hostEquals;
261    }
262
263    public void setHostEquals(String[] hostEquals) {
264        this.hostEquals = hostEquals;
265    }
266
267    public String[] getHostContains() {
268        return hostContains;
269    }
270
271    public void setHostContains(String[] hostContains) {
272        this.hostContains = hostContains;
273    }
274
275    public String[] getHostStartsWith() {
276        return hostStartsWith;
277    }
278
279    public void setHostStartsWith(String[] hostStartsWith) {
280        this.hostStartsWith = hostStartsWith;
281    }
282
283    public String[] getHostEndsWith() {
284        return hostEndsWith;
285    }
286
287    public void setHostEndsWith(String[] hostEndsWith) {
288        this.hostEndsWith = hostEndsWith;
289    }
290
291    public Map<String, String> getQueryEquals() {
292        return queryEquals;
293    }
294
295    public void setQueryEquals(Map<String, String> queryEquals) {
296        this.queryEquals = queryEquals;
297    }
298
299    public String[] getQueryContains() {
300        return queryContains;
301    }
302
303    public void setQueryContains(String[] queryContains) {
304        this.queryContains = queryContains;
305    }
306
307    public String[] getInterceptors() {
308        return interceptors;
309    }
310
311    public void setInterceptors(String[] interceptors) {
312        this.interceptors = interceptors;
313    }
314
315
316    private GatewayInterceptor[] gatewayInterceptors;
317
318    public GatewayInterceptor[] getGatewayInterceptors() {
319        if (gatewayInterceptors == null) {
320            if (interceptors == null || interceptors.length == 0) {
321                gatewayInterceptors = EMPTY_GATEWAY_INTERCEPTOR_ARRAY;
322            } else {
323                gatewayInterceptors = new GatewayInterceptor[interceptors.length];
324                for (int i = 0; i < interceptors.length; i++) {
325//                    GatewayInterceptor interceptor = ClassUtil.newInstance(interceptors[i]);
326                    GatewayInterceptor interceptor = JbootSpiLoader.load(GatewayInterceptor.class, interceptors[i]);
327                    if (interceptor == null) {
328                        throw new NullPointerException("can not new instance by class:" + interceptors[i]);
329                    }
330                    gatewayInterceptors[i] = interceptor;
331                }
332            }
333        }
334        return gatewayInterceptors;
335    }
336
337
338    public void setGatewayInterceptors(GatewayInterceptor[] gatewayInterceptors) {
339        this.gatewayInterceptors = gatewayInterceptors;
340    }
341
342
343    public String getLoadBalanceStrategy() {
344        return loadBalanceStrategy;
345    }
346
347    public void setLoadBalanceStrategy(String loadBalanceStrategy) {
348        this.loadBalanceStrategy = loadBalanceStrategy;
349    }
350
351    private GatewayLoadBalanceStrategy gatewayLoadBalanceStrategy;
352
353    public GatewayLoadBalanceStrategy buildLoadBalanceStrategy() {
354        if (gatewayLoadBalanceStrategy != null) {
355            return gatewayLoadBalanceStrategy;
356        }
357
358        if (StrUtil.isBlank(loadBalanceStrategy)) {
359            gatewayLoadBalanceStrategy = GatewayLoadBalanceStrategy.DEFAULT_STRATEGY;
360        } else {
361            GatewayLoadBalanceStrategy glbs = ClassUtil.newInstance(loadBalanceStrategy);
362            if (glbs == null) {
363                throw new NullPointerException("Can not new instance by class: " + loadBalanceStrategy);
364            }
365            gatewayLoadBalanceStrategy = glbs;
366        }
367
368        return gatewayLoadBalanceStrategy;
369    }
370
371    public void setGatewayLoadBalanceStrategy(GatewayLoadBalanceStrategy strategy) {
372        this.gatewayLoadBalanceStrategy = strategy;
373    }
374
375
376    public boolean matches(HttpServletRequest request) {
377        if (request == null) {
378            return false;
379        }
380
381        String path = request.getServletPath();
382        if (pathEquals != null) {
383            for (String p : pathEquals) {
384                if (path.equals(p)) {
385                    return true;
386                }
387            }
388        }
389
390        if (pathContains != null) {
391            for (String p : pathContains) {
392                if (path.contains(p)) {
393                    return true;
394                }
395            }
396        }
397
398        if (pathStartsWith != null) {
399            for (String p : pathStartsWith) {
400                if (path.startsWith(p)) {
401                    return true;
402                }
403            }
404        }
405
406        if (pathEndsWith != null) {
407            for (String p : pathEndsWith) {
408                if (path.endsWith(p)) {
409                    return true;
410                }
411            }
412        }
413
414        String host = request.getServerName();
415        if (hostEquals != null) {
416            for (String h : hostEquals) {
417                if (host.equals(h)) {
418                    return true;
419                }
420            }
421        }
422
423        if (hostContains != null) {
424            for (String h : hostContains) {
425                if (host.contains(h)) {
426                    return true;
427                }
428            }
429        }
430
431        if (hostStartsWith != null) {
432            for (String h : hostStartsWith) {
433                if (host.startsWith(h)) {
434                    return true;
435                }
436            }
437        }
438
439        if (hostEndsWith != null) {
440            for (String h : hostEndsWith) {
441                if (host.endsWith(h)) {
442                    return true;
443                }
444            }
445        }
446
447        if (queryContains != null || queryEquals != null) {
448            Map<String, String> queryMap = StrUtil.queryStringToMap(request.getQueryString());
449            if (!queryMap.isEmpty()) {
450
451                if (queryContains != null) {
452                    for (String q : queryContains) {
453                        if (queryMap.containsKey(q)) {
454                            return true;
455                        }
456                    }
457                }
458
459                if (queryEquals != null) {
460                    for (Map.Entry<String, String> e : queryEquals.entrySet()) {
461                        String queryValue = queryMap.get(e.getKey());
462                        if (Objects.equals(queryValue, e.getValue())) {
463                            return true;
464                        }
465                    }
466                }
467            }
468        }
469
470
471        return false;
472    }
473
474    public void syncDiscoveryUris(Collection<String> syncUris) {
475        if (ArrayUtil.isSameElements(syncUris, discoveryUris)) {
476            return;
477        }
478
479        if (syncUris == null || syncUris.isEmpty()) {
480            discoveryUris = null;
481            healthUriChanged = true;
482            return;
483        }
484
485        if (discoveryUris == null) {
486            discoveryUris = new HashSet<>(syncUris);
487            healthUriChanged = true;
488        } else {
489            discoveryUris.clear();
490            discoveryUris.addAll(syncUris);
491            healthUriChanged = true;
492        }
493    }
494
495
496    public void addUnHealthUri(String uri) {
497        if (unHealthUris.add(uri)) {
498            healthUriChanged = true;
499        }
500    }
501
502
503    public void removeUnHealthUri(String uri) {
504        if (unHealthUris.size() > 0) {
505            if (unHealthUris.remove(uri)) {
506                healthUriChanged = true;
507            }
508        }
509    }
510
511
512}