Spring boot controller mapping to react component - java

I am switching routes in my react app using -
<Route path="/myBootDomain" render={props => {
//check route path
const onpathA = window.location.href.indexOf('/pathA') !== -1;
const onpathB = window.location.href.indexOf('/pathB') !== -1;
if (onpathA){ return <PathAComponent />;}
else if(onpathB){ return <onpathBComponent />;}
}}/>
When I run the app on localhost it works as expected, so I want my controllers to map the subdomain's to the correct route, example controller -
#CrossOrigin(maxAge = 3600)
#RestController
#RequestMapping("/pathA")
public class WebContentController {
#RequestMapping(method = RequestMethod.GET)
public void method(HttpServletRequest request, HttpServletResponse httpServletResponse) {
httpServletResponse.setHeader("Location", "/myBootDomain/pathA");
httpServletResponse.setStatus(302);
}
}
When trying to access http://localhost:8080/myBootDomain/pathA instead of redirecting to the index file I got redirected to the same controller(infinite loop).
My index.html is under /resources/static.
Thank for any help.

What you want to do is to serve same index.html file that contains scripts (React app) but from different paths, ie: /myBootDomain/pathA and /myBootDomain/pathA/foo/bar. It'll make sure that when user reloads the URL he gets the same React app which then executes routing logic. See #tashkhisi answer to get more context https://stackoverflow.com/a/62193394/906265
Now it depends how the server app is set up and which paths you want to make "catch-all" but there was a similar question already Spring catch all route for index.html

The way React works with route is different from something you might have worked before with server side frameworks like JSF, PrimeFaces and other simmilar frameworks.
Generally in React all routing is handled in client side(at least it should be done this way) and this approach is called single page application because you have one page in which components will be changed based on the route you have in client side and those components communicate with server and changed their content based on response they are delivered with server. Here you are handling your routing in client side with <Route> Component and in browser when path changed to /pathA, PathAComponent will be rendered and no request is sent to the server in this situation because React is controlling everything here). You should design your PathAComponent in such a way when it is rendered it call your method in the backend with Http Rest call and do something based on the response (for instance show status code of response to user).
check this link for further discussion
the more clear way to do something you want to do is shown bellow:
class App extends Component {
render() {
return (
<Router>
<Switch>
<Route path='/pathA' exact={true} component={PathAComponent}/>
<Route path='/pathB' exact={true} component={PathBComponent}/>
</Switch>
</Router>
)
}
}
here when path change to /pathA in client side PathAComponent will be rendered and when path changed to /pathB component PathBComponent will be rendered. no request send to server up to this point to communicate with server you should call your REST api directly in your PathAComponent and PathBComponent, generally in componentDidMount method of your components.
class PathAComponent extends Component {
constructor(props) {
super(props);
this.state = {status: false, isLoading: true};
}
componentDidMount() {
this.setState({isLoading: true});
fetch('/myBootDomain/pathA')
.then(response => this.setState({status: response.status, isLoading: false}));
}
render() {
return (
<div>
statusCode: {status}
</div>
);
}
}

Related

Java Spring controller fetching from React fetch()

