官方图

1.Servlet

zuul.servletPath默认配置为/zuul,故请求为/zuul开头的会跳过dispatcherServlet直接进入ZuulServlet,该配置可以自定义配置,例如用于大文件上传

2.ZuulServlet中service方法

  1. public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
  2. try {
  3. this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
  4. RequestContext context = RequestContext.getCurrentContext();
  5. context.setZuulEngineRan();
  6.  
  7. try {
  8. //运行pre过滤器
  9. this.preRoute();
  10. } catch (ZuulException var12) {
  11. //有异常,执行errorFilter
  12. this.error(var12);
  13. //再执行postFilter
  14. this.postRoute();
  15. return;
  16. }
  17.  
  18. try {
  19. //运行rote过滤器
  20. this.route();
  21. } catch (ZuulException var13) {
  22. //有异常,执行errorFilter
  23. this.error(var13);
  24. //再执行postFilter
  25. this.postRoute();
  26. return;
  27. }
  28.  
  29. try {
  30. //运行post过滤器
  31. this.postRoute();
  32. } catch (ZuulException var11) {
  33. //有异常,执行errorFilter
  34. this.error(var11);
  35. }
  36. } catch (Throwable var14) {
  37. this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
  38. } finally {
  39. RequestContext.getCurrentContext().unset();
  40. }
  41. }

3.FilterProcessor

其运行交由FilterProcessor中的方法runFilters,根据service中的顺序,取不同的filter类型,执行其中的run方法

  1. public Object runFilters(String sType) throws Throwable {
  2. if (RequestContext.getCurrentContext().debugRouting()) {
  3. Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
  4. }
  5.  
  6. boolean bResult = false;
  7. List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
  8. if (list != null) {
  9. for(int i = 0; i < list.size(); ++i) {
  10. ZuulFilter zuulFilter = (ZuulFilter)list.get(i);
  11. Object result = this.processZuulFilter(zuulFilter);//见下面zuulFilter的runFilter()
  12. if (result != null && result instanceof Boolean) {
  13. bResult |= ((Boolean)result).booleanValue();
  14. }
  15. }
  16. }
  17.  
  18. return bResult;
  19. }

zuulFilter的runFilter方法,当filter的shouldFilter()返回true时才执行run()方法

  1. public ZuulFilterResult runFilter() {
  2. ZuulFilterResult zr = new ZuulFilterResult();
  3. if (!this.isFilterDisabled()) {
  4. if (this.shouldFilter()) {
  5. Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
  6.  
  7. try {
  8. Object res = this.run();
  9. zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
  10. } catch (Throwable var7) {
  11. t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
  12. zr = new ZuulFilterResult(ExecutionStatus.FAILED);
  13. zr.setException(var7);
  14. } finally {
  15. t.stopAndLog();
  16. }
  17. } else {
  18. zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
  19. }
  20. }
  21.  
  22. return zr;
  23. }

4.获取过滤器FilterRegistry

其中的属性private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap();
保存所有的过滤器
例子中有12个(其中有两个为自定义的):

  1. [org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter@3dc68586,
  2. org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter@4001d8c1,
  3. org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter@60dc1a4e,
  4. org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter@7a2fce12,
  5. com.example.springbootzuul.filters.AuthFilter@14fc9bd,
  6. org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter@74960e9d,
  7. org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter@61037caf,
  8. org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter@3c88191b,
  9. org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter@670342a2,
  10. com.example.springbootzuul.filters.LoginPostFilter@7ed49a7f,
  11. org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter@357bc488,
  12. org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter@d5e575c]

5.各个filter作用

pre过滤器:

