关于HTTP的介绍这篇博客写的不错,我借用了里面的图,http://blog.youkuaiyun.com/itachi85/article/details/50982995
在上一篇中我们讨论了计算机网络的体系结构和各层次的作用,在我们编程中TCP或UDP都提供了socket接口进行实现,实现的例子在上一篇中,这一篇我们主要讨论一下Http协议,以及如何实现Http协议。
讨论的问题:
- Http协议的定义和内容。
- Http协议的实现。
一、Http协议的定义和内容
HTTP的英文是(HyperText Transfer Protocol),即超文本传输协议,那么什么是超文本呢,超文本就是使用超文本标记语言HTML的文本。
首先互联网上每一个资源都用一个URL所标记,URL的格式为:
http://<主机>:<端口>/<路径>
它是应用层协议,规定了数据交互的格式内容。在运输层,它采用了TCP协议保证了可靠运输。而且HTTP协议是无状态的,不过在HTTP/1.1中,添加了持续连接的功能。
下面就来看看HTTP报文的格式:
首先它有两种报文:
- 请求报文(客户端发送给服务器的报文)
- 响应报文(服务器返回给客户端的报文)
而且HTTP是面向文本的,所以在报文中的每一个字段都是一些ASCII码。
请求报文:
请求报文包括 请求行、请求头部、请求数据
这里面第一行是请求行,举一个例子:
GET http://s2-im-notify.youkuaiyun.com/socket.io/1/xhr-polling/4-edxSXeVplQjhPcXY2V?t=1476099463098 HTTP/1.1
Host: s2-im-notify.youkuaiyun.com
Connection: keep-alive
Origin: http://blog.youkuaiyun.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36
Accept: */*
Referer: http://blog.youkuaiyun.com/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
请求行:
在这个请求中我们看到这个请求行为第一行,下面的都是请求头了,这个并没有请求数据。这个请求中请求方法为GET,GET是我们常用的请求方法之一,除了GET,我们还常用POST进行请求。
下面列举出请求方法,不过最常用的还是GET和POST:
- OPTION:请求一些选项的信息
- GET:请求读取由URL所标志的信息
- HEAD:请求读取由URL所标志的信息的首部
- POST:给服务器发送信息
- PUT:在指明的URL下存储一个文档
- DELETE:删除指明的URL所标志的资源
- TRACE:用来进行环回测试的请求报文
CONNECT:用于代理服务器
请求报头:
请求报头有许多种,下面说一些常见的请求头:Host域:指定请求的服务器地址,在HTTP/1.1中请求必须包含主机头域,否则系统会以400状态码返回。
- Connection域:指定与连接相关的属性,比如上面例子中Connection: keep-alive,代表保持连接。
- User-Agent域:发送请求的应用程序名称。
- Accept-Charset域:通知服务端可以发送的编码格式。
- Accept-Encoding域:通知服务端可以发送的数据压缩格式。
- Accept-Language域:通知服务器可以发送的语言。
- Accept域: 告诉WEB服务器自己接受什么介质类型,/ 表示任何类型,type/* 表示该类型下的所有子类型。
- Referer域:发送请求页面URL。浏览器向 WEB 服务器表明自己是从哪个 网页/URL 获得/点击 当前请求中的网址/URL。
- Pramga域:主要使用 Pramga: no-cache,相当于 Cache-Control: no-cache。
- Date域:表示消息发出的时间。
- Cookie域:设置Cookie相关的。
- Cache-Control域:使用的缓存机制(在请求时和响应时它的值不同,响应的下文会说到)。
no-cache(不要缓存的实体,要求现在从服务器去取)
max-age:(只接受 Age 值小于 max-age 值,并且没有过期的对象)
max-stale:(可以接受过去的对象,但是过期时间必须小于 max-stale 值)
min-fresh:(接受其新鲜生命期大于其当前 Age 跟 min-fresh 值之和的缓存对象)。
响应报文
类比请求报文,响应报文同样也包括 响应行、响应头、响应数据
下面也是用一个例子来看一下:
HTTP/1.1 200 OK
Server: openresty
Date: Mon, 10 Oct 2016 12:25:25 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Keep-Alive: timeout=20
Vary: Accept-Encoding
Cache-Control: private
Access-Control-Allow-Origin: *
Content-Encoding: gzip
6b0
X[ F~G ? $ I @ eiWb
j }Bc{ k ]{r F+A/Z} T U/- [ cJ _ _
6 g s wΜs Ç #g / sa
O [^
*** FIDDLER: RawDisplay truncated at 128 characters. Right-click to disable truncation. ***
响应行:
第一行为响应行,对于响应行,我们可以看出状态码(也成为响应码)为200。
关于状态码,都是由三位数字组成的,分为五大类:
- 1XX :表示通知信息,如请求收到了或者正在进行处理。
- 2XX:表示成功,其中最常见的便是200,表示请求成功。
- 3XX:表示重定向,如要完成请求还必须采取进一步的行动。
- 4XX:表示客户端出错,最常见的就是404,表示未找到。
- 5XX:表示服务器出错,比如服务器失效无法完成请求。
除了上面的还有一些常见的响应头,比如:
HTTP/1.1 202 Accepted //表示接受
HTTP/1.1 400 Bad Request //表示错误的请求
HTTP/1.1 404 Not Found //表示未找到
HTTP/1.1 301 Moved Permanently //永久性的转移了
响应头:
列举一下常用的头部,(PS:在百度时发现一篇写关于这个头部和状态码非常详细的文章,我有的头部也参考的他的博客,分享给大家:http://www.2cto.com/net/201607/528141.html) - Age域:当代理服务器用自己缓存的实体去响应请求时,用该头部表明该实体从产生到现在经过多长时间了。
- Accept-Ranges域:WEB服务器表明自己是否接受获取其某个实体的一部分(比如文件的一部分)的请求。bytes:表示接受,none:表示不接受。
- Content-Type域:服务器通知客户端它响应的对象类型,如Content-Type:application/json。
Content-Range域:服务器表明该响应包含的部分对象为整个对象的哪个部分。
Content-Length:服务器通知响应包含对象的长度。
- Content-Language:服务器通知客户端响应的语言。
- Content-Encoding:服务器通知客户端响应数据的压缩格式,在上面的例子中可以看出压缩格式为gzip。
- Connection:代表是否需要持久连接。
- Expired:WEB服务器表明该实体将在什么时候过期,对于过期了的对象,只有在跟WEB服务器验证了其有效性后,才能用来响应客户请求。
- Last-Modified: 服务器认为对象的最后修改时间,比如文件的最后修改时间,动态页面的最后产生时间等等。
- Location:服务器告诉浏览器,试图访问的对象已经被移到别的位置了,到该头部指定的位置去取。
- Proxy-Authenticate:代理服务器响应浏览器,要求其提供代理身份验证信息。
- Server: 服务器表明自己是什么软件及版本等信息。
- Refresh:表示浏览器应该在多少时间之后刷新文档,以秒计。
响应数据:
一般来说响应数据也会有一定的格式,上面的例子中因为数据用gzip压缩了,所以显示的为乱码。现在用的最火的为json格式数据,下面的例子为一个请求的响应数据,为json格式:
{“status”:true,”error”:”“,”data”:{“id”:010101,”url”:”http://blog.youkuaiyun.com/liushuaiq/article/details/52779689“}}
二、Http协议的实现
上一篇中我们使用了java提供的socket进行了数据的传输,socket是对tcp或udp的封装,对于应用层没有实现,这一篇就对上一篇进行进一步的拓展,实现Http协议。
我们只需要在socket传输数据的基础上,进行进一步的格式化数据就可以了。
首先我封装了一个请求实体。
package com.liushuai.model;
import java.util.List;
public class Request {
private RequestLine requestLine;
private List<RequestHeader> requestHeaders;
private RequestBody requestBody;
public Request() {
super();
}
public Request(RequestLine requestLine, List<RequestHeader> requestHeaders, RequestBody requestBody) {
super();
this.requestLine = requestLine;
this.requestHeaders = requestHeaders;
this.requestBody = requestBody;
}
public RequestLine getRequestLine() {
return requestLine;
}
public void setRequestLine(RequestLine requestLine) {
this.requestLine = requestLine;
}
public List<RequestHeader> getRequestHeaders() {
return requestHeaders;
}
public void setRequestHeaders(List<RequestHeader> requestHeaders) {
this.requestHeaders = requestHeaders;
}
public RequestBody getRequestBody() {
return requestBody;
}
public void setRequestBody(RequestBody requestBody) {
this.requestBody = requestBody;
}
}
其中里面还进行进一步封装了请求行和请求头和请求体,代码如下:
package com.liushuai.model;
/**
* 请求行实体
*
* @author LiuShuai
*
*/
public class RequestLine {
/**
* 请求方法
*/
private String method;
/**
* 请求的 URL
*/
private String url;
/**
* 版本
*/
private String version;
public RequestLine() {
super();
}
public RequestLine(String method, String url, String version) {
super();
this.method = method;
this.url = url;
this.version = version;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
package com.liushuai.model;
/**
* 请求头部实体
*
* @author LiuShuai
*
*/
public class RequestHeader {
/**
* 头部名称
*/
private String name;
/**
* 头部域值
*/
private String value;
public RequestHeader() {
super();
}
public RequestHeader(String name, String value) {
super();
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
package com.liushuai.model;
/**
* 请求体
*
* @author LiuShuai
*
*/
public class RequestBody {
private String requestBody;
public RequestBody() {
super();
}
public RequestBody(String requestBody) {
super();
this.requestBody = requestBody;
}
public String getRequestBody() {
return requestBody;
}
public void setRequestBody(String requestBody) {
this.requestBody = requestBody;
}
}
下面是在socket的基础上将数据进行封装,然后格式化为Http协议中要求的格式输出:
package com.liushuai.client;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import javax.management.relation.Relation;
import javax.sql.rowset.serial.SerialArray;
import com.liushuai.model.Request;
import com.liushuai.model.RequestHeader;
import com.liushuai.model.RequestLine;
import com.liushuai.server.MyServer;
public class MyClient {
public static void main(String[] args) {
InetAddress inet4Address;
try {
inet4Address = Inet4Address.getLocalHost();
Socket socket = new Socket("123.126.51.32", 80);
//请求行
RequestLine rLine = new RequestLine("GET",
"http://download.pinyin.sogou.com/picface/interface/cellupdate.php?h=53B24101A353034C6F75D2DB4436AA80&v=8.0.0.8381&r=0000_sogou_pinyin_win10a&ver=1.0.0.1464&cellid=80|77|79|78|2|37|41|47|52|54&cellver=2|3|2|2|4|2|4|5|3|6",
"HTTP/1.1");
//请求头
RequestHeader rh1 = new RequestHeader("User-Agent", "SogouComponentAgent");
RequestHeader rh2 = new RequestHeader("Host", "download.pinyin.sogou.com");
RequestHeader rh3 = new RequestHeader("Pragma", "no-cache");
RequestHeader rh4 = new RequestHeader("Cookie",
"YYID=53B24101A353034C6F75D2DB4436AA80; IMEVER=8.0.0.8381; IPLOC=CN3702");
List<RequestHeader> rhs = new ArrayList<>();
rhs.add(rh1);
rhs.add(rh2);
rhs.add(rh3);
rhs.add(rh4);
//构造请求,这里面让请求体为空了
Request request = new Request(rLine, rhs, null);
// 拼装一个Http请求
StringBuffer requestString = new StringBuffer();
// 拼装请求行
RequestLine reqLine = request.getRequestLine();
StringBuffer line = new StringBuffer();
line.append(reqLine.getMethod()).append(" ").append(reqLine.getUrl()).append(" ")
.append(reqLine.getVersion()).append("\r\n");
requestString.append(line);
// 拼装请求头部
List<RequestHeader> requestHeaders = request.getRequestHeaders();
StringBuffer headers = new StringBuffer();
for (RequestHeader h : requestHeaders) {
headers.append(h.getName()).append(":").append(h.getValue()).append("\r\n");
}
requestString.append(headers).append("\r\n");
if (request.getRequestBody()!= null) {
requestString.append(request.getRequestBody().getRequestBody());
}
//向服务器发送请求的数据,数据已经拼装成了Http请求报文的格式
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
printWriter.print(requestString.toString());
printWriter.flush();
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
StringBuffer responseString = new StringBuffer();
String servervInfo ;
//读取服务器的响应,不过这地方会阻塞,因为Java I/O是线程阻塞的,优化一下代码应当使用Java NIO
while((servervInfo = reader.readLine())!="\r\n"&&servervInfo!=null){
System.out.println(servervInfo);
responseString.append(servervInfo+"\n");
}
System.out.println("服务器端发送的数据为--->" + responseString);
} catch (Exception e) {
e.printStackTrace();
}
}
}
这就简单的模仿了一下Http的一次请求,我将返回结果进行了打印:
HTTP/1.1 200 OK
Server: nginx
Date: Tue, 11 Oct 2016 12:28:29 GMT
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
X-Powered-By: PHP/7.0.8
Pragma: cache
Cache-Control: public, must-revalidate, max-age=0
Accept-Ranges: bytes
Content-Disposition: filename="SGPicFaceCellList.ini"
可以看出这次是请求成功了,返回的格式也符合Http中要求的格式。
这一篇就对HTTP协议进行了一次详细的介绍,下一篇中我准备分析一下当前最火的Android网络框架OkHttp源码中的实现。