Code to get a Java servlet to act as a proxy? - java

I have two Java web applications that have a single servlet that gets mapped to a specific URL:
red.war/
WEB-INF/classes
com.me.myorg.red.RedServlet (maps to http://red.example.com/doStuff)
blue.war/
WEB-INF/classes
com.me.myorg.blue.BlueServlet (maps to http://blue.example.com/doStuff)
I want to put these application (I'm calling them my "backend apps") behind a "proxy app" (servlet) that will decide which of these two apps will ultimately service a client-side request.
This proxy web app would take an incoming HTTP request, and determines which of the 2 "backend apps" (red or blue) to forward the request onto. The request would then be forwarded on to either http://red.example.com/doStuff (and then processed by RedServlet#doGet(...)) or http://blue.example.com/doStuff (and then processed by BlueServlet#doGet(...)). The returned response from the backend app (again, either RedServlet#doGet(...) or BlueServlet#doGet(...)) would then be returned to the proxy servlet, and ultimately returned to the client.
In other words, in pseudo-code:
public class ProxyServlet extends HttpServlet {
#Override
public doGet(HttpServletRequest request, HttpServletResponse response) {
String forwardingAddress;
if(shouldBeRed(request))
forwardingAddress = "http://red.example.com/doStuff";
else
forwardingAddress = "http://blue.example.com/doStuff";
PrintWriter writer = response.getWriter();
writer.write(getResponseFromBackend(forwardingAddress, request));
}
private String getResponseFromBackend(String addr, HttpServletRequest req) {
// Somehow forward req to addr and get HTML response...
}
}
Is this possible? If so, how and what code would I need to write to make it work?

You could use a RequestDispatcher to forward your request in the following way:
RequestDispatcher dispatcher = httpRequest.getRequestDispatcher(forwardingAddress);
// here you have the choice whether to use include(..) or forward(..) see below
if(useInclude)
dispatcher.include(httpRequest, httpResponse);
else
dispatcher.forward(httpRequest, httpResponse);
... where useInlcude is set to your choice with the following options:
includeThis is probably what you want to do: Load the content from the forwardingAdress into your response.
This means you could even include multiple targets into a single response.
The client will not even realize this procedure nor does he need to be able to see the target document.
forwardSend a forward to the forwardingAddress. This will tell the client to submit a new request to the specified URL.
If you do it in a browser with developer tools, you will see a second request.
The client must be able to see and load the target URL.
You can only forward to a single target.
See, the following links, too:
RequestDispatcher javadoc, especially for the notes:
forward should be called before the response has been committed to the client (before response body output has been flushed). If the response already has been committed, this method throws an IllegalStateException. Uncommitted output in the response buffer is automatically cleared before the forward.
include: The request and response parameters must be either the same objects as were passed to the calling servlet's service method or be subclasses of the ServletRequestWrapper or ServletResponseWrapper classes that wrap them.
URLRewriteFilter examplealthough this example is implemented using a Filter instead of a Servlet the behavior is the same (Note: this example is part of a framework of mine and hence contains some overhead in the parent classes. Just have a look at the relevant section...)

Since there is not yet an approved answer I try to write how I see the solution to this request use apache-http-commons library. In addition I suggest to add a flush on writer.
public class ProxyServlet extends HttpServlet {
#Override
public doGet(HttpServletRequest request, HttpServletResponse response) {
String forwardingAddress;
if(shouldBeRed(request))
forwardingAddress = "http://red.example.com/doStuff";
else
forwardingAddress = "http://blue.example.com/doStuff";
PrintWriter writer = response.getWriter();
writer.write(getResponseFromBackend(forwardingAddress, request));
**writer.flush();**
}
private String getResponseFromBackend(String addr, HttpServletRequest req) {
HttpClient client = new HttpClient();
HttpMethod method = new GetMethod(url);
client.executeMethod(method);
String body=method.getResponseBodyAsString();
return body;
}
}

Related

cross origin servlet theoretical question

I have a theoretical doubt on CORS implementation.
A way to enable cross-origin requests is to set a specific Header on the response:
private void setAccessControlHeaders(HttpServletResponse resp) {
resp.setHeader("Access-Control-Allow-Origin", "http://www.allowed.domain.com");
resp.setHeader("Access-Control-Allow-Methods", "POST");
}
My question is: if I set the header in the response (which is at the end of the request-response chain), it means the request I receive is already processed, side effects are caused, and then the program decides if the response must be sent back or not, based on the presence of this header in the response.
For example:
public class MyServlet extends HttpServlet {
//...
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws Exception{
Order order = (Order) parseBodyRequest(req);
orderRepository.save(order); //if I check the allowed domains later, I can get serious side effects!
resp.setHeader("Access-Control-Allow-Origin","http://www.allowed.domain.com");
resp.getWriter().println("Order n."+ order.getId()+ "has been saved successfully!");
}
}
In the example above, the order is parsed and saved into the database before even checking if the domain from which the request comes is allowed or not.
This thing seems absurd, so how does it work in reality?
Try this article: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
In short: For requests that are able to alter user data, CORS specifies a preflight request that asks the destination server whether it would accept a request with a given method and set of headers. (eg POST and Content-type) without actually sending the request. The browser implements this transparently.

How to handle a POST request with a Servlet API

I am trying to make an API with Jetty Server, and I have this simple GET request:
#GET
public String helloWorld(){
return "Hello world";
}
In order to make a POST request, I assume that one must save the input to the Jetty server. I have tried to research for quite a while, but found nothing.
I imagine something like this:
#POST
public void Save(String stringToSave) {
// Save to DB?
}
You could likely google this but let me give you a quick overview. A Servlet is a chunk of code that is normally run during an HTTP action - GET, POST, etc. It is the original technology of the JavaEE world, having been released in the late 1990's.
A simple Java servlet, using modern annotations, would look something like:
#WebServlet(name = "SampleServlet", urlPatterns = "/sampleServlet")
public class SampleServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// called when an HTTP POST is sent
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// called when an HTTP GET is sent
}
}
The important parts to note are that the class extends HttpServlet and that you have to write code to pull data out of the request and push it into the response. This isn't bad to do but it does have to be done.
JAX-RS is a newer standard, aimed simplifying the creation of REST services. It too is a chunk of code that runs during an HTTP interaction.
A simple example of this would be:
#Path("/sampleService")
public class SampleService{
#Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
#POST
#Path("/v1/hello")
public Response sayHello( SomeObject someobject ) {
The code here is both simpler and a bit more complex. The use of annotations helps determine the path that the service exists on a URL (in this case /sampleService/v1/hello), the HTTP method, and the Content-Type for both the request and response. Additionally, if the SomeObject object is defined correctly, the JAX-RS framework will automatically deserialize the incoming JSON or XML payload into an object for you.
The Response object contains the HTTP response code (perhaps a teapot) and a response body. In this example, the body will be automatically serialized back to the requestor in a way that matches the Accept header of the HTTP request (i.e., JSON for an application/json Accept header and XML for application/xml).
Note that while not directly related the JAX-RS framework takes advantage of the Servlet framework. Indeed in JAX-RS you can access the HttpServletRequest and HttpServletResponse object in your methods.
Which way is "better"? In general I would recommend using JAX-RS where possible as it is the newer standard and is a bit easier to implement. However, if you do any work in the JavaEE world you're very likely to run into Servlet code so it's important to understand it too.
Note that both Servlets and JAX-RS require an application server of some sort. Jetty is one of those. Another very common one is Tomcat. The application server sets up the environment for your code and listens for incoming HTTP messages. When it gets one it looks to see if it knows how to handle the URL and routes to the appropriate place. In the servlet world the server routes solely on the URL. In the JAX-RS world the server routes on the URL and, if specified by the #Consumes annotation, the HTTP Content-Type header too.
There is much more but let's start there and see if it answers what you're after.

How to send parameters to a restful service from a java web project

I have 2 java projects. The first one is a RESTFUL webservice, that should handle CRUD requests. The second is a dynamic web project (which has the gui).
Let's say I have this html gui in my web project.
(Remember I don't care about security and authority principles, I just wan't to understand this first).
When I fill the information and click "Sign in" I call my login_servlet inside the web project. Inside the servlet I create a client object and call the RESTFUL web service (inside the doPost method):
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Client client = Client.create();
WebResource webR = client.resource("http://localhost:8080/MyNote/api/login/get");
ClientResponse resp = webR.accept("text/html").get(ClientResponse.class);
if (resp.getStatus() == 200){
System.out.println("****** 200 ******");
String output = resp.getEntity(String.class);
//System.out.println("****" + output + "****");
}
}
As for now the provided URL calls the following code inside the RESTFUL web service, which successfully get printed out:
#Path("/login")
public class UserLogin {
#GET
#Path("/get")
public void login(){
System.out.println("**** I'm checking if user exist in DB *****");
}
}
What I instead want to do, is to send the inserted username and password from the login_servlet as parameters to the Restful web service, and then return a response. How can I do that? (Just the part of sending parameters from one place to another + Response)
All security aside, you have a few options to send params.
As query params as Duran mentioned above.
In your Jersey request method you would handle those as:
#GET
#Path("/get")
public void login(#QueryParam("foo") String var1, #QueryParam("bar") String var2){
// do something
}
Note that the variable names do not have to match, the string you pass to #QueryParam() is what gets matched and the value injected into the variable.
As path params you would do:
#GET
#Path("/get/{foo}/{bar}")
public void login(#PathParam("foo") String var1, #PathParam("bar") String var2){
// do something
}
Here make sure that what you have as var name in {} matches what you pass to #PathParam.
As far as Jersey/JAX-RS goes this is only the tip of the iceberg, there are other options. Hope this helps you get started.
EDIT: People seem to take issue with password being passed openly so let me say this: NO, you should never pass a password in the url, this is just to serve as an example
EDIT2: Changed username to foo and password to bar.
Using path params:
//Rest API
#GET
#Path("/get/{username}/{password}")
public void login(#PathParam("username") String userName, #PathParam("password") String pwd){
}
//Jersey
ClientResponse resp = webR.accept("text/html")
.path(userName)
.path(password)
.get(ClientResponse.class);
Using Query params
//Rest API
#GET
#Path("/get")
public void login(#QueryParam("username") String username, #QueryParam("password") String pwd){
//Jersey
ClientResponse resp = webR.accept("text/html")
.queryParam("username", userName)
.queryParam("password", pwd)
.get(ClientResponse.class);
Just append the parameters to the service url like:
http://localhost:8080/MyNote/api/login/get&username=duran&password=password
Although it is possible to send parameters in a GET request (as described in previous answers), it is usually better to send a POST request and to send a JSON payload in the body.
Here, you only have 2 parameters (login and password), so it's not too bad, but I still prefer to send an object in a POST. If you want to do that, then in your RESTful service, you just have to have method annotated with POST (and check the annotations that allow you to retrieve the de-serialized object).
Having said that, 2 comments/questions:
1) why do you mix servlets and JAX-RS? You could implement everything with JAX-RS and I would recommend that. Move the registration to a JAX-RS resource.
2) #Path("/get") is an anti-pattern: you don't want to have /get in the url. You rarely want VERBS in URLs (purists would say never). Typically, to register a new user, I would send a POST request to /api/registrations, because I want to create a new registration.

How to avoid sending HTTP request from a Java web server to itself?

Real situation is like this: Java web server (Weblogic) recieves a request from user for which it has to send a ZIP archive in response. Archive has to be dynamically generated from some files user asked for and one HTML report generated by the server itself. I would like to reuse JSF servlets the server already uses in other cases to generate this report. So, basically, what I use is:
HttpURLConnection self = new URL ("http://me.myself.com/report.jsf?...").openConnection ();
String report_html = fetchHtmlFromConnection (self);
and then create the requested ZIP, including the generated HTML in it.
The question is, can I somehow avoid making an internal HTTP request (to report.jsf) in this scenario? That involves basically pointless (since application just "talks" to itself anyway) roundtrips through operating system, HTTPD (which might be on a different machine), etc.
I am not very familiar with JSF, but from what I understand of them you can use a technic that is also applicable to JSP pages:
Create your own HttpServletResponseWrapper (a class used by the container that lets you modify the response)
Use it to override the default Writer (that writes the rendered page to the output) and provide one that writes the output to a String or a temporary file that will feed the compressing code.
There is a nice and simple tutorial that shows you how to do that:
http://blog.valotas.com/2011/09/get-output-of-jsp-or-servlet-response.html
Then
As hinted by gyan, get a ServletRequestDispatcher from your servlet that will let you invoke the rendering of the JSF
Forward the servlet call in order to provide your own HttpServletResponseWrapper
Use your HttpServletResponseWrapper to get the rendered HTML and feed it to the zipping code.
So the zipping Servlet would be like:
TempFileRespWrapper respWrapper = new TempFileRespWrapper();
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher( "/report.jsf");
dispatcher.forward(request, respWrapper);
File f = respWrapper.getOutputPath();
addFileToZip(f);
You should have a business service layer so that a "generateReport" service could be used inside multiple presentation views (even the "zip file" one).
You could do this in standard J2EE way via EJBs or through any custom framework that let you specify injectable business services (e.g. spring).
I think the main problem here is that you can generate the report only through the client interface (http). That makes it an isolated service, not available to other parts of the application. Basically you need a refactoring.
Do not code business inside JSFs or similar. (by the way, try not using jsf at all :D )
BUSINESS LAYER PRESENTATION
generateReportService---|---jsf-HtmlReport
\__|___
| \
someOtherContentService-|----jsf-Zip
Think about request dispatcher strategy, where request/response object would be sent to the report servlet from the entry servlet. In turn report servlet would generate the report and control can be sent to next servlet, which completes the rest of zip and send process.
For constructing a RequestDispatcher object, you can use either the ServletRequest.getRequestDispatcher() method or the ServletContext.getRequestDispatcher() method.
RequestDispatcher dispatcher=getServletContext().getRequestDispatcher( "/report.jsf" );
dispatcher.forward( request, response );
The next servlet set mime type as 'application/zip' and write the zip binary to browser. The user's browser would handle the content in the form of download depending on the browser settings.
Make sure that your web server is configured to cache (generated) html and you will not have to worry about it. First request to full or partial URL will generate a call to jsf generator and later it will be fetched from web server cache.
Yes, there will be a little overhead involved
(your source -> we server -> generator page or cache)
But it is going to be easier in the end.
Tested Solution!
This solution actually get ideas already posted here (specially from #gyan).
Write a Servlet to zip
(you could use an filter for that too. For example, suppose that you have a ZipFilter. You could map the filter for all *.zip and chain that filter with a URLRewrite filter for respective .jsf URL).
public class ZipServlet
extends HttpServlet {
#Override
public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException,
IOException {
ZipEntry zipEntry = new ZipEntry("helloWorld.html");
ZipHttpServletResponseWrapper respWrapper = new ZipHttpServletResponseWrapper(response, zipEntry);
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/helloWorld.jsf");
dispatcher.forward(request, respWrapper);
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "inline; filename=output.zip;");
response.flushBuffer();
respWrapper.getOutputStream().close();
}
}
NOTE : Yes, you should use RequestDispatcher
ZipHttpServletResponseWrapper
There's no much to say about that. From Java 7, you can use a native Class to create zip files properly. Using Decorator pattern with ZipOutputStream on top of response.getOutputStream() should be recommended way.
Remember that HttpServletResponseWrapper is a decorator. You should not use that if you don't want to reuse the target "servlet" output directly (you could use an stunt HttpServletResponse rather than use HttpServletResponseWrapper).
public class ZipHttpServletResponseWrapper
extends HttpServletResponseWrapper {
private ZipEntry entry;
private ZipServletOutputStreamWrapper streamWrapper;
private ZipOutputStream outputStream;
private PrintWriter printWriter;
public ZipHttpServletResponseWrapper(HttpServletResponse response, ZipEntry entry) {
super(response);
this.entry = entry;
}
#Override
public ServletOutputStream getOutputStream() throws IOException {
if (streamWrapper == null) {
outputStream = new ZipOutputStream(this.getResponse().getOutputStream());
outputStream.putNextEntry(entry);
streamWrapper = new ZipServletOutputStreamWrapper(outputStream);
}
return streamWrapper;
}
#Override
public PrintWriter getWriter() throws IOException {
if (printWriter == null) {
printWriter = new PrintWriter(getOutputStream());
}
return printWriter;
}
private class ZipServletOutputStreamWrapper
extends ServletOutputStream {
private ZipOutputStream outputStream;
public ZipServletOutputStreamWrapper(ZipOutputStream outputStream) {
this.outputStream = outputStream;
}
#Override
public void close() throws IOException {
outputStream.closeEntry();
outputStream.finish();
}
#Override
public void write(int b) throws IOException {
outputStream.write(b);
}
}
}
Now, the secret: mapping wisely!
Some important parts of JSF could use Filter chain (for example, myfaces from Apache use some extensions to provide some JSF components). In this case, you should specify in these filters digest FORWARD and REQUEST
<filter-mapping>
<filter-name>extensionsFilter</filter-name>
<url-pattern>*.jsf</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>

Accessing a HashMap of custom objects in request scope after a redirect

I have a HashMap of custom objects being passed to a JSP using RequestDispatcher and I am able to access the object and its properties using JSTL.
However the code fails in case the parameter is sent using response.sendRedirect() .
I am not sure what the reason is and how to make it work?
The response.sendRedirect() basically instructs the client (the webbrowser) to send a new request on the given URL. You'll also see this being reflected by a change in the browser address bar.
A new request does of course not contain the attribtues of the previous (or any other) request. That would otherwise have broken the whole concept of "request scope".
To preprocess a GET request, you need to do the job in doGet() method of a servlet and then redirect to the URL of that servlet instead.
E.g.
response.sendRedirect(request.getContextPath() + "/foo");
and
#WebServlet("/foo")
public class FooServlet extends HttpServlet {
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Map<String, Foo> foos = fooService.map();
request.setAttribute("foos", foos);
request.getRequestDispatcher("/WEB-INF/foo.jsp").forward(request, response);
}
}
Note that this problem is in no way related to having a hashmap of custom objects in the request scope.
See also:
Our servlets wiki page
You can not share a request attribute in response.sendRedirect as it creates a new request.
But, if you want that HashMap, in response.sendRedirect, you can put that in session like
request.getSession().setAttribute("myMap", [HashMap object]);
and can share between the servlet and JSP. This works in both RequestDispatcher and sendRedirect.

Categories

Resources