Java中的HTTP URL地址编码

我的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 

(空格replace为%20)

我猜URLEncoder不是devise用于编码HTTP URL的… JavaDoc说“HTML表单编码的工具类”…有没有其他的方式来做到这一点?

java.net.URI类可以帮助; 在你find的URL的文档中

请注意,在某些情况下,URI类会执行其组件字段的转义。 pipe理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的单参数构造函数不能转义非法字符)


编辑:添加完全合格的类名称,以避免与其他URI类混淆(如apaches httpclient)


编辑2:
只有非法字符被上面的代码转义 – 它不能转义非ASCII字符(参见fatih的注释)。
toASCIIString方法只能用于US-ASCII字符来获取string:

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

编辑3:
对于像http://www.google.com/ig/api?weather=São Paulo这样的查询http://www.google.com/ig/api?weather=São Paulo ,请使用构造函数的5参数版本:

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

请注意,上面的大部分答案都是错误的。

URLEncoder类,尽pipe是名称,不是什么需要在这里。 不幸的是,Sun很恼火地给这个class级命名。 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 

-Matt

我要在这里添加一个针对Android用户的build议。 你可以做到这一点,避免得到任何外部库。 此外,上述某些答案中提出的所有search/replace字符解决scheme都是危险的,应该避免。

试试这个:

 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类中的一些function。 首先,URL类可以将URL分解成适当的组件,因此不需要任何stringsearch/replace工作。 其次,当你通过组件构造一个URI而不是从一个单独的string时,这种方法利用了正确转义组件的URI类function。

这种方法的优点是,你可以采取任何有效的urlstring,并使其工作,而不需要你自己的任何特殊的知识。

我开发的一个解决scheme,比任何其他更稳定:

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

如果你有一个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; } } 

是的,URL编码将会对该string进行编码,以便在URL中正确传递到最终目的地。 例如,你不能有http://stackoverflow.com?url=http://yyy.com 。 UrlEncoding参数将修复该参数值。

所以我有两个select:

  1. 你有权访问与域名分开的path吗? 如果是这样,你可能只需要UrlEncode的path。 但是,如果情况并非如此,则选项2可能适用于您。

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

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

这将输出正是你正在寻找,因为它只会编码URI的path部分。

仅供参考,您需要commons-codec和commons-logging才能在运行时使用此方法。

Nitpicking:根据定义,包含空白字符的string不是URI。 所以你要找的是实现RFC 3986中第2.1节定义的URI转义的代码。

不幸的是, org.apache.commons.httpclient.util.URIUtil不推荐使用, replacement org.apache.commons.codec.net.URLCodec编码适用于表单post,而不是实际的URL。 所以我不得不编写自己的函数,这个函数做了一个单独的组件(不适用于包含?和&的整个查询string)

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

URLEncoding可以很好地编码HTTP URL,正如你不幸发现的那样。 您传入的string“ http://search.barnesandnoble.com/booksearch/first book.pdf”已正确完整地编码为URL编码forms。 你可以传递一整串长长的gobbledigook作为URL中的一个参数,它可以被解码成你传入的string。

这听起来像你想要做一些有点不同,比传递整个URL作为参数。 从我所收集的内容中,您正尝试创build一个类似于“ http://search.barnesandnoble.com/booksearch/whateverTheUserPassesIn ”的searchurl。 唯一需要编码的是“whateverTheUserPassesIn”位,所以你可能需要做的就是这样的:

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

这应该会产生更有效的东西给你。

如果您的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中path分隔符处理的额外控制(两个选项默认为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 

我读了以前的答案,写我自己的方法,因为我不能有正确的工作使用以前的答案的解决scheme,它看起来不错,但如果你可以findURL不起作用,请让我知道。

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

我同意马特。 事实上,我从来没有在教程中看到过这样的解释,但是一个问题是如何对URLpath进行编码,而另一个非常不同的是如何编码附加到URL的参数(查询部分,后面的“ “符号)。 他们使用类似的编码,但不一样。

特别是对于空白字符的编码。 URLpath需要编码为%20,而查询部分允许%20和“+”符号。 最好的想法是用Web浏览器对我们的Web服务器进行testing。

对于这两种情况,我总是会编码COMPONENT BY COMPONENT ,而不是整个string。 的确URLEncoder允许查询部分。 对于path部分,您可以使用类的URI,但在这种情况下,它要求整个string,而不是一个单一的组件。

无论如何,我相信避免这些问题的最好方法是使用个人无冲突的devise。 怎么样? 例如,我永远不会使用除AZ,AZ,0-9和_之外的其他字符来命名目录或参数。 这样,唯一的需要是对每个参数的值进行编码,因为它可能来自用户input并且所使用的字符是未知的。

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

我们将URL的“path”部分传递到此处。 你可能不想传递完整的URL作为参数(查询string需要不同的转义等)。

 /** 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("!", "%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("%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; } 

并testing:

 @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("]"))); // Get a little complex assertEquals("[]]", percentDecode(percentEncode("[]]"))); assertEquals("a=d%*", percentDecode(percentEncode("a=d%*"))); assertEquals("%21 %2A %27 %28 %25 %29 %3B %3A %40 %26 %3D %2B %24 %2C %2F %3F %23 %5B %5D %25", percentEncode("! * ' ( % ) ; : @ & = + $ , / ? # [ ] %")); assertEquals("! * ' ( % ) ; : @ & = + $ , / ? # [ ] %", percentDecode("%21 %2A %27 %28 %25 %29 %3B %3A %40 %26 %3D %2B %24 %2C %2F %3F %23 %5B %5D %25")); assertEquals("%23456", percentDecode(percentEncode("%23456"))); } 

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

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

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

您也可以使用GUAVA和path助手: UrlEscapers.urlFragmentEscaper().escape(relativePath)

我有同样的问题。 通过释放解决这个问题:

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

它编码string,但跳过“:”和“/”。

我创build了一个新的项目来帮助构buildHTTP URL。 该库将自动对path段和查询参数进行URL编码。

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

这个问题中的示例url:

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

产生

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

我开发了一个服务于这个目的的图书馆: galimatias 。 它以与浏览器相同的方式parsingURL。 也就是说,如果一个URL在浏览器中工作,它将被加里马提亚(galimatias)正确parsing。

在这种情况下:

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

会给你: http://search.barnesandnoble.com/booksearch/first%20book.pdf : http://search.barnesandnoble.com/booksearch/first%20book.pdf 。 当然,这是最简单的情况,但它可以在java.net.URI以外的任何地方使用。

你可以在https://github.com/smola/galimatias查看;

你可以使用这样的function。 完成并修改它以满足您的需求:

 /** * 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

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

这将是恒定的,我猜只有文件名改变dyamically所以得到文件名

string文件名 //获取文件名

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

怎么样:

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; 

}