【问题标题】:Http Post both string and binary parameters in JavaHttp Post Java 中的字符串和二进制参数
【发布时间】:2013-11-09 14:10:37
【问题描述】:

我希望在 java 中发布一个同时具有字符串和二进制参数的表单,例如

name=sam&photo=<...>

不幸的是,可用的文档仅涵盖分别上传字符串或二进制数据。如何将两者结合起来?

【问题讨论】:

  • 使用 Apache Commons HTTPRequest - 手动操作真的很麻烦。您将发送一个多部分请求,每个部分都可以有自己的数据。
  • apache 库太乱了,建议的解决方案似乎不起作用。
  • 我的理解是您不想发送多部分请求。
  • java.net.URL 也不好玩。我编写了一个名为 HTTP 的实用程序,用于发布不包含 Apache Commons HTTPRequest(不是我的东西)的 REST 调用。 HTTP 使用 java.net.URL。我为字节数组添加了一个新的表单海报和我自己的 URLEncoder。我把它包括在下面。您可以剪切和粘贴它或使用恩惠(我不在乎,不是在寻找恩惠转换者,但我在 stackoverflow 上寻找人们所做的事情,这对练习来说太有利了)。我喜欢编程。 :)

标签: java http


【解决方案1】:

您需要发送 mime-type `application/x-www-form-urlencoded'。字段必须是文本。

字段名称和值被转义/编码,例如,空格字符替换为+', reserved characters are escaped using URL encoding. Oh and that is not all... Non-alphanumeric characters are replaced by%HH',如 %20 中的空格

所以两个十六进制数字代表字符的 ASCII 码。

如果只有 Java 可以为您做到这一点......哦等等它可以......

但是是一个新的类。它是从 Java 1.0 开始出现的。

查看 URLEncoder,它是一个用于 HTML 表单编码的实用程序类。

此类包含用于将字符串转换为 application/x-www-form-urlencoded MIME 格式的静态方法。您可以通过查阅 HTML 规范(下文引用)了解有关 HTML 表单编码的更多信息。

http://docs.oracle.com/javase/1.4.2/docs/api/java/net/URLEncoder.html

URLEncoder 处理以下内容:“字母数字字符“a”到“z”、“A”到“Z”和“0”到“9”保持不变。特殊字符“.”、“-” 、“*”和“_”保持不变。”空格字符“”转换为加号“+”。 "

这里是二进制的踢脚线...

"所有其他字符都是不安全的,首先使用某种编码方案将其转换为一个或多个字节。然后每个字节由 3 个字符的字符串 "%xy" 表示,其中 xy 是两位数的十六进制表示字节。推荐使用的编码方案是 UTF-8。但是,出于兼容性原因,如果未指定编码,则使用平台的默认编码。

始终指定 UTF-8。

这是 HTTP 规范。

http://www.w3.org/MarkUp/html-spec/html-spec_8.html

【讨论】:

    【解决方案2】:

    让我为你分解一下。 (顺便说一句,你可以在这里获取它:http://richardhightower.github.io/site/Boon/Welcome.html

    我把这个添加到了恩惠:

    public static String postForm(final String url, final Map<String, ?> headers,
                                                final Map<String, Object> formData
    )
    

    这里的关键是编码二进制数据:

        String response = HTTP.postForm ( "http://localhost:9220/test",
                Collections.EMPTY_MAP,
                map("hI", (Object)"hi-mom", "image", new byte[] {1,2,3})
        );
    
        boolean ok = true;
        ok |= response.startsWith ("hI=hi-mom&image=%01%02%03\n") ||
                die("encoding did not work");
    

    以上是一个测试,表明它在我理解规范的情况下有效。

    关键是把“image”,new byte[] {1,2,3}变成image\u0000=%01%02%03。

    顺便说一句,地图只是一种创建地图的实用方法(在底部列出)。

    http 服务器只是一个回声。

        return Exceptions.tryIt(String.class, new Exceptions.TrialWithReturn<String>() {
            @Override
            public String tryIt() throws Exception {
                URLConnection connection;
                connection = doPostFormData(url, headers, formData);
                return extractResponseString(connection);
            }
        });
    

    神奇的发生在 doPostFormData:

    private static URLConnection doPostFormData(String url, Map<String, ?> headers,
                                        Map<String, Object> formData
    ) throws IOException {
        HttpURLConnection connection;/* Handle output. */
    
    
        connection = (HttpURLConnection) new URL(url).openConnection();
        connection.setConnectTimeout(DEFAULT_TIMEOUT_SECONDS * 1000);
    
        connection.setDoOutput(true);
    
        connection.addRequestProperty ( "Content-Type", "application/x-www-form-urlencoded" );
    
        ByteBuf buf = ByteBuf.create ( 244 );
    
    
    
        final Set<String> keys = formData.keySet ();
    
        int index = 0;
        for ( String key : keys )  {
    
            Object value = formData.get ( key );
    
            if (index > 0) {
                buf.addByte ( '&' );
            }
    
    
            buf.addUrlEncoded (  key  );
            buf.addByte ( '=' );
    
            if ( ! ( value instanceof byte[] ) ) {
                buf.addUrlEncoded ( value.toString () );
            } else {
                buf.addUrlEncodedByteArray((byte[]) value);
            }
            index++;
        }
    
    
        manageContentTypeHeaders ( "application/x-www-form-urlencoded",
                StandardCharsets.UTF_8.name (), connection );
    
        manageHeaders(headers, connection);
    
    
        int len = buf.len ();
        IO.write(connection.getOutputStream(),
                new String(buf.readForRecycle (), 0, len, StandardCharsets.UTF_8), IO.DEFAULT_CHARSET);
        return connection;
    }
    

    注意对 addUrlEncodedByteArray 的调用传递了一个字节数组。 Java 可以很好地处理字符串的 URL 编码。我找不到简单的方法 编码一个字节数组,所以我只是写了它。

    public void addUrlEncodedByteArray ( byte[] value ) {
    
    
    
        final byte[] encoded = new byte [2];
    
        for (int index = 0; index < value.length; index++) {
            int i = value[index];
    
            if ( i >= 'a' && i <= 'z' ) {
                this.addByte ( i );
            } else if ( i >= 'A' && i <= 'Z' ) {
                this.addByte ( i );
            } else if ( i >= '0' && i <= '9' ) {
                this.addByte ( i );
            } else if ( i == '_' || i == '-' || i == '.' || i == '*') {
                this.addByte ( i );
            } else if ( i == ' ') {
                this.addByte ( '+' );
            } else {
                encodeByteIntoTwoAsciiCharBytes(i, encoded);
                this.addByte ( '%' );
                this.addByte ( encoded [0] );
                this.addByte ( encoded [1] );
            }
    
        }
    }
    

    这不是最漂亮的。但是单元测试有效。 我相信你明白要点。它遵循规范并进行相应的转换。

    所有不在特定范围内的数据都使用 %hexdigit hexdigit 进行编码。

    那么你只有这两种方法来完成编码:

    /**
     * Turns a single nibble into an ascii HEX digit.
     *
     * @param nibble the nibble to encode.
     *
     * @return the encoded nibble (1/2 byte).
     */
    protected static int encodeNibbleToHexAsciiCharByte( final int nibble ) {
    
        switch ( nibble ) {
            case 0x00:
            case 0x01:
            case 0x02:
            case 0x03:
            case 0x04:
            case 0x05:
            case 0x06:
            case 0x07:
            case 0x08:
            case 0x09:
                return nibble + 0x30; // 0x30('0') - 0x39('9')
            case 0x0A:
            case 0x0B:
            case 0x0C:
            case 0x0D:
            case 0x0E:
            case 0x0F:
                return nibble + 0x57; // 0x41('a') - 0x46('f')
            default:
                die("illegal nibble: " + nibble);
                return -1;
        }
    }
    
    
    /**
     * Turn a single bytes into two hex character representation.
     *
     * @param decoded the byte to encode.
     * @param encoded the array to which each encoded nibbles are now ascii hex representations.
     */
    public static void encodeByteIntoTwoAsciiCharBytes(final int decoded, final byte[] encoded) {
    
        Objects.requireNonNull ( encoded );
    
        boolean ok = true;
    
    
        ok |= encoded.length == 2 || die("encoded array must be 2");
    
    
        encoded[0] = (byte) encodeNibbleToHexAsciiCharByte((decoded >> 4) & 0x0F);
        encoded[1] = (byte) encodeNibbleToHexAsciiCharByte(decoded & 0x0F);
    }
    

    这是重要的部分。其余的只是处理 HTTP 请求/标头 gak。

    这里是 manageContentTypeHeaders

        manageContentTypeHeaders ( "application/x-www-form-urlencoded",
                StandardCharsets.UTF_8.name (), connection );
    
    ...
    
    private static void manageContentTypeHeaders(String contentType, String charset, URLConnection connection) {
        connection.setRequestProperty("Accept-Charset", charset == null ? StandardCharsets.UTF_8.displayName() : charset);
        if (contentType!=null && !contentType.isEmpty()) {
            connection.setRequestProperty("Content-Type", contentType);
        }
    }
    

    这里是管理标题

        manageHeaders(headers, connection);
    
    ...
    
    private static void manageHeaders(Map<String, ?> headers, URLConnection connection) {
        if (headers != null) {
            for (Map.Entry<String, ?> entry : headers.entrySet()) {
                connection.setRequestProperty(entry.getKey(), entry.getValue().toString());
            }
        }
    }
    

    然后我们用 UTF_8 编码要发送的流:

        int len = buf.len ();
        IO.write(connection.getOutputStream(),
                new String(buf.readForRecycle (), 0, len, StandardCharsets.UTF_8), IO.DEFAULT_CHARSET);
    

    IO 写入就是这样做的: IO.write...

    public static void write ( OutputStream out, String content, Charset charset ) {
    
        try ( OutputStream o = out ) {
            o.write ( content.getBytes ( charset ) );
        } catch ( Exception ex ) {
            Exceptions.handle ( ex );
        }
    
    }
    

    ByteBuf 与 ByteBuffer 类似,但更易于使用且速度非常快。我有基准。 :)

    我错过了什么?

    让我知道它是否适合你。

    --瑞克

    map 函数只是实用方法,因此我可以简洁地表示地图,因为我发现我经常使用它们。它只会到 9 或 10。除此之外,我还有一种方法可以传递条目列表。

    public static <K, V> Map<K, V> map(K k0, V v0) {
        Map<K, V> map = new LinkedHashMap<>(10);
        map.put(k0, v0);
        return map;
    }
    
    public static <K, V> Map<K, V> map(K k0, V v0, K k1, V v1) {
        Map<K, V> map = new LinkedHashMap<>(10);
        map.put(k0, v0);
        map.put(k1, v1);
        return map;
    }
    
    
    public static <K, V> Map<K, V> map(K k0, V v0, K k1, V v1, K k2, V v2) {
        Map<K, V> map = new LinkedHashMap<>(10);
        map.put(k0, v0);
        map.put(k1, v1);
        map.put(k2, v2);
        return map;
    }
    
    public static <K, V> Map<K, V> map(K k0, V v0, K k1, V v1, K k2, V v2, K k3,
                                       V v3) {
        Map<K, V> map = new LinkedHashMap<>(10);
        map.put(k0, v0);
        map.put(k1, v1);
        map.put(k2, v2);
        map.put(k3, v3);
        return map;
    }
    
    public static <K, V> Map<K, V> map(K k0, V v0, K k1, V v1, K k2, V v2, K k3,
                                       V v3, K k4, V v4) {
        Map<K, V> map = new LinkedHashMap<>(10);
        map.put(k0, v0);
        map.put(k1, v1);
        map.put(k2, v2);
        map.put(k3, v3);
        map.put(k4, v4);
        return map;
    }
    
    public static <K, V> Map<K, V> map(K k0, V v0, K k1, V v1, K k2, V v2, K k3,
                                       V v3, K k4, V v4, K k5, V v5) {
        Map<K, V> map = new LinkedHashMap<>(10);
        map.put(k0, v0);
        map.put(k1, v1);
        map.put(k2, v2);
        map.put(k3, v3);
        map.put(k4, v4);
        map.put(k5, v5);
        return map;
    }
    
    public static <K, V> Map<K, V> map(K k0, V v0, K k1, V v1, K k2, V v2, K k3,
                                       V v3, K k4, V v4, K k5, V v5, K k6, V v6) {
        Map<K, V> map = new LinkedHashMap<>(10);
        map.put(k0, v0);
        map.put(k1, v1);
        map.put(k2, v2);
        map.put(k3, v3);
        map.put(k4, v4);
        map.put(k5, v5);
        map.put(k6, v6);
        return map;
    }
    
    public static <K, V> Map<K, V> map(K k0, V v0, K k1, V v1, K k2, V v2, K k3,
                                       V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7) {
        Map<K, V> map = new LinkedHashMap<>(10);
        map.put(k0, v0);
        map.put(k1, v1);
        map.put(k2, v2);
        map.put(k3, v3);
        map.put(k4, v4);
        map.put(k5, v5);
        map.put(k6, v6);
        map.put(k7, v7);
        return map;
    }
    
    public static <K, V> Map<K, V> map(K k0, V v0, K k1, V v1, K k2, V v2, K k3,
                                       V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8) {
        Map<K, V> map = new LinkedHashMap<>(10);
        map.put(k0, v0);
        map.put(k1, v1);
        map.put(k2, v2);
        map.put(k3, v3);
        map.put(k4, v4);
        map.put(k5, v5);
        map.put(k6, v6);
        map.put(k7, v7);
        map.put(k8, v8);
        return map;
    }
    
    public static <K, V> Map<K, V> map(K k0, V v0, K k1, V v1, K k2, V v2, K k3,
                                       V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8,
                                       K k9, V v9) {
        Map<K, V> map = new LinkedHashMap<>(10);
        map.put(k0, v0);
        map.put(k1, v1);
        map.put(k2, v2);
        map.put(k3, v3);
        map.put(k4, v4);
        map.put(k5, v5);
        map.put(k6, v6);
        map.put(k7, v7);
        map.put(k8, v8);
        map.put(k9, v9);
        return map;
    }
    

    【讨论】:

      【解决方案3】:

      我刚刚为你添加了这个到 Boon。

      http://richardhightower.github.io/site/Boon/Welcome.html

          String response = HTTP.postForm ( "http://localhost:9220/test",
                  Collections.EMPTY_MAP,
                  map("hI", (Object)"hi-mom", "image", new byte[] {1,2,3})
          );
      

      https://github.com/RichardHightower/boon

      现在您可以在一个方法调用中完成。 :)

      :)

      【讨论】:

        猜你喜欢
        • 2014-04-28
        • 1970-01-01
        • 2013-02-02
        • 2020-12-20
        • 2014-03-24
        • 1970-01-01
        • 1970-01-01
        • 2017-09-26
        • 1970-01-01
        相关资源
        最近更新 更多