Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
273 views
in Technique[技术] by (71.8m points)

java - Images won't load if they are of high size

I am storing images in DB using MEDIUMBLOB. When I try to load image through a servlet, I am able to see those images. However if the image size is big (1 MB or more), I can see half or 3/4th image on the browser.

When I download same image and put it in public webcontent, it works perfectly. Any idea how to overcome this issue? Do I need to set any variable in the servlet or MySQL?

The (JSF-generated) HTML code is as follows:

<img src="DisplayImage?mainID=drawing" />

The image servlet does the following:

String imgLen = rs1.getString(1);
int len = imgLen.length();
byte[] rb = new byte[len];
InputStream readImg = rs1.getBinaryStream(1);
InputStream inputStream = readImg;
int index = readImg.read(rb, 0, len);
response.reset();
response.setHeader("Content-Length", String.valueOf(len));
response.setHeader("Content-disposition", "inline;filename=/file.png");
response.setContentType("image/png");
response.getOutputStream().write(rb, 0, len);
response.getOutputStream().flush();

Edit 1

When I use below code and save file to local disk, I see full Image.

String imgLen = rs1.getString(1);
int len = imgLen.length();
rb = new byte[len];
inputStream = rs1.getBinaryStream(1);
while ((read = inputStream.read(rb)) != -1) {
    out.write(rb, 0, read);
}
out.flush();
out.close();

Edit 2

When I save the half viewed images I noticed that the size of those images are 100KB. My 1 MB image is showing size as 100KB. This is happening with all images :(

I think this is biggest HINT what wrong is going on. BUT I am not getting what is wrong.

Edit 3

If I remove below stuff from my web.xml, I am able to view those images.

<filter>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <filter-class>org.apache.myfaces.webapp.filter.ExtensionsFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <servlet-name>Faces Servlet</servlet-name>
</filter-mapping>

Can I remove those? I don't re-collect why exactly I added those...

Edit 4

My web.xml file is

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <context-param>
        <param-name>javax.faces.PROJECT_STAGE</param-name>
        <param-value>Development</param-value>
    </context-param>
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>/faces/*</url-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>
            60
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>faces/index.xhtml</welcome-file>
    </welcome-file-list>
    <filter>
        <filter-name>restrict</filter-name>
        <filter-class>com.sac.filter.MyFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>restrict</filter-name>
        <url-pattern>*.xhtml</url-pattern>
    </filter-mapping>
    <filter>
        <filter-name>MyFacesExtensionsFilter</filter-name>
        <filter-class>org.apache.myfaces.webapp.filter.ExtensionsFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>MyFacesExtensionsFilter</filter-name>
        <servlet-name>Faces Servlet</servlet-name>
    </filter-mapping>

    <servlet>
        <servlet-name>DisplayImage</servlet-name>
        <servlet-class>com.sac.databean.DisplayImage</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>DisplayImage</servlet-name>
        <url-pattern>/DisplayImage</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>SaveMyImage</servlet-name>
        <servlet-class>com.sac.databean.SaveMyImage</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>SaveMyImage</servlet-name>
        <url-pattern>/SaveMyImage</url-pattern>
    </servlet-mapping>
</web-app>
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The way how you measured the image's length is wrong. You're basically converting the image bytes to human readable characters (in an unintelligible sequence, but that aside) and then calculating the amount of characters. This is not right. You need to calculate the amount of raw and unconverted bytes instead. One character is not necessarily represented by only one byte, but it can be represented by more than one byte.

There are 2 ways to solve your problem:

  1. Use ResultSet#getBytes() instead.

    byte[] content = resultSet.getBytes("content");
    // ...
    response.setContentLength(content.length);
    response.getOutputStream().write(content);
    

    Please note that this is memory hogging as every byte of a byte[] basically accumulates one byte of Java's memory.

  2. Select the BLOB length in the query as well. How to do that depends on the DB used. In MySQL, you can use the LENGTH() function for this.

    SELECT content, LENGTH(content) AS contentLength FROM image WHERE id = ?
    

    Which you then process as follows:

    InputStream content = resultSet.getBinaryStream("content");
    int contentLength = resultSet.getInt("contentLength");
    // ...
    response.setContentLength(contentLength);
    SomeUtil.streamByBuffer(content, response.getOutputStream());
    

    (wherein the process is not taken place through a byte[] of full image's length)


Update: after all, the request on the image seemed to have invoked the MyFacesExtensionsFilter which is apparently overzealously buffering the response without properly flushing it when the chain.doFilter() returns. This filter is according the mapping rules only invoked when the FacesServlet is invoked. But this shouldn't have happened. The image request should only have invoked the image servlet, not also the faces servlet.

According the mapping rules, the FacesServlet is invoked on /faces/* while your image servlet is mapped on /DisplayImage. The <img src> as you've there is relative to the current request URL, so it ultimately ends up to be /faces/DisplayImage which will first invoke the FacesServlet and then the DisplayImage servlet. This is wrong.

You should change the JSF code accordingly so that the <img src> ultimately becomes domain-relative so that it invokes only the /DisplayImage servlet.

<img src="/contextpath/DisplayImage?mainID=drawing" />

You can achieve this by just using the leading slash in <h:graphicImage>. It will auto-prepend the context path.

<h:graphicImage value="/DisplayImage?mainID=drawing" />

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...