org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter:
该过滤器order值为-3,是pre阶段第一个过滤器,并且总是会运行。主要用途是判断该请求是被spring的DispatcherServlet处理还是被zuul的ZuulServlet处理,并且将判断结果设置到context中,后续处理中可以依照此结果进行个性化处理
org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter:
该过滤器order值为-2,是pre阶段第二个过滤器,并且总是会运行。Zuul默认仅对servlet2.5兼容,该过滤器可以将request包装成3.0兼容的形式
org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter:
该过滤器order值为-1,是pre阶段第三个过滤器,仅针对两类请求生效,第一种是Context-Type为application/x-www-form-urlencoded,第二种是由spring的DispatcherServlet处理的Context-Type为multipart/form-data的请求。该过滤器的主要目的是将上述两种请求包装成FormBodyRequestWrapper
org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter:
该过滤器order值为1,是pre阶段第四个过滤器,仅在请求参数中出现debug=true(参数名称可设置)时执行。具体执行逻辑就是在context中设置debugRouting=true及debugRequest=true。在后续执行中可以通过这两个值来预埋一些debug信息,用于出现问题时提供排查所需的信息
org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter:
该过滤器order值为5,是pre阶段最后一个过滤器,仅在forward.to和serviceId都没有出现在context中的时候才执行。具体来说就是对请求做一些预处理,包括使用RouteLocator获取路由信息,在context中设置一些后续处理需要的信息,还有就是在请求头中添加一些代理信息,比如X-Forwarded-For
com.example.springbootzuul.filters.AuthFilter 自定义的。。

route:

org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter:
该过滤器order值为10,是route阶段第一个过滤器,仅在context中存在serviceId的情况下运行。存在serviceId,就是说需要面向服务进行路由,服务的路由信息就是我们上面讲过的两种方式,配置文件(静态)及服务注册。具体就是创建RibbonCommandContext,然后交由ribbon和hystrix向下游服务进行请求
org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter:
该过滤器order值为100,是route阶段第二个过滤器,仅在context中存在routeHost的情况下运行。存在routeHost,就是说我们配置了具体的http或者https url的请求信息。具体逻辑就是通过HttpClient直接向目标url发起请求,不再经过ribbon及hystrix,所以也就没有负载均衡以及熔断
org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter:
该过滤器order值为500,是route阶段第三个(最后一个)过滤器,仅在context中存在forward.to的情况下运行。存在forward.to,就是说我们配置了类似forward:/index的请求信息。具体就是通过RequestDispatcher进行forward

post:

com.example.springbootzuul.filters.LoginPostFilter 自定义的。。
org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter:
该过滤器order值为1000,是post阶段最后一个过滤器,仅在context中存在zuulResponseHeaders、responseDataStream、responseBody(三者是或的关系)的情况下运行,简单来说,就是在有响应数据的时候运行。我们以responseBody举例,来看下responseBody是什么时候被设置到context中的。还记得RibbonRoutingFilter吧,在他的run方法中会调用一个setResponse方法,responseBody就是在这个方法中被设置到context中

error:

org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter@670342a2,
该过滤器order值为0,是error阶段唯一一个过滤器,仅在context中存在throwable的情况下运行,也就是说有异常产生的情况下运行。将错误状态码、错误信息、异常对象设置到request中,然后forward到/error(默认,可配置)。之后我们可以自己定义一个/error端口对错误进行响应

6.关于路由处理

6.1 Zuul在自动配置加载时注入了2个RouteLocator

CompositeRouteLocator:是 @Primary的,它是组合多个RouteLocator的Locator
DiscoveryClientRouteLocator:存放至CompositeRouteLocator的属性routeLocators中,当调用RouteLocator时会调用CompositeRouteLocator中的
DiscoveryClientRouteLocator中的locateRoutes方法运行后就已经加载了配置文件中所有路由信息,以及注册中心中的服务路由信息,有的通过URL路由,有的通过serviceId路由

  1. protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
  2. //保存ZuulRoute的LinkedHashMap
  3. LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
  4. //调用父类SimpleRouteLocator#locateRoutes()
  5. //加载ZuulProperties中的所有配置文件中的路由信息
  6. routesMap.putAll(super.locateRoutes());
  7. //如果服务发现客户端discovery存在
  8. if (this.discovery != null) {
  9. //将routesMap已经存在的配置文件中的ZuulRoute放入staticServices<serviceId, ZuulRoute>
  10. Map<String, ZuulRoute> staticServices = new LinkedHashMap<String, ZuulRoute>();
  11. for (ZuulRoute route : routesMap.values()) {
  12. String serviceId = route.getServiceId();
  13.  
  14. //如果serviceId为null,以id作为serviceId,此情况适合 zuul.routes.xxxx=/xxxx/** 的情况
  15. if (serviceId == null) {
  16. serviceId = route.getId();
  17. }
  18. if (serviceId != null) {
  19. staticServices.put(serviceId, route);
  20. }
  21. }
  22. // Add routes for discovery services by default
  23. List<String> services = this.discovery.getServices(); //到注册中心找到所有service
  24. String[] ignored = this.properties.getIgnoredServices()
  25. .toArray(new String[0]);
  26. //遍历services
  27. for (String serviceId : services) {
  28. // Ignore specifically ignored services and those that were manually
  29. // configured
  30. String key = "/" + mapRouteToService(serviceId) + "/**";
  31. //如果注册中心的serviceId在staticServices集合中,并且此路由没有配置URL
  32. //那么,更新路由的location为serviceId
  33. if (staticServices.containsKey(serviceId)
  34. && staticServices.get(serviceId).getUrl() == null) {
  35. // Explicitly configured with no URL, cannot be ignored
  36. // all static routes are already in routesMap
  37. // Update location using serviceId if location is null
  38. ZuulRoute staticRoute = staticServices.get(serviceId);
  39. if (!StringUtils.hasText(staticRoute.getLocation())) {
  40. staticRoute.setLocation(serviceId);
  41. }
  42. }
  43. //如果注册中心的serviceId不在忽略范围内,且routesMap中还没有包含,添加到routesMap
  44. //(例子中的配置#忽略所有服务 ignoredServices: '*')
  45. if (!PatternMatchUtils.simpleMatch(ignored, serviceId)
  46. && !routesMap.containsKey(key)) {
  47. // Not ignored
  48. routesMap.put(key, new ZuulRoute(key, serviceId));
  49. }
  50. }
  51. }
  52.  
  53. // 如果routesMap中有 /** 的默认路由配置
  54. if (routesMap.get(DEFAULT_ROUTE) != null) {
  55. ZuulRoute defaultRoute = routesMap.get(DEFAULT_ROUTE);
  56. // Move the defaultServiceId to the end
  57. routesMap.remove(DEFAULT_ROUTE);
  58. routesMap.put(DEFAULT_ROUTE, defaultRoute);
  59. }
  60. //将routesMap中的数据微调后,放到values<String, ZuulRoute>,返回
  61. LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
  62. for (Entry<String, ZuulRoute> entry : routesMap.entrySet()) {
  63. String path = entry.getKey();
  64. // Prepend with slash if not already present.
  65. if (!path.startsWith("/")) {
  66. path = "/" + path;
  67. }
  68. if (StringUtils.hasText(this.properties.getPrefix())) {
  69. path = this.properties.getPrefix() + path;
  70. if (!path.startsWith("/")) {
  71. path = "/" + path;
  72. }
  73. }
  74. values.put(path, entry.getValue());
  75. }
  76.  
  77. return values;
  78. }

6.2 路由前的预处理:PreDecorationFilter.run()

  1. public Object run() {
  2. RequestContext ctx = RequestContext.getCurrentContext();
  3. final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
  4. Route route = this.routeLocator.getMatchingRoute(requestURI); //如下.找到匹配的路由
  5. // ==== 匹配到路由信息
  6. if (route != null) {
  7. String location = route.getLocation();
  8. if (location != null) {
  9. /** //RequestContext设置 requestURI:路由的pattern路径
  10. //RequestContext设置 proxy:路由id
  11. //设置需要忽略的敏感头信息,要么用全局默认的,要么用路由自定义的
  12. //设置重试信息
  13. //如果location是 http/https开头的,RequestContext设置 routeHost:URL
  14. //如果location是 forward:开头的,RequestContext设置 forward信息、routeHost:null
  15. //其它 RequestContext设置 serviceId、routeHost:null、X-Zuul-ServiceId
  16. //是否添加代理头信息 X-Forwarded-For
  17. //是否添加Host头信息
  18. */
  19. }
  20. }
  21. // ==== 没有匹配到路由信息
  22. else {
  23. /**............*/
  24. }
  25. return null;
  26. }

