我尝试用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)
循环中,哪些不是。
答案 0 :(得分:2)
即使对于简约的HTTP服务器,您的代码逻辑也非常不完整。您没有遵循RFC 2616所规定的基本规则。
您根本没有阅读客户端的HTTP请求标头。你只是在读第一行。标题规定了服务器需要如何表现,需要发送什么类型的响应,如何发送响应等等。
您没有检查客户端的HTTP请求版本。不要向HTTP 1.0请求发送HTTP 1.1响应,但是您可以向HTTP 1.1请求发送HTTP 1.0响应。
您没有检查客户端的HTTP请求是否具有Connection
标头。 HTTP 1.0连接默认情况下不使用keep-alives,因此HTTP 1.0客户端必须通过发送Connection: keep-alive
标头明确要求保持活动状态。 HTTP 1.1连接默认使用keep-alives,因此HTTP 1.1客户端可以通过发送Connection: close
标头显式禁用保持活动状态。如果HTTP 1.0请求不包含Connection: keep-alive
标头,则服务器必须假定已请求close
。如果HTTP 1.1请求不包含Connection: close
标头,则服务器必须假定keep-alive
被请求。无论哪种方式,您都应该发回自己的Connection
标头,指出您的服务器是否正在使用keep-alive
或close
(如果请求keep-alive
,则您不必遵守它,但你应该尽可能)。在close
的情况下,您必须在发送响应后关闭套接字。
在200
响应中,您没有发送Content-Length
标头(尽管Transfer-Encoding: chunked
方法更适合您正在进行的发送类型,但仅限于客户端发送HTTP 1.1请求。有关详细信息,请参阅RFC 2616 Section 3.6.1,因此必须在发送响应后关闭套接字(并发送Connection: close
标头),否则客户端无法知道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 class HTTPServer {
/**
* @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();
new HTTPClientThread(clientSocket);
}
} catch (IOException e) {
System.out.println("IO Exception:" + e.getMessage());
}
}
}
据说,Java有自己的HTTP server class可用。
答案 1 :(得分:1)
完成后,您需要关闭套接字。
答案 2 :(得分:1)
while(true)语句将无限期地执行这两行。这不会占用所有的CPU资源吗?不应该有一些事件或线程睡眠吗?