手写webserver

补充知识点
反射:把java类中的各种结构方法(方法、属性、构造器、类名)映射成一个个的java对象
1、获取Class对象
三种方式:
对象…getClass()
Iphone iphone = new Iphone(); Class cls = iphone.getClass();
类.class()
clz = iphone.getClass();
推荐:Class.forName(“包名.类名”)
Class.forName(“完整路径”);
2、可以动态创建对象(用构造器创建)
Iphone iphone2 = (Iphone)clz.getConstructor().newInstance();
HTTP协议
请求协议:
每个请求由三部分组成

  • 1.请求行 请求方法、资源路径、协议/版本号(CRLF)
  • ep:GET /index.html HTTP/1.0(CRLF)   //请求行以CRLF结束 CR:回车符,asc编码中对应数字13  LF:换行符,asc编码中对应数字10
    
  • 2.消息头(Request Header)
  • 3.消息正文

响应协议:

  • 1.状态行 协议/版本号、状态码、状态描述 (CRLF)
  • ep:HTTP/1.0 、200、OK(CRLF)   
    
  • 2.响应头(Response Header)
  • 3.响应正文(与响应头之间有空行)

具体实现
获取请求协议
1、创建ServerSocket
2、建立连接获取Socket
3、通过输入流获取请求协议(Get与Post不一致的地方)

package com.lijing.server.core;

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


