手写一个Web服务器,极简版Tomcat

网络传输是通过遵守HTTP协议的数据格式来传输的。

HTTP协议是由标准化组织W3C(World Wide Web Consortium,万维网联盟)和IETF(Internet Engineering Task Force,国际互联网工程任务组)推动和制定的,最后形成RFC文档 [RFC1945](HTTP/1.0)和RFC2616[HTTP/1.1]

可以响应HTTP请求的程序就是Web Server,实现方法并没有统一规范,可以说HTTP协议就是Web Server和网络传输数据之间的接口规范。以后我们会看到,Web Server和处理程序之间的规范是CGI。

一些常见的Web Server有Apache、Nginx、Microsoft IIS、Google GWS。

我们这里手写一个Web Server。

首先我们需要接收网络的请求,根据OSI七层模型,网络的实现是分层的。Web Server属于应用层,要调用下一层(传输层,TCP/IP)的接口,来得到对方发来的网络请求,这个接口就叫做Socket。《计算机网络:自顶向下方法》中写到:“Socket是同一台主机内应用层与传输层之间的接口。因为Socket是建立网络应用程序的可编程接口,所以Socket也被称为应用程序和网络之间的API”。

一、最简单的Web Server

要编写一个Web Server,我们调用系统Socket接口,监听8180端口上收到的网络数据,打印到控制台,并给出一个符合http标准的响应,代码如下:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
 
public class Server {
    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }
 
    public void start() {
        try {
            ServerSocket serverSocket = new ServerSocket(8180); // 调用系统socket接口,监听某端口的socket请求
            Socket httpInputSocket = serverSocket.accept(); // 在这里,程序线程等待socket输入
            this.printHttpRequest(httpInputSocket);
            this.responseHttpRequest(httpInputSocket);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 打印来自外部的http请求
     *
     * @param httpInputSocket
     */
    private void printHttpRequest(Socket httpInputSocket) {
        try {
            // 调用系统IO,打印请求
            BufferedReader br = new BufferedReader(new InputStreamReader(httpInputSocket.getInputStream()));
            StringBuilder receivedHttp = new StringBuilder();
            String line = null;
            while ( !"".equals(line = br.readLine())) {
                receivedHttp.append(line);
                receivedHttp.append(System.getProperty("line.searator", "\n"));
            }
            System.out.println(receivedHttp.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 返回页面供请求方显示
     *
     * @param httpInputSocket
     */
    private void responseHttpRequest(Socket httpInputSocket) {
 
        // 创建响应体
        StringBuilder contextText = new StringBuilder();
        contextText.append(
                "<html>" +
                        "<head>" +
                        "<title>Build A Web Server</title>" +
                        "</head>" +
                        "<body>Hello World, This is my page</body>" +
                        "</html>");
 
        // 创建响应头
        StringBuilder headText = new StringBuilder();
        headText.append("HTTP/1.1").append(" ").append("200").append(" ").append("OK").append("\n");
        headText.append("Server:myServer").append(" ").append("0.0.1v").append(" ");
        headText.append("Date:Sat,"+" ").append(new Date()).append("\n");
        headText.append("Content-Type:text/html;charset=UTF-8").append("\n");
        headText.append("Content-Length:").append(contextText.toString().getBytes().length).append("\n");
 
        // http response
        StringBuilder responseText = new StringBuilder();
        responseText.append(headText).append("\n").append(contextText);
        System.out.println(responseText);
 
        BufferedWriter bw = null;
        try {
            bw = new BufferedWriter(new OutputStreamWriter(httpInputSocket.getOutputStream()));
            bw.write(responseText.toString());
            bw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

至此,一个100行不到的Web Server就写好了,运行一下,在浏览器里访问:http://localhost:8180/,可以看到结果:

程序控制台也正确打印:

这个Web Server会对请求到8180端口的所有请求都返回同样的页面,比如我们访问http://localhost:8180/really?foo=123,结果也是一样的:

 

从这个例子可以看出,通过HTTP请求进行访问,可以不用创建socket客户端。 注意printHttpRequest和responseHttpRequest都要调用才能显示效果,就是说先执行socket 输入流的读取,然后再执行socket输出流输出

posted on 2020-05-27 15:39  芝麻开花——节节高  阅读(492)  评论(0编辑  收藏  举报

导航