I'm trying to find a way to retrieve the URL that is currently mapped by Feign Client method interface in a Spring app, but I could not find a solution yet. The following code works:
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
application.yml
api:
url: https://jsonplaceholder.typicode.com
ApiClient.class
package com.example.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
#FeignClient(name = "json-placeholder", url = "${api.url}")
public interface ApiClient {
#GetMapping(value = "/posts", consumes = "application/json")
ResponseEntity<String> getPosts();
}
FeignApplication.class
package com.example.feign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import javax.annotation.PostConstruct;
#EnableFeignClients
#SpringBootApplication
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
#Autowired
private ApiClient apiClient;
#PostConstruct
public void test() {
// this works
System.out.println(apiClient.getPosts().getBody());
// apiClient. ... getPosts request URL
}
}
I tried to compose this URL reading directly from annotations, but it doesn't seem to work. Can anybody give me an alternative? Thank you.
EDIT notes -
Sorry, but I had do a small change in my question, due to an unexpected problem while applying provided solutions.
Reading directly from Annotations works if the value set in annotation property is literal. If the value is read from application.yml file, the URL returned by the annotation property is the expression ifself, not the parsed value.
Any ideas about this updated scenario? What I need is the URL actually been called by FeignClient. I'm understands all provided solutions are actually workarounds.
I'm not sure if you are still looking for the answer, but my method is below
Return Response
import feign.Response;
#FeignClient(name = "json-placeholder", url = "${api.url}")
public interface ApiClient {
#GetMapping(value = "/posts", consumes = "application/json")
Response getPosts();
}
get Request.url from the Response
Response response = apiClient.getPosts();
String url = response.request().url()
then you can get the URL
Related
I am using some external API to GET and POST some ressources, locally my code works fine with the call of different endPoints (GET, POST...) and even with Postman, but when i try to run my code in another platerform (where the ressources are), i get the 412 HTTP error due to a POST call : after looking on the internet, i found out that i should generate an ETagd of the entity (that i went to modify) and add it into the header of my POST endPoint.
For that, i used ShallowEtagHeaderFilter and the #Bean annotation(above the filter method) and the #SpringBootApplication annotation above my class, here is my code :
package main.Runners;
import io.testproject.java.annotations.v2.Parameter;
import okhttp3.*;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.configurationprocessor.json.JSONArray;
import org.springframework.boot.configurationprocessor.json.JSONObject;
import org.springframework.context.annotation.Bean;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
import javax.servlet.Filter;
#SpringBootApplication
public class ActionRunner {
#Parameter(description = "the project ID")
public static String projectId = "xxx";
#Parameter(description = "the test ID")
public static String testId = "yyy";
public static void main(String[] args) throws Exception {
try {
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
Request request = new Request.Builder()
.url("https://api.testproject.io/v2/projects/"+projectId+"/tests/"+testId)
.method("GET", null)
.addHeader("Authorization", "nzmo4DI08ykizYgcp9-5cCTArlxq7k7zt9MYhGmTcRk1")
.build();
Response response = client.newCall(request).execute();
System.out.println("================ this is our response headers ::: \n"+ response.headers());
} catch(Exception e) {
System.out.println(e);
}
}
#Bean
public ShallowEtagHeaderFilter shallowEtagHeaderFilter(){
return new ShallowEtagHeaderFilter();
}
}
I really need Your help since i cant generate any ETag parameter on my GET response header(after checking reponse.headers() ).
Thanks in advance!
I have a RestController defined as follows:
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.PostMapping
import javax.validation.Valid
#RestController
class CaseController(
private val caseService: CaseService,
private val remoteFileService: RemoteFileService,
private val clientService: ClientService
) {
#PostMapping("/api/v1/cases", consumes = [MediaType.APPLICATION_JSON_VALUE])
#NeedsAuth
fun createCase(
#RequestBody #Valid caseCreationRequest: CaseCreationRequest,
#RequestHeader("Api-Key", required = false) apiKey: String,
): ResponseEntity<Case> { }
I have defined NeedsAuth as an annotation.
The problem is that the #Valid annotation is being called before #NeedsAuth.
If I send invalid request body with invalid authentication, I receive "Validation Error" as response.
If I send valid request body with invalid authentication, I receive "Authentication Error".
If I remove #Valid annotation from code and then send invalid request body with invalid authentication, I receive "Authentication Error".
What I want this to do?
I want it to call #NeedsAuth before #Valid.
Any help is greatly appreciated.
Thanks
Update:
Code related to handling of #NeedsAuth:
//NeedsAuth.kt
package com.jimdo.debtcollectionservice.adapters.apis.http.auth
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FUNCTION)
annotation class NeedsAuth
//AuthAspect.kt
package com.jimdo.debtcollectionservice.adapters.apis.http.auth
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import org.springframework.web.context.request.RequestContextHolder
import org.springframework.web.context.request.ServletRequestAttributes
#Component
#Aspect
class AuthAspect {
#Autowired
lateinit var authTokenHandler: AuthTokenHandler
#Before("execution(* *.*(..)) && #annotation(NeedsAuth)")
fun validateToken(joinPoint: JoinPoint) {
val request = (RequestContextHolder.currentRequestAttributes() as ServletRequestAttributes).request
authTokenHandler.authenticateToken(request.getHeader("Api-Key"))
}
}
Say that Java application makes requests to http://www.google.com/... and there's no way to configure the inherited library (making such requests internally), so I can not stub or replace this URL.
Please, share some best practices to create a mock like
whenCalling("http://www.google.com/some/path").withMethod("GET").thenExpectResponse("HELLO")
so a request made by any HTTP client to this URL would be redirected to the mock and replaced with this response "HELLO" in the context of current JVM process.
I tried to find a solution using WireMock, Mockito or Hoverfly, but it seems that they do something different from that. Probably I just failed to use them properly.
Could you show a simple set up from the main method like:
create mock
start mock simulation
make a request to the URL by an arbitrary HTTP client (not entangled with the mocking library)
receive mocked response
stop mock simulation
make the same request as on step 3
receive real response from URL
Here's how to achieve what you want with the API Simulator.
The example demonstrates two different ways to configure Embedded API Simulator as HTTP proxy for the Spring's RestTemplate client. Check with the documentation of the (quote from the question) "inherited library" - often times Java-based clients rely on system properties described here or may offer some way to configure HTTP proxy with code.
package others;
import static com.apisimulator.embedded.SuchThat.*;
import static com.apisimulator.embedded.http.HttpApiSimulation.*;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Proxy.Type;
import java.net.URI;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import com.apisimulator.embedded.http.JUnitHttpApiSimulation;
public class EmbeddedSimulatorAsProxyTest
{
// Configure an API simulation. This starts an instance of
// Embedded API Simulator on localhost, default port 6090.
// The instance is automatically stopped when the test ends.
#ClassRule
public static final JUnitHttpApiSimulation apiSimulation = JUnitHttpApiSimulation
.as(httpApiSimulation("my-sim"));
#BeforeClass
public static void beforeClass()
{
// Configure simlets for the API simulation
// #formatter:off
apiSimulation.add(simlet("http-proxy")
.when(httpRequest("CONNECT"))
.then(httpResponse(200))
);
apiSimulation.add(simlet("test-google")
.when(httpRequest()
.whereMethod("GET")
.whereUriPath(isEqualTo("/some/path"))
.whereHeader("Host", contains("google.com"))
)
.then(httpResponse()
.withStatus(200)
.withHeader("Content-Type", "application/text")
.withBody("HELLO")
)
);
// #formatter:on
}
#Test
public void test_using_system_properties() throws Exception
{
try
{
// Set these system properties just for this test
System.setProperty("http.proxyHost", "localhost");
System.setProperty("http.proxyPort", "6090");
RestTemplate restTemplate = new RestTemplate();
URI uri = new URI("http://www.google.com/some/path");
ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class);
Assert.assertEquals(200, response.getStatusCode().value());
Assert.assertEquals("HELLO", response.getBody());
}
finally
{
System.clearProperty("http.proxyHost");
System.clearProperty("http.proxyPort");
}
}
#Test
public void test_using_java_net_proxy() throws Exception
{
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
// A way to configure API Simulator as HTTP proxy if the HTTP client supports it
Proxy proxy = new Proxy(Type.HTTP, new InetSocketAddress("localhost", 6090));
requestFactory.setProxy(proxy);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(requestFactory);
URI uri = new URI("http://www.google.com/some/path");
ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class);
Assert.assertEquals(200, response.getStatusCode().value());
Assert.assertEquals("HELLO", response.getBody());
}
#Test
public void test_direct_call() throws Exception
{
RestTemplate restTemplate = new RestTemplate();
URI uri = new URI("http://www.google.com");
ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class);
Assert.assertEquals(200, response.getStatusCode().value());
Assert.assertTrue(response.getBody().startsWith("<!doctype html>"));
}
}
When using maven, add the following to project's pom.xml to include the Embedded API Simulator as a dependency:
<dependency>
<groupId>com.apisimulator</groupId>
<artifactId>apisimulator-http-embedded</artifactId>
<version>1.6</version>
</dependency>
... and this to point to the repository:
<repositories>
<repository>
<id>apisimulator-github-repo</id>
<url>https://github.com/apimastery/APISimulator/raw/maven-repository</url>
</repository>
</repositories>
I am very new to Spring and I am having trouble getting anything back from a rest service. I would love to get an answer to what I am doing wrong. Thanks in an advance!
This is the rest service from where I am trying to get an answer:
Here is my code.
package testi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.util.Arrays;
#SpringBootApplication
public class TestiApplication {
public static void main(String[] args) {
SpringApplication.run(TestiApplication.class, args);
}
public void run(String... args) throws Exception {
RestTemplate restTemplate = new RestTemplate();
//Asetettaan otsikkotietueet
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add("X-ESA-API-KEY", "ROBOT");
HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);
System.out.println("Testing " + entity.getHeaders());
String answer = restTemplate.postForObject("https://www.veikkaus.fi/api/v1/sport-games/draws?game-names=MULTISCORE", entity, String.class);
System.out.println(answer);
//ResponseEntity<String> response = restTemplate.exchange("https://www.veikkaus.fi/api/v1/sport-games/draws?game-names=MULTISCORE", HttpMethod.GET, entity, String.class);
//System.out.println(response);
}
}
The API documentation you provided seems to indicate the API you're trying to access uses the GET method. You are making the request with the POST method (restTemplate.postForObject(...)).
Try using the GET method via restTemplate.getForObject(...).
In general, when debugging your REST calls, you should also examine the response status code in addition to the response body. In this case this arguably would have yielded the HTTP 405 error indicating what went wrong.
Following the example provided by spring.io for creating a simple REST service, I run into a strange issue.
If I make a request to localhost:8080/greeting the greeting route is called and I receive the expected response:
{"id":1, "content":"Hello, World!"}
If I add a route "/test" and then make an HTTP GET request to localhost:8080/test I get the expected response:
I'm a teapot
The problem arises when I do one of two things. Firstly, if I add HttpServletResponse or HttpServletRequest as a parameter to the test route and make an HTTP GET request to localhost:8080/test, the request hangs, the route is not called/executed, and maybe but not always the following is returned:
BODY: OK STATUS CODE: UNKNOWN 43
The second case is when I try to overcome this by using the #Autowire annotation. If I remove the HttpServletResponse/HttpServletRequest method parameters and instead autowire them in as class members, I get the same behavior.
If I make a request to any other invalid/undefined route e.g. HTTP GET localhost:8080/undefinedroute I receive the expected 404.
package hello;
import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
//#Autowired
//HttpServletRequest request;
//#Autowired
//HttpServletResponse response;
#RequestMapping("/test")
public String index() {
return HttpStatus.I_AM_A_TEAPOT.getReasonPhrase();
}
//#RequestMapping("/test")
//public String index(HttpServletResponse response) {
//response.setStatus(HttpStatus.I_AM_A_TEAPOT.ordinal());
//return HttpStatus.I_AM_A_TEAPOT.getReasonPhrase();
//}
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value="name", defaultValue="World") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
You cannot autowire HttpServletRequest or HttpServletResponse, because these objects are created when the server receives and handles an HTTP request. They are not beans in the Spring application context.
The status code of your response is 43 (unknown) because of this line:
response.setStatus(HttpStatus.I_AM_A_TEAPOT.ordinal()); // status 43?
ordinal() gives you the position of enum declaration, not the value of the status code. I_AM_A_TEAPOT is the 43rd enum declared in HttpStatus. The request hangs because 43 is an invalid status code and your browser does not know how to deal with it. You should use:
response.setStatus(HttpStatus.I_AM_A_TEAPOT.value()); // status 418