public class Server {
    private ServerSocket serverSocket;
    private boolean isRunning;
    public static  void main(String[] args){
        Server server = new Server();
        server.start();
    }
    //启动服务
    public void start(){
        try {
            serverSocket = new ServerSocket(8888);
            isRunning = true;
            receive();
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("服务器启动失败");
            stop();
        }
    }
    //接受连接处理
    public void receive(){
        while (isRunning) {
            try {
                Socket client = serverSocket.accept();
                System.out.println("一个客户端建立了连接");
                //多线程处理
                new Thread(new Dispatcher(client)).start();
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("客户端错误");
            }
        }
    }
    //停止服务
    public void stop(){
        isRunning =false;
        try {
            this.serverSocket.close();
            System.out.println("服务器已停止");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

返回响应协议
1、准备内容
2、获取字节数的长度
3、拼接响应协议 (注意空格与换行)
4、使用输出流输出
封装响应协议 Response
1、动态添加内容print
2、累加字节数的长度
3、根据状态吗拼接响应头协议
4、根据状态码统一推送出去
调用处:动态调用print+传入状态码推送

package com.lijing.server.core;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Date;
/*
Response
1、动态添加内容print
2、累加字节数的长度
3、根据状态码拼接响应头协议
4、根据状态码同一推送出去
 */
public class Response {
    BufferedWriter bw;
    //正文
    private  StringBuilder content;
    //协议头信息(状态行与请求头 回车)信息
    private  StringBuilder headInfo;
    private  int len ;//正文的字节数
    private final String BLANK = " ";
    private final String CRLF="\r\n";

    public Response(){
         content = new StringBuilder();
         headInfo = new StringBuilder();
         len =0;

    }
    public Response(Socket client){
        this();
        try {
            bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
        } catch (IOException e) {
            e.printStackTrace();
            headInfo = null;
        }
    }

    public Response(OutputStream os){
        this();
        bw = new BufferedWriter(new OutputStreamWriter(os));
    }
    //动态添加内容
    public Response print(String info){
        content.append(info);
        len+=info.getBytes().length;
        return this;
    }
    public Response println(String info){
        content.append(info).append(CRLF);
        len+=(info+CRLF).getBytes().length;
        return this;
    }
    //推送响应信息
    public  void pushToBrowser(int code ) throws IOException {
        if(null==headInfo){
            code = 505;
        }
        creatHeadInfo(code);//先有内容才构建头信息
        bw.append(headInfo);
        bw.append(content);
        bw.flush();
    }

    //构建头信息
    private  void creatHeadInfo(int code){
        //1、响应行:HTTP/1.1 200 OK
        headInfo.append("HTTP/1.1").append(BLANK);
        headInfo.append(code).append(BLANK);
        switch (code){
            case 200:
                headInfo.append("OK").append(CRLF);
                break;
            case 404:
                headInfo.append("NOT FOUNF").append(CRLF);
                break;
            case 505:
                headInfo.append("SERVER ERROR").append(CRLF);
                break;
        }
        //2、响应头(最后一行存在空行)  字节数
        headInfo.append("Date:").append(new Date()).append(CRLF);
        headInfo.append("Server06:").append("server:Server02/0.0.1;charset=GBK").append(CRLF);
        headInfo.append("Content-type:test/html").append(CRLF);
        headInfo.append("Content-length:").append(len).append(CRLF);
        headInfo.append(CRLF);

    }
}

封装请求信息Request
1、通过分解字符串获取method,URL和请求参数(POST请求参数可能在请求体中还存在)
2、通过Map封装请求参数 两个方法(考虑一个参数多个值convertMap(),和中文decode)

package com.lijing.server.core;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.*;

/*
封装请求协议:封装请求参数为Map
 */
public class Request {
    private final String CRLF = "\r\n";
    //协议信息
    private String requestInfo;
    //请求方式
    private String method;
    //请求url
    private String url;
    //请求参数
    private String queryStr;
    //存储参数
    private Map<String, List<String>> parameterMap;

    public Request(Socket client) throws IOException {
        this(client.getInputStream());
    }

    public Request(InputStream is) {
        parameterMap = new HashMap<String, List<String>>();
        byte[] datas = new byte[1024 * 1024];
        int len = 0;
        try {
            len = is.read(datas);
            this.requestInfo = new String(datas, 0, len);
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        //分解字符串
        this.parseRequestInfo();
    }

    //分解字符串
    private void parseRequestInfo() {
        System.out.println("----分解开始----");
        //System.out.println(requestInfo);
        System.out.println("-----1、获取请求方式:开头到第一个/-----");
        this.method = this.requestInfo.substring(0, this.requestInfo.indexOf("/")).toLowerCase().trim();
        System.out.println(method);
        System.out.println("-----1、获取url:开头到第一个/到HTTP/-----");
        System.out.println("可能包含请求参数?前面的为url");
        //1、获取/的位置
        int startIdx = this.requestInfo.indexOf("/") + 1;
        //2、获取HTTP/的位置
        int endIdx = this.requestInfo.indexOf("HTTP/");
        //3、分割字符串
        this.url = this.requestInfo.substring(startIdx, endIdx).trim();
        System.out.println(this.url);
        //4、获取?的位置
        int queryIdx = this.url.indexOf("?");
        if (queryIdx >= 0) {//表示存在请求参数
            String[] urlArray = this.url.split("\\?");
            this.url = urlArray[0];
            queryStr = urlArray[1];
        }
        System.out.println(this.url);
        System.out.println("---3、获取请求参数:如果是Get已经获取。如果是Post可能在请求体中------");

        if (method.equals("post")) {
            String qStr = this.requestInfo.substring(this.requestInfo.lastIndexOf(CRLF)).trim();
            if (null == queryStr) {
                queryStr = qStr;
            } else {
                queryStr += "&" + qStr;
            }
        }
        queryStr = null == queryStr ? "" : queryStr;
        System.out.println(method + "->" + url + "->" + queryStr);
        //转成Map fav=1&fav=2&uname=lijing&others=
        convertMap();
    }

    //处理请求参数为Map
    public void convertMap() {
        //1、分割字符串
        String[] KeyValues = this.queryStr.split("&");
        for (String quertstr : KeyValues) {
            //再次分割字符串=
          //  System.out.println("jdjdksa--->"+quertstr);
            String[] kv = quertstr.split("=");
            kv = Arrays.copyOf(kv, 2);//保证有两个值 参数others=null时赋空
            //获取key和value
            String key = kv[0];
            String value = kv[1]==null?null:decode(kv[1],"utf-8");

            //存储到map中  现在map容器中找是否有相同key值若有加入其对应value,若无加入容器中
            //System.out.println("hhhhhh"+kv[0]+"--->"+kv[1]);
            if (!parameterMap.containsKey(key)) {//第一次
                parameterMap.put(key, new ArrayList<String>());
            }
            parameterMap.get(key).add(value);
        }
    }
    //处理中文
    private String decode(String value,String enc){
        try {
            return java.net.URLDecoder.decode(value,enc);//enc为传入处理的字符集
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /*
    通过name获取对应的多个值
     */
    public String[] getParameterValues(String key) {
        List<String> values = this.parameterMap.get(key);
        if (null == values || values.size() < 1) {
            return null;
        }
        return values.toArray(new String[0]);//转换为数组
    }
    /*
        通过name获取对应的一个值
     */
    public String getParameter(String key) {
        String[] values = getParameterValues(key);
        return values == null?null:values[0];
    }

    public String getUrl() {
        return url;
    }

    public String getMethod() {
        return method;
    }


    public String getQueryStr() {
        return queryStr;
    }

}

引入Servlet
1、将业务代码解耦到对应的业务类中(具体的Servlet,这也是web阶段主要写的内容。ep:登陆业务
先写好loginServlet 再在配置文件web.xml中进行相应配置
即配置"url-pattern" /login 对外公布接口
写前台页面login.html时在action中加入路径

package com.lijing.server.core;
/*
服务器小脚本接口
 */
public interface Servlet {
    void service(Request request,Response response);
}

业务类 LoginServlet

package com.lijing.user;

import com.lijing.server.core.Request;
import com.lijing.server.core.Response;
import com.lijing.server.core.Servlet;

public class LoginServlet implements Servlet{
    public void service(Request request,Response response){
        response.print("<html>");
        response.print("<head>");
        response.print("<title>");
        response.print("<第一个小脚本>");
        response.print("</title>");
        response.print("</head>");
        response.print("<body>");
        response.print("<...欢迎回来:"+request.getParameter("uname"));
        response.print("</body>");
        response.print("</html>");
    }
}

整合配置文件"web.xml"
1、根据配置文件动态的读取类名,再进行反射获取具体的Servlet来处理业务,真正的以不变应万变

<?xml version="1.0" encoding="utf-8"?>
<web-app>
    <servlet>
        <servlet-name>login</servlet-name>
        <servlet-class>com.lijing.user.LoginServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>login</servlet-name>
        <url-pattern>/login</url-pattern>
        <url-pattern>/g</url-pattern>
        <url-pattern>/hhh</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>reg</servlet-name>
        <servlet-class>com.lijing.user.RegisterServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>reg</servlet-name>
        <url-pattern>/reg</url-pattern>
        <url-pattern>/r</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>others</servlet-name>
        <servlet-class>com.lijing.user.OthersServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>others</servlet-name>
        <url-pattern>/o</url-pattern>
    </servlet-mapping>
</web-app>

封装分发器Dispatcher
1、加入了多线程,可以同时处理多个请求,使用的时短连接
404及首页处理
读取错误、首页内容即可

package com.lijing.server.core;

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

/*
分发器:加入状态内容处理 404,505,首页
 */
public class Dispatcher implements Runnable{
    private Socket client;
    private Request request;
    private Response response;

    public Dispatcher(Socket client){
        this.client =client;
        try {
            //获取请求协议
            //获取响应协议
            request =new Request(client);
            response = new Response(client);
        } catch (IOException e) {
            e.printStackTrace();
            this.release();
        }
    }
    @Override
    public void run() {

        try {
           // System.out.println("hhhhh"+request.getUrl().equals("")+"kj"+request.getUrl());
            Servlet servlet = WebApp.getServletFromUrl(request.getUrl());
            System.out.println("servlet:"+servlet);
            if(null != servlet) {
                servlet.service(request, response);
                //关注了状态码
                 response.pushToBrowser(200);
            }else{
                //错误页面
                response.print("页面找不到了");
                response.pushToBrowser(404);
            }
        }catch (Exception e){
            try {
                response.println("Dispatcher出错了^*^");
                response.pushToBrowser(500);
            }
            catch (Exception e1){
                e1.printStackTrace();
            }
        }
        release();//短连接,用完释放
    }
    //释放资源
    private void release(){
        try {
            client.close();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
    }
}

存储xml元素的类Entity

package com.lijing.server.core;
/*格式
    <servlet>
        <servlet-name>login</servlet-name>
        <servlet-class>com.lijing.LoginServlet</servlet-class>
    </servlet>
 */
public class Entity {
    private String name;
    private String clz;

    public Entity(){

    }
    public void setName(String name) {
        this.name = name;
    }

    public void setClz(String clz) {
        this.clz = clz;
    }
    public String getName() {
        return name;
    }
    public String getClz() {
        return clz;
    }
}

存储.xml文件元素的类Mapping

package com.lijing.server.core;

import java.util.HashSet;
import java.util.Set;

/*
    <servlet-mapping>
        <servlet-name>login</servlet-name>
        <url-pattern>/login</url-pattern>
        <url-pattern>/g</url-pattern>
    </servlet-mapping>
 */
public class Mapping {
     private String name;
     private Set<String> patterns =new HashSet<String>();
    public Mapping() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<String> getPatterns() {
        return patterns;
    }

    public void setPatterns(Set<String> patterns) {
        this.patterns = patterns;
    }

    public void addPattern(String pattern){
        this.patterns.add(pattern);
    }
}


解析配置文件.xml

package com.lijing.server.core;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

public class WebApp {
    private static WebContext webContext;
    static {
        try {
            //SAX解析
            //1、获取解析工厂
           // System.out.println("asdkjekk1");
            SAXParserFactory factory =SAXParserFactory.newInstance();
            //2、从解析工厂获取SAX解析器
            SAXParser parser = factory.newSAXParser();
            //3、创建Handler子类并实例化(编写处理器,加载文档Document注册处理器)
            //System.out.println("asdkjekk3");
            WebHandler handler = new WebHandler();
            //System.out.println("asdkjekk4");
            //4、重写该类中必要的方法
            //5、解析
            parser.parse(Thread.currentThread().getContextClassLoader()
                            .getResourceAsStream("com/lijing/server/core/web.xml"),
                    handler);
            //6、获取数据
         //   System.out.println("asdkjekk5");
            webContext = new WebContext(handler.getEntitys(),handler.getMappings());
           //System.out.println("asdkje"+webContext);
        }catch (Exception e){
            System.out.println("Webapp解析配置文件错误");
        }
    }
    /*
   通过url获取配置对应的servlet
    */
    public  static Servlet getServletFromUrl(String url){

        //假设你输入了/login
        String className = webContext.getClz("/"+url);
        //System.out.println(className+"aaaaa"+className);
        Class clz=null;
        try {
            clz = Class.forName(className);
            //反射实例化
            Servlet servlet = (Servlet)clz.getConstructor().newInstance();
            return servlet;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
        //  System.out.println(servlet);
        //servlet.service();
    }

}

处理器 WebHandler

package com.lijing.server.core;

import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;

import java.util.ArrayList;
import java.util.List;
/*
处理器
 */
public class WebHandler extends DefaultHandler {
    private List<Entity> entitys = new ArrayList<Entity>();
    private List<Mapping> mappings = new ArrayList<Mapping>();
    private Entity entity;
    private Mapping mapping;
    private String tag;
    private boolean isMapping = false;

    public void startElement(String uri, String localName, String qName, Attributes attributes) {
        if (null != qName) {
            tag = qName;//存储标签名
            if (tag.equals("servlet")) {
                entity = new Entity();
                isMapping = false;
            } else if (tag.equals("servlet-mapping")) {
                mapping = new Mapping();
                isMapping = true;
            }
        }
    }
    public void characters(char[] ch,int start,int length){
        String contents =new String(ch,start,length).trim();
        if(null!=tag) {//处理空
            if (isMapping) {//操作servlet-mapping
                if (tag.equals("servlet-name")) {
                    mapping.setName(contents);
                } else if (tag.equals("url-pattern")) {
                    mapping.addPattern(contents);
                }
            } else {//操作servlet
                if (tag.equals("servlet-name")) {
                    entity.setName(contents);
                } else if (tag.equals("servlet-class")) {
                    entity.setClz(contents);
                }
            }
        }
    }
    public void endElement(String uri,String localName, String qName){
        if(null!=qName){
            if(qName.equals("servlet")){
                entitys.add(entity);
            }else if(qName.equals("servlet-mapping")){
                mappings.add(mapping);
            }
        }
        tag=null;
    }

    public List<Entity> getEntitys() {
        return entitys;
    }

    public List<Mapping> getMappings() {
        return mappings;
    }

}

WebContext 相当于工具类,把数据传过来处理

package com.lijing.server.core;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class WebContext {
    private List<Entity> entitys = null;
    private List<Mapping> mappings = null;
    //key-->servlet-name  value-->servlet-class
    private Map<String,String> entityMap = new HashMap<String, String>();
    //key-->servlet-pattern  value-->servlet-name
    private Map<String,String> mappingMap = new HashMap<String, String>();
    public WebContext(List<Entity> entitys, List<Mapping> mappings) {
        this.entitys = entitys;
        this.mappings = mappings;

        //将mapping的List转成对应的Map
        for(Mapping mapping:mappings){
          //  System.out.println(mapping.getName()+"nnnn");//问题出在这里!!!!!!!!!mapping正常
            Set<String> tmappings = mapping.getPatterns();//按理tmappings应该不为0!!!!!!不知道为什么?????
        //    System.out.println(tmappings.size());         //////////////////////////////////////////////
            for(String pattern:tmappings){                //!!!!!!找出来了,问题在webHandler37行tag等于pattern时没加入
                mappingMap.put(pattern,mapping.getName());
                //System.out.println("mapmap:::"+pattern+mapping.getName());
            }
        }
        //将entity的List转成对应的Map
        for(Entity entity:entitys){
            entityMap.put(entity.getName(),entity.getClz());
        //    System.out.println("entity:::"+entity.getName());
        }


    }
    //通过url的路径找到对应的class
    public String getClz(String pattern){
        //System.out.println(pattern+"DSAJDASKJDAWDJW");
        //System.out.println(entitys.size()+"cccc"+mappings.size());
        //System.out.println(entityMap.size()+"dddd"+mappingMap.size());
        String name = mappingMap.get(pattern);
        //System.out.println("webname"+name+"HJGJHG"+entityMap.get(name));
        return entityMap.get(name);
    }
}

login.html文件

<html>
<head>
    <meta charset="UTF-8">
    <title>用户登录</title>
</head>
<body>
<center>
    <h1>用户登录</h1>
    <form action="http://localhost:8888/login.html" method="get">
        <!--post:提交基于http协议 量大 请求参数url不可见 安全 请求参数可能在请求体中
            get:  默认,获取      量小 请求参数url可见  不安全
            action:请求web服务资源
            name:作为后端使用,区分为一:请求服务器,必须存在,数据不能提交
            id:作为前端使用,区分为一-->
        <table border="1">
            <tr>
                <td>用户名</td>
                <td><input name="username" type="text"></td>
            </tr>
            <tr>
                <td>密码</td>
                <td><input name="password" type="password"></td>
            </tr>
            <tr>
                <td align="center" colspan="2">
                    <input type="submit" value="登录">
                </td>
            </tr>
        </table>
    </form>
</center>
</body>
</html>

package com.lijing.server.core中是服务器核心的部分,我们只关注package com.lijing.user;中的部分。根据不同的处理写不同的页面,写不同的后台逻辑

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值