欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java 自建服务器

程序员文章站 2022-07-10 17:32:18
手写一个服务器搭建服务器框架创建一个xml文件:web.xml创建一个解析XML文件的类:搭建服务器框架格式如下:创建一个xml文件:web.xml login

搭建服务器整体结构和接口-编写XML文件

格式如下:
java 自建服务器
创建一个xml文件:web.xml

<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
    <servlet>
        <servlet-name>login</servlet-name><!-- 我们自己起的小名 -->
        <servlet-class>bjsxt.servlet.LoginServlet</servlet-class>     <!--根据上面的小名可以获取到的包下的类-->
    </servlet>
    <servlet-mapping>   <!--映射 在浏览器地址栏输入不同的路径去访问共享资源-->
        <servlet-name>login</servlet-name>  <!--和上面自己起的小名相同-->
        <url-pattern>/login</url-pattern>   <!--谁可以访问--> 
        <url-pattern>/log</url-pattern>
    </servlet-mapping>
</web-app>

创建一个解析XML文件的类:

package bjsxt.server;

/**
 * 用于解析xml文件
 */
public class WebDom4j {
}

package bjsxt.server;

/**
 * 这是一个路径请求一个资源的实体类
 * servlet-name和一个servlet-name对应的实体类
 *
 *     <servlet>
 *         <servlet-name>login</servlet-name>
 *         <servlet-class>bjsxt.servlet.LoginServlet</servlet-class>
 *     </servlet>
 */
public class Entity {

}

package bjsxt.server;
/**
/**
 * 这是多个路径请求一个资源的实体类
 * 是映射关系,多个路径访问共享资源
 *
 *     <servlet-mapping>
 *         <servlet-name>login</servlet-name>
 *         <url-pattern>/login</url-pattern>
 *         <url-pattern>/log</url-pattern>
 *     </servlet-mapping>
 */
 */
public class Mapping {

}

package bjsxt.server;

/**
 * 请求
 */
public class Request {
}

package bjsxt.server;

/**
 * 响应
 */
public class Response {
}

package bjsxt.server;

/**
 * 用于启动和停止服务
 */
public class Server {
}

package bjsxt.server;

/**
 * 描述的是 Entity和Mapping的映射关系
 */
public class ServletContext {

}

package bjsxt.server;

/**
 * 应用程序
 */
public class WebApp {
}

package bjsxt.servlet;

/**
 * 是所有请求的Servlet的父类
 */
public class Servlet {
}

package bjsxt.servlet;

/**
 * 创建一个请求的servlet类
 */
public class LoginServlet extends Servlet{
}

package bjsxt.util;

import java.io.Closeable;
import java.io.IOException;

/**
 * 用于关闭流
 */