RouteLocator.getMatchingRoute(requestURI)

  1. public Route getMatchingRoute(final String path) {
  2. return getSimpleMatchingRoute(path);
  3. }
  4.  
  5. protected Map<String, ZuulRoute> getRoutesMap() {
  6. if (this.routes.get() == null) {
  7. this.routes.set(locateRoutes());
  8. }
  9. return this.routes.get();
  10. }
  11.  
  12. protected Route getSimpleMatchingRoute(final String path) {
  13. //未初始化则初始化
  14. getRoutesMap();
  15. //获取准确的path:根据servlet的类型将path中的serveletPath截取掉
  16. String adjustedPath = adjustPath(path);
  17. //通过path匹配已初化的ZuulRoute
  18. ZuulRoute route = getZuulRoute(adjustedPath);
  19. //通过ZuulRoute 获取route
  20. return getRoute(route, adjustedPath);
  21. }

在获取Zuulroute后通过getRoute获取到最后的Route

  1. protected Route getRoute(ZuulRoute route, String path) {
  2. if (route == null) {
  3. return null;
  4. }
  5. String targetPath = path;
  6. //配置的前缀prefix
  7. String prefix = this.properties.getPrefix();
  8. if(prefix.endsWith("/")) {
  9. prefix = prefix.substring(0, prefix.length() - 1);
  10. }
  11. //访问path以前缀开头且配置截取前缀为true(不配置默认为true),截取前缀
  12. if (path.startsWith(prefix + "/") && this.properties.isStripPrefix()) {
  13. targetPath = path.substring(prefix.length());
  14. }
  15. //配置截取前缀为true(不配置默认为true)
  16. if (route.isStripPrefix()) {
  17. //路由path有通配符
  18. int index = route.getPath().indexOf("*") - 1;
  19. if (index > 0) {
  20. //最后的targetPath即为各个服务里的路径
  21. String routePrefix = route.getPath().substring(0, index);
  22. targetPath = targetPath.replaceFirst(routePrefix, "");
  23. prefix = prefix + routePrefix;
  24. }
  25. }
  26. //标记是否默认支持重试(未配置默认false)
  27. Boolean retryable = this.properties.getRetryable();
  28. if (route.getRetryable() != null) {
  29. retryable = route.getRetryable();
  30. }
  31. return new Route(route.getId(), targetPath, route.getLocation(), prefix,
  32. retryable,
  33. route.isCustomSensitiveHeaders() ? route.getSensitiveHeaders() : null,
  34. route.isStripPrefix());
  35. }

6.3 处理路由

RibbonRoutingFilter:使用Ribbon、Hystrix和可插入的http客户端发送请求

  1. public Object run() {
  2. RequestContext context = RequestContext.getCurrentContext();
  3. this.helper.addIgnoredHeaders();
  4. try {
  5. RibbonCommandContext commandContext = buildCommandContext(context);
  6. ClientHttpResponse response = forward(commandContext);
  7. setResponse(response);
  8. return response;
  9. }
  10. catch (ZuulException ex) {
  11. throw new ZuulRuntimeException(ex);
  12. }
  13. catch (Exception ex) {
  14. throw new ZuulRuntimeException(ex);
  15. }
  16. }

