文字列パラメータとバイナリ パラメータの両方を持つフォームを Java で投稿したい
name=sam&photo=<...バイナリデータ...>
残念ながら、利用可能なドキュメントでは、文字列またはバイナリ データのいずれかを個別にアップロードする方法しか説明されていません。どうすれば2つを組み合わせることができますか?
MIME タイプ `application/x-www-form-urlencoded' を送信する必要があります。フィールドはテキストでなければなりません。
フィールド名と値はエスケープ/エンコードされます。たとえば、スペース文字は+', reserved characters are escaped using URL encoding. Oh and that is not all... Non-alphanumeric characters are replaced by
%20 のように %HH' に置き換えられます。
つまり、文字の ASCII コードを表す 2 桁の 16 進数です。
Javaだけがこれを行う方法があれば.....ああ、それができるのを待ってください...
しかし、それは新しいクラスです。Java 1.0 から登場したばかりです。
URLEncoder を確認してください。これは、HTML フォーム エンコーディング用のユーティリティ クラスです。
このクラスには、String を application/x-www-form-urlencoded MIME 形式に変換するための静的メソッドが含まれています。HTML 仕様 (以下に引用) を参照することで、HTML フォームのエンコーディングについて詳しく知ることができます。
http://docs.oracle.com/javase/1.4.2/docs/api/java/net/URLEncoder.html
URLEncoder は次の処理を行います。 "、および "_" は同じままです。" スペース文字「 」はプラス記号「+」に変換されます。"
これがバイナリのキッカーです...
「他のすべての文字は安全ではなく、最初に何らかのエンコード方式を使用して 1 つ以上のバイトに変換されます。次に、各バイトは 3 文字の文字列 "%xy" で表されます。ここで、xy はバイトの 2 桁の 16 進数表現です。推奨されるエンコーディング スキームは UTF-8 です。ただし、互換性の理由から、エンコーディングが指定されていない場合は、プラットフォームのデフォルトのエンコーディングが使用されます。"
必ず UTF-8 を指定してください。
これがHTTP仕様です。
これを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
1 回のメソッド呼び出しで実行できるようになりました。:)
:)
あなたのためにそれを分解させてください。(ここで取得できます: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 に変換していることです。
BTW map は、マップを作成する単なるユーティリティ メソッドです (リストは一番下にあります)。
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 でエンコードされます。
次に、エンコードを完了するには、次の 2 つの方法があります。
/**
* 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;
}