Syntax highlighter header

Wednesday, 7 February 2024

Creating a proxy for other service in Spring Boot

Recently we had a requirement to translate from HTTP headers to query parameter to expose a service to a client which can't send query parameter to the service. The client could send the information as HTTP header although.

We decided to build it in our spring boot application using FeignClient. We accepted API name as path variable so that same code can work for multiple APIs.

Following is the code our FeignClient interface which accept API name as a path variable:

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(name = "genericClient", configuration = GenericClientConfiguration.class,
        url = "${base-url}" )
public interface GenericClient {
    String LOGIN_ID = "_loginid";
    String USER_TOKEN = "_token";

    @PostMapping(value = "/prefix/{apiName}", consumes = "application/json", produces = "application/json")
    ResponseEntity<String> callAPI( @PathVariable String apiName,
            @RequestParam(value = LOGIN_ID) String loginId,
            @RequestParam(value = USER_TOKEN) String userToken,
            @RequestBody String requestBody);
}

Following is the code for our controller class which receive the request from client. This controller catches errors returned by the server and passes them to the client.

import feign.FeignException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Optional;

@RestController
@Slf4j
@RequestMapping("/api/public")
public class ProxyController {

    @Autowired
    private GenericClient genericClient;

    @PostMapping(value = "/proxyPrefix/{api}", consumes = "application/json", produces = "application/json")
    public ResponseEntity<?> omsApiCall(@PathVariable String api,
                                           @RequestHeader String userName,
                                           @RequestHeader String userToken,
                                           @RequestBody String reqBody) {
        try {
            ResponseEntity<String> resp= genericClient.callAPI(api, userName, userToken, reqBody);
            log.info("Generic call successful API={}",api);
            return resp;
        } catch(FeignException.FeignClientException fce) {
            log.info("Generic call failed: API={}", api, fce);
            Optional<ByteBuffer> respBody = fce.responseBody();
            if(respBody.isPresent()) {
                String errorText = StandardCharsets.UTF_8.decode(respBody.get()).toString();
                return ResponseEntity.status(fce.status()).body(errorText);
            } else {
                return ResponseEntity.status(fce.status()).build();
            }
        } catch(FeignException.FeignServerException fse) {
            log.error("Generic call failed with server error for API={}", api, fse);
            Optional<ByteBuffer> respBody = fse.responseBody();
            if(respBody.isPresent()) {
                String errorText = StandardCharsets.UTF_8.decode(respBody.get()).toString();
                return ResponseEntity.status(fse.status()).body(errorText);
            } else {
                return ResponseEntity.status(fse.status()).build();
            }
        } catch(FeignException fe) {
            log.error("Generic call failed with unknown error for API={}", api, fe);
            Optional<ByteBuffer> respBody = fe.responseBody();
            if(respBody.isPresent()) {
                String errorText = StandardCharsets.UTF_8.decode(respBody.get()).toString();
                return ResponseEntity.status(fe.status()).body(errorText);
            } else {
                return ResponseEntity.status(fe.status()).build();
            }
        }
    }
}

Following is the code for our trusting Feign Client configuration which is needed incase you are connecting to a https url with self signed certificate:

import feign.Client;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.ssl.TrustStrategy;
import org.springframework.context.annotation.Bean;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;

public class GenericClientConfiguration {

    @Bean
    public Client feignClient()
            throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
        TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
        HostnameVerifier hostnameVerifier = (s, sslSession) -> true;
        SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
        return new Client.Default(sslContext.getSocketFactory(),hostnameVerifier);
    }
}

No comments:

Post a Comment