自己动手实现一个WEB服务器

自己动手实现一个 Web Server

项目背景

最近在重温WEB服务器的相关机制和原理,为了方便记忆和理解,就尝试自己用Java写一个简化的WEB SERVER的实现,功能简单,简化了常规服务器的大部分功能和结构封装,但仍然保留从浏览器发送请求到将处理结果返回响应到浏览器的整个流程,现在把相关内容分享出来,供大家参考。

项目环境

IDE : eclipse 4.6.3

JDK : JDK1.8.0_131

Maven : Maven 3.5.2

项目结构

项目比较简单,就用一个普通的Java或Maven工程,引入JDK依赖即可。

工程下只有一个包,共包含六个文件。

WebServer : WEB 服务器主类,里面包含main方法,可直接运行启动服务器。

Request: 请求包装类,包含请求类型,请求URI。

Response:响应包装类,包含输出流,可向浏览器输出响应信息。

RequstParser:请求信息解析类,解析完成后返回一个Request。

ServiceDispacher:服务派发器,这里类似于Srping的DispatcherServlete。(不属于服务器部分)

TestController:模拟控制器返回信息。(不属于服务器部分)

其中ServiceDispacher和TestController,不属于服务器部分,这里为了方便测试,放在一个工程下。

实现流程

 实现流程大致如下:

1 创建服务端ServerSocket, 绑定一个 端口号

2 循环监听客户端请求,连接成功后返回一个Socket

3 开启一个新的线程,传入Socket处理当前请求

4 Web Server调用ServiceDispacher进行服务的分发

5 ServiceDispacher根据请求查找并调用相应的控制器

6 控制器方法执行返回结果,并将结果相应到浏览器

代码示例

下面给出完整的代码实现,代码注释已经解释的比较清楚了,在这里就不再多费口舌了,快来源码见。

1 WebServer.java

package com.louis.web.server;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Web Server
 * @author Louis
 */
public class WebServer {
    
    /**
     * 服务器启动端口
     */
    private int port = 8888;
    /**
     * 服务端Socket
     */
    private ServerSocket serverSocket;
    
    public WebServer() {
        init();
    }
    
    /**
     * 初始化服务端Socket
     */
    private void init() {
        try {
            // 创建服务端Socket
            serverSocket = new ServerSocket(port);
            System.out.println("服务端已启动,等待客户端连接..");
        } catch (IOException e) {
            e.printStackTrace();
        }    
    }

    /**
     * 启动服务器,监听并处理客户请求
     * @throws IOException
     */
    public void start() throws IOException {
        while (true) {
            // 侦听并接受客户请求
            Socket socket = serverSocket.accept();
            // 新启线程,处理客户请求
            new Thread() {
                @Override
                public void run() {
                    service(socket);
                }
            }.start();
        }
    }

    /**
     * 处理客户请求
     * @param socket
     */
    private void service(Socket socket) {
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            inputStream = socket.getInputStream();
            outputStream = socket.getOutputStream();
            // 读取请求信息内容
            Request request = new RequestParser().parse(inputStream);
            Response response = new Response(outputStream);
            service(request, response);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭连接
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println("接收到客户端连接, " + socket.getInetAddress() + ":" + socket.getPort());
    }

    /**
     * 处理客户请求, 把请求交给框架派遣服务,类似Spring的DispatcherServlet
     * @param request
     * @param response
     */
    private void service(Request request, Response response) {
        ServiceDispatcher serviceDispatcher = new ServiceDispatcher();
        serviceDispatcher.dispatcher(request, response);
    }

