从Java到Coldfusion的多部分文件传输 - 没有前导边界:%PDF-1.4

时间:2015-04-17 18:54:29

标签: java unit-testing pdf post coldfusion

在我的问题之前有一段历史,我发布了一个关于使用HTML5的multipart / form-data将多个文件上传到coldfusion的问题。它工作得很漂亮。 Can you isolate code from being seen from CF10 compiler?

我们的客户终于要求对我放在一起的RESTful功能进行一些单元测试,并且我已经完成了很多工作,但是我已经遇到了一个带有massUpload功能的障碍我在上面设计了。

抱歉这个长问题,记下与问题相关的内容。

以下是相关代码:

单元测试代码:

//Outside class calling sendHTTPrequest
HashMap<String,String> map = new HashMap<String,String>();
HashMap<String,File> getFiles = getFirstFileList();
map.put("testMethod", "massUploadTest");
map.put("method", "massUpload");
map.put("valueString1", valueString1);
map.put("valueString2", valueString2);
map.put("valueNumeric3", valueNumeric3);
map.put("valueBoolean4", valueBoolean4);
map.put("valueString5", valueString5);
map.put("valueBoolean6", valueBoolean6);
map.put("valueString7", valueString7);

try {                    
    sendHTTPrequest(map, getFiles);
} catch(RuntimeException e) {
    throw new RuntimeException("Fatal error in massUpload\n"
            + e.getMessage());
}
//End Call class code

Coldfusion功能:

<cffunction name="massUpload" access="remote" returntype="string">    
    <cfargument name="valueString1"  type="string"  required="false">
    <cfargument name="valueString2"  type="string"  required="false">
    <cfargument name="valueNumeric3" type="numeric" required="false" default=0>
    <cfargument name="valueBoolean4" type="boolean" required="true"  default="false">
    <cfargument name="valueString5"  type="string"  required="false">
    <cfargument name="valueBoolean6" type="boolean" required="false" default="true">
    <cfargument name="valueString7"  type="string"  required="true">

    <!--- massUpload code --->
</cffunction>

所以这里是造成重大问题的代码。我已经尝试了不同的方法来使POST工作,所以很明显代码有问题。我试图在不下载多个软件/库的情况下尝试这样做,但如果没有其他办法,那么我将做出必要的安排。否则,我们很乐意使用标准Java库完成这项工作。

功能代码:

//sendHTTPrequest method code
protected static final String BASE_URI = "<webaddress>/rest.cfc";
protected static final String CHARSET = "UTF-8";
protected String response;
protected int status;
protected String statusMessage;

protected void sendHTTPrequest(Map<String,String> map, Map<String, File> fileList) {

        Set<String> keys = map.keySet();
        status = 0;
        response = null;
        String boundary = "----" + System.currentTimeMillis();

        try {            
            URL_CONNECTION = BASE_URL.openConnection();
            HTTP_CONNECTION = (HttpURLConnection) (URL_CONNECTION);

            //Set the request headers
            HTTP_CONNECTION.setRequestMethod("POST");
            URL_CONNECTION.setRequestProperty("Accept-Charset", CHARSET);            
            URL_CONNECTION.setRequestProperty("Content-type", "multipart/form-data; boundary=" + boundary);

            //Set up a post request
            URL_CONNECTION.setDoOutput(true);

            OutputStream output = URL_CONNECTION.getOutputStream();
            ByteArrayOutputStream bOutput = new ByteArrayOutputStream();
            PrintWriter writer = new PrintWriter(new OutputStreamWriter(output, CHARSET), true);

            for(String key : keys) {
                writer.write("--" + boundary);
                writer.write(lineFeed);
                writer.write("Content-Disposition: form-data; name=\"" + key + "\"");
                writer.write(lineFeed);
                writer.write(lineFeed);
                writer.write(map.get(key));
                writer.write(lineFeed);
            }

            FileInputStream inputStream;
            for(Map.Entry<String, File> entry : fileList.entrySet()){
                String name = entry.getKey();
                writer.write("--" + boundary);
                writer.write(lineFeed);
                writer.write("Content-Disposition: form-data; "
                        + "name=\"allfiles\"; filename=\"" + name + "\"");
                writer.write(lineFeed);

                String contentType = URLConnection.guessContentTypeFromName(name);
                writer.write("Content-Type: " + contentType);
                writer.write(lineFeed);    

                writer.write("Content-Transfer-Encoding: binary");
                writer.write(lineFeed);    

                writer.write("Content-Id: <" + boundary + ">");
                writer.write(lineFeed);  
                writer.write(lineFeed);  

                File temp = entry.getValue();

                byte[] buffer = FileUtils.readFileToByteArray(temp);
                output.write(buffer, 0, buffer.length);
                output.flush();

                writer.write(lineFeed);    
            }

            writer.write("--" + boundary + "--");
            writer.write(lineFeed);
            writer.flush();

            writer.close();

            status = HTTP_CONNECTION.getResponseCode();
            statusMessage = HTTP_CONNECTION.getResponseMessage();
            if(status == SUCCESS) {
                response = IOUtils.toString(URL_CONNECTION.getInputStream(), URL_CONNECTION.getContentEncoding());
            }
            System.out.println("Finished test " + map.get("testMethod") + " Status: " + status);
            System.out.println("Response: " + response);
            HTTP_CONNECTION.disconnect();

        } catch(UnknownServiceException use) {
            throw new RuntimeException("Protocol for the output is not supported");
        } catch(IOException ioe) {
            throw new RuntimeException("Unable to create the output stream");
        }
    }
