返回介绍

00-02、理解 HTTP

发布于 2025-03-09 23:21:22 字数 9838 浏览 0 评论 0 收藏 0

HTTP 是基于 TCP 协议的。TCP 负责数据传输,而 HTTP 只是规范了 TCP 传输的数据的格式,而这个具体的格式,请见后面给出的资料。

HTTP 服务的底层实现就是 socket 编程。

下面基于 socket 编写一个简单的 HTTP server。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

class SocketHandler implements Runnable {

  final static String CRLF = "\r\n";   // 1

  private Socket clientSocket;

  public SocketHandler(Socket clientSocket) {
    this.clientSocket = clientSocket;
  }

  public void handleSocket(Socket clientSocket) throws IOException {

    BufferedReader in = new BufferedReader(
        new InputStreamReader(clientSocket.getInputStream())
        );
    PrintWriter out = new PrintWriter(
        new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream())),
        true
        );

    String requestHeader = "";
    String s;
    while ((s = in.readLine()) != null) {
      s += CRLF;  // 2 很重要,默认情况下 in.readLine 的结果中`\r\n`被去掉了
      requestHeader = requestHeader + s;
      if (s.equals(CRLF)){ // 3 此处 HTTP 请求头我们都得到了;如果从请求头中判断有请求正文,则还需要继续获取数据
        break;
      }
    }
    System.out.println("客户端请求头:");
    System.out.println(requestHeader);

    String responseBody = "客户端的请求头是:\n"+requestHeader;

    String responseHeader = "HTTP/1.0 200 OK\r\n" +
        "Content-Type: text/plain; charset=UTF-8\r\n" +
        "Content-Length: "+responseBody.getBytes().length+"\r\n" +
        "\r\n";
    // 4 问题来了:1、浏览器如何探测编码 2、浏览器受到 content-length 后会按照什么方式判断?汉字的个数?字节数?

    System.out.println("响应头:");
    System.out.println(responseHeader);

    out.write(responseHeader);
    out.write(responseBody);
    out.flush();

    out.close();
    in.close();
    clientSocket.close();

  }

  @Override
  public void run() {
    try {
      handleSocket(clientSocket);
    } catch(Exception ex) {
      ex.printStackTrace();
    }
  }

}

public class MyHTTPServer {

  public static void main(String[] args) throws Exception {

    int port = 8000;
    ServerSocket serverSocket = new ServerSocket(port);
    System.out.println("启动服务,绑定端口: " + port);

    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(30);  // 5

    while (true) {  // 6
      Socket clientSocket = serverSocket.accept();
      System.out.println("新的连接"
          + clientSocket.getInetAddress() + ":" + clientSocket.getPort());
      try {
        fixedThreadPool.execute(new SocketHandler(clientSocket));
      } catch (Exception e) {
        System.out.println(e);
      }
    }
  }
}

这是一个实现 HTTP 1.0 的服务器,对于所有的 HTTP 请求,会把 HTTP 请求头响应回去。 这个程序说明了 web 服务器处理请求的基本流程,JSP、Servlet、Spring MVC 等只是在 这个基础上嫁了许多方法,以让我们更方面的编写 web 应用。web 服务器不仅可以基于多线程, 也可以基于多进程、Reactor 模型等。

测试程序:
运行上面的程序。我们使用 curl 访问 http://127.0.0.1 (也可以使用浏览器):

$ curl -i http://127.0.0.1:8000
HTTP/1.0 200 OK
Content-Type: text/plain; charset=UTF-8
Content-Length: 106

客户端的请求头是:
GET / HTTP/1.1
User-Agent: curl/7.35.0
Host: 127.0.0.1:8000
Accept: */*

Java 程序输出:

启动服务,绑定端口: 8000
新的连接/127.0.0.1:36463
新的连接/127.0.0.1:36463 客户端请求头:
GET / HTTP/1.1
User-Agent: curl/7.35.0
Host: 127.0.0.1:8000
Accept: */*

响应头:
HTTP/1.0 200 OK
Content-Type: text/plain; charset=UTF-8
Content-Length: 106

程序解析:

// 1 :定义了 HTTP 头的换行符。

// 2 :in.readLine() 的结果默认不带换行符,这里把它加上。(这不是强制的,主要看你的程序逻辑需不需要, 这个程序的目标是把 HTTP 请求头响应回去)。

// 3 :此时 s 是一个空行,根据 HTTP 协议,整个请求头都得到了。

// 4 :Content-Length 的值是字节的数量。

// 5 :线程池。

// 6 :这个循环不停监听 socket 连接,使用 SocketHandler 处理连入的 socket,而这个处理是放在线程池中的。

HTTP 1.1:
HTTP 1.1 也是在这个思路的基础上实现的,即多个 HTTP 请求都在一个 TCP 连接中传输。对于 HTTP 1.1,如何区分出每个 HTTP 请求很重要, 比较简单的可以是用过 Content-Length 判断一条请求是否结束。如果一个 HTTP 请求数据较多,往往采用 Chunked 方式, 可以参考 Chunked transfer encoding

HTTP 相关资料

网络教程:
菜鸟教程-HTTP 教程
List of HTTP header fields
14 Header Field Definitions
HTTP header should use what character encoding?

