gpt4 book ai didi

java - 使用 Socket 实现 HTTP 服务器 - 如何让它永远运行?

转载 作者:行者123 更新时间:2023-12-01 18:36:48 24 4
gpt4 key购买 nike

我尝试用 Java 实现一个简单的带有套接字的 HTTP 服务器。它的作用是从客户端浏览器接受文件名,在磁盘上打开该文件并在浏览器中打印出来。我当前的代码如下所示:

public class HTTPServer {

public String getFirstLine(Scanner s) {
String line = "";
if (s.hasNextLine()) {
line = s.nextLine();
}
if (line.startsWith("GET /")) {
return line;
}
return null;
}

public String getFilePath(String s) {
int beginIndex = s.indexOf("/");
int endIndex = s.indexOf(" ", beginIndex);
return s.substring(beginIndex + 1, endIndex);
}

/**
* @param args the command line arguments
*/
public static void main(String[] args) throws IOException {
Socket clientSocket = null;
int serverPort = 7777; // the server port

try {
ServerSocket listenSocket = new ServerSocket(serverPort);

while (true) {
clientSocket = listenSocket.accept();
Scanner in;
PrintWriter out;
HTTPServer hs;
in = new Scanner(clientSocket.getInputStream());
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream())));
hs = new HTTPServer();
// get the first line
String httpGetLine = hs.getFirstLine(in);
if (httpGetLine != null) {
// parse the file path
String filePath = hs.getFilePath(httpGetLine);
// open the file and read it
File f = new File(filePath);

if (f.exists()) {
// if the file exists, response to the client with its content
out.println("HTTP/1.1 200 OK\n\n");
out.flush();
BufferedReader br = new BufferedReader(new FileReader(filePath));
String fileLine;
while ((fileLine = br.readLine()) != null) {
out.println(fileLine);
out.flush();
}
} else {
out.println("HTTP/1.1 404 NotFound\n\nFile " + filePath + " not found.");
out.flush();
}
}
}

} catch (IOException e) {
System.out.println("IO Exception:" + e.getMessage());
} finally {
try {
if (clientSocket != null) {
clientSocket.close();
}
} catch (IOException e) {
// ignore exception on close
}
}
}

}

在 NetBeans 中运行它后,我打开浏览器并访问“localhost:7777/hello.html”(hello.html 是项目文件夹中的文件)。它只是显示页面正在加载。只有在 NetBeans 中停止服务器后,hello.html 的内容才会显示在浏览器中。

我希望我的服务器无限期地工作,响应一个又一个的 GET 请求并向客户端显示文件内容。我不确定代码的哪些部分应该放入 while(true) 循环中,哪些部分不应该放入。

最佳答案

即使对于简约的 HTTP 服务器来说,您的代码逻辑也非常不完整。您没有遵守 RFC 2616 管辖的基本规则.

您根本没有读取客户端的 HTTP 请求 header 。您只阅读第一行。 header 规定服务器需要如何运行、需要发送什么类型的响应、如何发送响应等。

您没有检查客户端的 HTTP 请求版本。不要向 HTTP 1.0 请求发送 HTTP 1.1 响应,但您可以向 HTTP 1.1 请求发送 HTTP 1.0 响应。

您没有检查客户端的 HTTP 请求是否有 Connection header 。默认情况下,HTTP 1.0 连接不使用保持 Activity 状态,因此 HTTP 1.0 客户端必须通过发送 Connection: keep-alive 来显式请求保持 Activity 状态。 header 。 HTTP 1.1 连接默认使用保持 Activity 状态,因此 HTTP 1.1 客户端可以通过发送 Connection: close 显式禁用保持 Activity 状态。标题代替。如果 HTTP 1.0 请求不包含 Connection: keep-alive header ,那么服务器必须假设 close被要求。如果 HTTP 1.1 请求不包含 Connection: close header ,那么服务器必须假设 keep-alive被要求。无论哪种方式,您都应该发回您自己的 Connection header 指示是否 keep-aliveclose正在被您的服务器使用(如果 keep-alive 被请求,您不必遵守它,但如果可能的话您应该这样做)。以close为例,发送响应后必须关闭套接字。

