Server side rendering of different web apps using Spring MVC portlet - spring-mvc

I have two different applications A and B running on two different Azure Kubernetes clusters. I have to display app B within an iframe in App A. I tried using MVC portlet and it works when App A is accessed from within the private network. Is there any way I can use portlets to stream the APP B when not in private network? This is the code that loads App B within an iframe in App A .
public class BridgeViewPortlet extends GenericPortlet implements Serializable{
public void doView(final RenderRequest request, final RenderResponse response) throws IOException {
response.setContentType("text/html");
String url = request.getParameter("url");
// Set the content type of the response
response.setContentType("text/html");
// Get the output stream for the response
PrintWriter out = response.getWriter();
// Write the HTML for the website to the output stream
out.println("<iframe width=\"1200\" height=\"600\" src=" + url + "></iframe>");
}

Related

Integration Spring Reactive with Spring MVC + MySQL

Trying to figure out if I can use Spring Reactive (Flux/Mono) with Spring MVC ?
The structure of microservices using Spring MVC + Feign Client, Eureka Server (Netflix OSS), Hystrix, MySQL database.
My first microservice addDistanceClient adds data to the database.
Here is an example controller:
#RequestMapping("/")
#RestController
public class RemoteMvcController {
#Autowired
EmployeeService service;
#GetMapping(path = "/show")
public List<EmployeeEntity> getAllEmployeesList() {
return service.getAllEmployees();
}
}
Here I can use Mono/Flux, I think there will be no problems.
My second microservice is showDistanceClient - it is not directly connected to the database.
He has a method that calls the method (as described above) on the first microservice to retrieve data from the database.
It uses the Feign Client.
Second microservice controller:
#Controller
#RequestMapping("/")
public class EmployeeMvcController {
private ServiceFeignClient serviceFeignClient;
#RequestMapping(path = "/getAllDataFromAddService")
public String getData2(Model model) {
List<EmployeeEntity> list = ServiceFeignClient.FeignHolder.create().getAllEmployeesList();
model.addAttribute("employees", list);
return "resultlist-employees";
}
}
and ServiceFeignClient itself, with which we call the method on the first microservice, looks like this:
#FeignClient(name = "add-client", url = "http://localhost:8081/", fallback = Fallback.class)
public interface ServiceFeignClient {
class FeignHolder {
public static ServiceFeignClient create() {
return HystrixFeign.builder().encoder(new GsonEncoder()).decoder(new GsonDecoder()).target(ServiceFeignClient.class, "http://localhost:8081/", new FallbackFactory<ServiceFeignClient>() {
#Override
public ServiceFeignClient create(Throwable throwable) {
return new ServiceFeignClient() {
#Override
public List<EmployeeEntity> getAllEmployeesList() {
System.out.println(throwable.getMessage());
return null;
}
};
}
});
}
}
#RequestLine("GET /show")
List<EmployeeEntity> getAllEmployeesList();
}
It is working properly now. Those, if both microservices are OK, I get data from the database.
If the first microservice (addDistanceClient) is dead, then when I call the method on second microservice (showDistanceClient) to get data from the database through the first microservice (using Feign Client on second microservice), I get a page on which the spinner is spinning and the text that the service is unavailable, try again later. All perfectly.
My goal:
To do this using Spring Reactive (not sure if this will help me, but I think I'm thinking in the right direction) to make the message that the service is currently unavailable and the spinning spinner on the second microservice will automatically disappear and the data from the database will be displayed as soon as the first microservice (addDistanceClient) will come to life again (without re-sending the request, i.e. without reloading the page).
Will I be able to do this through Spring WebFlux ?
I know that a stream is used through Spring WebFlux, which itself will notify us if data appears in it, we do not need to resubmit the request here.
I started thinking about this and cannot figure out how to do this:
1) using Spring Reactive
In this case, I need to implement Flux/Mono into the MVC model in the second showDistanceClient microservice, which returns HTML. I don't understand how. I know how to do this with REST.
2) If the first item is incorrect, maybe I need to use a WebSocket for this ?
If so, please share useful links with examples. I will be very grateful.
Indeed, this topic is very interesting to me and I want to understand it.
I will be very grateful for your help. Thanks everyone!
UPDATED POST:
I updated both controllers with REST + WebFlux. Everything works for me.
The first addDistanceClient service and its controller:
#RestController
#RequestMapping("/")
public class BucketController {
#Autowired
private BucketRepository bucketRepository;
// Get all Bucket from the database (every 1 second you will receive 1 record from the DB)
#GetMapping(value = "/stream/buckets/delay", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Bucket> streamAllBucketsDelay() {
return bucketRepository.findAll().delayElements(Duration.ofSeconds(5));
}
}
He pulls out all the records from the database with an interval of 5 seconds each record. I added an interval for an example to test.
The second service is showDistanceClient and its controller.
Here I used WebClient instead of Feign Client.
#RestController
#RequestMapping("/")
public class UserController {
#Autowired
private WebClient webClient;
#Autowired
private WebClientService webClientService;
// Using WebClient
#GetMapping(value = "/getDataByWebClient",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Bucket> getDataByWebClient() {
return webClientService.getDataByWebClient();
}
}
and its Service layer (WebClientService):
#Service
public class WebClientService {
private static final String API_MIME_TYPE = "application/json";
private static final String API_BASE_URL = "http://localhost:8081";
private static final String USER_AGENT = "User Service";
private static final Logger logger = LoggerFactory.getLogger(WebClientService.class);
private WebClient webClient;
public WebClientService() {
this.webClient = WebClient.builder()
.baseUrl(API_BASE_URL)
.defaultHeader(HttpHeaders.CONTENT_TYPE, API_MIME_TYPE)
.defaultHeader(HttpHeaders.USER_AGENT, USER_AGENT)
.build();
}
public Flux<Bucket> getDataByWebClient() {
return webClient.get()
.uri("/stream/buckets/delay")
.exchange()
.flatMapMany(clientResponse -> clientResponse.bodyToFlux(Bucket.class));
}
}
Now everything works in a reactive environment. Fine.
But my problem remained unresolved.
My goal: everything works, everything is fine, and if I suddenly called on the second service a method that using WebClient called the first service to get the data, and at that moment my first service died, I received a message that the service is temporarily unavailable and then my first service My request for data was revived and I received all the data and instead of reporting that the service was temporarily unavailable I would get all the data (important: without reloading the page).
How do I achieve this ?

