Unable to fetch JSON as String from a REST API - java

I am trying to fetch JSON from a REST API as a string.
I am using retrofit with scalarConverter for that purpose. I am able to pass the URL to fetch and my retrofit instance is also created successfully but I am not getting any response from the server.
PS: there is no request on the server, so that means my request is not going out from my machine.
I am new to android, kindly help me.
Retrofit instance creation:
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(base)
.addConverterFactory(ScalarsConverterFactory.create())
.build();
jsonApi jsonapi=retrofit.create(jsonApi.class);
Call<String> stringcall=jsonapi.getStringResponse(speech);
jsonApi interface:
public interface jsonApi {
#GET
Call<String> getStringResponse(#Url String url);
}
base: it is the base URL
speech: it is a variable containing rest of the URL to be processed.
when I run, the app gets stuck here with this message being displayed in Run Tab:
W/OpenGLRenderer: Fail to change FontRenderer cache size, it already initialized
W/art: Before Android 4.1, method int android.support.v7.widget.DropDownListView.lookForSelectablePosition(int, boolean) would have incorrectly overridden the package-private method in android.widget.ListView

With the below line
Call<String> stringcall=jsonapi.getStringResponse(speech);
you just get a Call object which is a representation of the HTTP request, and just by that, the request isn't executed. You need to use this object and call the execute method to make a synchronous request or the enqueue method to make an asynchronous request.
So in case you want to make a sync request, try the below:
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(base)
.addConverterFactory(ScalarsConverterFactory.create())
.build();
jsonApi jsonapi=retrofit.create(jsonApi.class);
Call<String> stringcall=jsonapi.getStringResponse(speech);
try {
Response<String> response = stringcall.execute();
String result = response.body();
} catch (Exception ex) {
//handle exception
}
The documentation of Call interface is here, for your reference.

Related

How to send Retrofit POST body as JSON String?

I have a service like below. I call the method Retroifit library. The service works when I send the paramter with #Body Test type but I want work with #Body String that i don't create a Test class and using this in #Body. I create a JSONObject and convert this to String but in this way, the program doesn't work! Can you help me or suggest a solution for this?
My Web API:
[Route("TestService"), HttpPost, IgnoreDataLog]
public async Task<Result<TestResult>> Add(Test pmDeviceObj)
{
var listResult = await pmService.AddAsync(pmDeviceObj);
return listResult;
}
Android part:
#POST("TestService")
Call<Result<TestResult>> TestService(#Header("Content-Type") String content_type,#Body String body);
Call Service in Android-> I get the StatusCode 400 with the below code
JSONObject jsonBody=new JSONObject();
try {
jsonBody.put("Id",73);
jsonBody.put("seri","55656573");
jsonBody.put("code","fc24009b9160");
jsonBody.put("sID",8);
}catch (JSONException ex){
ex.printStackTrace();
}
retrofit2.Call<Result<TestResult>> call1=service.TestService("application/json",jsonBody.toString());
If I use the below code in the Android part, everything works correct and I take the data.
#POST("TestService")
Call<Result<TestResult>> TestService(#Header("Content-Type") String content_type,#Body Test inputValue);
Test test=new Test(73,"556565","fc24009b9160",8);
retrofit2.Call<Result<TestResult>> call1=service.TestService("application/json",test);
This happens because Retrofit considers String as a 'normal' object that has to be converted to a JSON and is not aware that it already is a JSON represntation of object.
If you have HttpLogginInterceptor configured, you should see that (simplified example) your JSON string:
{"sId": "8"}
actually goes like:
"{\"sId\": \"8\"}"
To prevent this happen you need to use something like the comment suggests ScalarsConverterFactory. First you need to set dependencies for it:
For Gradle
dependencies {
implementation “com.squareup.retrofit2:converter-scalars:2.4.0”
}
or for Maven
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>converter-scalars</artifactId>
<version>2.4.0</version>
</dependency>
After done that you need to add the converter factory to your Retrofit, like (added also logging to be easily tested):
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor).build();
retrofit = new Retrofit.Builder()
.baseUrl(MY_URL)
// be sure to add this before gsonconverterfactory!
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory
.create())
.client(client).build();
myApi = retrofit.create(MY_API_CLASS);

Spring 5 Webclient Handle 500 error and modify the response

I have a Spring app acting as a passthrough from one app to another, making a http request and returning a result to the caller using WebClient. If the client http call returns a 500 internal server error I want to be able to catch this error and update the object that gets returned rather than re-throwing the error or blowing up the app.
This is my Client:
final TcpClient tcpClient = TcpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeout)
.doOnConnected(connection -> connection.addHandlerLast(new ReadTimeoutHandler(readTimeout))
.addHandlerLast(new WriteTimeoutHandler(writeTimeout)));
this.webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.filter(logRequest())
.filter(logResponse())
.filter(errorHandler())
.build();
And this is my error handler. I've commented where I want to modify the result, where ResponseDto is the custom object that is returned from the client call happy path
public static ExchangeFilterFunction errorHandler(){
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
ResponseDto resp = new ResponseDto();
if(nonNull(clientResponse.statusCode()) && clientResponse.statusCode().is5xxServerError()){
//this is where I want to modify the response
resp.setError("This is the error");
}
//not necessarily the correct return type here
return Mono.just(clientResponse);
});
}
How can I achieve this? I can't find any tutorials or any information in the docs to help explain it.
Disclaimer, I'm new to webflux. We're only starting to look at reactive programming

