【问题标题】:How to parse http header to get uploaded file and save it to disk如何解析 http 标头以获取上传的文件并将其保存到磁盘
【发布时间】:2023-04-10 21:00:02
【问题描述】:

我正在使用套接字在 java 中开发一个 http Web 服务器,它获取 post header InputStream,然后我处理了一些由 header 'boundary' 和 '\r\n' 拆分的 String 的 header,并获得了 HashMap 中的所有 Headers、Cookie (s) 并以字符串形式获取文件的内容,并将该字符串保存到服务器上的文件中。当我将文本文件或 java 源文件上传到服务器时,它工作正常,但在 doc、pdf 和图像的情况下,它会显示损坏的文件和损坏的图像。

    PrintWriter out;
        try {
            out = new PrintWriter(new OutputStreamWriter(
                    new FileOutputStream(UploadPath + "\\" + FileName)));
            out.print(FileData);
            out.close();
        } catch (Exception e) {

        }

上面的代码将用“FileName”将“FileData”的内容保存在“UploadPath”中。

如果是 jpg 或 doc 文件 String FileData 具有由上述代码保存的上传文件的二进制内容,我还检查了两个文件的字节大小,两者的字节大小相同,我也匹配了内容实际文件和内容 FileData String 通过调试应用程序。

我还检查了实际上传的图像文件和 FileData 字符串,两者都逐字节匹配,但上传的图像完全损坏。

在互联网上搜索了这一天之后,我无法找到解决方案。请帮忙。

我不想使用大多数页面上建议的 apache commons。

如果您想查看更多代码,那么我将发布它们。

【问题讨论】:

  • 你应该使用一个普通的好 ole FileOutputStream 来写入文件,你为什么使用 PrintWriter??
  • 顺便问一下,你用的是Java 7还是6?
  • 您知道文件损坏的原因吗?任何线索都对我很有帮助吗?文件格式编码有问题吗,因为这些是二进制文件
  • 也许数据是以base64的形式出现的?尝试在文本编辑器中打开结果文件。如果是 base64,则文本由大写和小写字母 A-Z、数字和字符 + 和 / 组成。
  • 至于为什么它会被损坏,这很正常......我会编辑我的答案

标签: java image http file-upload


【解决方案1】:

当你处理二进制数据时,你应该使用byteOutputStream而不是StringWriter:如果你把一些字节放在一个字符串中,它们就是decoded

因此,如果您在请求中找到了二进制数据的边界(由字节数组表示),请将内容按字节直接复制到输出流中。

仅当您的请求已完全在内存中时才有效。关于文件上传,这并不总是可行的,因为如果您有大文件,您可能会耗尽内存。

所以实现文件上传的最佳方式是只读取流中的下一个字节:这就是拆分解析的区别时间>。实际上,您需要一个真正的 解析器 来处理多部分表单数据。现在事情变得复杂了,这就是每个人都使用 commons-fileupload 的原因:如果您的“前瞻”只是一些字节,那么检测边界并不容易。

出于法律原因,我不得不实施无尘室实施。如果这不是您的情况,请查看 commons-fileupload 的来源。看看RFC

【讨论】:

  • 是的,你说得对,大文件不应该在单个字符串中处理,我会尝试读取 InputStream 字节。我不想使用任何现有的包或类,但我会阅读它们的源代码。目前我正在尝试将文件数据字符串转换为base64字符串。谢谢。
【解决方案2】:

由于您使用 Java 7,这很容易:使用 Files.copy()

此外,请勿将文件内容存储为Strings,这些内容仅对文本文件有效。使用经典的InputStream/OutputStreams 进行读/写。

【讨论】:

  • 但是我如何在不获取字符串的情况下拆分 http 标头,因为 html 表单以 multipart/form-data 格式发送标头,我需要解析标头以获取标头内容和文件内容
  • 你已经为自己设定了相当的任务开始(编写一个完整的 HTTP 服务器??在 Java 中已经有很多,包括 NanoHTTPD),但策略是读取 InputStream 直到你找到一个空行,那些是标题;内容,如果有的话,跟随。
  • 我已经下载了 NanoHTTPD 的源代码,我正在尝试学习他们的文件上传机制
