21-12-03
banq
在这个微服务世界中,我们总是强调通过 API/服务网关层传递任何 HTTP 请求,该层连接多个微服务,并有一个最低要求,即记录每个服务的所有请求和响应以获得更清晰的可见性。
我们可以考虑在以下场景中编写我们的反向代理层。
- 1.假设具有API服务像“PHP”或“Python”等另一种语言,我们需要以部分地变换该请求。
- 2. 添加授权或在服务未能响应请求时实现重试
- 3. 记录所有流经此反向代理层的请求和响应,稍后推送到某些日志记录堆栈,如 ELK
- 4. 为服务传播实现我们的自定义跟踪逻辑.
- 5. 或者重新发明轮子只是为了好玩
在这个示例中,我使用 Springboot 及其嵌入式 Tomcat 服务器。和 Spring Spring retry Dependency 来实现请求失败时的重试逻辑。
Springboot 控制器拦截所有请求并将它们传递给代理服务类:
@RestController public class ProxyController { @Autowired ProxyService service; @RequestMapping("/**") public ResponseEntity<String> sendRequestToSPM(@RequestBody(required = false) String body, HttpMethod method, HttpServletRequest request, HttpServletResponse response) throws URISyntaxException { return service.processProxyRequest(body,method,request,response, UUID.randomUUID().toString()); } } |
ProxySerivce:
当我们收到来自控制器的请求时,Simple RestTemplate 会向所需的域执行 HTTP 请求。这是可以扩展到我们的要求的类,例如执行自定义日志记录,或者在这个例子中实现重试,当 HTTP 调用失败时。
@Service public class ProxyService { String domain = "example.com"; private final static Logger logger = LogManager.getLogger(ProxyService.class); @Retryable(exclude = { HttpStatusCodeException.class}, include = Exception.class, backoff = @Backoff(delay = 5000, multiplier = 4.0), maxAttempts = 4) public ResponseEntity<String> processProxyRequest(String body, HttpMethod method, HttpServletRequest request, HttpServletResponse response, String traceId) throws URISyntaxException { ThreadContext.put("traceId", traceId); String requestUrl = request.getRequestURI(); //log if required in this line URI uri = new URI("https", null, domain, -1, null, null, null); // replacing context path form urI to match actual gateway URI uri = UriComponentsBuilder.fromUri(uri) .path(requestUrl) .query(request.getQueryString()) .build(true).toUri(); HttpHeaders headers = new HttpHeaders(); Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); headers.set(headerName, request.getHeader(headerName)); } headers.set("TRACE", traceId); headers.remove(HttpHeaders.ACCEPT_ENCODING); HttpEntity<String> httpEntity = new HttpEntity<>(body, headers); ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()); RestTemplate restTemplate = new RestTemplate(factory); try { ResponseEntity<String> serverResponse = restTemplate.exchange(uri, method, httpEntity, String.class); HttpHeaders responseHeaders = new HttpHeaders(); responseHeaders.put(HttpHeaders.CONTENT_TYPE, serverResponse.getHeaders().get(HttpHeaders.CONTENT_TYPE)); logger.info(serverResponse); return serverResponse; } catch (HttpStatusCodeException e) { logger.error(e.getMessage()); return ResponseEntity.status(e.getRawStatusCode()) .headers(e.getResponseHeaders()) .body(e.getResponseBodyAsString()); } } @Recover public ResponseEntity<String> recoverFromRestClientErrors(Exception e, String body, HttpMethod method, HttpServletRequest request, HttpServletResponse response, String traceId) { logger.error("retry method for the following url " + request.getRequestURI() + " has failed" + e.getMessage()); logger.error(e.getStackTrace()); throw new RuntimeException("There was an error trying to process you request. Please try again later"); } } 主类: @SpringBootApplication @EnableRetry public class ProxyAppApplication { public static void main(String[] args) { SpringApplication.run(ProxyAppApplication.class, args); } } pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.1</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.ashrithgn.example</groupId> <artifactId>proxyApp</artifactId> <version>0.0.1-SNAPSHOT</version> <name>proxyApp</name> <description>Demo project for Spring Boot</description> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> <version>1.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> |