What's the best way to write integration tests that uses the new Spring 6 http interfaces with a mock server? Example:
#Bean
Blah configService() {
var client = WebClient.builder().baseUrl(baseUrl)
.defaultStatusHandler(HttpStatusCode::is4xxClientError, resp -> Mono.empty())
.build();
var proxyFactory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
return proxyFactory.createClient(Blah.class);
}
interface BlahService {
#GetExchange("/ok")
ResponseEntity<Blah> getBlah();
}
basically trying to discover a reasonable way to use the mock server from okhttp (https://github.com/spring-projects/spring-framework/blob/main/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java)
with the server.url as a property I can inject the value and use it in test env as base url in my WebClient config.
Thanks.
I can't find anything specific on the web on this topic yet.
You will need to use #DynamicPropertySource to inject that property in your test class like so
#DynamicPropertySource
static void properties(DynamicPropertyRegistry registry) {
registry.add("server.url", () -> "http://localhost:" + mockWebServer.getPort());
}
Related
Is it possible to get PathPattern in the SpringBoot web as a Bean and reuse it in user code?
For example, if the url is : /user/1990/lily, it return the url patten on the Controller: /user/{year}/{name}.
This said:
Patterns are parsed on startup and re-used at runtime for efficient
URL matching
Reactor-netty metrics need a uriTagValue to avoid cardinality explosion,
public class Application {
public static void main(String[] args) {
Metrics.globalRegistry
.config()
.meterFilter(MeterFilter.maximumAllowableTags("reactor.netty.http.server", "URI", 100, MeterFilter.deny()));
DisposableServer server =
HttpServer.create()
.metrics(true, s -> { // HERE is the uriTagValue, it's a smaple of how to handle url mapping.
if (s.startsWith("/stream/")) {
return "/stream/{n}";
}
else if (s.startsWith("/bytes/")) {
return "/bytes/{n}";
}
return s;
})
.route(r ->
r.get("/stream/{n}",
(req, res) -> res.sendString(Mono.just(req.param("n"))))
.get("/bytes/{n}",
(req, res) -> res.sendString(Mono.just(req.param("n")))))
.bindNow();
server.onDispose()
.block();
}
}
Config the Netty to enable metrics in a SpringBoot WebFlux app:
#Configuration
public class NettyWebServerConfig {
#Bean
public ReactiveWebServerFactory reactiveWebServerFactory() {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();
factory.addServerCustomizers(httpServer -> httpServer
.wiretap(true)
.metrics(true, s -> "") // enable metrics, ignore all uri, if SpringBoot Web expose URI-Match-Patterns as Bean, we can use it here.
);
return factory;
}
}
My wondering is that is it possible to get PathPattern as a Bean in the SpringBoot web and reuse it in reactor-netty metrics code? As simmple as: bestPattern.matchAndExtract(lookupPath)
I tested PathContainer.parsePath(s);, it seems doesn't work.
With this setup, you are not using Spring WebFlux but actually Reactor Netty directly. PathContainer and PathPattern are then irrevelant here.
I don't think reactor-netty is storing anywhere the matching UriPathTemplate when considering the HttpPredicate.
Is it possible to mock CordaRPCops so as to execute a flow in project without creating a standalone node or in-memory node (like in a mock network) ? Kindly let me know.
I am also adding a link which I found informative regarding this from github issues QA
There is no specific class available mock CordaRPCops in the TestDSL. If you referring to mock some of the fuctionality of the node for cordapp testing, you should use the MockNode.
If you want to Mock CordaRPCops in the client app, you could use mockito to do so, example below:
Test:
#Test
public void testGetStateList(){
CordaRPCOps cordaRPCOps = Mockito.mock(CordaRPCOps.class);
Service service = new Service(cordaRPCOps);
Vault.Page<MyState> myStatePage =
new Vault.Page<>(Collections.EMPTY_LIST, Collections.EMPTY_LIST, 0L, Vault.StateStatus.ALL, Collections.EMPTY_LIST);
Mockito.when(cordaRPCOps.vaultQuery(MyState.class)).thenReturn(myStatePage);
service.getStateList();
}
Service:
public class Service {
CordaRPCOps cordaRPCOps;
public Service(CordaRPCOps cordaRPCOps) {
this.cordaRPCOps = cordaRPCOps;
}
public List<StateAndRef<MyState>> getStateList() {
return cordaRPCOps.vaultQuery(MyState.class).getStates();
}
}
#ReactiveFeignClient(name = "service.b",configuration = CustomConfiguration.class)
public interface FeingConfiguration {
#PostMapping("/api/students/special")
public Flux<Student> getAllStudents(#RequestBody Flux<SubjectStudent> lista);
}
Help, how can I add a basic authentication to my header that I have in the service: service.b.
I have the CustomConfiguration.class class but it doesn't allow me, I have 401 authorization failed
#Configuration
public class CustomConfiguration {
#Bean
public BasicAuthRequestInterceptor basic() {
return new BasicAuthRequestInterceptor("user","user") ;
}
Looks like you are trying to use feign-reactive (https://github.com/Playtika/feign-reactive) to implement your REST clients. I am also using it for one of my projects and it looks like this library does not have an out-of-the-box way to specify basic auth credentials. At least no way to do this declaratively. So I didn't find a better way to do this than to abandon the auto-configuration via #ReactiveFeignClient and start configuring reactive feign clients manually. This way you can manually add "Authorization" header to all outgoing requests. So, provided this client definition:
public interface FeingClient {
#PostMapping("/api/students/special")
public Flux<Student> getAllStudents(#RequestBody Flux<SubjectStudent> lista);
}
Add the following configuration class to your Spring context, replacing username, password and service-url with your own data:
#Configuration
public class FeignClientConfiguration {
#Bean
FeignClient feignClient() {
WebReactiveFeign
.<FeignClient>builder()
.addRequestInterceptor(request -> {
request.headers().put(
"Authorization",
Collections.singletonList(
"Basic " + Base64.getEncoder().encodeToString(
"username:password".getBytes(StandardCharsets.ISO_8859_1))));
return request;
})
.target(FeignClient.class, "service-url");
}
}
Note, that this API for manual configurftion of reactive feign clients can differ between different versions of the reactive-feign library. Also note that this approach has a major drawback - if you start creating beans for your feign clients manually you lose the main advantage of Feign - ability to write REST-clients declaratively with just a few lines of code. E.g. if you want to use the above client with some sort of client-side load-balancing mechanism, like Ribbon/Eureka or Ribbon/Kubernetes, you will also need to configure that manually.
You can use a direct interceptor:
#Configuration
class FeignClientConfiguration {
#Bean
fun reactiveHttpRequestInterceptor(): ReactiveHttpRequestInterceptor {
return ReactiveHttpRequestInterceptor { request: ReactiveHttpRequest ->
request.headers()["Authorization"] = //insert data from SecurityContextHolder;
Mono.just(request)
}
}
}
I was trying to setup Feign to work with RibbonClient, something like MyService api = Feign.builder().client(RibbonClient.create()).target(MyService.class, "https://myAppProd");, where myAppProd is an application which I can see in Consul. Now, if I use Spring annotations for the Feign client (#FeignClient("myAppProd"), #RequestMapping), everything works as Spring Cloud module will take care of everything.
If I want to use Feign.builder() and #RequestLine, I get the error:
com.netflix.client.ClientException: Load balancer does not have available server for client: myAppProd.
My first initial thought was that Feign was built to work with Eureka and only Spring Cloud makes the integration with Consul, but I am unsure about this.
So, is there a way to make Feign work with Consul without Spring Cloud?
Thanks in advance.
In my opinion, it's not feign work with consul, its feign -> ribbon -> consul.
RibbonClient needs to find myAppProd's serverList from its LoadBalancer.
Without ServerList, error: 'does not have available server for client'.
This job has been done by SpringCloudConsul and SpringCloudRibbon project, of course you can write another adaptor, it's just some glue code. IMHO, you can import this spring dependency into your project, but use it in non-spring way . Demo code:
just write a new feign.ribbon.LBClientFactory, that generate LBClient with ConsulServerList(Spring's class).
public class ConsulLBFactory implements LBClientFactory {
private ConsulClient client;
private ConsulDiscoveryProperties properties;
public ConsulLBFactory(ConsulClient client, ConsulDiscoveryProperties consulDiscoveryProperties) {
this.client = client;
this.properties = consulDiscoveryProperties;
}
#Override
public LBClient create(String clientName) {
IClientConfig config =
ClientFactory.getNamedConfig(clientName, DisableAutoRetriesByDefaultClientConfig.class);
ConsulServerList consulServerList = new ConsulServerList(this.client, properties);
consulServerList.initWithNiwsConfig(config);
ZoneAwareLoadBalancer<ConsulServer> lb = new ZoneAwareLoadBalancer<>(config);
lb.setServersList(consulServerList.getInitialListOfServers());
lb.setServerListImpl(consulServerList);
return LBClient.create(lb, config);
}
}
and then use it in feign:
public class Demo {
public static void main(String[] args) {
ConsulLBFactory consulLBFactory = new ConsulLBFactory(
new ConsulClient(),
new ConsulDiscoveryProperties(new InetUtils(new InetUtilsProperties()))
);
RibbonClient ribbonClient = RibbonClient.builder()
.lbClientFactory(consulLBFactory)
.build();
GitHub github = Feign.builder()
.client(ribbonClient)
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
List<Contributor> contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
interface GitHub {
#RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(#Param("owner") String owner, #Param("repo") String repo);
}
public static class Contributor {
String login;
int contributions;
}
}
you can find this demo code here, add api.github.com to your local consul before running this demo.
Using Spring RestTemplate to invoke client rest calls, would it be possible to throttle these calls?
E.g. max 10 concurrent calls.
The RestTemplate itself does not seem to provide this itself so I wonder what the options are.
It would be best to have a generic solution to e.g. also throttle SOAP calls.
From the docs:
To create an instance of RestTemplate you can simply call the default
no-arg constructor. This will use standard Java classes from the
java.net package as the underlying implementation to create HTTP
requests. This can be overridden by specifying an implementation of
ClientHttpRequestFactory. Spring provides the implementation
HttpComponentsClientHttpRequestFactory that uses the Apache
HttpComponents HttpClient to create requests.
HttpComponentsClientHttpRequestFactory is configured using an instance
of org.apache.http.client.HttpClient which can in turn be configured
with credentials information or connection pooling functionality.
I'd look into configuring RestTemplate to use HTTP Components and play with setMaxPerRoute and setMaxTotal. If your SOAP client also happens to be using HTTP Components there may be a way to share the Commons HTTP Components settings between the two.
The other option is to roll your own. You could create a Proxy that uses a Semaphore to block until another request is finished. Something along these lines (note that this code is totally untested and is only to communicate the general idea of how you'd implement this):
public class GenericCounterProxy implements InvocationHandler
{
private final Object target;
private final int maxConcurrent;
private final Semaphore sem;
GenericCounterProxy(Object target, int maxConcurrent)
{
this.target = target;
this.maxConcurrent = maxConcurrent;
this.sem = new Semaphore(maxConcurrent, true);
}
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try
{
// block until acquire succeeds
sem.acquire()
method.invoke(target, args);
}
finally
{
// release the Semaphore no matter what.
sem.release();
}
}
public static <T> T proxy(T target, int maxConcurrent)
{
InvocationHandler handler = new GenericCounterProxy(target, maxConcurrent);
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler);
}
}
If you wanted to go with this type of approach:
You should probably refine the methods for which the proxy acquires the Semaphore since not every method on the target would be subject to throttling (for example, getters for settings).
You need to change from RestTemplate to RestOperations which is an interface or change the proxying mechanism to use class based proxying.