【解决方案3】:

您可以使用如下所示的字节数组读取它

InputStream is = ...
ByteArrayOutputStream buffer = new ByteArrayOutputStream();

int nRead;
byte[] data = new byte[16384];

while ((nRead = is.read(data, 0, data.length)) != -1) {
  buffer.write(data, 0, nRead);
}

buffer.flush();

return buffer.toByteArray();

【讨论】:

  • 但我必须先解析标头,因为它还包含 http 标头的内容,我无法将输入数据直接发送到文件编写器
【解决方案4】:

我这样解决了我的问题,

    while (inputRequest.available()>0) {
            try {
                int t = inputRequest.read();
                ch = (char) t;
                //here i checked each byte data
            } catch (IOException e) {
            }
    }

问题是输入流具有 http 标头字段以及位于流中任何位置的文件内容,因此我首先将字节存储在临时字符串中,直到我在流中获得 '\r' 和 '\n' .通过这种方式,我得到了 multipart/form-data HTTP 标头的边界,然后我比较了临时字符串,直到找到边界和其他已知标头内容,然后我将输入流发送到文件输出流。但在某些情况下,标题可能在文件内容之后包含其他内容,因此它肯定会有一个结束边界,所以我不断跟踪我读取的每个字节,然后我将每个字节单独发送到文件输出流。这是示例 http 标头-

   Host: localhost
   User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0
   Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
   Accept-Language: en-US,en;q=0.5
   Accept-Encoding: gzip, deflate
   DNT: 1
   Referer: http://localhost/index.html
   Connection: keep-alive
   Content-Type: multipart/form-data; boundary=---------------------------274761981030199
   Content-Length: 1405

   -----------------------------274761981030199
   Content-Disposition: form-data; name="name1"

   pppppp
   -----------------------------274761981030199
   Content-Disposition: form-data; name="name2"

   rrrrrrrrr
   -----------------------------274761981030199
   Content-Disposition: form-data; name="name3"

   eeeeeeee
   -----------------------------274761981030199
   Content-Disposition: form-data; name="name4"

   2
   -----------------------------274761981030199
   Content-Disposition: form-data; name="name5"; filename="CgiPost.java"
   Content-Type: text/x-java-source

   import java.io.*;

   // This appears in Core Web Programming from
   // Prentice Hall Publishers, and may be freely used
   // or adapted. 1997 Marty Hall, hall@apl.jhu.edu.


   public class CgiPost extends CgiGet 
   {

   public static void main(String[] args) 
   {

   try 
   {

   DataInputStream in
    = new DataInputStream(System.in);

   String[] data = { in.readLine() };

   CgiPost app = new CgiPost("CgiPost", data, "POST");

   app.printFile();
       } catch(IOException ioe) {
         System.out.println
           ("IOException reading POST data: " + ioe);

   }
     }

     public CgiPost(String name, String[] args,
     String type) {
       super(name, args, type);
     }
   }

   -----------------------------274761981030199
   Content-Disposition: form-data; name="name6"

   pppppppppp
   -----------------------------274761981030199--

注意:在某些情况下,您的应用程序代码可能会到达 inputRequest.available() 但浏览器尚未发送请求,在这种情况下 inputRequest.available() 将始终返回 0 并且您的 while 循环将立即退出。为了避免这种情况,首先使用 inputRequest.read() 读取一个字节,然后执行代码,因为如果是 http 标头,您可以从其他字节中猜测第一个字节。

如果您使用一些 count int,请使用 long 而不是 int,因为在某些情况下流会在 int 变量达到其限制的情况下停止。

尝试将int t = inputRequest.read()返回的int值传递给fileoutputstream.write(t)。

inputRequest.available() 在您读取字节形式的输入流时不断减少,它返回流中可用的字节数。

通过这种方式,您可以上传大文件而不会损坏。

如果有人需要更多详细信息,请留下您的评论。

【讨论】:

    猜你喜欢
    • 2013-12-26
    • 2018-09-14
    • 1970-01-01
    • 1970-01-01
    • 2016-02-20
    • 2016-11-26
    • 1970-01-01
    • 1970-01-01
    • 2010-10-26
    相关资源
    最近更新 更多