web-dev-qa-db-ja.com

URIBuilder.path(...)に「%AD」などのパラメータを強制的にエンコードさせる方法は?このメソッドは、常にパーセンテージでパラメーターを正しくエンコードするとは限りません

_"%AD"_のようなパラメータをURIBuilder.path(...)に強制的にエンコードさせる方法は?

pathのメソッドreplacePathsegment、およびURIBuilderは、必ずしもパーセントでパラメーターを正しくエンコードするとは限りません。

パラメータに「%」という文字の後に2つの文字が続き、URLエンコードされた文字を形成する場合、「%」は「%25」としてエンコードされません。

例えば

_URI uri = UriBuilder.fromUri("https://dummy.com").queryParam("param", "%AD");
String test = uri.build().toString();
_

「テスト」は「 https://dummy.com?param=%AD "
しかし、それは " https://dummy.com?param=%25AD "( "%25"が "%25"としてエンコードされている)である必要があります

メソッドUriBuilderImpl.queryParam(...)は、「%」に続く2つの文字が16進数である場合にこのように動作します。つまり、メソッド「com.Sun.jersey.api.uri.UriComponent.isHexCharacter(char)」は、「%」に続く文字に対してtrueを返します。

UriBuilderImplの動作は正しいと思います。すでにエンコードされているパラメーターをエンコードしようとしないためです。しかし、私のシナリオでは、既にエンコードされたパラメーターを使用してURLを作成しようとはしません。

どうすればよいですか?

私のWebアプリケーションはJerseyを使用しており、多くの場所でUriBuilderクラスを使用してURIを構築するか、getBaseUriBuilderオブジェクトのUriInfoメソッドを呼び出します。

メソッドqueryParamreplaceQueryParamまたはsegmentを呼び出すたびに、「%」を「%25」に置き換えることができます。しかし、私はそれほど煩わしくない解決策を探しています。

UriBuilderの独自の実装を返すようにジャージーを作成するにはどうすればよいですか?

これらのメソッドをオーバーライドし、super.queryParam(...)などを呼び出す前にこの置換を実行するUriBuilderImplを拡張するクラスを作成することを考えました。

UriBuilder.fromURL(...)、UriInfo.getBaseUriBuilder(...)などを呼び出すときに、JerseyがUriBuilderImplではなく自分のUriBuilderを返すようにする方法はありますか?

メソッドRuntimeDelegateを見て、RuntimeDelegateImplを拡張することを考えました。私の実装では、UriBuilderではなく、独自のUriBuilderImplを返すメソッドcreateUriBuilder(...)をオーバーライドします。次に、ファイル_META-INF/services/javax.ws.rs.ext.RuntimeDelegate_を追加し、その中に私のRuntimeDelegateImplの完全なクラス名を追加します。

問題は、jersey-bundle.jarに_META-INF/services/javax.ws.rs.ext.RuntimeDelegate_を指す_com.Sun.jersey.server.impl.provider.RuntimeDelegateImpl_がすでに含まれているため、コンテナーが_javax.ws.rs.ext.RuntimeDelegate_ではなくそのファイルをロードすることです。したがって、RuntimeDelegateimplementationは読み込まれません。

RuntimeDelegateの独自の実装を提供することは可能ですか?

別のアプローチを取るべきですか?

18
Montecarlo

UriBuilder

これは、ジャージーからの UriComponent またはJavaからの URLEncoder の助けを借りて可能です:

UriBuilder.fromUri("https://dummy.com")
        .queryParam("param",
                UriComponent.encode("%AD",
                    UriComponent.Type.QUERY_PARAM_SPACE_ENCODED))
        .build();

その結果:

https://dummy.com/?param=%25AD

または:

UriBuilder.fromUri("https://dummy.com")
        .queryParam("param", URLEncoder.encode("%AD", "UTF-8"))
        .build()

結果は:

https://dummy.com/?param=%25AD

より複雑な例(クエリパラメータでのJSONのエンコードなど)では、このアプローチも可能です。 {"Entity":{"foo":"foo","bar":"bar"}}のようなJSONがあるとします。 UriComponentを使用してエンコードすると、クエリパラメータの結果は次のようになります。

https://dummy.com/?param=%7B%22Entity%22:%7B%22foo%22:%22foo%22,%22bar%22:%22bar%22%7D%7D

このようなJSONは、@QueryParamを介してリソースフィールド/メソッドパラメータに注入することもできます(クエリパラメータの JSONまたはカスタムの注入方法Javaタイプを参照) JAX-RSパラメータアノテーションを介して )。


どのジャージーバージョンを使用していますか?タグではJersey 2について言及していますが、RuntimeDelegateセクションではJersey 1のものを使用しています。

36
Michal Gajdos

次の例が役立つかどうかを確認してください。以下のリンク先のスレッドでは、使用可能な関数とそれらのさまざまな出力に関する広範な議論があります。

