JAVA网络编程个人笔记 第五章 URL和URI
URL和URI
URL/URI简介
URI与URL的定义
URL:统一资源定位符
URI:统一资源标志符
- URI与URL都是定位资源位置的,就是表示这个资源的位置信息,就像经纬度一样可以表示你在世界的那个角落
- URI是一种宽泛的含义更广的定义,而URL则是URI的一个子集,就是说URL是URI的一部分
- 每个URL都是URI,但不是每个URI都是URL
- URL是可以直接操作的,但是URI并不行
- 在java.net.URI,只能看到他的一些属性,他只是表示一个表示,但是你没有办法通过URI获取这个对象的流
- 但是URL不一样。java.net.URL该类提供方法openConnection(),通过该方法我们可以通过IO流操作它
URL的语法
URL与URI很像,两者的格式几乎差不多,但是我们接触的还是URL比较多,就以URL为例说明
URL提供了一种访问定位因特网上任意资源的手段,但是这些资源可以通过不同的方法来访问
不管怎么样,他都基本由9个部分组成:
scheme
获取资源使用的协议,例如:http、ftp等,没有默认值
user:password
用户名与密码,这个是一个特殊的存在,一般访问ftp时会用到,她显示的表明了访问资源的用户名和密码。但是这个可以不写,不写的话可能会让你输入用户名密码
host
主机,访问的那台主机,有时候可以是IP,有时候是主机名,例如www.baidu.com
port
端口,访问主机时的端口,如果http访问默认80,可以省略
path
通过host:port我们能找到主机,但是主机上文件很多,通过path则可能定位具体文件
Params
主要作用就是向服务器提供额外的参数,用来表示本次请求的一些特性
例如ftp传输模式有两种,二进制和文本,type=d
query
通过get方式请求的参数
例如:www.qiandu.com/index.html?username=dgh&passwd=123
Fragment
当html页面比较长时,我们通常会将其分为好几段,#1就可以快速定位到某一段
代码实例
URI的组成测试
import java.net.*;
public class uriTest {
public static void main(String[] args) throws Exception{
URI uri = new URI("http://www.qiandu.com:8080/goods/index.html?username=dgh&passwd=123#j2se");
System.out.println("scheme :"+uri.getScheme());
System.out.println("schemeSpecificPart :"+uri.getSchemeSpecificPart());
System.out.println("Authority :"+uri.getAuthority());
System.out.println("host :"+uri.getHost());
System.out.println("port :"+uri.getPort());
System.out.println("path :"+uri.getPath());
System.out.println("query :"+uri.getQuery());
System.out.println("fragment :"+uri.getFragment());
}
}
URL测试组成
import java.net.*;
public class urlTest {
public static void main(String[] args) throws Exception{
URL url = new URL("http://www.qiandu.com:8080/goods/index.html?username=dgh&passwd=123#j2se");
System.out.println("URL :"+url.toString());
System.out.println("protocol :"+url.getProtocol());
System.out.println("Authority :"+url.getAuthority());
System.out.println("file name :"+url.getFile());
System.out.println("host :"+url.getHost());
System.out.println("port :"+url.getPort());
System.out.println("path :"+url.getPath());
System.out.println("query :"+url.getQuery());
System.out.println("default port :"+url.getDefaultPort());
System.out.println("ref :"+url.getRef());
}
}
UserInfo属性
其实关于资源定位的时候还有一种写法,就是在主机名前面有类似于xx@的东西,其实这种表示就:用户@主机名或者用户@IP。@前面表示登录主机的用户,也就是UserInfo了
代码实例
import java.net.*;
public class userInfo {
public static void main(String[] args) throws Exception{
URL url = new URL("https://denggonghai@www.qiandu.com");
System.out.println("UserInfo:"+url.getUserInfo());
}
}
URL类
URL类简介
java.net.URL类是JAVA对URL的抽象,是一个final类,不能被继承。实际上URL类使用了策略设计模式,通过不同的协议处理器来扩展功能
URL类是不可变的,对象构建完成后,字段就无法改变,因此能够保证线程安全
URL类提供了多种构造器,用于在不同情况下创建对象
创建对象
从字符串构建URL对象
public URL(string url)throws MalformedURLException
根据一个字符串形式的绝对URL构建URL对象。如果构造成功,说明URL的协议得到了支持。
失败则会抛出MalformedURLException异常
代码实例
import java.net.*;
public class testProtocol {
public static void testProtocol(String url){
try{
URL u = new URL(url);
System.out.println(u.getProtocol()+"is supported.");
}catch(MalformedURLException e) {
String protocol = url.substring(0, url.indexOf(":"));
System.out.println(protocol + " is supported.");
}
}
public static void main(String[] args) {
testProtocol("http://www.baidu.com");
}
}
由组成部分构建URL对象
public URL(String protocol,String host,String file)throws MalformedURLEception
public URL(String protocol,String host,int port,String file)throws MalformedURLException
- 两个方法都是利用URL的组成部分来构建URL对象,区别在于有无端口号。如果不设置端口号,构造器会将端口设置为-1,即使用协议的默认端口
- 此处的protocol和host只要正常填写内容即可,如“http”、“www.csdn.net”,而file需要在开头加上/.file包括路径、文件名和可选片段标识符
根据相对URL构建URL对象
public URL(URL context,String spec)throws MalformedURLException
这个构造器根据基础URL和相对URL构建URL对象。简单来讲,就是用spec(相对URL)中的信息替换掉context(基础URL)中的对应部分
代码实现
import java.net.*;
public class protocolHostTest {
public static void main(String[] args) throws Exception{
URL url = new URL("http","osu.ppy.sh","/s/557145");
URL url2 = new URL(url,"625822");
URL url3 = new URL(url,"/p/beatmaplist");
System.out.println(url);
System.out.println(url2);
System.out.println(url3);
}
}
从URL对象获取信息
- public final InputStream openStream() throws java.io.IOException
- public URLConnection openConnection() throws java.io.IOException
- public URLConnection openConnection(Proxy proxy) throws java.io.IOException
- public final Object getContent() throws java.io.IOException
- public final Object getContent(Class[] classes)throws java.io.IOException
openStream()方法
openStream()方法是最直接的,它能够打开到指定URL的连接,并返回一个InputStream,从这个InputStream获得的数据是URL引用的原始内容,因此可能是ASCII文本,HTML、二进制图片数据等
代码实现
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.*;
public class openStreamDemo {
public static void showWebSourceCode(String url){
try{
URL u = new URL(url);
try(BufferedReader reader = new BufferedReader(new InputStreamReader(new BufferedInputStream(u.openStream()),"UTF-8"))){
String line;
while((line = reader.readLine())!= null){
System.out.println(line);
}
}catch (IOException e){
e.printStackTrace();
}
}catch(MalformedURLException e){
System.out.println("Fail to connect to the url.");
}
}
public static void main(String[] args) {
showWebSourceCode("https://www.baidu.com/");
}
}
记得url最前面加上http://
openConnection方法
- openConnection方法为指定的URL打开一个Socket,并返回一个URLConnec对象
- 一个URLConn对象表示一个网络资源的打开的连接。如果需要和服务器通信,这个方法是最好的。
- 重载版本可以指定一个代理服务器
getContent()方法
getContent()方法获取由URL引用的数据,尝试由它建立某种类型的对象。返回的对象可能是InputStream、HttpURLConnection、sun.awt.image.URLImageSource等等
getContent(Class[] classes)接受一个class数组,用于选择最合适的返回类型。它会从数组的第一个开始判断,如果能够返回该类型则返回该类型,否则判断下一个,以此类推。注意返回值依然是Object,需要用instanceof判断实际类型
分解URL
URL分为5个部分:
- 模式(协议)
- 授权结构(主机)
- 路径
- 查询字符串
- 片段标识符(ref)
URL类的Object方法
equals()方法
URL类的equals方法会判断两个URL的各个部分,包括查询字符串和片段标识符,只有全部相等才返回true。需要注意两点:
- equals()方法会尝试用DNS查询主机,来判断两个主机是否相同,这导致equals()方法可能阻塞。因此,应避免将URL存储在依赖equals()方法的数据结构里(如hashMap中)
- equals不会具体比较URL指向的资源
sameFile()方法
与equals()方法相似,但sameFile()方法不考虑片段标识符
toString()方法
URL类的toString()方法会返回一个包含绝对URL的字符串
toURI()方法
将URL对象转换为对应的URI对象,返回一个对应的URI对象
URI类
URI类简介
URI类与URL类的主要区别如下:
- URI类只有关于资源的标识和对URI进行解析的方法,没有关于获取URI指向的资源的方法。这是因为URI只能用于标识一个资源,不一定能确定资源在网络上的位置
- URI对象可以表示相对URI,URL类存储之前会将其绝对化
- 相比于URL类,URI类与相关规范更加一致
- 简而言之,如果你需要获取资源,那么应当使用URL类;而如果你只需要标识资源,那么用URI类更加合适
创建URI对象
URI类同样提供了一系列的构造器,用于根据不同条件创建对象,和URL类在很多方面类似
- public URI(String str)throws URISyntaxException
- public URI(String scheme,String ssp,String fragment)throws URISyntaxExeption
- public URI(String scheme,String host,String path,String fragment)throws URISyntaxException
- public URI(String scheme,String authority,String path,String query,String fragment)throws URISyntaxException
- public URI(String scheme,String userInfo,String host,int port,String path,String query,String fragment)throws URISynataxException
URI(String str)
public URI(String str)throws URISyntaxException
根据字符串创建对象
URI(String scheme,String ssp,String fragment)
public URI(String scheme,String ssp,String fragment)throws URISyntaxExeption
根据模式、模式特定部分、片段标识符创建对象
注意:模式可以为null,此时创建的就是相对URI
注意:URI类会对特殊字符自动进行编码
根据传入的不同部分构建URI。参数含义和URL的基本一致,此处不再赘述。唯一需要注意的是,URI类会对特殊字符自动进行编码,但是路径内的“/”会被视作分隔符而不参与编码,此时只能手动处理“/”
所有的构造器在传入的参数格式与URI规范不符时都会抛出一个URISyntaxException异常,这个异常不是运行时异常,因此必须在编译时就进行处理
与URL类相似,URI类也提供了一系列的get方法,用于获取URI的各个组成部分。由于绝大多数部分和URL类相同,此处不再赘述
和URL类的几点区别:
- isAbsolute():判断是否绝对URI,若是则返回true。判断依据是URI的模式(scheme)是否为null
- isOpaque():判断是否是不透明的,若是则返回true。不透明的URI只能得到模式、模式特定部分和片段标识符。层次URI是透明的
- URI类的许多getxxx方法都一个getRawXXX版本,区别在于getXXX返回解码后的结果,而getRawXXX返回未解码的结果
解析相对URI
- 相对URI转绝对URI
- public URI resolve(URI uri)
- public URI resolve(String uri)
- 绝对URI转相对URI
- public URI relativize(URI uri)
代码实现
import java.net.*;
public class URIabsolute {
public static void main(String[] args) throws URISyntaxException{
URI absolute = new URI("https://osu.ppy.sh/s/452625");
URI base = new URI("https://osu.ppy.sh/");
URI relative = base.relativize(absolute);
System.out.println(relative);
System.out.println(base.resolve(relative));
}
}
相对URI
import java.net.URISyntaxException;
import java.net.*;
public class URIbase {
public static void main(String[] args) throws URISyntaxException {
URI base = new URI("https://osu.ppy.sh/");
URI relative = new URI("s/452625");
URI resolved = base.resolve(relative);
System.out.println(resolved);
System.out.println(resolved.resolve(new URI("653523")));
}
}
URI替换
import java.net.URISyntaxException;
import java.net.*;
public class URIrelative {
public static void main(String[] args) throws URISyntaxException {
URI base = new URI("https://osu.ppy.sh/a/b/c/");
String relative = "s/452625";
URI resolved = base.resolve(relative);
System.out.println(resolved);
System.out.println(resolved.resolve("/d/653523"));
}
}
URI相等性和比较
equals()方法
- URL的equals()方法不是单纯地比较字符串。两个URI要相等,首先它们必须都是层次的或都是不透明的
- 比较模式(scheme)和主机(host)时不区分大小写(HTTP和http相同,www.baidu.com和www.BAIDU.com相同)
hashCode()方法
hashCode()方法的相等性与equals()一致
toString()方法
返回URI未编码的字符串形式(尽管实际上经过了编码)。可以使用toASCIIString方法返回URI的编码形式
代码实现
import java.net.*;
public class toString {
public static void main(String[] args) throws URISyntaxException{
URI uri = new URI("https://www.example.com/s/新年快乐");
System.out.println(uri.toString());
System.out.println(uri.toASCIIString());
}
}
Comparable接口
基于字符串的比较,他的排序规则如下:
- 如果模式不同则比较模式,不考虑大小写
- 若模式相同则观察两个URI是否有层次,一般认为层次URI不小于不透明URI
- 若都是不透明URI则根据模式特定部分排序;若模式特定部分也相同则根据片段排序
- 若都是层次URI,则根据授权机构排序。授权机构本身按照用户信息、主机和端口排序,比较主机时不区分大小写;如果模式和授权机构都相等,则根据路径排序;路径相等则根据查询字符串排序;查询字符串相等则根据片段排序;URI只能和URI比较,否则会抛出ClassCastException异常
URLEncoder与URLDecoder
前面提到过,URL需要对特殊字符进行编码,而中国工作在构建他们的对象时不会自动完成
java提供了URLEncoder与URLDecoder,用于编码和解码
URIEncoder
public static String encode(String s,String enc)throws UnsupportedEncodingException
提供了这样一个方法进行编码,第一个参数是需要编码的原始字符串,第二个是字符集,一般都用UTF-8
这个方法的问题在于,它会进行过度编码,实例如下:
String str = URLEncoder.encode("https://www.baidu.com","UTF-8");
System.out.println(str);
可以看到,它把本不需要编码的“/”和“:”都进行了编码。这个问题没有通解,只能通过手工编码将字符串各个部分分别编码,之后再组合起来
Decoder
public static String decode(String s,String enc)throws UnsupportedEncodingException
- 参数化意义和URLEncoder相同,第一个参数是需要编码的原始字符串,第二个是字符集,一般都用UTF-8
- 使用这个方法的时候不需要拆分,只需要将整个字符串即可,这是因为它不会对非转义字符做任何处理,只会把+号替换为空格,以及将%XX转换为对应的字符
通过GET方法与服务器端程序通信
通过GET方法与服务端程序通信
- URL类使得和使用GET方法的服务器端程序通信非常容易。需要做的事情只有根据网站的表单构造查询字符串,然后拼接到URL上即可。所有查询字符串的名和值必须通过编码
- 关于然后获取网站表单所需的参数,可以利用浏览器的调试功能找到对应表单的源代码,从中可以获知相关信息
访问口令保护的网站
有些网站使用了HTTP认证(BASIC认证等),因此在访问之前需要通过认证。