Can we call external API without API ID? - java

I am new to API design, I am working on one project where I need to call currency exchange API from National Bank of Poland http://api.nbp.pl but I do not see any indication where I can find API ID. This development is on Spring Boot if I am trying to run the application without API ID it is throwing 404 error.
Here is the piece of code that I have written.
#RequestMapping(method = RequestMethod.GET, value = "/exchangerates/rates/{table}/{code}")
public #ResponseBody Object getAllCurriencyExchangeRates(#PathVariable String table, #PathVariable String code) {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
ResponseEntity<Object> response =
restTemplate.getForEntity("http://api.nbp.pl/api/" +table+ "," +code+ Object.class, null, headers);
return response;
}
Actual query http://api.nbp.pl/api/exchangerates/rates/a/chf/
So, my question is can we call an external API without API ID?

First things first, you are trying to reach wrong API. That is why you are getting 404 not found. 404 means there is no url like you are calling.
Check your restTemplate carefully,
restTemplate.getForEntity("http://api.nbp.pl/api/" + table+ "," +code+ Object.class, null, headers);
You are doing wrong when concatenate strings.
It should look something like this;
restTemplate.getForEntity("http://api.nbp.pl/api/exchangerates/rates/"+table+"/"+code, Object.class, null, headers);
And a hint for API developers, firstly you should play with api using Postman and then write code with api.

Try this - I have tested it - it works. Please keep in mind this is just a test implementation. Things inside main method have to be copied into your getAllCurriencyExchangeRates method.
And for sure replace "a" and "chf" through variables. I assume table and code are the variables you want to use. I used String because I don't know which type of object you want to return. You can use your own pojo for sure instead of String.
package scripts;
import java.net.URI;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
/**
* author: flohall
* date: 08.12.19
*/
public class Test {
public static void main(final String[] args){
final String url = "http://api.nbp.pl/api/exchangerates/rates";
final URI uri = UriComponentsBuilder.fromHttpUrl(url).path("/").path("a").path("/").path("chf").build().toUri();
System.out.println(uri);
final RestOperations restTemplate = new RestTemplate();
final ResponseEntity<String> result = restTemplate.getForEntity(uri, String.class);
System.out.println(result.getBody());
}
}

Try with this
ResponseEntity<Object> response =
restTemplate.getForEntity("http://api.nbp.pl/api/exchangerates/rates/" + table + "/" + code, Object.class, headers);

Related

Get access token from Oauth2 rest api using java

