Created
March 23, 2016 09:10
-
-
Save humanium/ebc41cf3b6e63c9b09f3 to your computer and use it in GitHub Desktop.
Simple HTTP(S) client based on RestTemplate
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package service; | |
import com.fasterxml.jackson.core.type.TypeReference; | |
import com.fasterxml.jackson.databind.ObjectMapper; | |
import lombok.Getter; | |
import lombok.Setter; | |
import org.springframework.core.NestedRuntimeException; | |
import org.springframework.core.ParameterizedTypeReference; | |
import org.springframework.http.*; | |
import org.springframework.http.client.ClientHttpResponse; | |
import org.springframework.http.client.SimpleClientHttpRequestFactory; | |
import org.springframework.http.converter.BufferedImageHttpMessageConverter; | |
import org.springframework.util.FileCopyUtils; | |
import org.springframework.web.client.ResponseErrorHandler; | |
import org.springframework.web.client.RestClientException; | |
import org.springframework.web.client.RestTemplate; | |
import org.springframework.web.client.UnknownHttpStatusCodeException; | |
import javax.net.ssl.*; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.nio.charset.Charset; | |
import java.security.KeyManagementException; | |
import java.security.NoSuchAlgorithmException; | |
import java.security.cert.X509Certificate; | |
import java.util.LinkedList; | |
import java.util.List; | |
import java.util.Map; | |
/** | |
* Simple HTTP(S) client for test purposes. | |
* Ignores certificates check and wraps RestClientExceptions | |
* to rethrow them with custom error message | |
*/ | |
public class HTTPService { | |
private HttpHeaders headers; | |
private RestTemplate template; | |
public HTTPService(){ | |
this.headers = new HttpHeaders(); | |
template = new RestTemplate(); | |
template.setRequestFactory(new ClientRequestFactory((s, sslSession) -> true)); | |
template.setErrorHandler(new CustomResponseErrorHandler()); | |
template.getMessageConverters().add(new BufferedImageHttpMessageConverter()); | |
} | |
protected HttpHeaders getHeaders(){ | |
return headers; | |
} | |
protected <TBody, TResponse> TResponse request(String urlTemplate, HttpMethod method, TBody body, | |
Class<TResponse> responseType, | |
Object... args){ | |
return template.exchange(urlTemplate, | |
method, | |
new HttpEntity<>(body, headers), | |
responseType, | |
args).getBody(); | |
} | |
protected <TBody, TResponse> TResponse request(String urlTemplate, HttpMethod method, TBody body, | |
ParameterizedTypeReference<TResponse> reference, | |
Object... args){ | |
return template.exchange(urlTemplate, | |
method, | |
new HttpEntity<>(body, headers), | |
reference, | |
args).getBody(); | |
} | |
private static class ClientRequestFactory extends SimpleClientHttpRequestFactory { | |
private final HostnameVerifier verifier; | |
public ClientRequestFactory(HostnameVerifier verifier) { | |
this.verifier = verifier; | |
} | |
private TrustManager[] getTrustManagers(){ | |
return new TrustManager[] { | |
new X509TrustManager() { | |
public java.security.cert.X509Certificate[] getAcceptedIssuers() { | |
return null; | |
} | |
public void checkClientTrusted(X509Certificate[] certs, String authType) { } | |
public void checkServerTrusted(X509Certificate[] certs, String authType) { } | |
} | |
}; | |
} | |
@Override | |
protected void prepareConnection(java.net.HttpURLConnection connection, String httpMethod) throws IOException { | |
if (connection instanceof HttpsURLConnection) { | |
try { | |
HttpsURLConnection con = (HttpsURLConnection) connection; | |
con.setHostnameVerifier(verifier); | |
// disabling certificates check since it fails on self-signed ones | |
SSLContext sc = SSLContext.getInstance("SSL"); | |
sc.init(null, getTrustManagers(), new java.security.SecureRandom()); | |
con.setSSLSocketFactory(sc.getSocketFactory()); | |
} catch (KeyManagementException | NoSuchAlgorithmException e) { | |
e.printStackTrace(); | |
} | |
} | |
super.prepareConnection(connection, httpMethod); | |
} | |
} | |
/** | |
* Custom handler that throws exception with errors mapped to list and status code | |
* so we can catch it in tests and check errors in an easy way | |
*/ | |
public static class CustomResponseErrorHandler implements ResponseErrorHandler { | |
@Getter | |
@Setter | |
public static class ClientException extends NestedRuntimeException { | |
private HttpStatus status; | |
private List<String> errors; | |
public ClientException(String msg, HttpStatus status, List<String> errors){ | |
super(msg); | |
this.status = status; | |
this.errors = errors; | |
} | |
} | |
public static Boolean isError(HttpStatus status){ | |
HttpStatus.Series series = status.series(); | |
return (HttpStatus.Series.CLIENT_ERROR.equals(series) || HttpStatus.Series.SERVER_ERROR.equals(series)); | |
} | |
@Override | |
public boolean hasError(ClientHttpResponse response) throws IOException { | |
return isError(response.getStatusCode()); | |
} | |
private HttpStatus getHttpStatusCode(ClientHttpResponse response) throws IOException { | |
HttpStatus statusCode; | |
try { | |
statusCode = response.getStatusCode(); | |
} | |
catch (IllegalArgumentException ex) { | |
throw new UnknownHttpStatusCodeException(response.getRawStatusCode(), | |
response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response)); | |
} | |
return statusCode; | |
} | |
@Override | |
public void handleError(ClientHttpResponse response) throws IOException { | |
HttpStatus statusCode = getHttpStatusCode(response); | |
switch (statusCode.series()) { | |
case CLIENT_ERROR: | |
case SERVER_ERROR: | |
Map<String, List<String>> responseBody = new ObjectMapper() | |
.readValue(response.getBody(), new TypeReference<Map>() { | |
}); | |
String message = String.format("[%s] %s: %s", response.getRawStatusCode(), response.getStatusText(), responseBody); | |
throw new ClientException(message, | |
response.getStatusCode(), | |
responseBody.getOrDefault("errors", new LinkedList<>())); | |
default: | |
throw new RestClientException("Unknown status code [" + statusCode + "]"); | |
} | |
} | |
private byte[] getResponseBody(ClientHttpResponse response) { | |
try { | |
InputStream responseBody = response.getBody(); | |
if (responseBody != null) { | |
return FileCopyUtils.copyToByteArray(responseBody); | |
} | |
} | |
catch (IOException ex) { | |
// ignore | |
} | |
return new byte[0]; | |
} | |
private Charset getCharset(ClientHttpResponse response) { | |
HttpHeaders headers = response.getHeaders(); | |
MediaType contentType = headers.getContentType(); | |
return contentType != null ? contentType.getCharSet() : null; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment