Java-URL-Codierung von Abfragezeichenfolgenparametern

710

Angenommen, ich habe eine URL

http://example.com/query?q=

und ich habe eine vom Benutzer eingegebene Abfrage wie:

zufälliges Wort £ 500 Bank $

Ich möchte, dass das Ergebnis eine ordnungsgemäß codierte URL ist:

http://example.com/query?q=random%20word%20%A3500%20bank%20%24

Was ist der beste Weg, um dies zu erreichen? Ich habe versucht URLEncoder, URI / URL-Objekte zu erstellen, aber keines davon ist ganz richtig.

user1277546
quelle
25
Was meinst du mit "keiner von ihnen kommt ganz richtig raus"?
Mark Elliot
2
Ich habe URI.create verwendet und Leerzeichen durch + in Querystring ersetzt. Am Client-Standort wurde + zurück in Leerzeichen konvertiert, als ich die Abfragezeichenfolgen ausgewählt habe. Das hat bei mir funktioniert.
ND27
Warum erwarten Sie, dass $ prozentual codiert wird?
jschnasse

Antworten:

1151

URLEncoderist der Weg zu gehen. Sie müssen nur berücksichtigen, dass Sie nur den Namen und / oder den Wert der einzelnen Abfragezeichenfolgenparameter codieren , nicht die gesamte URL, ganz sicher nicht das Trennzeichen für die Abfragezeichenfolgenparameter &oder das Trennzeichen für den Parameternamen-Wert =.

String q = "random word £500 bank $";
String url = "https://example.com?q=" + URLEncoder.encode(q, StandardCharsets.UTF_8);

Beachten Sie, dass Leerzeichen in Abfrageparametern durch +nicht dargestellt werden %20, was rechtmäßig gültig ist. Das %20wird normalerweise verwendet, um Leerzeichen im URI selbst (dem Teil vor dem Trennzeichen für die URI-Abfragezeichenfolge ?) und nicht in der Abfragezeichenfolge (dem Teil danach ?) darzustellen .

Beachten Sie auch, dass es drei encode()Methoden gibt. Eines ohne Charsetals zweites Argument und eines mit Stringals zweitem Argument, das eine geprüfte Ausnahme auslöst. Der ohne CharsetArgument ist veraltet. Verwenden Sie es niemals und geben Sie immer das CharsetArgument an. Das Javadoc empfiehlt sogar ausdrücklich die Verwendung der UTF-8-Codierung, wie von RFC3986 und W3C vorgeschrieben .

Alle anderen Zeichen sind unsicher und werden zuerst mithilfe eines Codierungsschemas in ein oder mehrere Bytes konvertiert. Dann wird jedes Byte durch die 3-stellige Zeichenfolge "% xy" dargestellt, wobei xy die zweistellige hexadezimale Darstellung des Bytes ist. Das empfohlene Codierungsschema ist UTF-8 . Wenn jedoch aus Kompatibilitätsgründen keine Codierung angegeben ist, wird die Standardcodierung der Plattform verwendet.

Siehe auch:

BalusC
quelle
Die URL kann zwei Arten von Parametern enthalten. Abfragezeichenfolge (gefolgt von?) Und Pfadparameter (normalerweise Teil der URL selbst). Was ist also mit den Pfadparametern? URLEncoder erzeugt + für Speicherplatz auch für Pfadparameter. Tatsächlich behandelt es nur eine Abfragezeichenfolge. Außerdem ist dieses Verhalten nicht mit den Servern des Knotens js synchron. Für mich ist diese Klasse eine Verschwendung und kann nur für sehr spezielle Szenarien verwendet werden.
Sharadendu Sinha
2
@sharadendusinha: Wie dokumentiert und beantwortet, entsprechen URLEncoderfür URL-codierte Abfrageparameter application/x-www-form-urlencodedRegeln. Pfadparameter passen nicht in diese Kategorie. Sie benötigen stattdessen einen URI-Encoder.
BalusC
Wie ich vorhergesagt hatte, würden Benutzer verwirrt sein, da das Problem offensichtlich darin besteht, dass die Benutzer mehr als nur den Parameterwert codieren müssen. Es ist ein sehr seltener Fall, dass Sie nur einen Parameterwert codieren müssen. Deshalb habe ich meine "verwirrte" Wiki-Antwort gegeben, um Leuten wie @sharadendusinha zu helfen.
Adam Gent
1
@WijaySharma: Weil URL-spezifische Zeichen ebenfalls codiert würden. Sie sollten dies nur tun, wenn Sie die gesamte URL als Abfrageparameter einer anderen URL übergeben möchten.
BalusC
1
"+, nicht% 20" musste ich hören. Ich danke dir sehr.
Wetjosh
173

