I have an endless InputStream with some data, which I want to return in response to a GET HTTP request. I want my web/API client to read from it endlessly. How can I do it with JAX-RS? I'm trying this:
#GET
#Path("/stream")
#Produces(MediaType.TEXT_PLAIN)
public StreamingOutput stream() {
final InputStream input = // get it
return new StreamingOutput() {
#Override
public void write(OutputStream out) throws IOException {
while (true) {
out.write(input.read());
out.flush();
}
}
};
}
But content doesn't appear for the client. However, if I add OutputStream#close(), the server delivers the content at that very moment. How can I make it truly streamable?
Simply use the StreamingOutput of JAX-RS
#Path("/numbers")
public class NumbersResource {
#GET
public Response streamExample(){
StreamingOutput stream = new StreamingOutput() {
#Override
public void write(OutputStream out) throws IOException, WebApplicationException {
Writer writer = new BufferedWriter(new OutputStreamWriter(out));
for (int i = 0; i < 10000000 ; i++){
writer.write(i + " ");
}
writer.flush();
}
};
return Response.ok(stream).build();
}
}
So, you have flush issues, you could try to get the ServletResponse as the spec says:
The #Context annotation can be used to indicate a dependency on a
Servlet-defined resource. A Servlet- based implementation MUST support
injection of the following Servlet-defined types: ServletConfig,
ServletContext, HttpServletRequest and HttpServletResponse.
An injected HttpServletResponse allows a resource method to commit the
HTTP response prior to returning. An implementation MUST check the
committed status and only process the return value if the response is
not yet committed.
Then flushing everything you can, like this:
#Context
private HttpServletResponse context;
#GET
#Path("/stream")
#Produces(MediaType.TEXT_PLAIN)
public String stream() {
final InputStream input = // get it
ServletOutputStream out = context.getOutputStream();
while (true) {
out.write(input.read());
out.flush();
context.flushBuffer();
}
return "";
}
Just a wild guess:
#GET
#Path("/stream")
#Produces(MediaType.TEXT_PLAIN)
public Response stream() {
final InputStream input = getit();
return Response.ok(input, MediaType.TEXT_PLAIN_TYPE).build();
}
Folks should be using Java 9 or later can use transferTo to copy the input stream to the output stream so do this:
#GET
#Path("/stream")
#Produces(MediaType.TEXT_PLAIN)
public StreamingOutput stream() {
final InputStream input = // get it
StreamingOutput stream = output -> {
try {
is.transferTo(output);
}
catch (Exception e) {
throw new WebApplicationException(e);
} finally {
is.close();
}
};
return Response.ok(stream, MediaType.APPLICATION_OCTET_STREAM).build();
}
Related
I'm working on a Filter in which I have to get the request payload, decrypt it, check if it's a valid JSON and if it is go on with the chain and go to my service. The thing is that, so far I haven't been able to find a way to rewrite the body. Why I want to rewrite it? As the service expects a JSON and the request has an encrypted text in the body, once I decrypt it I want the body to be the decrypted JSON. Also, once I return from the service, I should rewrite the response to have the json encrypted. I've read a lot of forums and questions but couldn't get to a working solution.
Here's my code:
RequestLoginFilter.java
#WebFilter("/RequestLoginFilter")
public class RequestLoginFilter implements Filter{
protected final static Log logger = LogFactory.getLog(RequestLoginFilter.class);
private ServletContext context;
private CryptoUtil crypto;
public void init(FilterConfig fConfig) throws ServletException {
this.context = fConfig.getServletContext();
this.context.log("RequestLoggingFilter initialized");
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// use wrapper to read multiple times the content
AuthenticationRequestWrapper req = new AuthenticationRequestWrapper((HttpServletRequest) request);
HttpServletResponse resp = (HttpServletResponse) response;
String payload = req.getPayload();
try {
String decryptedPayload = crypto.decrypt(payload);
JSONUtils.convertJSONStringToObject(decryptedPayload, LoginTokenTO.class);
} catch (GeneralSecurityException e) {
logger.error("Error when trying to decrypt payload '"+payload+"'");
throw new ServletException("Error when trying to decrypt payload '"+payload+"'", e);
}
chain.doFilter(req, resp);
System.out.println("a ver");
}
#Override
public void destroy() {
// TODO Auto-generated method stub
}
}
And also the wrapper, just in case:
AuthenticationRequestWrapper.java
public class AuthenticationRequestWrapper extends HttpServletRequestWrapper {
protected final static Log logger = LogFactory.getLog(AuthenticationRequestWrapper.class);
private final String payload;
public AuthenticationRequestWrapper (HttpServletRequest request) throws AuthenticationException {
super(request);
// read the original payload into the payload variable
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
// read the payload into the StringBuilder
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
// make an empty string since there is no payload
stringBuilder.append("");
}
} catch (IOException ex) {
logger.error("Error reading the request payload", ex);
throw new AuthenticationException("Error reading the request payload", ex);
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException iox) {
// ignore
}
}
}
payload = stringBuilder.toString();
}
#Override
public ServletInputStream getInputStream () throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(payload.getBytes());
ServletInputStream inputStream = new ServletInputStream() {
public int read ()
throws IOException {
return byteArrayInputStream.read();
}
};
return inputStream;
}
public String getPayload() {
return payload;
}
}
Hopefully somebody here knows how I can get to get this working.
Thanks in advance guys.
Whilst what you are asking is probably technically possible, it doesn't sound like the right approach to me.
What you need is a security layer that sits between the incoming request (endpoint) and your service. Re-writing the body of the request is a strange thing to be doing (which probably explains why you're having issues). Is there a reason you want this to be done in a Filter? After all, filters are designed to filter requests, not rewrite them ;)
A more logical/transparent solution would be to have your endpoint accept all incoming requests, decrypt and validate them before passing the request onto your service tier. Something like this:
public void handleRequest(Request request) {
try {
IncomingRequest x = securityManager.decrypt(request);
Response r = myService.handleRequest(x);
handleResponse(securityManager.encrypt(r));
}catch(InvlidateMessage x) {
handleInvalidMessage...
}catch(BusinessException x) {
handleBusinessException...
}
}
I've used an ExceptionMapper on the server side, putting the custom exception in the Response's body. How can I retrieve the original exception on the client side, and throw it to the caller?
You can serialize the exception and include it as a part of the response:
public final class SerializingExceptionMapper implements ExceptionMapper<Exception> {
#Override
public Response toResponse(Exception exception) {
try {
final byte[] serializedException = serializeException(exception);
final String base64EncodedException = Base64.getEncoder().encodeToString(serializedException);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new Message(base64EncodedException))
.build();
} catch (Exception ex) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
}
private byte[] serializeException(Exception ex) throws IOException {
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(ex);
oos.close();
return bos.toByteArray();
}
}
final class Message {
public Message(String exception) {
this.exception = exception;
}
public String exception;
}
Then on the client side you should do just the opposite:
Unmarshal the (maybe JSON) response
Decode the base64 encoded exception to a byte[]
De-serialize the exception
Create a ByteArrayInputStream
Create ObjectInputStream
Just readObject() the exception
Do whatever you want with it on the client side
PS: This can be achieved without any buffering (i.e. without the byte[]s) -> just use a StreamingOutput as .entity() and write to the provided output stream instead of a ByteArrayOutputStream. The same applies for deserialization on the client side.
I have written a interceptor in the outgoing chain in the SEND phase and I want to get the payload of the response of my REST service into a String variable. How can I achieve this?
Here is my interceptor
public class MyLoginOutInterceptor extends LoggingOutInterceptor {
public MyLoginOutInterceptor() {
super(Phase.SEND);
}
#Override
public void handleMessage(Message message) throws Fault {
OutputStream os = message.getContent(OutputStream.class);
}
}
When I put a breakpoint to the OutputStream os = message.getContent(OutputStream.class); I see that the payload is in the os but I don't know how to get it into a String.
Any ideas?
In the responsePayload you should have what you want.
public void handleMessage(Message message) throws Fault {
OutputStream os = message.getContent(OutputStream.class);
StringBuilder responsePayload = new StringBuilder();
CachedOutputStream cos = (CachedOutputStream) os;
try {
cos.writeCacheTo(responsePayload);
} catch (IOException e) {
e.printStackTrace();
}
}
My requirement is, I should send a 10MB zip file to the client with a restful service. I found the code in forums that sending a StreamingOutput object is the better way, but how can I create a StreamingOutput object in the following code:
#Path("PDF-file.pdf/")
#GET
#Produces({"application/pdf"})
public StreamingOutput getPDF() throws Exception {
return new StreamingOutput() {
public void write(OutputStream output) throws IOException, WebApplicationException
{
try {
//------
} catch (Exception e) {
throw new WebApplicationException(e);
}
}
};
}
Its the better way and easy way for file dowload.
private static final String FILE_PATH = "d:\\Test2.zip";
#GET
#Path("/get")
#Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response getFile() {
File file = new File(FILE_PATH);
ResponseBuilder response = Response.ok((Object) file);
response.header("Content-Disposition", "attachment; filename=newfile.zip");
return response.build();
}
For your code as you asked:
#GET
#Path("/helloWorldZip")
#Produces(MediaType.APPLICATION_OCTET_STREAM)
public StreamingOutput helloWorldZip() throws Exception {
return new StreamingOutput(){
#Override
public void write(OutputStream arg0) throws IOException, WebApplicationException {
// TODO Auto-generated method stub
BufferedOutputStream bus = new BufferedOutputStream(arg0);
try {
//ByteArrayInputStream reader = (ByteArrayInputStream) Thread.currentThread().getContextClassLoader().getResourceAsStream();
//byte[] input = new byte[2048];
java.net.URL uri = Thread.currentThread().getContextClassLoader().getResource("");
File file = new File("D:\\Test1.zip");
FileInputStream fizip = new FileInputStream(file);
byte[] buffer2 = IOUtils.toByteArray(fizip);
bus.write(buffer2);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
}
I am writing a small file upload utility thing as part of a larger project. Originally I was handling this from a servlet using the Apache commons File utility classes. Here is a snippet from a quick test client I wrote for the service:
public static void main(String[] args) {
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.getInInterceptors().add(new LoggingInInterceptor());
factory.getOutInterceptors().add(new LoggingOutInterceptor());
factory.setServiceClass(FileUploadService.class);
factory.setAddress("http://localhost:8080/FileUploadService/FileUploadService");
FileUploadService client = (FileUploadService) factory.create();
FileType file = new FileType();
file.setName("statemo_1256144312279");
file.setType("xls");
DataSource source = new FileDataSource(new File("c:/development/statemo_1256144312279.xls"));
file.setHandler(new DataHandler(source));
Boolean ret = client.uploadFile(file);
System.out.println (ret);
System.exit(0);
}
This works absolutely fine. Now the problem comes when I am trying to replace the Apache commons utilities. In the above code I am creating a DataSource from a File with an absolute path name. In my servlet, I can't get an absolute path name however and the file I am sending over the wire is empty.
Here is the servlet code:
#SuppressWarnings("unchecked")
protected void doPost (final HttpServletRequest request, final HttpServletResponse response)
throws ServletException, IOException {
// form should have enctype="multipart/form-data" as an attribute
if (!ServletFileUpload.isMultipartContent (request)) {
LOG.info("Invalid form attribute");
return;
}
//DataInputStream in = new DataInputStream(request.getInputStream());
final DiskFileItemFactory factory = new DiskFileItemFactory ();
factory.setSizeThreshold(FILE_THRESHOLD_SIZE);
final ServletFileUpload sfu = new ServletFileUpload (factory);
sfu.setSizeMax(MAX_FILE_SIZE);
final HttpSession session = request.getSession();
final List<FileItem> files = new ArrayList<FileItem>();
final List<String> filesToProcess = new ArrayList<String>();
try {
final List<FileItem> items = sfu.parseRequest(request);
for (final FileItem f : items) {
if (!f.isFormField())
files.add(f);
}
/*for (final FileItem f : files) {
final String absoluteFileName = UPLOAD_DESTINATION + FilenameUtils.getName(f.getName());
//f.write(new File (absoluteFileName));
filesToProcess.add(absoluteFileName);
}*/
FileItem f = files.get(0);
LOG.info("File: " + FilenameUtils.getName(f.getName()));
LOG.info("FileBaseName: " + FilenameUtils.getBaseName(f.getName()));
LOG.info("FileExtension: " + FilenameUtils.getExtension(f.getName()));
FileUploadServiceClient client = new FileUploadServiceClient();
DataSource source = new FileDataSource(new File(f.getName()));
FileType file = new FileType();
file.setHandler(new DataHandler(source));
file.setName(FilenameUtils.getBaseName(f.getName()));
file.setType(FilenameUtils.getExtension(f.getName()));
Boolean ret = client.uploadFile(file);
LOG.info("File uploaded - " + ret);
filesToProcess.add(UPLOAD_DESTINATION + FilenameUtils.getName(f.getName()));
session.setAttribute("filesToProcess", filesToProcess);
final RequestDispatcher dispatcher = request.getRequestDispatcher("Validate");
if (null != dispatcher) {
dispatcher.forward(request, response);
}
} catch (FileUploadException e) {
LOG.info("Exception " + e.getMessage());
e.printStackTrace();
} catch (Exception e) {
LOG.info("Exception " + e.getMessage());
e.printStackTrace();
}
}
I've been working on this for the better part of this morning and am not getting anywhere. Even if I get rid of the Apache commons file stuff completely and handle the parsing of the request myself, I still can't construct the DataSource appropriately.
Thanks!
This was rather simple actually, I just copied over the bytes from the InputStream to the DataSource:
FileItem f = files.get(0);
// there is a problem here where the file being created is empty, since we only have a
// partial path:
DataSource source = new FileDataSource(new File(f.getName()));
// because of the above problem, we are going to copy over the data ourselves:
byte[] sourceBytes = f.get();
OutputStream sourceOS = source.getOutputStream();
sourceOS.write(sourceBytes);
This is the code of commons-email ByteArrayDataSource
it sounds odd to try to replace apache commons - don't, unless you have a really good reason
you can get absolute paths in a servlet. You can call getServletContext().getRealPath("/") which will return the absolute path of your application, and then you can get files relative to it.
In our application there are objects that have properties InputStream and Name. We are using next class to construct DataSource with those properties.
public class InputStreamDataSource implements DataSource {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
private final String name;
public InputStreamDataSource(InputStream inputStream, String name) {
this.name = name;
try {
int nRead;
byte[] data = new byte[16384];
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
inputStream.close();
buffer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public String getContentType() {
return new MimetypesFileTypeMap().getContentType(name);
}
#Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(buffer.toByteArray());
}
#Override
public String getName() {
return name;
}
#Override
public OutputStream getOutputStream() throws IOException {
throw new IOException("Read-only data");
}
}
Most of the solutions shown here require that the InpustStream be closed (read into memory). It is possible to wrap the InputStream in a DataSource object without closing the InputStream though:
private record PipedDataSource(InputStream in, String contentType, String encoding)
implements DataSource, EncodingAware {
public String getContentType() {
return contentType;
}
public InputStream getInputStream() {
return in;
}
public String getName() {
return "PipedDataSource";
}
public OutputStream getOutputStream() throws IOException {
throw new IOException("No OutputStream");
}
#Override
public String getEncoding() {
return encoding;
}
}
The example above also implements EncodingAware. This can prevent the InputStream from being closed by third part libraries (for example java.mail.internet.MimeUtility) when they get the data source encoding.