How does a Micronaut controller determine its base URL - java

For example if I have the following controller:
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.*;
#Controller("/test")
public class TestController {
#Get()
#Produces(MediaType.TEXT_PLAIN)
public String index() {
// How should this be implemented?
return "???";
}
}
and I run it on my-server, then I would like the index method to return http://my-server:8080.

Asof Micronaut V1.2.0, you can use the HttpHostResolver interface, for example:
import io.micronaut.http.*;
import io.micronaut.http.annotation.*;
import io.micronaut.http.server.util.HttpHostResolver;
import io.micronaut.web.router.RouteBuilder;
#Controller("/test")
public class TestController {
private final HttpHostResolver httpHostResolver;
private final RouteBuilder.UriNamingStrategy uriNamingStrategy;
public TestController(
HttpHostResolver httpHostResolver,
RouteBuilder.UriNamingStrategy uriNamingStrategy
) {
this.httpHostResolver = httpHostResolver;
this.uriNamingStrategy = uriNamingStrategy;
}
#Get()
#Produces(MediaType.TEXT_PLAIN)
public String index(HttpRequest httpRequest) {
return httpHostResolver.resolve(httpRequest) +
uriNamingStrategy.resolveUri(TestController.class);
}
}

This seems to work:
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.*;
import io.micronaut.runtime.server.EmbeddedServer;
import io.micronaut.web.router.RouteBuilder;
import java.net.*;
#Controller("/test")
public class TestController {
protected final String baseUrl;
public TestController(EmbeddedServer embeddedServer, RouteBuilder.UriNamingStrategy uns)
throws MalformedURLException {
final String host = embeddedServer.getHost();
final int port = embeddedServer.getPort();
final String file = uns.resolveUri(TestController.class);
baseUrl = new URL("http", host, port, file).toString();
}
#Get()
#Produces(MediaType.TEXT_PLAIN)
public String index() {
return baseUrl;
}
}
I'm not sure whether it's idiomatic, or whether it works in all cases. If someone posts a better answer I'll accept that.

If you want the controller to respond at / then use #Controller("/") instead of #Controller("/test").

package com.tech.api;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.PathVariable;
import java.util.List;
import javax.inject.Inject;
#Controller("/")
public class ModelDefinitionsApi {
#Get(uri="/modelName", produces = MediaType.APPLICATION_JSON)
public String getModel(#PathVariable String modelName) {
return "modelName";
}
}
http://my-server:8080 => main controller url
http://my-server:8080/modelName => for getModel method

Related

How to have a RestTemplate encode all characters with UriComponents and EncodingMode.VALUES_ONLY?

