【问题标题】:How can I get MIME type of an InputStream of a file that is being uploaded?如何获取正在上传的文件的 InputStream 的 MIME 类型?
【发布时间】:2011-06-03 21:01:54
【问题描述】:

简单问题:对于用户上传到我的 servlet 的文件,如何在不保存文件的情况下获取 InputStream 的 MIME 类型(或内容类型)?

【问题讨论】:

  • InputStream 是从哪里来的?如果它只是带有一些字节序列的通用输入流,它们是“无类型的”,如果不阅读内容本身并确定,您将不会知道。但是,如果您从(例如)HTTP 连接获取字节,则有边带标头可以告诉您您想要什么。
  • 它来自用户上传文件。
  • 你可以试试MimeUtils库。

标签: java servlets inputstream mime-types


【解决方案1】:

我非常支持“先自己动手,然后寻找库解决方案”。幸运的是,这个案例就是这样。

您必须知道文件的“幻数”,即它的签名。 我举个例子来检测InputStream是否代表PNG文件。

PNG 签名是通过在 HEX 中附加以下内容组成的:

1) 错误检查字节

2) ASCII 格式的字符串“PNG”:

     P - 0x50
     N - 0x4E
     G - 0x47

3)CR(回车)-0x0D

4) LF(换行)-0xA

5)SUB(替代)-0x1A

6) LF(换行)-0xA

所以,神奇的数字是

89   50 4E 47 0D 0A 1A 0A

137  80 78 71 13 10 26 10 (decimal)
-119 80 78 71 13 10 26 10 (in Java)

137 -> -119转换说明

N 位数可以用来表示2^N 不同的值。 对于 2^8=2560..255 范围的字节(8 位)。 Java 考虑要签名的字节原语,因此范围是-128..127。 因此,137 被认为是被烧毁的并代表-119 = 137 - 256

Koltin 中的示例

private fun InputStream.isPng(): Boolean {
    val magicNumbers = intArrayOf(-119, 80, 78, 71, 13, 10, 26, 10)
    val signatureBytes = ByteArray(magicNumbers.size)
    read(signatureBytes, 0, signatureBytes.size)
    return signatureBytes.map { it.toInt() }.toIntArray().contentEquals(magicNumbers)
}

当然,为了支持多种 MIME 类型,您必须以某种方式扩展此解决方案,如果您对结果不满意,请考虑使用一些库。