在您的 200 中响应,您没有发送 Content-Length header (尽管 Transfer-Encoding: chunked 方法更适合您正在执行的发送类型,但前提是客户端发送了 HTTP 1.1 请求。有关更多详细信息,请参阅 RFC 2616 Section 3.6.1),因此您必须关闭套接字(并发送Connection: close header )发送响应后,否则客户端无法知道何时到达 EOF。请参阅RFC 2616 Section 4.4了解更多详情。

您的代码一次只能服务一个客户端和一个请求。如果你希望你的服务器一次处理多个客户端,特别是如果你想支持 HTTP keep-alives(你应该这样做),那么你需要为每个连接的客户端创建一个工作线程,并且该线程可以服务很多客户端发送请求,直到断开连接。

尝试更像这样的东西(可能需要一些调整来编译、跟踪线程等):

public class HTTPClientThread extends Thread {

private Socket clientSocket;

public HTTPClientThread(Socket client) {
clientSocket = client;
}

public void run() {

Scanner in = new Scanner(clientSocket.getInputStream());
OutputStream out = clientSocket.getOutputStream();
PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(out)));
bool keepAlive = false;

do {
String requestLine = s.nextLine();
String line;
String connection = "";
keepAlive = false;

do {
line = s.nextLine();
if (line == "") {
break;
}
if (line.startsWith("Connection:") {
connection = line.substring(11).trim();
}
}
while (true);

int idx = requestLine.indexOf(" ");
if (idx == -1) {
pw.println("HTTP/1.0 400 Bad Request");
pw.println("Connection: close");
pw.println("");
pw.flush();
continue;
}

String httpMethod = line.substring(0, idx);
line = line.substring(idx+1);

idx = line.indexOf(" ");
if (idx == -1) {
pw.println("HTTP/1.0 400 Bad Request");
pw.println("Connection: close");
pw.println("");
pw.flush();
continue;
}

String httpVersion = line.subString(endIndex+1);
if (!httpVersion.equals("HTTP/1.0") && !httpVersion.equals("HTTP/1.1")) {
pw.println("HTTP/1.0 505 HTTP Version Not Supported");
pw.println("Connection: close");
pw.println("");
pw.flush();
continue;
}

if (connection != "") {
keepAlive = connection.equalsIgnoreCase("keep-alive");
}
else if (httpVersion.equals("HTTP/1.1")) {
keepAlive = true;
}
else {
keepAlive = false;
}

String filePath = line.substring(0, endIndex);
if (filePath.startsWith("/")) {
filePath = filePath.substring(1);
}

// open the file and read it
File f = new File(filePath);

if (!f.exists()) {
pw.println(httpVersion + " 404 Not Found");
pw.println("Content-Length: 0");
if (keepAlive) {
pw.println("Connection: keep-alive");
} else {
pw.println("Connection: close");
}
pw.println("");
pw.flush();
continue;
}

if (httpMethod != "GET") {
pw.println(httpVersion +" 405 Method Not Allowed");
pw.println("Allow: GET");
if (keepAlive) {
pw.println("Connection: keep-alive");
} else {
pw.println("Connection: close");
}
pw.println("");
pw.flush();
continue;
}

pw.println(httpVersion + " 200 OK");
pw.println("Content-Type: application/octet-stream");
pw.print("Content-Length: ");
pw.println(f.length());
if (keepAlive) {
pw.println("Connection: keep-alive");
} else {
pw.println("Connection: close");
}
pw.println("");
pw.flush();

FileInputStream fis = new FileInputStream(f);
BufferedOutputStream bw = new BufferedOutputStream(out);
byte[] buffer = new byte[1024];
int buflen;

while ((buflen = fis.read(buffer)) > 0) {
bw.write(buffer, 0, buflen);
bw.flush();
}
}
while (keepAlive);

clientSocket.close();
}
}
公共(public)类 HTTPServer { /** * @param args 命令行参数 */ 公共(public)静态无效主(字符串[] args)抛出IOException { 套接字 clientSocket = null; int 服务器端口 = 7777;//服务器端口 尝试 { ServerSocket ListenSocket = new ServerSocket(serverPort); 而(真){ clientSocket = ListenSocket.accept(); 新的 HTTPClientThread(clientSocket); } } catch (IOException e) { System.out.println("IO异常:"+ e.getMessage()); } } }

话虽如此,Java 有自己的 HTTP server class可用。

关于java - 使用 Socket 实现 HTTP 服务器 - 如何让它永远运行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21615273/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com