getting Java Bad File Descriptor Close Bug while reading multipart/form-data http body - parsing

My web service hosted on Play! framework. I have few image files uploaded from a non-play! framework based client using the standard HTTP client request with content-type of multipart/form-data.
On the web service side, I tried using Play! ApacheMultipartParser to parse the Http.request.body, but failed with the Java IO Bad File Descriptor exception.
The problem seems come from Java MultipartStream, by looking at the following callstack
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(FileInputStream.java:208)
at org.apache.commons.fileupload.MultipartStream$ItemInputStream.makeAvailable(MultipartStream.java:976)
at org.apache.commons.fileupload.MultipartStream$ItemInputStream.read(MultipartStream.java:886)
at java.io.InputStream.read(InputStream.java:85)
I also tried directly reading the http.request.body into a big buffer for experiment, got the same exception. What could be wrong?
The http data sent out from client side is something like the following. On web service side, I could using IO.write to save it to a file w/o any problem.
Content-Type: multipart/form-data; boundary=--3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f
--3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f
Content-Disposition: form-data; name="foo1.jpg"; filename="foo1.jpg"
Content-Length: 5578
Content-Type: image/jpeg
<image data 1 omitted>
--3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f
Content-Disposition: form-data; name="foo2.jpg"; filename="foo2.jpg"
Content-Length: 327
Content-Type: image/jpeg
<image data 2 omitted>
--3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f--