Ich würde nicht verwenden URLEncoder. Abgesehen davon, dass es falsch benannt ist ( URLEncoderhat nichts mit URLs zu tun), ineffizient ist (es verwendet ein StringBufferanstelle von Builder und macht ein paar andere Dinge, die langsam sind). Es ist auch viel zu einfach, es zu vermasseln.

Stattdessen würde ich URIBuilderoder Spring's org.springframework.web.util.UriUtils.encodeQueryoder Commons Apache verwendenHttpClient . Der Grund dafür ist, dass Sie den Namen der Abfrageparameter (dh die Antwort von BalusC q) anders als den Parameterwert maskieren müssen .

Der einzige Nachteil des oben Gesagten (den ich schmerzhaft herausgefunden habe) ist, dass URLs keine echte Teilmenge von URIs sind .

Beispielcode:

import org.apache.http.client.utils.URIBuilder;

URIBuilder ub = new URIBuilder("http://example.com/query");
ub.addParameter("q", "random word £500 bank \$");
String url = ub.toString();

// Result: http://example.com/query?q=random+word+%C2%A3500+bank+%24

Da ich nur auf andere Antworten verweise, habe ich dies als Community-Wiki markiert. Fühlen Sie sich frei zu bearbeiten.

Adam Gent
quelle
2
Warum hat es nichts mit URLs zu tun?
Luis
15
@Luis: URLEncoderist, wie sein Javadoc sagt, beabsichtigt, Abfragezeichenfolgenparameter zu codieren, application/x-www-form-urlencodeddie der HTML-Spezifikation entsprechen: w3.org/TR/html4/interact/… . Einige Benutzer verwechseln / missbrauchen es tatsächlich für die Codierung ganzer URIs, wie es der aktuelle Antwortende anscheinend getan hat.
BalusC
8
@LuisSep kurz URLEncoder dient zur Codierung für die Formularübermittlung. Es ist nicht zum Entkommen. Es ist nicht genau das gleiche, was Sie verwenden würden, um URLs zu erstellen, die in Ihre Webseite eingefügt werden sollen, aber es ist ähnlich genug, dass die Leute es missbrauchen. Sie sollten URLEncoder nur verwenden, wenn Sie einen HTTP-Client schreiben (und selbst dann gibt es weitaus bessere Optionen für die Codierung).
Adam Gent
1
@BalusC " Einige Benutzer verwechseln / missbrauchen es tatsächlich für die Codierung ganzer URIs, wie es der aktuelle Antwortende anscheinend getan hat. " Du hast falsch angenommen. Ich habe nie gesagt, dass ich es vermasselt habe. Ich habe gerade andere gesehen, die es getan haben, deren Fehler ich beheben muss. Der Teil, den ich vermasselt habe, ist, dass die Java-URL-Klasse nicht entkoppelte Klammern akzeptiert, aber nicht die URI-Klasse. Es gibt viele Möglichkeiten, das Erstellen von URLs zu vermasseln, und nicht jeder ist so brillant wie Sie. Ich würde sagen, dass die meisten Benutzer, die SO nach URLEncoding suchen, wahrscheinlich URIs sind, die " tatsächlich verwirren / missbrauchen ".
Adam Gent
1
Frage war nicht darüber, aber Ihre Antwort impliziert das.
BalusC
99

Sie müssen zuerst einen URI erstellen wie:

String urlStr = "http://www.example.com/CEREC® Materials & Accessories/IPS Empress® CAD.pdf"
URL url= new URL(urlStr);
URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());

Konvertieren Sie dann diesen Uri in einen ASCII-String:

urlStr=uri.toASCIIString();

