HTTP实际上是基于TCP的应用层协议,他在更高层次封装了TCP的使用细节,使网络请求操作更为易用。TCP连接是因特网上基于流的可靠连接,他为HTTP提供了一条可靠的比特传输管道。从TCP连接一端输入的字节会从另一端以原有的顺序、正确地传送出来。如果所示:
TCP的数据是通过名为IP分组(或IP数据报)的小数据块来发送的。这样的话,如图的HTTP协议所示,HTTP就是“HTTP over TCP over IP”这个“协议栈”中的最顶层了。
HTTP要传送一条报文时,会以流的形式将报文数据的内容通过一条打开的TCP连接按序传输。TCP收到数据流之后,会将数据流分隔成被称作段的小数据块,并将段封装在IP分组中,通过因特网进行传输。所有这些工作都是由TCP/IP软件来处理的,程序员什么都看不到。
我们来模拟一个简单的Web服务器来深度了解一下HTTP的报文格式以及HTTP协议与TCP协议之间的协作原理。
一个HTTP请求就是一个典型的C/S模式,服务端在监听某个端口,客户端向服务端的端口发起请求。服务端解析请求,并且向客户端返回结果。下面我们就看看这个简单的Web服务端。
// 简单的服务器实现
package com.example.a.server;
import java.io.IOException;
import java.net.ServerSocket;
/**
* 简单的服务器端实现
*
* @author lijian
* @date 2017-9-10 下午3:59:00
*/
public class SimpleHttpServer extends Thread {
public static final int HTTP_PORT = 8000;// 监听端口
ServerSocket mSocket = null;// 服务端Socket
public SimpleHttpServer() {
try {
// 构造服务端Socket,监听8000端口
mSocket = new ServerSocket(HTTP_PORT);
} catch (IOException e) {
e.printStackTrace();
}
if (mSocket == null) {
throw new RuntimeException("服务器Socket初始化失败");
}
}
@Override
public void run() {
try {
while (true) {// 无限循环,进入等待连接状态
System.out.println("等待连接中");
// 一旦接收到连接请求,构建一个线程来处理
new DeliverThread(mSocket.accept()).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new SimpleHttpServer().start();
}
}
SimpleHttpServer继承自Thread类,在构造函数中我们会创建一个监听8000端口的服务端Socket,并且覆写Thread的run函数,在该函数中开启无限循环,在该循环中调用ServerSocket的accept()函数等待客户端的连接,该函数会阻塞,直到有客户端进行连接,接收连接之后会构造一个线程来处理该请求。也就是说,SimpleHttpServer本身是一个子线程,他在后台等待客户端的连接,一旦接收到连接又会创建一个线程处理该请求,避免阻塞SimpleHttpServer线程。
// 请求处理线程
DeliverThread也继承自Thread,在run函数中主要封装了如下步骤:
(1)获取客户端Socket的输入、输出流用于读写数据;
(2)解析请求参数;
(3)处理、返回请求结果;
(4)关闭输入、输出流、客户端Socket
我们知道TCP的数据操作是基于流的,因此得到客户端Socket连接之后,我们首先获取到他的输入、输出流。其中我们可以从输入流中获取该请求的数据,而通过输出流就可以将结果返回给该客户端。得到流之后我们首先解析该请求,根据他请求的路径、header、参数等作出处理,最后将处理结果通过输出流返回给客户端。最终关闭流和Socket。
package com.example.a.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
/**
* 请求处理线程
*
* @author lijian
* @date 2017-9-10 下午4:06:18
*/
public class DeliverThread extends Thread {
Socket mClientSocket;
/** 输入流 */
BufferedReader mInputStream;
/** 输出流 */
PrintStream mOutputStream;
/** 请求方法GET、POST等 */
String httpMethod;
/** 子路径 */
String subPath;
/** 分隔符 */
String boundary;
/** 请求头 */
Map mHeaders = new HashMap();
/** 请求参数 */
Map mParams = new HashMap();
/** 是否已经解析完Header */
boolean isParseHeader = false;
public DeliverThread(Socket socket) {
this.mClientSocket = socket;
}
@Override
public void run() {
try {
// 获取输入流
mInputStream = new BufferedReader(new InputStreamReader(
mClientSocket.getInputStream()));
// 获取输出流
mOutputStream = new PrintStream(mClientSocket.getOutputStream());
// 解析请求
parseRequest();
// 返回Response
handleResponse();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流和Socket
IoUtils.closeQuitly(mInputStream);
IoUtils.closeQuitly(mOutputStream);
IoUtils.closeSocket(mClientSocket);
}
}
/**
* 解析请求
*/
private void parseRequest() {
String line;
try {
int lineNum = 0;
// 从输入流读取数据
while ((line = mInputStream.readLine()) != null) {
// 第一行为请求行
if (lineNum == 0) {
parseRequestLine(line);
}
// 判断是否是数据的结束行
if (isEnd(line)) {
break;
}
// 解析header参数
if (lineNum != 0 && !isParseHeader) {
parseHeaders(line);
}
// 解析请求参数
if (isParseHeader) {
parseRequestParams(line);
}
lineNum++;
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 第一行为请求行
*
* @param lineOne
*/
private void parseRequestLine(String lineOne) {
String[] tempStrs = lineOne.split(" ");
this.httpMethod = tempStrs[0];
this.subPath = tempStrs[1];
System.out.println("请求方式:" + tempStrs[0]);
System.out.println("子路径:" + tempStrs[1]);
System.out.println("HTTP版本:" + tempStrs[2]);
}
/**
* 判断是否是数据的结束行
*
* @param line
* @return
*/
private boolean isEnd(String line) {
if ("POST".equals(this.httpMethod)) {
if (("--" + this.boundary + "--").equals(line)) {
return true;
}
} else if ("GET".equals(this.httpMethod)) {
// TODO 待完善
}
return false;
}
/**
* 解析header参数
*
* @param headerLine
*/
private void parseHeaders(String headerLine) {
// header区域的结束符
if ("".equals(headerLine)) {
isParseHeader = true;
System.out.println("--->header解析完成\n");
return;
} else if (headerLine.contains("boundary")) {
this.boundary = parseSecondField(headerLine);
System.out.println("分隔符:" + boundary);
} else {
// 解析普通header参数
parseHeaderParam(headerLine);
}
}
/**
* 解析header中的第二个参数
*
* @param line
* @return
*/
private String parseSecondField(String line) {
String[] headerArr = line.split(";");
parseHeaderParam(headerArr[0]);
if (headerArr.length > 1) {
return headerArr[1].split("=")[1];
}
return "";
}
/**
* 解析单个header
*
* @param headerLine
*/
private void parseHeaderParam(String headerLine) {
String[] keyValue = headerLine.split(":");
this.mHeaders.put(keyValue[0].trim(), keyValue[1].trim());
System.out.println("header参数名:" + keyValue[0].trim() + ", 参数值:"
+ keyValue[1].trim());
}
/**
* 解析请求参数
*
* @param paramLine
* @throws IOException
*/
private void parseRequestParams(String paramLine) throws IOException {
if (("--" + this.boundary).equals(paramLine)) {
// 读取Content-Disposition行
String ContentDisposition = mInputStream.readLine();
// 解析参数名
String paramName = parseSecondField(ContentDisposition);
// 读取参数header与参数值之间的空行
mInputStream.readLine();
// 读取参数值
String paramValue = mInputStream.readLine();
this.mParams.put(paramName, paramValue);
System.out.println("参数名:" + paramName + ", 参数值:" + paramValue);
}
}
/**
* 返回Response
*/
private void handleResponse() {
// 模拟处理耗时
sleep();
// 向输出流写数据
mOutputStream.println("HTTP/1.1 200 OK");
mOutputStream.println("Content-Type: application/json");
mOutputStream.println();
mOutputStream.println("{\"stCode\":\"success\"}");
}
private void sleep() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
工具类:
package com.example.a.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
public class IoUtils {
public static void closeQuitly(BufferedReader mInputStream) {
if (mInputStream != null) {
try {
mInputStream.close();
mInputStream = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void closeQuitly(PrintStream mOutputStream) {
if (mOutputStream != null) {
mOutputStream.close();
mOutputStream = null;
}
}
public static void closeSocket(Socket mSocket) {
if (mSocket != null) {
try {
mSocket.close();
mSocket = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
我们知道客户端要做的就是主动向服务器发起HTTP请求,他们之间的通信通道就是TCP/IP,因此,也是基于Socket实现。
package com.example.a.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import com.example.a.server.IoUtils;
import com.example.a.server.SimpleHttpServer;
public class HttpPost {
/** 请求URL */
public String url;
/** 请求参数 */
private Map mParamsMap = new HashMap();
/** 客户端Socket */
Socket mSocket;
public HttpPost(String url) {
this.url = url;
}
public void addParam(String key, String value) {
this.mParamsMap.put(key, value);
}
public void execute() {
try {
// 创建Socket连接
this.mSocket = new Socket(this.url, SimpleHttpServer.HTTP_PORT);
PrintStream outputStream = new PrintStream(
mSocket.getOutputStream());
BufferedReader inputStream = new BufferedReader(
new InputStreamReader(mSocket.getInputStream()));
final String boundary = "my_boundary_123";
// 写入header
writeHeader(boundary, outputStream);
// 写入参数
writeParams(boundary, outputStream);
// 等待返回数据
waitResponse(inputStream);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
IoUtils.closeSocket(mSocket);
}
}
private void writeHeader(String boundary, PrintStream outputStream) {
outputStream.println("POST /api/login/ HTTP/1.1");
outputStream.println("Content-Length: 123");
outputStream.println("Host:" + this.url + ":"
+ SimpleHttpServer.HTTP_PORT);
outputStream.println("Content-Type: nultipart/form-data; boundary="
+ boundary);
outputStream.println("User-Agent: android");
outputStream.println();
}
private void writeParams(String boundary, PrintStream outputStream) {
Iterator paramsKeySet = mParamsMap.keySet().iterator();
while (paramsKeySet.hasNext()) {
String paramName = paramsKeySet.next();
outputStream.println("--" + boundary);
outputStream.println("Content-Disposition: form-data; name="
+ paramName);
outputStream.println();
outputStream.println(mParamsMap.get(paramName));
}
// 结束符
outputStream.println("--" + boundary + "--");
}
private void waitResponse(BufferedReader inputStream) throws IOException {
System.out.println("请求结果:");
String responseLine = inputStream.readLine();
while (responseLine == null || !responseLine.contains("HTTP")) {
responseLine = inputStream.readLine();
}
// 输出Response
while ((responseLine = inputStream.readLine()) != null) {
System.out.println(responseLine);
}
}
public static void main(String[] args) {
HttpPost httpPost = new HttpPost("127.0.0.1");
// 设置两个参数
httpPost.addParam("username", "mr.simple");
httpPost.addParam("pwd", "my_pwd123");
// 执行请求
httpPost.execute();
}
}