I need to call Oauth2 ResT API service to fetch the access token and expire_in values from the JSON file by it.
Below is a sample CURL which i need to call using JAVA i am beginner in JAVA so not able to figure out how to do it however i can do it using shell script.
curl -u 'ClientId:Clientaccesskey' https://oauth2.url/oauth/token -X POST -d 'response_type=token&client_id=ClientId&username=user&password=userpassword&scope=process&grant_type=password'
Sample JSON retured by above curl command --
{"access_token":"accessTokentobefetched","token_type":"bearer","refresh_token":"refreshToken","expires_in":7199,"scope":"process","jti":"somehexadecimalvaliu"}
In shell script we can fetch the value of access token and other fields using AWK command and other commands.
So i need to call this CURL command in JAVA and fetch the value of access token and other keys from the JSON file.
Any help which can help me start with this is welcome as i am new to JAVA and learning.
There are quite a few libraries that you can use to help you make a regular HTTP POST request from Java, but since you seem to require to send plain text/plain body content - I suggest that you use okhttp3. This is a fairly lightweight and easy to work with HTTP client.
You will need to add the following dependency to your pom.xml, grabbed from https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp/4.7.2:
<!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.7.2</version>
</dependency>
If you are using gradle, just visit the before mentioned URL, and get the gradle equivalent dependency declaration.
And here's a complete class that illustrates how the okhttp3 client can be used to perform the POST request, and extract the return value. This example expects that you are using the spring-boot-starter-web dependency (this will include the jackson and tomcat libraries that are used in the example).
package com.example.demo;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
#Component
public class TokenRequester {
public String getAccessToken() throws IOException {
// Create a new HTTP client
OkHttpClient client = new OkHttpClient().newBuilder().build();
// Create the request body
MediaType mediaType = MediaType.parse("text/plain");
RequestBody body = RequestBody.create(mediaType, "response_type=token&client_id=ClientId&username=user&password=userpassword&scope=process&grant_type=password");
// Build the request object, with method, headers
Request request = new Request.Builder()
.url("https://oauth2.url/oauth/token")
.method("POST", body)
.addHeader("Authorization", createAuthHeaderString("ClientId", "Clientaccesskey"))
.addHeader("Content-Type", "text/plain")
.build();
// Perform the request, this potentially throws an IOException
Response response = client.newCall(request).execute();
// Read the body of the response into a hashmap
Map<String,Object> responseMap = new ObjectMapper().
readValue(response.body().byteStream(), HashMap.class);
// Read the value of the "access_token" key from the hashmap
String accessToken = (String)responseMap.get("access_token");
// Return the access_token value
return accessToken;
}
// Just a helper metod to create the basic auth header
private String createAuthHeaderString(String username, String password) {
String auth = username + ":" + password;
byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.US_ASCII));
String authHeader = "Basic " + new String(encodedAuth);
return authHeader;
}
}
You may need to tweak a few things here. I could ask you to supply me the verbose output from the curl command, in order to be sure about the encoding - but give this one a try and see what you get?
Here's a solution that involves only Spring, using a RestTemplate for the POST request.
I found that when you use curl -X POST -d 'key=data', curl will add the header content-type: application/x-www-form-urlencoded, so the solution here will do the same.
This solution sets up the RestTemplate with the headers and body you have specified, and captures the response in an object equivalent to the one you have described.
The following solution consists of two files that you can try to introduce into your solution:
RestTemplateTokenRequester.java
package com.example.demo;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.StandardCharsets;
#Component
public class RestTemplateTokenRequester {
public TokenResponse requestAccessToken() {
// Create a RestTemplate to describe the request
RestTemplate restTemplate = new RestTemplate();
// Specify the http headers that we want to attach to the request
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.add("Authorization", createAuthHeaderString("ClientId", "Clientaccesskey"));
// Create a map of the key/value pairs that we want to supply in the body of the request
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("response_type","token");
map.add("client_id","ClientId");
map.add("username","user");
map.add("password","userpassword");
map.add("scope","process");
map.add("grant_type","password");
// Create an HttpEntity object, wrapping the body and headers of the request
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(map, headers);
// Execute the request, as a POSt, and expecting a TokenResponse object in return
ResponseEntity<TokenResponse> response =
restTemplate.exchange("https://oauth2.url/oauth/token",
HttpMethod.POST,
entity,
TokenResponse.class);
return response.getBody();
}
// Just a helper metod to create the basic auth header
private String createAuthHeaderString(String username, String password) {
String auth = username + ":" + password;
byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.US_ASCII));
String authHeader = "Basic " + new String(encodedAuth);
return authHeader;
}
}
TokenResponse.java
This is simply a POJO that is used by the jackson mapper, to capture the response in an object that you can easily read your result from.
package com.example.demo;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonIgnoreProperties(ignoreUnknown = true)
public class TokenResponse {
#JsonProperty("access_token")
private String accessToken;
#JsonProperty("token_type")
private String tokenType;
#JsonProperty("refresh_token")
private String refreshToken;
#JsonProperty("expires_in")
private Integer expiresIn;
#JsonProperty("scope")
private String scope;
#JsonProperty("jti")
private String jti;
}
I hope this solution will help you - I would prefer it over the other solution I have suggested with okhttp3.
curl is a HTTP client.
better solution is using HTTP client APIs for java to call endpoints.
RestTemplate is common HTTP client comes with spring and it is your best choice.

How to consume a HTTPS GET service with Spring Boot