Jetzt ist Ihre URL-Zeichenfolge vollständig codiert. Zuerst haben wir eine einfache URL-Codierung durchgeführt und sie dann in eine ASCII-Zeichenfolge konvertiert, um sicherzustellen, dass keine Zeichen außerhalb von US-ASCII in der Zeichenfolge verbleiben. Genau so machen es Browser.

M Abdul Sami
quelle
7
Vielen Dank! Es ist dumm, dass Ihre Lösung funktioniert, aber die integrierte URL.toURI()nicht.
user11153
2
Leider scheint dies nicht mit "file: ///" zu funktionieren (zB: "file: /// some / directory / eine Datei mit Leerzeichen.html"); es bombardiert mit MalformedURLException in "new URL ()"; Irgendeine Idee, wie man das behebt?
ZioByte
Sie müssen Folgendes tun: String urlStr = " some / directory / eine Datei mit Leerzeichen.html"; URL url = neue URL (urlStr); URI uri = neuer URI (url.getProtocol (), url.getUserInfo (), url.getHost (), url.getPort (), url.getPath (), url.getQuery (), url.getRef ()); urlStr = uri.toASCIIString (); urlStr.replace ("http: //", "file: ///"); Ich habe es nicht getestet, aber ich denke, es wird funktionieren .... :)
M Abdul Sami
1
@tibi Sie können einfach die uri.toString () -Methode verwenden, um sie in eine Zeichenfolge anstelle einer Ascii-Zeichenfolge zu konvertieren.
M Abdul Sami
1
Die API, mit der ich gearbeitet habe, hat den +Ersatz für Leerzeichen nicht akzeptiert , aber die% 20 akzeptiert, sodass diese Lösung besser funktioniert als BalusC, danke!
Julian Honma
35

Guava 15 hat jetzt eine Reihe einfacher URL-Escaper hinzugefügt .

Emmanuel Touzery
quelle
1
Diese leiden unter den gleichen albernen Fluchtregeln wie URLEncoder.
2rs2ts
3
Ich bin mir nicht sicher, ob sie das Problem haben. Sie unterscheiden zum Beispiel "+" oder "% 20", um "" (Formparameter oder Pfadparameter) zu entkommen, was URLEncodernicht der Fall ist.
Emmanuel Touzery
1
Dies funktionierte für mich. Ich habe gerade den Aufruf von URLEncoder () ersetzt, um UrlEscapers.urlFragmentEscaper () aufzurufen, und es hat funktioniert, nicht klar, ob ich stattdessen UrlEscapers.urlPathSegmentEscaper () verwenden sollte.
Paul Taylor
2
Eigentlich hat es bei mir nicht funktioniert, weil es im Gegensatz zu URLEncoder nicht '+' codiert, sondern es in Ruhe lässt. Der Server decodiert '+' als Leerzeichen. Wenn ich URLEncoder verwende, werden die + in% 2B konvertiert und korrekt zurück in +
Paul Taylor
2
Link Update: UrlEscapers
mgaert
6

Die Apache Http Components-Bibliothek bietet eine übersichtliche Option zum Erstellen und Codieren von Abfrageparametern.

Verwenden Sie mit HttpComponents 4.x - URLEncodedUtils

Verwenden Sie für HttpClient 3.x - EncodingUtil

Sashi
quelle
6

Hier ist eine Methode, die Sie in Ihrem Code verwenden können, um eine URL-Zeichenfolge und eine Zuordnung von Parametern in eine gültige codierte URL-Zeichenfolge zu konvertieren, die die Abfrageparameter enthält.