I use React for fronted + Java Spring controllers that have some endpoints for backend. But for some of endpoints I have response with React features and for some without React features. I am new with React part, so I suppose that I am missing some principles of fetch(). Please help me to understand and fix.
Case 1
App.js code:
import React, {useState, useEffect} from 'react';
import './App.css';
function App () {
const [message, setMessage] = useState("");
useEffect(() => {
fetch('http://localhost:8080/api/v1/hello')
.then(response => response.text())
.then(message => {
setMessage(message);
});
},[])
return (
<div className="App">
<header className="">
<h1>React is here!</h1>
<h2 className="App-title">{message}</h2>
</header>
</div>
)
}
export default App;
Java controller code:
#RestController
#CrossOrigin
#RequestMapping(path = "/api/v1")
public class BasicController {
#GetMapping("/hello")
public String sayHello() {
return "Hello Everyone !";
}
}
As a result I see "React is here!" + "Hello Everyone !" with according style string on localhost:8080. So I see that the backend returns value and React also works with it.
But if I go to localhost:8080/api/v1/hello, I see only "Hello Everyone !" string without React features. So the backend returns value, but React doesn't work.
Why, if I am fetching this particular endpoint? - Question 1
Actually, the same result if I use
#RequestMapping(path = "api/v1")
without the first /
Case 2
I have the same App.js but change fetch(URL) to
fetch('http://localhost:8080')
And I add a new Controller:
#RestController
#CrossOrigin
#RequestMapping(path = "/")
public class StartPageController {
#GetMapping()
public String startPage() {
return "Start page";
}
}
If I go to localhost:8080, that I am fetching I see only "Start page" string without React features. So the backend returns value, but React doesn't work.
Why, if it is the simplest option for endpoint path and even more complex fetch worked as I mentioned above? - Question 2
Case 3
As it seems that I have some issue with "/" endpoint, I decided to check how the Case 1 endpoint will work if I leave StartPageController from the Case 2. So I just return back url:
fetch('http://localhost:8080/api/v1/hello'),
but leave both controllers.
As a result, for now, I see that React features doesn't work either for localhost:8080, or localhost:8080/api/v1/hello (the last one actually as in Case 1). Only backend values "Start page" or "Hello Everyone !" return for all mentioned endpoints. Without React.
So it seems that the "/" endpoint from StartPageController doesn't work with React by itself and also doesn't allow to work other more complex endpoints as a root endpoint.
So 2 questions as a result:
What is the issue with some particular paths - Case 1?
What is the issue with "/" endpoint - Case 2 and 3?
How I run Spring boot + React - I make a .jar file by Maven, where I collect both parts for frontend and backend by .pom build configurations. And I run the .jar in Intellij IDEA. I need to do it this way because I want to deploy .jar later on AWS Elastic Beanstalk.
Let's understand this code first :
#RestController
#CrossOrigin
#RequestMapping(path = "/api/v1")
public class BasicController {
#GetMapping("/hello")
public String sayHello() {
return "Hello Everyone !";
}
}
Probably your backend server is running at port 8080 and if you hit this url localhost:8080/api/v1/hello , it gives the output as "Hello Everyone" which you've returned from the controller right ? How do you expect to get React Part when you hit this endpoint ? When you run localhost:8080/api/v1/hello this is Spring Part [Java part] and not the react part. If you use this endpoint from the react app or any client you'll always get "Hello Everyone" as per your logic, same applies for the StartPage controller as well.
Try to run your react application, if you're fetching correctly, you'll see the backend part used with front end part. It's as simple as that.
See which port the react application is using ? It's probably localhost:3000 see if it's working or not. There, you'll see the result.
Hope this helps :)

Spring Cloud Zuul: RedirectView redirecting to service and not gateway

