Mã hóa URL Java của các tham số chuỗi truy vấn


710

Nói rằng tôi có một URL

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

và tôi có một truy vấn được nhập bởi người dùng, chẳng hạn như:

từ ngẫu nhiên £ 500 ngân hàng $

Tôi muốn kết quả là một URL được mã hóa chính xác:

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

Cách tốt nhất để đạt được điều này là gì? Tôi đã thử URLEncodervà tạo các đối tượng URI / URL nhưng không có đối tượng nào xuất hiện hoàn toàn đúng.


25
Bạn có ý gì bởi "không ai trong số họ đi ra hoàn toàn đúng"?
Đánh dấu Elliot

2
Tôi đã sử dụng URI.create và thay thế khoảng trắng bằng + trong chuỗi truy vấn. Tại trang khách, nó được chuyển đổi + trở lại khoảng trắng khi tôi chọn chuỗi truy vấn. Điều đó đã làm việc cho tôi.
ND27


Tại sao bạn mong đợi $ được mã hóa phần trăm?
jschnasse

Câu trả lời:


1151

URLEncoderlà con đường để đi Bạn chỉ cần lưu ý chỉ mã hóa tên và / hoặc giá trị của tham số chuỗi truy vấn riêng lẻ, không phải toàn bộ URL, chắc chắn không phải là ký tự phân tách tham số chuỗi truy vấn &cũng như ký tự phân tách giá trị tên tham số =.

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

Lưu ý rằng khoảng trắng trong tham số truy vấn được biểu thị bằng +, không %20, có giá trị hợp pháp. Các %20thường được sử dụng để đại diện cho khoảng trống trong URI bản thân (phần trước URI truy vấn chuỗi ký tự phân cách ?), không phải trong chuỗi truy vấn (phần phía sau ?).

Cũng lưu ý rằng có ba encode()phương pháp. Một không có Charsetđối số thứ hai và một đối số khác Stringlà đối số thứ hai sẽ ném ngoại lệ được kiểm tra. Một trong những không có Charsetđối số được phản đối. Không bao giờ sử dụng nó và luôn luôn chỉ định Charsetđối số. Các javadoc thậm chí rõ ràng để khuyến cáo sử dụng bảng mã UTF-8, như là bắt buộc RFC3986W3C .

Tất cả các ký tự khác đều không an toàn và trước tiên được chuyển đổi thành một hoặc nhiều byte bằng cách sử dụng một số lược đồ mã hóa. Sau đó, mỗi byte được biểu thị bằng chuỗi 3 ký tự "% xy", trong đó xy là biểu diễn thập lục phân hai chữ số của byte. Lược đồ mã hóa được khuyến nghị sử dụng là UTF-8 . Tuy nhiên, vì lý do tương thích, nếu mã hóa không được chỉ định, thì mã hóa mặc định của nền tảng được sử dụng.

Xem thêm:


Có thể có 2 loại tham số trong URL. Chuỗi truy vấn (theo sau?) Và tham số đường dẫn (Thường là một phần của chính URL). Vì vậy, những gì về các tham số đường dẫn. URLEncoder tạo + cho không gian ngay cả đối với tham số đường dẫn. Trong thực tế, nó chỉ không xử lý bất cứ điều gì ngoài chuỗi truy vấn. Ngoài ra, hành vi này không đồng bộ với các máy chủ nút js. Vì vậy, đối với tôi, lớp này là một sự lãng phí và không thể được sử dụng ngoài các tình huống rất cụ thể / đặc biệt.
sharadendu sinha

2
@sharadendusinha: như được ghi lại và trả lời, URLEncoderdành cho các application/x-www-form-urlencodedquy tắc truy vấn được mã hóa URL . Các tham số đường dẫn không phù hợp trong danh mục này. Bạn cần một bộ mã hóa URI thay thế.
BalusC

Như tôi dự đoán sẽ xảy ra ... người dùng đang bối rối vì rõ ràng vấn đề là mọi người cần mã hóa nhiều hơn chỉ là giá trị tham số. Đây là một trường hợp rất hiếm khi bạn chỉ cần mã hóa một giá trị tham số. Đó là lý do tại sao tôi cung cấp câu trả lời wiki "bối rối" của mình để giúp đỡ những người như @sharadendusinha.
Adam Gent

1
@WijaySharma: Bởi vì các ký tự dành riêng cho URL cũng sẽ được mã hóa. Bạn chỉ nên làm điều đó khi bạn muốn chuyển toàn bộ URL dưới dạng tham số truy vấn của một URL khác.
BalusC

