这期内容当中小编将会给大家带来有关如何进行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; @Configuration public 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 */ @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); } }
在每个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 @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请求重复包装 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过滤器等)
RPC请求只允许通过Spring Cloud注册中心、网关等调用[schema]://[ip]:[port]/[request_uri] 的形式 ,nginx 需要拦截路径包含 /rpc/ 目录的 RPC 接口调用(彻底隔离只需分离本地请求和RPC请求的应用即可):
location ~* /rpc/ { return 403; }
推荐最外层包裹一层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} */ @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); } }
处理超时异常,实现@FeignClient注解中的属性fallback或fallbackFactory的相关类,进行熔断处理
上述就是小编为大家分享的如何进行Spring MVC框架集成本地HTTP请求和Spring Cloud RPC请求了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。