I am trying to consume the following HTTPS endpoints from Yahoo Weather Service:
Yahoo Weather Service API
I am doing some special query according to the API to get the current weather at some parametrized location.
#Service("weatherConditionService")
public class WeatherConditionServiceImpl implements WeatherConditionService {
private static final String URL = "http://query.yahooapis.com/v1/public/yql";
public WeatherCondition getCurrentWeatherConditionsFor(Location location) {
RestTemplate restTemplate = new RestTemplate();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(URL);
stringBuilder.append("?q=select%20item.condition%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22");
// TODO: Validate YQL query injection
stringBuilder.append(location.getName());
stringBuilder.append("%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys");
WeatherQuery weatherQuery = restTemplate.getForObject(stringBuilder.toString(), WeatherQuery.class);
// TODO: Test Json mapping response
Condition condition = weatherQuery.getQuery().getResults().getChannel().getItem().getCondition();
return new WeatherCondition(condition.getDate(), Integer.parseInt(condition.getTemp()), condition.getText());
}
Location is a class that provides the attribute "name" that is a String description of the location, such as "New York" or "Manila".
Condition an other classes just map the returning object.
When executing I get the following HTTP response:
org.springframework.web.client.HttpClientErrorException: 403 Forbidden
So this means I am not authorized to access the resource from what I understand.
The URL works great if I just copy & paste it in a web browser:
Yahoo Weather Query
I think that mapping is not a problem since I am not getting "400" (Bad Request) but "403" (Forbidden)
There must be some error on the way I use the RestTemplate object. I am researching but I can't find an answer.
The docs say you need an api key. But when I make a call like this:
fetch('https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22nome%2C%20ak%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys')
.then(resp=> resp.json())
.then((res)=>console.log(res.query.results))
https://repl.it/NeoM
It works fine without one. Perhaps you've been blackisted for hitting the api too often.
Your code seems fine.
I finally found the answer. It finally WAS a Bad Request because I needed to pass the parameters differently (not as part of the URL).
I found the answer here. Here goes the code for my particular Yahoo Weather API call return a String (I still will have to do some work to use the mapping).
private static final String URL = "http://query.yahooapis.com/v1/public/yql";
public String callYahooWeatherApi() {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(URL)
.queryParam("q", "select wind from weather.forecast where woeid=2460286")
.queryParam("format", "json");
HttpEntity<?> entity = new HttpEntity<>(headers);
HttpEntity<String> response = restTemplate.exchange(
builder.build().encode().toUri(),
HttpMethod.GET,
entity,
String.class);
return response.getBody();
}

RestTemplate not working with S3 pre-signed put URLs

I have a server generate AWS S3 pre-signed PUT URLs and then I'm trying to uploading a byte[] into that URL using RestTemplate with this code:
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.ALL));
HttpEntity<byte[]> entity = new HttpEntity<>("Testing testing testing".getBytes(), headers);
System.out.println(restTemplate.exchange(putUrl, HttpMethod.PUT, entity, String.class));
When I run that code, I get this error:
Exception in thread "JavaFX Application Thread" org.springframework.web.client.HttpClientErrorException: 400 Bad Request
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:63)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:700)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:653)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:531)
at tech.dashman.dashman.controllers.RendererAppController.lambda$null$2(RendererAppController.java:95)
Unfortunately, there's nothing in the AWS S3 logs, so, I'm not sure what's going on. If I take that exact same URL and put it in the REST Client of IntelliJ IDEA, it just works (it creates an empty file in S3).
Any ideas what's wrong with my Java code?
Here's a full example that does the signing and tries to uploading a small payload to S3:
import com.amazonaws.HttpMethod;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
import org.joda.time.DateTime;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.web.client.RestTemplate;
import java.util.Date;
public class S3PutIssue {
static public void main(String[] args) {
String awsAccessKeyId = "";
String awsSecretKey = "";
String awsRegion = "";
String path = "";
String awsBucketName = "";
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(awsAccessKeyId, awsSecretKey);
AmazonS3 s3Client = AmazonS3ClientBuilder.standard().withRegion(awsRegion).
withCredentials(new AWSStaticCredentialsProvider(awsCredentials)).build();
Date expiration = new DateTime().plusDays(1).toDate();
GeneratePresignedUrlRequest urlRequest = new GeneratePresignedUrlRequest(awsBucketName, path);
urlRequest.setMethod(HttpMethod.PUT);
urlRequest.setExpiration(expiration);
String putUrl = s3Client.generatePresignedUrl(urlRequest).toString();
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
HttpEntity<byte[]> entity = new HttpEntity<>("Testing testing testing".getBytes(), headers);
restTemplate.exchange(putUrl, org.springframework.http.HttpMethod.PUT, entity, Void.class);
}
}
The source of issue is a double encoding of url characters. There are / in extended secret key which are encoded as %2 by s3Client.generatePresignedUrl. When already encoded string is passed to restTemplate.exchange it's internally converted to URI and encoded for the second time as %252 by UriTemplateHandler in RestTemplate source code.
#Override
#Nullable
public <T> T execute(String url, HttpMethod method, #Nullable RequestCallback requestCallback,
#Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException {
URI expanded = getUriTemplateHandler().expand(url, uriVariables);
return doExecute(expanded, method, requestCallback, responseExtractor);
}
So the easiest solution is to convert URL to URI using URL.toURI(). If you don't have URI and have String when RestTemplate is invoked then two options are possible.
Pass URI instead for string to exchange method.
restTemplate.exchange(new URI(putUrl.toString()), HttpMethod.PUT, entity, Void.class);
Create default UriTemplateHandler with NONE encoding mode and pass it to RestTemplate.
DefaultUriBuilderFactory defaultUriBuilderFactory = new DefaultUriBuilderFactory();
defaultUriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);
restTemplate.setUriTemplateHandler(defaultUriBuilderFactory);
restTemplate.exchange(putUrl.toString(), org.springframework.http.HttpMethod.PUT, entity, Void.class);
Do not convert your URL to String. Instead convert it to URI. I think there are some encoding issues when you converted to String. For example the URL in String format had %252F, where it should have just been %2F. Looks like some sort of double encoding issue.
Leave as URL...
URL putUrl = amazonS3Client.generatePresignedUrl(urlRequest);
Convert to URI...
ResponseEntity<String> re = restTemplate.exchange(putUrl.toURI(), org.springframework.http.HttpMethod.PUT, entity, String.class);
EDIT: More info to clarify what is going on.
The problem that occurred here is that when you call URL.toString() in this instance you are given back an encoded String representation of the URL. But the RestTemplate is expecting a String url that is not yet encoded. RestTemplate will do the encoding for you.
For example look at the code below...
public static void main(String[] args) {
RestTemplate rt = new RestTemplate();
rt.exchange("http://foo.com/?var=<val>", HttpMethod.GET, HttpEntity.EMPTY, String.class);
}
When you run this you get the following debug message from Spring, notice how the url in the debug msg is encoded.
[main] DEBUG org.springframework.web.client.RestTemplate - Created GET request for "http://foo.com/?var=%3Cval%3E"
So you can see the RestTemplate will encode for you any String url's passed to it. But the URL provided by AmazonS3Client is already encoded. See the code below.
URL putUrl = amazonS3Client.generatePresignedUrl(urlRequest);
System.out.println("putUrl.toString = " + putUrl.toString());
This prints out a String that is already encoded.
https://private.s3.amazonaws.com/testing/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20171114T191829Z&X-Amz-SignedHeaders=host&X-Amz-Expires=0&X-Amz-Credential=AKIAIJ7ZSL22IJTM6NTQ%2F20171114%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=eca611ea33d9ad5710207568dcf181e4318ce39271fd0f1ce05bd99ebbf4097
So when I stick that into the exchange method of the RestTemplate I get the following debug message.
[main] DEBUG org.springframework.web.client.RestTemplate - PUT request for "https://turretmaster.s3.amazonaws.com/testing/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20171114T191829Z&X-Amz-SignedHeaders=host&X-Amz-Expires=0&X-Amz-Credential=AKIAIJ7ZSL22IJTM6NTQ%252F20171114%252Fus-east-1%252Fs3%252Faws4_request&X-Amz-Signature=eca611ea33d9ad5710207568dcf181e4318ce39271fd0f1ce05bd99ebbf40975"
Notice how every %2F from the url String turned into %252F. %2F is the encoded representation of /. But %25 is %. So it encoded a url that was already encoded. The solution was to pass a URI object to RestTemplate.exchange, instead of an encoded String url.