API call with Java + STS returning "Content type 'application/octet-stream' not supported"

I am working on part of an API, which requires making a call to another external API to retrieve data for one of its functions. The call was returning an HTTP 500 error, with description "Content type 'application/octet-stream' not supported." The call is expected to return a type of 'application/json."
I found that this is because the response received doesn't explicitly specify a content type in its header, even though its content is formatted as JSON, so my API defaulted to assuming it was an octet stream.
The problem is, I'm not sure how to adjust for this. How would I get my API to treat the data it receives from the other API as an application/json even if the other API doesn't specify a content type? Changing the other API to include a contenttype attribute in its response is infeasible.
Code:
The API class:
#RestController
#RequestMapping(path={Constants.API_DISPATCH_PROFILE_CONTEXT_PATH},produces = {MediaType.APPLICATION_JSON_VALUE})
public class GetProfileApi {
#Autowired
private GetProfile GetProfile;
#GetMapping(path = {"/{id}"})
public Mono<GetProfileResponse> getProfile(#Valid #PathVariable String id){
return GetProfile.getDispatchProfile(id);
}
The service calling the external API:
#Autowired
private RestClient restClient;
#Value("${dispatch.api.get_profile}")
private String getDispatchProfileUrl;
#Override
public Mono<GetProfileResponse> getDispatchProfile(String id) {
return Mono.just(id)
.flatMap(aLong -> {
MultiValueMap<String, String> headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
return restClient.get(getDispatchProfileUrl, headers);
}).flatMap(clientResponse -> {
HttpStatus status = clientResponse.statusCode();
log.info("HTTP Status : {}", status.value());
return clientResponse.bodyToMono(GetProfileClientResponse.class);
// the code does not get past the above line before returning the error
}).map(GetProfileClientResponse -> {
log.debug("Response : {}",GetProfileClientResponse);
String id = GetProfileClientResponse.getId();
log.info("SubscriberResponse Code : {}",id);
return GetProfileResponse.builder()
// builder call to be completed later
.build();
});
}
The GET method for the RestClient:
public <T> Mono<ClientResponse> get(String baseURL, MultiValueMap<String,String> headers){
log.info("Executing REST GET method for URL : {}",baseURL);
WebClient client = WebClient.builder()
.baseUrl(baseURL)
.defaultHeaders(httpHeaders -> httpHeaders.addAll(headers))
.build();
return client.get()
.exchange();
}
One solution I had attempted was setting produces= {MediaType.APPLICATION_JSON_VALUE} in the #RequestMapping of the API to produces= {MediaType.APPLICATION_OCTET_STREAM_VALUE}, but this caused a different error, HTTP 406 Not Acceptable. I found that the server could not give the client the data in a representation that was requested, but I could not figure out how to correct it.
How would I be able to treat the response as JSON successfully even though it does not come with a content type?
Hopefully I have framed my question well enough, I've kinda been thrust into this and I'm still trying to figure out what's going on.
Are u using jackson library or jaxb library for marshalling/unmarshalling?
Try annotating Mono entity class with #XmlRootElement and see what happens.

Reddit Api GET request succeeds in Postman, but fails with Retrofit

I am attempting to get a user's Reddit front page. I have successfully received an Auth Token via the Token Retrieval (code flow). I have managed to get the expected JSON response via Postman, but cannot produce the same results with Retrofit. The request seems to be timing out as onFailure() is being triggered in the callback. I am using the scopes: identity, mysubreddits, and read.
Additional note: I have got a 401 and 403 response with the code below when using insufficient scopes and using an expired Auth Token respectively.
Relevant constants:
redditToken = (actual auth token String)
RedditConstants.REDDIT_BASE_URL_OAUTH2 = "https://oauth.reddit.com"
Relevant method Section:
if (redditToken != null) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(RedditConstants.REDDIT_BASE_URL_OAUTH2)
.addConverterFactory(GsonConverterFactory.create())
.build();
Api api = retrofit.create(Api.class);
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "bearer " + redditToken);
headers.put("User-Agent", RedditConstants.REDDIT_USER_AGENT);
Call<RedditFeed> call = api.getFeed(headers);
call.enqueue(new Callback<RedditFeed>() {
#Override
public void onResponse(Call<RedditFeed> call, Response<RedditFeed> response) {
Log.d("FINDME", "response "+ response.toString());
if (response.isSuccessful()) {
Log.d("FINDME", "response was a success! we got the feed!");
} else {
Log.d("FINDME", "responce was not successfull triggered");
}
}
#Override
public void onFailure(Call<RedditFeed> call, Throwable t) {
Log.d("FINDME", "onFailure called from populateRedditFeed");
}
});
} else {
Toast.makeText(this, "Please Login with Reddit", Toast.LENGTH_SHORT).show();
}
Retrofit Interface:
public interface Api {
#GET(".")
Call<RedditFeed> getFeed (
#HeaderMap Map<String, String> headers
);
}
Log Results:
D/NetworkSecurityConfig: No Network Security Config specified, using
platform default
I/zygote: Do full code cache collection, code=123KB, data=105KB
After code cache collection, code=111KB, data=79KB
D/FINDME: onFailure called from populateRedditFeed
Postman Success:
After many starts and stops, seemingly randomly getting either a 200 or calling onFailure() I discovered the problem in one of my Retrofit model classes. The JSON response from Reddit contains a field that can either be a long or boolean. I had it defined as a boolean in my java class which threw an llegalStateException when it was returned as a long.
type name description
special edited false if not edited, edit date in UTC epoch-seconds
otherwise. NOTE: for some old edited comments on reddit.com, this will
be set to true instead of edit date.
*I'm unsure how to deal with this duality of types in java so for now I've commented out the field and the code works as expected.

