我的目标是了解请求的工作原理。我目前的知识是,对于每个请求,都有一个预检请求。换句话说,如果你发送一个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
答案 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 客户端案例)