1
"+, Không phải% 20" là những gì tôi cần nghe. Cảm ơn bạn rất nhiều.
wetjosh

173

Tôi sẽ không sử dụng URLEncoder. Bên cạnh việc được đặt tên không chính xác ( URLEncoderkhông liên quan gì đến URL), không hiệu quả (nó sử dụng StringBufferthay cho Builder và thực hiện một số điều khác chậm) Cũng rất dễ để làm hỏng nó.

Thay vào đó tôi sẽ sử dụng URIBuilderhoặc Spring's org.springframework.web.util.UriUtils.encodeQueryhoặc Commons ApacheHttpClient . Lý do là bạn phải thoát tên tham số truy vấn (nghĩa là câu trả lời của BalusC q) khác với giá trị tham số.

Nhược điểm duy nhất ở trên (mà tôi phát hiện ra một cách đau đớn) là URL không phải là một tập hợp con thực sự của URI .

Mã mẫu:

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

Vì tôi chỉ liên kết với các câu trả lời khác, tôi đã đánh dấu đây là wiki cộng đồng. Hãy chỉnh sửa.


2
Tại sao nó không liên quan gì đến URL?
Luis

15
@Luis: URLEncodernhư javadoc của nó nói có ý định mã hóa các tham số chuỗi truy vấn tuân thủ application/x-www-form-urlencodednhư được mô tả trong HTML spec: w3.org/TR/html4/interact/ . Một số người dùng thực sự nhầm lẫn / lạm dụng nó để mã hóa toàn bộ URI, giống như người trả lời hiện tại rõ ràng đã làm.
BalusC

8
@LuisSep trong URLEncoder ngắn là để mã hóa để gửi biểu mẫu. Nó không phải để trốn thoát. Nó không hoàn toàn giống với lối thoát mà bạn sẽ sử dụng để tạo URL được đưa vào trang web của mình nhưng tình cờ lại tương tự nhau đến mức mọi người lạm dụng nó. Lần duy nhất bạn nên sử dụng URLEncoder là nếu bạn viết một ứng dụng khách HTTP (và thậm chí sau đó có các tùy chọn vượt trội hơn nhiều cho mã hóa).
Adam Gent

1
@BalusC " Một số người dùng thực sự nhầm lẫn / lạm dụng nó để mã hóa toàn bộ URI, giống như người trả lời hiện tại rõ ràng đã làm. " Bạn đã giả định sai. Tôi không bao giờ nói tôi làm hỏng nó. Tôi vừa thấy những người khác đã làm điều đó, những lỗi tôi phải sửa. Phần mà tôi đã làm hỏng là lớp URL Java sẽ chấp nhận dấu ngoặc đơn nhưng không phải là lớp URI. Có rất nhiều cách để cải thiện việc xây dựng URL và không phải ai cũng xuất sắc như bạn. Tôi sẽ nói rằng hầu hết người dùng đang tìm kiếm SO cho URLEncoding có thể là " người dùng thực sự nhầm lẫn / lạm dụng " URI thoát.
Adam Gent

1
Câu hỏi không phải là về điều đó nhưng câu trả lời của bạn ngụ ý rằng.
BalusC

99

Trước tiên bạn cần tạo một URI như:

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

Sau đó chuyển đổi chuỗi Uri đó thành chuỗi ASCII:

urlStr=uri.toASCIIString();

Bây giờ chuỗi url của bạn đã được mã hóa hoàn toàn trước tiên, chúng tôi đã thực hiện mã hóa url đơn giản và sau đó chúng tôi đã chuyển đổi nó thành Chuỗi ASCII để đảm bảo không có ký tự nào bên ngoài US-ASCII còn lại trong chuỗi. Đây chính xác là cách trình duyệt làm.


7
Cảm ơn! Thật ngu ngốc khi giải pháp của bạn hoạt động, nhưng tích hợp URL.toURI()thì không.
dùng11153

2
Thật không may, điều này dường như không hoạt động với "file: ///" (ví dụ: "file: /// some / thư mục / một tệp chứa khoảng trống.html"); nó đánh bom với MalformedURLException trong "URL mới ()"; Bất kỳ ý tưởng làm thế nào để khắc phục điều này?
ZioByte