Send a Post with url parameter with Retrofit 2

I want to send a POST with Retrofit 2. The url has some parameters:
#Headers({
"Accept: application/x-www-form-urlencoded;",
"User-Agent: my-app"
})
#FormUrlEncoded
#POST("server/directory/location.type")
`public Call<POJOStringValue> dataWithUr(#Path("arg1") String arg1, #Path("arg2"), String arg2);
The url looks like this
www.website.com/server/directory/location.type?arg1=value1&arg2=value2
I was requested to use a POST request. The values (value1 and value2) are dynamic at runtime. I started the project with Xamarin using HttpClient and now I'm rewriting it in Java native. In C# all I had to do was to concact the strings and send the resulting string in a single Post.
I tried to use #Path and the error was :
"server/directory/location.type" does not contain "{arg1}". (parameter #1)
Then, I tried to use #Query and the error was:
java.lang.IllegalArgumentException: Form-encoded method must contain at least one #Field.
Finally I tried with #Field the request never gets any response (I sette the connection timeout to 5 seconds)
Please help me, or tell me if I have to don't have any other choice but to use a GET request.
((EDIT))
Here is my code for the setup of the client:
private static void setupClient(){
final OkHttpClient client = new okhttp3.OkHttpClient.Builder()
.connectTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
.retryOnConnectionFailure(false)
.build();
//define retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(iXUtils.getUrl_())
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
this.client_ = retrofit.create(RequestInterface.class);
}
The get() method:
public static RequestInterface get(){
return this.client_;
}
Here is how I call it:
public String callFunctionDB(String arg1, String arg2){
setupClient();
Call<POJOStringValue> call = get().dataWithUrlString(arg1, arg2);
try {
POJOStringValue response = call.execute().body();
String value = response.getValue();
int test = 0;
} catch (IOException e) {
String value = "it failded";
e.printStackTrace();
}
return "test";
}
I put the test=0 to be able to put a breaking point, it never gets there. Plus I called the method "callFunctionDB" in a doInbackground to avoid the android.os.NetworkOnMainThreadException.
Retrofit requires you to have at least one form parameter if you request form encoding. You have answered your own question -- you are using query parameters instead of POST fields, so that annotation is not necessary. Remove the #FormUrlEncoded annotation, and change your parameters to #Query annotations.

Categories