Kodowanie URL Java parametrów ciągu zapytania

710

Powiedz, że mam adres URL

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

i mam zapytanie wprowadzone przez użytkownika, takie jak:

losowe słowo 500 £ bank $

Chcę, aby wynik był poprawnie zakodowanym adresem URL:

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

Jaki jest najlepszy sposób na osiągnięcie tego? Próbowałem URLEncoderutworzyć obiekty URI / URL, ale żaden z nich nie wyszedł całkiem dobrze.

użytkownik1277546
źródło
24
Co rozumiesz przez „żaden z nich nie wychodzi całkiem dobrze”?
Mark Elliot
2
Użyłem URI.create i zastąpiłem spacje znakiem + w querystring. W witrynie klienta konwersja + powrót do spacji, gdy wybrałem ciągi zapytania. To zadziałało dla mnie.
ND27,
Dlaczego spodziewasz się, że $ będzie kodowane procentowo?
jschnasse

Odpowiedzi:

1150

URLEncoderjest droga. Trzeba tylko pamiętać, aby zakodować tylko nazwę i / lub wartość parametru pojedynczego ciągu zapytania, a nie cały adres URL, na pewno nie znak separatora parametru ciągu zapytania &ani znak separatora nazwa-wartość parametru =.

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

Zauważ, że spacje w parametrach zapytania są reprezentowane przez +, a nie %20, co jest zgodne z prawem. %20Zwykle być używany do reprezentowania w samej przestrzeni (część przed URI-separatora ciąg znaków zapytania URI? ), a nie w ciągu zapytania (po części ?).

Pamiętaj również, że istnieją trzy encode()metody. Jeden bez Charsetdrugiego argumentu i drugi z Stringdrugim argumentem, który zgłasza sprawdzony wyjątek. Ten bez Charsetargumentu jest przestarzały. Nigdy go nie używaj i zawsze określaj Charsetargument. Javadoc nawet wyraźnie zaleca stosowanie kodowania UTF-8, przewidzianym w RFC3986 i W3C .

Wszystkie pozostałe znaki są niebezpieczne i są najpierw konwertowane na jeden lub więcej bajtów przy użyciu jakiegoś schematu kodowania. Następnie każdy bajt jest reprezentowany przez 3-znakowy ciąg „% xy”, gdzie xy jest dwucyfrową reprezentacją szesnastkową bajtu. Zalecanym schematem kodowania jest UTF-8 . Jednak ze względu na kompatybilność, jeśli kodowanie nie jest określone, stosowane jest domyślne kodowanie platformy.

Zobacz też:

BalusC
źródło
W adresie URL mogą występować 2 typy parametrów. Ciąg zapytania (po którym następuje?) I parametr ścieżki (zazwyczaj część samego adresu URL). A co z parametrami ścieżki. URLEncoder wytwarza + dla przestrzeni nawet dla parametrów ścieżki. W rzeczywistości po prostu nie obsługuje niczego innego niż ciąg zapytania. Ponadto to zachowanie nie jest zsynchronizowane z serwerami węzłów js. Więc dla mnie ta klasa jest marnotrawstwem i nie może być wykorzystywana inaczej niż w bardzo specyficznych / specjalnych scenariuszach.
sharadendu sinha
2
@sharadendusinha: zgodnie z dokumentacją i odpowiedzią, URLEncoderparametry zapytań zakodowane w adresie URL są zgodne z application/x-www-form-urlencodedregułami. Parametry ścieżki nie pasują do tej kategorii. Zamiast tego potrzebujesz kodera URI.
BalusC
Tak jak przewidziałem, zdarzy się ... Użytkownicy się mylą, ponieważ oczywiście problemem jest to, że ludzie muszą zakodować coś więcej niż tylko wartość parametru. Jest to bardzo rzadki przypadek, w którym wystarczy zakodować wartość parametru. Właśnie dlatego podałem moją „zdezorientowaną” odpowiedź wiki, aby pomóc ludziom takim jak @sharadendusinha.
Adam Gent
1
@WijaySharma: Ponieważ znaki specyficzne dla adresów URL również zostałyby zakodowane. Powinieneś to zrobić tylko wtedy, gdy chcesz przekazać cały adres URL jako parametr zapytania innego adresu URL.
BalusC
1
„+, a nie% 20” to to, co musiałem usłyszeć. Dziękuję bardzo.
wetjosh
173

