溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何進(jìn)行Spring MVC框架集成本地HTTP請(qǐng)求和Spring Cloud RPC請(qǐng)求

發(fā)布時(shí)間:2021-10-12 15:11:56 來(lái)源:億速云 閱讀:111 作者:柒染 欄目:大數(shù)據(jù)

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)如何進(jìn)行Spring MVC框架集成本地HTTP請(qǐng)求和Spring Cloud RPC請(qǐng)求,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

請(qǐng)求區(qū)分
  • 請(qǐng)求路徑,比如:uri加/rpc前綴用來(lái)標(biāo)識(shí)RPC請(qǐng)求

  • 請(qǐng)求頭信息,比如:Accept:application/sc-rpc 用來(lái)標(biāo)識(shí)RPC請(qǐng)求

輸入?yún)?shù)和響應(yīng)內(nèi)容

方式一(舊):

對(duì)Spring MVC的消息轉(zhuǎn)換進(jìn)行封裝:

  • 輸入(@RequestBody): 重寫com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter#read方法,對(duì)本地請(qǐng)求和RPC請(qǐng)求做兼容。

    @Override
    public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException {
        try {
            // transform inputStream to string
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            IOUtils.copy(inputMessage.getBody(), byteArrayOutputStream);
            String str = byteArrayOutputStream.toString(StandardCharsets.UTF_8.name());
            // parse json object
            JSONObject jsonObject = JSON.parseObject(str, super.getFastJsonConfig().getFeatures());

            // if RPC, transform the data format
            if (jsonObject.containsKey("data")) {
                return JSON.parseObject(jsonObject.getString("data"), type, super.getFastJsonConfig().getFeatures());
            }
            // otherwise, call super method
            return readType(super.getType(type, contextClass), new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
        } catch (JSONException ex) {
            throw new HttpMessageNotReadableException("JSON parse error: " + ex.getMessage(), ex);
        } catch (IOException ex) {
            throw new IOException("I/O error while reading input message", ex);
        }
    }

    private Object readType(Type type, InputStream in) {
        try {
            return JSON.parseObject(in,
                    super.getFastJsonConfig().getCharset(),
                    type,
                    super.getFastJsonConfig().getParserConfig(),
                    super.getFastJsonConfig().getParseProcess(),
                    JSON.DEFAULT_PARSER_FEATURE,
                    super.getFastJsonConfig().getFeatures());
        } catch (JSONException ex) {
            throw new HttpMessageNotReadableException("JSON parse error: " + ex.getMessage(), ex);
        } catch (IOException ex) {
            throw new HttpMessageNotReadableException("I/O error while reading input message", ex);
        }
    }
  • 輸出(@ResponseBody):
    重寫com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter#writeInternal方法,本地請(qǐng)求和RPC請(qǐng)求的數(shù)據(jù)格式保持一致。

package com.caiya.web.base;

import com.alibaba.fastjson.JSONPObject;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.caiya.web.constant.CommonConstant;
import com.google.common.base.Joiner;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.HttpMessageNotWritableException;

import java.io.IOException;

/**
 * fastjson消息轉(zhuǎn)換器.
 */
public class ExtendedFastJsonHttpMessageConverter extends FastJsonHttpMessageConverter {

    @Override
    protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        super.writeInternal(wrapResult(object), outputMessage);
    }

    private Object wrapResult(Object object) {
        // 防止json請(qǐng)求重復(fù)包裝
        if (object instanceof ResponseDataWrapper) {
            return object;
        }

        if (object instanceof JSONPObject) {
            JSONPObject jsonpObject = (JSONPObject) object;
            JSONPObject newJsonpObject = new JSONPObject(jsonpObject.getFunction());
            ResponseDataWrapper data;
            if (jsonpObject.getParameters().size() == 1) {
                // 防止jsonp請(qǐng)求重復(fù)包裝
                if (jsonpObject.getParameters().get(0) instanceof ResponseDataWrapper) {
                    return object;
                }
                data = ResponseDataWrapperBuilder.build(jsonpObject.getParameters().get(0));
            } else if (jsonpObject.getParameters().size() > 1) {
                data = ResponseDataWrapperBuilder.build(Joiner.on(",").join(jsonpObject.getParameters()));
            } else {
                data = ResponseDataWrapperBuilder.build(CommonConstant.PLACEHOLDER_OBJECT_EMPTY);
            }
            newJsonpObject.addParameter(data);
            return newJsonpObject;
        }

        return ResponseDataWrapperBuilder.build(object);
    }

}

