URLEncoder ไม่สามารถแปลอักขระช่องว่างได้


179

ฉันคาดหวัง

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

เพื่อส่งออก:

Hello%20World

(20 คือรหัส ASCII Hex สำหรับช่องว่าง)

อย่างไรก็ตามสิ่งที่ฉันได้รับคือ:

Hello+World

ฉันใช้วิธีที่ผิดหรือเปล่า? วิธีการที่ถูกต้องที่ฉันควรใช้คืออะไร


3
ชื่อคลาสนั้นสับสนและหลายคนใช้มันผิด อย่างไรก็ตามพวกเขาไม่ได้สังเกตเห็นเพราะเมื่อมีการใช้ URLDecoder ค่าดั้งเดิมจะถูกกู้คืนดังนั้น + หรือ% 20 จึงไม่สำคัญสำหรับพวกเขา
เถียงไม่ได้

คำตอบ:


227

สิ่งนี้ทำงานตามที่คาดไว้ การURLEncoderใช้ข้อมูลจำเพาะ HTML สำหรับวิธีการเข้ารหัส URL ในรูปแบบ HTML

จากjavadocs :

คลาสนี้มีวิธีสแตติกสำหรับการแปลงสตริงเป็นรูปแบบ MIME ของแอปพลิเคชัน / x-www-form-urlencoded

และจากข้อกำหนดของ HTML :

แอพลิเคชัน / x-www ฟอร์ม urlencoded