//End sendHTTPrequest method code

我在输出中看到status,response和statusMessage,我得到500错误,null,内部服务器错误。

在Coldfusion服务器上看,我看到了:

SEVERE: Servlet.service() for servlet [CFCServlet] in context with path [/] threw exception 
java.io.IOException: Corrupt form data: no leading boundary: %PDF-1.4 != ------1429222349902
at com.oreilly.servlet.multipart.MultipartParser.<init>(MultipartParser.java:182)
...

massUpload可以处理不同类型的文件,但专门针对PDF。因此,单元测试需要能够将不同的文件类型发送到massUpload,而不仅仅是PDF。

欢迎任何有关此问题的见解。谢谢。

1 个答案:

答案 0 :(得分:2)

(来自评论)

您是否使用过数据包嗅探器来查看上面实际生成的内容?

尝试在写入文件字节之前刷新writer:

...
byte[] buffer = FileUtils.readFileToByteArray(temp);
writer.flush(); // flush stream here
output.write(buffer, 0, buffer.length);
...

<强>更新

为了澄清,原始错误的原因是即使代码创建了一个带有autoFlush=true的PrintWriter,当调用write()时,它仍然不会自动将文本保存到底层的OutputStream。 autoFlush设置仅影响这些方法:

  

autoFlush - 如果为true,则为printlnprintfformat方法   将[自动]刷新输出缓冲区

因此,代码会在 boundary markers之前将文件内容写入流,从而创建无效的HTTP POST。因此500错误。您可以使用像Fiddler这样的包嗅探器来验证这一点:

原创 - (无效):

POST http://localhost:8888/test.cfm HTTP/1.1
Accept-Charset: UTF-8
Content-Type: multipart/form-data; boundary=14cd4c75e24
...
Content-Length: 65570

%PDF-1.4
%    
2 0 obj <</Type/XObject/ColorSpace/DeviceGray/Subtype/Image/BitsPerComponent 8/Width 612/Length 11876/Height 792/Filter/FlateDecode>>stream

... more binary

--14cd4c75e24
Content-Disposition: form-data; name="allfiles"; filename="file0.pdf"
Content-Type: application/pdf
Content-Transfer-Encoding: binary
Content-Id: <14cd4c75e24>


--14cd4c75e24--

解决方案是在添加文件内容之前刷新writer 。这可确保将边界标记添加到正确的位置,因此生成的POST内容有效。

新代码(有效)

POST http://localhost:8888/test.cfm HTTP/1.1
Accept-Charset: UTF-8
Content-Type: multipart/form-data; boundary=14cd4c91d29
...
Content-Length: 65570

--14cd4c91d29
Content-Disposition: form-data; name="allfiles"; filename="file0.pdf"
Content-Type: application/pdf
Content-Transfer-Encoding: binary
Content-Id: <14cd4c91d29>

%PDF-1.4
%    
2 0 obj <</Type/XObject/ColorSpace/DeviceGray/Subtype/Image/BitsPerComponent 8/Width 612/Length 11876/Height 792/Filter/FlateDecode>>stream
...

顺便说一句,我相信Content-Id必须是唯一的。所以“边界”可能不是Content-Id值的好选择。