为什么 GET 请求会向 ServerSocket.accept() 发送 2 个请求?

时间:2021-06-06 20:08:18

标签: java

我的目标是了解请求的工作原理。我目前的知识是,对于每个请求,都有一个预检请求。换句话说,如果你发送一个GET请求,它会先发送一个预检请求,然后在收到服务器的响应后发送实际请求。

我的预期结果:我可以从 Postman 发送 GET 请求,或者至少我可以在浏览器中看到 OPTIONS 请求,但我似乎找不到它。

我的实际结果:我无法从 Postman 发送 GET 请求,但我可以从浏览器获得响应(第二次尝试后,如果您重新启动 HttpServer.main(),“Hello World”将以某种方式显示,然后如果你再次从浏览器发送请求,它将拒绝连接,冲洗并重复)

我所做的:我修改了代码以继续接受与 while(serverSocket.isBound() && !serverSocket.isClosed()) { //... until before the serverSocket.close(); } 的连接。当我从浏览器和邮递员发送请求时,LOGGER 记录了 2 连接请求,但它有效。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class HttpServer {
  private static final Logger LOGGER = LoggerFactory.getLogger(HttpServer.class);

  public static void main(String[] args) {
    LOGGER.info("Server starting...");
    try {
      ServerSocket serverSocket = new ServerSocket(8080);
      Socket socket = serverSocket.accept();

      LOGGER.info("* Connection accepted: " + socket.getInetAddress());

      InputStream inputStream = socket.getInputStream();
      OutputStream outputStream = socket.getOutputStream();

      final String CRLF = "\r\n"; // 13 10

      String messageBody = "Hello World";

      // RFC2616 Section 6 Response
      // response = Status-Line +
      //            * ((general-header
      //               | response-header
      //               | entity-header) + CRLF) +
      //             CRLF +
      //             [ message-body ]
      String response = "HTTP/1.1 200 OK" + CRLF + // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
              "Content-Length: " + messageBody.getBytes().length + CRLF + // end of entity-header fields
              CRLF + // end of Header Fields
              messageBody;

      outputStream.write(response.getBytes());
      LOGGER.info("Connection processing finished");
      inputStream.close();
      outputStream.close();
      socket.close();
      serverSocket.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

这是我使用 while(serverSocket.isBound() && !serverSocket.isClosed()) { // ... until before the serverSocket.close(); } 时得到的输出

03:17:05.514 [main] INFO HttpServer - Server starting...
03:17:10.786 [main] INFO HttpServer - * Connection accepted: /0:0:0:0:0:0:0:1
03:17:10.789 [main] INFO HttpServer - Connection processing finished
03:17:10.955 [main] INFO HttpServer - * Connection accepted: /0:0:0:0:0:0:0:1
03:17:10.955 [main] INFO HttpServer - Connection processing finished

我也尝试记录 inputStream.read()。我假设这应该发送预检请求,但它会立即发送实际请求。

GET / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: " Not;A Brand";v="99", "Microsoft Edge";v="91", "Chromium";v="91"
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36 Edg/91.0.864.41
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9

2 个答案:

答案 0 :(得分:0)

预检请求仅发生在 CORS(跨源)请求中 - 从一台主机到另一台主机。 如果你有一个,你可以在 Chrome 上按 f12 后在网络标签中看到它

答案 1 :(得分:0)

<块引用>

。我目前的知识是,对于每个请求,都有一个 预检请求。换句话说,如果您发送 GET 请求,它将 先发送预检请求,然后发送实际请求 接收来自服务器的响应。

这里你错了,不是针对每个请求,而是针对不是“简单请求”的请求https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests

<块引用>

我的预期结果:我可以从 Postman 发送 GET 请求,或者至少 我可以在浏览器中看到 OPTIONS 请求,但我似乎找不到

这是错误的,因为 Postman 不是网络浏览器。 CORS 的整个概念基于浏览器本身将执行预检请求的想法,因此如果您在站点 X.com 上实际上可以从 Y.com 获取资源,那么浏览器就是一个很好奇的浏览器。这不是服务器强制的。简单的代理配置可以完全抑制预检请求和任何其他跨站点检查。

当您从 Java 发出请求时 - 您处于与邮递员完全相同的位置。如果您不关心 CORS,则不会应用它。为此,您实际上必须发出额外的预检请求(这是一个简单的 OPTIONS 请求,带有适当的 CORS 相关标头)并停止或允许将来处理代码。

长话短说 - CORS 由浏览器强制执行,而不是服务器。拥有没有 CORS 的浏览器,没有人会注意到。

作为一个简单的类比,假设您是一个小孩,您的朋友告诉您从罐子里拿一块饼干。因为你实际上不知道你是否可以这样做(因为它是你妈妈的罐子),你问你妈妈是否可以从罐子里拿一块饼干(预检)。如果妈妈说可以,你就接受。如果她说你不能,你就拒绝做(继续,或拒绝)。如果饼干罐首先是你的,你就不会像木乃伊一样费心(它是同一个网站,不需要预检请求许可)。那时你会想到 cookie CORS 政策。

作为一个邮递员,你可以简单地忽略你妈妈要说的话,只需要拿 cookie - 在这两种情况下 cookie jar 都不会抱怨;)(邮递员案例,java 案例,任何其他 http 客户端案例)