SimpleHostRoutingFilter:简单路由,通过HttpClient向预定的URL发送请求

  1. public Object run() {
  2. RequestContext context = RequestContext.getCurrentContext();
  3. HttpServletRequest request = context.getRequest();
  4. MultiValueMap<String, String> headers = this.helper
  5. .buildZuulRequestHeaders(request);
  6. MultiValueMap<String, String> params = this.helper
  7. .buildZuulRequestQueryParams(request);
  8. String verb = getVerb(request);
  9. InputStream requestEntity = getRequestBody(request);
  10. if (request.getContentLength() < 0) {
  11. context.setChunkedRequestBody();
  12. }
  13. String uri = this.helper.buildZuulRequestURI(request);
  14. this.helper.addIgnoredHeaders();
  15. try {
  16. CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
  17. headers, params, requestEntity);
  18. setResponse(response);
  19. }
  20. catch (Exception ex) {
  21. throw new ZuulRuntimeException(ex);
  22. }
  23. return null;
  24. }

SendForwardFilter:forward到本地URL

  1. public Object run() {
  2. try {
  3. RequestContext ctx = RequestContext.getCurrentContext();
  4. String path = (String) ctx.get(FORWARD_TO_KEY);
  5. RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher(path);
  6. if (dispatcher != null) {
  7. ctx.set(SEND_FORWARD_FILTER_RAN, true);
  8. if (!ctx.getResponse().isCommitted()) {
  9. dispatcher.forward(ctx.getRequest(), ctx.getResponse());
  10. ctx.getResponse().flushBuffer();
  11. }
  12. }
  13. }
  14. catch (Exception ex) {
  15. ReflectionUtils.rethrowRuntimeException(ex);
  16. }
  17. return null;
  18. }

7.其他重要组件

FilterRegistry:使用ConcurrentHashMap存储全部的filter
RequestContext:请求上下文对象,继承于ConcurrentHashMap<String, Object>,用于请求时的所有参数或其他信息,该对象放入线程中的,故在各个filter中都可获取到
//注意这里的ThreadLocal实例的initialValue()方法,当ThreadLocal的get()方法返回null的时候总是会调用initialValue()方法

  1. protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() {
  2. @Override
  3. protected RequestContext initialValue() {
  4. try {
  5. return contextClass.newInstance();
  6. } catch (Throwable e) {
  7. throw new RuntimeException(e);
  8. }
  9. }
  10. };
  11. public RequestContext() {
  12. super();
  13. }
  14. public static RequestContext getCurrentContext() {
  15. /**省略*/
  16. if (testContext != null) return testContext;
  17. //当ThreadLocal的get()方法返回null的时候总是会调用initialValue()方法,所以这里是"无则新建RequestContext"的逻辑
  18. RequestContext context = threadLocal.get();
  19. return context;
  20. }

参考资料:

  https://www.cnblogs.com/liangzs/p/8946740.html

  https://www.jianshu.com/p/2cc9e2ba2256

  