方式二:

  • 輸入:
    不需要處理,RPC請(qǐng)求時(shí)指定Accept即可:

package com.caiya.test.spring.cloud.configuration;

import org.apache.http.HttpHost;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Collections;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

@Configuration
public class FeignConfig {
    @Bean
    public HttpClient httpClient() {
        System.out.println("init feign httpclient configuration 1111");
        // 生成默認(rèn)請(qǐng)求配置
        RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
        // 超時(shí)時(shí)間
        requestConfigBuilder.setSocketTimeout(5 * 1000);
        // 連接時(shí)間
        requestConfigBuilder.setConnectTimeout(5 * 1000);

        // 設(shè)置代理
//        requestConfigBuilder.setProxy(new HttpHost("127.0.0.1", 8880));

        RequestConfig defaultRequestConfig = requestConfigBuilder.build();
        // 連接池配置
        // 長(zhǎng)連接保持30秒
        final PoolingHttpClientConnectionManager pollingConnectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.MILLISECONDS);
        // 總連接數(shù)
        pollingConnectionManager.setMaxTotal(5000);
        // 同路由的并發(fā)數(shù)
        pollingConnectionManager.setDefaultMaxPerRoute(100);

        // httpclient 配置
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        // 保持長(zhǎng)連接配置,需要在頭添加Keep-Alive
        httpClientBuilder.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy());
        httpClientBuilder.setConnectionManager(pollingConnectionManager);
        httpClientBuilder.setDefaultRequestConfig(defaultRequestConfig);
        httpClientBuilder.setDefaultHeaders(Collections.singleton(new BasicHeader("Accept", "application/sc-rpc")));
        HttpClient client = httpClientBuilder.build();


        // 啟動(dòng)定時(shí)器,定時(shí)回收過(guò)期的連接
        /*Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                //        System.out.println("=====closeIdleConnections===");
                pollingConnectionManager.closeExpiredConnections();
                pollingConnectionManager.closeIdleConnections(5, TimeUnit.SECONDS);
            }
        }, 10 * 1000, 5 * 1000);*/
        System.out.println("===== Apache httpclient 初始化連接池===");

        return client;
    }
}
  • 輸出:
    根據(jù)mediaType區(qū)分消息轉(zhuǎn)換。

package com.caiya.web.configuration;

import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.caiya.web.base.ExtendedFastJsonHttpMessageConverter;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;

import java.util.Arrays;
import java.util.Collections;

/**
 * fastjson消息轉(zhuǎn)換器配置.
 *
 * @author caiya
 * @since 1.0
 */
@Configuration
public class FastJsonConfiguration {

    @Bean
    public HttpMessageConverters extendedFastJsonHttpMessageConverter() {
        // for web controller
        FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new ExtendedFastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
//      fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue);
        fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
        fastJsonHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON_UTF8));
        // for web resource(Spring Cloud RPC)
        FastJsonHttpMessageConverter fastJsonHttpMessageConverterPlain = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfigPlain = new FastJsonConfig();
        fastJsonConfigPlain.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
//      fastJsonConfigPlain.setSerializerFeatures(SerializerFeature.WriteMapNullValue);
        fastJsonHttpMessageConverterPlain.setFastJsonConfig(fastJsonConfigPlain);
        fastJsonHttpMessageConverterPlain.setSupportedMediaTypes(Collections.singletonList(MediaType.valueOf("application/sc-rpc")));
        return new HttpMessageConverters(fastJsonHttpMessageConverter, fastJsonHttpMessageConverterPlain);
    }

}

方式三

在每個(gè)controller的method,返回最終響應(yīng)內(nèi)容。

方式四

通過(guò)aop處理不同的請(qǐng)求(RPC請(qǐng)求不處理即可):

package com.caiya.web.base;

import com.alibaba.fastjson.JSONPObject;
import com.caiya.web.constant.CommonConstant;
import com.google.common.base.Joiner;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 對(duì)響應(yīng)內(nèi)容的包裝處理AOP.
 */
@Component
@Aspect
public class ResponseDataWrapperAspect {

    @Pointcut("execution(* com.caiya.web.controller.rest..*(..))")
    public void aspect() {
    }

    @Around("aspect()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object object = joinPoint.proceed(joinPoint.getArgs());
        return wrapResult(object);
    }