I'm a bit new to microservices and Spring. I have Spring Cloud microservices (ports: 8xxx-8xxx) with a Zuul gateway running on port 9000. There's a method inside a controller on a UI service which should do a login and then return to a index.html page:
#RequestMapping(value="/do-login", method = RequestMethod.POST)
public RedirectView doLogin (#ModelAttribute("authEntity") final AuthEntity authEntity, final Model model) {
model.addAttribute(VERSION, applicationVersion);
model.addAttribute("authEntity", new AuthEntity());
authenticatedStatus = true;
model.addAttribute(AUTHENTICATED, authenticatedStatus);
return new RedirectView("index");
}
The problem is that when above method completes it returns an url of the microservice itself localhost:8888/index but not localhost:9000/services/ui/.
If I use a simpler method:
#RequestMapping(value="/do-login", method = RequestMethod.POST)
public String doLogin (#ModelAttribute("authEntity") final AuthEntity authEntity, final Model model) {
model.addAttribute(VERSION, applicationVersion);
model.addAttribute("authEntity", new AuthEntity());
authenticatedStatus = true;
model.addAttribute(AUTHENTICATED, authenticatedStatus);
return "index";
}
This returns correctly an url of gateway localhost:9000/services/ui/do-login but with a /do-login which I do not need.
Maybe I can get rid of /do-login/ part of url? Or maybe there is a solution for the incorrect redirect?
Thanks in advance!
If you use relative path like in return "index"; the result of the POST request sent to http://localhost:9000/services/ui/do-login will include URLs to http://localhost:9000/... unless coded otherwise in the jsp / freemarker / thymeleaf file.
If you want to get rid of the do-login, you would need to implement what's called a Redirect After Post (or redirect after form submit) approach so that a page refresh doesn't resubmit the form. If you take this approach, which seem what you were doing when using: return new RedirectView("index");, I can think of a couple ways of fixing the URL and set it to the proxy host.
1) http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/view/RedirectView.html, there are a couple of constructors that takes a host parameter, you would need to inject the proxy host in the controller class and most-likely in every controller class that implements Redirect After Post.
2) http://tuckey.org/urlrewrite/, include UrlRewriteFilter and configure rules to rewrite from webapp host to proxy host when webapp http status code response is 302. With this approach it would only be once rule and no need to inject proxy host to controller classes or change the return new RedirectView("index");`
3) Maybe this rewriting is implemented in Zuul and you don't need include and configure UrlRewriteFilter as suggested in 2).
As a side note, I have configured Nginx's proxy_pass to a Java webapps (where I implemented Redirect After Post) in the past and I don't recall having this issue. Will have to take a look at both UrlRewriteFilter and Nginx config files to expand on this.
I found that this (thanks to answer in here: Spring redirect url issue when behind Zuul proxy) seems to work as required (but is considered a 'workaround'):
#RequestMapping(value="/do-login", method = RequestMethod.POST)
public void doLogin (#ModelAttribute("authEntity") final AuthEntity authEntity,
final Model model,
HttpServletResponse servletResponse) throws IOException {
...
String rUrl = ServletUriComponentsBuilder.fromCurrentContextPath().path("/").build().toUriString();
servletResponse.sendRedirect(rUrl);
}

Play 2.2 Return Json Response within Web Application

Is there a way to make a request RESTfully to a service that is on the same application and display it's response too? I've created a UI that has form parameters to fill out. When the user clicks submit, I'd like to have the response be embedded in the same page, displaying it to the user as json. I'd also like it to be able to be called externally of course, as it will be a restful api.
I can define in the routes file a path to return some json, but I'm not sure about how to consume it from the application itself.
I hope this is clear. I'd be happy to provide more details if necessary.
Ok. First of all, let's consider that we have the route and controller that produce JSON response for us.
GET /foo controllers.FooController.foo
object FooController extends Controller
{
.....
implicit val fooWrites = Json.writes[Foo]
def foo = Action {
Ok(Json.toJson(Foo)).as("application/json")
}
}
Then we can use ajax from our page to get the response:
<script>
......
$.get("#routes.FooController.foo")
.done(function(data){
//do something with recieved data
});
</script>
Or if you want to consume your data within your Play App,you may use WS lib. For example.
object FooController extends Controller {
....
def fooConsumerController = Action.async {
val fooJsonResultFuture =
WS.url("http://localhost:9000/foo").get().map(_.body)
.....
fooJsonResultFuture.map { json =>
// do something with this result
Ok(.....)
}
}
}
It's not very clear from your question, what behavior you want to achieve, but I hope it will help you to figure out some directions.

Play framework: redirect to a different domain

I am using Play 2 with Java and one of my controller methods returns a redirect:
return redirect(<some other domain>);
The client side call happens from an angular controller through $http:
$http.get("/signin").
...
This does not work; Firefox tells me to enable CORS. So I tried to enable CORS as suggested by the answers to this StackOverflow question. But I still get the same error. However that answer seems to be directed towards JSON responses. Do I need to do anything different for a redirect?
I would have thought that setting Access-Control-Allow-Origin to * would do the trick, but that doesn't seem to work.
Http 3xx redirection responses are transparent to AJAX calls. One possible solution for this problem is to return something else than 303 which can be resolved by AJAX. For example you can assume that all responses from your application with code 280 are intended for an AJAX redirection. Then your controller would look like this:
public class Application extends Controller {
public static Result signin() {
// ...
return status(280, "https://api.twitter.com/oauth/authenticate?oauth_token=" + requestToken.getToken());
}
}
On the client side you could check a result status code and react for code 280. Below there's a simple example with a page redirect but you can do anything you like with that response.
<script>
$(function() {
$.ajax({'url': '/signin', statusCode: {
280: function(response) {
window.location = response;
}
}});
});
</script>

