一步一步实现一个Web Server-03

8 篇文章 0 订阅

上一期的时候,我们完成了http报文的解析,但是解析出来的参数还散落在各处,我们返回给浏览器的内容也没进行规整。相信搞过web后端开发的同志们对HttpServletRequest和HttpServletResponse都不陌生,同样地,我们也搞一个Request和Response出来,方便起见,我们就直接写两个类,先不进行封装了。走起,老铁们!

首先,我们需要把解析出来的参数进行存放,细想一下,每个请求的参数肯定都不一样撒,而且有的参数还有多个,有多个也好办,问题是有时候参数是一对多的,比如兴趣爱好这种字段,欧美、日韩、亚洲无码、华人精品。。毕竟各位老铁们口味不同嘛。而且后面我们还需要根据字段拿到参数值,所以很自然就是用map来搞。首先弄个Request出来。

    private Socket client;
    /**请求方法*/
    private String methodName;
    /**请求路径*/
    private String requestUrl;
    /**请求协议版本*/
    private String protocolVersion;
    /**请求参数的封装*/
    private Map<String,List<String>> parameterMap;
    /**构造函数*/
    public Request(Socket client) {

        this.client = client;
        /**初始化用于保存参数的容器*/
        this.parameterMap = new HashMap<>();
        /**用于接收流数据*/
        byte[] bytes = new byte[1024 * 1024 * 50];
        try {
            int dataLength = client.getInputStream().read(bytes);
            /**将流数据转化为字符串*/
            String content = new String(bytes,0,dataLength);
            parseHttpContent(content);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

构造函数里边,我们就从socket里把数据挨盘拿出来,然后做解析就好了。

最后,将参数放到map就可以了。

private void putKV2Map(String key, String value) {

        /**如果存在key,则进行参数追加*/
        if (parameterMap.containsKey(key)) {
            parameterMap.get(key).add(value);
            return;
        }
        /**不存在则创建*/
        List<String> valueList = new ArrayList<>();
        valueList.add(value);
        parameterMap.put(key,valueList);
    }

这样请求部分就暂时先这样,我们再开看响应部分。

当前版本,我们的响应逻辑是直接放在了服务器的启动类的doService方法里,写写demo还行,逻辑稍微复杂,就要一顿if else for循环的操作。。。不利于扩展,不利于重构,不方便使用。所以需要抽象一番。

public class Response {
    
    /**通信管道*/
    private Socket client;
    /**数据回写*/
    private BufferedWriter bufferedWriter;
    /**写回给浏览器的内容*/
    private StringBuilder contentToClient;
    /**写回内容的长度*/
    private int len;

    public Response(Socket client) {
        this.client = client;
        try {
            bufferedWriter = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("响应创建失败,socket忙碌或已关闭");
        }
    }

根据前面的学习,我们知道往浏览器写数据,需要遵从格式 header,body的要求;我们还知道http有个statusCode,不同code对应不同的响应状态。于是
 

    /**
     * 根据响应值创建返回内容
     * @param code
     * @return
     */
    private String createHeadInfo(int code) {

        /**回送一个信息给浏览器,因为要交给浏览器显示,所以要按照http协议的报文格式组织数据*/
        StringBuilder headerInfo = new StringBuilder();
        headerInfo.append("HTTP/1.1").append(Constant.BLANK).append(code).append(Constant.CRLF);

        /**根据不同status添加不同响应内容*/
        switch (code) {
            case 200:
                headerInfo.append("OK").append(Constant.CRLF);
                break;
            case 404:
                headerInfo.append("NOT FOUND").append(Constant.CRLF);
                break;
            case 500:
                headerInfo.append("SERVER INTERNAL ERROR").append(Constant.CRLF);
                break;
        }

        /**添加header剩余内容*/
        headerInfo.append("Content-Type: text/html;charset=utf-8").append(Constant.CRLF)
                .append("Date:"+new Date()).append(Constant.CRLF)
                .append("Content-Length: "+len).append(Constant.CRLF)
                .append("Server: LynServer/1.1").append(Constant.CRLF).append(Constant.CRLF);

        return headerInfo.toString();
    }

为了方便使用,我们给Response加一个print方法:每次调用都返回自身

    /**
     * 包装返回体内容
     * @param content
     * @return
     */
    private Response print(String content) {
        contentToClient.append(content);
        len += content.getBytes().length;
        return this;
    }

这样,我们服务器的doService方法就变成如下这样了:

        /**创建请求对象*/
        Request request = new Request(client);
        /**处理请求对象中的参数*/
        String currentUserName = request.getParameterByName("username");

        if (request.getRequestUrl().contains("/index")) {
            /**创建响应对象*/
            Response response = new Response(client);
            response.println("<html>");
            response.println("<body>");
            response.println("<h1>您好,"+currentUserName+",欢迎使用 Lyn Server ");
            response.println("</h1>");
            response.println("</body>");
            response.println("</html>");

            response.send2Client(200);

到这里,大家是不是找到一些感觉了?我们在doService方法里,只关注请求和响应了。做到了细节的屏蔽,我们来做一下测试:

我们看下服务器给我们的返回:

这样我们就顺利获取到了服务器的返回信息。错误页面呢?

浏览器输入下 http://localhost:8008/sdfwer ,

最后试一下我们的表单页面:

http://localhost:8008/login.do

全部OK,到这里我们就完成了请求和响应的封装。我们回过头看看,服务器代码精简了不少,但现在还存在什么问题呢?

现在首页、登录、错误页面的逻辑还是在doService方法里,按照职责单一原则,服务器只应该负责转发,根据不同的url将请求派发给不同的XXX去处理,那这些XXX是啥呢?http那帮人给取了个名字——Servlet,就是运行在服务器端的Applet。下面一期,我们就把Servlet引入进来,进一步解耦我们的服务器实现。今天就到这里,我们下期再见!

本期代码地址:https://github.com/justein/lyn-server/tree/master/server-wrapper

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鹏鹏超人

如果觉得不错,请我喝杯茶如何?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值