แบบฟอร์มที่ส่งมาพร้อมกับประเภทเนื้อหานี้จะต้องได้รับการเข้ารหัสดังนี้:

  1. ชื่อและค่าควบคุมจะถูกหลีกหนี อักขระช่องว่างจะถูกแทนที่ด้วย `+ '

คุณจะต้องแทนที่มันเช่น:

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

19
นี่เป็นคำตอบที่แน่นอนแทนที่จะแทนที่ไม่มีไลบรารีของจาวาหรือฟังก์ชั่นในการทำงาน /?
co2f2e

5
เครื่องหมายบวกต้องได้รับการหลบหนีt.println(java.net.URLEncoder.encode("Hello World", "UTF-8").replace("\\+", "%20"));
จอร์จ

26
@congliu ไม่ถูกต้อง - คุณอาจจะคิดว่า replaceAll () ซึ่งทำงานกับ regex - replace () เป็นการเปลี่ยนลำดับตัวอักษรอย่างง่าย
CupawnTae

12
ใช่ @congliu วิธีที่ดีคือ: URLEncoder.encode ("Myurl", "utf-8") replaceAll ("\\ +", "% 20");
eento

9
@ClintEastwood คำตอบนี้สนับสนุนให้ใช้ java.net.URLEncoder ซึ่งไม่ได้ทำงานในสิ่งที่ถูกถามตั้งแต่แรก ดังนั้นคำตอบนี้แนะนำแพทช์โดยใช้ replace () ด้านบนของมัน ทำไมจะไม่ล่ะ? เพราะโซลูชันนี้มีแนวโน้มที่จะเกิดข้อผิดพลาดและอาจนำไปสู่คำถามที่คล้ายกัน 20 ข้อ แต่มีตัวละครที่ต่างออกไป นั่นเป็นเหตุผลที่ฉันบอกว่านี่เป็นกางเกงขาสั้น
pyb

57

พื้นที่ถูกเข้ารหัส%20ใน URL และ+ในรูปแบบที่ส่งข้อมูล (แอปพลิเคชันประเภทเนื้อหา / x-www-form-urlencoded) คุณต้องการอดีต

ใช้ฝรั่ง :

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

คุณสามารถใช้UrlEscapers :

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

อย่าใช้ String.replace สิ่งนี้จะเข้ารหัสพื้นที่เท่านั้น ใช้ห้องสมุดแทน


นอกจากนี้ยังใช้งานได้กับ Android, com.google.guava: guava: 22.0-rc1-android
Bevor

1
@Bevor rc1 หมายถึงผู้สมัครรุ่นที่ 1 นั่นคือรุ่นที่ยังไม่ได้รับการอนุมัติสำหรับรุ่นทั่วไป หากคุณสามารถทำได้ให้เลือกรุ่นที่ไม่มีสแนปชอตอัลฟ่าเบต้าและ rc เนื่องจากทราบว่ามีบั๊ก
pyb

1
@pyb ขอบคุณ แต่ฉันจะอัปเดต libs ต่อไปเมื่อโครงการของฉันจะเสร็จสิ้น หมายความว่าฉันจะไม่ไปแยงหากไม่มีรุ่นสุดท้าย และมันยังคงใช้เวลาหลายสัปดาห์ดังนั้นฉันเดาว่ามีรุ่นสุดท้ายแล้ว
Bevor

1
แต่น่าเสียดายที่ฝรั่งไม่ได้ให้ถอดรหัสซึ่งแตกต่างจากของ Apache URLCodec
Benny Bottema

26

คลาสนี้ทำการapplication/x-www-form-urlencodedเข้ารหัสชนิดมากกว่าการเข้ารหัสเปอร์เซ็นต์ดังนั้นการแทนที่ด้วย+จึงเป็นพฤติกรรมที่ถูกต้อง

จาก javadoc:

เมื่อเข้ารหัสสตริงจะใช้กฎต่อไปนี้:

  • อักขระตัวอักษรผสมตัวเลข "a" ถึง "z", "A" ถึง "Z" และ "0" ถึง "9" ยังคงเหมือนเดิม
  • อักขระพิเศษ ".", "-", "*" และ "_" ยังคงเหมือนเดิม
  • อักขระช่องว่าง "" ถูกแปลงเป็นเครื่องหมายบวก "+"
  • อักขระอื่น ๆ ทั้งหมดไม่ปลอดภัยและถูกแปลงเป็นครั้งแรกอย่างน้อยหนึ่งไบต์โดยใช้รูปแบบการเข้ารหัสบางส่วน จากนั้นแต่ละไบต์จะถูกแทนด้วยสตริงอักขระ 3 ตัว "% xy" โดยที่ xy คือการแสดงเลขฐานสิบหกสองหลักของไบต์ รูปแบบการเข้ารหัสที่แนะนำให้ใช้คือ UTF-8 อย่างไรก็ตามสำหรับเหตุผลด้านความเข้ากันได้หากไม่ได้ระบุการเข้ารหัสดังนั้นจะใช้การเข้ารหัสเริ่มต้นของแพลตฟอร์ม

@axtavt คำอธิบายที่ดี แต่ฉันยังมีคำถามอยู่ ในพื้นที่ที่ควรจะตีความว่าเป็นurl %20ดังนั้นเราต้องทำurl.replaceAll("\\+", "%20")อย่างไร และถ้าเป็นจาวาสคริปต์เราไม่ควรใช้escapeฟังก์ชั่น ใช้encodeURIหรือencodeURIComponentแทน นั่นคือสิ่งที่ฉันคิดว่า.
Alston

1
@Stallman นี่คือ Java ไม่ใช่ JavaScript ภาษาที่แตกต่างกันโดยสิ้นเชิง
Charles Wood

19

เข้ารหัสข้อความค้นหาพารามิเตอร์

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

หรือถ้าคุณต้องการหลีกเลี่ยงตัวอักษรภายใน 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;
 }

3
การใช้org.apache.commons.httpclient.util.URIUtilน่าจะเป็นวิธีที่มีประสิทธิภาพที่สุดในการแก้ปัญหา!
Stéphane Ammar

11

Hello+Worldเป็นวิธีที่เบราว์เซอร์จะเข้ารหัสข้อมูลในแบบฟอร์ม ( application/x-www-form-urlencoded) สำหรับGETคำขอและนี่คือรูปแบบที่ยอมรับโดยทั่วไปสำหรับส่วนแบบสอบถามของ URI

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

หากคุณส่งการร้องขอนี้ไปยังเซิร์ฟเล็ต Java เซิร์ฟเล็ตจะถอดรหัสค่าพารามิเตอร์ได้อย่างถูกต้อง โดยทั่วไปแล้วมีเพียงปัญหาเดียวที่นี่คือถ้าการเข้ารหัสไม่ตรงกัน

พูดอย่างเคร่งครัดไม่มีข้อกำหนดใน HTTP หรือ URI specs ที่ส่วนแบบสอบถามจะถูกเข้ารหัสโดยใช้application/x-www-form-urlencodedคู่คีย์ - ค่า ส่วนของแบบสอบถามจะต้องอยู่ในรูปแบบที่เว็บเซิร์ฟเวอร์ยอมรับ ในทางปฏิบัติสิ่งนี้ไม่น่าจะเป็นปัญหา

โดยทั่วไปจะไม่ถูกต้องที่จะใช้การเข้ารหัสนี้สำหรับส่วนอื่น ๆ ของ URI (ตัวอย่างเช่นพา ธ ) ในกรณีที่คุณควรจะใช้รูปแบบการเข้ารหัสตามที่อธิบายไว้ในRFC 3986

http://host/Hello%20World

เพิ่มเติมที่นี่


5

คำตอบอื่น ๆ ทั้งนำเสนอการเปลี่ยนสตริงคู่มือURLEncoderซึ่งอันที่จริงเข้ารหัสสำหรับรูปแบบ HTML ของ Apache ทอดทิ้ง URIUtilหรือใช้ของฝรั่งUrlEscapers อันสุดท้ายนั้นก็ดียกเว้นมันไม่มีตัวถอดรหัส

Apache คอมมอนส์แลงให้URLCodecซึ่งเข้ารหัสและถอดรหัสตามรูปแบบ URL rfc3986

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

หากคุณกำลังใช้ฤดูใบไม้ผลินี้คุณยังสามารถเลือกที่จะใช้มัน UriUtilsชั้นเช่นกัน


6
URLCodec ไม่ใช่วิธีแก้ปัญหาที่ดีที่นี่เพราะจะเข้ารหัสช่องว่างเป็นเครื่องหมายบวก แต่คำถามนั้นขอให้มีการเข้ารหัสช่องว่างเป็น% 20
davidwebster48

3

"+" ถูกต้อง หากคุณต้องการ% 20 จริงๆให้เปลี่ยน Plusses ด้วยตัวเองหลังจากนั้น


5
อาจมีปัญหาหากสตริงเริ่มต้นมีอักขระ +
Alexis Dufrenoy

17
@Traroth - ไม่จริง ตัวละครในข้อความเดิมที่ควรจะได้รับการเข้ารหัสเป็น+ %2B
Ted Hopp

บอกว่า+ถูกต้องโดยที่ไม่รู้ว่าบริบทคืออะไร downvoted อ่านคำตอบอื่น ๆ ที่ควรรู้เกี่ยวกับเวลาที่จะใช้ + หรือ% 20
Clint Eastwood

@ClintEastwood: คุณช่วยบอกฉันเกี่ยวกับ usecase ใด ๆ ที่อักขระ + สำหรับช่องว่างไม่ถูกต้องใน URL หรือไม่ ยกเว้นเมื่อมีการแยกวิเคราะห์ URL ที่ไม่สอดคล้องในด้านอื่น ๆ ?
Daniel

@ แดเนียลแน่นอนไม่พูดว่า "ไม่ถูกต้อง" แต่ไม่เหมาะสม? ใช่. เครื่องมือวิเคราะห์มักใช้พารามิเตอร์การสืบค้นด้วยค่าที่คั่นด้วยอักขระบางตัวตัวอย่างเช่น "+" ในกรณีนี้การใช้ "+" แทน "% 20" อาจผิด "+" ใช้สำหรับการเว้นช่องว่างในรูปแบบในขณะที่ "การเข้ารหัสเปอร์เซ็นต์" (การเข้ารหัส URL ที่รู้จัก) จะเน้นไปที่ URL
Clint Eastwood

3

เพิ่งได้รับการดิ้นรนกับเรื่องนี้เช่นกันบน Android จัดการเพื่อสะดุด Uri.encode (String, String) ในขณะที่เฉพาะสำหรับ Android (android.net.Uri) อาจมีประโยชน์สำหรับบางคน

การเข้ารหัสสตริงแบบคงที่ (String s, อนุญาตให้ใช้สตริง)

https://developer.android.com/reference/android/net/Uri.html#encode(java.lang.String, java.lang.String)



1

แม้ว่าจะค่อนข้างเก่า แต่ก็เป็นการตอบสนองที่รวดเร็ว:

Spring นำเสนอ UriUtils - ด้วยวิธีนี้คุณสามารถระบุวิธีการเข้ารหัสและส่วนใดที่เกี่ยวข้องกับ URI เช่น

encodePathSegment
encodePort
encodeFragment
encodeUriVariables
....

ฉันใช้มันเพราะเราใช้ Spring อยู่แล้วไม่จำเป็นต้องใช้ไลบรารีใด ๆ !



0

ฉันใช้วิธีที่ผิดหรือเปล่า? วิธีการที่ถูกต้องที่ฉันควรใช้คืออะไร

ใช่วิธีนี้ java.net.URLEncoder.encode ไม่ได้ถูกสร้างขึ้นเพื่อแปลง "" เป็น "20%" ตามข้อมูลจำเพาะ ( แหล่งที่มา )

อักขระช่องว่าง "" ถูกแปลงเป็นเครื่องหมายบวก "+"

แม้ว่านี่จะไม่ใช่วิธีที่ถูกต้องคุณสามารถแก้ไขสิ่งนี้เป็น: System.out.println(java.net.URLEncoder.encode("Hello World", "UTF-8").replaceAll("\\+", "%20"));have a nice day =)


คุณกำลังแนะนำให้ใช้วิธีการที่ไม่เพียงพอ ( URLEncoder.encode) และทำการแก้ไขโดยใช้วิธีนี้replaceAllซึ่งจะใช้ได้เฉพาะในกรณีนี้เท่านั้น ใช้คลาสและวิธีการที่ถูกต้องแทนดูคำตอบอื่น ๆ
pyb

@pyb ดูเหมือนว่าคุณไม่เข้าใจสิ่งที่ฉันเขียน ฉันไม่เคยพูดว่า "ฉันแนะนำให้ใช้" ฉันพูดว่า "คุณทำได้" โปรดอ่านและทำความเข้าใจก่อนเขียน
Pregunton

นี่คือเว็บไซต์คำถามและคำตอบไม่ใช่กระดานข้อความทั่วไปที่มีคนแชท หากคุณมีความคิดเห็นด้านข้างให้ใช้ความคิดเห็น พูดคุยอีกต่อไป? ใช้การแชท อย่าโพสต์รหัสที่คุณไม่เห็นด้วยกับคำตอบ โปรดอ่านและทำความเข้าใจกฎของเว็บไซต์นี้ก่อนบริจาคและบรรยายผู้อื่น
pyb

1
ฉันถอนกลับเพราะการแก้ปัญหาอื่น ๆ ส่วนใหญ่ให้คำแนะนำเดียวกัน ไม่มีการระบุ "กรณีเฉพาะ" เพื่อพิสูจน์วิธีการนี้ผิด การใช้ apache คอมมอนส์กับบล็อคลองหรือการอ้างอิงนั้นเป็นเรื่องที่ยุ่งยากเกินไปสำหรับวิธีที่สามารถแก้ไขได้ด้วย replaceAll อย่างมีประสิทธิภาพ
Eugene Kartoyev

-2

ใช้MyUrlEncode.URLencoding (String url, String enc)เพื่อจัดการปัญหา

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

}

9
การคิดค้นวงล้อใหม่การเพิ่มรหัสข้อผิดพลาดที่ผิดพลาดไปยังรหัสฐานเป็นการตัดสินใจที่ไม่ดี
Clint Eastwood

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.