URLEncoder nie może przetłumaczyć znaku spacji

179

Spodziewam się

System.out.println(java.net.URLEncoder.encode("Hello World", "UTF-8"));

do wyjścia:

Hello%20World

(20 to kod szesnastkowy ASCII dla spacji)

Jednak otrzymuję:

Hello+World

Czy używam złej metody? Jaka jest właściwa metoda, której powinienem użyć?

Cheok Yan Cheng
źródło
3
nazwa klasy jest rzeczywiście myląca i wielu ludzi używało jej niewłaściwie. jednak nie zauważają tego, ponieważ po zastosowaniu URLDecoder przywracana jest oryginalna wartość, więc + lub% 20 nie ma dla nich znaczenia.
niezaprzeczalny

Odpowiedzi:

227

Zachowuje się zgodnie z oczekiwaniami. Te URLEncodernarzędzia specyfikacji HTML dla Jak zakodować URL w postaci HTML.

Z javadocs :

Ta klasa zawiera statyczne metody konwertowania ciągu znaków na format MIME application / x-www-form-urlencoded.

i ze specyfikacji HTML :

application / x-www-form-urlencoded

Formularze przesyłane z tym typem zawartości muszą być kodowane w następujący sposób:

  1. Nazwy i wartości kontrolek są chronione. Znaki spacji są zastępowane przez `+ '

Będziesz musiał go wymienić, np:

System.out.println(java.net.URLEncoder.encode("Hello World", "UTF-8").replace("+", "%20"));
dogbane
źródło
19
cóż, to jest rzeczywiście odpowiedź, a nie zastępowanie, czy nie ma biblioteki java lub funkcji do wykonania zadania /?
co2f2e
5
Znak plus musi zostać usuniętyt.println(java.net.URLEncoder.encode("Hello World", "UTF-8").replace("\\+", "%20"));
George
26
@congliu to niepoprawne - prawdopodobnie myślisz o replaceAll (), który działa z wyrażeniem regularnym - replace () to prosta zamiana sekwencji znaków.
CupawnTae
12
Tak @congliu, dobrym sposobem jest: URLEncoder.encode ("Myurl", "utf-8"). ReplaceAll ("\\ +", "% 20");
eento
9
@ClintEastwood Ta odpowiedź zachęca do korzystania z java.net.URLEncodera, który nie spełnia tego, o co pierwotnie pytano. A więc ta odpowiedź sugeruje poprawkę, używając funkcji replace (). Dlaczego nie? Ponieważ to rozwiązanie jest podatne na błędy i może prowadzić do 20 innych podobnych pytań, ale o innym charakterze. Dlatego powiedziałem, że to krótkowzroczność.
pyb
57

Spacja jest kodowana %20w adresach URL oraz +w przesyłanych danych formularzach (typ zawartości application / x-www-form-urlencoded). Potrzebujesz tego pierwszego.

Korzystanie z guawy :

dependencies {
     compile 'com.google.guava:guava:23.0'
     // or, for Android:
     compile 'com.google.guava:guava:23.0-android'
}

Możesz użyć UrlEscapers :

String encodedString = UrlEscapers.urlFragmentEscaper().escape(inputString);

Nie używaj String.replace, to zakoduje tylko spację. Zamiast tego użyj biblioteki.

pyb
źródło
Działa również na Androida, com.google.guava: guava: 22.0-rc1-android.
Bevor
1
@Bevor rc1 oznacza 1st Release Candidate, tj. Wersję, która nie została jeszcze zatwierdzona do ogólnego wydania. Jeśli możesz, wybierz wersję bez migawki, alfa, beta, rc, ponieważ są znane z błędów.
pyb
1
@pyb Dzięki, ale mimo to zaktualizuję biblioteki, kiedy mój projekt zostanie ukończony. Znaczy, nie pójdę do produkcji bez ostatecznych wersji. I nadal trwa to wiele tygodni, więc myślę, że jest ostateczna wersja.
Bevor
1
Niestety, Guava nie zapewnia dekodera, w przeciwieństwie do URLCodec Apache .
Benny Bottema,
26

Ta klasa wykonuje application/x-www-form-urlencodedkodowanie typu zamiast kodowania procentowego, dlatego zastąpienie go +jest poprawnym zachowaniem.

Z javadoc:

Podczas kodowania ciągu obowiązują następujące zasady:

  • Znaki alfanumeryczne od „a” do „z”, od „A” do „Z” i od „0” do „9” pozostają takie same.
  • Znaki specjalne „.”, „-”, „*” i „_” pozostają takie same.
  • Znak spacji „” jest konwertowany na znak plusa „+”.
  • Wszystkie inne 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ą szesnastkową reprezentacją bajtu. Zalecany schemat kodowania to UTF-8. Jednak ze względu na zgodność, jeśli nie określono kodowania, używane jest domyślne kodowanie platformy.
axtavt
źródło
@axtavt Ładne wyjaśnienie. Ale wciąż mam kilka pytań. W programie urlprzestrzeń należy interpretować jako %20. Więc musimy zrobić url.replaceAll("\\+", "%20")? A jeśli to javascript, nie powinniśmy używać escapefunkcji. Użyj encodeURIlub encodeURIComponentzamiast. Tak myślałem.
Alston
1
@Stallman to jest Java, a nie JavaScript. Całkowicie różne języki.
Charles Wood
19

Koduj parametry zapytania

org.apache.commons.httpclient.util.URIUtil
    URIUtil.encodeQuery(input);

LUB jeśli chcesz uniknąć znaków w identyfikatorze URI

public static String escapeURIPathParam(String input) {
  StringBuilder resultStr = new StringBuilder();
  for (char ch : input.toCharArray()) {
   if (isUnsafe(ch)) {
    resultStr.append('%');
    resultStr.append(toHex(ch / 16));
    resultStr.append(toHex(ch % 16));
   } else{
    resultStr.append(ch);
   }
  }
  return resultStr.toString();
 }

 private static char toHex(int ch) {
  return (char) (ch < 10 ? '0' + ch : 'A' + ch - 10);
 }

 private static boolean isUnsafe(char ch) {
  if (ch > 128 || ch < 0)
   return true;
  return " %$&+,/:;=?@<>#%".indexOf(ch) >= 0;
 }
fmucar
źródło
3
Używanie org.apache.commons.httpclient.util.URIUtilwydaje się być najbardziej efektywnym sposobem rozwiązania problemu!
Stéphane Ammar
11

Hello+Worldto sposób, w jaki przeglądarka zakoduje dane formularza ( application/x-www-form-urlencoded) dla GETżądania i jest to ogólnie przyjęta forma dla części adresu URI zawierającej zapytanie.

http://host/path/?message=Hello+World

Jeśli wysłałeś to żądanie do serwletu Java, serwlet poprawnie zdekodowałby wartość parametru. Zwykle jedyny przypadek, w którym występują tutaj problemy, to niezgodność kodowania.

Ściśle mówiąc, w specyfikacjach HTTP lub URI nie ma wymogu, aby część zapytania była kodowana przy użyciu application/x-www-form-urlencodedpar klucz-wartość; część zapytania musi po prostu mieć formę, którą akceptuje serwer WWW. W praktyce raczej nie będzie to problemem.

Ogólnie byłoby niepoprawne użycie tego kodowania dla innych części identyfikatora URI (na przykład ścieżki). W takim przypadku należy użyć schematu kodowania opisanego w dokumencie RFC 3986 .

http://host/Hello%20World

Więcej tutaj .

McDowell
źródło
5

Inne odpowiedzi przedstawiają albo ręczną zamianę ciągu, URLEncoder, który faktycznie koduje format HTML, porzucony URIUtil Apache lub użycie UrlEscapers Guava . Ten ostatni jest w porządku, z wyjątkiem tego, że nie zapewnia dekodera.

Apache Commons Lang dostarcza URLCodec , który koduje i dekoduje zgodnie z formatem URL rfc3986 .

String encoded = new URLCodec().encode(str);
String decoded = new URLCodec().decode(str);

Jeśli korzystasz już ze Springa, możesz również zdecydować się na użycie jej klasy UriUtils .

Benny Bottema
źródło
6
URLCodec nie jest tutaj dobrym rozwiązaniem, ponieważ koduje spacje jako plusy, ale pytanie dotyczy spacji do zakodowania jako% 20.
davidwebster48
3

„+” jest poprawne. Jeśli naprawdę potrzebujesz% 20, potem sam wymień Plusses.

Daniel
źródło
5
Może wystąpić problem, jeśli początkowy ciąg naprawdę zawiera znak +.
Alexis Dufrenoy
17
@Traroth - Niezupełnie. +Postać w oryginalnym tekście ma być zakodowany jako %2B.
Ted Hopp,
mówienie tego +poprawnie bez znajomości kontekstu jest przynajmniej pedantyczne. Głosowano w dół. Przeczytaj inne odpowiedzi, aby dowiedzieć się, kiedy należy użyć + lub% 20.
Clint Eastwood
@ClintEastwood: Czy możesz mi powiedzieć o jakimś przypadku użycia, w którym znak + spacji nie jest poprawny w adresach URL? Z wyjątkiem sytuacji, gdy po drugiej stronie znajduje się niezgodny parser URL?
Daniel
@Daniel na pewno, nie mówi „niepoprawne”, ale nieodpowiednie? tak. Narzędzia analityczne często używają parametrów zapytań z wartościami oddzielonymi określonym znakiem, na przykład „+”. W takim przypadku użycie „+” zamiast „% 20” byłoby niewłaściwe. „+” służy do zmiany znaczenia spacji w formularzu, podczas gdy „kodowanie procentowe” (inaczej kodowanie adresu URL) jest bardziej zorientowane na adresy URL.
Clint Eastwood
2

To zadziałało dla mnie

org.apache.catalina.util.URLEncoder ul = new org.apache.catalina.util.URLEncoder().encode("MY URL");
Hitesh Kumar
źródło
1

Chociaż dość stary, niemniej jednak szybka odpowiedź:

Spring udostępnia UriUtils - dzięki temu możesz określić, jak zakodować i która część jest powiązana z URI, np.

encodePathSegment
encodePort
encodeFragment
encodeUriVariables
....

Używam ich, ponieważ używamy już Springa, czyli nie jest wymagana żadna dodatkowa biblioteka!

Lew
źródło
0

Sprawdź klasę java.net.URI.

Fredrik Widerberg
źródło
0

Czy używam złej metody? Jaka jest właściwa metoda, której powinienem użyć?

Tak, ta metoda java.net.URLEncoder.encode nie została stworzona do konwersji „” na „20%” zgodnie ze specyfikacją ( źródło ).

Znak spacji „” jest konwertowany na znak plusa „+”.

Nawet to nie jest właściwa metoda, możesz ją zmodyfikować, aby: System.out.println(java.net.URLEncoder.encode("Hello World", "UTF-8").replaceAll("\\+", "%20"));miłego dnia =).

Pregunton
źródło
Sugerujesz użycie metody, która jest nieodpowiednia ( URLEncoder.encode) i załatanie jej, replaceAllktóra zadziała tylko w tym konkretnym przypadku. Zamiast tego użyj właściwej klasy i metody, zobacz inne odpowiedzi.
pyb
@pyb wygląda na to, że nie rozumiesz, co napisałem. Nigdy nie powiedziałem „Proponuję go używać”, powiedziałem „możesz”. Przeczytaj i zrozum, zanim napiszesz.
Pregunton
To jest witryna z pytaniami i odpowiedziami, a nie zwykła tablica ogłoszeń, na której ludzie rozmawiają. Jeśli masz komentarze boczne, skorzystaj z komentarzy. Dłuższa rozmowa? Skorzystaj z czatu. Nie wysyłaj kodu, z którym się nie zgadzasz. Prosimy o przeczytanie i zrozumienie zasad tej witryny, zanim zaczniesz publikować materiały i udzielać im wykładów.
pyb
1
Głosuję za tym z powrotem, ponieważ większość innych rozwiązań zapewnia tę samą radę. Nie przedstawiono żadnych „konkretnych przypadków”, aby udowodnić, że ta metoda jest błędna. Używanie Apache commons z blokami try-catch lub zależnościami jest zbyt kłopotliwe dla metody, którą można skutecznie załatać za pomocą replaceAll.
Eugene Kartoyev,
-2

UŻYJ MyUrlEncode.URLencoding (String url, String enc), aby rozwiązać problem

    public class MyUrlEncode {
    static BitSet dontNeedEncoding = null;
    static final int caseDiff = ('a' - 'A');
    static {
        dontNeedEncoding = new BitSet(256);
        int i;
        for (i = 'a'; i <= 'z'; i++) {
            dontNeedEncoding.set(i);
        }
        for (i = 'A'; i <= 'Z'; i++) {
            dontNeedEncoding.set(i);
        }
        for (i = '0'; i <= '9'; i++) {
            dontNeedEncoding.set(i);
        }
        dontNeedEncoding.set('-');
        dontNeedEncoding.set('_');
        dontNeedEncoding.set('.');
        dontNeedEncoding.set('*');
        dontNeedEncoding.set('&');
        dontNeedEncoding.set('=');
    }
    public static String char2Unicode(char c) {
        if(dontNeedEncoding.get(c)) {
            return String.valueOf(c);
        }
        StringBuffer resultBuffer = new StringBuffer();
        resultBuffer.append("%");
        char ch = Character.forDigit((c >> 4) & 0xF, 16);
            if (Character.isLetter(ch)) {
            ch -= caseDiff;
        }
        resultBuffer.append(ch);
            ch = Character.forDigit(c & 0xF, 16);
            if (Character.isLetter(ch)) {
            ch -= caseDiff;
        }
         resultBuffer.append(ch);
        return resultBuffer.toString();
    }
    private static String URLEncoding(String url,String enc) throws UnsupportedEncodingException {
        StringBuffer stringBuffer = new StringBuffer();
        if(!dontNeedEncoding.get('/')) {
            dontNeedEncoding.set('/');
        }
        if(!dontNeedEncoding.get(':')) {
            dontNeedEncoding.set(':');
        }
        byte [] buff = url.getBytes(enc);
        for (int i = 0; i < buff.length; i++) {
            stringBuffer.append(char2Unicode((char)buff[i]));
        }
        return stringBuffer.toString();
    }
    private static String URIEncoding(String uri , String enc) throws UnsupportedEncodingException { //对请求参数进行编码
        StringBuffer stringBuffer = new StringBuffer();
        if(dontNeedEncoding.get('/')) {
            dontNeedEncoding.clear('/');
        }
        if(dontNeedEncoding.get(':')) {
            dontNeedEncoding.clear(':');
        }
        byte [] buff = uri.getBytes(enc);
        for (int i = 0; i < buff.length; i++) {
            stringBuffer.append(char2Unicode((char)buff[i]));
        }
        return stringBuffer.toString();
    }

    public static String URLencoding(String url , String enc) throws UnsupportedEncodingException {
        int index = url.indexOf('?');
        StringBuffer result = new StringBuffer();
        if(index == -1) {
            result.append(URLEncoding(url, enc));
        }else {
            result.append(URLEncoding(url.substring(0 , index),enc));
            result.append("?");
            result.append(URIEncoding(url.substring(index+1),enc));
        }
        return result.toString();
    }

}
IloveIniesta
źródło
9
wymyślanie na nowo koła, dodawanie bardzo podatnego na błędy kodu do bazy kodu jest prawie zawsze złą decyzją.
Clint Eastwood
-6

użyj zestawu znaków „ ISO-8859-1” dla URLEncoder

Akhil Sikri
źródło