首页 文章

Java中的HTTP URL地址编码

提问于
浏览
339

我的Java独立应用程序从用户获取一个URL(指向文件),我需要点击它并下载它 . 我面临的问题是我无法正确编码HTTP URL地址......

例:

URL:  http://search.barnesandnoble.com/booksearch/first book.pdf

java.net.URLEncoder.encode(url.toString(), "ISO-8859-1");

回报我:

http%3A%2F%2Fsearch.barnesandnoble.com%2Fbooksearch%2Ffirst+book.pdf

但是,我想要的是

http://search.barnesandnoble.com/booksearch/first%20book.pdf

(空间由%20取代)

我猜 URLEncoder 不是为了编码HTTP URL而设计的...... JavaDoc说"Utility class for HTML form encoding" ......有没有其他办法可以做到这一点?

24 回答

  • 283

    也许可以在org.springframework.web.util中尝试UriUtils

    UriUtils.encodeUri(input, "UTF-8")
    
  • 8

    我阅读以前的答案来编写我自己的方法,因为我无法使用前面的答案的解决方案正常工作,它看起来不错,但如果你能找到不适合这个的URL,请告诉我 .

    public static URL convertToURLEscapingIllegalCharacters(String toEscape) throws MalformedURLException, URISyntaxException {
                URL url = new URL(toEscape);
                URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
                //if a % is included in the toEscape string, it will be re-encoded to %25 and we don't want re-encoding, just encoding
                return new URL(uri.toString().replace("%25", "%"));
    }
    
  • 4

    我将在这里针对Android用户添加一个建议 . 您可以这样做,避免必须获得任何外部库 . 此外,在上面的一些答案中提出的所有搜索/替换字符解决方案都是危险的,应该避免 .

    尝试一下:

    String urlStr = "http://abc.dev.domain.com/0007AC/ads/800x480 15sec h.264.mp4";
    URL url = new URL(urlStr);
    URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
    url = uri.toURL();
    

    你可以看到,在这个特定的URL中,我需要对这些空间进行编码,以便我可以将它用于请求 .

    这利用了Android类中可用的一些功能 . 首先,URL类可以将url分解为其正确的组件,因此您无需进行任何字符串搜索/替换工作 . 其次,当您通过组件而不是单个字符串构造URI时,此方法利用了正确转义组件的URI类功能 .

    这种方法的优点在于,您可以使用任何有效的url字符串并使其工作,而无需自己了解任何特殊知识 .

  • -5

    如果有人不想在项目中添加依赖项,这些函数可能会有所帮助 .

    我们将URL的“路径”部分传递到此处 . 您可能不希望将完整的URL作为参数传递(查询字符串需要不同的转义等) .

    /**
     * Percent-encodes a string so it's suitable for use in a URL Path (not a query string / form encode, which uses + for spaces, etc)
     */
    public static String percentEncode(String encodeMe) {
        if (encodeMe == null) {
            return "";
        }
        String encoded = encodeMe.replace("%", "%25");
        encoded = encoded.replace(" ", "%20");
        encoded = encoded.replace("!", "%21");
        encoded = encoded.replace("#", "%23");
        encoded = encoded.replace("$", "%24");
        encoded = encoded.replace("&", "%26");
        encoded = encoded.replace("'", "%27");
        encoded = encoded.replace("(", "%28");
        encoded = encoded.replace(")", "%29");
        encoded = encoded.replace("*", "%2A");
        encoded = encoded.replace("+", "%2B");
        encoded = encoded.replace(",", "%2C");
        encoded = encoded.replace("/", "%2F");
        encoded = encoded.replace(":", "%3A");
        encoded = encoded.replace(";", "%3B");
        encoded = encoded.replace("=", "%3D");
        encoded = encoded.replace("?", "%3F");
        encoded = encoded.replace("@", "%40");
        encoded = encoded.replace("[", "%5B");
        encoded = encoded.replace("]", "%5D");
        return encoded;
    }
    
    /**
     * Percent-decodes a string, such as used in a URL Path (not a query string / form encode, which uses + for spaces, etc)
     */
    public static String percentDecode(String encodeMe) {
        if (encodeMe == null) {
            return "";
        }
        String decoded = encodeMe.replace("%21", "!");
        decoded = decoded.replace("%20", " ");
        decoded = decoded.replace("%23", "#");
        decoded = decoded.replace("%24", "$");
        decoded = decoded.replace("%26", "&");
        decoded = decoded.replace("%27", "'");
        decoded = decoded.replace("%28", "(");
        decoded = decoded.replace("%29", ")");
        decoded = decoded.replace("%2A", "*");
        decoded = decoded.replace("%2B", "+");
        decoded = decoded.replace("%2C", ",");
        decoded = decoded.replace("%2F", "/");
        decoded = decoded.replace("%3A", ":");
        decoded = decoded.replace("%3B", ";");
        decoded = decoded.replace("%3D", "=");
        decoded = decoded.replace("%3F", "?");
        decoded = decoded.replace("%40", "@");
        decoded = decoded.replace("%5B", "[");
        decoded = decoded.replace("%5D", "]");
        decoded = decoded.replace("%25", "%");
        return decoded;
    }
    

    并测试:

    @Test
    public void testPercentEncode_Decode() {
        assertEquals("", percentDecode(percentEncode(null)));
        assertEquals("", percentDecode(percentEncode("")));
    
        assertEquals("!", percentDecode(percentEncode("!")));
        assertEquals("#", percentDecode(percentEncode("#")));
        assertEquals("$", percentDecode(percentEncode("$")));
        assertEquals("@", percentDecode(percentEncode("@")));
        assertEquals("&", percentDecode(percentEncode("&")));
        assertEquals("'", percentDecode(percentEncode("'")));
        assertEquals("(", percentDecode(percentEncode("(")));
        assertEquals(")", percentDecode(percentEncode(")")));
        assertEquals("*", percentDecode(percentEncode("*")));
        assertEquals("+", percentDecode(percentEncode("+")));
        assertEquals(",", percentDecode(percentEncode(",")));
        assertEquals("/", percentDecode(percentEncode("/")));
        assertEquals(":", percentDecode(percentEncode(":")));
        assertEquals(";", percentDecode(percentEncode(";")));
    
        assertEquals("=", percentDecode(percentEncode("=")));
        assertEquals("?", percentDecode(percentEncode("?")));
        assertEquals("@", percentDecode(percentEncode("@")));
        assertEquals("[", percentDecode(percentEncode("[")));
        assertEquals("]", percentDecode(percentEncode("]")));
        assertEquals(" ", percentDecode(percentEncode(" ")));
    
        // Get a little complex
        assertEquals("[]]", percentDecode(percentEncode("[]]")));
        assertEquals("a=d%*", percentDecode(percentEncode("a=d%*")));
        assertEquals(")  (", percentDecode(percentEncode(")  (")));
        assertEquals("%21%20%2A%20%27%20%28%20%25%20%29%20%3B%20%3A%20%40%20%26%20%3D%20%2B%20%24%20%2C%20%2F%20%3F%20%23%20%5B%20%5D%20%25",
                        percentEncode("! * ' ( % ) ; : @ & = + $ , / ? # [ ] %"));
        assertEquals("! * ' ( % ) ; : @ & = + $ , / ? # [ ] %", percentDecode(
                        "%21%20%2A%20%27%20%28%20%25%20%29%20%3B%20%3A%20%40%20%26%20%3D%20%2B%20%24%20%2C%20%2F%20%3F%20%23%20%5B%20%5D%20%25"));
    
        assertEquals("%23456", percentDecode(percentEncode("%23456")));
    
    }
    
  • 1

    不幸的是, org.apache.commons.httpclient.util.URIUtil 已被弃用, replacement org.apache.commons.codec.net.URLCodec 的编码适用于表单帖子,而不是实际的URL 's. So I had to write my own function, which does a single component (not suitable for entire query strings that have ?'和&s)

    public static String encodeURLComponent(final String s)
    {
      if (s == null)
      {
        return "";
      }
    
      final StringBuilder sb = new StringBuilder();
    
      try
      {
        for (int i = 0; i < s.length(); i++)
        {
          final char c = s.charAt(i);
    
          if (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) ||
              ((c >= '0') && (c <= '9')) ||
              (c == '-') ||  (c == '.')  || (c == '_') || (c == '~'))
          {
            sb.append(c);
          }
          else
          {
            final byte[] bytes = ("" + c).getBytes("UTF-8");
    
            for (byte b : bytes)
            {
              sb.append('%');
    
              int upper = (((int) b) >> 4) & 0xf;
              sb.append(Integer.toHexString(upper).toUpperCase(Locale.US));
    
              int lower = ((int) b) & 0xf;
              sb.append(Integer.toHexString(lower).toUpperCase(Locale.US));
            }
          }
        }
    
        return sb.toString();
      }
      catch (UnsupportedEncodingException uee)
      {
        throw new RuntimeException("UTF-8 unsupported!?", uee);
      }
    }
    
  • 3

    您还可以使用 GUAVA 和路径escaper: UrlEscapers.urlFragmentEscaper().escape(relativePath)

  • -2

    我有同样的问题 . 通过unsing解决了这个问题:

    android.net.Uri.encode(urlString, ":/");
    

    它对字符串进行编码,但跳过“:”和“/” .

  • 35

    如果您的URL中有编码的“/”(%2F),则仍然存在问题 .

    RFC 3986 - 第2.2节说:“如果URI组件的数据与保留字符作为分隔符的目的冲突,那么冲突数据必须在形成URI之前进行百分比编码 . ” (RFC 3986 - 第2.2节)

    但是Tomcat存在一个问题:

    http://tomcat.apache.org/security-6.html - 修正了Apache Tomcat 6.0.10重要:目录遍历CVE-2007-0450 Tomcat允许'','%2F'和'%5C'[ . ..] . 已将以下Java系统属性添加到Tomcat,以提供对URL中路径分隔符处理的额外控制(两个选项都默认为false):org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH:true | false org.apache .catalina.connector.CoyoteAdapter.ALLOW_BACKSLASH:true | false由于无法保证所有URL都由Tomcat处理,因为它们在代理服务器中,因此应始终保护Tomcat,就好像没有使用代理限制上下文访问一样 . 影响:6.0.0-6.0.9

    因此,如果您有一个带有%2F字符的URL,Tomcat将返回:“400无效的URI:noSlash”

    您可以在Tomcat启动脚本中切换错误修复:

    set JAVA_OPTS=%JAVA_OPTS% %LOGGING_CONFIG%   -Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true
    
  • -3

    是的URL编码将编码该字符串,以便它可以在URL中正确传递到最终目的地 . 例如,你没有http://stackoverflow.com?url=http://yyy.com . UrlEncoding参数将修复该参数值 .

    所以我有两个选择:

    • 您是否可以访问与域名分开的路径?如果是这样,您可以简单地将UrlEncode路径 . 但是,如果不是这种情况,那么选项2可能适合您 .

    • 获取commons-httpclient-3.1 . 这有一个类URIUtil:

    System.out.println(URIUtil.encodePath(“http://example.com/x y ", " ISO-8859-1”));

    这将输出您正在寻找的内容,因为它只会编码URI的路径部分 .

    仅供参考,您需要使用commons-codec和commons-logging来使此方法在运行时工作 .

  • 0

    请注意,上面的大部分答案都是不正确的 .

    URLEncoder 类,尽管是名字,但不是必须在这里 . 令人遗憾的是,Sun这个课程非常令人讨厌 . URLEncoder 用于将数据作为参数传递,而不是用于对URL本身进行编码 .

    换句话说, "http://search.barnesandnoble.com/booksearch/first book.pdf" 是URL . 参数例如是 "http://search.barnesandnoble.com/booksearch/first book.pdf?parameter1=this&param2=that" . 参数是您将使用的 URLEncoder .

    以下两个例子突出了两者之间的差异 .

    根据HTTP标准,以下内容产生错误的参数 . 请注意,&符号(&)和加号()编码不正确 .

    uri = new URI("http", null, "www.google.com", 80, 
    "/help/me/book name+me/", "MY CRZY QUERY! +&+ :)", null);
    
    // URI: http://www.google.com:80/help/me/book%20name+me/?MY%20CRZY%20QUERY!%20+&+%20:)
    

    以下将生成正确的参数,并正确查询编码 . 请注意空格,&符号和加号 .

    uri = new URI("http", null, "www.google.com", 80, "/help/me/book name+me/", URLEncoder.encode("MY CRZY QUERY! +&+ :)", "UTF-8"), null);
    
    // URI: http://www.google.com:80/help/me/book%20name+me/?MY+CRZY+QUERY%2521+%252B%2526%252B+%253A%2529
    
  • 7

    我已经创建了一个新项目来帮助构建HTTP URL . 该库将自动对路径段和查询参数进行URL编码 .

    您可以在https://github.com/Widen/urlbuilder查看源代码并下载二进制文件

    此问题中的示例网址:

    new UrlBuilder("search.barnesandnoble.com", "booksearch/first book.pdf").toString()
    

    产生

    http://search.barnesandnoble.com/booksearch/first%20book.pdf

  • 1

    我开发了一个用于此目的的库:galimatias . 它以与Web浏览器相同的方式解析URL . 也就是说,如果URL在浏览器中工作,它将被galimatias正确解析 .

    在这种情况下:

    // Parse
    io.mola.galimatias.URL.parse(
        "http://search.barnesandnoble.com/booksearch/first book.pdf"
    ).toString()
    

    会给你: http://search.barnesandnoble.com/booksearch/first%20book.pdf . 当然这是最简单的情况,但它可以用于任何事情,超越 java.net.URI .

    你可以在以下网址查看:https://github.com/smola/galimatias

  • 26

    java.net.URI课程可以提供帮助;在您找到的URL文档中

    注意,URI类确实在某些情况下执行其组件字段的转义 . 管理URL编码和解码的推荐方法是使用URI

    使用具有多个参数的构造函数之一,例如:

    URI uri = new URI(
        "http", 
        "search.barnesandnoble.com", 
        "/booksearch/first book.pdf",
        null);
    URL url = uri.toURL();
    //or String request = uri.toString();
    

    (URI的单参数构造函数不会转义非法字符)


    只有非法字符才会被上面的代码转义 - 它不会转义非ASCII字符(请参阅fatih的评论) .
    toASCIIString 方法可用于仅使用US-ASCII字符获取String:

    URI uri = new URI(
        "http", 
        "search.barnesandnoble.com", 
        "/booksearch/é",
        null);
    String request = uri.toASCIIString();
    

    对于具有 http://www.google.com/ig/api?weather=São Paulo 之类的查询的URL,请使用构造函数的5参数版本:

    URI uri = new URI(
            "http", 
            "www.google.com", 
            "/ig/api",
            "weather=São Paulo",
            null);
    String request = uri.toASCIIString();
    
  • 74

    你可以使用这样的功能 . 根据您的需要完成并修改它:

    /**
         * Encode URL (except :, /, ?, &, =, ... characters)
         * @param url to encode
         * @param encodingCharset url encoding charset
         * @return encoded URL
         * @throws UnsupportedEncodingException
         */
        public static String encodeUrl (String url, String encodingCharset) throws UnsupportedEncodingException{
                return new URLCodec().encode(url, encodingCharset).replace("%3A", ":").replace("%2F", "/").replace("%3F", "?").replace("%3D", "=").replace("%26", "&");
        }
    

    使用示例:

    String urlToEncode = ""http://www.growup.com/folder/intérieur-à_vendre?o=4";
    Utils.encodeUrl (urlToEncode , "UTF-8")
    

    其结果是:http://www.growup.com/folder/int%C3%A9rieur-%C3%A0_vendre?o=4

  • 85

    我把上面的内容改成了一下 . 我首先喜欢积极的逻辑,我认为HashSet可能比其他一些选项提供更好的性能,比如搜索String . 虽然,我不确定自动装箱惩罚是否值得,但如果编译器优化ASCII字符,那么装箱的成本将会很低 .

    /***
     * Replaces any character not specifically unreserved to an equivalent 
     * percent sequence.
     * @param s
     * @return
     */
    public static String encodeURIcomponent(String s)
    {
        StringBuilder o = new StringBuilder();
        for (char ch : s.toCharArray()) {
            if (isSafe(ch)) {
                o.append(ch);
            }
            else {
                o.append('%');
                o.append(toHex(ch / 16));
                o.append(toHex(ch % 16));
            }
        }
        return o.toString();
    }
    
    private static char toHex(int ch)
    {
        return (char)(ch < 10 ? '0' + ch : 'A' + ch - 10);
    }
    
    // https://tools.ietf.org/html/rfc3986#section-2.3
    public static final HashSet<Character> UnreservedChars = new HashSet<Character>(Arrays.asList(
            'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
            'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
            '0','1','2','3','4','5','6','7','8','9',
            '-','_','.','~'));
    public static boolean isSafe(char ch)
    {
        return UnreservedChars.contains(ch);
    }
    
  • 7

    String url =“”http://search.barnesandnoble.com/booksearch/;

    这将是不变的我猜,只有文件名动态改变,所以得到文件名

    字符串文件; //获取文件名

    String urlEnc = url fileName.replace(“”,“%20”);

  • 2

    怎么样:

    public String UrlEncode(String in_){

    String retVal = "";
    
    try {
        retVal = URLEncoder.encode(in_, "UTF8");
    } catch (UnsupportedEncodingException ex) {
        Log.get().exception(Log.Level.Error, "urlEncode ", ex);
    }
    
    return retVal;
    

    }

  • 49

    挑剔:根据定义,包含空白字符的字符串不是URI . 所以你要找的是实现Section 2.1 of RFC 3986中定义的URI转义的代码 .

  • 11

    如果您有URL,则可以将url.toString()传递给此方法 . 首先解码,以避免双重编码(例如,编码空格导致%20并编码百分号导致%25,因此双重编码将空格转换为%2520) . 然后,使用上面解释的URI,添加URL的所有部分(这样就不会删除查询参数) .

    public URL convertToURLEscapingIllegalCharacters(String string){
        try {
            String decodedURL = URLDecoder.decode(string, "UTF-8");
            URL url = new URL(decodedURL);
            URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef()); 
            return uri.toURL(); 
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }
    
  • 11

    除了Carlos Heuberger的回复:如果需要不同于默认值(80),则应使用7参数构造函数:

    URI uri = new URI(
            "http",
            null, // this is for userInfo
            "www.google.com",
            8080, // port number as int
            "/ig/api",
            "weather=São Paulo",
            null);
    String request = uri.toASCIIString();
    
  • 3

    正如您遗憾地发现的那样,URLEncoding可以很好地编码HTTP URL . 您传入的字符串“http://search.barnesandnoble.com/booksearch/first book.pdf”已正确完整地编码为URL编码形式 . 你可以传递你在URL中作为参数返回的整个长串gobbledigook,它可以被解码回你传入的字符串 .

    听起来你想要做一些与将整个URL作为参数传递一点点不同的东西 . 根据我收集的内容,您尝试创建一个看起来像“http://search.barnesandnoble.com/booksearch/whateverTheUserPassesIn ". The only thing that you need to encode is the " whateverTheUserPassesIn”位的搜索URL,所以您可能需要做的就是这样:

    String url = "http://search.barnesandnoble.com/booksearch/" + 
           URLEncoder.encode(userInput,"UTF-8");
    

    那会产生一些对你更有效的东西 .

  • -7

    我同意马特的观点 . 实际上,我从未在教程中看到过很好的解释,但有一个问题是如何对URL路径进行编码,而一个非常不同的是如何编码附加到URL的参数(查询部分,后面的“? “符号) . 他们使用类似的编码,但不一样 .

    特别适用于空白字符的编码 . URL路径需要将其编码为%20,而查询部分允许%20以及“”符号 . 最好的想法是使用Web浏览器自行测试我们的Web服务器 .

    对于这两种情况,我 ALWAYS 将编码 COMPONENT BY COMPONENT ,而不是整个字符串 . 实际上URLEncoder允许用于查询部分 . 对于路径部分,您可以使用类URI,尽管在这种情况下它会要求整个字符串,而不是单个组件 .

    无论如何,我相信 the best way to avoid these problems is to use a personal non-conflictive design. 怎么样?例如,我永远不会使用除-Z,A-Z,0-9和_之外的其他字符来命名目录或参数 . 这样,唯一的需要是对每个参数的值进行编码,因为它可能来自用户输入,并且使用的字符是未知的 .

  • 7

    使用以下标准Java解决方案(传递Web Plattform Tests提供的大约100个测试用例):

    1. 将URL拆分为结构件 . 使用 java.net.URL .

    2. 正确编码每个结构部件!

    3. 使用 IDN.toASCII(putDomainNameHere)Punycode编码主机名!

    4. 使用 java.net.URI.toASCIIString() 进行百分比编码,NFC编码的unicode - (更好的是NFKC!) .

    在这里找到更多:https://stackoverflow.com/a/49796882/1485527

  • 0

    我开发的解决方案比任何其他解决方案更稳定:

    public class URLParamEncoder {
    
        public static String encode(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;
        }
    
    }
    

相关问题