【讨论】:

    【解决方案2】:

    我认为这解决了问题:

        public String readIt(InputStream is) {
        if (is != null) {
                BufferedReader reader = new BufferedReader(new InputStreamReader(is, "utf-8"), 8);
    
                StringBuilder sb = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    sb.append(line).append("\n");
                }
                is.close();
                return sb.toString();
        }
        return "error: ";
    }        
    

    它返回什么?例如对于 png:“♦PNG\n\n♦♦♦.....”,对于 xml:

    很有用,你不能尝试 string.contains() 来检查它是什么

    【讨论】:

      【解决方案3】:

      如果使用 JAX-RS 休息服务,您可以从 MultipartBody 获取它。

      @POST
      @Path( "/<service_path>" )
      @Consumes( "multipart/form-data" )
      public Response importShapeFile( final MultipartBody body ) {
          String filename = null;
          String InputStream stream = null;
          for ( Attachment attachment : body.getAllAttachments() )
          {
              ContentDisposition disposition = attachment.getContentDisposition();
              if ( disposition != null && PARAM_NAME.equals( disposition.getParameter( "name" ) ) )
              {
                  filename = disposition.getParameter( "filename" );
                  stream = attachment.getDataHandler().getInputStream();
                  break;
              }
          }
      
          // Read extension from filename to get the file's type and
          // read the stream accordingly.
      }
      

      其中 PARAM_NAME 是一个字符串,表示保存文件流的参数的名称。

      【讨论】:

        【解决方案4】:

        我为 byte[] 编写了自己的内容类型检测器,因为上面的库不适合或者我无权访问它们。希望这可以帮助某人。

        // retrieve file as byte[]
        byte[] b = odHit.retrieve( "" );
        
        // copy top 32 bytes and pass to the guessMimeType(byte[]) funciton
        byte[] topOfStream = new byte[32];
        System.arraycopy(b, 0, topOfStream, 0, topOfStream.length);
        String mimeGuess = guessMimeType(topOfStream);
        

        ...

        private static String guessMimeType(byte[] topOfStream) {
        
            String mimeType = null;
            Properties magicmimes = new Properties();
            FileInputStream in = null;
        
            // Read in the magicmimes.properties file (e.g. of file listed below)
            try {
                in = new FileInputStream( "magicmimes.properties" );
                magicmimes.load(in);
                in.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        
            // loop over each file signature, if a match is found, return mime type
            for ( Enumeration keys = magicmimes.keys(); keys.hasMoreElements(); ) {
                String key = (String) keys.nextElement();
                byte[] sample = new byte[key.length()];
                System.arraycopy(topOfStream, 0, sample, 0, sample.length);
                if( key.equals( new String(sample) )){
                    mimeType = magicmimes.getProperty(key);
                    System.out.println("Mime Found! "+ mimeType);
                    break;
                } else {
                    System.out.println("trying "+key+" == "+new String(sample));
                }
            }
        
            return mimeType;
        }
        

        magicmimes.properties 文件示例(不确定这些签名是否正确,但它们适用于我的用途)

        # SignatureKey                  content/type
        \u0000\u201E\u00f1\u00d9        text/plain
        \u0025\u0050\u0044\u0046        application/pdf
        %PDF                            application/pdf
        \u0042\u004d                    image/bmp
        GIF8                            image/gif
        \u0047\u0049\u0046\u0038        image/gif
        \u0049\u0049\u004D\u004D        image/tiff
        \u0089\u0050\u004e\u0047        image/png
        \u00ff\u00d8\u00ff\u00e0        image/jpg
        

        【讨论】:

        • 请注意,这不适用于 PNG,例如,它的第一个字节是 137。考虑到字节在 Java 中签名(不能保存大于 128 的值),它被转换为 - 119.我所做的是我使用 InputStream#read() 方法将 InputStream 读取到 int[4] 数组,该方法将字节作为整数返回,因此它们不会被转换。无论如何,谢谢你的回答!
        【解决方案5】:

        只要您不在其他任何地方使用 slf4j 日志记录,您就可以将 tika-app-1.x.jar 添加到您的类路径中,因为它会导致冲突。如果您使用 tika 检测输入流,则必须标记为支持。否则,调用 tika 将删除您的输入流。但是,如果您使用 apache IO 库来解决这个问题,只需将 InputStream 转换为内存中的文件。

        import org.apache.tika.*;
        
        Tike tika = new Tika();
        InputStream in = null;
        FileOutputStream out = null;
        try{
           out = new FileOutputStream(c:/tmp.tmp);
           IOUtils.copy(in, out);
           String mimeType = tika.detect(out);
        }catch(Exception e){
           System.err.println(e);
        } finally {
           if(null != in) 
               in.close();
           if(null != out)
               out.close();
         }
        

        【讨论】:

          【解决方案6】:

          您可以查看Content-Type header field 并查看使用的extension of the filename。对于其他一切,您必须运行更复杂的例程,例如通过Tikaetc 进行检查。

          【讨论】:

            【解决方案7】:

            这取决于您从哪里获取输入流。如果您从 servlet 获取它,则可以通过作为 doPost 参数的 HttpServerRequest 对象访问它。如果您使用的是诸如 Jersey 之类的某种 REST API,则可以使用 @Context 注入请求。如果您通过套接字上传文件,则您有责任将 MIME 类型指定为协议的一部分,因为您不会继承 http 标头。

            【讨论】:

            【解决方案8】:

            根据Real Gagnon's excellent site,更好的解决方案是使用Apache Tika

            【讨论】:

            • 我查看了 Tika,但有 20 个依赖项...占用 18MB。我会再考虑...
            • 啊哈哈哈,18MB!我的应用大小为 4MB!
            猜你喜欢
            • 2011-05-08
            • 2011-07-03
            • 2014-01-30
            • 2015-09-13
            • 2018-12-01
            • 2011-04-20
            • 2011-06-09
            • 2017-03-18
            • 2014-09-28
            相关资源
            最近更新 更多