Skip to content

Instantly share code, notes, and snippets.

@humanium
Created March 23, 2016 09:10
Show Gist options
  • Save humanium/ebc41cf3b6e63c9b09f3 to your computer and use it in GitHub Desktop.
Save humanium/ebc41cf3b6e63c9b09f3 to your computer and use it in GitHub Desktop.
Simple HTTP(S) client based on RestTemplate
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