写了这么久 Java 代码,对网络编程的了解还停留在简单使用网络请求框架的阶段。
说起网络编程的知识点,好像大部分的东西也都知道,但是好像就知道一个专有名词的意思。比如说:
“URL 是什么?”
“URL 就是统一资源定位器”
“嗯?完了?”
“它就是一个专有名词啊”
“...”
再比如说:
“你了解 http 吗?”
“哦,这个呀,我知道,超文本传输协议呀”
“嗯?还有吗?”
“...”
“那 https 呢?”
“这就是一个安全的超文本传输协议呀”
"..."
网络编程嘛,说白了就是和服务器的一次通话/交互资源,说起来其实很简单,用起来好像也挺简单的。
以下是使用 PostMan 自动生成的一次网络请求代码,和我们项目中的代码基本上差不多。
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://gank.io/api/history/content/2/1")
.get()
.addHeader("cache-control", "no-cache")
.addHeader("postman-token", "c9b415ce-4a35-097c-1a53-b12d526d8d97")
.build();
Response response = client.newCall(request).execute();复制代码
这个过程真的很简单了,因为这特么是已经封装好的OkHttp,那么问题来了,这些参数到底都表示什么意思?参数都是以什么样的格式上传给服务器的?服务器怎么响应的?服务器响应的数据是什么格式?缓存、Token、性能优化、异常怎么处理?
今天,就和大家一起来探讨这些问题。
在探讨这些问题之前,得先做一些准备工作,首先我们来学习以下几个知识点。
- Internet 地址
- URL 和 URI
- HTTP
- URLConnection
- HttpURLConnection
Internet 地址
本来标题是想用 IP 的,查了一下发现IP 是Internet Protocol(网络协议)的缩写,这个话题太大了,我们简单了解一下 Internet 地址即可。
我们将所有连接到 Internet 的设备看做一个节点(node),计算机节点称为主机(host)。每个节点或主机都由至少一个唯一的数来标识,这称为 Internet 地址,也就是我们所说的 IP 地址。
IPv4和 IPv6
就是4个字节长度的ip 地址和6个字节长度的 ip 地址。
IPv6目前还没在世界范围内推广,大家记住因为 IPv4地址紧张不够用才引入 IPv6的概念即可。
比如 61.135.169.121就是百度首页服务器的 ip 地址。
域名解析 DNS
刚刚我说了,61.135.169.121是百度服务器的地址,但是我们访问百度的时候用的是 www.baidu.com,这个网址叫域名,通常访问的时候使用这个域名进行访问,访问的过程中会去 DNS 服务器查找域名“www.baidu.com”所对应的 IP 地址“61.135.169.121”,然后通过 IP 地址访问服务器。
InetAddress
java.net.InetAddress 类是 Java 对 IP地址(包括 v4和 v6)的高层表示。大多数其他网络都要用到这个类,包括 Socket、ServerSocket、URP、DatagramSocket、DatagramPacket 等。一般来说,它包括一个主机名和一个 IP 地址。
具体 api 就不细讲了,反正和 IP地址相关的问题,都可以来找这个类。
子类实现有 Inet4Address 和 Inet6Address。
大多数情况下,我们不用考虑 v4还是 v6,因为 v6还没普及,从代码层面来说,用 v4还是 v6,Java 已经帮我们处理好了,作为应用层(OSI 模型的最高层)的我们不需要关心这些。
URL 和 URI
在上面我们了街道如何通过主机名和 IP 地址确定主机在 Internet 的地址(其实就是调用 InetAddress 的 api)。这里我们继续学习如何确定资源的地址。
HTML 是一个超文本标记语言,因为它提供了一种方法,可以知道 URL 标识的其他文档的链接。URL 可以唯一地标识一个资源在 Internet 上的位置。URL 是最常见的 URI(统一资源标识符)。URI 可以由资源的网络位置来标识资源,也可以由资源的名字、编号或其他特性来标识。
URL 类是 Java 程序在网络上定位和获取数据最简单的方法。你不需要考虑所使用协议的细节,也不用担心如何与吴福气通信。只要把 URL 告诉 Java,它就会为你获得数据。
URI 和 URL 的区别
统一资源标志符URI就是在某一规则下能把一个资源独一无二地标识出来。拿人做例子,假设这个世界上所有人的名字都不能重复,那么名字就是URI的一个实例,通过名字这个字符串就可以标识出唯一的一个人。
现实当中名字当然是会重复的,所以身份证号才是URI,通过身份证号能让我们能且仅能确定一个人。
那统一资源定位符URL是什么呢。也拿人做例子然后跟HTTP的URL做类比,就可以有:动物住址协议://地球/中国/浙江省/杭州市/西湖区/某大学/14号宿舍楼/525号寝/张三.人可以看到,这个字符串同样标识出了唯一的一个人,起到了URI的作用,所以URL是URI的子集。
URL是以描述人的位置来唯一确定一个人的。在上文我们用身份证号也可以唯一确定一个人。对于这个在杭州的张三,我们也可以用:身份证号:123456789来标识他。所以不论是用定位的方式还是用编号的方式,我们都可以唯一确定一个人,都是URI的一种实现,而URL就是用定位的方式实现的URI。回到Web上,假设所有的Html文档都有唯一的编号,记作html:xxxxx,xxxxx是一串数字,即Html文档的身份证号码,这个能唯一标识一个Html文档,那么这个号码就是一个URI。而URL则通过描述是哪个主机上哪个路径上的文件来唯一确定一个资源,也就是定位的方式来实现的URI。对于现在网址我更倾向于叫它URL,毕竟它提供了资源的位置信息,如果有一天网址通过号码来标识变成了http://741236985.html
,那感觉叫成URI更为合适,不过这样子的话还得想办法找到这个资源咯…
以上,摘抄自知乎。接下来,我们来看看 URL 和 URI 的 api 吧
URI
以下,是从URI 源码里面摘选出来的九个重要字段
private transient String scheme;//方案
private transient String fragment;//特定于方案的部分
private transient String authority;//授权
private transient String userInfo;//用户信息
private transient String host;//主机
private transient int port = -1;//端口
private transient String path;//路径
private transient String query;//查询
private volatile transient String schemeSpecificPart;//片段复制代码
可能大家还是不太理解 URI 是啥,其实就是将一个String地址分解成特定的属性。看看下图就能明白了~
URL
直接看图吧~~
URL 的 api 基本和 URI 差不多,大多都是构造方法和各个字段的get、set 方法。但是有个方法我们需要注意一下。
- public URLConnection openConnection()
URL 可以根据我们传的参数直接创建一个通信链接,我们来简单看看是怎么创建的。
public URLConnection openConnection() throws java.io.IOException {
return handler.openConnection(this);
}复制代码
在 URL 类里面找到 handler 的创建代码如下:
if (handler == null) {
try {
if (protocol.equals("file")) {
handler = (URLStreamHandler)Class.forName("sun.net.www.protocol.file.Handler").newInstance();
} else if (protocol.equals("ftp")) {
handler = (URLStreamHandler)Class.forName("sun.net.www.protocol.ftp.Handler").newInstance();
} else if (protocol.equals("jar")) {
handler = (URLStreamHandler)Class.forName("sun.net.www.protocol.jar.Handler").newInstance();
} else if (protocol.equals("http")) {
handler = (URLStreamHandler)Class.forName("com.android.okhttp.HttpHandler").newInstance();
} else if (protocol.equals("https")) {
handler = (URLStreamHandler)Class.forName("com.android.okhttp.HttpsHandler").newInstance();
}
} catch (Exception e) {
throw new AssertionError(e);
}
}复制代码
这里根据 protocol 字段,创建了不同的 handler,根据逻辑我们可以看到创建了 HttpHandler,使用 HttpHandler 的 openConnection()方法创建了一个sun.net.www.protocol.http.HttpURLConnection 对象。
HTTP
超文本传输协议(Hypertext Transfer Protocol,HTTP)是一个标准,定义了客户端如何与服务器对话,以及数据如何从服务器传回客户端。尽管通常认为 HTTP是一种传输 HTML 文件以及文件中内嵌图片的方法,但实际上 HTTP 是一个数据格式。它可以用来传输 TIFF 图片、Word 文档、exe 文件等。这里我们将深入后台,了解当我们子安地址栏输入http://www.google.com
并按下回车键时到底发生了什么。
HTTP 协议
HTTP 是客户端和服务器之间通信的标准协议。HTTP 指定客户端与服务器如何建立连接、客户端如何从服务器请求数据,服务器如何响应请求,以及最后如何关闭连接。HTTP 连接使用 TCP/IP 来传输数据。对于从客户端到服务器的每一个请求,都有4个步骤:
1.默认情况下,客户端在端口80打开与服务器的一个 TCP 连接,URL 中还可以指定其他端口。
2.客户端向服务器发生消息,请求指定路径上的资源。这个请求包括一个首部,可选的(取决于请求的性质)还可以有一个空行,后面是这个请求的数据。
3.服务器向客户端发生响应,响应以响应码开头,后面是包含数据的首部、一个空行以及所请求的文档或错误消息。
4.服务器关闭连接。
这是基本 HTTP1.0过程,在 HTTP1.1以及以后的版本中,可以通过一个 TCP 连接连续发送多个请求和响应。也就是说,在第一步和第四步直接,第二步和第三步可以反复多次。另外,在 HTTP1.1中,请求和响应可以分为多个块发送。这样有更好的扩展性。
HTTP Request
每个请求和响应都有同样的基本形式:一个首部行、一个包含元素数据的 HTTP 首部、一个空行,然后是一个消息体。
格式如下:
- 请求行,分为三部分:请求方法、请求地址、协议版本
例如 GET/image/logo.gif HTTP/1.1,表示从/image目录下请求 logo.gif这个文件
- 请求头,用于传递一些附加信息
常见通用请求 Header
名称 | 作用 |
---|---|
Content-Type | 请求体的类型,如:text/plain,application/json |
Accept | 说明接收的类型,可以多个值,用","隔开 |
Content-Length | 请求体长度 |
Content-Encoding | 请求体的编码格式,如 gzip、deflate |
Accept-Encoding | 告知对方我方接受的 Content-Encoding |
Catche-Control | 取值一般为 no-catche、max-age=xx(xx 为整数,表示自愿缓存 xx 秒) |
- 空行 请求头结束的标识符
- 可选消息体
也就是三个部分,分别是:请求行、请求头、请求正文
请求方法:
HTTP/1.1协议中一共定义了八种方法来表面 Request-URI指定的资源的不同操作方式:OPTIONS、HEAD、GET、POST、PUT、DELETE、TRACE、CONNECT
这八种方法里面,现在我们一般都只有 GET 和 POST 方法。
GET 和 POST 的区别:
1.GET 提交的数据会放在 URL 之后,以?分割 URL 和传输数据,参数之间以&相连。
2.GET 提交的数据大小有限制,最多只能有1024字节,而 POST 方法提交的数据没有限制
3.GET 方式需要使用 Requ.QueryString来取得变量的值,而 POST 方式通过 Request.Form来获取值
4.Get 方式提交数据,会带来安全问题,比如一个登陆页面,通过 GET 方式提交数据时,用户名和密码将出现在 URL 上,如果页面可以被缓存或者其他人可以访问这台机器,就可以从历史记录获取该用户的账号和密码。
HTTP Response
当客户端向服务器发生一个请求,服务器以一个状态作为相应,相应的内容包括:消息协议的版本、成功或者错误编码、服务器信息、实体元信息以及必要的实体内容。根据响应类别,服务器响应可以包含实体内容,但不是所有的响应都应有实体内容。
响应消息的结构也分为三部分
- 响应状态行
主要包含 HTTP 版本信息(一般是1.1)和状态码,状态码对于信息如下所示:
状态码 | 对应信息 |
---|---|
1xx | 提示信息-表示请求已接受,继续处理 |
2xx | 用于表示请求已经被成功接收、处理、 |
3xx | 用于表示自愿被永久转到其他 URL,也就是重定向 |
4xx | 客户端的错误-请求有语法错误或无法实现 |
5xx | 服务器端错误-服务器未能实现合法的请求 |
- 响应头
响应头同样可用于传递一些附加信息。
常见响应 Header
名词 | 作用 |
---|---|
Date | 服务器日期 |
Last-Modified | 最后被修改时间 |
Transfer-Encoding | 取值一般为 chunked,一般出现在响应体长度不能确定的情况下 |
Set-cookie | 设置 Cookie |
Location | 重定向到另一个 URL |
Server | 后台服务器 |
- 响应体
响应体也就是我们需要的内容,一般在相应头中会用 Content-Length来明确相应体的长度,便于浏览器接受,对于大数据量的正文消息,也会使用 chunked 的编码方式。
HTTPS
简介
HTTPS(Hypertext Transfer Protocol over Secure Socket Layer),是以安全为目标的 http 通道,简单的讲就算 HTTP 的安全版。即HTTP 下加入 SSL 层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL。
HTTPS 和 HTTP 的区别
1.https 需要到 ca 申请证书,一般免费证书很少,需要交费。
2.http 是超文本传输协议,信息是明文传输;https 则是具有安全性的 ssl 加密传输协议。
3.http 和 https 室友完全不同的连接方式,用的端口也不一样,前者是80,后者是443.
4.http 的链接很简单,无状态的。https 协议是由ssl+http 协议构建的可进行加密传输、身份认证的网络协议,比 http 协议安全。
https 的作用
它的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输安全;另一种是确认网站的真实性。
1.一般意义上的 https,就是服务器有一个证书。主要目的是保证服务器就是他声称的服务器,这个跟第一点一样;服务端和客户端之间的所有通讯,都是加密的。
2.具体讲,是客户端产生的一个对称的秘钥,通过服务器的证书来交换秘钥,即一般意义上的握手过程。
3.接下来所有的信息往来都是加密的。即使第三方截取,也没有任何意义,因为他没有秘钥,当然篡改也就没有什么意义了。
4.少许对客户端有妖气的情况下,会要求客户端也必须有一个证书。
这里的客户端证书,其实就类似表示个人信息的时候,除了用户名/密码,还有一个 CA 认证过的身份。因为个人证书一般来说是别人无法模拟的,所有这样能够更深的确认自己的身份。少数个人银行的专业版是这种做法,比如 U 盾。
SSL 就不讲了吧,涉及的东西太多了。
URLConnection
URLConnection 是一个抽象类,表示指向 URL 指定资源的活动连接。URLConnection 有了两个不同但相关的用途。首先,与 URL 类相比,它对与服务器(特别是 HTTP 服务器)的交互提供了更多的控制。URLConnection 可以检查服务器发送的首部,并相应地做出响应。它可以设置客户端请求中使用的首部字段。最后,URLConnection 可以用 POST、PUT 和其他 HTTP 请求方法向服务器发生数据。
打开 URLConnection
直接使用URLConnection 类的程序遵循以下基本步骤:
1.构造一个 URL 对象
2.调用这个 URL 对象的 openConnection()获取一个对应该 URL 的 URLConnection 对象。(如 http 请求获取的是 HttpURLConnection)
3.配置这个 URLConnection
4.读取首部字段
5.获取输入流并读取数据
6.获取输出流并写入数据
7.关闭连接
并不一定执行所有这些步骤。例如,如果某种 URL 的默认设置是可以接受的,那么可能会跳过步骤3。如果只需要服务器的数据,不关心任何元信息,或者协议不提供任何元信息,那么可以跳过步骤4。如果只希望接受服务器的数据,而不向服务器发生数据,就可以跳过步骤6.依据不同协议,步骤5和6的顺序可能会反过来或者交替出现。
URLConnection 类仅有一个构造函数为保护类型。
protected RULConnection(RUL url)复制代码
因此,除非派生 URLConnection 的子类来处理新的 URL 类型,否则要通过调用 URL 类的 openConnection()方法来创建这样一个对象。
try{
URL u = new URL("http://www.baidu.com");
URLConnection uc = u.openConnection();
}catch(Exception e){
System.out.println(e);
}复制代码
URLConnection类声明为抽象类。不过,除了一个方法外,其余方法都已经实现。你会发现覆盖这个类的其他方法很方便,或者可能很有必要。必须由子类实现的一个方法是 connect(),他建立与服务器的连接,因而依赖于服务类型(HTTP,FTP 等)。例如 sun.net.www.protocol.http.HttpURLConnection的 connect()方法会创建一个sun.net.www.http.HttpClient对象,由他负责连接服务器。
第一次构造 URLConnection 时,它是未连接的。也就是说,本地和原创主机无法发送和接收数据。没有 sockey 连接这两个主机。connect()方法在本地和远程主机之间建立一个连接(一般使用 TCP socket,但也可能通过其他机制来建立),这样就可以收发数据了,不过,对于 getInputStream()、getContent()、getHeaderFiled()和其他要求打开连接的方法,如果连接尚未打开,他们就会调用 connect()。因此,你很少需要直接调用 connect()。
读取服务器的数据
下面是使用 URLConnection 对象从一个 URL 获取数据所需的最起码的步骤:
1.构造一个 URL 对象。
2.调用这个 URL 对象的 openConnection()方法,获取对应的 URLConnection。
3.调用这个 URLConnection 的 getInputStream()方法
4.使用通常的流 API 读取输入流。
getInputStream() 方法返回一个通用的 InputStream,可以读取和解析服务器发送的数据。以下示例使用 URLConnection 下载一个 Web 页面:
对于这个例子中这样一个简单的输入流,URL 和 URLConnection 直接的区别并不明显。
这两个类的最大不同在于:
- URLConnection 提供了对 HTTP 首部的访问
- URLConnection 可以配置发送给服务器的请求参数
- URLConnection 除了读取服务器数据外,还可以向服务器写入数据
读取首部
HTTP 服务器在每个响应前面的首部中提供了大量信息。例如,下面是一个服务器返回的典型的 HTTP 首部:
bdpagetype →1
bdqid →0xef0ab4fd0001728d
bduserid →0
cache-control →private
connection →Keep-Alive
content-encoding →gzip
content-type →text/html; charset=utf-8
cxy_all →baidu+631df64ada7e1077f47313160f7a4090
date →Thu, 16 Nov 2017 08:20:15 GMT
expires →Thu, 16 Nov 2017 08:19:54 GMT
p3p →CP=" OTI DSP COR IVA OUR IND COM "
server →BWS/1.1
strict-transport-security →max-age=172800
transfer-encoding →chunked
vary →Accept-Encoding
x-powered-by →HPHP
x-ua-compatible →IE=Edge,chrome=1复制代码
这里有大量信息,haha,我很多都不认识,具体字段可对照我上文列的那个响应头字段表。
URLConnection 有几个获取常用响应头字段的方法
- getContentType() 获取响应主体的 MIME 内容类型
- getContentLength() 获取内容中有多少字节
- getContentEncoding()获取内容的编码格式
- getDate() 获取内容返回时间
- getLastModified() 获取最近修改时间
- getExpires() 表示文档过期时间,过期后需要重新下载
- getHeaderField(String name)获取首部的任意字段
缓存
Web 浏览器多年来一直在缓存页面和图片。如果一个 logo 在网站的每一个页面上重复出现,浏览器一般只会从远程服务器上加载一次,将它保存在缓存上,每次需要的时候会从缓存重新加载,而不是每次遇到这个 logo 都请求远程服务器加载。一些 HTTP 首部(包括 Expires 和 Cache-control)可以控制缓存。
默认情况下,一般认为使用 GET 通过 HTTP 访问的页面可以缓存,也应当缓存。使用 HTTPS 或 POST 访问的页面不应缓存。不过 HTTP 首部可以对此作出调整:
- Expires 首部(主要针对 HTTP1.0)指示可以缓存这个资源表示,直到指定的世界为止。
Catche-control 首部(HTTP1.1)提供了细粒度的缓存策略:
- max-age=[seconds]:从现在直到缓存项过期之前的秒数
- s-maxage=[seconds]:从现在起,直到缓存项在共享缓存中过期之前的秒数。私有缓存可以将缓存项保存更长时间
- public:可以缓存一个经过认证的响应。否则已认证的响应不能缓存。
- private:仅耽搁用户缓存可以保存响应,而共享缓存不应保存。
- no-catch:这个策略的作用与名字不太一致。缓存项仍然可以缓存,不过客户端在每次访问时要用一个 Etag 或 Last-modified 首部重新验证响应的状态。
no-store:不管怎样都不缓存。
如果 Catch-control 和 Expires 首部都出现,Cache-control 会覆盖 Expires。服务器可以在一个首部中发送多个 Cache-control 首部,只要它们没有冲突
Last-modified 首部指示资源最后一次修改的日期。客户端可以用一个 HEAD 请求来检查这个日期,只要当本地缓存的副本遭遇 Last-modified 日期时,它才会真正执行 GET 来获取资源。
- Etag 首部(HTTP1.1)是资源改变时这个资源的位移标识符。客户端可以使用一个 HEAD 请求来检查这个标识符,只有当本地缓存的副本有一个不同的 Etag 时,它才会真正执行 GET 来获取资源。
配置连接
URLConnection 类有7个保护的实例字段,定义了客户端如何向服务器做出请求。这些字段包括
protected URL url;
protected boolean doInpt = true;
protected boolean doOutput = false;
protected boolean allowUserInteraction = defaultAllowUserInteraction;
protected useCaches = defaultUserCachesl;
protected long ifModifiedSince = 0;
protected boolean connected = false;复制代码
例如,如果 doOutput 为 true,那么除了通过 URLConnection 读取数据外,还可以通过将数据写入到服务器。如果useCaches为 false,连接会染过所有本地缓存,重新从服务器下载文件。
由于这些字段是保护字段,所以要读写这些字段只能通过 get、set 方法。
只能在 URLConnection 连接之前修改这些字段。对于设置字段的方法,如果调用这些方法时连接已经打开,大多数会抛出一个 IllegaStateException 异常。一般情况下,只能在连接打开之前设置 URLConnection 对象的属性。
- protected URL url
url 字段制定了这个 URLConnection 连接的 URL。构造函数会在创建 URLConnection 时设置这个字段,此后不能再改变。可以通过调用 getURL()方法获取这个字段的值。
- protected boolean connected
如果连接已经打开,boolean 字段 connected 为 true,如果连接关闭,这个字段则为 false。由于在创建一个新 URLConnection 对象时连接尚未打开,所以初始值为 false。
- protected boolean allowUserInteraction
有些 URLConnection 需要与用户交互。例如,Web 浏览器可能需要用户名和口令。不过,很多应用程序不能假定真实存在一个可以与它交互的用户。不过好像我们 Android 开发用不上。
- protected boolean doInput
URLConnection 可以用于读取服务器、写入服务器,或者同时用于读/写服务器。如果 URLConnection 可以用来读取,保护类型 boolean 字段 doInput 就为 true,否则为 false。默认值是 true。
- protected boolean doOutput
程序可以用 URLConnection 将输出发回服务器。例如,如果程序需要使用 POST 方法向服务器发生数据,可以通过从 URLConnection 获取输出流来完成。如果 URLConnection 可以用于写入,字段 doOutput 就为 true,否则为 false。
- protected boolean ifModifySince
许多客户端会保留以前获取的文档缓存。如果用户再次要求相同的文档,可以从缓存中获取。不过,在最后一次获取这个文档之后,服务器上的文档可能会改变。要判断是否有变化,唯一的办法就是询问服务器。客户端可以在户请求 HTTP 首部中包括一个If-MOdified_since
。这个首部包括一个日期和时间。如果文档在这个时间之后有所修改,服务器就发送该文档,否则就不发生。一般情况下,这个世界是客户端最后获得文档的时间。
- protected boolean useCaches
有些客户端可以从本地缓存获取文档,而不是从服务器获取。applet 可以访问浏览器的缓存。独立的应用程序可以用 java.net.ResponseCache类。如果有缓存,useCaches 变量确定了是否可以使用缓存。默认值为 true,表示将使用缓存。
超时
public void setConnectionTimeout()
调用这个方法设置连接超时。
配置客户端请求 HTTP 首部
HTTP 客户端(如浏览器)向服务器发生一个请求行和一个首部。
服务器可以根据请求信息向客户端返回不同的数据,获取和设置cookie,通过口令认证用户等。通过再客户端发送和服务器响应的首部中放置不同的字段,就可以完成这些工作。
- public void setRequestProperty(String name,String value)
setRequestProperty()方法指定的名和值为这个 URLConnection 的首部加一个字段。这个方法只能在连接打开之前使用。重复调用这个方法会覆盖之前的字段。如果要增加一对,需要使用 addRequestProperty()方法。
并没有一个固定的合法首部列表。服务器一般会忽略无法识别的首部。HTTP 确实对首部字段的名和值有一些限制。例如,名不能包含空白符,值不能包含任何换行符。如果一个字段包含换行符,会抛出 IllegalArgumentException 异常。
向服务器写入数据
有时候需要向URLConnection 写入数据,例如,使用 POST 向服务器提交表单,或者使用 PUT 上传文件。getOutputStream()方法返回一个 OutputStream,可以用来写入数据传给服务器:
public OutputStream getOutputStream()复制代码
由于 URLConnection 在默认情况下不允许输出,所以在请求输入流之前必须调用 setDooutput(true)。为一个 http URL 将 doOutput 设置为 true 时,请求方法将由GET 变为 POST。
猜测 MIME 媒体类型
- public static String guessContentTypeFromName(String var0)
通过文件扩展名判断,一般来说没有问题
- public static String guessContentTypeFromStream(InputStream var0)
通过尝试查看流中前几个字节来猜测内容类型,猜测结果通常没有根据扩展名猜测靠谱。
HttpURLConnection
java.net.HttpURLConnection类是 URLConnection 的抽象子类。它提供了另外一些方法,在处理 http RUL 时尤其有帮助。具体的,它包含的方法可以获得和设置请求方法、确定是否重定向、获取响应码和消息,以及确定是否使用了代理服务器。
请求方法
当客户端联系一个服务器时,它发送的第一个内容是请求行。一般情况下,这一行以 GET 开头,后面是客户端希望获取的资源路径,以及 HTTO 版本。
通常情况下,默认请求方法是 GET,可以通过以下方法进行修改
- public void setRequestMethod(String method)
请求的方法有 GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE,一般我们 Android 都只用 GET 和 POST,但是现在好像后台都比较喜欢统一用 POST 了。
断开与服务器的连接
HTTP1.1支持持久连接,允许一个 TCP socket 发送多个请求和响应。服务器不会因为已经向客户端发送了最后一个字节的数据就立即关闭连接。也就是说,在服务器关闭连接之前,如果再连接同一个服务器,它会重用 socket。手动调用 disconnect()方法会主动断开连接,如果还有打开的流,也会关闭。但是反过来关闭打开的流,并不会关闭连接。
处理服务器响应
前面我们介绍过服务器响应的格式,这里就不再赘述。
- getResponseCode() 获取响应状态码
- getResponseMessage()获取响应码后面的文本字符串
重定向
300一级的响应吗都表示某种重定向,即请求的资源不再期望的位置上,但有可能会在其他位置找到。遇到这样的响应时,大多数浏览器会自动从新位置加载文档。
默认情况下,HTTPURLConnection 会跟随重定向,不过 HTTPURLConnection 有两个静态方法可以控制是否跟随重定向。
- public static boolean getFollowRedirects()
- public static void steFollowRedirects(boolean follow)
注意,这里是静态方法,全局修改。
结束语
可能这一章写的比较凌乱,其实我也很绝望,很多东西我也是一边学一边记录的,这已经是整理的第三版笔记了。
这一章学习的主要目的是为了对整个 HTTP请求有个大致的了解,很多知识点都是一笔带过(还忽略了一些),同学们知道有这么回事就行了,大概在十八、十九课的时候,我会和大家一起用我们学过的知识点,手撸一个 Volley 网络请求架构。
我提前透露一下这次手撸的 Volley会实现哪些需求以及用到了哪些知识点。
实现的需求
- 支持请求 JSON 文本类型学,音频,图片类型,批量下载。上传~
- 请求各种数据时,调用层不用关心上传参数的封装
- 获取数据后,调用层不用关心 JSON 数据的解析
- 回调时,调用层只需要知道传入的 JSON 的对应响应类
- 回调响应结果发生在主线程(线程切换)
- 对下载,上传扩展
- 支持高并发请求,请求队列一次获取,可以设置最大并发数,设置先请求先执行
会用到的知识点
- 泛型
- 请求队列
- 阻塞队列
- 线程拒绝策略
用到的设计模式
- 模板方法模式
- 单例模式
- 策略模式
- 生产者消费者模式(不属于23种基本的消费模式 haha)