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

时间:2014-02-06 22:18:16

标签: java sockets httpserver

我尝试用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)循环中,哪些不是。

3 个答案:

答案 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-aliveclose(如果请求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资源吗?不应该有一些事件或线程睡眠吗?