    public static void main(String[] args) {
        try {
            new WebServer().start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2 Request.java

package com.louis.web.server;

/**
 * Request
 * @author Louis
 */
public class Request {
    /**
     * 请求方式: GET\POST\DELETE..
     */
    private String type;
    /**
     * 请求URI
     */
    private String uri;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getUri() {
        return uri;
    }

    public void setUri(String uri) {
        this.uri = uri;
    }

}

3 Response.java

package com.louis.web.server;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * Response
 * 
 * @author Louis
 */
public class Response {
    private OutputStream output;

    public Response(OutputStream output) {
        this.output = output;
    }

    /**
     * 输出文本信息
     * @param text
     * @throws IOException
     */
    public void writeText(String text) {
        FileInputStream fis = null;
        try {
            output.write("HTTP/1.1 200 OK\n".getBytes());
            output.write("Content-Type: text/html; charset=UTF-8\n\n".getBytes());
            output.write(text.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4 RequestParser.java

package com.louis.web.server;
import java.io.InputStream;

/**
 * Request Parser
 * @author Louis
 */
public class RequestParser {
    private final static int BUFFER_SIZE = 1024;

    /**
     * 解析请求
     * @param inputStream
     * @return Request
     */
    public Request parse(InputStream inputStream) {
        Request request = new Request();
        // 读取请求信息
        String requestMessage = readRequestMessage(inputStream);
        // 解析请求方式
        String type = parseType(requestMessage);
        request.setType(type);
        // 解析请求类型
        String uri = parseUri(requestMessage);
        request.setUri(uri);
        return request;
    }

    /**
     * 读取请求信息
     * @param input
     * @return
     */
    private String readRequestMessage(InputStream input) {
        StringBuffer requestMessage = new StringBuffer();
        int readLength = 0;
        byte[] buffer = new byte[BUFFER_SIZE];
        try {
            readLength = input.read(buffer);
        } catch (Exception e) {
            e.printStackTrace();
            readLength = -1;
        }
        for(int i = 0; i < readLength; i++) {
            requestMessage.append((char) buffer[i]);
        }
        return requestMessage.toString();
    }
    
    /**
     * 解析请求方式
     * @param requestString
     * @return
     */
    private String parseType(String requestString) {
        int index = 0;
        index = requestString.indexOf(' ');
        if (index != -1) {
            return requestString.substring(0, index);
        }
        return null;
    }

    /**
     * 解析请求类型
     * @param requestString
     * @return
     */
    private String parseUri(String requestString) {
        int index1, index2;
        index1 = requestString.indexOf(' ');
        if (index1 != -1) {
            index2 = requestString.indexOf(' ', index1 + 1);
            if (index2 > index1)
                return requestString.substring(index1 + 1, index2);
        }
        return null;
    }

}

5 ServiceDispatcher.java

package com.louis.web.server;

/**
 * 根据请求类型和URI找到对应的控制器,将请求交给控制器处理
 * 
 * @author Louis
 */
public class ServiceDispatcher {

    /**
     * 转发处理请求
     * @param request
     * @param response
     */
    public void dispatcher(Request request, Response response) {
        execController(request, response);
    }

    /**
     * 根据请求类型及URI等请求信息,找到并执行对应的控制器方法后返回
     * 此处直接返回一个控制器,模拟查找和执行控制器方法的过程
     * @param request
     * @param response
     * @return
     */
    private void execController(Request request, Response response) {
        String text = getControllerResult(request, response);
        StringBuilder sb = new StringBuilder();
        sb.append("请求类型: " + request.getType());
        sb.append("<br/>请求URI: " + request.getUri());
        sb.append("<br/>返回结果: " + text);
        // 输出控制器返回结果
        response.writeText(sb.toString());
    }

    /**
     * 模拟查找和执行控制器方法并返回结果
     * @param request
     * @param response
     * @return
     */
    private String getControllerResult(Request request, Response response) {
        String text = "";
        String uri = request.getUri();
        String [] uriArray = uri.split("\\/");
        if(uriArray.length != 3) {
            text = "请求路径没有找到相关匹配服务. ";
        } else if("test".equalsIgnoreCase(uriArray[1])) {
            TestController testController = new TestController();
            if("test1".equalsIgnoreCase(uriArray[2])) {
                text = testController.test1();
            } else if("test2".equalsIgnoreCase(uriArray[2])) {
                text = testController.test2();
            } else {
                text = "请求路径没有找到相关匹配服务. ";
            }
        } else {
            text = "请求路径没有找到相关匹配服务. ";
        }
        return text;
    }

}

6 TestController.java

package com.louis.web.server;

public class TestController {

    public String test1() {
        return "TestController.test1() 调用成功";
    }
    
    public String test2() {
        return "TestController.test2() 调用成功";
    }
}

启动测试

直接运行WebServer的main方法即可,控制台输出“服务端已启动,等待客户端连接..”, 启动成功。

启动完成之后,浏览器访问分别访问不同路径,查看响应结果。

如下图所示:

http://localhost:8888/test/test1

http://localhost:8888/test/test2

http://localhost:8888/test/test3

http://localhost:8888/test/mack

我们看到,当调用 /test/test1 和 /test/test2 的时候,控制器 TestController 的 test1 和 test2 方法相应被调用成功。而输入 test3 获取其他不存在的服务的时候,将会得到“请求路径没有找到相关匹配服务”的响应。

源码下载

码云:https://gitee.com/liuge1988/web-server


作者:西风小马
出处:https://www.cnblogs.com/xifengxiaoma/ 
版权所有,欢迎转载,转载请注明原文作者及出处。

猜你喜欢

转载自www.cnblogs.com/xifengxiaoma/p/9451044.html