I am using java.net.http.HttpClient in my Java Spring Boot app and I noticed this weird behaviour.
When my code call HTTP request to 3rd party API, next request to different 3rd party API returns always as bad request (400). When I execute this request first, it works just fine.
When I restart the app, first API call is always successful, but second one is always bad and so I have to call it again after some timeout and then it work.
So I was thinking, if there is any form of "cache" that remember previous settings or whatever from previous request, because second request to different API is always bad. When I inspected HttpRequest in debugger, it seems okay to me and there was nothing really different from the one that worked.
Here is my bean config
#Configuration
public class HttpClientBean {
#Bean
public HttpClient httpClient() {
return HttpClient.newHttpClient();
}
}
HttpRequest builder
public static HttpRequest buildGetRequest(final String url) {
return HttpRequest.newBuilder()
.uri(URI.create(url))
.GET()
.build();
}
public static HttpRequest buildPostRequest(final String url, final String body) {
return HttpRequest.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.uri(URI.create(url))
.setHeader(CONTENT_TYPE, APPLICATION_JSON)
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
}
and here is HttpService
#Service
public class HttpServiceImpl implements HttpService {
private final HttpClient httpClient;
#Autowired
public HttpServiceImpl(final HttpClient httpClient) {
this.httpClient = httpClient;
}
#Override
public HttpResponse<String> sendGetRequestWithParams(final String url, final String params) throws Exception {
final HttpRequest request = buildGetRequest(url, params);
return httpClient.send(request, HttpResponse.BodyHandlers.ofString());
}
#Override
public HttpResponse<String> sendGetRequestWithoutParams(final String url) throws Exception {
final HttpRequest request = buildGetRequest(url);
return httpClient.send(request, HttpResponse.BodyHandlers.ofString());
}
#Override
public HttpResponse<String> sendPostRequestWithBody(final String url, final String body) throws Exception {
final HttpRequest request = buildPostRequest(url, body);
return httpClient.send(request, HttpResponse.BodyHandlers.ofString());
}
}
Thank you for your advices.
Related
I'm writing custom annotation processor and got struggling with code elements' positions.
Imagine I have the code:
public class DummyStepClass {
#Step
public void getInfo() throws Exception {
HttpClient client = HttpClient.newBuilder().build();
#Remember("REQUEST") HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/get"))
.GET()
.build();
#Remember("RESPONSE") HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
}
Purpose of annotation is to save variable into context of a program flow through my steps.
Context class:
public class Context {
private static final HashMap<String, Object> storage = new LinkedHashMap<>();
private Context() {}
public static void save(String k, Object v) {
storage.put(k, v);
}
}
Literally I want to achieve invoking Context.save(k, v); when annotated rather than writing it every time.
So as a result I want to have something like this:
public class DummyStepClass {
#Step
public void getInfo() throws Exception {
HttpClient client = HttpClient.newBuilder().build();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/get"))
.GET()
.build();
Context.save("REQUEST", request);
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
Context.save("RESPONSE", request);
System.out.println(response.statusCode());
System.out.println(response.body());
}
}
I do generate this lines of code and they appears in .class files. However this two statements are added in the end of a method.
How can I handle this?
I've managed to make this work by hooking into "blocks" of java code and pasting my code at the end.
In the java 15 project I have file application.properties of yml type:
NetanyaInfo:
HydrantPoints: https://www.data.com/
I need to use suiteUrlin in the function:
public String getHydrantPoints() throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create(suiteUrl))//here
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return response.body();
}
My question is how to get suiteUrl value from the java function?
If this is spring-boot, you do not need anything "special", it will be done for you under the hood. What you do is:
define that application.properties/yaml in src/main/resources
inject that value (almost) anywhere you want
For example:
#Value("${tridelInfo.siteUrl}")
private String siteUrl;
or even inside any bean, via a constructor:
#Bean/#Service/#Component
public class YourBean {
private final String siteUrl;
public YourBean(#Value("${tridelInfo.siteUrl}") String siteUrl) {
this.siteUrl = siteUrl;
}
}
Under the hood, you actually need to define a PropertySourcesPlaceholderConfigurer, but spring-boot does that for you.
There are some options available for this in spring. You can use any of them.
Injecting org.springframework.core.env.Environment to your bean.
#Autowired
private Environment env;
...
public String getTridelSuites() throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create(env.getProperty("suiteUrl");))//here
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return response.body();
}
I would like to use a global header for all my requests. Therefore I have implemented the following class:
public class HeaderInterceptor {
public Response intercept(Chain chain) throws IOException {
Request request = chain.request()
.newBuilder()
.method("GET", null)
.addHeader("Accept", "application/json")
.addHeader("Basic ", "abcdefghi123456789")
.build();
Response response = chain.proceed(request);
return response;
}
}
Now I would like to do the following in the main()-method:
public static void main(String[] args) throws Exception {
OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(MyInterceptor).build();
Request reqAllProjects = new Request.Builder()
.url("https://example.com/projects")
.build();
Response resAllProjects = httpClient.newCall(reqAllProjects).execute();
String responseData = resAllProjects.body().string();
System.out.println(responseData);
}
I'm not sure now how to use my HeaderInterceptor. I guess I'll have to enter it here, right?
OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(??MyInterceptor??).build();
I tried something like this: addInterceptor(HeaderInterceptor.intercept()) but this is not working...
Can someone help me please? And does the rest of it look fine? Many thanks in advance!
The interceptor class that you have created doesn't seem to be implementing the Interceptor interface. You need to implement as below
public class HeaderInterceptor implements Interceptor {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request()
.newBuilder()
.addHeader("Accept", "application/json")
.addHeader("Basic ", "abcdefghi123456789")
.build();
Response response = chain.proceed(request);
return response;
}
}
Do note that you should not be modifying the method and body of the request as .method("GET", null) unless you actually need so, as it can result in all the HTTP requests made by the client to make GET requests with null body.
Then add the interceptor while building the client as below
OkHttpClient httpClient = new OkHttpClient.Builder()
.addInterceptor(new HeaderInterceptor()).build();
Have a look at the OkHttp documentation for more info.
Have you checked this question : Okhttp3: Add global header to all requests error
It should be something like
.addInterceptor(new Interceptor())
When my Java web application receives an HTTP POST request, it needs to create a new OkHttp3 Request from the HttpServletRequest and send this to another URL. The original post request could be simple form data or multi-part.
Here's the interface that I am looking to implement:
import okhttp3.Request;
public interface OkHttp3RequestBuilder {
Request create(HttpServletRequest request);
}
Looks like the challenge boils down to how I would create an okhttp3.RequestBody. Here's the relevant part of the implementation...
final HttpUrl targetUrl = HttpUrl.get("http://internal.xyz.com");
final RequestBody requestBody = // ?????
final Request httpRequest = new Request.Builder()
.post(requestBody)
.url(targetUrl)
.build();
return httpRequest;
How do I go about doing it? Any suggestions? Thanks!
This should work, but request.getReader() must never have been called as you can call it only once.
Request create(HttpServletRequest request)
{
final HttpUrl targetUrl = HttpUrl.get("http://internal.xyz.com");
final String originalBody = request.getReader().lines().collect (Collectors.joining (System.lineSeparator()));
final RequestBody requestBody = RequestBody.create(MediaType.parse(request.getContentType()), originalBody);
final Request httpRequest = new Request.Builder()
.post(requestBody)
.url(targetUrl)
.build();
return httpRequest;
}
Thanks for your answer, ETL! This seems to work for me:
#Override
public RequestBody getRequestBody(final HttpServletRequest httpServletRequest) throws IOException {
final InputStream inputStream = httpServletRequest.getInputStream();
final byte[] requestBodyBytes = ByteStreams.toByteArray(inputStream);
final String contentType = httpServletRequest.getContentType();
final MediaType mediaType = MediaType.parse(contentType);
final RequestBody requestBody = RequestBody.create(mediaType, requestBodyBytes);
return requestBody;
}
Im trying to authenticate to Cloudinary API service using the below code but i get 401 unauthorized error, it expects credentials in this format https://API_KEY:API_SECRET#..., when i substitute with actual values it works great with browser/postman but fails with retrofit2, below is my code.
// create and initialize retrofit2 client
public static OkHttpClient getClient(){
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(Level.BASIC);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder()
.addHeader("API_KEY","API_SECRET")
.addHeader("Accept","Application/JSON").build();
return chain.proceed(request);
}
})
.addInterceptor(interceptor)
.build();
return client;
}
private static Retrofit retrofit = null;
public static Retrofit getClient(String baseUrl){
if (retrofit == null){
retrofit = new Retrofit.Builder()
.client(getClient())
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
// Interface with get methods to access image resources
public interface CloudinaryService {
#GET("resources/image")
Call<imageresponse> getImageResource();
}
// Util class to make requests
public class ApiUtils {
private static final String BASE_URL = "http://api.cloudinary.com/v...";
public static CloudinaryService getImageService(){
return RetrofitClient.getClient(BASE_URL)
.create(CloudinaryService.class);
}
}
Any help fixing the error will be highly appreciated, not sure if need custom converter. thanks
***** Edit******
public static String credentials = Credentials.basic(API_KEY,API_SECRET);
OkHttpClient client = new OkHttpClient.Builder()
// .authenticator(new Authenticator() {
// #Override
// public Request authenticate(Route route, Response response) throws IOException {
//
// return response.request().newBuilder().header("Authorization", credentials).build();
// }
// })
.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = (chain.request().newBuilder()
.header("Accept","Application/JSON")
.header("Cache-Control", "public, max-age=" + 60)
.header("Authorization",credentials).build());
return chain.proceed(request);
}
})
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.addInterceptor(loggingInterceptor)
.addInterceptor(provideOfflineCacheInterceptor())
.addNetworkInterceptor(provideCacheInterceptor())
.cache(getCache())
.build();
return client;
}
I was able to fix the issue with adding authenticator to the builder.
.authenticator(new Authenticator() {
#Override
public Request authenticate(Route route, Response response) throws IOException {
return response.request().newBuilder().header("Authorization", credentials).build();
}
})
thanks for all your help.
request = chain.request();
builder = request.newBuilder();
if (TextUtils.isEmpty(request.header(AUTH)) && UserPreference.getInstance().isSignin())
builder.addHeader(AUTH, UserPreference.getInstance().getAccessToken());
if (NetUtil.hasNetwork(GridInnApplication.getInstance()))
builder.header(USER_AGENT, userAgent);
else
builder.cacheControl(CacheControl.FORCE_CACHE);
request = builder.build();
Response response = chain.proceed(request);
if (NetUtil.hasNetwork(GridInnApplication.getInstance())) {
String cacheControl = request.cacheControl().toString();
return response.newBuilder()
.header(CACHE_CONTROL, cacheControl)
.removeHeader(PRAGMA)
.build();
} else {
return response.newBuilder()
.addHeader(CACHE_CONTROL, CACHE_CONTROL_ONLY_CACHED)
.removeHeader(PRAGMA)
.build();
}
//you can results before returing intercept
The answer provided by leafNext will work but will cause every request to be sent twice - The authenticator only kicks in if the server responds with 401. You send the request, get 401 and then send it again with proper credentials.
The correct solution is to provide the credentials from the get go, using the interceptor. It's similar to what you tried to do originally, but you got the syntax wrong. The expected format is basic authentication.
.addInterceptor(new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
// Request customization: add request headers
return chain.proceed(chain.request().newBuilder()
.header("Authorization", credentials).build());
}
});
Where credentials should follow the basic authentication protocol: Assuming the Api key is key and the secret is secret, you base64-encode the expression key:secret and prefix it with Basic. In this example the value of credentials should end up like so:
Basic a2V5OnNlY3JldA==
Edit - Added a fully working independent code bit to verify basic auth is working for okhttp (and thus with retrofit when using okhttp):
public int testBasicAuth() throws IOException {
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = (chain.request().newBuilder()
.header("Authorization",okhttp3.Credentials.basic(KEY, SECRET)).build());
return chain.proceed(request);
}
}).build();
Request request = new Request.Builder()
.url("https://api.cloudinary.com/v1_1/[cloud_name]/resources/image")
.build();
int code = client.newCall(request).execute().code();
return code; // 200
}