关于HTTP的介绍这篇博客写的不错,我借用了里面的图,http://blog.csdn.net/itachi85/article/details/50982995
在上一篇中我们讨论了计算机网络的体系结构和各层次的作用,在我们编程中TCP或UDP都提供了socket接口进行实现,实现的例子在上一篇中,这一篇我们主要讨论一下Http协议,以及如何实现Http协议。
讨论的问题:
HTTP的英文是(HyperText Transfer Protocol),即超文本传输协议,那么什么是超文本呢,超文本就是使用超文本标记语言HTML的文本。
首先互联网上每一个资源都用一个URL所标记,URL的格式为:
http://<主机>:<端口>/<路径>
它是应用层协议,规定了数据交互的格式内容。在运输层,它采用了TCP协议保证了可靠运输。而且HTTP协议是无状态的,不过在HTTP/1.1中,添加了持续连接的功能。
下面就来看看HTTP报文的格式:
首先它有两种报文:
- 请求报文(客户端发送给服务器的报文)
- 响应报文(服务器返回给客户端的报文)
而且HTTP是面向文本的,所以在报文中的每一个字段都是一些ASCII码。
请求报文包括 请求行、请求头部、请求数据
这里面第一行是请求行,举一个例子:
GET http://s2-im-notify.csdn.net/socket.io/1/xhr-polling/4-edxSXeVplQjhPcXY2V?t=1476099463098 HTTP/1.1
Host: s2-im-notify.csdn.net
Connection: keep-alive
Origin: http://blog.csdn.net
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.csdn.net/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
请求行:
在这个请求中我们看到这个请求行为第一行,下面的都是请求头了,这个并没有请求数据。这个请求中请求方法为GET,GET是我们常用的请求方法之一,除了GET,我们还常用POST进行请求。
下面列举出请求方法,不过最常用的还是GET和POST:
CONNECT:用于代理服务器
请求报头:
请求报头有许多种,下面说一些常见的请求头:
Host域:指定请求的服务器地址,在HTTP/1.1中请求必须包含主机头域,否则系统会以400状态码返回。
类比请求报文,响应报文同样也包括 响应行、响应头、响应数据
下面也是用一个例子来看一下:
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。
关于状态码,都是由三位数字组成的,分为五大类:
Content-Range域:服务器表明该响应包含的部分对象为整个对象的哪个部分。
Content-Length:服务器通知响应包含对象的长度。
上一篇中我们使用了java提供的socket进行了数据的传输,socket是对tcp或udp的封装,对于应用层没有实现,这一篇就对上一篇进行进一步的拓展,实现Http协议。
我们只需要在socket传输数据的基础上,进行进一步的格式化数据就可以了。
首先我封装了一个请求实体。
package com.liushuai.model;
import java.util.List;
public class Request {
private RequestLine requestLine;
private List requestHeaders;
private RequestBody requestBody;
public Request() {
super();
}
public Request(RequestLine requestLine, List 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 getRequestHeaders() {
return requestHeaders;
}
public void setRequestHeaders(List 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 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 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源码中的实现。