以下:

  1. UriBuilder.fromUri("http://localhost:8080").queryParam("name", "{value}").build("%20");
  2. UriBuilder.fromUri("http://localhost:8080").queryParam("name", "{value}").buildFromEncoded("%20");
  3. UriBuilder.fromUri("http://localhost:8080").replaceQuery("name={value}).build("%20");
  4. UriBuilder.fromUri("http://localhost:8080").replaceQuery("name={value}).buildFromEncoded("%20");

出力されます:

  1. http://localhost:8080?name=%2520
  2. http://localhost:8080?name=%20
  3. http://localhost:8080?name=%2520
  4. http://localhost:8080?name=%20

http://comments.gmane.org/gmane.comp.Java.jsr311.user/71 経由

また、 クラスUriBuilderのドキュメントに基づいて 、次の例は、目的を取得する方法を示しています。

URIテンプレートは、URIのほとんどのコンポーネントで許可されていますが、その値は特定のコンポーネントに制限されています。例えば。

UriBuilder.fromPath("{arg1}").build("foo#bar");

結果のURIが「foo%23bar」になるように、「#」のエンコードが行われます。 URI "foo#bar"を作成するには

UriBuilder.fromPath("{arg1}").fragment("{arg2}").build("foo", "bar")

代わりに。 URIテンプレート名と区切り文字は決してエンコードされませんが、それらの値はURIが構築されるときにエンコードされます。 URIを構築する場合、テンプレートパラメータの正規表現は無視されます。つまり、検証は実行されません。

6
JSuar

起動時にジャージーのデフォルトの動作を手動で上書きすることが可能です。 RuntimeDelegate.setInstance(yourRuntimeDelegateImpl)を呼び出す静的ヘルパーを使用します。

したがって、パーセントがすでにエンコードされたシーケンスの一部であるように見えても、パーセントをエンコードするUriBuilderが必要な場合は、次のようになります。

[...]
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.ext.RuntimeDelegate;

import com.Sun.jersey.api.uri.UriBuilderImpl;
import com.Sun.ws.rs.ext.RuntimeDelegateImpl;
// or for jersey2:
// import org.glassfish.jersey.uri.internal.JerseyUriBuilder;
// import org.glassfish.jersey.internal.RuntimeDelegateImpl;

public class SomeBaseClass {

    [...]

    // this is the lengthier custom implementation of UriBuilder
    // replace this with your own according to your needs
    public static class AlwaysPercentEncodingUriBuilder extends UriBuilderImpl {

        @Override
        public UriBuilder queryParam(String name, Object... values) {
            Object[] encValues = new Object[values.length];
            for (int i=0; i<values.length; i++) {
                String value = values[i].toString(); // TODO: better null check here, like in base class
                encValues[i] = percentEncode(value);
            }
            return super.queryParam(name, encValues);
        }

        private String percentEncode(String value) {
            StringBuilder sb = null;
            for (int i=0;  i < value.length(); i++) {
                char c = value.charAt(i);
                // if this condition is is true, the base class will not encode the percent
                if (c == '%' 
                    && i + 2 < value.length()
                    && isHexCharacter(value.charAt(i + 1)) 
                    && isHexCharacter(value.charAt(i + 2))) {
                    if (sb == null) {
                        sb = new StringBuilder(value.substring(0, i));
                    }
                    sb.append("%25");
                } else {
                    if (sb != null) sb.append(c);
                }
            }
            return (sb != null) ? sb.toString() : value;
        }

        // in jersey2 one can call public UriComponent.isHexCharacter
        // but in jersey1 we need to provide this on our own
        private static boolean isHexCharacter(char c) {
            return ('0' <= c && c <= '9')
                || ('A' <=c && c <= 'F')
                || ('a' <=c && c <= 'f');
        }
    }

    // here starts the code to hook up the implementation
    public static class AlwaysPercentEncodingRuntimeDelegateImpl extends RuntimeDelegateImpl {
        @Override
        public UriBuilder createUriBuilder() {
            return new AlwaysPercentEncodingUriBuilder();
        }
    }

    static {
        RuntimeDelegate myDelegate = new AlwaysPercentEncodingRuntimeDelegateImpl();
        RuntimeDelegate.setInstance(myDelegate);
    }

}

警告:もちろん、そのように構成することはあまりできません。他のユーザーが再利用する可能性があるライブラリコードでこれを行うと、多少の不快感が生じる可能性があります。

たとえば、Confluenceプラグインでレストクライアントを作成するときにOPと同じ問題があり、プラグインがOSGiを介してロードされ、単純にRuntimeDelegateImplJava.lang.ClassNotFoundException: com.Sun.ws.rs.ext.RuntimeDelegateImpl代わりに実行時)。

(そして、念のために言うと、これはjersey2では非常によく似ています。特に、カスタムのRuntimeDelegateImplをフックするコードは同じです。)