How to use a java socket in tapestry5?

How do I implement a java socket in tapestry5?
What I want to do is create a socket which I can send an XmlHttpRequest over, through a piece of javascript code.
function sendPost(url, postdata, callback) {
xmlHttp=GetXmlHttpObject()
if (xmlHttp==null) {
alert ("Browser does not support HTTP Request")
return
}
xmlHttp.onreadystatechange=callback
xmlHttp.open("POST",url,true)
xmlHttp.send(postdata);
}
Where the URL is the socket i have just created.
So you want to do an AJAX request from your client code to the server, recieve a response and process it in some way? You will not need sockets. Instead, use Tapestry's built-in AJAX functionality.
If you're loading additional content inside your page via Javascript, chances are you will not need to write any code at all. Be sure you have read the AJAX section from the Tapestry docs, and you understand what a Zone is and how it works.
Here's a basic example. Template:
<div id="myZone" t:type="Zone" t:id="myZone">
... [Initial content, if any] ...
</div>
<a t:type="ActionLink" t:id="updateContent" t:zone="myZone">Update</a>
And class:
#Inject
private Zone myZone;
#Inject
private Request request;
#OnEvent(component = "updateContent")
Object updateContent() {
... [your code] ....
if (this.request.isXHR()) {
return this.myZone.getBody();
} else {
return this;
}
}
Tapestry will do everything else, like registering the proper event listener on the link and inserting the updated content in the proper place. The if (this.request.isXHR()) makes sure your page will degrade gracefully for clients without JavaScript enabled.
If you'd like to do something else entirely, like returning a JSON object and processing it with your own JavaScript code, you can return any of these JSON classes from your event handler.
Also, if you want to write your own client-side code, be sure to use the built-in, cross-browser AJAX functionality of Prototype, which ships with Tapestry.
Edit based on comment:
You won't be able to access a different server (host + port) through AJAX because of the same origin policy. You could, however, proxy the call through your Tapestry app. I've modified my code to illustrate this (assuming the thing listening on port 2112 is an HTTP server, otherwise change as needed):
#OnEvent(component = "updateContent")
Object updateContent() throws IOException {
final URL url = new URL("http://localhost:2112");
final HttpURLConnection con = url.openConnection();
final String content;
InputSteam input = null;
try {
input = con.getInputStream();
content = IOUtils.toString(input);
} finally {
IOUtils.closeQuietly(input);
}
return new StreamResponse() {
#Override
public String getContentType() {
return "text/javascript";
}
#Override
public InputStream getStream() throws IOException {
return new ByteArrayInputStream(content.getBytes("UTF-8"));
}
#Override
public void prepareResponse(Response response) {
response.setHeader("Expires", "0");
response.setHeader("Cache-Control",
"must-revalidate, post-check=0, pre-check=0");
}
}
}
Use of Sockets is independent of your webapp view framework - you would do it pretty much the same way regardless of how the view is coded. The only thing that changes is once you've implemented your code that uses sockets, is how that's invoked.
I used tapestry with spring, and so injecting services into the spring context is the most natural approach.
The services subpackage in tapestry is mostly for creating implementations that plug into tapestry, like encoders, property conduits, and binding factories. So whether you use this or not depends upon what you are trying to achieve.
For example, if you are creating a component that reads from a socket, and renders the data read in, then you can create that as a regular component, in the components subpackage.
The XmlHttpRequest will just do a web server request which can be handled perfectly well by whatever you use to run Tapestry in. There is no need to open sockets and stuff.
Just define a route in your wep application to accept the XmlHttpRequest and have a handler, servlet, controller, ... collect the necessary data, transform it to xml and send it to the Javascript component.
I found an example here

Categories

Resources