How to get base uri of site on application start?

I have asp.net mvc application. I was using the following code to get base URI inside controller.
var baseApplicationUrl = string.Format("{0}://{1}", HttpContext.Current.Request.Url.Scheme, HttpContext.Current.Request.Url.Authority);
this was working fine.
Is there a way to construct baseuri in Application_Start()?
In application start method HttpContext.Current.Request will throw exception
Update 1
I have a UISettings class which holds some links. The base URI of these links would be different based on how the application is hosted. For example it could be http://www.example.com/home/index or https://www.example.com/home/index or http://www.example.com/subdomain/home/index. so home/index has different base uri depends on how its hosted.
I understand that the request is not available on application start. But i wanted to load the UISettings on application start so i can register with DI framework as singleton instance.
public class UISettings
{
public string Link1 {get;set;}
public string Link2 {get;set;}
public static UiSettings Load()
{
// need to get baseURI here???
var settings = new UISettings();
settings.Link1 = baseURI + "/home/index";
return settings;
}
}
and then register with DI framework as singleton on application start so i can inject it in any class latter
container.RegisterInstance<UISettings>(UiSettings.Load());
I'm not sure what you are trying to accomplish here. At Application_Start() stage Request is not constructed yet, so I think the earliest time you can get URI from Current Request is in Aplication_BeginRequest(), before then it is your starting page that can be assigned in and used from WebConfig file.

Spring Boot - MockMVC forwardedUrl using Thymeleaf

