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.
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.
I am using feign client to connect to downstream service.
I got a requirement that when one of the downstream service endpoint returns 400 ( it's partial success scenario ) our service need this to be converted to 200 success with the response value.
I am looking for a best way of doing this.
We are using error decoder to handle the errors and the above conversion is applicable for only one endpoint not for all the downstream endpoints and noticed that decode() method should returns exception back.
You will need to create a customized Client to intercept the Response early enough to change the response status and not invoke the ErrorDecoder. The simplest approach is to create a wrapper on an existing client and create a new Response with a 200 status. Here is an example when using Feign's ApacheHttpClient:
public class ClientWrapper extends ApacheHttpClient {
private ApacheHttpClient delegate;
public ClientWrapper(ApacheHttpClient client) {
this.client = client;
}
#Override
public Response execute(Request request, Request.Options options) throws IOException {
/* execute the request on the delegate */
Response response = this.client.execute(request, options);
/* check the response code and change */
if (response.status() == 400) {
response = Response.builder(response).status(200).build();
}
return response;
}
}
This customized client can be used on any Feign client you need.
Another way of doing is by throwing custom exception at error decoder and convert this custom exception to success at spring global exception handler (using #RestControllerAdvice )
public class CustomErrorDecoder implements ErrorDecoder {
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() == 400 && response.request().url().contains("/wanttocovert400to200/clientendpoints") {
ResponseData responseData;
ObjectMapper mapper = new ObjectMapper();
try {
responseData = mapper.readValue(response.body().asInputStream(), ResponseData.class);
} catch (Exception e) {
responseData = new ResponseData();
}
return new PartialSuccessException(responseData);
}
return FeignException.errorStatus(methodKey, response);
}}
And the Exception handler as below
#RestControllerAdvice
public class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.OK)
#ExceptionHandler(PartialSuccessException.class)
public ResponseData handlePartialSuccessException(
PartialSuccessException ex) {
return ex.getResponseData();
}
}
Change the microservice response:
public class CustomFeignClient extends Client.Default {
public CustomFeignClient(
final SSLSocketFactory sslContextFactory, final HostnameVerifier
hostnameVerifier) {
super(sslContextFactory, hostnameVerifier);
}
#Override
public Response execute(final Request request, final Request.Options
options) throws IOException {
Response response = super.execute(request, options);
if (HttpStatus.SC_OK != response.status()) {
response =
Response.builder()
.status(HttpStatus.SC_OK)
.body(InputStream.nullInputStream(), 0)
.headers(response.headers())
.request(response.request())
.build();
}
return response;
}
}
Add a Feign Client Config:
#Configuration
public class FeignClientConfig {
#Bean
public Client client() {
return new CustomFeignClient(null, null);
}
}
I am working in AWS(Amazon web service) , mockito and java junit4 environment. In my class I am using one method which takes Request object as a parameter and depending on that object I am getting response. Following is my code,
private Response<String> getStringResponse(Request<?> request) {
try {
AmazonHttpClient client = new AmazonHttpClient(new ClientConfiguration());
ExecutionContext executionContext = new ExecutionContext(true);
HttpResponseHandler<AmazonClientException> handler = getErrorResponseHandler();
HttpResponseHandler<String> responseHandler = getHttpResponseHandler();
RequestExecutionBuilder requestExecutionBuilder = client.requestExecutionBuilder();
requestExecutionBuilder = requestExecutionBuilder.executionContext(executionContext);
requestExecutionBuilder = requestExecutionBuilder.request(request);
requestExecutionBuilder = requestExecutionBuilder.errorResponseHandler(handler);
Response<String> response = requestExecutionBuilder.execute(responseHandler);
return response;
} catch (Exception e) {
AppLogger.getLogger().error("Exception in :: classname :: getStringResponse() ::");
throw e;
}
}
What I want to do is, I want to mock this whole scenario means on whatever request, my method should give me the custom response object which I want, irrespective of what Request is coming. I am calling this method from my junit test. So is there anyway to do this?
Before you can test this method with JUnit and Mockito,
you should write a clean and testable codes.
Importantly, you have to remove all dependencies inside method and initialize them from outside. For example,
private Response<String> getStringResponse(Request<?> request,
AmazonHttpClient client,
ExecutionContext executionContext,
HttpResponseHandler<AmazonClientException> handler,
HttpResponseHandler<String> responseHandler) {
try {
RequestExecutionBuilder requestExecutionBuilder = client.requestExecutionBuilder()
.executionContext(executionContext)
.request(request)
.errorResponseHandler(handler);
Response<String> response = requestExecutionBuilder.execute(responseHandler);
return response;
} catch (Exception e) {
AppLogger.getLogger().error("Exception in :: classname :: getStringResponse() ::");
throw e;
}
}
Now you can test it by mocking these above dependencies.
#Mock
AmazonHttpClient client;
#Mock
ExecutionContext executionContext;
#Mock
HttpResponseHandler<AmazonClientException> handler;
#Mock
HttpResponseHandler<String> responseHandler;
// For request, you can create a custom one or use mock data
Request<?> request;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
// other setups
}
#Test
public void getStringResponseTest() {
// you can test now
yourCall.getStringResponse(request, client, executionContext, handler, responseHandler);
// verify whatever you want....
}
I am using Jersey client to connect to an SSE stream. The server requires that I add a header to the http request for authorization, but I can't figure out how to add the header.
Here is my code:
Client client = ClientBuilder.newBuilder().register(SseFeature.class).build();
WebTarget target = client.target(baseurl + "/v1/devices/events/");
eventSource = EventSource.target(target).build();
eventSource.register(getEventListener());
eventSource.open();
Here is an example of the header I need to add:
Authorization: Bearer 38bb7b318cc6898c80317decb34525844bc9db55
It would be something like this for Basic Authentication:
Client client = ClientBuilder.newClient();
HttpAuthenticationFeature feature = HttpAuthenticationFeature.basicBuilder().build();
client.register(feature);
client.register(SseFeature.class);
WebTarget target = client.target(baseurl + "/v1/devices/events/")
.property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_BASIC_USERNAME, "...")
.property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_BASIC_PASSWORD, "...");
...
You already get the password encoded by Jersey.
And if it is a token:
Client client = ClientBuilder.newClient();
WebTarget target = client.target(baseurl + "/v1/devices/events/")
.request("...")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + "... encoded token ...");
Hope it helps!
In case someone would want to add the bearer token header at the Client entity level itself, rather than at the Request entity level (in my case I had a factory method for returning preconfigured Client entities, so I had no way of adding the authorization header within the factory method, as .header(...) becomes available only after you go through the ClientBuilder.newBuilder().register(...).build().target(...).request(...) call chain, as of Jersey 2.x):
// client is a javax.ws.rs.client.Client entity
Feature feature = OAuth2ClientSupport.feature("YOUR_BEARER_TOKEN");
client.register(feature);
// now you can use client.target(...).request(...).post(...), without calling .header(...) after .request(...)
Unfortunately (as you may have guessed) this requires a new dependency: org.glassfish.jersey.security:oauth2-client
<dependency>
<groupId>org.glassfish.jersey.security</groupId>
<artifactId>oauth2-client</artifactId>
<version>2.15</version>
</dependency>
// Using SSL + Header Key
uri = UriBuilder.fromUri(sslUrl).port(sslServerPort).build();
sslConfig = SslConfigurator.newInstance().trustStoreFile(trustStoreFile).trustStorePassword(trustStorePassword);
sslContext = sslConfig.createSSLContext();
client = ClientBuilder.newBuilder().sslContext(sslContext).build();
target = client.target(uri).path(path);
Entity<?> entity = Entity.entity(Object, MediaType.APPLICATION_JSON);
response = target.request().header("key","value").post(entity);
// Using UserName & Password + Header Key
uri = UriBuilder.fromUri(url).port(serverPort).build();
basicAuth = HttpAuthenticationFeature.basic(username, userPassword);
client = ClientBuilder.newBuilder().register(basicAuth).build();
target = client.target(uri).path(path);
Entity<?> entity = Entity.entity(Object, MediaType.APPLICATION_JSON);
response = target.request().header("key","value").post(entity);
// Using only Header Key
uri = UriBuilder.fromUri(url).port(serverPort).build();
client = ClientBuilder.newBuilder().build();
target = client.target(uri).path(path);
Entity<?> entity = Entity.entity(Object, MediaType.APPLICATION_JSON);
response = target.request().header("key","value").post(entity);
Hope this helps you with your problem.
Here is the complete examples
ClientConfig clientConfig = new ClientConfig();
Client client = ClientBuilder.newClient(clientConfig);
WebTarget webTarget = client.target("http://localhost:8080/MyApp/customer/");
Invocation.Builder invocationBuilder =
webTarget.request(MediaType.APPLICATION_JSON).header(HttpHeaders.AUTHORIZATION, "your
secret key");
response = invocationBuilder.get();
output = response.readEntity(String.class);
Dependency for jersey client
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>2.25.1</version>
</dependency>
Try this:
Invocation.Builder invocationBuilder = target.request(MediaType.APPLICATION_JSON).header(HttpHeaders.AUTHORIZATION, "Bearer38bb7b318cc6898c80317decb34525844bc9db55");
I realize this question is a year old but since there are not a lot to be found on that subject, I'll share my solution.
Based on suggested OAuth2Feature, I came up with this solution:
Create a custom feature. Feature will reference a custom filter
Create a custom filter of priority HEADER_DECORATOR
Create a HeaderProvider interface. Provider will be passed to the filter
Register the WebClient with the custom feature
Header provider interface
#FunctionalInterface
public interface ISseHeaderProvider {
Map<String, String> getHeaders();
}
Custom feature
public class SseHeaderSupportFeature implements Feature {
private final SseHeaderSupportFilter filter;
public SseHeaderSupportFeature(ISseHeaderProvider provider) {
this.filter = new SseHeaderSupportFilter(provider);
}
#Override
public boolean configure(FeatureContext context) {
context.register(filter);
return true;
}
}
Custom filter
#Priority(Priorities.HEADER_DECORATOR)
public class SseHeaderSupportFilter implements ClientRequestFilter {
private final ISseHeaderProvider provider;
public SseHeaderSupportFilter(#NotNull ISseHeaderProvider provider) {
this.provider = provider;
}
#Override
public void filter(ClientRequestContext request) throws IOException {
provider.getHeaders().forEach((k, v) -> request.getHeaders().add(k, v));
}
}
Usage
ISseHeaderProvider provider = () -> MapBuilder.<String, String>builder().add("Authorization", "Bearer ...").build();
Client client = ClientBuilder.newBuilder()
.register(SseFeature.class)
.register(new SseHeaderSupportFeature(provider))
.build();
WebTarget target = client.target(UriBuilder.fromPath(getUrl()));
//EventSource eventSource = ....
This solution is generic and allows you to easily add an Authorization header without having to add another dependency.
Following answer is useful:
Server Sent Event Client with additional Cookie
It use a customized WebTarget to add cookie and the same way on header also work.
public class AuthorizationHeaderWebTarget implements WebTarget {
private WebTarget base;
private String token;
public AuthorizationHeaderWebTarget(WebTarget base, String token) {
this.base = base;
this.token = token;
}
// Inject that cookie whenever someone requests a Builder (like EventSource does):
public Invocation.Builder request() {
return base.request().header(HttpHeaders.AUTHORIZATION, token);
}
public Invocation.Builder request(String... paramArrayOfString) {
return base.request(paramArrayOfString).header(HttpHeaders.AUTHORIZATION, token);
}
public Invocation.Builder request(MediaType... paramArrayOfMediaType) {
return base.request(paramArrayOfMediaType).header(HttpHeaders.AUTHORIZATION, token);
}
public Configuration getConfiguration() {
return base.getConfiguration();
}
//All other methods from WebTarget are delegated as-is:
public URI getUri() {
return base.getUri();
}
public UriBuilder getUriBuilder() {
return base.getUriBuilder();
}
public WebTarget path(String paramString) {
return base.path(paramString);
}
public WebTarget matrixParam(String paramString, Object... paramArrayOfObject) {
return base.matrixParam(paramString, paramArrayOfObject);
}
public WebTarget property(String paramString, Object paramObject) {
return base.property(paramString, paramObject);
}
public WebTarget queryParam(String paramString, Object... paramArrayOfObject) {
return base.queryParam(paramString, paramArrayOfObject);
}
public WebTarget register(Class<?> paramClass, Class<?>... paramArrayOfClass) {
return base.register(paramClass, paramArrayOfClass);
}
public WebTarget register(Class<?> paramClass, int paramInt) {
return base.register(paramClass, paramInt);
}
public WebTarget register(Class<?> paramClass, Map<Class<?>, Integer> paramMap) {
return base.register(paramClass, paramMap);
}
public WebTarget register(Class<?> paramClass) {
return base.register(paramClass);
}
public WebTarget register(Object paramObject, Class<?>... paramArrayOfClass) {
return base.register(paramObject, paramArrayOfClass);
}
public WebTarget register(Object paramObject, int paramInt) {
return base.register(paramObject, paramInt);
}
public WebTarget register(Object paramObject, Map<Class<?>, Integer> paramMap) {
return base.register(paramObject, paramMap);
}
public WebTarget register(Object paramObject) {
return base.register(paramObject);
}
public WebTarget resolveTemplate(String paramString, Object paramObject) {
return base.resolveTemplate(paramString, paramObject);
}
public WebTarget resolveTemplate(String paramString, Object paramObject, boolean paramBoolean) {
return base.resolveTemplate(paramString, paramObject, paramBoolean);
}
public WebTarget resolveTemplateFromEncoded(String paramString, Object paramObject) {
return base.resolveTemplateFromEncoded(paramString, paramObject);
}
public WebTarget resolveTemplates(Map<String, Object> paramMap) {
return base.resolveTemplates(paramMap);
}
public WebTarget resolveTemplates(Map<String, Object> paramMap, boolean paramBoolean) {
return base.resolveTemplates(paramMap, paramBoolean);
}
public WebTarget resolveTemplatesFromEncoded(Map<String, Object> paramMap) {
return base.resolveTemplatesFromEncoded(paramMap);
}
}
Following is the code to use it:
EventSource eventSource = new EventSource(new AuthorizationHeaderWebTarget(target, token));
eventSource.register(new EventListener() {
public void onEvent(final InboundEvent inboundEvent) {
//...
}
});
If you use jercy client using header in websource
Client client=Client.create();
WebResource webresource=client.resource(urlLink);
ClientResponse clientResponse=webresource.header("authorization", accessToken)
.accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);