使用Spring Boot重試失敗編寫一個反向代理 - Ashrith

banq發表於2021-12-03

在這個微服務世界中,我們總是強調透過 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>



 

相關文章