I want my REST client, using Spring Web's RestTemplate, to %-encode all special characters in URL parameters, not only illegal characters. Spring Web's documentation states that the encoding method can be changed by configuring the DefaultUriBuilderFactory used by RestTemplate with setEncodingMode(EncodingMode.VALUES_ONLY):
String baseUrl = "http://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(EncodingMode.VALUES_ONLY);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
this should "apply UriUtils.encode(String, Charset) to each URI variable value" which in turn will "encode all characters that are either illegal, or have any reserved meaning, anywhere within a URI, as defined in RFC 3986".
I wrote the following test case to try and demonstrate that changing to EncodingMode.VALUES_ONLY does not have the desired effect. (executing it with dependencies org.springframework.boot:spring-boot-starter:2.0.3.RELEASE, org.springframework:spring-web:5.0.7.RELEASE, org.springframework.boot:spring-boot-starter-test:2.0.3.RELEASE)
package com.example.demo.encoding;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import java.nio.charset.StandardCharsets;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;
#RunWith(SpringRunner.class)
#RestClientTest(DemoClient.class)
public class EncodingTest {
#Autowired private MockRestServiceServer mockServer;
#Autowired private DemoClient client;
#Test
public void encodeAllCharactersInParameter() {
mockServer.expect(requestTo(encodedQueryUrl("https://host", "+:/")))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess());
client.request("https://host", "+:/");
mockServer.verify();
}
private String encodedQueryUrl(final String baseUrl, final String parameter) {
return String.format("%s?parameter=%s", baseUrl,
UriUtils.encode(parameter, StandardCharsets.UTF_8));
}
}
#Component
class DemoClient {
private final RestTemplate restTemplate;
public DemoClient(RestTemplateBuilder restTemplateBuilder) {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
factory.setEncodingMode(EncodingMode.VALUES_ONLY);
restTemplateBuilder.uriTemplateHandler(factory);
this.restTemplate = restTemplateBuilder.build();
}
public Object request(final String url, final String parameter) {
UriComponents queryUrl = UriComponentsBuilder.fromHttpUrl(url)
.queryParam("parameter", parameter).build().encode();
return restTemplate.getForObject(queryUrl.toUri(), Object.class);
}
}
This test fails with java.lang.AssertionError: Request URI expected:<https://host?parameter=%2B%3A%2F> but was:<https://host?parameter=+:/>. So what am I doing wrong? Is it a bug in Spring Framework or does MockRestServiceServer decode URLs before verifying expectations?
Two issues in the example:
One, the request method prepares and encodes a java.net.URI externally, so the RestTemplate is not the one preparing it. You need to pass a URI template with a URI variable in it, so that the RestTemplate has a chance to prepare the URI and do the encoding. For example:
public Object request(final String url, final String parameter) {
String urlString = UriComponentsBuilder.fromHttpUrl(url)
.queryParam("parameter", "{param}")
.build()
.toUriString();
return restTemplate.getForObject(urlString, Object.class, parameter);
}
Or simply have request take the URI template string:
public Object request(final String url) {
return restTemplate.getForObject(url, Object.class, parameter);
}
// then invoke like this...
request("https://host?parameter={param}");
Two, the RestTemplateBuilder#uriTemplateHandler returns a new instance, so you need to use that for the configuration change to take effect:
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
factory.setEncodingMode(EncodingMode.VALUES_ONLY);
restTemplateBuilder = restTemplateBuilder.uriTemplateHandler(factory); // <<<< see here
this.restTemplate = restTemplateBuilder.build();
It works as expected with the above changes.
Note that https://jira.spring.io/browse/SPR-17039 will make it easier to also achieve the same effect using UriComponentsBuilder, so check for updates there.
Corrected example:
package com.example.demo.encoding;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import java.nio.charset.StandardCharsets;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;
#RunWith(SpringRunner.class)
#RestClientTest(DemoClient.class)
public class EncodingTest {
#Autowired private MockRestServiceServer mockServer;
#Autowired private DemoClient client;
#Test
public void encodeAllCharactersInParameter() {
mockServer.expect(requestTo(encodedQueryUrl("https://host", "+:/")))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess());
client.request("https://host", "+:/");
mockServer.verify();
}
private String encodedQueryUrl(final String baseUrl, final String parameter) {
return String.format("%s?parameter=%s", baseUrl,
UriUtils.encode(parameter, StandardCharsets.UTF_8));
}
}
#Component
class DemoClient {
private final RestTemplate restTemplate;
public DemoClient(RestTemplateBuilder restTemplateBuilder) {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
factory.setEncodingMode(EncodingMode.VALUES_ONLY);
this.restTemplate = restTemplateBuilder.uriTemplateHandler(factory).build();
}
public Object request(final String url, final String parameter) {
String urlString = UriComponentsBuilder.fromHttpUrl(url)
.queryParam("parameter", "{param}").build().toUriString();
return restTemplate.getForObject(urlString, Object.class, parameter);
}
}

Rest API cant't return ArrayList of Object which have composition of other object