书籍:
HTTP 权威指南 计算机网络 谢希仁 HTTP 是基于 TCP 协议的。TCP 负责数据传输,而 HTTP 只是规范了 TCP 传输的数据的格式,而这个具体的格式,请见后面给出的资料。

HTTP 服务的底层实现就是 socket 编程。

下面基于 socket 编写一个简单的 HTTP server。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

class SocketHandler implements Runnable {

  final static String CRLF = "\r\n";   // 1

  private Socket clientSocket;

  public SocketHandler(Socket clientSocket) {
    this.clientSocket = clientSocket;
  }

  public void handleSocket(Socket clientSocket) throws IOException {

    BufferedReader in = new BufferedReader(
        new InputStreamReader(clientSocket.getInputStream())
        );
    PrintWriter out = new PrintWriter(
        new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream())),
        true
        );

    String requestHeader = "";
    String s;
    while ((s = in.readLine()) != null) {
      s += CRLF;  // 2 很重要,默认情况下 in.readLine 的结果中`\r\n`被去掉了
      requestHeader = requestHeader + s;
      if (s.equals(CRLF)){ // 3 此处 HTTP 请求头我们都得到了;如果从请求头中判断有请求正文,则还需要继续获取数据
        break;
      }
    }
    System.out.println("客户端请求头:");
    System.out.println(requestHeader);

    String responseBody = "客户端的请求头是:\n"+requestHeader;

    String responseHeader = "HTTP/1.0 200 OK\r\n" +
        "Content-Type: text/plain; charset=UTF-8\r\n" +
        "Content-Length: "+responseBody.getBytes().length+"\r\n" +
        "\r\n";
    // 4 问题来了:1、浏览器如何探测编码 2、浏览器受到 content-length 后会按照什么方式判断?汉字的个数?字节数?

    System.out.println("响应头:");
    System.out.println(responseHeader);

    out.write(responseHeader);
    out.write(responseBody);
    out.flush();

    out.close();
    in.close();
    clientSocket.close();

  }

  @Override
  public void run() {
    try {
      handleSocket(clientSocket);
    } catch(Exception ex) {
      ex.printStackTrace();
    }
  }

}

public class MyHTTPServer {

  public static void main(String[] args) throws Exception {

    int port = 8000;
    ServerSocket serverSocket = new ServerSocket(port);
    System.out.println("启动服务,绑定端口: " + port);

    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(30);  // 5

    while (true) {  // 6
      Socket clientSocket = serverSocket.accept();
      System.out.println("新的连接"
          + clientSocket.getInetAddress() + ":" + clientSocket.getPort());
      try {
        fixedThreadPool.execute(new SocketHandler(clientSocket));
      } catch (Exception e) {
        System.out.println(e);
      }
    }
  }
}

这是一个实现 HTTP 1.0 的服务器,对于所有的 HTTP 请求,会把 HTTP 请求头响应回去。 这个程序说明了 web 服务器处理请求的基本流程,JSP、Servlet、Spring MVC 等只是在 这个基础上嫁了许多方法,以让我们更方面的编写 web 应用。web 服务器不仅可以基于多线程, 也可以基于多进程、Reactor 模型等。

测试程序:
运行上面的程序。我们使用 curl 访问 http://127.0.0.1 (也可以使用浏览器):

$ curl -i http://127.0.0.1:8000
HTTP/1.0 200 OK
Content-Type: text/plain; charset=UTF-8
Content-Length: 106

客户端的请求头是:
GET / HTTP/1.1
User-Agent: curl/7.35.0
Host: 127.0.0.1:8000
Accept: */*

Java 程序输出:

启动服务,绑定端口: 8000
新的连接/127.0.0.1:36463
新的连接/127.0.0.1:36463 客户端请求头:
GET / HTTP/1.1
User-Agent: curl/7.35.0
Host: 127.0.0.1:8000
Accept: */*

响应头:
HTTP/1.0 200 OK
Content-Type: text/plain; charset=UTF-8
Content-Length: 106

程序解析:

// 1 :定义了 HTTP 头的换行符。

// 2 :in.readLine() 的结果默认不带换行符,这里把它加上。(这不是强制的,主要看你的程序逻辑需不需要, 这个程序的目标是把 HTTP 请求头响应回去)。

// 3 :此时 s 是一个空行,根据 HTTP 协议,整个请求头都得到了。

// 4 :Content-Length 的值是字节的数量。

// 5 :线程池。

// 6 :这个循环不停监听 socket 连接,使用 SocketHandler 处理连入的 socket,而这个处理是放在线程池中的。

HTTP 1.1:
HTTP 1.1 也是在这个思路的基础上实现的,即多个 HTTP 请求都在一个 TCP 连接中传输。对于 HTTP 1.1,如何区分出每个 HTTP 请求很重要, 比较简单的可以是用过 Content-Length 判断一条请求是否结束。如果一个 HTTP 请求数据较多,往往采用 Chunked 方式, 可以参考 Chunked transfer encoding

HTTP 相关资料

网络教程:
菜鸟教程-HTTP 教程
List of HTTP header fields
14 Header Field Definitions
HTTP header should use what character encoding?

书籍:
HTTP 权威指南 计算机网络 谢希仁

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文