Nie użyłbym URLEncoder. Poza tym, że ma niepoprawną nazwę ( URLEncodernie ma nic wspólnego z adresami URL), jest nieefektywny (używa StringBufferzamiast Buildera i robi kilka innych rzeczy, które są powolne) Jest to również zbyt łatwe do zepsucia.

Zamiast tego chciałbym użyć URIBuilderlub sprężyny org.springframework.web.util.UriUtils.encodeQuerylub Commons ApacheHttpClient . Powodem jest to, że musisz zmienić nazwę parametru zapytania (tj. Odpowiedź BalusC q) inaczej niż wartość parametru.

Jedynym minusem powyższego (który dowiedziałem się boleśnie) jest to, że adresy URL nie są prawdziwym podzbiorem identyfikatorów URI .

Przykładowy kod:

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

Ponieważ odsyłam do innych odpowiedzi, oznaczyłem to jako wiki społeczności. Nie krępuj się edytować.

Adam Gent
źródło
2
Dlaczego nie ma to nic wspólnego z adresami URL?
Luis Sep
15
@Luis: URLEncoderjest, jak mówi javadoc, przeznaczone do kodowania parametrów ciągu zapytania zgodnych z application/x-www-form-urlencodedopisem w specyfikacji HTML: w3.org/TR/html4/interact/… . Niektórzy użytkownicy rzeczywiście mylą / nadużywają go do kodowania całych identyfikatorów URI, jak najwyraźniej obecny użytkownik odpowiadający.
BalusC
8
@LuisSep w skrócie URLEncoder służy do kodowania w celu przesłania formularza. To nie jest ucieczka. To nie jest dokładnie to samo, co byś użył do stworzenia adresów URL, które mają być umieszczone na twojej stronie internetowej, ale zdarza się, że są na tyle podobne, że ludzie je nadużywają. Jedynym momentem, w którym powinieneś używać URLEncodera, jest napisanie klienta HTTP (i nawet wtedy istnieją znacznie lepsze opcje kodowania).
Adam Gent
1
@BalusC „ Niektórzy użytkownicy rzeczywiście mylą / nadużywają go do kodowania całych identyfikatorów URI, jak najwyraźniej zrobił to obecny odpowiadający. ”. Założyłeś. Nigdy nie mówiłem, że to popieprzyłem. Właśnie widziałem innych, którzy to zrobili, a które błędy muszę naprawić. To, co spieprzyłem, polega na tym, że klasa Java URL akceptuje nawiasy klamrowe, ale nie klasę URI. Istnieje wiele sposobów na zepsucie budowania adresów URL i nie wszyscy są tacy jak Ty. Powiedziałbym, że większość użytkowników, którzy szukają SO dla URLEncoding, prawdopodobnie są „ użytkownikami mylą / nadużywają ” ucieczki URI.
Adam Gent
1
Pytanie nie dotyczyło tego, ale twoja odpowiedź implikuje.
BalusC
99

Musisz najpierw utworzyć identyfikator URI, taki jak:

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());

Następnie przekonwertuj ten Uri na ciąg ASCII:

urlStr=uri.toASCIIString();

Teraz twój ciąg adresu URL jest całkowicie zakodowany, najpierw wykonaliśmy proste kodowanie adresu URL, a następnie przekonwertowaliśmy go na ciąg ASCII, aby upewnić się, że żaden ciąg poza US-ASCII nie pozostanie w ciągu. Właśnie tak robią przeglądarki.

