springcloud中zuul的應用

zuul常用作網關服務,本例使用zuul作爲網關限流服務

一.pom.xml依賴

 

 <dependencyManagement> 
        <dependencies> 
            <dependency> 
                <groupId>org.springframework.cloud</groupId> 
                <artifactId>spring-cloud-netflix</artifactId> 
                <version>1.2.3.RELEASE</version> 
                <type>pom</type> 
                <scope>import</scope> 
            </dependency> 
        </dependencies> 
    </dependencyManagement>

        <dependency> 
            <groupId>org.springframework.cloud</groupId> 
            <artifactId>spring-cloud-starter-zuul</artifactId> 
        </dependency>

 

二.註冊zuul過濾器

package zuul;

import java.io.IOException;
import java.util.Enumeration;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.filters.FilterRegistry;

/**
 * Created by Jack on 2016/11/22.
 */

@EnableZuulProxy
@SpringBootApplication
@Import({
    XdiamondApplication.class
})
public class AppGateway {
    private static final Logger logger = LoggerFactory.getLogger(AppGateway.class);
    private static final String OAUTH_SERVER = "/oauthserver";

    public static void main(String[] args) {
        SpringApplication.run(AppGateway.class, args);
    }

    @Bean
    public WebMvcConfigurer corsConfigurer() {  // 允許跨域
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedHeaders("origin, content-type, accept, x-requested-with")
                        .allowedOrigins("*")
                        .allowedMethods("POST", "DELETE", "PUT", "GET", "OPTIONS");

            }
        };
    }

    @Component
    public static class MyCommandLineRunner implements CommandLineRunner {
        @Autowired
        private UrlLimit urlLimit;

        public void run(String... args) throws Exception {
            initJavaFilters();
        }

        private void initJavaFilters() {
            final FilterRegistry r = FilterRegistry.instance();

            r.put("javaPreFilter", new ZuulFilter() {
                @Override
                public int filterOrder() {
                    return 50000;
                }

                @Override
                public String filterType() {
                    return "pre";
                }

                public boolean shouldFilter() {
                    return true;
                }

                public Object run() {
                    try {
                        RequestContext ctx = RequestContext.getCurrentContext();
                        HttpServletRequest request = ctx.getRequest();
                        String authType = request.getAuthType();
                        String contextPath = request.getContextPath();
                        Enumeration<String> headerNames = request.getHeaderNames();

                        String method = request.getMethod();
                        String pathInfo = request.getPathInfo();
                        String queryString = request.getQueryString();
                        String sessionId = request.getRequestedSessionId();
                        String remoteUser = request.getRemoteUser();
                        String uri = request.getRequestURI();
                        String url = request.getRequestURL().toString();
                        String servletPath = request.getServletPath();
                        String forwardedFor = RequestContext.getCurrentContext().getZuulRequestHeaders().get("x-forwarded-for");
                        String forwardedHost = RequestContext.getCurrentContext().getZuulRequestHeaders().get("x-forwarded-host");
                        String os = request.getParameter("os");

                        logger.info("running javaPreFilter");
                        logger.info("authType:" + authType +
                                     " contextPath:" + contextPath +
                                     " method:" + method + " pathInfo:" + pathInfo +
                                     " queryString:" + queryString + " sessionId:" + sessionId + " remoteUser:" + remoteUser+
                                     " uri:" + uri +   " servletPath:" + servletPath +" url:" + url +
                                     " headerNames:"+headerNames +
                                     " forwardedFor:"+forwardedFor+" forwardedHost:"+forwardedHost + " os:" + os);
                        logger.info(RequestContext.getCurrentContext().getZuulRequestHeaders().toString());

                        if("OPTIONS".equals(method)) {  //web端加head之前的探測請求
                            logger.info("web options request allow pass.");
                            return null;
                        }

                        //通過計算次數來判斷請求是否需要限制
                        if(urlLimit.needLimit(ctx)){
                            ctx.setSendZuulResponse(false);
                            ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());

                            logger.info("AuthorizePreFilter : the request had been limited");
                        }else {
                            /**處理token的邏輯, 需要在url中加入oauthserver
                             * 如:https://api.998jk.com/analyzer/brs/df/detail?type=1&userId=1657110
                             * 最終變爲https://api.998jk.com/oauthserver/analyzer/brs/df/detail?type=1&userId=1657110
                             */
                            String newUrl = request.getScheme() + "://" + forwardedHost + OAUTH_SERVER + request.getRequestURI();
                            newUrl = "https://api.998jk.com/oauthserver/analyzer/brs/df/detail?type=1&userId=1657110";
                            logger.info("new url is :" + newUrl);

                            int status = 0;
                            try {
                                HttpHead httpHead = new HttpHead(newUrl);
                                httpHead.setHeader("Authorization",request.getHeader("Authorization"));
                                httpHead.setHeader("Content-Type", "application/json");

                                HttpResponse returnResp = HttpClients.createDefault().execute(httpHead);
                                status = returnResp.getStatusLine().getStatusCode();
                            } catch (IOException e) {
                                logger.error("get oauthserver response error :" + e);
                            }

                            if(status == HttpServletResponse.SC_UNAUTHORIZED) { //token沒有驗證通過
                                ctx.setSendZuulResponse(false);
                                ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());

                                logger.info("AuthorizePreFilter : the request has no auth");
                            }else {
                                logger.info("AuthorizePreFilter : the request is passed. and the status is :" + status);
                            }
                        }
                    }
                    catch(Exception e){
                        logger.info("error:", e);
                        e.printStackTrace();
                    }
                    return null;
                }
            });
        }
    }

}

 

用redis記錄訪問次數,進行限流

package zuul;

import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import com.netflix.zuul.context.RequestContext;