public class IOCloseUtil {
    public static void closeAll(Closeable...close){
        for (Closeable closeable:close){
            if (closeable!=null){
                try {
                    closeable.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

以上,是搭建一个服务器的前期框架


DOM4J解析XML配置文件

我们先对XML文件的内容进行一些添加

<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
    <servlet>
        <servlet-name>login</servlet-name><!-- 我们自己起的小名 -->
        <servlet-class>bjsxt.servlet.LoginServlet</servlet-class>     <!--根据上面的小名可以获取到的包下的类-->
    </servlet>
    <servlet-mapping>   <!--映射 在浏览器地址栏输入不同的路径去访问共享资源-->
        <servlet-name>login</servlet-name>  <!--和上面自己起的小名相同-->
        <url-pattern>/login</url-pattern>   <!--谁可以访问-->
        <url-pattern>/log</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>register</servlet-name>
        <servlet-class>bjsxt.servlet.RegisterServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>register</servlet-name>
        <url-pattern>/reg</url-pattern>
        <url-pattern>/register</url-pattern>
        <url-pattern>/regis</url-pattern>
    </servlet-mapping>
</web-app>

使用WebDom4j.java文件解析XML文件

package bjsxt.server;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;


/**
 * 用于解析xml文件
 */
public class WebDom4j {
    private List<Entity> entityList;//用于存储是N多Entity,而每一个Entity都是servlet-name与servlet-class
    private List<Mapping> mappingList;//用于存储N多Mapping,而每一个Mapping都是一个servlet-name与N多个url-pattern

    public WebDom4j() {
       entityList = new ArrayList<Entity>();
       mappingList = new ArrayList<Mapping>();
    }

    public WebDom4j(List<Entity> entityList, List<Mapping> mappingList) {
        this.entityList = entityList;
        this.mappingList = mappingList;
    }

    @Override
    public String toString() {
        return "WebDom4j{" +
                "entityList=" + entityList +
                ", mappingList=" + mappingList +
                '}';
    }

    public void setEntityList(List<Entity> entityList) {
        this.entityList = entityList;
    }

    public List<Entity> getEntityList() {
        return entityList;
    }

    public List<Mapping> getMappingList() {
        return mappingList;
    }

    public void setMappingList(List<Mapping> mappingList) {
        this.mappingList = mappingList;
    }

    //解析XML,获取Document对象的方法
    public Document getDocument(){
        //1.创建SAXReader对象
        SAXReader reader = new SAXReader();
        //2.调用read方法
        try {
            return reader.read(new File("src/WEB_INFO/web.xml"));
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return null;
    }
    public void parse(Document doc){
        //1.获取根元素
        Element root = doc.getRootElement();    //可以获取到根元素web-app
        //2.获取servlet子元素
        for (Iterator<Element> ite=root.elementIterator("servlet");ite.hasNext();){
            Element subElement = ite.next();   //得到每一个servlet
            //创建一个实体类
            Entity ent = new Entity();  //用于存储servlet-name与servlet-class
            //因为servlet下有许多子元素,再次使用一个for循环
            for (Iterator<Element> subIte=subElement.elementIterator();subIte.hasNext();){
                Element ele = subIte.next();//可能是servlet-name,也可以能是servlet-class
                if ("servlet-name".equals(ele.getName())){  //如果获得的元素节点的名称为servlet-name
                    ent.setName(ele.getText());//那么就对实体类的name赋值
                }else if("servlet-class".equals(ele.getName())){
                    ent.setClazz(ele.getText());
                }
            }
            //将Entity放置到集合内
            entityList.add(ent);
        }

        //测试
        System.out.println("开始测试XML是否工作正常");
        for (Entity entity : entityList) {
         System.out.println(entity.getName()+"\t"+entity.getClazz());
         }
        System.out.println("=======================+");
        //解析servlet-mapping
        for(Iterator<Element> ite=root.elementIterator("servlet-mapping");ite.hasNext();){
            Element subEle=ite.next();//得到每一个servlet-mapping
            //创建一个Mapping类的对象
            Mapping map=new Mapping();
            //解析servlet-mapping下的子元素
            for (Iterator<Element> subIte=subEle.elementIterator(); subIte.hasNext();){
                Element ele = subIte.next();//servlet-name,也有可能是url-pattern
                if ("servlet-name".equals(ele.getName())){
                    map.setName(ele.getText());
                }else if("url-pattern".equals(ele.getName())){
                    //获取集合对象,调用集合对象的添加方法,添加元素
                    map.getUrlPattern().add(ele.getText());
                }
            }
            //Mapping添加到集合中
            mappingList.add(map);
        }

        //测试
        System.out.println("再次测试XML是否工作正常");
        for (Mapping m : mappingList) {
            System.out.println(m.getName());
            for(String s:m.getUrlPattern()){
                System.out.println(s);
            }
        }
    }
//用于测试
    public static void main(String[] args) {
        WebDom4j web = new WebDom4j();
        web.parse(web.getDocument());
    }
}

运行结果:
java 自建服务器

发射创建Servlet对象

编写ServletContext类

Servlet的上下文,就是一个容器,用于存储映射关系

package bjsxt.server;

import java.util.HashMap;
import java.util.Map;

/**
 * 描述的是 Entity和Mapping的映射关系
 * Servlet上下文,就是一个容器
 */
public class ServletContext {
    private Map<String,String> servlet;//key是servlet-name(entity中的值),值servlet-class(Entity中的class)
    private Map<String,String> mapping;//key是url-pattern(Mapping中的List集合中的每一个元素),value是servlet-name(是Mapping中的name)

    public ServletContext() {
        servlet = new HashMap<String, String>();
        mapping = new HashMap<String, String>();

    }

    @Override
    public String toString() {
        return "ServletContext{" +
                "servlet=" + servlet +
                ", mapping=" + mapping +
                '}';
    }

    public ServletContext(Map<String, String> servlet, Map<String, String> mapping) {
        this.servlet = servlet;
        this.mapping = mapping;
    }

    public Map<String, String> getServlet() {
        return servlet;
    }

    public void setServlet(Map<String, String> servlet) {
        this.servlet = servlet;
    }

    public Map<String, String> getMapping() {
        return mapping;
    }

    public void setMapping(Map<String, String> mapping) {
        this.mapping = mapping;
    }
}

编写WebApp类

  1. 初始化程序运行的数据

  2. 根据不同的url创建所请求的Servlet对象

package bjsxt.server;

import java.util.List;
import java.util.Map;

import bjsxt.servlet.Servlet;

/**
 * 应用程序
 */
public class WebApp {
  private static ServletContext context;
  static {
      context = new ServletContext();
      //分别获取对应关系的Map集合
      Map<String,String> servlet =  context.getServlet();
      Map<String,String> mapping =  context.getMapping();
      //创建解析XML文件对象
      WebDom4j web = new WebDom4j();
      web.parse(web.getDocument()); //开始解析XML,放到两个List集合内
      //获取解析XML之后的List集合
      List<Entity> entityList = web.getEntityList();
      List<Mapping> mappingList = web.getMappingList();

      //将集合中获取到的数据存储到Map集合内,以键值对的形式去显示
      for (Entity entity:entityList){
          servlet.put(entity.getName(),entity.getClazz());
      }
      System.out.println("---------将dom4j从XML文件解析的数据放到Map集合内,以键值对的形式去显示--------");
      System.out.println(servlet.toString());
      /**
       * 输出的结果:{login=bjsxt.servlet.LoginServlet, register=bjsxt.servlet.RegisterServlet}
       * 这时的login和register应该是键,bjsxt.servlet.LoginServlet和bjsxt.servlet.RegisterServlet是值
       */
      //开始遍历映射关系
      for (Mapping map:mappingList){
          //遍历url-pattern的集合
         List<String> urlPattern = map.getUrlPattern();
         //之后,将得到的集合urlPattern都作为map集合的键
         for (String s: urlPattern){
             mapping.put(s,map.getName());
         }
      }
      System.out.println("---------遍历映射关系,url-pattern作为键,servlet作为值--------");
      System.out.println(mapping);
      /**
       * 输出的结果:{/log=login, /reg=register, /login=login, /regis=register, /register=register}
       * 在这个过程内,url-pattern作为键,servlet作为值
       */
    //到此,我们已经将数据以键值对的形式存储到两个map集合内
  }
    //根据url创建不同的Servlet对象
    public static Servlet getServlet(String url){
        if (url==null||url.trim().equals("")) { //如果传过来的url不正确
            return null;
        }
        try {
            //如果传来的url正确
            //根据url的key获取servlet-name的值 (/log=login, /reg=register -- /log是键,login是值)
            String servletName =  context.getMapping().get(url);
            //根据servletName得到对应的servlet-class
            String servletClass =  context.getServlet().get(servletName);   //这样得到的是一个完整的包名加类名的字符串
            //使用反射创建Servlet对象
            Class<?> clazz = Class.forName(servletClass);
            Servlet servlet = (Servlet) clazz.newInstance();
            return servlet;

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
      /**
      * 根据url创建不同的Servlet对象
       * 我们可以看出,/log的内存地址可以被获取,且创建的是LoginServlet对象
       *             /reg的内存地址可以被获取,且创建的是RegisterServlet对象
      * */
        System.out.println("---------创建LoginServlet对象,输出内存地址---------");
        System.out.println(getServlet("/log"));
        System.out.println(getServlet("/login"));
        System.out.println(getServlet("/reg"));
    }
}

运行结果:
java 自建服务器

封装 Request_method_url

编写Server

1> 启动服务

2> 关闭服务

package bjsxt.server;

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

/**
 * 服务器,用于启动和停止服务
 */
public class Server {
    private ServerSocket server;

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }
    public void start(){
        this.start(9999);
    }
    //启动服务的方法 -- port是指端口
    public void start(int port){
      try {
          //启动服务
          server = new ServerSocket(port);
          //检测是否有客户机发送请求
          this.receive();//调用接收信息的方法

      } catch (IOException e) {
          e.printStackTrace();
      }
    }
    //接收服务
    private void receive() {
        try {
            //监听
            Socket client = server.accept();
            //获取用户的请求
            InputStream is=client.getInputStream();
            byte []buf =new byte [20480];
            int len=is.read(buf);
            System.out.println(new String(buf,0,len));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

      //关闭服务的方法
    public void stop(){

    }
}

在IE浏览器内,输入localhost:9999,自建服务器接收到客户端的请求,返回结果如下:
java 自建服务器

编写HTML

<html>
	<head>
		<title>登录</title>
	</head>
	<body>
		<form action="http://localhost:9999/log" method="get">
			<p>用户名:<input  type="text" name="username" id="username"/></p>
			<p>密码:<input  type="password" name="pwd" id="password"/></p>
			<p>
				爱好:<input  type="checkbox" name="hobby" value="ball"/>足球
				      <input  type="checkbox" name="hobby" value="read"/>读书
					  <input type="checkbox" name="hobby" value="paint"/>画画
			</p>
			<p><input  type="submit" value="登录"/></p>
		</form>
	</body>
</html>

java 自建服务器

封装Request_method_url

我们对原来的Server类的读取请求的部分进行封装
java 自建服务器
完善Request类:

package bjsxt.server;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 封装请求信息
 * 也就是将原来的Server类的
 * InputStream is=client.getInputStream();
 *             byte []buf =new byte [20480];
 *             int len=is.read(buf);
 *             System.out.println(new String(buf,0,len));
 *  进行了封装
 */
public class Request {
    private InputStream is;//输入流
    private String requestInfo;//请求字符串  (请求方式,请求的路径,参数,协议,协议版本,请求的正文...)
    private String method;//请求的方式
    private String url;//请求的url,也就是请求的路径

    /**
     * 我们需要从请求的url内分解出请求的参数,也就是用户名,密码,以及选择的爱好,
     * 爱好有很多,可以将其放到List集合内,这里编写一个Map集合
     */
    /*
     *  输入框的name为key,值为value
     *  key:username    value:bjsxt
     *  key:pwd          value:123
     *  key:hobby        value:read,ball
     * */
    private Map<String, List<String>> parametermapValues;//参数
    //添加两个常量:换行和空格
    private static final String CRLF="\r\n";//换行
    private static final String BLANK=" ";//空格

    //编写构造方法,开始进行初始化属性
    public Request() {
        parametermapValues=new HashMap<String,List<String>>();
        method="";
        url="";
        requestInfo="";
    }
    //编写带参构造方法 -- 获取输入的内容
    public Request(InputStream is){
        this();//调用本类无参的构造方法
        this.is=is; //将局部变量的InputStream is的值赋值给成员变量is
        try {
            //读取输入流的内容
            byte [] buf=new byte[20480];
            int len=this.is.read(buf);
            requestInfo=new String(buf,0,len);
        } catch (IOException e) {
            return;
        }
        //调用本类中的分解请求信息的方法
        this.parseRequestInfo();
    }
    //分解请求信息的方法
    /**
     * 请求方式
     * 请求路径
     * 请求的参数
     *
     */
    private void parseRequestInfo() {
        String paraString="";//用于存储请求参数

        //获取请求参数的第一行       也就是: GET /log HTTP/1.1
        // 从0开始,到第一个换行的位置
        // trim(): 用于删除字符串的头尾空白符
        // substring() 方法用于提取字符串中介于两个指定下标之间的字符
        String firstLine=requestInfo.substring(0, requestInfo.indexOf(CRLF)).trim();
        //然后,我们从获取到的“GET /log HTTP/1.1”内,分解出请求方式
        int index=firstLine.indexOf("/");
        this.method=firstLine.substring(0, index).trim();  //将获取到的请求方式(这里是“GET”)赋值给method

        /**分解url   (get可能包含参数,post不包含参数)*/
        //获取“/log ”,通过trim()方法去除空格,将"/log"赋值给urlString常量
        String urlString= firstLine.substring(index,firstLine.indexOf("HTTP/")).trim();

        //我们再判断获取到的请求方式是get还是post
        if("get".equalsIgnoreCase(this.method)){  //get 可能包含请求参数
            //通过urlString内是否包含"?"来判断这次的get请求是否包含请求参数
            if (urlString.contains("?")) {  // ?之前是参数 ?之后是路径
                //使用split()方法将一个字符串以问号为界分割为字符串数组
                String [] urlArray=urlString.split("\\?");
                this.url=urlArray[0];   //urlArray[0]: GET /log
                paraString=urlArray[1]; //urlArray[1]: 请求参数
            }else{
                //如果不包含请求参数,也就是"?"没有出现
                this.url=urlString;
            }
        }else{   //post 不包含请求参数
            this.url=urlString;
            //如果是post请求,参数在正文的最后一行,也就是最后一个换行的位置
            paraString=requestInfo.substring(requestInfo.lastIndexOf(CRLF)).trim();
        }
        //如果没有请求参数
        if (paraString.equals("")) {
            return;
        }
        //请求参数
        System.out.println(paraString);
    }
     public void show(){
            System.out.println(this.url);
            System.out.println(this.method);
        }
}

在Server类的receive方法上添加对请求参数的解析封装类的调用
改动如下:

//接收服务
    private void receive() {
        try {
            //监听
            Socket client = server.accept();
            //获取用户的请求
            /**InputStream is=client.getInputStream();
             byte []buf =new byte [20480];
             int len=is.read(buf);
             System.out.println(new String(buf,0,len));*/
            //封装请求信息
            Request req=new Request(client.getInputStream());
            req.show();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

我们在IE浏览器输入的网址是:
file:///C:/Users/Administrator/Desktop/index.html
输入内容,运行结果为:
java 自建服务器
可以看出,根据浏览器向客户端发送的请求,我们知道访问的html页面是get请求,访问的url是/log,请求参数 username是12,密码是qwe,爱好也可以看出
那么我们将html页面的请求方式换为post呢 ?
java 自建服务器
编辑完成

封装Reuqest_存储参数_处理中文

1> 编写分解参数的方法
我们在request的基础上进行完善,添加parseParam方法分解参数:

package bjsxt.server;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * 封装请求信息
 * 也就是将原来的Server类的
 * InputStream is=client.getInputStream();
 *             byte []buf =new byte [20480];
 *             int len=is.read(buf);
 *             System.out.println(new String(buf,0,len));
 *  进行了封装
 */
public class Request {
    private InputStream is;//输入流
    private String requestInfo;//请求字符串  (请求方式,请求的路径,参数,协议,协议版本,请求的正文...)
    private String method;//请求的方式
    private String url;//请求的url,也就是请求的路径

    /**
     * 我们需要从请求的url内分解出请求的参数,也就是用户名,密码,以及选择的爱好,
     * 爱好有很多,可以将其放到List集合内,这里编写一个Map集合
     */
    /*
     *  输入框的name为key,值为value
     *  key:username    value:bjsxt
     *  key:pwd          value:123
     *  key:hobby        value:read,ball
     * */
    private Map<String, List<String>> parametermapValues;//参数
    //添加两个常量:换行和空格
    private static final String CRLF="\r\n";//换行
    private static final String BLANK=" ";//空格

    //编写构造方法,开始进行初始化属性
    public Request() {
        parametermapValues=new HashMap<String,List<String>>();
        method="";
        url="";
        requestInfo="";
    }
    //编写带参构造方法 -- 获取输入的内容
    public Request(InputStream is){
        this();//调用本类无参的构造方法
        this.is=is; //将局部变量的InputStream is的值赋值给成员变量is
        try {
            //读取输入流的内容
            byte [] buf=new byte[20480];
            int len=this.is.read(buf);
            requestInfo=new String(buf,0,len);
        } catch (IOException e) {
            return;
        }
        //调用本类中的分解请求信息的方法
        this.parseRequestInfo();
    }
    //分解请求信息的方法
    /**
     * 请求方式
     * 请求路径
     * 请求的参数
     *
     */
    private void parseRequestInfo() {
        String paraString="";//用于存储请求参数

        //获取请求参数的第一行       也就是: GET /log HTTP/1.1
        // 从0开始,到第一个换行的位置
        // trim(): 用于删除字符串的头尾空白符
        // substring() 方法用于提取字符串中介于两个指定下标之间的字符
        String firstLine=requestInfo.substring(0, requestInfo.indexOf(CRLF)).trim();
        //然后,我们从获取到的“GET /log HTTP/1.1”内,分解出请求方式
        int index=firstLine.indexOf("/");
        this.method=firstLine.substring(0, index).trim();  //将获取到的请求方式(这里是“GET”)赋值给method

        /**分解url   (get可能包含参数,post不包含参数)*/
        //获取“/log ”,通过trim()方法去除空格,将"/log"赋值给urlString常量
        String urlString= firstLine.substring(index,firstLine.indexOf("HTTP/")).trim();

        //我们再判断获取到的请求方式是get还是post
        if("get".equalsIgnoreCase(this.method)){  //get 可能包含请求参数
            //通过urlString内是否包含"?"来判断这次的get请求是否包含请求参数
            if (urlString.contains("?")) {  // ?之前是参数 ?之后是路径
                //使用split()方法将一个字符串以问号为界分割为字符串数组
                String [] urlArray=urlString.split("\\?");
                this.url=urlArray[0];   //urlArray[0]: GET /log
                paraString=urlArray[1]; //urlArray[1]: 请求参数
            }else{
                //如果不包含请求参数,也就是"?"没有出现
                this.url=urlString;
            }
        }else{   //post 不包含请求参数
            this.url=urlString;
            //如果是post请求,参数在正文的最后一行,也就是最后一个换行的位置
            paraString=requestInfo.substring(requestInfo.lastIndexOf(CRLF)).trim();
        }
        //如果没有请求参数
        if (paraString.equals("")) {
            return;
        }
        //请求参数
        System.out.println(paraString);
    }
     public void show(){
            System.out.println(this.url);
            System.out.println(this.method);
     }
     //username=123&pwd=qwe&hobby=read&hobby=paint
    //将获取的参数放到我们的Map集合内,我们的键是请求参数的名称,如username;值是获取到的属性值,如123,read
    private void parseParam(String prarString){
        //使用split()方法根据"&"符号去分解获取到的请求参数字符串,将其分割为字符串数组
        String [] token = prarString.split("&");
        System.out.println("token数组:"+token[0]+"  "+token[1]+"  "+token[2]);
        //将键和值分解出来
        for (int i=0;i<token.length;i++){
            //先获得token数组的一个字符串
            String keyValues = token[i];
            //将获得的字符串根据“=”将这个字符串分割成新的字符串数组,放到自定义的数组keyValue内
            String []keyValue = keyValues.split("=");// keyValue[0]:(username,123) keyValue[1]:(pwd,qwe)
            System.out.println("keyValue数组的第一个和第二个keyValue字符串数组:"+keyValue[0]+" "+keyValue[1]);
            if (keyValue.length==1) {  //当用户不输入用户名时: username=
                //对数组进行拷贝
                keyValue= Arrays.copyOf(keyValue, 2);
                keyValue[1]=null;   // 为什么是将下标为1的置为空?--我的理解是,将数组复制之后,将username的值设为null,放到原有的两个数组值之间
            }
            //将表单元素的name与name对应的值存储到Map集合
            String key=keyValue[0].trim();  //键
            String value=keyValue[1]==null?null:keyValue[1].trim(); //值
            //放到集合中存储
            if (!parametermapValues.containsKey(key)) { //检测parametermapValues集合是否包含指定的键,不包含的话
                parametermapValues.put(key, new ArrayList<String>());
            }
            //存在的话
            System.out.println("当前需要放入的键key:"+key);
            List<String> values=parametermapValues.get(key);  //根据键去获取对应的值
            System.out.println("当前需要放入的值value:"+value);
            System.out.println("values填充之前:"+values);
            values.add(value);
            System.out.println("values填充之后:"+values);
            System.out.println("当前集合的内容:"+parametermapValues.toString());
            System.out.println("--------------");
        }
    }
 
    public static void main(String[] args) {
        Request req=new Request();
        //调用分解参数的方法
        req.parseParam("username=123&pwd=qwe&age=26&sex=男&hobby=read&hobby=paint");
        System.out.println(req.parametermapValues);

    }
}

运行结果:

token数组:username=123  pwd=qwe  sex=男
keyValue数组的第一个和第二个keyValue字符串数组:username 123
当前需要放入的键key:username
当前需要放入的值value:123
values填充之前:[]
values填充之后:[123]
当前集合的内容:{username=[123]}
--------------
keyValue数组的第一个和第二个keyValue字符串数组:pwd qwe
当前需要放入的键key:pwd
当前需要放入的值value:qwe
values填充之前:[]
values填充之后:[qwe]
当前集合的内容:{pwd=[qwe], username=[123]}
--------------
keyValue数组的第一个和第二个keyValue字符串数组:sex 男
当前需要放入的键key:sex
当前需要放入的值value:男
values填充之前:[]
values填充之后:[]
当前集合的内容:{sex=[], pwd=[qwe], username=[123]}
--------------
keyValue数组的第一个和第二个keyValue字符串数组:hobby read
当前需要放入的键key:hobby
当前需要放入的值value:read
values填充之前:[]
values填充之后:[read]
当前集合的内容:{sex=[], pwd=[qwe], username=[123], hobby=[read]}
--------------
keyValue数组的第一个和第二个keyValue字符串数组:hobby paint
当前需要放入的键key:hobby
当前需要放入的值value:paint
values填充之前:[read]
values填充之后:[read, paint]
当前集合的内容:{sex=[], pwd=[qwe], username=[123], hobby=[read, paint]}
--------------
{sex=[], pwd=[qwe], username=[123], hobby=[read, paint]}

Process finished with exit code 0

提示
当没有输入用户名时,请注释127行的这句输出语句再次尝试,

System.out.println("keyValue数组的第一个和第二个keyValue字符串数组:"+keyValue[0]+" "+keyValue[1]);

效果如下:

token数组:username=  pwd=qwe  sex=男
当前需要放入的键key:username
当前需要放入的值value:null
values填充之前:[]
values填充之后:[null]
当前集合的内容:{username=[null]}
--------------
当前需要放入的键key:pwd
当前需要放入的值value:qwe
values填充之前:[]
values填充之后:[qwe]
当前集合的内容:{pwd=[qwe], username=[null]}
--------------
当前需要放入的键key:sex
当前需要放入的值value:男
values填充之前:[]
values填充之后:[]
当前集合的内容:{sex=[], pwd=[qwe], username=[null]}
--------------
当前需要放入的键key:hobby
当前需要放入的值value:read
values填充之前:[]
values填充之后:[read]
当前集合的内容:{sex=[], pwd=[qwe], username=[null], hobby=[read]}
--------------
当前需要放入的键key:hobby
当前需要放入的值value:paint
values填充之前:[read]
values填充之后:[read, paint]
当前集合的内容:{sex=[], pwd=[qwe], username=[null], hobby=[read, paint]}
--------------
{sex=[], pwd=[qwe], username=[null], hobby=[read, paint]}

2> 编写根据页面上的name获取多个值的方法

//根据表单元素的name获取多个值
    private String [] getParamterValues(String name){
        //根据key获取value
        List<String> values=parametermapValues.get(name);
        if (values==null) {
            return null;
        }else{
            return values.toArray(new String [0] );//将集合类型转为数组
        }

    }

3> 编写根据页面上的name获取单个值的方法

  private String getParamter(String name){
        //调用本类中根据name获取多个值的方法
        String [] values=this.getParamterValues(name);
        if (values==null) {
            return null;
        }else{
            return values[0];
        }
    }

第二步和第三步的测试:

public static void main(String[] args) {
        Request req=new Request();
        //调用分解参数的方法
        req.parseParam("username=张三&pwd=qwe&sex=男&hobby=read&hobby=paint");
        System.out.println(req.parametermapValues);

        //调用获取多个值的方法
        String [] str=req.getParamterValues("hobby");
        for (String string : str) {
            System.out.println(string);
        }
        //调用获取单个值的方法
        System.out.println(req.getParamter("hobby"));
    }

输出结果:
java 自建服务器

4> 编写解码的方法

    //处理中文,因类浏览器对中文进行了编码,进行解码
    private String decode(String value,String code){
        try {
            return URLDecoder.decode(value, code);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

将这一处的代码进行改变:java 自建服务器
改为如下代码:

String value=keyValue[1]==null?null:decode(keyValue[1].trim(),"utf-8"); //值

我们将 张三 使用 %E5%BC%A0%E4%B8%89 代替,重新运行:
java 自建服务器
任然可以输出,解码测试完成
5>完善
之后,我们需要在分解请求信息的方法parseRequestInfo内调用本类中分解请求参数的方法parseParam:

        //调用本类中分解请求参数的方法
        this.parseParam(paraString);

封装Response

原始的响应

在Server类内,添加响应头和响应的内容

private void receive() {
        try {
            //监听
            Socket client = server.accept(); 
            //获取用户的请求 封装请求信息
            Request req=new Request(client.getInputStream());
            req.show();

            /**做出响应*/
            //响应头是一个字符串 -- 什么协议 协议和状态码之间有一个空格,如果成功的话,有一个200,一个“OK”,然后换行
            StringBuilder sb=new StringBuilder();
            sb.append("HTTP/1.1").append(" ").append(200).append(" ").append("OK").append("\r\n");
            sb.append("Content-Type:text/html;charset=utf-8").append("\r\n");
            //响应的内容 --我们所采用的文本以及编码格式
            String str="<html><head><title>响应结果</title></head><body>成功</body></html>";
            sb.append("Content-Length:"+str.getBytes("utf-8").length).append("\r\n");
            sb.append("\r\n");
            sb.append(str);
            //通过输出流发送出去
            BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(client.getOutputStream(),"utf-8"));
            bw.write(sb.toString());
            bw.flush();
            bw.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

启动Server类,在IE浏览器输入http://localhost:9999/log,结果如下:
java 自建服务器

封装Response

1> 构造响应头

2> 推送到客户端

package bjsxt.server;

import bjsxt.util.IOCloseUtil;

import java.io.*;

/**
 * 响应
 */
public class Response {
    private StringBuilder headInfo;//响应头
    private StringBuilder content;//响应内容
    private int length;          //响应内容的长度
    //流
    private BufferedWriter bw;
    //两个常量,换行和空格
    private static final String CRLF="\r\n";//换行
    private static final String BLANK=" ";//空格

    //构造方法 -- 对属性名称进行初始化
    public Response() {
        headInfo=new StringBuilder();
        content=new StringBuilder();
    }
    //带参构造方法 -- 去获得输出流
    public Response(OutputStream os){
        this();//调用本类的无参构造方法,对我们的属性进行初始化
        try {
            //初始化我们的输出流
            bw=new BufferedWriter(new OutputStreamWriter(os, "utf-8"));
        } catch (UnsupportedEncodingException e) {
            headInfo=null;  //出现错误时,我们的响应头为空
        }
    }
    //构造正文部分
    //需要换行的时候,调用自定义的print方法
    public Response print(String info){
        content.append(info);   //给我们传什么,就往响应内容上加什么
        try {
            length+=info.getBytes("utf-8").length;  //原来的长度加上新的长度
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return this;
    }
    //不需要换行的时候,调用自定义的println方法
    public Response println(String info){
        content.append(info).append(CRLF);
        try {
            length+=(info+CRLF).getBytes("utf-8").length;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return this;
    }

    //构造响应头
    private void createHeadInfo(int code){
        headInfo.append("HTTP/1.1").append(BLANK).append(code).append(BLANK);
        switch (code) {
            case 200:
                headInfo.append("OK");
                break;
            case 500:
                headInfo.append("SERVER ERROR");
                break;
            default:
                headInfo.append("NOT FOUND");  //找不到页面
                break;
        }
        headInfo.append(CRLF);  //换行
        headInfo.append("Content-Type:text/html;charset=utf-8").append(CRLF);   //编码格式
        headInfo.append("Content-Length:"+length).append(CRLF); //追加长度,新旧长度的连接
        headInfo.append(CRLF);
    }
    /**响应头有了(createHeadInfo方法提供),响应的内容有了(print或者println方法提供),
     * 需要将其推送到客户机的浏览器
     * @param code
     */
    public void pushToClient(int code){
        if (headInfo==null) {
            code=500;
        }
        try {
            //调用本类中的构造响应头的方法
            this.createHeadInfo(code);
            bw.write(headInfo.toString());  //写入构造头
            bw.write(content.toString());   //写入响应的内容
            bw.flush();
            this.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void close() {
        IOCloseUtil.closeAll(bw);
    }
}

编写相应的Servlet构造响应内容

package bjsxt.servlet;

import bjsxt.server.Request;
import bjsxt.server.Response;

/**
 * 是所有请求的Servlet的父类
 */
public abstract class Servlet {
    //处理请求和响应的方法  req是处理请求 rep是处理响应
    public void service(Request req, Response rep) throws Exception {
        this.doGet(req,rep);
        this.doPost(req,rep);
    }
    //创建两个抽象方法
    public abstract void doGet(Request req,Response rep) throws Exception;
    public abstract void doPost(Request req,Response rep) throws Exception;
}

package bjsxt.servlet;

import bjsxt.server.Request;
import bjsxt.server.Response;

/**
 * 创建一个请求的servlet类
 */
public class LoginServlet extends Servlet{

    @Override
    public void doGet(Request req, Response rep) throws Exception {
        //获取请求参数
        String name=req.getParameter("username");
        String pwd=req.getParameter("pwd");

        if(this.login(name, pwd)){
            //调用响应中的构建内容的方法
            rep.println(name+"登录成功");
        }else{
            rep.println(name+"登录失败,对不起,账号或密码不正确");
        }
    }
    //判断是否登陆成功
    private boolean login(String name,String pwd){
        if ("bjsxt".equals(name)&&"123".equals(pwd)) {
            return true;
        }
        return false;
    }
    @Override
    public void doPost(Request req, Response rep) throws Exception {

    }
}

我们需要在Request类内添加url的get方法


    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

然后,我们将封装的响应方法在Server类的接受服务方法receive内调用

    //接收服务
    private void receive() {
        try {
            //监听
            Socket client = server.accept();
            //获取用户的请求
/*            InputStream is=client.getInputStream();
             byte []buf =new byte [20480];
             int len=is.read(buf);
             System.out.println(new String(buf,0,len));*/
            //封装请求信息
            Request req=new Request(client.getInputStream());
            req.show();

            /**做出响应*/
 /*         //响应头是一个字符串 -- 什么协议 协议和状态码之间有一个空格,如果成功的话,有一个200,一个“OK”,然后换行
            StringBuilder sb=new StringBuilder();
            sb.append("HTTP/1.1").append(" ").append(200).append(" ").append("OK").append("\r\n");
            sb.append("Content-Type:text/html;charset=utf-8").append("\r\n");
            //响应的内容 --我们所采用的文本以及编码格式
            String str="<html><head><title>响应结果</title></head><body>成功</body></html>";
            sb.append("Content-Length:"+str.getBytes("utf-8").length).append("\r\n");
            sb.append("\r\n");
            sb.append(str);
            //通过输出流发送出去
            BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(client.getOutputStream(),"utf-8"));
            bw.write(sb.toString());
            bw.flush();
            bw.close();*/

            Response rep=new Response(client.getOutputStream());
            Servlet servlet=WebApp.getServlet(req.getUrl());
            int code=200;
            if (servlet==null) {
                code=404;
            }
        //调用Servlet中的服务的方法
            try {
                servlet.service(req, rep);
            } catch (Exception e) {
                e.printStackTrace();
            }
            rep.pushToClient(code);
            IOCloseUtil.closeAll(client);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

启动服务器进行测试

我们设定的登录名称和密码是bjsxt 123
输入不正确的名称和密码时:
java 自建服务器
输入正确的名称和密码时
java 自建服务器
到此,我们暂时告一段落,在封装Request使用的是单线程的方式,下一篇文章我们使用多线程进行完善。本项目的源码如有需要,请留言

本文地址:https://blog.csdn.net/qq_43545801/article/details/110140309