    private Object wrapResult(Object object) {
        // 防止json請(qǐng)求重復(fù)包裝
        if (object instanceof ResponseDataWrapper) {
            return object;
        }

        if (object instanceof JSONPObject) {
            JSONPObject jsonpObject = (JSONPObject) object;
            JSONPObject newJsonpObject = new JSONPObject(jsonpObject.getFunction());
            ResponseDataWrapper data;
            if (jsonpObject.getParameters().size() == 1) {
                // 防止jsonp請(qǐng)求重復(fù)包裝
                if (jsonpObject.getParameters().get(0) instanceof ResponseDataWrapper) {
                    return object;
                }
                data = ResponseDataWrapperBuilder.build(jsonpObject.getParameters().get(0));
            } else if (jsonpObject.getParameters().size() > 1) {
                data = ResponseDataWrapperBuilder.build(Joiner.on(",").join(jsonpObject.getParameters()));
            } else {
                data = ResponseDataWrapperBuilder.build(CommonConstant.PLACEHOLDER_OBJECT_EMPTY);
            }
            newJsonpObject.addParameter(data);
            return newJsonpObject;
        }

        return ResponseDataWrapperBuilder.build(object);
    }

}
攔截器和過(guò)濾器

對(duì)RPC請(qǐng)求都不必?cái)r截,放行處理(包括會(huì)話攔截器、權(quán)限攔截器、XSS過(guò)濾器等)

nginx

RPC請(qǐng)求只允許通過(guò)Spring Cloud注冊(cè)中心、網(wǎng)關(guān)等調(diào)用[schema]://[ip]:[port]/[request_uri] 的形式 ,nginx 需要攔截路徑包含 /rpc/ 目錄的 RPC 接口調(diào)用(徹底隔離只需分離本地請(qǐng)求和RPC請(qǐng)求的應(yīng)用即可):

	location ~* /rpc/ {
            return 403;
        }
Spring Cloud 異常處理

服務(wù)端異常處理

推薦最外層包裹一層Result對(duì)象,表示接口執(zhí)行結(jié)果,包含經(jīng)過(guò)處理的異常信息

客戶端異常處理

  • 開啟feignclient的hystrix熔斷:

feign:
  hystrix:
    enabled: true
  • 實(shí)現(xiàn)接口feign.codec.ErrorDecoder,處理服務(wù)端異常:

package com.caiya.web.configuration;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.netflix.hystrix.exception.HystrixBadRequestException;
import feign.Response;
import feign.Util;
import feign.codec.ErrorDecoder;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

/**
 * Spring Cloud(feign with hystrix)自定義異常處理.
 *
 * @see FeignClientsConfiguration.HystrixFeignConfiguration#feignHystrixBuilder() 開啟hystrix入口
 * @see HystrixBadRequestException 此異常類型不會(huì)進(jìn)行熔斷操作
 * HystrixBadRequestException.message:
 * {"path":"/rpc/session/user/info","error":"Internal Server Error","message":"Illegal Agument Exception..","timestamp":1540266379459,"status":500}
 */
@Configuration
public class FeignErrorDecoder implements ErrorDecoder {

    private static final Logger logger = LoggerFactory.getLogger(FeignErrorDecoder.class);

    @Override
    public Exception decode(String methodKey, Response response) {
        String message = null;
        if (response.status() >= 400 && response.status() <= 500) {
            try {
                if (response.body() != null) {
                    String body = Util.toString(response.body().asReader());
                    try {
                        JSONObject jsonObject = JSON.parseObject(body);
                        if (jsonObject.containsKey("data")) {
                            JSONObject content = jsonObject.getJSONObject("data");
                            if (StringUtils.isNotBlank(content.getString("message"))) {
                                message = content.getString("message");
                                if ("connect timed out".equals(message)) {
                                    return feign.FeignException.errorStatus(methodKey, response);
                                }
                            } else {
                                message = content.getString("error");
                            }
                        }
                        if (message == null) {
                            message = jsonObject.getString("message");
                        }
                    } catch (Exception e) {
                        logger.error(e.getMessage(), e);
                        message = body;
                    }
                }
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
            }
            return new HystrixBadRequestException(message);
        }
        return feign.FeignException.errorStatus(methodKey, response);
    }
}
  • 處理超時(shí)異常,實(shí)現(xiàn)@FeignClient注解中的屬性fallback或fallbackFactory的相關(guān)類,進(jìn)行熔斷處理

其他補(bǔ)充

如何進(jìn)行Spring MVC框架集成本地HTTP請(qǐng)求和Spring Cloud RPC請(qǐng)求

上述就是小編為大家分享的如何進(jìn)行Spring MVC框架集成本地HTTP請(qǐng)求和Spring Cloud RPC請(qǐng)求了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI