In my Spring Boot project I have set several endpoints (which refer to some GET or POST REST API) in the application-dev.yml file.
spring:
username: xxx
password: acb132
route:
source:
protocol: https://
ip: 10.xxx.y.zz/
root: "swdfr/"
paths: >
- "ofh/ert/hAFG5"
- "ofh/ert/ryt54"
I would like to manage these endpoints within a service class with a single method. Currently I have implemented this solution:
//REST CONTROLLER
#GetMapping("/Multiple_Get")
public void manageGetEndpointsWithRestTemplate() throws Exception{
final String methodName = "manageGetEndpointsWithRestTemplate()";
try {
service.manageGetEndpointsWithRestTemplate();
} catch (final Exception e) {
this.errorLog(methodName, e);
throw e;
}
}
//SERVICE
#ResponseBody
public void manageGetEndpointsWithRestTemplate() {
final String methodName = "manageGetEndpointsWithRestTemplate()";
try {
String urlGet1 = protocol + ip + root + paths.get(0);
String urlGet2 = protocol + ip + root + paths.get(1);
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(username, password);
HttpEntity request = new HttpEntity(headers);
try {
RestTemplate restTemplate;
if (urlGet1.startsWith("https") || urlGet2.startsWith("https")) {
restTemplate = getRestTemplateForSelfSsl();
} else {
restTemplate = new RestTemplate();
}
// GET1
ResponseEntity<String> response1 = restTemplate.exchange(urlGet1, HttpMethod.GET, request,
String.class);
HttpStatus statusCode1 = response1.getStatusCode();
logger.info("STATUS GET1: " + statusCode1);
// GET2
ResponseEntity<String> response2 = restTemplate.exchange(urlGet2, HttpMethod.GET, request,
String.class);
HttpStatus statusCode2 = response2.getStatusCode();
logger.info("STATUS GET2: " + statusCode2);
} catch (HttpStatusCodeException e) {
logger.error(e.getMessage());
}
} catch (Exception e) {
logger.error(e.getMessage());
}
}
public RestTemplate getRestTemplateForSelfSsl()
throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
TrustStrategy acceptingTrustStrategy = (X509Certificate[] x509Certificates, String s) -> true;
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy)
.build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}
I would like to use a single method making it as generalizable as possible especially if I have to manage a large number of endpoints.
Do you have any ideas?
Thanks in advance
You can iterate over all the paths and execute the common code, considering you are doing GET request for all.
#ResponseBody
public void manageGetEndpointsWithRestTemplate() {
final String methodName = "manageGetEndpointsWithRestTemplate()";
try {
paths.forEach(path -> {
String urlGet = protocol + ip + root + path;
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(username, password);
HttpEntity request = new HttpEntity(headers);
try {
RestTemplate restTemplate = urlGet.startsWith("https") ? getRestTemplateForSelfSsl() : new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(urlGet, HttpMethod.GET, request,
String.class);
HttpStatus statusCode = response.getStatusCode();
logger.info("STATUS GET - {} : {}", urlGet, statusCode);
} catch (HttpStatusCodeException e) {
logger.error(e.getMessage());
}
});
} catch (Exception e) {
logger.error(e.getMessage());
}
}
In case you're gonna use POST or any other HTTP methods, mention in application-dev.yml file along with the paths. Add few extra logic to determine the HTTP.XXX before passing into restTemplate.exchange().
Related
I have a service I am mocking like this:
#ExtendWith(MockitoExtension.class)
class MyServiceTest {
#InjectMocks
MyService myService;
#Test
void testSendRec() {
myService.sendDocRec(.. pass params..);
}
}
the service:
#Service
public class MyService {
String sendDocRec( params ) {
// builds request
HttpUriRequestBase request = getRequest( params );
String response = doRequest(request);
}
public String doRequest(ClassicHttpRequest request) {
String result = null;
try (CloseableHttpClient httpclient = HttpClients.custom()
.setConnectionManager(this.connectionManager)
.setConnectionManagerShared(true)
.setDefaultRequestConfig(this.requestConfig)
.build()) {
final HttpClientContext clientContext = HttpClientContext.create();
try (CloseableHttpResponse response = httpclient.execute(request, clientContext)) {
result = EntityUtils.toString(response.getEntity());
}
} catch (URISyntaxException e) {
log.error("Invalid URI {}", e);
} catch (IOException e) {
log.error("Failed to make HTTP Request {}", e);
} catch (ParseException e) {
log.error("Failed parsing response body {}", e);
}
return result;
}
}
I need to be able to mock the "CloseableHttpResponse response = httpclient.execute(request, clientContext)", such that the "response" object is something I create ahead of time. I am hoping some mocking when/then constructs would work for this? I would grateful for ideas on how to do this. Thanks!
You can't do it using the current code structure. httpclient object is getting created within the method under test. So it can't be mocked.
You need to delegate httpclient object creation to another method with belongs to a different class (something similar to HttpClientFactory class). That HttpClientFactory class should only be responsible for creating httpClient instances. If you need you can write separate unit test case for the class.
#Inject
private HttpClientFactory httpClientFactory;
public String doRequest(ClassicHttpRequest request) {
String result = null;
try (CloseableHttpClient httpclient = httpClientFactory.getInstance()) {
final HttpClientContext clientContext = HttpClientContext.create();
try (CloseableHttpResponse response = httpclient.execute(request, clientContext)) {
result = EntityUtils.toString(response.getEntity());
}
} catch (URISyntaxException e) {
log.error("Invalid URI {}", e);
} catch (IOException e) {
log.error("Failed to make HTTP Request {}", e);
} catch (ParseException e) {
log.error("Failed parsing response body {}", e);
}
return result;
}
Now you can mock response like below:
#Mock
private HttpClientFactory httpClientFactory;
public String testDoRequest(ClassicHttpRequest request) {
....
CloseableHttpClient mockedClient = mock(CloseableHttpClient.class);
CloseableHttpResponse mockedResponse = mock(CloseableHttpResponse.class);
when(httpClientFactory.getInstance()).thenReturn(mockedClient);
when(mockedClient.execute(eq(request), any(clientContext))).thenReturn(mockedResponse);
....}
public String sendAPICall(String portalApiUrl, String reviewAppsAuthKey) {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI(portalApiUrl + CHECK_ORG_ATT_EXP))
.setHeader(AUTH_REVIEW_API_KEY, reviewAppsAuthKey)
.setHeader(CONTENT_TYPE, APPLICATION_JSON)
.GET()
.build();
HttpClient httpClient = HttpClient.newHttpClient();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == HttpStatus.OK.value()) {
LOGGER.info(API_SUCCESS);
return API_SUCCESS;
}
} catch (URISyntaxException e) {
LOGGER.error(API_FAIL_URI, e.getMessage());
} catch (InterruptedException e) {
LOGGER.error(API_FAIL_INTER, e.getMessage());
} catch (IOException e) {
LOGGER.error(API_FAIL_IO, e.getMessage());
}
return API_FAILURE;
}
}
Junit :
#Test
public void sendAPICallTest() throws IOException, InterruptedException {
Checkorg test = getCheckOrg();
String portalApiUrl = JUNIT_PORTAL_URL;
String reviewAppsAuthKey = JUNIT_AUTH_KEY;
String message = test.sendAPICall(portalApiUrl, reviewAppsAuthKey);
assertEquals(Checkorg.API_SUCCESS, message);
}
How to mock the HTTP Client in Test Class.
Thank you
As per your comment:
how can we test this method for success if we can't mock
you can use spy for that :
#Test
public void sendAPICallTest() throws IOException, InterruptedException {
Checkorg test = getCheckOrg();
String portalApiUrl = JUNIT_PORTAL_URL;
String reviewAppsAuthKey = JUNIT_AUTH_KEY;
Checkorg mockedTest = Mockito.spy(test);
Mockito.when(mockedTest.sendAPICall(portalApiUrl,reviewAppsAuthKey)).thenReturn("API Call Success");
String message = mockedTest.sendAPICall(portalApiUrl, reviewAppsAuthKey);
assertEquals(Checkorg.API_SUCCESS, message);
}
This can be a quick workaround if HttpClient mocking is not feasible.
I am trying to send an http post to a given oAuth1.0 protected endpoint, the owner of the endpoint provided to me:
consumerKey
consumerSecret
accessToken
accessTokenSecret
realm
I wrote some code based on How to call API (Oauth 1.0)?
public class HttpAuthPost {
public HttpAuthPost() {
realmID = "XXXXXXX";
String consumerKey = "kjahsdkjhaskdjhaskjdhkajshdkajsd";
String consumerSecret = "jklahsdkjhaskjdhakjsd";
String accessToken = "iuyhiuqhwednqkljnd";
String accessTokenSecret = "oihkhnasdiguqwd56qwd";
setupContext(consumerKey, consumerSecret, accessToken, accessTokenSecret);
}
public void setupContext(String consumerKey, String consumerSecret, String accessToken, String accessTokenSecret) {
this.oAuthConsumer = new CommonsHttpOAuthConsumer(consumerKey, consumerSecret);
oAuthConsumer.setTokenWithSecret(accessToken, accessTokenSecret);
oAuthConsumer.setSigningStrategy(new AuthorizationHeaderSigningStrategy());
}
public void authorize(HttpRequestBase httpRequest) throws FMSException {
try {
oAuthConsumer.sign(httpRequest);
} catch (OAuthMessageSignerException e) {
throw new FMSException(e);
} catch (OAuthExpectationFailedException e) {
throw new FMSException(e);
} catch (OAuthCommunicationException e) {
throw new FMSException(e);
}
}
public String executeGetRequest(String customURIString, String _content) throws UnsupportedEncodingException {
DefaultHttpClient client = new DefaultHttpClient();
HttpPost httpRequest = null;
//Preparing HttpEntity and populating httpRequest
try {
authorize(httpRequest);
} catch (FMSException e) {
e.printStackTrace();
}
HttpResponse httpResponse = null;
try {
HttpHost target = new HttpHost(uri.getHost(), -1, uri.getScheme());
httpResponse = client.execute(target, httpRequest);
// Process response and generate output
return output;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
I did some tests and I am getting this error: USER_ERROR : header is not NLAuth scheme.
I noticed the realm value is never actually set in the oAuthConsumer configuration, I try to find a way to specify the realm but I have not found a way to do it.
Does anyone have a clue on this?
Well the solution was actually pretty simple and now that I figured it out it seems obvious. Adding the realm as an additional parameter to the authconsumer worked for me.
Hope this help someone else in the future.
public void setupContext(String consumerKey, String consumerSecret, String accessToken, String accessTokenSecret) {
this.oAuthConsumer = new CommonsHttpOAuthConsumer(consumerKey, consumerSecret);
oAuthConsumer.setTokenWithSecret(accessToken, accessTokenSecret);
oAuthConsumer.setSigningStrategy(new AuthorizationHeaderSigningStrategy());
HttpParameters parameters = new HttpParameters();
parameters.put("realm", realmID);
oAuthConsumer.setAdditionalParameters(parameters);
}
Im trying to send restTemplate with long and image data. Im following this tutorial code and getting next error:
W/RestTemplate: POST request for "http://192.168.0.250:8081/server/upload" resulted in 400 (Bad Request); invoking error handler
E/ContentValues: 400 Bad Request org.springframework.web.client.HttpClientErrorException: 400 Bad Request
I have made simply restteamplate for Long data, but here I have some trubles.
Here are my Android client:
protected AnotherPostDTO doInBackground(Void... params) {
Resource resource = new ClassPathResource("res/drawable/bbb.png");
formData = new LinkedMultiValueMap<String, Object>();
formData.add("owners_id", "1");
formData.add("file", resource);
try {
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<MultiValueMap<String, Object>>(formData, requestHeaders);
ArrayList<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>(Arrays.asList(new MappingJackson2HttpMessageConverter(), new FormHttpMessageConverter()));
RestTemplate restTemplate = new RestTemplate(converters);
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
return restTemplate.postForObject(Constants.URLs.UPLOAD_FILE, requestEntity, AnotherPostDTO.class);
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
return post;}
My server controller:
#RequestMapping(value = "postformdata", method = RequestMethod.POST, headers = "Content-Type=multipart/form-data")
public #ResponseBody String handleFormUpload(#RequestParam("description") String description,
#RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
byte[] bytes = null;
try {
bytes = file.getBytes();
} catch (IOException e) {
System.out.println("taski");
}
return "file upload received! Name:[" + description + "] Size:["
+ bytes.length + "]";
} else {
return "file upload failed!";
}
}
and bean:
#Bean
MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxFileSize("128KB");
factory.setMaxRequestSize("128KB");
return factory.createMultipartConfig();
}
Any ideas?
I am doing a asynchronous http request to a web service. I am not that sure if this is the correct way to do it but it works.
Is this the correct way to make POST + authentication with HttpAsyncClient?
Should I close the httpclient at the end with httpclient.close(); ?
public void asyncHttpRequest() {
try {
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(3000)
.setConnectTimeout(3000).build();
CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom()
.setDefaultRequestConfig(requestConfig)
.build();
httpclient.start();
String postParameter = new JSONObject().put("key", "value").toString(); //Creating JSON string
HttpPost httpPost = new HttpPost("http://www.url.com");
httpPost.setEntity(new StringEntity(postParameter));
UsernamePasswordCredentials creds
= new UsernamePasswordCredentials("username", "password");
httpPost.addHeader(new BasicScheme().authenticate(creds, httpPost, null));
httpPost.setHeader("Accept", "application/json");
httpPost.setHeader("Content-type", "application/json");
httpclient.execute(httpPost, new FutureCallback<HttpResponse>() {
#Override
public void completed(final HttpResponse response) {
try {
InputStream responseBody = response.getEntity().getContent();
String serverResponse = IOUtils.toString(responseBody);
System.out.println("Server response : " + serverResponse);
System.out.println(httpPost.getRequestLine() + "->" + response.getStatusLine());
} catch (IOException | UnsupportedOperationException ex) {
//Do something
}
}
#Override
public void failed(final Exception ex) {
//Do something
}
#Override
public void cancelled() {
//Do something
}
});
} catch (IOException ex) {
//Do something
} catch (AuthenticationException ex) {
//Do something
}
}
Any help is appreciated!
I would suggest using a credentials provider like below, instead of explicitly adding a header for basic authentication:
CredentialsProvider provider = new BasicCredentialsProvider();
UsernamePasswordCredentials creds =
new UsernamePasswordCredentials("username", "password");
provider.setCredentials(AuthScope.ANY, creds);
CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom()
.setDefaultRequestConfig(requestConfig)
.setDefaultCredentialsProvider(provider)
.build();
Also, it would be best to explicitly close the httpclient after the request has been completely processed.