All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.hutool.core.util.URLUtil Maven / Gradle / Ivy

There is a newer version: 5.8.33
Show newest version
package cn.hutool.core.util;

import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.net.URLEncodeUtil;
import cn.hutool.core.net.url.UrlQuery;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.jar.JarFile;

/**
 * URL(Uniform Resource Locator)统一资源定位符相关工具类
 *
 * 

* 统一资源定位符,描述了一台特定服务器上某资源的特定位置。 *

* URL组成: *
 *   协议://主机名[:端口]/ 路径/[:参数] [?查询]#Fragment
 *   protocol :// hostname[:port] / path / [:parameters][?query]#fragment
 * 
* * @author xiaoleilu */ public class URLUtil extends URLEncodeUtil { /** * 针对ClassPath路径的伪协议前缀(兼容Spring): "classpath:" */ public static final String CLASSPATH_URL_PREFIX = "classpath:"; /** * URL 前缀表示文件: "file:" */ public static final String FILE_URL_PREFIX = "file:"; /** * URL 前缀表示jar: "jar:" */ public static final String JAR_URL_PREFIX = "jar:"; /** * URL 前缀表示war: "war:" */ public static final String WAR_URL_PREFIX = "war:"; /** * URL 协议表示文件: "file" */ public static final String URL_PROTOCOL_FILE = "file"; /** * URL 协议表示Jar文件: "jar" */ public static final String URL_PROTOCOL_JAR = "jar"; /** * URL 协议表示zip文件: "zip" */ public static final String URL_PROTOCOL_ZIP = "zip"; /** * URL 协议表示WebSphere文件: "wsjar" */ public static final String URL_PROTOCOL_WSJAR = "wsjar"; /** * URL 协议表示JBoss zip文件: "vfszip" */ public static final String URL_PROTOCOL_VFSZIP = "vfszip"; /** * URL 协议表示JBoss文件: "vfsfile" */ public static final String URL_PROTOCOL_VFSFILE = "vfsfile"; /** * URL 协议表示JBoss VFS资源: "vfs" */ public static final String URL_PROTOCOL_VFS = "vfs"; /** * Jar路径以及内部文件路径的分界符: "!/" */ public static final String JAR_URL_SEPARATOR = "!/"; /** * WAR路径及内部文件路径分界符 */ public static final String WAR_URL_SEPARATOR = "*/"; /** * 将{@link URI}转换为{@link URL} * * @param uri {@link URI} * @return URL对象 * @see URI#toURL() * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 * @since 5.7.21 */ public static URL url(URI uri) throws UtilException{ if(null == uri){ return null; } try { return uri.toURL(); } catch (MalformedURLException e) { throw new UtilException(e); } } /** * 通过一个字符串形式的URL地址创建URL对象 * * @param url URL * @return URL对象 */ public static URL url(String url) { return url(url, null); } /** * 通过一个字符串形式的URL地址创建URL对象 * * @param url URL * @param handler {@link URLStreamHandler} * @return URL对象 * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { if(null == url){ return null; } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { url = url.substring(CLASSPATH_URL_PREFIX.length()); return ClassLoaderUtil.getClassLoader().getResource(url); } try { return new URL(null, url, handler); } catch (MalformedURLException e) { // 尝试文件路径 try { return new File(url).toURI().toURL(); } catch (MalformedURLException ex2) { throw new UtilException(e); } } } /** * 获取string协议的URL,类似于string:///xxxxx * * @param content 正文 * @return URL * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { if(null == content){ return null; } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } /** * 将URL字符串转换为URL对象,并做必要验证 * * @param urlStr URL字符串 * @return URL * @since 4.1.9 */ public static URL toUrlForHttp(String urlStr) { return toUrlForHttp(urlStr, null); } /** * 将URL字符串转换为URL对象,并做必要验证 * * @param urlStr URL字符串 * @param handler {@link URLStreamHandler} * @return URL * @since 4.1.9 */ public static URL toUrlForHttp(String urlStr, URLStreamHandler handler) { Assert.notBlank(urlStr, "Url is blank !"); // 编码空白符,防止空格引起的请求异常 urlStr = encodeBlank(urlStr); try { return new URL(null, urlStr, handler); } catch (MalformedURLException e) { throw new UtilException(e); } } /** * 单独编码URL中的空白符,空白符编码为%20 * * @param urlStr URL字符串 * @return 编码后的字符串 * @since 4.5.14 */ public static String encodeBlank(CharSequence urlStr) { if (urlStr == null) { return null; } int len = urlStr.length(); final StringBuilder sb = new StringBuilder(len); char c; for (int i = 0; i < len; i++) { c = urlStr.charAt(i); if (CharUtil.isBlankChar(c)) { sb.append("%20"); } else { sb.append(c); } } return sb.toString(); } /** * 获得URL * * @param pathBaseClassLoader 相对路径(相对于classes) * @return URL * @see ResourceUtil#getResource(String) */ public static URL getURL(String pathBaseClassLoader) { return ResourceUtil.getResource(pathBaseClassLoader); } /** * 获得URL * * @param path 相对给定 class所在的路径 * @param clazz 指定class * @return URL * @see ResourceUtil#getResource(String, Class) */ public static URL getURL(String path, Class clazz) { return ResourceUtil.getResource(path, clazz); } /** * 获得URL,常用于使用绝对路径时的情况 * * @param file URL对应的文件对象 * @return URL * @throws UtilException MalformedURLException */ public static URL getURL(File file) { Assert.notNull(file, "File is null !"); try { return file.toURI().toURL(); } catch (MalformedURLException e) { throw new UtilException(e, "Error occured when get URL!"); } } /** * 获得URL,常用于使用绝对路径时的情况 * * @param files URL对应的文件对象 * @return URL * @throws UtilException MalformedURLException */ public static URL[] getURLs(File... files) { final URL[] urls = new URL[files.length]; try { for (int i = 0; i < files.length; i++) { urls[i] = files[i].toURI().toURL(); } } catch (MalformedURLException e) { throw new UtilException(e, "Error occured when get URL!"); } return urls; } /** * 获取URL中域名部分,只保留URL中的协议(Protocol)、Host,其它为null。 * * @param url URL * @return 域名的URI * @since 4.6.9 */ public static URI getHost(URL url) { if (null == url) { return null; } try { return new URI(url.getProtocol(), url.getHost(), null, null); } catch (URISyntaxException e) { throw new UtilException(e); } } /** * 补全相对路径 * * @param baseUrl 基准URL * @param relativePath 相对URL * @return 相对路径 * @throws UtilException MalformedURLException */ public static String completeUrl(String baseUrl, String relativePath) { baseUrl = normalize(baseUrl, false); if (StrUtil.isBlank(baseUrl)) { return null; } try { final URL absoluteUrl = new URL(baseUrl); final URL parseUrl = new URL(absoluteUrl, relativePath); return parseUrl.toString(); } catch (MalformedURLException e) { throw new UtilException(e); } } //-------------------------------------------------------------------------- decode /** * 解码URL
* 将%开头的16进制表示的内容解码。 * * @param url URL * @return 解码后的URL * @throws UtilException UnsupportedEncodingException * @since 3.1.2 */ public static String decode(String url) throws UtilException { return decode(url, CharsetUtil.UTF_8); } /** * 解码application/x-www-form-urlencoded字符
* 将%开头的16进制表示的内容解码。
* 规则见:https://url.spec.whatwg.org/#urlencoded-parsing * * @param content 被解码内容 * @param charset 编码,null表示不解码 * @return 编码后的字符 * @since 4.4.1 */ public static String decode(String content, Charset charset) { return URLDecoder.decode(content, charset); } /** * 解码application/x-www-form-urlencoded字符
* 将%开头的16进制表示的内容解码。 * * @param content 被解码内容 * @param charset 编码,null表示不解码 * @param isPlusToSpace 是否+转换为空格 * @return 编码后的字符 * @since 5.6.3 */ public static String decode(String content, Charset charset, boolean isPlusToSpace) { return URLDecoder.decode(content, charset, isPlusToSpace); } /** * 解码application/x-www-form-urlencoded字符
* 将%开头的16进制表示的内容解码。 * * @param content URL * @param charset 编码 * @return 解码后的URL * @throws UtilException UnsupportedEncodingException */ public static String decode(String content, String charset) throws UtilException { return decode(content, StrUtil.isEmpty(charset) ? null : CharsetUtil.charset(charset)); } /** * 获得path部分
* * @param uriStr URI路径 * @return path * @throws UtilException 包装URISyntaxException */ public static String getPath(String uriStr) { return toURI(uriStr).getPath(); } /** * 从URL对象中获取不被编码的路径Path
* 对于本地路径,URL对象的getPath方法对于包含中文或空格时会被编码,导致本读路径读取错误。
* 此方法将URL转为URI后获取路径用于解决路径被编码的问题 * * @param url {@link URL} * @return 路径 * @since 3.0.8 */ public static String getDecodedPath(URL url) { if (null == url) { return null; } String path = null; try { // URL对象的getPath方法对于包含中文或空格的问题 path = toURI(url).getPath(); } catch (UtilException e) { // ignore } return (null != path) ? path : url.getPath(); } /** * 转URL为URI * * @param url URL * @return URI * @throws UtilException 包装URISyntaxException */ public static URI toURI(URL url) throws UtilException { return toURI(url, false); } /** * 转URL为URI * * @param url URL * @param isEncode 是否编码参数中的特殊字符(默认UTF-8编码) * @return URI * @throws UtilException 包装URISyntaxException * @since 4.6.9 */ public static URI toURI(URL url, boolean isEncode) throws UtilException { if (null == url) { return null; } return toURI(url.toString(), isEncode); } /** * 转字符串为URI * * @param location 字符串路径 * @return URI * @throws UtilException 包装URISyntaxException */ public static URI toURI(String location) throws UtilException { return toURI(location, false); } /** * 转字符串为URI * * @param location 字符串路径 * @param isEncode 是否编码参数中的特殊字符(默认UTF-8编码) * @return URI * @throws UtilException 包装URISyntaxException * @since 4.6.9 */ public static URI toURI(String location, boolean isEncode) throws UtilException { if (isEncode) { location = encode(location); } try { return new URI(StrUtil.trim(location)); } catch (URISyntaxException e) { throw new UtilException(e); } } /** * 提供的URL是否为文件
* 文件协议包括"file", "vfsfile" 或 "vfs". * * @param url {@link URL} * @return 是否为文件 * @since 3.0.9 */ public static boolean isFileURL(URL url) { Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // URL_PROTOCOL_VFS.equals(protocol)); } /** * 提供的URL是否为jar包URL 协议包括: "jar", "zip", "vfszip" 或 "wsjar". * * @param url {@link URL} * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // URL_PROTOCOL_VFSZIP.equals(protocol) || // URL_PROTOCOL_WSJAR.equals(protocol)); } /** * 提供的URL是否为Jar文件URL 判断依据为file协议且扩展名为.jar * * @param url the URL to check * @return whether the URL has been identified as a JAR file URL * @since 4.1 */ public static boolean isJarFileURL(URL url) { Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } /** * 从URL中获取流 * * @param url {@link URL} * @return InputStream流 * @since 3.2.1 */ public static InputStream getStream(URL url) { Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { throw new IORuntimeException(e); } } /** * 获得Reader * * @param url {@link URL} * @param charset 编码 * @return {@link BufferedReader} * @since 3.2.1 */ public static BufferedReader getReader(URL url, Charset charset) { return IoUtil.getReader(getStream(url), charset); } /** * 从URL中获取JarFile * * @param url URL * @return JarFile * @since 4.1.5 */ public static JarFile getJarFile(URL url) { try { JarURLConnection urlConnection = (JarURLConnection) url.openConnection(); return urlConnection.getJarFile(); } catch (IOException e) { throw new IORuntimeException(e); } } /** * 标准化URL字符串,包括: * *
    *
  1. 自动补齐“http://”头
  2. *
  3. 去除开头的\或者/
  4. *
  5. 替换\为/
  6. *
* * @param url URL字符串 * @return 标准化后的URL字符串 */ public static String normalize(String url) { return normalize(url, false); } /** * 标准化URL字符串,包括: * *
    *
  1. 自动补齐“http://”头
  2. *
  3. 去除开头的\或者/
  4. *
  5. 替换\为/
  6. *
* * @param url URL字符串 * @param isEncodePath 是否对URL中path部分的中文和特殊字符做转义(不包括 http:, /和域名部分) * @return 标准化后的URL字符串 * @since 4.4.1 */ public static String normalize(String url, boolean isEncodePath) { return normalize(url, isEncodePath, false); } /** * 标准化URL字符串,包括: * *
    *
  1. 自动补齐“http://”头
  2. *
  3. 去除开头的\或者/
  4. *
  5. 替换\为/
  6. *
  7. 如果replaceSlash为true,则替换多个/为一个
  8. *
* * @param url URL字符串 * @param isEncodePath 是否对URL中path部分的中文和特殊字符做转义(不包括 http:, /和域名部分) * @param replaceSlash 是否替换url body中的 // * @return 标准化后的URL字符串 * @since 5.5.5 */ public static String normalize(String url, boolean isEncodePath, boolean replaceSlash) { if (StrUtil.isBlank(url)) { return url; } final int sepIndex = url.indexOf("://"); String protocol; String body; if (sepIndex > 0) { protocol = StrUtil.subPre(url, sepIndex + 3); body = StrUtil.subSuf(url, sepIndex + 3); } else { protocol = "http://"; body = url; } final int paramsSepIndex = StrUtil.indexOf(body, '?'); String params = null; if (paramsSepIndex > 0) { params = StrUtil.subSuf(body, paramsSepIndex); body = StrUtil.subPre(body, paramsSepIndex); } if (StrUtil.isNotEmpty(body)) { // 去除开头的\或者/ //noinspection ConstantConditions body = body.replaceAll("^[\\\\/]+", StrUtil.EMPTY); // 替换\为/ body = body.replace("\\", "/"); if (replaceSlash) { //issue#I25MZL@Gitee,双斜杠在URL中是允许存在的,默认不做替换 body = body.replaceAll("//+", "/"); } } final int pathSepIndex = StrUtil.indexOf(body, '/'); String domain = body; String path = null; if (pathSepIndex > 0) { domain = StrUtil.subPre(body, pathSepIndex); path = StrUtil.subSuf(body, pathSepIndex); } if (isEncodePath) { path = encode(path); } return protocol + domain + StrUtil.nullToEmpty(path) + StrUtil.nullToEmpty(params); } /** * 将Map形式的Form表单数据转换为Url参数形式
* paramMap中如果key为空(null和"")会被忽略,如果value为null,会被做为空白符("")
* 会自动url编码键和值 * *
	 * key1=v1&key2=&key3=v3
	 * 
* * @param paramMap 表单数据 * @param charset 编码,编码为null表示不编码 * @return url参数 */ public static String buildQuery(Map paramMap, Charset charset) { return UrlQuery.of(paramMap).build(charset); } /** * 获取指定URL对应资源的内容长度,对于Http,其长度使用Content-Length头决定。 * * @param url URL * @return 内容长度,未知返回-1 * @throws IORuntimeException IO异常 * @since 5.3.4 */ public static long getContentLength(URL url) throws IORuntimeException { if (null == url) { return -1; } URLConnection conn = null; try { conn = url.openConnection(); return conn.getContentLengthLong(); } catch (IOException e) { throw new IORuntimeException(e); } finally { if (conn instanceof HttpURLConnection) { ((HttpURLConnection) conn).disconnect(); } } } /** * Data URI Scheme封装,数据格式为Base64。data URI scheme 允许我们使用内联(inline-code)的方式在网页中包含数据,
* 目的是将一些小的数据,直接嵌入到网页中,从而不用再从外部文件载入。常用于将图片嵌入网页。 * *

* Data URI的格式规范: *

	 *     data:[<mime type>][;charset=<charset>][;<encoding>],<encoded data>
	 * 
* * @param mimeType 可选项(null表示无),数据类型(image/png、text/plain等) * @param data 编码后的数据 * @return Data URI字符串 * @since 5.3.11 */ public static String getDataUriBase64(String mimeType, String data) { return getDataUri(mimeType, null, "base64", data); } /** * Data URI Scheme封装。data URI scheme 允许我们使用内联(inline-code)的方式在网页中包含数据,
* 目的是将一些小的数据,直接嵌入到网页中,从而不用再从外部文件载入。常用于将图片嵌入网页。 * *

* Data URI的格式规范: *

	 *     data:[<mime type>][;charset=<charset>][;<encoding>],<encoded data>
	 * 
* * @param mimeType 可选项(null表示无),数据类型(image/png、text/plain等) * @param encoding 数据编码方式(US-ASCII,BASE64等) * @param data 编码后的数据 * @return Data URI字符串 * @since 5.3.6 */ public static String getDataUri(String mimeType, String encoding, String data) { return getDataUri(mimeType, null, encoding, data); } /** * Data URI Scheme封装。data URI scheme 允许我们使用内联(inline-code)的方式在网页中包含数据,
* 目的是将一些小的数据,直接嵌入到网页中,从而不用再从外部文件载入。常用于将图片嵌入网页。 * *

* Data URI的格式规范: *

	 *     data:[<mime type>][;charset=<charset>][;<encoding>],<encoded data>
	 * 
* * @param mimeType 可选项(null表示无),数据类型(image/png、text/plain等) * @param charset 可选项(null表示无),源文本的字符集编码方式 * @param encoding 数据编码方式(US-ASCII,BASE64等) * @param data 编码后的数据 * @return Data URI字符串 * @since 5.3.6 */ public static String getDataUri(String mimeType, Charset charset, String encoding, String data) { final StringBuilder builder = StrUtil.builder("data:"); if (StrUtil.isNotBlank(mimeType)) { builder.append(mimeType); } if (null != charset) { builder.append(";charset=").append(charset.name()); } if (StrUtil.isNotBlank(encoding)) { builder.append(';').append(encoding); } builder.append(',').append(data); return builder.toString(); } /** * 获取URL对应数据长度 *
    *
  • 如果URL为文件,转换为文件获取文件长度。
  • *
  • 其它情况获取{@link URLConnection#getContentLengthLong()}
  • *
* * @param url URL * @return 长度 * @since 6.0.0 */ public static long size(final URL url) { if (URLUtil.isFileURL(url)) { // 如果资源以独立文件形式存在,尝试获取文件长度 final File file = FileUtil.file(url); final long length = file.length(); if (length == 0L && !file.exists()) { throw new IORuntimeException("File not exist or size is zero!"); } return length; } else { // 如果资源打在jar包中或来自网络,使用网络请求长度 // issue#3226, 来自Spring的AbstractFileResolvingResource try { final URLConnection con = url.openConnection(); useCachesIfNecessary(con); if (con instanceof HttpURLConnection) { final HttpURLConnection httpCon = (HttpURLConnection) con; httpCon.setRequestMethod("HEAD"); } return con.getContentLengthLong(); } catch (final IOException e) { throw new IORuntimeException(e); } } } /** * 如果连接为JNLP方式,则打开缓存 * * @param con {@link URLConnection} * @since 6.0.0 */ public static void useCachesIfNecessary(final URLConnection con) { con.setUseCaches(con.getClass().getSimpleName().startsWith("JNLP")); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy