4.1.3.简单模拟HTTP服务器

HTTP实际上是基于TCP的应用层协议,他在更高层次封装了TCP的使用细节,使网络请求操作更为易用。TCP连接是因特网上基于流的可靠连接,他为HTTP提供了一条可靠的比特传输管道。从TCP连接一端输入的字节会从另一端以原有的顺序、正确地传送出来。如果所示:
4.1.3.简单模拟HTTP服务器_第1张图片
TCP的数据是通过名为IP分组(或IP数据报)的小数据块来发送的。这样的话,如图的HTTP协议所示,HTTP就是“HTTP over TCP over IP”这个“协议栈”中的最顶层了。
4.1.3.简单模拟HTTP服务器_第2张图片
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();
    }
}

你可能感兴趣的:(通讯协议)