I had the exact same issue. The problem lies in the way that Play! handles multipart uploads. Usually you can add a FileUpload to your upload method and get your files there. This helps a lot as you can get the filenames and sizes and all this stuff directly from Play:
public static void uploadFile(File fileUpload) {
String name = fileUpload.getName() // etc.
}
However using this logic prevents you from using the HTTPRequest. So if you use a non-Play way of uploading files (e.g with XMLHTTPRequest) where the automatic mapping to the fileUpload won't work the following thing happens:
Play tries to bind the request to your arguments
Play encounters your File argument and parses the request.
Play finds nothing of use (as it doesn't understand XMLHttpRequest) and maps your File argument to null.
Now the request input stream has already been consumed by Play and you get your "Bad File Descriptor" message.
The solution to this is, to not use any Play! magic, if you want to use the same method for uploading via Form and XMLHttpRequest (XHR). I wanted to use Valum's file uploader script (http://github.com/valums/file-uploader) in addition to my own form based upload method. One uses XHR, the other uses plain multipart form uploads. I created the following method in my controller, that takes the uploaded file from the "qqfile" parameter and works with form based and XHR-Uploads:
#SuppressWarnings({"UnusedDeclaration"})
public static void uploadFile() {
FileUpload qqfile = null;
DataParser parser = DataParser.parsers.get(request.contentType);
if (parser != null) {
// normal upload. I have to manually parse this because
// play kills the body input stream for XHR-requests when I put the file upload as a method
// argument to {#link #uploadFile)
parser.parse(request.body);
#SuppressWarnings({"unchecked"})
ArrayList<FileUpload> uploads = (ArrayList<FileUpload>) request.args.get("__UPLOADS");
for (FileUpload upload : uploads) {
if ("qqfile".equals(upload.getFieldName())) {
qqfile = upload;
break;
}
}
} else {
// XHR upload
qqfile = new FileUpload(new XHRFileItem("qqfile"));
}
if (qqfile == null) {
badRequest();
return;
}
// and now do something with your Fileupload object here (e.g. write it to db or something else)
}
You probably can skip the IF-part of the if, if you split this method into two, so you can use the normal Play! magic for default uploads and use a separate method for your XHR uploads.
I also had to create the XHRFileItem class which just wraps around a file item that is posted via an XMLHttpRequest. You might have to modify it a bit to work with multiple files and your particular file uploader, but nevertheless here it is:
package application.util;
import org.apache.commons.fileupload.FileItem;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import static play.mvc.Http.Request.current;
/**
* An implementation of FileItem to deal with XmlHttpRequest file uploads.
*/
public class XHRFileItem implements FileItem {
private String fieldName;
public XHRFileItem(String fieldName) {
this.fieldName = fieldName;
}
public InputStream getInputStream() throws IOException {
return current().body;
}
public String getContentType() {
return current().contentType;
}
public String getName() {
String fileName = current().params.get(fieldName);
if (fileName == null) {
fileName = current().headers.get("x-file-name").value();
}
return fileName;
}
public boolean isInMemory() {
return false;
}
public long getSize() {
return 0;
}
public byte[] get() {
return new byte[0];
}
public String getString(String s) throws UnsupportedEncodingException {
return s;
}
public String getString() {
return "";
}
public void write(File file) throws Exception {
FileOutputStream fos = new FileOutputStream(file);
InputStream is = getInputStream();
byte[] buf = new byte[64000];
int read;
while ((read = is.read(buf)) != -1) {
fos.write(buf, 0, read);
}
fos.close();
}
public void delete() {
}
public String getFieldName() {
return fieldName;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public boolean isFormField() {
return false;
}
public void setFormField(boolean b) {
}
#Nullable
public OutputStream getOutputStream() throws IOException {
return null;
}
}
Hope this helps, it took me about a day to make this work on my end.

Related

Grails 2.4.4 request object logging

I need to log the incomming REST request (xml).
So I create a
public class RequestWrapper extends HttpServletRequestWrapper {
private String _body;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
_body = "";
BufferedReader bufferedReader = request.getReader();
String line;
while ((line = bufferedReader.readLine()) != null){
_body += line;
}
}
Now I use a filter to log out the incomming request:
class XmlogFilterFilters {
def filters = {
all(controller:'*', action:'*') {
before = {
RequestWrapper wrappedRequest = new RequestWrapper(request)
log.info(wrappedRequest.reader.text)
}
}
}
This logs the incomming request as expected.
But now in my controller the request is empty and cannot be used to Build my Domain-Object:
class InquiryHandlerController {
def save(Inquiry inquiryInstance) {
... *** inquiryInstance is null here
}
}
I guess the problem is, that th request was already read in the RequestWrapper, and therefor cannot be read again in the magic requestToDomain conversion.
So how can I pass the new RequestWrapper Object instead of the original request to the controller?
Finally found a solution:
use grailsplugin: grails-httplogger in version 1.1 do the right wrapper thing, so logging and consuming is working now.
https://github.com/prumps/grails-httplogger

Configuring message-bundle with UTF-8 encoded messages

What I'm trying to achieve is UTF-8 encoded JSF validation and converter messages (message-bundle). Also, I would like custom resource bundle for labels, etc (resource-bundle).
I managed to create and use custom resource bundle successfully, but I have problem with defining message-bundle.
I have the following structure under src/ directory:
- com.example.i18n.resources
- FacesMsgs.properties
- FacesMsgs_de.properties
- UTF8FacesMsgs.java
- Msgs.properties
- Msgs_de.properties
- UTF8Msgs.java
Faces-config.xml snippet:
<application>
<message-bundle>com.example.i18n.resources.UTF8FacesMsgs</message-bundle>
<resource-bundle>
<base-name>com.example.i18n.resources.UTF8Msgs</base-name>
<var>msg</var>
</resource-bundle>
<locale-config>
<default-locale>de</default-locale>
</locale-config>
</application>
In my implementation I have followed the instructions given here:
Internationalization in JSF with UTF-8 encoded properties files
These are resulting classes:
public class ResourceBundleWrapper extends ResourceBundle {
public ResourceBundleWrapper(ResourceBundle parent) {
setParent(parent);
}
#Override
protected Object handleGetObject(String key) {
return parent.getObject(key);
}
#Override
public Enumeration<String> getKeys() {
return parent.getKeys();
}
}
EncodingControl.java
public class EncodingControl extends Control {
private String encoding;
private String extension;
public EncodingControl(String encoding, String extension) {
this.encoding = encoding;
this.extension = extension;
}
#Override
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
throws IllegalAccessException, InstantiationException, IOException {
String bundleName = toBundleName(baseName, locale);
String resourceName = toResourceName(bundleName, extension);
ResourceBundle bundle = null;
InputStream stream = null;
if (reload) {
URL url = loader.getResource(resourceName);
if (url != null) {
URLConnection connection = url.openConnection();
if (connection != null) {
connection.setUseCaches(false);
stream = connection.getInputStream();
}
}
} else {
stream = loader.getResourceAsStream(resourceName);
}
if (stream != null) {
try {
bundle = new PropertyResourceBundle(new InputStreamReader(stream, encoding));
} finally {
stream.close();
}
}
return bundle;
}
}
UTF8FacesMsgs.java
public class UTF8FacesMsgs extends ResourceBundleWrapper {
public UTF8FacesMsgs() {
super(ResourceBundle.getBundle("com.example.i18n.resources.FacesMsgs",
FacesContext.getCurrentInstance().getViewRoot().getLocale(),
new EncodingControl("UTF-8", "properties"));
}
}
UTF8Msgs.java
public class UTF8Msgs extends ResourceBundleWrapper {
public UTF8Msgs() {
super(ResourceBundle.getBundle("com.example.i18n.resources.Msgs",
FacesContext.getCurrentInstance().getViewRoot().getLocale(),
new EncodingControl("UTF-8", "properties"));
}
}
Resulting behaviour:
resource-bundle configuration seems to be working without issue: default UTF8Msgs.java is used and all labels on the page are rendered successfully.
On the other hand, behaviour with message-bundle is quite different although everything is configured likewise: when validation fails (submiting the button triggers validation) I get MissingResourceException - the default UTF8FacesMsgs.java is not used and UTF8FacesMsgs_de.java is expected.
Behaviour with changes introduced:
When I configure faces-config.xml like this (directly using properties file, not java class):
<application>
<message-bundle>com.example.i18n.resources.FacesMsgs</message-bundle>
<resource-bundle>
<base-name>com.example.i18n.resources.UTF8Msgs</base-name>
<var>msg</var>
</resource-bundle>
<locale-config>
<default-locale>de</default-locale>
</locale-config>
</application>
and delete FacesMsgs_de.properties everything runs without issue. So - default properties file is used and default java file is not.
Does anybody know what is the problem with message-bundle configuration?

Read and write cookies with #Push

In my vaadin application, i need to use #Push, but since i added it, i can't read and write cookies because VaadinService.getSurrentResponse()returns null because of Push. I manager cookies using this class :
import javax.servlet.http.Cookie;
import com.vaadin.server.VaadinResponse;
import com.vaadin.server.VaadinService;
public class CookieManager {
private VaadinResponse response;
public CookieManager(VaadinResponse response){
this.response = response;
}
public Cookie getCookieByName(final String name) {
// Fetch all cookies from the request
Cookie[] cookies = VaadinService.getCurrentRequest().getCookies();
// Iterate to find cookie by its name
for (Cookie cookie : cookies) {
if (name.equals(cookie.getName())) {
return cookie;
}
}
return null;
}
public Cookie createCookie(final String name, final String value, final int maxAge) {
// Create a new cookie
final Cookie cookie = new Cookie(name, value);
cookie.setMaxAge(maxAge);
// Set the cookie path.
cookie.setPath(VaadinService.getCurrentRequest().getContextPath());
// Save cookie
addCookie(cookie);
return cookie;
}
private void addCookie(Cookie cookie){
response.addCookie(cookie);
}
public Cookie updateCookieValue(final String name, final String value) {
// Create a new cookie
Cookie cookie = getCookieByName(name);
cookie.setValue(value);
// Save cookie
addCookie(cookie);
return cookie;
}
public void destroyCookieByName(final String name) {
Cookie cookie = getCookieByName(name);
if (cookie != null) {
cookie.setValue(null);
// By setting the cookie maxAge to 0 it will deleted immediately
cookie.setMaxAge(0);
cookie.setPath(VaadinService.getCurrentRequest().getContextPath());
addCookie(cookie);
}
}
}
When i want to create a cookie (like at user's login), i get a nullPointerException because of the VaadinResponse being null.
So i tried to disable Push in constructor and re-enable it at the end of addCookie()method, but it disabled push for all of my application, even if i re-enable it just after the addCookiemethod.
I saw a ticket on vaadin's trac (http://dev.vaadin.com/ticket/11808) saying that will not be fixed, and someone suggested to create a regular AJAX query from server to create cookie, but i really don't know how to do.
How can i manage my cookies? i need to create AND get cookies, so javascript can't help me there, because i can't get javascript's return in vaadin, so i can't get a cookie.
Here is my solution how to store cookie when #Push is using.
First we create container to storage all instance of client UI. (
This container itself has a great potential)
public class UISession {
private List<WebAppUI> uis = new ArrayList<WebAppUI>();
public void addUI(WebAppUI webAppUI) {
uis.add(webAppUI);
}
public List<WebAppUI> getUIs() {
return uis;
}
public static UISession getInstance() {
try {
UI.getCurrent().getSession().lock();
return (UISession) UI.getCurrent().getSession().getAttribute("userUiSession");
} finally {
UI.getCurrent().getSession().unlock();
}
}
In UI.init() we add new instance to the session (e.g when user open new tab)
#Override
protected void init(VaadinRequest vaadinRequest) {
/** Set singleton uisesison for each browser*/
if(UISession.getInstance()==null){
UI.getCurrent().getSession().setAttribute("userUiSession",new UISession());
}
UISession.getInstance().addUI(this);
System.out.println("UI count fo current browser "+UISession.getInstance().getUIs().size());
...
}
Here is my helper cookie class:
class MyCookie{
private String value;
private String name;
private Date expired;
private String path="/";
public MyCookie(String name, String value) {
this.name=name;
this.value=value;
}
public void setMaxAge(int minute) {
Calendar c = Calendar.getInstance();
c.add(Calendar.MINUTE, minute);
expired=c.getTime();
}
public String getStringToCreateCookie(){
return "document.cookie=\""+getName()+"="+getValue()+"; expires="+expired.toString()+"; path="+path+"\"";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public Date getExpired() {
return expired;
}
public void setExpired(Date expired) {
this.expired = expired;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}
And on final when we need add new cookie, we just must find Ui that is active and call js function
public static void addCookie(String name, String value, int age){
MyCookie myCookie = new MyCookie(name, value);
myCookie.setMaxAge(age);
for(WebAppUI ui : UISession.getInstance().getUIs()){
if(ui.isAttached()){
ui.getPage().getJavaScript().execute(myCookie.getStringToCreateCookie());
return;
}
}
}
In my case i have access to storage cookie (when user made request). I just only have problem with add new cookie so this is my working solutions.
As mentioned in the ticket, you can use JavaScript to call client code and also request a cookie value back by that. E.g.
#Grapes([
#Grab('org.vaadin.spring:spring-boot-vaadin:0.0.3'),
#Grab('com.vaadin:vaadin-server:7.4.0.beta1'),
#Grab('com.vaadin:vaadin-client-compiled:7.4.0.beta1'),
#Grab('com.vaadin:vaadin-themes:7.4.0.beta1'),
])
import com.vaadin.ui.*
#org.vaadin.spring.VaadinUI
#groovy.transform.CompileStatic
class MyUI extends UI {
protected void init(com.vaadin.server.VaadinRequest request) {
final resultLabel = new Label()
// provide a callback for the client to tell the cookies
JavaScript.current.addFunction("tellCookie", { elemental.json.JsonArray arguments ->
resultLabel.value = arguments?.get(0)?.asString()
} as JavaScriptFunction)
setContent(new VerticalLayout().with{
addComponent(new Button("Set Cookie", {
// just simply set the cookies via JS (attn: quoting etc)
JavaScript.current.execute("document.cookie='mycookie=${System.currentTimeMillis()}'")
} as Button.ClickListener))
addComponent(new Button("Get Cookie", {
// tell the client to tell the server the cookies
JavaScript.current.execute("this.tellCookie(document.cookie)")
} as Button.ClickListener))
addComponent(resultLabel)
return it
})
}
}
This is a running example (e.g. spring run vaadin.groovy) for testing. See the comments for the important parts.
The Viritin add-on contains a helper class called BrowserCookie. It works in pretty much the way suggested by cfrick, but just hides all the cookie handling complexity into a helper class. It don't contain built in "max age" handling yet, but that could be easily added as a workaround you can manually "encode" the age into cookie value.
BTW. Don't know what you are doing, but if you happen to be using TouchKit add-on, it has a helper for html5 local storage. It has rather wide browsers support already and is in many ways better way to store e.g. settings than cookies.

Disable automatic image loading in Webview/

I am developing a web scraper using JavaFX webview. For the scraping purpose, I don't need to have the images to be loaded. When the page is being loaded, Webkit spawns lots of UrlLoader thread. So I think it's better to have the images disabled, so I will save lots of system resources. Does anyone know how to disable automatic image loading in Webview?
Solution Approach
Define your own protocol handler for http and filter out anything with an image mime type or content.
URL.setURLStreamHandlerFactory(new HandlerFactory());
Sample Code
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.web.*;
import javafx.stage.Stage;
import java.io.IOException;
import java.net.*;
public class LynxView extends Application {
private static final String BLANK_IMAGE_LOC =
"https://upload.wikimedia.org/wikipedia/commons/c/ce/Transparent.gif";
public static final String WEBSITE_LOC =
"http://fxexperience.com";
public static final String IMAGE_MIME_TYPE_PREFIX =
"image/";
#Override
public void start(Stage stage) throws Exception {
WebView webView = new WebView();
WebEngine engine = webView.getEngine();
engine.load(WEBSITE_LOC);
stage.setScene(new Scene(new StackPane(webView)));
stage.show();
}
public static void main(String[] args) throws IOException {
URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() {
#Override
public URLStreamHandler createURLStreamHandler(String protocol) {
if ("http".equals(protocol)) {
return new sun.net.www.protocol.http.Handler() {
#Override
protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
String[] fileParts = url.getFile().split("\\?");
String contentType = URLConnection.guessContentTypeFromName(fileParts[0]);
// this small hack is required because, weirdly, svg is not picked up by guessContentTypeFromName
// because, for Java 8, svg is not in $JAVA_HOME/lib/content-types.properties
if (fileParts[0].endsWith(".svg")) {
contentType = "image/svg";
}
System.out.println(url.getFile() + " : " + contentType);
if ((contentType != null && contentType.startsWith(IMAGE_MIME_TYPE_PREFIX))) {
return new URL(BLANK_IMAGE_LOC).openConnection();
} else {
return super.openConnection(url, proxy);
}
}
};
}
return null;
}
});
Application.launch();
}
}
Sample Notes
The sample uses concepts from:
Getting A File's Mime Type In Java
The sample only probes the filename to determine the content type and not the input stream attached to the url. Though probing the input stream would be a more accurate way to determine if the resource the url is connected to is actually an image or not, it is slightly less efficient to probe the stream, so the solution presented trades accuracy for efficiency.
The provided solution only demonstrates locations served by a http protocol, and not locations served by a https protocol.
The provided solution uses a sun.net.www.protocol.http.Handler class which may not be publicly visible in Java 9, (so the solution might not work for Java 9).
The urlStreamHandlerFactory is a global setting for the JVM, so once it is set, it will stay that way (e.g. all images for any java.net.URL connections will be ignored).
The sample solution returns a blank (transparent) image, which it loads over the net. For efficiency, the image could be loaded as a resource from the classpath instead of over the net.
You could return a null connection rather a than a connection to a blank image, if you do so, the web view code will start reporting null pointer exceptions to the console because it is not getting the url connection it expects, and will replace all images with an x image to show that the image is missing (I wouldn't really recommend an approach which returned a null connection).
public URLStreamHandler createURLStreamHandler(String protocol) {
if ("http".equals(protocol)) {
return new URLFortuneHandler();
}
else return null;
}
}
public class URLFortuneHandler extends sun.net.www.protocol.http.Handler {
protected URLConnection openConnection(URL url) throws IOException {
String file = url.getFile();
int mid= file.lastIndexOf(".");
String ext = file.substring(mid+1,file.length());
if ("jpg".equals(ext) || "png".equals(ext))
return somethinghere;
else
return super.openConnection(url);
}
}

Return contents of a URL as a file in a jaxRS response

I am a REST wrapper service and when I call the back end service, in some cases they have generated a file which I can retrieve with a particular URL: https://localhost:1234/... How do I most efficiently use javax.ws.core.Response to send the contents at that link to the caller? I can probably read the URL myself to a local file and send it that way, but am wondering if REST will do that for me. Thanks,
when your back-end url is public to your client, you can send redirect 301.
When it's not
#Get
#Produces({...})
public Response readFile() {
return Response.ok().entity(new StreamingOutput() {
#Override public void write(OutputStream output)
throws IOException, WebApplicationException {
// open the back-end url and copy the bytes to given output
final InputStream input = new URL(back-end-url).openStream();
final byte[] buf = new byte[1024];
for (int read = -1; (read = input.read(buf)) != -1; ) {
output.write(buf, 0, read);
}
output.flush();
}
}).build();
}

Resources