ikki@github.io:~$

解剖http协议

解剖HTTP协议

什么是HTTP

相比于称呼HTTP 是一个应用层协议, 我个人跟倾向于 称呼http 是一种类似json文字语法。http 分为请求和响应,其格式如下

request
POST /path HTTP/1.1
Content-Type: application/x-www-form-urlencoded

key1=value1&key2=value2
response
HTTP/1.1 200 OK
Content-Type: application/json

{
    "key":"value"
}
  • 其中,请求报文中的第一行称为 请求行 , 第二行到空行为止称为 请求头 , 从空行的下一行开始到结束称为请求体
  • 响应报文中的第一行称为 状态行 , 第二行到空行为止称为 响应头 , 从空行的下一行开始到结束称为 响应体

手工实现一个HTTP 请求

try {
    Socket socket = new Socket("www.baidu.com",80);
    StringBuilder sb = new StringBuilder("GET / HTTP/1.1\r\n");
    sb.append("Accept: text/html\r\n");
    sb.append("Host: www.baidu.com\r\n");
    sb.append("Connection: keep-alive\r\n");
    sb.append("\r\n");
    OutputStream outputStream = socket.getOutputStream();
    outputStream.write(sb.toString().getBytes());
    outputStream.flush();

    InputStream is = socket.getInputStream();
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] bytes = new byte[1024];
    int len = -1;
    while ((len = is.read(bytes)) != -1) {
        baos.write(bytes, 0, len);
    }
    System.out.println(new String(baos.toByteArray()));

} catch (IOException e) {
    e.printStackTrace();
}
  • 由此可见,HTTP 协议本质上是一个阅读友好的文字协议,同理 HTTP 响应也是一个文字协议
  • 所以,JDK 中的HttpURLConnection类,Apache 的 httpcomponents 亦或是OkHttp 本质上都是 按固定语法格式解析字符串的解析逻辑

被玩坏的HTTP

以下代码,模拟了一个HTTP 响应,并使用浏览器访问 http://localhost:8080,

try {
    ServerSocket serverSocket = new ServerSocket(8080);
    Socket socket = null;
    while (true) {
        socket = serverSocket.accept();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

        StringBuilder stringBuilder = new StringBuilder();
        String line = null;

        while ((line = bufferedReader.readLine()) != null && !line.equals("")) {
            stringBuilder.append(line).append("<br/>");
        }
        System.out.println(stringBuilder.toString());
        PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
        printWriter.println("HTTP/1.1 1000 摸摸哒");
        printWriter.println("Content-Type: application/json; charset=utf-8");
        printWriter.println("");
        printWriter.println("{\n" +
            "    \"code\":\"1000\",\n" +
            "    \"status\":\"successful\"\n" +
            "}");
        printWriter.flush();
        printWriter.close();
    }

} catch (IOException e) {
    e.printStackTrace();
}
  • 可以发现,浏览器并没有出现崩溃和其他异常,是因为,虽然返回了一个非常规的1000 状态码, 但这是合乎http 的语法的

深入思考

曾经有前端同事错误的认为, HTTP 状态码 200 是表示网络通畅

但是从上面的代码看来,状态码的本质其实就是一个字符串,它本身并不能表示网络状态

而且 HTTP 似乎是被封装在一个Socket 中传输的。

所以万一网络出了问题,HTTP 是无法感知的,并且,即使服务器返回了200 状态, 客户端也无法收到的

那么Socket 是一个什么东西呢? Socket 本质上是一个 TCP 连接,它是由java 封装抽象出来,并由上层应用使用的对象。

总结

  • 打个比方,HTTP 是写信的固定格式,比如 开头必须是”亲爱的”开头,结尾必须写祝语和署名一般

  • TCP 则是 邮递系统,根据邮编和地址送信

  • 网卡、网线则是邮递车和马路,是邮递系统必须依赖的硬件基础设施