[Http] 透過 Proxy 與 API 溝通

在內網向外網的 API 溝通的時候,有時候因為特別的安全層級要求,需要透過內網的 Proxy 與在外網的 API 溝通,此時會傾向透過 Proxy 扮演類似 DNS 的角色!本篇想要整理並記錄兩個可以使用的方法!分別是利用 Curl 與 Spring-boot 中的 RestTemplate。

使用 Curl

curl 是一個在開發前期可以很快速了解 API 運作方法的作法,更多 curl 的指令可以參考

curl -X POST -u $user:$password\
     $url\
     -H "Content-Type:application/xml" -H "X-ContentName: ***.xml"\
     --data-binary "@/path/***.xml" -o output.txt\
     --insecure -x http://proxy --proxy-user $proxy_user:$proxy_password

以上是一個比較複雜的 curl 範例,以下我們來解析不同的參數的功能與其使用方法:

–insecure

有時一些內部使用的加密 SSL 網頁使用自簽憑證, 如果用 curl 擷取這些使用自簽憑證的 SSL 網頁內容,會出現錯誤,要避免這個情況,需要在 curl 指令後面加上“–insecure” 參數, 這樣 curl 便不會檢查 SSL 的有效性。

–data-binary

–data-binary 這個參數主要是將指定的檔案路徑的檔案利用二進位的方式讀進來且依附在 Http Request 裡面。

-o/-O

如果 curl 會收到 API 端的回應訊息的話,可以利用 -o 這個參數將 Reponse 放在指定的 txt 等等的檔案裡面,如果是使用 -O 的話則是可以直接使用遠端的檔案名稱!

其他更多的 curl 指令可以參考:https://curl.haxx.se/docs/manpage.html

使用 Spring-boot 的 RestTemplate

以下是一個初始化 RestTemplate 的範例:

private String PROXY_URL;
private String PROXY_HOST;
private String PROXY_ACCOUNT;
private String PROXY_PASSWORD;
private int PROXY_PORT = 8080;

public RestTemplate initRestTemplate(){
   
    List<ClientHttpRequestInterceptor> interceptors = null;
    CredentialsProvider credsProvider = new BasicCredentialsProvider();
    credsProvider.setCredentials(
    	new AuthScope(PROXY_URL, PROXY_PORT),
    	new UsernamePasswordCredentials(PROXY_ACCOUNT, PROXY_PASSWORD)
    );
    Proxy myProxy = new java.net.Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXY_HOST, PROXY_PORT));	    
    SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
    requestFactory.setProxy(myProxy);
    ClientHttpRequestFactory = factory = new InterceptingClientHttpRequestFactory(requestFactory, interceptors);
    return new RestTemplate(factory);
}

使用以上的 Java 程式碼可以透過 Proxy 到達外網的指定位址,但是如果 Proxy 是有帳密保護的話,則會出現以下的錯誤訊息:

ERROR[]() - Unexpected error occurred in scheduled task.
org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://target_URL": Unable to tunnel through proxy. Proxy returns "HTTP/1.1 407 authenticationrequired"; nested exception is java.io.IOException:Unable to tunnel through proxy. Proxy returns "HTTP/1.1 407 authenticationrequired"
    at.org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:473)
    at....
    at....
Caused by: java.io.IOException: Unable to tunnel through proxy. Proxy returns "HTTP/1.1 407 authenticationrequired"
    at sun.net.www.protocol.http.HttpURLConnection.doTunneling(HttpURLConnection.java:2152)

將以下的程式碼加進函式中則可以解決以上的錯誤訊息!

Authenticator.setDefault( 
    new Authenticator(){ 
        @Override 
        public PasswordAuthentication getPasswordAuthentication(){ 
            return new PasswordAuthentication(PROXY_ACCOUNT, PROXY_PASSWORD.toCharArray()); 
        } 
    } 
);

以上方法經過筆者測試可以在 spring-boot 的環境中執行!但是也有很多其他初始化 RestTemplate 的方法筆者並沒有成功運行起來,例如其中一個利用 HttpClientBuilder 與 HttpComponentsClientHttpRequestFactory 的方法

另外也可以參考連結

使用 Mock 來建立 RestTemplate 的測試環境

在使用 RestTemplate 來執行 HTTP Session 的任務之後,為了增加程式開發程式碼的覆蓋率,可以利用以下的方法來虛擬化遠端 API 行為。

public class RestTemplateTest{

    private static String URL = "http://example.url";
    private String returnValue = "<msg>Succeed</msg>"
    private RestTemplate restTemplate;
    private MockRestServiceServer server;
    
    @Before 
    public void setup(){
    	restTemplate = new RestTemplate();
    	server = MockRestServiceServer.bindTo(restTemplate).build();
    	this.server.expect(ExpectedCount.once(), MockRestRequestMatchers.requestTo(URL))
    	    .andExpect(MockRestRequestMatchers.method(HttpMethod.POST))
    	    .andRespond(MockRestResponseCreators.withStatus(HttpStatus.OK).body(returnValue));
    }

    @Test
    public void sendTest(){
    	String value = sendRequest();
    	Assert.assertEquals(value, returnValue);
    }
}