My POJO Classes are like this
1.
/*crating MesageModel like this **/
/** ==========================================================================**/
package tech.sach.webapp.webserve.subpack;
import javax.xml.bind.annotation.XmlRootElement;
import tech.sach.webapp.webserve.link.Link;
#XmlRootElement
public class MessageModel{
int mesageid;
String mesgForm;
String name;
Link links; //id i comment this it works fine
public MessageModel() {
}
public MessageModel(int id,String a,String b,Link link )
{
this.mesageid=id;
this.mesgForm=a;
this.name=b;
this.links=link;
}
public MessageModel(int id,String a,String b )
{
this.mesageid=id;
this.mesgForm=a;
this.name=b;
}
public int getMesageid() {
return mesageid;
}
public void setMesageid(int mesageid) {
this.mesageid = mesageid;
}
public String getMesgForm() {
return mesgForm;
}
public void setMesgForm(String mesgForm) {
this.mesgForm = mesgForm;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Link getLinks() {
return links;
}
public void setLinks(Link links) {
this.links = links;
}
}
2.My second POJO class
/*Link is the object used in MessageModel */
package tech.sach.webapp.webserve.link;
public class Link {
String url;
String rel;
public Link(String url,String rel) {
this.url=url;
this.rel=rel;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getRel() {
return rel;
}
public void setRel(String rel) {
this.rel = rel;
}
}
And I am trying to send response like this using Rest JAX-RS API like this.
Below is the actual rest resource i am trying to call.
Return Arraylist
package tech.sach.webapp.webserve.subpack;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Scope;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import javax.xml.bind.annotation.XmlRootElement;
import tech.sach.webapp.webserve.link.Link;
#Path("subpack")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class SubpackageClassCall {
/*Creating arraylist*/
ArrayList<MessageModel> aArr=new ArrayList<MessageModel>();
/* Getting response*/
#GET
public ArrayList<MessageModel> getSubpack()
{
//Creating arraylikst here with dynamic MessageModel object
aArr.add(new MessageModel(1, "sac", "sdf"));
aArr.add(new MessageModel(2, "adsdsac", "csxcxcdf"));
aArr.add(new MessageModel(3, "sadasdcxcxcsac", "swwwwwdf"));
return aArr;
}
}
I am trying to call "http://localhost:8080/webserve/webapi/subpack" in postman or browser and I am getting following error
**"SEVERE: MessageBodyWriter not found for media type=application/json, type=class java.util.ArrayList, genericType=java.util.ArrayList<tech.sach.webapp.webserve.subpack.MessageModel>."**
So in 'SubpackageClassCall' class I am creating 'MessageModel' object dynamically but I am not doing anything related to its composed object 'Link'.
If I remove that composed 'Link' object from 'MessageModel' then its working fine.
But Why its not working when I include 'Link' object in 'MessageModel' ?

RestAssured Java: How to get header user and pass from setup method

I have a class which has the following
package com.example.misc;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.authentication.PreemptiveBasicAuthScheme;
import org.junit.BeforeClass;
public class QueryEndpoint {
#BeforeClass
public static void setup() {
RestAssured.port = 8010;
PreemptiveBasicAuthScheme authScheme = new PreemptiveBasicAuthScheme();
authScheme.setUserName("username123");
authScheme.setPassword("password123");
RestAssured.authentication = authScheme;
String basePath;
basePath = "/api/version1/";
RestAssured.basePath = basePath;
String baseHost;
baseHost = "http://localhost";
RestAssured.baseURI = baseHost;
}
}
Then in another class, I have a test...
package com.example.tests;
import com.example.misc.QueryEndpoint;
import org.junit.Test;
import static com.jayway.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
public class ApiTest extends QueryEndpoint{
#Test
public void verifyTopLevelURL() {
given()
.auth(). preemptive().basic("username", "password")// THIS LINE DON'T WORK, need to add here something?
.contentType("application/json")
.when().get("/123456789").then()
.body("fruit",equalTo("123456789"))
.body("fruit.apple",equalTo(37))
.body("fruit.red",equalTo("apple"))
.statusCode(200);
}
My Question is: How do I use the header + user + pass set in the method setup() and call that to be used in my test verifyTopLevelURL.
You can directly use static variable approach as you are inheriting ApiTest Class from QueryEndpoint Class. Here is the code snippet :
Your QueryEndpoint Class :
package com.example.misc;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.authentication.PreemptiveBasicAuthScheme;
import org.junit.BeforeClass;
public class QueryEndpoint {
static String userName = "username123";
static String password = "password123";
#BeforeClass
public static void setup() {
RestAssured.port = 8010;
PreemptiveBasicAuthScheme authScheme = new PreemptiveBasicAuthScheme();
authScheme.setUserName(userName);
authScheme.setPassword(password);
RestAssured.authentication = authScheme;
String basePath;
basePath = "/api/version1/";
RestAssured.basePath = basePath;
String baseHost;
baseHost = "http://localhost";
RestAssured.baseURI = baseHost;
}
}
Your ApiTest Class :
package com.example.tests;
import com.example.misc.QueryEndpoint;
import org.junit.Test;
import static com.jayway.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
public class ApiTest extends QueryEndpoint{
#Test
public void verifyTopLevelURL() {
given()
.auth(). preemptive().basic(userName, password)
.contentType("application/json")
.when().get("/123456789").then()
.body("fruit",equalTo("123456789"))
.body("fruit.apple",equalTo(37))
.body("fruit.red",equalTo("apple"))
.statusCode(200);
}
You can do same thing with headers too. Hope this helped.

Error Handling that give JSON

I'm trying to create a simple error handling project, that will give JSON with error data after receiving an error (for example 404, 422 or 500). I work with code from this site, but it's not working for me.
I actually have this two classes:
BasicController class
package com.mycompany.jsonerrorhandler;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
/**
* Class to catch all exception
*/
public class BasicController
{
#ExceptionHandler (Exception.class)
#ResponseStatus (HttpStatus.INTERNAL_SERVER_ERROR)
public ModelAndView handleAllExceptions(Exception ex)
{
return new JsonError(ex.getMessage()).asModelAndView();
}
}
JsonError class
package com.mycompany.jsonerrorhandler;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJacksonJsonView;
import com.google.common.collect.ImmutableMap;
/**
* Class that defines what JSON Error looks like
*/
public class JsonError
{
private final String message;
public JsonError(String message)
{
this.message = message;
}
public ModelAndView asModelAndView()
{
MappingJacksonJsonView jsonView = new MappingJacksonJsonView();
return new ModelAndView(jsonView, ImmutableMap.of("error", message));
}
}
I wonder what I need to connect them and receive JSON (or maybe there is other solution for this problem).
Based on the like you provided, the JsonError class should contain the following:
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJacksonJsonView;
import com.google.common.collect.ImmutableMap;
public class JsonError
{
private final String message;
public JsonError(String message) {
this.message = message;
}
public ModelAndView asModelAndView() {
MappingJacksonJsonView jsonView = new MappingJacksonJsonView();
return new ModelAndView(jsonView, ImmutableMap.of("error", message));
}
}

Unit Testing Spring MVC REST controllers when result Object/json contains a Long field type

I have a problem when trying to test the JSON output from a Spring REST Service using MockMvcResultMatchers where the returned object should contain a Long value.
The test will only pass when the value within the JSON object is is higher than Integer.MAX_VALUE. This seems a little odd to me as I feel that I should be able to test the full range of applicable values.
I understand that since JSON does not include type information it is performing a best guess at the type at de-serialisation, but I would have expected there to be a way to force the type for extraction when performing the comparison in the MockMvcResultMatchers.
Full code is below but the Test is:
#Test
public void testGetObjectWithLong() throws Exception {
Long id = 45l;
ObjectWithLong objWithLong = new ObjectWithLong(id);
Mockito.when(service.getObjectWithLong(String.valueOf(id))).thenReturn(objWithLong);
mockMvc.perform(MockMvcRequestBuilders.get("/Test/" + id))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$longvalue")
.value(Matchers.isA(Long.class)))
.andExpect(MockMvcResultMatchers.jsonPath("$longvalue")
.value(Matchers.equalTo(id)));
}
and the Result is:
java.lang.AssertionError: JSON path$longvalue
Expected: is an instance of java.lang.Long
but: <45> is a java.lang.Integer
at org.springframework.test.util.MatcherAssertionErrors.assertThat(MatcherAssertionErrors.java:80)
...
Any ideas or suggestions as to the proper way to fix this would be appreciated. Obviously I could just add Integer.MAX_VALUE to the id field in the test but that seems fragile.
Thanks in advance.
The following should be self contained apart from the third party libraries
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Service;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#RunWith(MockitoJUnitRunner.class)
public class TestControllerTest {
private MockMvc mockMvc;
#Mock
private RandomService service;
#InjectMocks
private TestController controller = new TestController();
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setMessageConverters(new MappingJackson2HttpMessageConverter())
.build();
}
#Test
public void testGetObjectWithLong() throws Exception {
Long id = 45l;
ObjectWithLong objWithLong = new ObjectWithLong(id);
Mockito.when(service.getObjectWithLong(String.valueOf(id))).thenReturn(objWithLong);
mockMvc.perform(MockMvcRequestBuilders.get("/Test/" + id))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$longvalue").value(Matchers.isA(Long.class)))
.andExpect(MockMvcResultMatchers.jsonPath("$longvalue").value(Matchers.equalTo(id)));
}
#RestController
#RequestMapping(value = "/Test")
private class TestController {
#Autowired
private RandomService service;
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ObjectWithLong getObjectWithLong(#PathVariable final String id) {
return service.getObjectWithLong(id);
}
}
#Service
private class RandomService {
public ObjectWithLong getObjectWithLong(String id) {
return new ObjectWithLong(Long.valueOf(id));
}
}
private class ObjectWithLong {
private Long longvalue;
public ObjectWithLong(final Long theValue) {
this.longvalue = theValue;
}
public Long getLongvalue() {
return longvalue;
}
public void setLongvalue(Long longvalue) {
this.longvalue = longvalue;
}
}
}
You can use anyOf Matcher along with a Class match against the Number super class and set it up like
.andExpect(MockMvcResultMatchers.jsonPath("$longvalue")
.value(Matchers.isA(Number.class)))
.andExpect(MockMvcResultMatchers.jsonPath("$longvalue")
.value(Matchers.anyOf(
Matchers.equalTo((Number) id),
Matchers.equalTo((Number) id.intValue()))));

Categories

Resources