I have a basic SpringBoot app. using Spring Initializer, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file.
I have this controller:
#Controller
#RequestMapping("/deviceevent")
public class DeviceEventController {
#RequestMapping(value={ "/list"}, method = { RequestMethod.GET})
public String deviceeventList() {
return "tdk/deviceEvent/DeviceEventList";
}
}
and this other test class. Tests using Spring's MockMVC framework. This drives an MVC application in a test, as if it was running in a container,
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#WebMvcTest
public class MockMvcTests {
// Pull in the application context created by #ContextConfiguration
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
// Setup MockMVC to use our Spring Configuration
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void getDeviceEventsTest() throws Exception {
this.mockMvc
.perform(get("/deviceevent/list") //
.accept(MediaType.parseMediaType("text/html;charset=UTF-8")))
.andExpect(status().isOk()) //
.andExpect(model().size(1)) //
.andExpect(forwardedUrl("tdk/deviceEvent/DeviceEventList"));
}
But I got this error in the forwarded URL. I always used this method in JSP, never in Thymeleaf, but I guess that It is the same:
java.lang.AssertionError: Forwarded URL expected:</tdk/deviceEvent/DeviceEventList> but was:<null>
Assuming a standard Thymeleaf/Spring setup, it looks like there is a misunderstanding with what the controller is doing - when the controller returns that string "tdk/deviceEvent/DeviceEventList" it is not forwarding the HTTP request somewhere, but returning a view name.
With a normal Spring-thymeleaf setup, that string corresponds to the name of a thymeleaf view that will be rendered on hitting that endpoint (I assume the controller is just serving a normal webpage - so that path probably corresponds to some file path most likely in src/main/resources - but again, this depends a lot on your spring config) - at this point the HTTP request has not been returned to the user, and Spring is still processing it - and will attempt to render the HTML view before returning to the user.
The forwarded URL is used if Spring is not rendering anything but instead returning a HTTP response to the user to forward them to another URL (which will start a different Spring request-response process) using a 301/302 mechanism.
Note the difference in the following methods:
#RequestMapping( value="/document", method=RequestMethod.GET )
public String newDocumentSettings( Model model ){
model.addAllAttributes( contentManagementService.documentToJson() );
return "pages/document-settings";
}
#RequestMapping( value="/document", method=RequestMethod.POST )
public String createNewDocument( #RequestParam String title, #RequestParam String overview, #RequestParam String tags ){
Document doc = documentService.createDocument( title, overview, tags );
return "redirect:/document/${doc.url}/1?getting-started";
}
The first renders the template at the given filepath, the second returns a redirect command to the browser to make another HTTP request to the given URL.
In any case, the forwardedUrl in your test case is because hte HTTP Response doesn't have a URL to forward to (because its returning the HTML). If you do want forwarding behaviour (e.g. you actually want to complete the response and the browser to make a second HTTP request) then you would likely need to update the controller as per example, however, if you are happy with the rendered html page, then the test is invalid (look at the Thymeleaf testing framework to see how to test templating).
Caveat: This is based on the assumption of default Spring-Boot config - if you have other config whereby that string does result in a forwarded HTTP request then this doesnt apply!
Taking a guess here, but the URL tdk/deviceEvent/DeviceEventList is probably not defined. Try replacing it with the URL associated with your context (edit as necessary):
#Test
public void getDeviceEventsTest() throws Exception {
this.mockMvc
.perform(get("/deviceevent/list")
.accept(MediaType.parseMediaType("text/html;charset=UTF-8")))
.andExpect(status().isOk())
.andExpect(model().size(1))
.andExpect(forwardedUrl("/WEB-INF/tdk/deviceEvent/DeviceEventList.html"));
}
Aside, instead of:
#RequestMapping(value={ "/list"}, method = { RequestMethod.GET})
you can use the shorthand:
#GetMapping("/list")

Spring Cloud Netflix : Passing host request parameter via RequestInterceptor to FeignClient

I am building a Spring Cloud project (Brixton.M4 with Spring Boot 1.3.1) with Eureka, Zuul and FeignClient where I am trying to add multi tenancy support (Tenants are identified by subdomain : tenant1.myservice.com). To do so, I would like to somehow pass the original subdomain along requests that are forwarded from a service to the other via Feign but I can't seem to be able to find the right way to do it.
What I have is a client that exposes a #RestController which calls a #FeignClient to communicate with my backend which exposes server operations to the client through its own #RestController.
The #FeignClient using same interface as my #RestController on the server :
#FeignClient(name = "product")
public interface ProductService extends IProductService {
}
What I am currently trying to do is set a header in a RequestInterceptor :
#Component
public class MultiTenancyRequestInterceptor implements RequestInterceptor {
private CurrentTenantProvider currentTenantProvider;
#Autowired
public MultiTenancyRequestInterceptor(CurrentTenantProvider currentTenantProvider) {
this.currentTenantProvider = currentTenantProvider;
}
#Override
public void apply(RequestTemplate template) {
try {
template.header("TENANT", currentTenantProvider.getTenant());
} catch (Exception e) {
// "oops"
}
}
}
My provider class is a simple component where I'm trying to inject a request / session scope bean :
#Component
public class CurrentTenantProvider {
#Autowired
private CurrentTenant currentTenant;
//...
}
The bean (I tried both session and request scope) :
#Bean
#Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public CurrentTenant currentTenant() {
return new CurrentTenant();
}
On the server, I use Hibernate multitenant provider that is supposed to catch the header value and use it to define which DB to connect to :
#Autowired
private HttpServletRequest httpRequest;
#Override
public String resolveCurrentTenantIdentifier() {
return httpRequest.getHeader("TENANT");
}
It seems the Feign call to the server is done in another thread and out of the incoming request scope, so i'm not sure how to pass that value along.
It all works fine when I hardcode the tenant value in the RequestInterceptor so I know the rest is working properly.
I have also looked at many other posts about Zuul "X-Forwaded-For" header and cannot find it in the request received on the server. I have also tried adding a ZuulFilter to pass host name to next request but what I see is that original request to the Client is picked up by the ZuulFilter and I can add but not when the Feign request is sent to the backend service even if I map it in zuul (i guess that is intended ?).
I am not really sure what's the next step and would appreciate some suggestions.
Hope that it's of any use for you but we're doing sth similar in Spring-Cloud-Sleuth but we're using a ThreadLocal to pass span between different libraries and approaches (including Feign + Hystrix).
Here is an example with the highlighted line where we retrieve the Span from the thread local: https://github.com/spring-cloud/spring-cloud-sleuth/blob/master/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceFeignClientAutoConfiguration.java#L123

JAAS Custom Login Module with Wildfly

I'm trying to develop a custom JAAS login module, which consumes a token and get's the user data from different sources.
It should work as an authentication realm for wildfly 8.2.1 final. I've got a test servlet in which I want to authenticate so the allowed roles for a injected EJB is checked.
The test from this site: http://www.radcortez.com/custom-principal-and-loginmodule-for-wildfly/ In fact I even started with his git project.
Because I only use a token, I can not use request.login(username, password) to initiate the wildfly login process. So I tried this:
#Inject
private SampleEJB sampleEJB;
...
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String token = request.getParameter("token");
try{
context = new LoginContext("TokenLoginDomain", new TokenCallbackHandler(token));
context.login();
}catch(Exception e){
e.printStackTrace();
}
response.getWriter().println("username=" + exampleEJB.getPrincipalName());
}
My EJB looks like this:
#Stateless
public class SampleEJB {
#Resource
private EJBContext ejbContext;
#RolesAllowed("partner")
public String getPrincipalName() {
return ejbContext.getCallerPrincipal().getName();
}
}
How do I start the login process without credentials in a servlet, so the user is logged in as wildfly user?
You can't authenticate that way in Java EE. With the given code you'll only be authenticated within the LoginContext instance.
As you already found out request.login(username, password) triggers the authentication process, but it's limited to the username/password credential.
In this case you should not only write a custom LoginModule (identity store), but a custom authentication mechanism as well. Authentication mechanisms are the controllers so to speak in the authentication process, while the identity store is the model.
Custom authentication mechanisms can be added via the JASPIC SPI, which WildFly supports well.
You can register a ServerAuthModule (SAM) from the app with some utility code that you can copy from here: https://github.com/arjantijms/glassfish-sam-ee-namespaces/tree/master/src/main/java/javax/security/authenticationmechanism
Then actually register the SAM with code as shown here:
https://github.com/arjantijms/glassfish-sam-ee-namespaces/blob/master/src/main/java/test/SamAutoRegistrationListener.java

Resources