Pengkodean URL Java untuk parameter string kueri

710

Katakanlah saya punya URL

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

dan saya memiliki kueri yang dimasukkan oleh pengguna seperti:

kata acak £ 500 bank $

Saya ingin hasilnya menjadi URL yang disandikan dengan benar:

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

Apa cara terbaik untuk mencapai ini? Saya mencoba URLEncoderdan membuat objek URI / URL tetapi tidak satupun yang benar.

pengguna1277546
sumber
25
Apa yang Anda maksud dengan "tidak ada yang keluar dengan benar"?
Mark Elliot
2
Saya telah menggunakan URI.create dan mengganti spasi dengan + di querystring. Di situs klien itu dikonversi + kembali ke spasi ketika saya memilih string kueri. Itu berhasil bagi saya.
ND27
Mengapa Anda berharap $ dikodekan persen?
jschnasse

Jawaban:

1151

URLEncoderadalah cara untuk pergi. Anda hanya perlu mengingat untuk menyandikan hanya nama dan / atau nilai parameter string kueri individual, bukan keseluruhan URL, yang pasti bukan karakter pemisah parameter string kueri &atau karakter pemisah nilai-nilai parameter =.

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

Perhatikan bahwa spasi dalam parameter kueri diwakili oleh +, bukan %20, yang sah secara sah. The %20biasanya digunakan untuk mewakili ruang di URI sendiri (bagian sebelum URI-string kueri karakter pemisah ?), tidak dalam query string (bagian setelah ?).

Perhatikan juga bahwa ada tiga encode()metode. Satu tanpa Charsetsebagai argumen kedua dan lainnya dengan Stringsebagai argumen kedua yang melempar pengecualian diperiksa. Yang tanpa Charsetargumen sudah usang. Jangan pernah menggunakannya dan selalu tentukan Charsetargumennya. The javadoc bahkan secara eksplisit menganjurkan untuk menggunakan UTF-8 encoding, sebagaimana diamanatkan oleh RFC3986 dan W3C .

Semua karakter lain tidak aman dan pertama-tama dikonversi menjadi satu atau lebih byte menggunakan beberapa skema penyandian. Kemudian setiap byte diwakili oleh string 3-karakter "% xy", di mana xy adalah representasi heksadesimal dua digit dari byte. Skema pengkodean yang disarankan untuk digunakan adalah UTF-8 . Namun, untuk alasan kompatibilitas, jika penyandian tidak ditentukan, maka penyandian standar platform digunakan.

Lihat juga:

BalusC
sumber
Ada 2 jenis parameter dalam URL. String kueri (diikuti oleh?) Dan parameter jalur (Biasanya bagian dari URL itu sendiri). Jadi, bagaimana dengan parameter path. URLEncoder menghasilkan + untuk ruang bahkan untuk parameter jalur. Sebenarnya itu hanya tidak menangani apa pun selain string kueri. Juga, perilaku ini tidak sinkron dengan server node js. Jadi bagi saya kelas ini adalah pemborosan dan tidak dapat digunakan selain untuk skenario yang sangat spesifik / khusus.
sharadendu sinha
2
@sharadendusinha: seperti yang didokumentasikan dan dijawab, URLEncoderadalah untuk parameter kueri yang disandikan URL sesuai application/x-www-form-urlencodedaturan. Parameter jalur tidak cocok dalam kategori ini. Anda membutuhkan encoder URI sebagai gantinya.
BalusC
Seperti yang saya perkirakan akan terjadi ... pengguna menjadi bingung karena jelas masalahnya adalah orang perlu mengkodekan lebih dari sekedar nilai parameter. Ini adalah kasus yang sangat jarang bahwa Anda hanya perlu menyandikan nilai parameter. Itu sebabnya saya memberikan jawaban wiki "bingung" saya untuk membantu orang-orang seperti @sharadendusinha.
Adam Gent
1
@WijaySharma: Karena karakter spesifik URL akan disandikan juga. Anda hanya harus melakukan itu ketika Anda ingin meneruskan seluruh URL sebagai parameter kueri dari URL lain.
BalusC
1
"+, bukan% 20" adalah yang perlu saya dengar. Terima kasih banyak.
wetjosh
173

