千家信息网

如何进行Spring MVC框架集成本地HTTP请求和Spring Cloud RPC请求

发表于:2025-01-31 作者:千家信息网编辑
千家信息网最后更新 2025年01月31日,这期内容当中小编将会给大家带来有关如何进行Spring MVC框架集成本地HTTP请求和Spring Cloud RPC请求,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所
千家信息网最后更新 2025年01月31日如何进行Spring MVC框架集成本地HTTP请求和Spring Cloud RPC请求

这期内容当中小编将会给大家带来有关如何进行Spring MVC框架集成本地HTTP请求和Spring Cloud RPC请求,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。

请求区分
  • 请求路径,比如:uri加/rpc前缀用来标识RPC请求

  • 请求头信息,比如:Accept:application/sc-rpc 用来标识RPC请求

输入参数和响应内容

方式一(旧):

对Spring MVC的消息转换进行封装:

  • 输入(@RequestBody): 重写com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter#read方法,对本地请求和RPC请求做兼容。

    @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方法,本地请求和RPC请求的数据格式保持一致。

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消息转换器. */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请求重复包装        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请求重复包装                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请求时指定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;@Configurationpublic class FeignConfig {    @Bean    public HttpClient httpClient() {        System.out.println("init feign httpclient configuration 1111");        // 生成默认请求配置        RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();        // 超时时间        requestConfigBuilder.setSocketTimeout(5 * 1000);        // 连接时间        requestConfigBuilder.setConnectTimeout(5 * 1000);        // 设置代理//        requestConfigBuilder.setProxy(new HttpHost("127.0.0.1", 8880));        RequestConfig defaultRequestConfig = requestConfigBuilder.build();        // 连接池配置        // 长连接保持30秒        final PoolingHttpClientConnectionManager pollingConnectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.MILLISECONDS);        // 总连接数        pollingConnectionManager.setMaxTotal(5000);        // 同路由的并发数        pollingConnectionManager.setDefaultMaxPerRoute(100);        // httpclient 配置        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();        // 保持长连接配置,需要在头添加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();        // 启动定时器,定时回收过期的连接        /*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;    }}
  • 输出:
    根据mediaType区分消息转换。

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消息转换器配置. * * @author caiya * @since 1.0 */@Configurationpublic 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);    }}

方式三

在每个controller的method,返回最终响应内容。

方式四

通过aop处理不同的请求(RPC请求不处理即可):

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;/** * 对响应内容的包装处理AOP. */@Component@Aspectpublic 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请求重复包装        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请求重复包装                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请求都不必拦截,放行处理(包括会话拦截器、权限拦截器、XSS过滤器等)

nginx

RPC请求只允许通过Spring Cloud注册中心、网关等调用[schema]://[ip]:[port]/[request_uri] 的形式 ,nginx 需要拦截路径包含 /rpc/ 目录的 RPC 接口调用(彻底隔离只需分离本地请求和RPC请求的应用即可):

    location ~* /rpc/ {            return 403;        }
Spring Cloud 异常处理

服务端异常处理

推荐最外层包裹一层Result对象,表示接口执行结果,包含经过处理的异常信息

客户端异常处理

  • 开启feignclient的hystrix熔断:

feign:  hystrix:    enabled: true
  • 实现接口feign.codec.ErrorDecoder,处理服务端异常:

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 此异常类型不会进行熔断操作 * HystrixBadRequestException.message: * {"path":"/rpc/session/user/info","error":"Internal Server Error","message":"Illegal Agument Exception..","timestamp":1540266379459,"status":500} */@Configurationpublic 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);    }}
  • 处理超时异常,实现@FeignClient注解中的属性fallback或fallbackFactory的相关类,进行熔断处理

其他补充

上述就是小编为大家分享的如何进行Spring MVC框架集成本地HTTP请求和Spring Cloud RPC请求了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注行业资讯频道。

0