Bạn cần phải làm một cái gì đó như thế này: Chuỗi urlStr = " some / thư mục / một tệp có chứa khoảng trống.html"; URL url = URL mới (urlStr); URI uri = URI mới (url.getProtocol (), url.getUserInfo (), url.gethost (), url.getPort (), url.getPath (), url.getQuery (), url.getRef ()); urlStr = uri.toASCIIString (); urlStr.replace ("http: //", "tệp: ///"); Tôi chưa thử nó, nhưng tôi nghĩ nó sẽ hoạt động .... :)
M Abdul Sami

1
@tibi bạn chỉ cần sử dụng phương thức uri.toString () để chuyển đổi nó thành chuỗi thay vì chuỗi Ascii.
M Abdul Sami

1
API tôi đang làm việc không chấp nhận +thay thế cho khoảng trắng, nhưng chấp nhận% 20 để giải pháp này hoạt động tốt hơn BalusC, cảm ơn!
Julian Honma

35

Guava 15 hiện đã thêm một tập hợp các URL thoát đơn giản .


1
Những người chịu đựng các quy tắc thoát hiểm ngớ ngẩn tương tự như URLEncoder.
2rs2ts

3
không chắc họ có vấn đề họ phân biệt ví dụ "+" hoặc "% 20" để thoát "" (mẫu param hoặc path param) URLEncoderkhông có.
Emmanuel Touzery

1
Điều này làm việc với tôi Tôi chỉ thay thế cuộc gọi đến URLEncoder () để gọi tới UrlEscapers.urlFragmentEscaper () và nó đã hoạt động, không rõ liệu tôi có nên sử dụng UrlEscapers.urlPathSegmentEscaper () không.
Paul Taylor

2
Trên thực tế, nó không hoạt động với tôi vì không giống như URLEncoder, nó không mã hóa '+' nó để nó một mình, máy chủ giải mã '+' thành không gian trong khi nếu tôi sử dụng URLEncoder '+' được chuyển đổi thành% 2B và được giải mã chính xác thành +
Paul Taylor

2
Cập nhật liên kết: UrlEscapers
mgaert

6

Thư viện các thành phần của Apache cung cấp một tùy chọn gọn gàng để xây dựng và mã hóa các tham số truy vấn -

Với sử dụng httpComponents 4.x - URLEncodingUtils

Để sử dụng httpClient 3.x - EncodingUtil


6

Đây là một phương pháp bạn có thể sử dụng trong mã của mình để chuyển đổi chuỗi url và ánh xạ các tham số thành chuỗi url được mã hóa hợp lệ có chứa các tham số truy vấn.

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

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

Bản in

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

Có chuyện gì đang xảy ra ở đây?

1. Chia URL thành các phần cấu trúc. Sử dụngjava.net.URL cho nó.

2. Mã hóa từng phần cấu trúc đúng cách!

3. Sử dụng IDN.toASCII(putDomainNameHere)để Punycode mã hóa tên máy chủ!

4. Sử dụng java.net.URI.toASCIIString()để mã hóa phần trăm, mã hóa NFC được mã hóa - (tốt hơn sẽ là NFKC!). Để biết thêm thông tin, hãy xem: Cách mã hóa chính xác URL này

Trong một số trường hợp, nên kiểm tra xem url đã được mã hóa chưa . Đồng thời thay thế các không gian được mã hóa '+' bằng các không gian được mã hóa '% 20'.

Dưới đây là một số ví dụ cũng sẽ hoạt động đúng

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

Giải pháp vượt qua khoảng 100 trong số các testcase được cung cấp bởi Web Plattform Test .


1

Trong Android tôi sẽ sử dụng mã này:

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

Trong trường hợp Urilà mộtandroid.net.Uri


10
Điều này không sử dụng API Java tiêu chuẩn. Vì vậy, vui lòng chỉ định thư viện được sử dụng.
rmuller

1

Trong trường hợp của tôi, tôi chỉ cần truyền toàn bộ url và chỉ mã hóa giá trị của từng tham số. Tôi đã không tìm thấy một mã phổ biến để làm điều đó (!!) vì vậy tôi đã tạo ra phương thức nhỏ này để thực hiện công việc:

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

Nó sử dụng org.apache.commons.lang3.StringUtils


-2
  1. Sử dụng URL URLEncoder.encode này (truy vấn, StandardCharsets.UTF_8.displayName ()); hoặc này: URLEncoder.encode (truy vấn, "UTF-8");
  2. Bạn có thể sử dụng mã 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);

4
Không chính xác. Bạn phải mã hóa các tên và giá trị tham số riêng biệt. Mã hóa toàn bộ chuỗi truy vấn cũng sẽ mã hóa các dấu phân cách =&không đúng.
Hầu tước Lorne
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.