import io.github.xdiamond.client.XDiamondConfig;
import io.github.xdiamond.client.event.AllKeyListener;
import io.github.xdiamond.client.event.ConfigEvent;

/**
* @author mhl
* @version 創建時間:2017年3月8日
* 
*/
@Component
public class UrlLimit {
    private static final Logger logger = LoggerFactory.getLogger(UrlLimit.class);

    @SuppressWarnings("rawtypes")
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private XDiamondConfig xDiamondConfig;

    private static final String BLOCKED_IP = "blockedip";
    private static final String LIMIT_COUNT = "limitCount";
    private static final String REQUEST_TIME = "requestTime";
    private static final String BLACK_LIST_TIME = "blackListTime";

    private int limitCount = 5;  //請求次數限制

    private int requestTime = 1;  //請求次數時限(秒)

    private int blackListTime = 7; //黑名單時長(天)

    /**
     * 通過請求次數來做限制
     * mhl 2017年3月8日
     */
    public boolean needLimit(RequestContext context) {
        boolean result = false;
        HttpServletRequest request = context.getRequest();
        String url = request.getRequestURI();  //訪問url
        String forwardedFor = context.getZuulRequestHeaders().get("x-forwarded-for");  //IP

        if(forwardedFor == null) {
            return false;
        }

        if(redisTemplate.hasKey(forwardedFor)) {
            //黑名單需要限制
            result = true;
            logger.info("[UrlLimit:calculate] the ip is in blackList");

        }else {  //檢查訪問頻次
            String key = forwardedFor + "_" + url;
            Long count = redisTemplate.opsForValue().increment(key, 1);

            if (count == 1) {
                requestTime = getConfigInt(REQUEST_TIME, 1);
                redisTemplate.expire(key, requestTime, TimeUnit.SECONDS);
            }

            limitCount = getConfigInt(LIMIT_COUNT, limitCount);

            if(count > limitCount) {
                result = true;
                logger.info("[UrlLimit:calculate] the ip called too many times: " + key);
            }
        }

        return result;
    }

    /**
     * 從配置文件讀取ip 批量加入黑名單
     * mhl 2017年3月9日
     */
    @PostConstruct
    public void batchAddBlacklist() {
        String blockedIps = getConfig(BLOCKED_IP);
        if(blockedIps == null || blockedIps.length() == 0) {
            logger.info("UrlLimit | blockedIps is null or has no data");
            return;
        }
        String[] array = blockedIps.split(",");
        if(array.length > 0) {
            for (String string : array) {
                addBlacklist(string);
            }
        }

        logger.info("UrlLimit | blockedIps successed");
    }

    public String getConfig(String key) {
        if(xDiamondConfig.getProperties() == null || xDiamondConfig.getProperties().isEmpty()) {
            init();
        }
        return xDiamondConfig.getProperty(key);
    }

    public int getConfigInt(String key, int defaultInt) {
        int returnInt = defaultInt;
        if(xDiamondConfig.getProperties() == null || xDiamondConfig.getProperties().isEmpty()) {
            init();
        }
        String keyStr = xDiamondConfig.getProperty(key);
        if (StringUtils.isNotBlank(keyStr)){
            returnInt = Integer.parseInt(keyStr);
        }
        return returnInt;
    }

    /**
     * 初始化xDiamondConfig 並且增加監聽事件
     * mhl 2017年3月14日
     */
    private void init() {
        xDiamondConfig.init();
        xDiamondConfig.addAllKeyListener(new AllKeyListener() {
            public void onConfigEvent(ConfigEvent configevent) {
                String key = configevent.getKey();
                if(key.equals(BLOCKED_IP)) {
                    String newBlackIpStr = configevent.getValue();
                    if(newBlackIpStr != null) {
                        String[] array = newBlackIpStr.split(",");
                        if(array.length > 0) {
                            for (String string : array) {
                                addBlacklist(string);
                            }
                        }
                    }

                    logger.info("UrlLimit | blackListListener successed");

                }else if(key.equals(LIMIT_COUNT)) {
                    limitCount = Integer.valueOf(configevent.getValue());
                    logger.info("UrlLimit | limitCount value changed " + limitCount);
                }else if(key.equals(REQUEST_TIME)) {
                    requestTime = Integer.valueOf(configevent.getValue());
                    logger.info("UrlLimit | requestTime value changed " + requestTime);
                }else if(key.equals(BLACK_LIST_TIME)) {
                    blackListTime = Integer.valueOf(configevent.getValue());
                    logger.info("UrlLimit | blackListTime value changed " + blackListTime);
                }
            }
        });
    }

    /**
     * 手動添加黑名單
     * mhl 2017年3月9日
     * @param ip
     * @return
     */
    public boolean addBlacklist(String key) {
        redisTemplate.opsForSet().add(key, key);

        String blackListTimeStr = getConfig(BLACK_LIST_TIME);
        if(blackListTimeStr != null) {
            blackListTime = Integer.valueOf(blackListTimeStr);
        }

        redisTemplate.expire(key, blackListTime, TimeUnit.DAYS);

        return true;
    }

    /**
     * 手動移除黑名單
     * mhl 2017年3月9日
     * @param ip
     * @return
     */
    public boolean removeBlacklist(String key) {
        redisTemplate.delete(key);

        return true;
    }

    /**
     * 判斷key是否在redis中
     * mhl 2017年3月9日
     * @param key
     * @return
     */
    public boolean inRadis(String key) {
        return redisTemplate.hasKey(key);
    }
}

 

--------------


spring mvc+tomcat源碼分析視頻(鏈接複製在瀏覽器打開)

https://study.163.com/course/courseMain.htm?share=2&shareId=480000001919582&courseId=1209399899&_trace_c_p_k2_=6d81bc445e9c462ab8d6345e40f6b0bf

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章