springcloud -zuul(2-执行流程及源码)的更多相关文章

  1. SpringMVC执行流程及源码分析

    SpringMVC流程及源码分析 前言 ​ 学了一遍SpringMVC以后,想着做一个总结,复习一下.复习写下面的总结的时候才发现,其实自己学的并不彻底.牢固.也没有学全,视频跟书本是要结合起来一起, ...

  2. django Rest Framework----APIView 执行流程 APIView 源码分析

    在django—CBV源码分析中,我们是分析的from django.views import View下的执行流程,这篇博客我们介绍django Rest Framework下的APIView的源码 ...

  3. Django 基于类的视图(CBV)执行流程 CBV 源码分析

    一.CBV(基于类的视图) 视图是可以调用的,它接受请求并返回响应,这不仅仅是一个函数,Django提供了一些可以用作视图的类的例子,这些允许您通过继承或mixin来构建视图并重用代码. 基本示例 D ...

  4. Android 全面插件化 RePlugin 流程与源码解析

    转自 Android 全面插件化 RePlugin 流程与源码解析 RePlugin,360开源的全面插件化框架,按照官网说的,其目的是“尽可能多的让模块变成插件”,并在很稳定的前提下,尽可能像开发普 ...

  5. Spark Streaming运行流程及源码解析(一)

    本系列主要描述Spark Streaming的运行流程,然后对每个流程的源码分别进行解析 之前总听同事说Spark源码有多么棒,咱也不知道,就是疯狂点头.今天也来撸一下Spark源码. 对Spark的 ...

  6. 【SpringCloud技术专题】「Eureka源码分析」从源码层面让你认识Eureka工作流程和运作机制(上)

    前言介绍 了解到了SpringCloud,大家都应该知道注册中心,而对于我们从过去到现在,SpringCloud中用的最多的注册中心就是Eureka了,所以深入Eureka的原理和源码,接下来我们要进 ...

  7. 服务网关zuul之二:过滤器--请求过滤执行过程(源码分析)

    Zuul的核心是一系列的过滤器,这些过滤器可以完成以下功能: 身份认证与安全:识别每个资源的验证要求,并拒绝那些与要求不符的请求. 审查与监控:在边缘位置追踪有意义的数据和统计结果,从而带来精确的生成 ...

  8. SpringCloud(4)---Ribbon服务调用,源码分析

    SpringCloud(4)---Ribbon 本篇模拟订单服务调用商品服务,同时商品服务采用集群部署. 注册中心服务端口号7001,订单服务端口号9001,商品集群端口号:8001.8002.800 ...

  9. Android应用层View绘制流程与源码分析

    1  背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原 ...

随机推荐

  1. react 16.3+ 新生命周期 作业

    1.有哪些⽣命周期被舍弃(3个),哪些⽣命 周期是新增(2个)? componentWillMount().componentWillReceiveProps().componentWillUpdat ...

  2. MySql进行批量插入时的几种sql写法

    insert into:插入数据,如果主键重复,则报错 insert repalce:插入替换数据,如果存在主键或unique数据则替换数据 insert ignore:如果存在数据,则忽略. INS ...

  3. docker build 时 alpine 无法安装软件问题的解决

    使用 alpine 作为 docker 基础镜像时,运行 apk add ..... 遇到如下错误: WARNING: Ignoring http://dl-cdn.alpinelinux.org/a ...

  4. 前端学习(十八)js的json(笔记)

    json: 数组:        1.有序        var arr=[2,1,3,4] arr[0]; 2.有length 3.普通for 4.下标数字 5.添加删除 splice json:  ...

  5. java反射技术主要实现类有哪些,作用分别是什么

    Java反射技术主要实现类有哪些,作用分别是什么? 在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中 1)Class类:代表一个类 2)Field 类 ...

  6. translation

    *过渡写到本体上(谁做动画写谁身上) transition transition-property  规定应用过渡的CSS属性的名称. transition-duration  定义过渡效果花费的时间 ...

  7. ECMAScript6 Promise

    Promise在Javascript中早就已经实现,在ECMAScript6中正式加入到标准.那么Promise到底是干什么的?怎么用? 一.Promise介绍 Promise是一个对象,用来传递异步 ...

  8. FTP的PORT和PASV的连接方式以及数据连接端口号计算

    FTP的PORT和PASV的连接方式以及数据连接端口号计算   PORT(自动)方法的连接途中是: 客户端向服务器的FTP端口(原始是21)发送连接请求,服务器领受连接,建立一条command链路. ...

  9. luoguP2709 小B的询问 [莫队]

    题目描述 小B有一个序列,包含N个1~K之间的整数.他一共有M个询问,每个询问给定一个区间[L..R],求Sigma(c(i)^2)的值,其中i的值从1到K,其中c(i)表示数字i在[L..R]中的重 ...

  10. Android中的onWindowFocusChanged()方法详解

    Android中获取手机屏幕的高度和宽度,我们知道在onCreate方法中获取到的值都是为0的,有人说可以在onClick方法中获取值,这个也是个方法 ,但在onWindowFocusChanged方 ...