小项目:手写Webserver服务器

将sax解析xml以及http的请求与响应协议结合的小项目:
项目结构图分析:
在这里插入图片描述
1.Server01类:
创建服务器serverSocket
多线程调用分发类Dispatcher,重复接收客户端请求协议并返回
关闭客户端

public class Server01 {
    private ServerSocket serverSocket;
    private  boolean isrunning;//分发器控制标志
    public static void main(String[] args) {
        Server01 s = new Server01();
        s.start();
    }

    public void start() {
        try {
            serverSocket = new ServerSocket(8888);
            isrunning=true;
            Serverrunning();//建立连接
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("服务器启动失败");
            stop();
        }
    }


    public void Serverrunning() {
        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 {
            serverSocket.close();
            System.out.println("服务器关闭");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.Dispatcher类
该类继承Runnable接口,实现多线程
该类里面创建好两个封装的类Request,Response类进行对接收请求协议,以及包装发送响应协议
再通过Webapp类解析xml协议,再利用反射机制,得到对应的Servlet类,进行响应协议正文的包装。

public class Dispatcher implements Runnable {
    private Socket client;
    private  Request r1;
    private  Response r2;
    public Dispatcher(Socket client){//传入服务器接收的插座
        this.client=client;
        try {
            r1=new Request(client);
            r2=new Response(client);//文件正文
        } catch (IOException e) {
            e.printStackTrace();
            release();//如果创建请求协议失败,直接释放资源
        }

    }
    @Override
    public void run() {
        try {
            Servlet servlet = Webapp.getServletfromXML(r1.getUrl());
            if (servlet != null) {//如果找到xml文件的类
                servlet.service(r1, r2);//通过反射得到的servlet方法,调用指定函数,构建响应正文
                r2.pushToBrowser(200);//构建响应协议成功
            } else {
                r2.pushToBrowser(404);//若在xml文件里找不到类,则404错误}
            }
        } catch (IOException e) {
            try {
                r2.pushToBrowser(505);//服务器错误
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        release();//响应一次就释放一次资源(短连接),否则Webapp类被一个线程一直占用,无法连续接收客户端请求
    }

    private void release(){
        try {
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.封装的Request类
接收请求协议

/*封装请求信息
* 获取方法(GET/POST等),URL以及请求参数*/
public class Request {
    private String requestinfo;
    private String method;//方法
    private String Url;
    private String queryStr;//请求参数
    private Map<String, List<String>> parameterMap;//封装请求参数,因为可能一个key值要有不同的value值,所有value是个容器

    public Request(InputStream is){
        parameterMap=new HashMap<String, List<String>>();
        byte[] datas = new byte[1024 * 10 * 10];
        try {
            is.read(datas);
            this.requestinfo = new String(datas, 0, datas.length);
            System.out.println(requestinfo);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }



    public Request(Socket client) throws IOException {
     this(client.getInputStream());//构造器相互调用
        parseRequest();
    }

    private void parseRequest(){
        System.out.println("获取请求方式-----------------------------");
        int idx1=requestinfo.indexOf("/");
        this.method=requestinfo.substring(0,idx1).trim();
        System.out.println(method);

        System.out.println("获取请求URL-----------------------------");
        int idx2=requestinfo.indexOf("HTTP");
        int idx3=requestinfo.indexOf("?");
       if(idx3>0&&idx3<idx2){
        Url=requestinfo.substring(idx1+1,idx3).trim();}else {Url=requestinfo.substring(idx1+1,idx2).trim();}
        System.out.println(Url);
        System.out.println("获取请求参数-----------------------------");
        if(idx3>0&&idx3<idx2){
            queryStr=requestinfo.substring(idx3+1,idx2).trim();}else{queryStr=null;}
        if(method.equals("POST")){
            queryStr=requestinfo.substring(requestinfo.lastIndexOf("\r\n")).trim();
        }
        System.out.println(queryStr);
      if(queryStr!=null)  convertMap();
    }


    private void convertMap(){//处理请求参数,将其转换为Map
        //请求参数格式:name1=curry&name2=Ayesha
        String[] keyValues=this.queryStr.split("&");//以&符号分割为name1=curry
     for(String kv:keyValues){//以=号分割出key与value
        String kv1[]=kv.split("=");
        String key=kv1[0];//等号前面为key
        String value=kv1[1];//等号后为value
         //System.out.println(key+"----"+value);
         if(!parameterMap.containsKey(key)){
             parameterMap.put(key,new ArrayList<String>());//第一次出现当前key值
         }
         parameterMap.get(key).add(value);//get(key)得到容器v,往里面添值

     }
    }

    public String[] getParameterValues(String key){
        List<String> list=parameterMap.get(key);
        if(list==null){return null;}

        return  list.toArray(new String[0]);
    }

    public String getParameterSingleValues(String key){
       String[] s=getParameterValues(key);
        return s==null?null:s[0];
    }
    
    public String getMethod() {//返回方法名(post/get)
        return method;
    }

    public String getUrl() {//返回URL
        return Url;
    }

    public String getQueryStr() {//返回请求参数
        return queryStr;
    }

}

4.封装的Response类:
对外界有个公共方法print,可以传入响应协议内容
内部构建好响应协议头

/*封装返回响应协议*/
public class Response {
    private BufferedWriter bw;
    private StringBuilder headInfo;
    private final String BLANK=" ";
    private final String CRLF="\r\n";
    private long size=0;//协议正文大小
    private StringBuilder content;//协议正文
    private Response(){
        headInfo=new StringBuilder();
        content=new StringBuilder();
    }
    public Response(Socket client)  {
        this();//重载无参构造函数
        try {
           bw=new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

public void createHeadInfo(int code){//构造响应协议头
    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 FOUND").append(CRLF);
        break;
        case 505: headInfo.append("SERVER ERROR").append(CRLF);
        break;
    }

    //响应头(最后一行有空行)
    headInfo.append("Date:").append(new Date()).append(CRLF);
    headInfo.append("Server:").append("WebServer02/0.0.1;charset=UTF8").append(CRLF);
    headInfo.append("Content-type:text/html").append(CRLF);
    headInfo.append("Content-length:").append(size).append(CRLF);
    headInfo.append(CRLF);//与正文直接有个空行

}


    public Response print(String info){//传入响应协议正文,流操作,返回自身则可以 print.print反复依次调用
        content.append(info);
        size+=info.getBytes().length;
        return this;//返回本身实例
}
    public Response println(String info){//换行的传入正文操作
        content.append(info).append(CRLF);
        size+=(info+CRLF).getBytes().length;
        return this;//返回本身实例
    }

    public void pushToBrowser(int code) throws IOException {
        createHeadInfo(code);//传入状态码,生产响应协议头
        bw.append(headInfo);//Writer内的方法
        bw.append(content);
        bw.flush();
    }
}

5.Webapp类
内部静态块通过sax分析xml配置文件,为了进行反射
与webcontext类关联,webcontext处理配置文件内的数据,封装成容器。之前的博客内有相关内容

public class Webapp {
    private static Webcontext context;
   static{
        SAXParserFactory factory=SAXParserFactory.newInstance();
        SAXParser Parse= null;
        try {
            Parse = factory.newSAXParser();
            Handler1 handler=new Handler1();
            Parse.parse(Thread.currentThread().getContextClassLoader().getResourceAsStream("cn/xjh3/Net/WebServer02/web.xml"),handler);
             context=new Webcontext(handler.getEntitys(),handler.getMappings());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Servlet getServletfromXML(String url) {//上下文类处理Handler得到的数据
       if(context==null){
           System.out.println("context is null");
       }
        String classname= context.getclz("/"+url);
        Class clz= null;
        try {
            clz = Class.forName(classname);
            Servlet servlet=(Servlet) clz.getConstructor().newInstance();//使用反射得到类名
            return servlet;
        } catch (Exception e) {
        }
        return null;

    }
}



class Handler1 extends DefaultHandler {//继承DefaultHandler类,必要条件

    private List<Entity> Entitys=new ArrayList<>();//定义对象容器
    private List<Mapping> Mappings=new ArrayList<>();
    private Mapping mapping;//将xml的内容转为对象型数据
    private Entity entity;
    private String tag;
    private boolean ismapping=false;
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
   //    System.out.println(qName+"---->解析开始");
        tag=qName;
        if(qName=="servlet"){
            entity=new Entity();
        }else if(qName=="servlet-mapping"){
            mapping=new Mapping();
            ismapping=true;//读取servlet-mapping
        }

    }



    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
      // System.out.println(qName+"---->解析结束");
        if(qName.equals("servlet")){Entitys.add(entity);}//把类加入容器内
        if(qName.equals("servlet-mapping")){Mappings.add(mapping);}
        tag=null;
    }



    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        if(tag!=null){//处理了,解析完一个标题后空内容的情况
            String contents=new String(ch,start,length).trim();//trim去掉空
            if(ismapping){//操作servlet-mapping
                if(tag=="servlet-name"){mapping.setName(contents);}
                else if(tag=="url-pattern"){mapping.addPatterns(contents);}

            }else {//操作servlet
                if(tag=="servlet-name"){entity.setName(contents);}
                else if(tag=="servlet-class"){entity.setClz(contents);}
            }
        }}

    @Override
    public void startDocument() throws SAXException {
        System.out.println("开始解析xml");
    }

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

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

6.webcontext类
上下文类用于对webapp类解析xml得到的数据进行处理,将其一 一转为键值对的形式存储

public class Webcontext {
    private List<Entity> E=null;
    private   List<Mapping> M=null;
    private Map<String,String> EntityMap=new HashMap<>();//KEY->Entity里的name,VALUE-》Entity里的class
    private Map<String,String> MappingMap=new HashMap<>();//KEY->Mapping里的pattern,VALUE-》Mapping里的name
    public Webcontext(List<Entity> E, List<Mapping> M) {
        this.E = E;
        this.M = M;

        for(Entity e:E){
            EntityMap.put(e.getName(),e.getClz());//将Handler得到的entity list转换为map
        }

        for(Mapping m:M){
            for(String pattern:m.getPatterns()){
                MappingMap.put(pattern,m.getName());//将得到的mapping list转换为map,因为mapping内还有一个set容器,双重循环
            }
        }
    }

    //通过URL路径找到class:由url-pattern找到servlet-name再找到servlet-class
    public String getclz(String pattern){//得到类名
        String name=MappingMap.get(pattern);
        return EntityMap.get(name);
    }
}

7.Entity以及Mapping类
用于将xml内的文件分块,分为两种数据类型:
Entity:对应xml文件内的块

public class Entity {
    private String name;
    private String clz;

    public Entity() {
    }

    public String getName() {
        return name;
    }

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

    public String getClz() {
        return clz;
    }

    public void setClz(String clz) {
        this.clz = clz;
    }
}

Mapping:对应xml文件内的 块

public class Mapping {
    private String name;
    private Set<String > patterns;

    public Mapping(){
        patterns=new HashSet<String>();
    }
    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 addPatterns(String pattern){
        this.patterns.add(pattern);//便于操作
    }
}

xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
    <servlet>
        <servlet-name>login</servlet-name>
        <servlet-class> cn.xjh3.Net.WebServer02.loginServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>reg</servlet-name>
        <servlet-class> cn.xjh3.Net.WebServer02.registerServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>login</servlet-name>
        <url-pattern>/login</url-pattern>
        <url-pattern>/g</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>reg</servlet-name>
        <url-pattern>/reg</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>reg</servlet-name>
        <url-pattern>/re</url-pattern>
    </servlet-mapping>
</web-app>

关键的后端处理部分:
8.Servlet接口以及相关类

定义一个Servlet接口,每个业务有不同的类构成,这些类都继承Servlet接口。每个类根据请求协议的URL的不同,进行不同的操作实现。
例如:写一个登录操作类loginServlet,继承Servlet接口
请求协议的URL为login,解析xml,反射得到类loginServlet,调用。

/*服务器小脚本接口,解耦了业务代码(登录/注册等业务)*/
public interface Servlet {
    void service(Request r1,Response r2) throws IOException;
}

继承Servlet接口的类:loginServlet

public class loginServlet implements Servlet{
/*登录小脚本*/
    @Override
    public void service(Request r1, Response r2) throws IOException {
        r2.print("<html>");
        r2.println("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />");//这一行很重要,可以强制让浏览器按UTF-8格式显示!
        r2.print("<head>");
        r2.print("<title>");
        r2.print("服务器响应成功");
        r2.print("服务器小脚本1");
        r2.print("</title>");
        r2.print("</head>");
        r2.print("<body>");
        r2.print("欢迎回来:"+r1.getParameterSingleValues("uname"));
        r2.print("</body>");
        r2.print("</html>");
        System.out.println("-----------响应结束------------"+"\r\n");
    }
}

继承Servlet接口的类:registerServlet
当请求协议的URL为register,反射得到该类,响应回注册的页面

public class registerServlet implements Servlet {
    @Override
    public void service(Request r1, Response r2) {

        r2.print("注册成功");


    }
}

最后一个小例子:
首先打开这个登录的html页面:
html代码:

<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<head>
    <title>html登录</title>
</head>
   <body>
   <form method="post" action="http://localhost:8888/login">
         用户名:<input type="text" name="uname" id="uname"/>
         密码:<input type="password" name="pwd" id="pwd"/>
          <input type="submit" value="登录"/>
   </form>
   </body>
</html>

在这里插入图片描述点击登录,服务器得到其请求协议,得到该请求协议的URL值以及请求参数,URL值为为login,请求参数为输入的用户名与密码,调用loginServlet函数,返回响应协议。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值