I'm using Jetty 9 and I'm trying to process the headers of a PUT request before all the body has arrived on the server. Here's what I've done:
Server.java:
public class SimplestServer
{
public static void main(String[] args) throws Exception
{
Server server = new Server(9080);
ServletHandler handler = new ServletHandler();
server.setHandler(handler);
handler.addServletWithMapping(HelloServlet.class, "/*");
handler.addFilterWithMapping(HelloPrintingFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
server.start();
server.dumpStdErr();
server.join();
}
public static class HelloServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println(System.currentTimeMillis() + ": Hello from HelloServlet GET");
}
#Override
protected void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println(System.currentTimeMillis() + ": Hello from HelloServlet PUT");
}
}
public static class HelloPrintingFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println(System.currentTimeMillis() + ": Hello from filter");
chain.doFilter(request, response);
}
#Override
public void init(FilterConfig arg0) throws ServletException {
System.out.println(System.currentTimeMillis() + ": Init from filter");
}
#Override
public void destroy() {
System.out.println(System.currentTimeMillis() + ": Destroy from filter");
}
}
}
Client.java
public class SimplestClient
{
public static void main(String[] args) throws Exception
{
URL url = new URL("http://localhost:9080/resource");
HttpURLConnection httpCon = (HttpURLConnection) url.openConnection();
httpCon.setDoOutput(true);
httpCon.setRequestMethod("PUT");
OutputStream out = httpCon.getOutputStream();
byte[] b = new byte[65536];
Random r = new Random();
r.nextBytes(b);
for (int i = 0; i < 1024; i++) {
out.write(b);
}
System.out.println(System.currentTimeMillis() + ": Data sent. Waiting 5 seconds...");
try {
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
out.close();
System.out.println(System.currentTimeMillis() + ": Done!");
httpCon.getInputStream();
}
}
In a nutshell, the server program listens for connections on port 9080, when a request arrives the filter HelloPrintingFilter gets executed, then the request is processed by the HelloServlet. The client, instead, connects to the server, sends a bunch of data, then sleeps for 5 seconds and finally closes the connection with the server.
A run of both programs yields the following result:
Client:
1469613522350: Data sent. Waiting 5 seconds...
1469613527351: Done!
Server:
1469613527373: Hello from filter
1469613527373: Hello from HelloServlet PUT
Looking at the timestamps I can only get my filter code executed after all the body has arrived. Could anyone explain me how to do it? A typical use case is: a client tries to upload a 5GB file. As soon as the headers arrive, I want to check if they are OK (e.g. by checking if the Content-MD5 header, or whatever custom header I need to check, is present). If the request is OK then start processing the body. If the request is not OK then close the connection.
Thanks.
Use multiple requests. e.g. first request includes custom header and subsequent requests are used to upload a 5GB file.
You aren't doing anything in your HelloServlet.doPut(), so you are basically telling the Servlet container (aka Jetty) that you are done handling that request.
The request processing in Jetty is handled by a series of buffers from the network.
Your PUT headers and the start of your body content likely fit in a single buffer.
Jetty will parse the headers out, and then start the dispatch of the request to the Servlet chain, hitting your HelloFilter, and then your filter moves it along the chain with the chain.doFilter(request, response);
The point in time when HelloServlet.doPut() is reached, the headers have been processed, and the start of the body content hasn't, waiting for your implementation in doPut() to call HttpServletRequest.getInputStream() and start processing it, at which point Jetty is free to start reading more buffers off the network.
Note: if your servlet exits without reading the request input stream, and the response hasn't indicated a Connection: close, then Jetty will be forced to read the entire request to completion looking for the next request after it (known as a persistent connection in HTTP/1.1 spec)
The closest you'll get to reaching your stated goal to reject the Request body content is to use what you have available in the HTTP/1.1 spec (assuming this is a HTTP/1.1 request). Namely a proper response status code and a server initiated Connection: close response header.
Here's a complete example:
package jetty;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.Uptime;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class PutRejectExample
{
public static class RejectServlet extends HttpServlet
{
#Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
timedLog("doPut() - enter");
if (req.getHeader("X-Key") == null)
{
resp.setHeader("Connection", "close");
resp.sendError(HttpServletResponse.SC_FORBIDDEN);
timedLog("doPut() - rejected");
return;
}
File output = File.createTempFile("reject-", ".dat");
try (FileOutputStream out = new FileOutputStream(output))
{
IO.copy(req.getInputStream(), out);
}
resp.setStatus(HttpServletResponse.SC_OK);
resp.setHeader("Connection", "close"); // be a good HTTP/1.1 citizen
timedLog("doPut() - exit");
}
}
private static Server server;
private static int port;
private static void timedLog(String format, Object... args)
{
System.out.printf(Uptime.getUptime() + "ms " + format + "%n", args);
}
#BeforeClass
public static void startServer() throws Exception
{
server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.addConnector(connector);
// collection for handlers
HandlerCollection handlers = new HandlerCollection();
server.setHandler(handlers);
// servlet context
ServletContextHandler context = new ServletContextHandler();
context.addServlet(RejectServlet.class, "/reject");
handlers.addHandler(context);
// default handler
handlers.addHandler(new DefaultHandler());
// start server
server.start();
// grab port
port = connector.getLocalPort();
}
#AfterClass
public static void stopServer() throws Exception
{
server.stop();
}
private void performPUT(int requestSize, String... extraRequestHeaders) throws IOException
{
StringBuilder req = new StringBuilder();
req.append("PUT /reject HTTP/1.1\r\n");
req.append("Host: localhost:").append(port).append("\r\n");
req.append("Content-Length: ").append(requestSize).append("\r\n");
for (String extraHeader : extraRequestHeaders)
{
req.append(extraHeader);
}
req.append("\r\n");
timedLog("client open connection");
try (Socket socket = new Socket())
{
socket.connect(new InetSocketAddress("localhost", port));
try (OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
InputStreamReader reader = new InputStreamReader(in))
{
timedLog("client send request (headers + body)");
try
{
// write request line + headers
byte headerBytes[] = req.toString().getBytes(StandardCharsets.UTF_8);
out.write(headerBytes);
out.flush();
// write put body content
int bufSize = 65535;
byte[] buf = new byte[bufSize];
int sizeLeft = requestSize;
while (sizeLeft > 0)
{
int writeSize = Math.min(sizeLeft, bufSize);
ThreadLocalRandom.current().nextBytes(buf);
out.write(buf, 0, writeSize);
out.flush();
sizeLeft -= writeSize;
try
{
// simulate a slower connection
TimeUnit.MILLISECONDS.sleep(10);
}
catch (InterruptedException ignore)
{
// ignore
}
}
}
catch (IOException e)
{
timedLog("client request send exception");
e.printStackTrace(System.out);
}
timedLog("client send request complete");
timedLog("client read response");
try
{
StringWriter respStream = new StringWriter();
IO.copy(reader, respStream);
timedLog("client response: %s", respStream.toString());
}
catch (IOException e)
{
timedLog("client read response exception");
e.printStackTrace(System.out);
}
}
}
timedLog("client connection complete");
}
#Test
public void testBadPost() throws IOException
{
timedLog("---- testBadPost()");
performPUT(1024 * 1024 * 10);
}
#Test
public void testGoodPost() throws IOException
{
timedLog("---- testGoodPost()");
performPUT(1024 * 1024 * 10, "X-Key: foo\r\n");
}
}
This uses raw Socket and raw streams to avoid getting confused by all of the buffering present in the HttpUrlConnection.
The output you'll see for the normal / happy case is like this ...
416ms ---- testGoodPost()
416ms client open connection
2016-07-27 06:40:22.180:INFO:oejs.AbstractConnector:main: Started ServerConnector#55f3ddb1{HTTP/1.1,[http/1.1]}{0.0.0.0:46748}
2016-07-27 06:40:22.181:INFO:oejs.Server:main: Started #414ms
421ms client send request (headers + body)
494ms doPut() - enter
2084ms doPut() - exit
2093ms client send request complete
2093ms client read response
2094ms client response: HTTP/1.1 200 OK
Date: Wed, 27 Jul 2016 13:40:22 GMT
Connection: close
Server: Jetty(9.3.11.v20160721)
2094ms client connection complete
The output for the rejected case will look like this ...
2095ms ---- testBadPost()
2095ms client open connection
2096ms client send request (headers + body)
2096ms doPut() - enter
2101ms doPut() - rejected
2107ms client request send exception
java.net.SocketException: Broken pipe
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:109)
at java.net.SocketOutputStream.write(SocketOutputStream.java:153)
at jetty.PutRejectExample.performPUT(PutRejectExample.java:137)
at jetty.PutRejectExample.testBadPost(PutRejectExample.java:180)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
2109ms client send request complete
2109ms client read response
2109ms client response: HTTP/1.1 403 Forbidden
Date: Wed, 27 Jul 2016 13:40:23 GMT
Cache-Control: must-revalidate,no-cache,no-store
Content-Type: text/html;charset=iso-8859-1
Content-Length: 322
Connection: close
Server: Jetty(9.3.11.v20160721)
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/>
<title>Error 403 </title>
</head>
<body>
<h2>HTTP ERROR: 403</h2>
<p>Problem accessing /reject. Reason:
<pre> Forbidden</pre></p>
<hr />Powered by Jetty:// 9.3.11-SNAPSHOT<hr/>
</body>
</html>
2109ms client connection complete
Gotcha! The problem was not in the server side, but in the client side, which was only intended as a stub. Specifically, the problem was the buffering in the HttpUrlConnection.
Recalling my client.java, I have:
for (int i = 0; i < 1024; i++) {
out.write(b);
}
and if I change the loop to something like
for (int i = 0; i < 1024*1024; i++) {
out.write(b);
}
I immediately get an OutOfMemoryError exception, getting nothing on the server side, indicating that no single byte was transferred. And of course it is right, because BEFORE putting the headers on the wire the HttpUrlConnection needs to know the body length since it must emit the Content-Length header. Changing the client implementation to raw sockets, effectively controlling when bytes go to the wire, resolved the issue.
As a side note, the server code can be further simplified by removing the filter class. The complete server-side code is:
server.java:
public class SimplestServer
{
public static void main(String[] args) throws Exception
{
Server server = new Server(9080);
ServletHandler handler = new ServletHandler();
server.setHandler(handler);
handler.addServletWithMapping(HelloServlet.class, "/*");
server.start();
server.dumpStdErr();
server.join();
}
public static class HelloServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println(System.currentTimeMillis() + ": Hello from HelloServlet GET");
}
#Override
protected void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println(System.currentTimeMillis() + ": Hello from HelloServlet PUT");
// Perform some checks here
if (request.getHeader("X-Key") == null)
{
response.setHeader("Connection", "close");
response.sendError(HttpServletResponse.SC_FORBIDDEN);
System.out.println(System.currentTimeMillis() + ": Filter --> X-Key failed!");
return;
}
// Everything OK! Read the stream.
System.out.println(System.currentTimeMillis() + ": Proceded!!");
InputStream body = request.getInputStream();
long bytesReadSoFar = 0;
byte[] data = new byte[65536];
while (true) {
int bytesRead = body.read(data);
if (bytesRead < 0)
break;
bytesReadSoFar += bytesRead;
}
System.out.println(System.currentTimeMillis() + ": Finished! Read " + bytesReadSoFar + " bytes.");
response.setHeader("Connection", "close");
response.setStatus(HttpServletResponse.SC_OK);
}
}
}
Related
I have a Java web application running under Apache Tomcat (8.0.21.0). Its function is to monitor various external processes and display alerts and periodic updated statuses in a browser. The main HTTP request handler is simple enough.
public class MyApplication extends HttpServlet
{
.
.
.
public void doPost (HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
processRequest (request, response);
}
public void doGet (HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
processRequest (request, response);
}
private static void processRequest (HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
String strOption = request.getParameter ("option");
int nOption = Integer.parseInt (strOption);
response.setContentType ("text/html");
PrintWriter out = response.getWriter ();
outputPage (out, nOption);
}
private void outputPage (PrintWriter out, int nOption)
{
out.println ("<!DOCTYPE html>");
out.println ("<html>");
out.println ("<head>");
switch (nOption)
{
// title, style and <body> content depend on option passed in request
.
.
.
}
out.println ("</body>);
out.println ("</html>");
}
}
However, the application also includes a TCP Listener and socket, to receive IoT (Internet of Things) messages:
public class MyTCPconnection extends Thread
{
public Socket clientSocket; // socket created by listener
private String url = "[local host address and servlet name]";
.
.
.
public void run ()
{
int RC = 400; // default value = "Bad Request"
try
{
// get bytes from remote process
int receiveBufferSize = clientSocket.getReceiveBufferSize ();
byte[] receiveBuffer = new byte[receiveBufferSize];
int bytesRead = TCPreceive (clientSocket, receiveBuffer); // not shown
if (bytesRead != -1 && bytesRead != 0)
{
String strOption = getOption (receiveBuffer); // not shown
}
HttpPost httpPost = new HttpPost (url);
httpPost.setHeader ("Accept", "text/html,application/xhtml+xml,application/xml");
httpPost.setHeader ("Content-Type", "application/x-www-form-urlencoded");
List<NameValuePair> requestParams = new ArrayList<NameValuePair>();
reqestParams.add (new BasicNameValuePair ("option", value));
CloseableHttpClient httpClient = HttpClients.createDefault ();
CloseableHttpResponse response = httpClient.execute (httpPost);
RC = response.getStatusLine().getStatusCode();
String responseBody = EntityUtils.toString (response.getEntity ());
system.out.println (responseBody);
}
catch (UnknownHostException e)
{
RC = 404;
}
catch (IOException e)
{
RC = 400;
}
TCPsend (clientSocket, RC); // reply to remote process, not shown
}
}
Take for granted that MyTCPconnection.run () generates a valid HTTP request body and submits a POST request to the main application. The problem I have encountered is that, where the POST is made from a web browser (IExplorer, Firefox etc), the application outputs a web page in the browser, but on receiving the POST request from the internal MyTCPconnection instance, it outputs nothing to any browser. Instead, it redirects the entire output to the responseBody.
I thought at first that I merely needed to save the HttpServletResponse and PrintWriter variables from a request from the browser, and pass the saved PrintWriter instance to the function outputPage. However, when I logged these, the results were:
Request from browser:
HttpServletResponse response = org.apache.catalina.connector.ResponseFacade#3e1d266b
PrintWriter out = org.apache.catalina.connector.CoyoteWriter#6bc55aa8
Request from MyTCPconnection.run ():
HttpServletResponse response = org.apache.catalina.connector.ResponseFacade#3e1d266b
PrintWriter out = org.apache.catalina.connector.CoyoteWriter#6bc55aa8
Any hints or hlp would be appreciated
In a Java HttpServlet, is it possible to request data from another local service using the original request's header information without necessarily forwarding?
For example, I have FooBar.java:
// Handles the url at /foo/bar and can be accessed at http://localhost/foo/bar
public class FooBar extends HttpServlet
{
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
Object data = ... // 1. Retrieve data at http://localhost/foo/baz utilizing the current request's header
Object newData = doSomething(data); // 2. Process the data
response.getWriter().write(newData.toString); // 3. Return the processed data
}
private Object doSomething(Object data)
{
// Perform some business logic
}
}
Step 1 is the issue here. The purpose of this is that I want to be able to perform some sort of logic on the data before returning it in full, but don't necessarily have access do make the changes on the handler at /foo/baz do to the propriety nature of things.
You can use this answer of me to create a HTTP Request: send get request
In addition, it may be necessary to copy the request header with some care:
private static final Set forbiddenCopyHeaders = new HashSet<>(Arrays.asList(new String[]{
"connection"
, "transfer-encoding"
, "content-length" // POST kann zu Status 500 führen, wenn die content-length kopiert wird
, "via"
, "x-forwarded-for"
, "x-forwarded-host"
, "x-forwarded-server"
}));
private void copyRequestHeaders(HttpServletRequest customerRequest, HttpRequestBase internRequest) throws
HttpException
{
Enumeration<String> headerNames = customerRequest.getHeaderNames();
String connectionHeader = customerRequest.getHeader("connection");
while (headerNames.hasMoreElements())
{
String headerName = headerNames.nextElement();
boolean copyAllowed = !forbiddenCopyHeaders.contains(headerName.toLowerCase()) &&
!StringUtils.containsIgnoreCase(connectionHeader, headerName);
if (copyAllowed)
{
Enumeration<String> values = customerRequest.getHeaders(headerName);
while (values.hasMoreElements())
{
internRequest.addHeader(headerName, values.nextElement());
}
}
}
setProxySpecificRequestHeaders(customerRequest, internRequest);
}
private void setProxySpecificRequestHeaders(HttpServletRequest customerRequest,
HttpRequestBase internRequest) throws HttpException
{
String serverHostName = "doorman";
try
{
serverHostName = InetAddress.getLocalHost().getHostName();
}
catch (UnknownHostException e)
{
logger.error("Couldn't get the hostname needed for headers x-forwarded-server and Via", e);
}
String originalVia = customerRequest.getHeader("via");
StringBuilder via = new StringBuilder("");
if (originalVia != null)
{
if (originalVia.contains(serverHostName))
{
logger.error("This proxy has already handled the Request, will abort.");
throw new HttpException("Request has a cyclic dependency on this proxy.");
}
else
{
via.append(originalVia).append(", ");
}
}
via.append(customerRequest.getProtocol()).append(" ").append(serverHostName);
internRequest.addHeader("via", via.toString());
internRequest.addHeader("x-forwarded-for", customerRequest.getRemoteAddr());
internRequest.addHeader("x-forwarded-host", customerRequest.getServerName());
internRequest.addHeader("x-forwarded-server", serverHostName);
internRequest.addHeader("accept-encoding", "");
}
Using HttpURLConnection and altering the header to include a property from the original request, I was able to get a BufferedReader from the HTTP request:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
// Step 1
String serverName = request.getLocalName();
String contextPath = request.getContextPath();
URL url = new URL("https://" + serverName + contextPath + "/baz");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Key Header", request.getHeader("Key Header"));
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
// Step 2
... // Do something with the data from the reader
// Step 3
... // Write the data back using the response
}
I have multiple jax-rs services built using cxf/spring. I want to control the output payload response size of all services. For simplicity sake, let's say none of api's in any of the services should ever return a JSON response payload more than 500 characters and I want to control this in one place instead of relying on individual services to adhere to this requirement. (We already have other features built into the custom framework/base component that all services depend on).
I have tried implementing this using JAX-RS's WriterInterceptor, ContainerResponseFilter and CXF's Phase Interceptor, but none of the approaches seem to be completely satisfy my requirement. More details on what I've done so far:
Option 1: (WriterInteceptor) In the overridden method, I get the ouputstream and set the max size of the cache to 500. When I invoke an api that returns more than 500 characters in the response payload, I get an HTTP 400 Bad Request status, but the response body contains the entire JSON payload.
#Provider
public class ResponsePayloadInterceptor implements WriterInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(ResponsePayloadInterceptor.class);
#Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
final OutputStream outputStream = context.getOutputStream();
CacheAndWriteOutputStream cacheAndWriteOutputStream = new CacheAndWriteOutputStream(outputStream);
cacheAndWriteOutputStream.setMaxSize(500);
context.setOutputStream(cacheAndWriteOutputStream);
context.proceed();
}
}
Option 2a: (CXF Phase Inteceptor) In the overridden method, I get the response as String from the ouputstream and check it's size. If it's greater than 500, I create a new Response object with only the data Too much data and set it in the message. Even if the response is > 500 characters, I get an HTTP 200 OK status with the entire JSON. Only when I use the phase as POST_MARSHAL or a later phase, I'm able to get hold of the JSON response and check it's length, but by that time the response has already been streamed to the client.
#Provider
public class ResponsePayloadInterceptor extends AbstractPhaseInterceptor<Message> {
private static final Logger LOGGER = LoggerFactory.getLogger(ResponsePayloadInterceptor.class);
public ResponsePayloadInterceptor() {
super(Phase.POST_MARSHAL);
}
#Override
public void handleMessage(Message message) throws Fault {
LOGGER.info("handleMessage() - Response intercepted");
try {
OutputStream outputStream = message.getContent(OutputStream.class);
...
CachedOutputStream cachedOutputStream = (CachedOutputStream) outputStream;
String responseBody = IOUtils.toString(cachedOutputStream.getInputStream(), "UTF-8");
...
LOGGER.info("handleMessage() - Response: {}", responseBody);
LOGGER.info("handleMessage() - Response Length: {}", responseBody.length());
if (responseBody.length() > 500) {
Response response = Response.status(Response.Status.BAD_REQUEST)
.entity("Too much data").build();
message.getExchange().put(Response.class, response);
}
} catch (IOException e) {
LOGGER.error("handleMessage() - Error");
e.printStackTrace();
}
}
}
Option 2b: (CXF Phase Inteceptor) Same as above, but only the contents of if block is changed. If response length is greater than 500, I create a new output stream with the string Too much data and set it in message. But if the response payload is > 500 characters, I still get an HTTP 200 OK status with an invalid JSON response (entire JSON + additional text) i.e., the response looks like this: [{"data":"", ...}, {...}]Too much data (the text 'Too much data' is appended to the JSON)
if (responseBody.length() > 500) {
InputStream inputStream = new ByteArrayInputStream("Too much data".getBytes("UTF-8"));
outputStream.flush();
IOUtils.copy(inputStream, outputStream);
OutputStream out = new CachedOutputStream();
out.write("Too much data".getBytes("UTF-8"));
message.setContent(OutputStream.class, out);
}
Option 3: (ContainerResponseFilter) Using the ContainerResponseFilter, I added a Content-Length response header with value as 500. If response length is > 500, I get an HTTP 200 OK status with an invalid JSON response (truncated to 500 characters). If the response length is < 500, still get an HTTP 200 OK status, but the client waits for more data to be returned by the server (as expected) and times out, which isn't a desirable solution.
#Provider
public class ResponsePayloadFilter implements ContainerResponseFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(ResponsePayloadFilter.class);
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
LOGGER.info("filter() - Response intercepted");
CachedOutputStream cos = (CachedOutputStream) responseContext.getEntityStream();
StringBuilder responsePayload = new StringBuilder();
ByteArrayOutputStream out = new ByteArrayOutputStream();
if (cos.getInputStream().available() > 0) {
IOUtils.copy(cos.getInputStream(), out);
byte[] responseEntity = out.toByteArray();
responsePayload.append(new String(responseEntity));
}
LOGGER.info("filter() - Content: {}", responsePayload.toString());
responseContext.getHeaders().add("Content-Length", "500");
}
}
Any suggestions on how I can tweak the above approaches to get what I want or any other different pointers?
I resolved this partially using help from this answer. I say partially because I'm successfully able to control the payload, but the not the response status code. Ideally, if the response length is greater than 500 and I modify the message content, I would like to send a different response status code (other than 200 OK). But this is a good enough solution for me to proceed at this point. If I figure out how to update the status code as well, I'll come back and update this answer.
import org.apache.commons.io.IOUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class ResponsePayloadInterceptor extends AbstractPhaseInterceptor<Message> {
private static final Logger LOGGER = LoggerFactory.getLogger(ResponsePayloadInterceptor.class);
public ResponsePayloadInterceptor() {
super(Phase.PRE_STREAM);
}
#Override
public void handleMessage(Message message) throws Fault {
LOGGER.info("handleMessage() - Response intercepted");
try {
OutputStream outputStream = message.getContent(OutputStream.class);
CachedOutputStream cachedOutputStream = new CachedOutputStream();
message.setContent(OutputStream.class, cachedOutputStream);
message.getInterceptorChain().doIntercept(message);
cachedOutputStream.flush();
cachedOutputStream.close();
CachedOutputStream newCachedOutputStream = (CachedOutputStream) message.getContent(OutputStream.class);
String currentResponse = IOUtils.toString(newCachedOutputStream.getInputStream(), "UTF-8");
newCachedOutputStream.flush();
newCachedOutputStream.close();
if (currentResponse != null) {
LOGGER.info("handleMessage() - Response: {}", currentResponse);
LOGGER.info("handleMessage() - Response Length: {}", currentResponse.length());
if (currentResponse.length() > 500) {
InputStream replaceInputStream = IOUtils.toInputStream("{\"message\":\"Too much data\"}", "UTF-8");
IOUtils.copy(replaceInputStream, outputStream);
replaceInputStream.close();
message.setContent(OutputStream.class, outputStream);
outputStream.flush();
outputStream.close();
} else {
InputStream replaceInputStream = IOUtils.toInputStream(currentResponse, "UTF-8");
IOUtils.copy(replaceInputStream, outputStream);
replaceInputStream.close();
message.setContent(OutputStream.class, outputStream);
outputStream.flush();
outputStream.close();
}
}
} catch (IOException e) {
LOGGER.error("handleMessage() - Error", e);
throw new RuntimeException(e);
}
}
My requirement:
I want to make API like dropbox's /longpoll_delta,I have useed jerser 2 in java for my restful. Actually I want to notify all client when there is change found on server. So server may take 2hr or more then notify all its client that i have some update. I don't want to use comet, websocket etc for security purpose.
1> Suggest how can i made such API ?
My effor :
I have created Asynchronous Server API
Server code:
#GET
#Path("/longpoll")
#ManagedAsync
public void async(#Suspended final AsyncResponse asyncResponse)
{
new Thread(new Runnable()
{
#Override
public void run()
{
String result = veryExpensiveOperation();
asyncResponse.resume(result);
}
private String veryExpensiveOperation()
{
try
{
// put some long running code here
Thread.sleep(60000);
return "Hello World";
}
catch (Exception e)
{
e.printStackTrace();
return "error";
}
}
}).start();
}
So now for testing this i have created client
Client code:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.InvocationCallback;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.client.ClientConfig;
public class JersyClient
{
public static void main(String args[]) throws InterruptedException, ExecutionException
{
ClientConfig clientConfig = new ClientConfig();
Client client = ClientBuilder.newClient(clientConfig);
WebTarget target = client.target("http://localhost:8080");
Invocation.Builder invocationBuilder = target.path("MyApp_3_3_5/api/1/longpoll").request();
invocationBuilder.header("Authorization", "Bearer yBOrnV6zlOrgoYxrMsfQ_BYZ5p37gALB");
final Future<Response> future = invocationBuilder.async().get(new InvocationCallback<Response>()
{
#Override
public void completed(Response response)
{
System.out.println("Response status code "
+ response.getStatus() + " received.");
System.out.println("In response " + response.readEntity(String.class));
}
#Override
public void failed(Throwable throwable)
{
System.out.println("Invocation failed.");
throwable.printStackTrace();
}
});
Response response = future.get();
System.out.println("your response " + response.readEntity(String.class));
// get() waits for the response to be ready
System.out.println("Go ahead");
}
}
But this not working properly.
OutPut:
Response status code 200 received.
In response
your response
Go ahead
So here client is not wait for actual response that server send after 1 minute.
Is server code have any problem ?
Why client not wait for actual response ?
Should i achieve long_pooling using this way ?
if server send response after 2hr then in such case is this working ?
Is it possible to create a server sent event using java servlets so that a client could receive updates using:
<script>
var source = new EventSource('/events');
source.onmessage = function(e) {
document.body.innerHTML += e.data + '<br>';
};
</script>
All examples I have found on the web are using PHP but I would assume that it should work using Java's HTTP Servlet.
this does the trick.
HTML
<!DOCTYPE html>
<html>
<body onload ="registerSSE()" >
<script>
function registerSSE()
{
alert('test 1');
var source = new EventSource('http://frewper:8080/hello/sse');
alert('Test2');
source.onmessage=function(event)
{
document.getElementById("result").innerHTML+=event.data + "<br />";
};
/*source.addEventListener('server-time',function (e){
alert('ea');
},true);*/
}
</script>
<output id ="result"></output>
</body>
</html>
Servlet :
import java.io.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class sse extends HttpServlet
{
public void doPost(HttpServletRequest request, HttpServletResponse response)
{
try
{
System.out.println("SSE Demo");
response.setContentType("text/event-stream");
PrintWriter pw = response.getWriter();
int i=0;
while(true)
{
i++;
pw.write("event: server-time\n\n"); //take note of the 2 \n 's, also on the next line.
pw.write("data: "+ i + "\n\n");
System.out.println("Data Sent!!!"+i);
if(i>10)
break;
}
pw.close();
}catch(Exception e){
e.printStackTrace();
}
}
public void doGet(HttpServletRequest request,HttpServletResponse response)
{
doPost(request,response);
}
}
Server-Sent Events is a HTML5 feature. We say "HTML5", therefore it's just client side feature.
So long server can set https respnse header "text/event-stream;charset=UTF-8","Connection", "keep-alive", then it is supported by server. You can set such header with Java Servlet.
Here you can find a step for step guide on SSE with servlet
I have created a very simple library that can be integrated within plain Java Servlets in Asynchronous mode, so no extra server threads are required for each client: https://github.com/mariomac/jeasse
It integrates the SseDispatcher (for point-to-point SSEs) and SseBroadcaster (for event broadcasting). An example of use:
public class TestServlet extends HttpServlet {
SseBroadcaster broadcaster = new SseBroadcaster();
#Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Scanner scanner = new Scanner(req.getInputStream());
StringBuilder sb = new StringBuilder();
while(scanner.hasNextLine()) {
sb.append(scanner.nextLine());
}
System.out.println("sb = " + sb);
broadcaster.broadcast("message",sb.toString());
}
//http://cjihrig.com/blog/the-server-side-of-server-sent-events/
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
broadcaster.addListener(req);
}
}