String addQueryStringToUrlString(String url, final Map<Object, Object> parameters) throws UnsupportedEncodingException {
    if (parameters == null) {
        return url;
    }

    for (Map.Entry<Object, Object> parameter : parameters.entrySet()) {

        final String encodedKey = URLEncoder.encode(parameter.getKey().toString(), "UTF-8");
        final String encodedValue = URLEncoder.encode(parameter.getValue().toString(), "UTF-8");

        if (!url.contains("?")) {
            url += "?" + encodedKey + "=" + encodedValue;
        } else {
            url += "&" + encodedKey + "=" + encodedValue;
        }
    }

    return url;
}
Pellet
quelle
6
URL url= new URL("http://example.com/query?q=random word £500 bank $");
URI uri = new URI(url.getProtocol(), url.getUserInfo(), IDN.toASCII(url.getHost()), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
String correctEncodedURL=uri.toASCIIString(); 
System.out.println(correctEncodedURL);

Druckt

http://example.com/query?q=random%20word%20%C2%A3500%20bank%20$

Was passiert hier?

1. Teilen Sie die URL in Strukturteile auf. Verwenden java.net.URL Sie dafür.

2. Codieren Sie jedes Bauteil richtig!

3. Verwenden Sie Punycode, um den Hostnamen IDN.toASCII(putDomainNameHere)zu codieren!

4. Verwenden Sie java.net.URI.toASCIIString(), um NFC-codierten Unicode in Prozent zu codieren - (besser wäre NFKC!). Weitere Informationen finden Sie unter: So codieren Sie diese URL richtig

In einigen Fällen ist es ratsam zu überprüfen, ob die URL bereits verschlüsselt ist . Ersetzen Sie auch '+' codierte Leerzeichen durch '% 20' codierte Leerzeichen.

Hier sind einige Beispiele, die auch richtig funktionieren

{
      "in" : "http://نامه‌ای.com/",
     "out" : "http://xn--mgba3gch31f.com/"
},{
     "in" : "http://www.example.com/‥/foo",
     "out" : "http://www.example.com/%E2%80%A5/foo"
},{
     "in" : "http://search.barnesandnoble.com/booksearch/first book.pdf", 
     "out" : "http://search.barnesandnoble.com/booksearch/first%20book.pdf"
}, {
     "in" : "http://example.com/query?q=random word £500 bank $", 
     "out" : "http://example.com/query?q=random%20word%20%C2%A3500%20bank%20$"
}

Die Lösung besteht rund 100 der von Web Plattform Tests bereitgestellten Testfälle .

jschnasse
quelle
1

In Android würde ich diesen Code verwenden:

Uri myUI = Uri.parse ("http://example.com/query").buildUpon().appendQueryParameter("q","random word A3500 bank 24").build();

Wo Uriist einandroid.net.Uri

Sharjeel Lasharie
quelle
10
Dies verwendet nicht die Standard-Java-API. Bitte geben Sie die verwendete Bibliothek an.
Müller
1

In meinem Fall musste ich nur die gesamte URL übergeben und nur den Wert der einzelnen Parameter codieren. Ich habe keinen gemeinsamen Code dafür gefunden (!!), also habe ich diese kleine Methode erstellt, um den Job zu erledigen:

public static String encodeUrl(String url) throws Exception {
    if (url == null || !url.contains("?")) {
        return url;
    }

    List<String> list = new ArrayList<>();
    String rootUrl = url.split("\\?")[0] + "?";
    String paramsUrl = url.replace(rootUrl, "");
    List<String> paramsUrlList = Arrays.asList(paramsUrl.split("&"));
    for (String param : paramsUrlList) {
        if (param.contains("=")) {
            String key = param.split("=")[0];
            String value = param.replace(key + "=", "");
            list.add(key + "=" +  URLEncoder.encode(value, "UTF-8"));
        }
        else {
            list.add(param);
        }
    }

    return rootUrl + StringUtils.join(list, "&");
}

public static String decodeUrl(String url) throws Exception {
    return URLDecoder.decode(url, "UTF-8");
}

Es verwendet org.apache.commons.lang3.StringUtils

Laurent
quelle
-2
  1. Verwenden Sie Folgendes: URLEncoder.encode (Abfrage, StandardCharsets.UTF_8.displayName ()); oder dies: URLEncoder.encode (Abfrage "UTF-8");
  2. Sie können den folgenden Code verwenden.

    String encodedUrl1 = UriUtils.encodeQuery(query, "UTF-8");//not change 
    String encodedUrl2 = URLEncoder.encode(query, "UTF-8");//changed
    String encodedUrl3 = URLEncoder.encode(query, StandardCharsets.UTF_8.displayName());//changed
    
    System.out.println("url1 " + encodedUrl1 + "\n" + "url2=" + encodedUrl2 + "\n" + "url3=" + encodedUrl3);
Xuelian Han
quelle
4
Nicht richtig. Sie müssen die Parameternamen und -werte separat codieren. Durch das Codieren der gesamten Abfragezeichenfolge werden auch die Trennzeichen =und codiert &, was nicht korrekt ist.
Marquis von Lorne