如果要相对清楚的明白一次普通的网络请求的流程,首先要明白Http协议的一些基础,接下来的内容基于常用的Http1.1协议
请求URL
这里以普通的Http/Https为例
平常在使用浏览器的时候都会有一个请求资源标识符,比方说http://www.baidu1.com这种,具体的含义就是[scheme]://[host]:[post(默认80,可以隐藏)]/path/...这种。
scheme:请求协议。
在一个完整的通话流程中,为了保证发送数据、接收数据、传输数据等操作的统一性,必须先预先定义一个协议,这个协议内部会预先定义好一系列的规则,接下来的操作会基于这一套规则进行。
host:请求域名。
网络请求实际上就是某一个主机访问另一个主机的过程,那么对于被访问的主机必须要有标识,这个就是域名。
实际的过程中会相对复杂,单纯通过一个域名是无法找到主机的,因为在传输的过程中基本的标识是IP地址,那么中间会需要DNS服务器将域名转换为确切的一个IP地址,接着再通过这个IP地址去访问对应的主机
port:请求端口
当通过某一个IP地址访问到对应主机之后,但是一个主机可能有多个服务,比方说Http和Https这两个不同的协议,同一台主机应该是可以处理这两种不同的协议,那么就是通过不同的端口来进行区分,目前默认的Http是80端口,Https是443端口。
path:当确切访问到主机的某一个端口后,为了区别访问的具体文件,需要通过path来指定具体的访问对象
Http协议
这里以Http1.1协议为例
在一次的通信过程中,传输的基本单位称为报文,这里看一下报文的基本结构,这个也是传递过程中的数据,报文主要关注请求报文和响应报文即可
请求报文
协议中定义的报文有着自己的格式,接下来看一下一个Http1.1的请求报文格式
起始行
请求方式 host/path/...?参数 协议
举例说明:(注意空格)
POST www.hh.com?a=1 HTTP/1.1
请求头部
起始行结束之后要进行换行,然后接着写入请求头部数据,请求头部的基本格式是键值对模式。
需要添加的头部1: 参数1
需要添加的头部2: 参数2
...其余需要添加的头部
换行符
这里以一个常用的头部作为例子说明一下:
Connection: Keep-alive(长连接)
Content-Length: 10248(数据的字节数)
Content-Type: multipart/form-data(表单格式数据)
Host: api.hh.com(请求域名)
换行符
请求数据
这里以form表单为例子,传输一个文本和一个文件,注意空格
--boundary(一串字符)
Content-Disposition: form-data; name="text1"
Content-Length: 5
text1
--boundary(一串字符)
Content-Disposition: form-data; name="file1";
filename="water.png"
Content-Type: application/octet-stream
Content-Length: 该文件的字节数
##########(总之就是该文件的二进制流数据)
--boundary(一串字符)--
这就是定义的表单数据格式,通过分隔符开始,然后通过特定的分隔符结束。接着在获取对应数据的时候只要获得name,然后去读取对应数据即可。
传输模式
数据拼接完成之后,接下来要做的就是将这些数据传输到对应主机(服务器)上面,在Java层面来说,一旦通过socket进行TCP连接之后,将会在发起连接的主机和被连接的主机之间建立一个流通道,那么只需要通过向被连接主机的输出流写入上述报文数据,这样就完成了发送数据请求的流程。
总结
这里简单的模拟一个完整报文用于明确请求报文数据
POST api.bi.com HTTP/1.1
Content-Type: multipart/form-data
Content-Length: 10005
Connection: Keep-alive
--abcdefghijklmn
Content-Disposition: form-data;name="text1"
Content-Length: 5
text1
--abcdefghijklmn
Content-Disposition: form-data;name="file1";filename="water.png"
Content-Type: application/octet-stream
Content-Length: 10000
#########(二进制数据,看上去就是一堆乱码)
--abcdefghijklmn--
响应报文
发送一个请求到服务器之后,服务器进行一些业务上面的处理,接着便是返回结果通知发送请求的主机,以方便主机进行后续操作。
和上面相似的,也是通过报文的格式返回
起始行
协议 状态码 状态码对应说明
举例:
http/1.1 200 OK
表示当前协议为Http1.1,并且请求成功。
Http默认定义了很多状态码,这个可以自行查阅资料,比方说200表示成功、304表示缓存没有变化、404表示请求资源未找到等等
响应头部
以请求一张图片为例子:
Date: Tue, 15 Aug 2017 03:38:02 GMT(服务端处理时间)
Content-Type: image/png
Content-Length: 10005
Expires: Fri, 13 Aug 2027 03:38:02 GMT(响应数据过期时间)
Last-Modified: Fri, 11 Aug 2017 08:55:57 GMT(响应数据在服务端最后一次修改时间)
Location: http://www.ba.com(如果当前返回重定向,则该头部就是用来标记需要重新访问的URL)
Cache-Control: no-cache, no-store, must-revalidate
换行符
响应数据
实际上就是字节流数据,需要更具自身的需求进行处理
传输模式
这里服务端需要往发起请求的主机的输出流中写入数据,从发起请求的主机角度上面来看,就是通过获取服务端的输入流来读取响应数据即可。
Android的一个例子
这里通过HttpURLConnection来实现一个简易的连接
try {
//请求URL
URL url = new URL("http://api.kdniao.cc/Ebusiness/EbusinessOrderHandle.aspx");
//通过该URL打开一个TCP连接通道
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setReadTimeout(30000);//读取超时
connection.setConnectTimeout(20000);//连接超时
connection.addRequestProperty("Connection","Keep-Alive");//添加请求头部
connection.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
connection.setRequestProperty("Accept-Encoding","utf-8");
connection.setDoOutput(true);
//进行POST传参
connection.setRequestMethod("POST");
OutputStream os = connection.getOutputStream();
String text = "RequestType=1002&EBusinessID=1";
os.write(text.getBytes("UTF-8"));
os.flush();
os.close();
//该接口不需要传参
int code = connection.getResponseCode();//获取状态码
if(code == 200){//当前请求成功
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
//获取响应流
BufferedInputStream bufferedInputStream = new BufferedInputStream(connection.getInputStream());
int num = -1;
while (-1 != (num = bufferedInputStream.read())){//读取服务端返回的数据
outputStream.write(num);
}
bufferedInputStream.close();
Log.i("tag1",outputStream.toString());//打印数据
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
实际上该接口的参数没有传完整,这里只是通过一个例子说明,这个接口要求数据格式为application/x-www-form-urlencoded,这个是要求参数通过a1=123&a2=123这种方式进行传递。如果是上述说过的form-data模式,那么text在拼接的时候处理不同,要手动拼接boundary之类的数据。
总结
这篇文章仅仅希望能够入门网络请求基础,实际在使用的时候,一般不会自己进行数据拼接和获取,毕竟现在现成封装后的库还是很多的,不过基础还是要理解的。