您好,登錄后才能下訂單哦!
這期內容當中小編將會給大家帶來有關如何進行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進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。