Saya tidak akan menggunakan URLEncoder. Selain salah nama ( URLEncodertidak ada hubungannya dengan URL), tidak efisien (ia menggunakan StringBufferBuilder dan melakukan beberapa hal lain yang lambat) Ini juga terlalu mudah untuk mengacaukannya.

Sebaliknya saya akan menggunakan URIBuilderatau Spring's org.springframework.web.util.UriUtils.encodeQueryatau Commons ApacheHttpClient . Alasannya adalah Anda harus melarikan diri nama parameter kueri (yaitu jawaban BalusC q) berbeda dari nilai parameter.

Satu-satunya downside ke atas (yang saya temukan dengan menyakitkan) adalah URL bukan subset sebenarnya dari URI .

Kode sampel:

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

Karena saya hanya menautkan ke jawaban lain, saya menandai ini sebagai wiki komunitas. Jangan ragu untuk mengedit.

Adam Gent
sumber
2
Mengapa tidak ada hubungannya dengan URL?
Luis Sep
15
@Luis: URLEncoderadalah seperti kata javadoc yang bermaksud untuk menyandikan parameter string kueri sesuai application/x-www-form-urlencodedseperti yang dijelaskan dalam spesifikasi HTML: w3.org/TR/html4/interact/… . Beberapa pengguna memang bingung / menyalahgunakannya untuk menyandikan seluruh URI, seperti yang dilakukan penjawab saat ini.
BalusC
8
@LuisSep dalam URLEncoder pendek adalah untuk penyandian untuk pengiriman formulir Ini bukan untuk melarikan diri. Ini bukan melarikan diri yang sama persis yang Anda gunakan untuk membuat URL untuk dimasukkan ke halaman web Anda, tetapi kebetulan cukup mirip bahwa orang menyalahgunakannya. Satu-satunya waktu Anda harus menggunakan URLEncoder adalah jika Anda menulis klien HTTP (dan itupun ada opsi yang jauh lebih unggul untuk pengkodean).
Adam Gent
1
@BalusC " Beberapa pengguna memang membingungkan / menyalahgunakannya untuk menyandikan seluruh URI, seperti yang dilakukan penjawab saat ini. " Anda salah mengira. Saya tidak pernah mengatakan saya mengacaukannya. Saya baru saja melihat orang lain yang telah melakukannya, siapa bug yang harus saya perbaiki. Bagian yang saya mengacaukan adalah bahwa kelas Java URL akan menerima tanda kurung tidak dihapus tetapi tidak kelas URI. Ada banyak cara untuk mengacaukan pembuatan URL dan tidak semua orang brilian seperti Anda. Saya akan mengatakan bahwa sebagian besar pengguna yang mencari SO untuk URLEncoding mungkin adalah " pengguna memang bingung / menyalahgunakan " URI melarikan diri.
Adam Gent
1
Pertanyaan belum tentang itu namun jawaban Anda menyiratkan itu.
BalusC
99

Anda harus terlebih dahulu membuat URI seperti:

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

Kemudian konversikan Uri ke string ASCII:

urlStr=uri.toASCIIString();

Sekarang string url Anda benar-benar dikodekan terlebih dahulu kami melakukan pengkodean url sederhana dan kemudian kami mengubahnya menjadi ASCII String untuk memastikan tidak ada karakter di luar US-ASCII yang tersisa dalam string. Inilah yang dilakukan browser.

M Abdul Sami
sumber
7
Terima kasih! Sangat bodoh bahwa solusi Anda berfungsi, tetapi bawaan URL.toURI()tidak.
user11153
2
Sayangnya ini sepertinya tidak berfungsi dengan "file: ///" (mis: "file: /// some / direktori / file yang mengandung spasi.html"); itu dibom dengan MalformedURLException di "URL baru ()"; ada ide bagaimana cara memperbaikinya?
ZioByte
Anda perlu melakukan sesuatu seperti ini: String urlStr = " some / direktori / file yang berisi spasi.html"; URL URL = URL baru (urlStr); URI uri = URI baru (url.getProtocol (), url.getUserInfo (), url.getHost (), url.getPort (), url.getPath (), url.getPath (), url.getQuery (), url.getRef ()); urlStr = uri.toASCIIString (); urlStr.replace ("http: //", "file: ///"); Saya belum mengujinya, tapi saya pikir ini akan berhasil .... :)
M Abdul Sami
1
@tibi Anda cukup menggunakan metode uri.toString () untuk mengubahnya menjadi string, bukan string Ascii.
M Abdul Sami
1
API yang saya kerjakan tidak menerima +penggantian spasi, tetapi menerima% 20 sehingga solusi ini bekerja lebih baik daripada BalusC, terima kasih!
Julian Honma
35

Guava 15 sekarang telah menambahkan satu set escapers URL langsung .

Emmanuel Touzery
sumber
1
Ini menderita dari aturan kabur yang sama konyol seperti URLEncoder.
2rs2ts
3
tidak yakin mereka memiliki masalah. mereka membedakan misalnya "+" atau "% 20" untuk keluar "" (bentuk param atau path param) yang URLEncodertidak.
Emmanuel Touzery
1
Ini bekerja untuk saya, saya baru saja mengganti panggilan ke URLEncoder () untuk memanggil ke UrlEscapers.urlFragmentEscaper () dan berhasil, tidak jelas apakah saya harus menggunakan UrlEscapers.urlPathSegmentEscaper () sebagai gantinya.
Paul Taylor
2
Sebenarnya itu tidak bekerja untuk saya karena tidak seperti URLEncoder ia tidak menyandikan '+' ia meninggalkannya sendiri, server menerjemahkan '+' sebagai ruang sedangkan jika saya menggunakan URLEncoder '+' s dikonversi ke% 2B dan diterjemahkan dengan benar ke +
Paul Taylor
2
Pembaruan tautan
mgaert
6

Pustaka Komponen Apache Http menyediakan opsi yang rapi untuk membuat dan menyandikan param query -

Dengan penggunaan HttpComponents 4.x - URLEncodedUtils

Untuk penggunaan HttpClient 3.x - EncodingUtil

Sashi
sumber
6

Berikut adalah metode yang dapat Anda gunakan dalam kode Anda untuk mengonversi string url dan memetakan parameter ke string url yang disandikan yang berisi parameter kueri.

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;
}
Pelet
sumber
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);

Cetakan

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

Apa yang terjadi disini?

1. Pisahkan URL menjadi bagian-bagian struktural. Gunakan java.net.URL untuk itu.

2. Encode setiap bagian struktural dengan benar!

3. Gunakan IDN.toASCII(putDomainNameHere)untuk Punycode menyandikan nama host!

4. Gunakan java.net.URI.toASCIIString()untuk persen-encode, NFC dikodekan unicode - (lebih baik NFKC!). Untuk info lebih lanjut, lihat: Cara menyandikan URL ini dengan benar

Dalam beberapa kasus, disarankan untuk memeriksa apakah url sudah dikodekan . Juga ganti ruang yang disandikan '+' dengan ruang yang disandikan '% 20'.

Berikut adalah beberapa contoh yang juga akan berfungsi dengan baik

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

Solusi melewati sekitar 100 dari testcases yang disediakan oleh Web Plattform Tests .

Jschnasse
sumber
1

Di android saya akan menggunakan kode ini:

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

Dimana Uriaandroid.net.Uri

Sharjeel Lasharie
sumber
10
Ini tidak menggunakan API Java standar. Jadi tolong tentukan perpustakaan yang digunakan.
rmuller
1

Dalam kasus saya, saya hanya perlu melewatkan seluruh url dan mengkodekan hanya nilai dari setiap parameter. Saya tidak menemukan kode umum untuk melakukannya (!!) jadi saya membuat metode kecil ini untuk melakukan pekerjaan:

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

Ini menggunakan org.apache.commons.lang3.StringUtils

Laurent
sumber
-2
  1. Gunakan ini : URLEncoder.encode (permintaan, StandardCharsets.UTF_8.displayName ()); atau ini: URLEncoder.encode (permintaan, "UTF-8");
  2. Anda dapat menggunakan kode follwing.

    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
sumber
4
Tidak benar. Anda harus menyandikan nama dan nilai parameter secara terpisah. Pengkodean seluruh string kueri juga akan menyandikan =dan &pemisah, yang tidak benar.
Marquis of Lorne