Spring data rest application backend notify frontend

Hi I would like to my backend (spring-data-rest) application to generate some sample data and notify frontend. However the repository event handler is REST only so I tried to write a restTemplate but failed.
#Scheduled(fixedRate = 5000)
public void addCounter() throws Exception {
String url = String.format("http://localhost:%d/%s/counters", 8080, api);
Counter counterExpected = new Counter('xxx', random.nextInt(100));
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(counterExpected);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<String>(jsonString, headers);
restTemplate.postForObject(url, entity, String.class);
}
Error:
Description:
Field restTemplate in ScheduledTask required a bean of type 'org.springframework.boot.test.web.client.TestRestTemplate' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.boot.test.web.client.TestRestTemplate' in your configuration.
This error makes sense because I am using TestTestTemplate in my runtime application instead of test scope.
My questions are:
Is it possible to change the addCounter() method to something simpler just like:
counterRepository.save(newCounter);
/* Raise AfterCreate event */
If yes, then how?
If not then is there any other way to do a HTTP post instead of using restTemplate?
My bad. I should be using
import org.springframework.web.client.RestTemplate;
instead of
import org.springframework.boot.test.web.client.TestRestTemplate;

How do I add attributes to http request to spring controller?

I would like to test the spring controller below, which reads http request attributes and acts on them. I am able to trigger the controller code below by typing localhost:8080/someURL into my web browser. But the result is {"id":1,"content":"null and null and null"}, which indicate null values in the named request attributes. How do I send a request to a named url like localhost:8080/someURL which contains values for the specified request attributes, so that I can confirm that the receiving controller code works properly?
Here is the code for the controller:
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestPa ram;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
#Controller
public class SomeController {
private final AtomicLong counter = new AtomicLong();
#RequestMapping(value = "/someURL", method=RequestMethod.GET)
public #ResponseBody Greeting receiveSMS(HttpServletRequest req){
String att1 = (String) req.getAttribute("att1");
String att2 = (String) req.getAttribute("att2");
String att3 = (String) req.getAttribute("att3");
String total = att1 + " and " + att2 + " and " + att3;
return new Greeting(counter.incrementAndGet(), String.format(total));
}
}
Note: I am trying to recreate in Spring the PHP functionality that is given in the script at the following link. I have not written this kind of code below, if I am framing the question poorly, I would appreciate advice for reframing it. Along with a link to any example solution such as a JUNIT or other means by which to recreate the request.
Request attributes are server-side only constructs. Try using request parameters instead:
#RequestMapping(value = "/someURL", method = RequestMethod.GET)
public #ResponseBody Greeting receiveSMS(#RequestParam("att1") String att1, #RequestParam("att2") String att2, #RequestParam("att3") String att3){
String total = String.format("%s and %s and %s", att1, att2, att3);
return new Greeting(counter.incrementAndGet(), total);
}
Then send a request of the form:
http://localhost:8080/someURL?att1=value1&att2=value2&att3=value3
And you should be able to read the values that you are trying to pass.
Checkout Spring MVC Test framework - instead of manually fire some URLs write unit tests instead.
Regarding your note
Yes, that's parameters. In php you have $_GET and $_POST or (if you don't care about the method) simply $_REQUEST for accessing the request parameters. Recode from getAttribute() to getParameter() or put them in your method signature using #RequestParam annotation.
#RequestMapping(value = "/receiveSMS", method=RequestMethod.GET)
public #ResponseBody Greeting receiveSMS(#RequestParam("from") String from,
#RequestParam("to") String to, #RequestParam("body") String body){
}
Now you can try http://localhost:8080/yourapp/receiveSMS?from=me&to=you&body=stackoverflow
Sidenote:
If you want that info send from the client, you should use getParameter() calls instead.
test something like :
http://localhost:8080/someURL?att1=value1&att2=value2&att3=value3
It will show you the value1, value2 and value3 passed into the URL...

Categories