M. Abdul Sami
źródło
7
Dzięki! To głupie, że twoje rozwiązanie działa, ale wbudowane URL.toURI()nie.
user11153,
2
Niestety wydaje się, że to nie działa z „file: ///” (np .: „file: /// some / katalog / plik zawierający spacje.html”); bombarduje MalformedURLException w „new URL ()”; Jakiś pomysł jak to naprawić?
ZioByte
Musisz zrobić coś takiego: String urlStr = " some / katalog / plik zawierający spacje.html"; URL url = nowy adres URL (urlStr); URI uri = nowy URI (url.getProtocol (), url.getUserInfo (), url.getHost (), url.getPort (), url.getPath (), url.getQuery (), url.getRef ()); urlStr = uri.toASCIIString (); urlStr.replace („http: //”, „plik: ///”); Nie testowałem tego, ale myślę, że zadziała .... :)
M Abdul Sami
1
@ tibi możesz po prostu użyć metody uri.toString (), aby przekonwertować ją na ciąg zamiast na ciąg Ascii.
M Abdul Sami,
1
Interfejs API, z którym pracowałem, nie zaakceptował +zastąpienia spacji, ale zaakceptował% 20, więc to rozwiązanie działało lepiej niż BalusC, dzięki!
Julian Honma
35

Guava 15 dodał teraz zestaw prostych kodów ucieczki adresów URL .

Emmanuel Touzery
źródło
1
Ci cierpią z powodu tych samych głupich zasad ucieczki jak URLEncoder.
2rs2ts
3
nie jestem pewien, czy mają problem. rozróżniają na przykład „+” lub „% 20”, aby uciec ”(param form lub path param), co URLEncodernie.
Emmanuel Touzery
1
To zadziałało dla mnie. Właśnie zastąpiłem wywołanie URLEncoder (), aby wywołać UrlEscapers.urlFragmentEscaper () i zadziałało, nie jest jasne, czy zamiast tego powinienem używać UrlEscapers.urlPathSegmentEscaper ().
Paul Taylor
2
Właściwie to nie działało dla mnie, ponieważ w przeciwieństwie do URLEncodera nie koduje „+”, pozostawia go w spokoju, serwer dekoduje „+” jako spację, natomiast jeśli użyję URLEncodera +, są konwertowane na% 2B i poprawnie dekodowane z powrotem do +
Paul Taylor
2
Aktualizacja linku: UrlEscapers
mgaert
6

Biblioteka komponentów Apache Http zapewnia ciekawą opcję budowania i kodowania parametrów zapytań -

Z HttpComponents 4.x użyj - URLEncodedUtils

Do użytku w HttpClient 3.x - EncodingUtil

Sashi
źródło
6

Oto metoda, której możesz użyć w kodzie, aby przekonwertować ciąg adresu URL i mapę parametrów na prawidłowy zakodowany ciąg adresu URL zawierający parametry zapytania.

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;
}
Śrut
źródło
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);

Wydruki

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

Co tu się dzieje?

1. Podziel adres URL na części strukturalne. Użyj java.net.URL do tego.

2. Zakoduj poprawnie każdą część konstrukcyjną!

3. Użyj IDN.toASCII(putDomainNameHere)aby Punycode zakodować nazwę hosta!

4. Użyj java.net.URI.toASCIIString()do kodowania procentowego, kodowania NFC unicode - (lepiej byłoby NFKC!). Aby uzyskać więcej informacji, zobacz: Jak poprawnie zakodować ten adres URL

W niektórych przypadkach wskazane jest sprawdzenie, czy adres URL jest już zakodowany . Zastąp także spacje zakodowane „+” spacjami zakodowanymi „% 20”.

Oto kilka przykładów, które również będą działać poprawnie

{
      "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$"
}

Rozwiązanie przechodzi około 100 przypadków testowych dostarczonych przez Web Plattform Tests .

jschnasse
źródło
1

W Androidzie użyłbym tego kodu:

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

Gdzie Urijestandroid.net.Uri

Sharjeel Lasharie
źródło
10
To nie używa standardowego API Java. Podaj więc używaną bibliotekę.
rmuller,
1

W moim przypadku musiałem tylko przekazać cały adres URL i zakodować tylko wartość każdego parametru. Nie znalazłem wspólnego kodu, aby to zrobić (!!), więc stworzyłem tę małą metodę wykonania zadania:

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");
}

Wykorzystuje org.apache.commons.lang3.StringUtils

Laurent
źródło
-2
  1. Użyj tego : URLEncoder.encode (zapytanie, StandardCharsets.UTF_8.displayName ()); lub to: URLEncoder.encode (zapytanie, „UTF-8”);
  2. Możesz użyć następującego kodu.

    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
źródło
4
Niepoprawne. Musisz zakodować nazwy parametrów i wartości osobno. Kodowanie całego ciągu zapytania spowoduje również zakodowanie separatorów =i &, co jest niepoprawne.
user207421,