专栏首页朝雨忆轻尘自己动手实现一个WEB服务器

自己动手实现一个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/  版权所有,欢迎转载,转载请注明原文作者及出处。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring Boot使用Shiro实现登录授权认证

    1、Shiro是Apache下的一个开源项目,我们称之为Apache Shiro。它是一个很易用与Java项目的的安全框架,提供了认证、授权、加密、会话管理,与...

    朝雨忆轻尘
  • Java第三方支付接入案例(支付宝)

    到蚂蚁金服注册开发者账号,注册地址:https://open.alipay.com,用你的 支付宝 账号扫码登录,完善个人信息,选择服务类型。

    朝雨忆轻尘
  • Spring Boot + Spring Cloud 实现权限管理系统

    1. 新建 kitty-core 工程,把 kitty-admin 工程 page 包下的内容 迁移到 kitty-core 工程 page 包内。

    朝雨忆轻尘
  • ​SpringMVC 教程 - Handler Method

    由注解@RequestMapping注解修饰的处理请求的函数的签名非常的灵活,可以使用controller函数支持的一系列参数和返回值。

    代码拾遗
  • Java企业微信开发_05_消息推送之被动回复消息

    微信加解密包 下载地址:http://qydev.weixin.qq.com/java.zip      ,此包中封装好了AES加解密方法,直接调用方法即可。

    shirayner
  • Java Web之Spring Boot

    我一直在尝试一个人写demo(Android和iOS)时,如何模拟服务器端返回的 JSON 数据,总的来说,我试过以下几种: 纯Servlet开发,这种方式配合...

    YungFan
  • 经典设计模式之策略模式【如何重构聚合支付平台,对接【支付宝,微信,银联支付】】 顶 原 荐

    策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理,最终可以实现解决多重if判断问题。

    须臾之余
  • 经典设计模式之策略模式【如何重构聚合支付平台,对接【支付宝,微信,银联支付】】

    策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理,最终可以实现解决多重if判断问题。

    须臾之余
  • Java中list<Object[]>、list<Student>、list<Map<String,String>>排序

    1:list<Object[]>的排序 public static void main(String[] args) { // TODO Auto-gene...

    cMusketeer
  • Serializable详解(1):代码验证Java序列化与反序列化

    通过实现java.io.Serializable interface接口来序列化一个类。

    宜信技术学院

扫码关注云+社区

领取腾讯云代金券