手写http请求响应服务器

一、项目介绍

1.1功能

当客户端向服务器发送http请求时,服务器根据客户端http请求中的url跳转到指定页面显示内容

1.2需要用到的基本知识

1.xml文件解析
2.集合
3.IO流
4.基础socket网络编程
5.多线程

二、项目目录的介绍

项目目录

2.1包的介绍

1.core包
服务器程序的核心,后期进入javaweb开发时,此包下的类是不用程序员自己写的。
2.user包
存放url反射的类,对于不同的url,根据xml文件中的配置,加载对应的类,其中每个类都实现了Servlet接口,通过Servlet接口中的service方法,加载不同的html文件。
3.xml包
存放xml文件,可以灵活增加修改xml中的信息,解耦了页面与代码,便于维护和扩展。

2.2类的介绍

1.service01类

package com.lyz.server.core;

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

public class Server01 {
	//服务器端的socket,用于阻塞式接收客户端socket发来的请求
    private ServerSocket serverSocket;
    //用于判断程序是否还需要运行
    private boolean isRunning;
    
    public static void main(String[] args) {
        Server01 server01 = new Server01();
        server01.start();
    }

    public void start() {
        try {
        //服务器监听端口8888
            serverSocket = new ServerSocket(8888);
            isRunning = true;
            receive();
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("连接建立失败");
        }

    }

    public void receive() {
        while(isRunning) {
            try {
                Socket accept = serverSocket.accept();
                //开启一个新线程
                new Thread(new Dispacher(accept)).start();
            } catch (IOException e) {
            	//这里可以添加对应的http错误代码
                e.printStackTrace();
                System.out.println("客户端出了问题");
            }
        }
    }
	
	//在适当的时候需要结束某个客户端连接的线程,释放资源
    public void stop() {
        isRunning = false;
        try {
            serverSocket.close();
            System.out.println("服务器已停止");
        } catch (IOException e) {
            e.printStackTrace();

        }
    }
}

2.Dispacher类

/* 用于实现多线程访问 */
package com.lyz.server.core;

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

public class Dispacher implements Runnable {
	//服务器端的socket
    private Socket accept;
    //封装好的http请求消息,会在下面介绍
    private Request request;
    //封装好的http响应消息
    private Response response;
    
    public Dispacher(Socket accept) {
        this.accept = accept;
    }
    @Override
    public void run() {
        try {
        	//在创建Request对象的时候,完成了通过socket读入http请求的过程
            request = new Request(accept);
            /* 创建Response对象后必须用pushToBrowser(int)返回一个状态码,
            响应消息才会发出 */
            response = new Response(accept);
            /* WebApp中只封装了一个通过url创建并返回对应的Servlet实现类*/
            Servlet servlet = WebApp.getServletFromUrl(request.getUrl());
            if(servlet!=null) {
            	/*这里注意service方法必须先执行,因为响应正文是
            	在service方法中执行的*/
                servlet.service(request,response);
                response.pushToBrowser(200);
            } else {
                response.pushToBrowser(404);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        	//响应消息法出后记得释放资源哦
            release();
        }
    }

    public void release() {
        try {
            accept.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.Request类
必须说明的是这个请求消息的处理,作者完成的并不好,写之前还没有学过正则表达式处理,如果使用split分割字符串,则可以避免复杂又低效的循环处理。

/*必须说明的是
*/
package com.lyz.server.core;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;

public class Request {
	//http请求消息全文
    private String protocol;
    //请求方式
    private String method;
    //url
    private String url;
    //参数列表
    private String queryStr;
    //将参数封装进Map集合,因为存在多选输入的情况故采用Map
    private Map<String,String> parameterMap = new HashMap<>();

    public Request(Socket accept) {
        InputStream inputStream = null;
        try {
        	//在构造方法中读取客户端通过socket发来的请求消息
            inputStream = accept.getInputStream();
            byte[] buffer = new byte[1024*1024];
            int len;
            len = inputStream.read(buffer);
            //这里编码的方式完全取决于客户端发送的消息编码,出现乱码就是客户端的原因
            this.protocol = new String(buffer,0,len,"utf-8");
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
        	//解析字符串
            parseHttp();
        } catch (UnsupportedEncodingException e) {
            System.out.println("url解析出错");
            e.printStackTrace();
        }
    }
    //这里可以采用更简单高效的split分割函数处理哟
    private void parseHttp() throws UnsupportedEncodingException {
        int id1 = protocol.indexOf("/");
        int id2 = protocol.indexOf("HTTP/");
        int id3,id4,id5;
        String queryTemp;
        method = protocol.substring(0,id1-1);
        url = protocol.substring(id1,id2-1);
        if("GET".equals(method)) {
            if((id3=url.indexOf('?'))!=-1) {
                queryTemp = queryStr = url.substring(id3+1);
                while((id4=queryTemp.indexOf('&'))!=-1) {
                    id5 = queryTemp.indexOf('=');
                  /*注意url采用自己独有的编码方式,必须采用URLDecoder类的
                  decode方法解码,否则url中的中文会出现乱码*/
                   parameterMap.put(URLDecoder.decode(queryTemp.substring(0,id5),"utf-8"),
                            URLDecoder.decode(queryTemp.substring(id5+1,id4),"utf-8"));
                    queryTemp = queryTemp.substring(id4+1);
                }
                id5 = queryTemp.indexOf('=');
                parameterMap.put(URLDecoder.decode(queryTemp.substring(0,id5),"utf-8"),
                        URLDecoder.decode(queryTemp.substring(id5+1),"utf-8"));
            }
        } else if("POST".equals(method)) {
        	//POST方式中参数不出现在url中,用两个回车换行去定位它
            id3=protocol.lastIndexOf("\r\n\r\n");
            if(id3!=-1) {
                queryTemp = protocol.substring(id3).trim();
                queryStr = queryTemp;
                while((id4=queryTemp.indexOf('&'))!=-1) {
                    id5 = queryTemp.indexOf('=');
                    /*这里可以不用decode解码,因为POST方式中请求参数不出现在url
                    中而是出现在请求正文中
                    */
                    parameterMap.put(URLDecoder.decode(queryTemp.substring(0,id5),"utf-8"),
                            URLDecoder.decode(queryTemp.substring(id5+1,id4),"utf-8"));
                    queryTemp = queryTemp.substring(id4+1);
                }
                id5 = queryTemp.indexOf('=');
                parameterMap.put(URLDecoder.decode(queryTemp.substring(0,id5),"utf-8"),
                        URLDecoder.decode(queryTemp.substring(id5+1),"utf-8"));
            }

        }
    }


    public String getProtocol() {
        return protocol;
    }

    public String getMethod() {
        return method;
    }

    public String getUrl() {
        return url;
    }

    public String getQueryStr() {
        return queryStr;
    }

    public Map<String, String> getParameterMap() {
        return parameterMap;
    }

}

4.Response类

package com.lyz.server.core;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.Date;

public class Response {
	//响应消息头部
    private StringBuilder headInfo;
    //响应消息正文
    private StringBuilder content;
    //记录正文长度,因为http必须根据正文长度判断响应消息是否结束
    private int len;
    //缓冲输出字符流,将响应消息通过soket返回客户端
    private BufferedWriter bufferedWriter;
	
    private static final String CRLF = "\r\n";
    private static final String BLANK = " ";

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

    public Response(Socket accept) throws IOException {
        this();
        /*注意输出字符的编码格式,若这里出现问题,则响应消息中正文会出现乱码*/
        bufferedWriter = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream(),"UTF-8"));
    }
	//此方法用于添加响应消息正文
    public Response addContent(String info) {
        this.content.append(info);
        try {
        	//注意正文长度是字节长度
            len += info.getBytes("utf-8").length;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return this;
    }
	//每一个响应消息都附带一个状态码用来标识连接的状态
    public Response pushToBrowser(int code) throws IOException {

        createHeadInfo(code);

        bufferedWriter.write(headInfo.toString());

        bufferedWriter.write(content.toString());
        bufferedWriter.flush();
        return this;
    }
	
    public void createHeadInfo(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 FOUND").append(CRLF);
                break;
            case 505:
                headInfo.append("SERVER ERROR").append(CRLF);
                break;
        }
        //2、响应头(最后一行存在空行):
        headInfo.append("Date:").append(new Date()).append(CRLF);
        headInfo.append("Server:").append("shsxt?Server/0.0.1;charset=UTF-8").append(CRLF);
        headInfo.append("Content-type:text/html").append(CRLF);
        headInfo.append("Content-length:").append(len).append(CRLF);
        headInfo.append(CRLF);
    }

}

5.WebApp类

package com.lyz.server.core;

import java.lang.reflect.InvocationTargetException;

public class WebApp {
	/*WebContext类中封装了两个Map集合,
	分别存储url->servlet-name映射和servlet-name->servlet-class映射*/
    private static WebContext webContext;
	
    static {
    	/*XMLParse用来解析xml文件,将得到的信息封装为
    	包含url、servlet-name属性类的List集合mappings和包含
    	servlet-class、servlet-name属性类的List集合entities*/
    	//xml加载路径是以IDEA下的项目目录为根,而不是模板为根
        XMLParse xmlParse = new XMLParse("Server\\xml\\web.xml");
        //WebContext将两个List集合转换为Map集合
        webContext = new WebContext(xmlParse.entities,xmlParse.mappings);
    }

    public static Servlet getServletFromUrl(String url) {
        Servlet servlet =null;
        String servletClassName = webContext.getEntityMap().get(webContext.getMappingMap().get(url));
        try {
            servlet = (Servlet) Class.forName(servletClassName).getConstructor().newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NullPointerException e) {
            e.printStackTrace();
        }
        return servlet;
    }
}

6.WebContext类

package com.lyz.server.core;

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

public class WebContext {
    private Map<String,String> entityMap = new HashMap<>();
    private Map<String,String> mappingMap = new HashMap<>();
    public WebContext(List<Entity> entityList,List<Mapping> mappingList) {
        Iterator<Entity> iterator = entityList.iterator();
        while(iterator.hasNext()) {
            Entity entity = iterator.next();
            this.entityMap.put(entity.getServlet_name(),entity.getServlet_class());
        }
        Iterator<Mapping> iterator1 = mappingList.iterator();
        while(iterator1.hasNext()){
            Mapping mapping = iterator1.next();
            Iterator<String> iterator2 = mapping.getUrl_pattern().iterator();
            while(iterator2.hasNext()) {
                String url = iterator2.next();
                this.mappingMap.put(url,mapping.getServlet_name());
            }
        }
    }

    public Map<String, String> getEntityMap() {
        return entityMap;
    }

    public Map<String, String> getMappingMap() {
        return mappingMap;
    }
    public String urlIndexClass(String url) {
        return this.entityMap.get(this.mappingMap.get(url));
    }
}

7.XMLParse类
采用SAX解析技术

package com.lyz.server.core;

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

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class XMLParse  {
    List<Entity> entities;
    List<Mapping> mappings;
    public XMLParse(String pathXML){
        EntityServletMappingHandler handler = null;
        try {
            SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
            SAXParser saxParser = saxParserFactory.newSAXParser();
            XMLReader xmlReader = saxParser.getXMLReader();
            handler = new EntityServletMappingHandler();
            xmlReader.setContentHandler(handler);
            xmlReader.parse(pathXML);
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        entities = handler.getEntities();
        mappings = handler.getMappings();
    }
}

class EntityServletMappingHandler extends DefaultHandler {
    private Entity entity;
    private Mapping mapping;
    private List<Entity> entities;
    private List<Mapping> mappings;
    private String tag;
    private String classTag;

    @Override
    public void startDocument() throws SAXException {
        super.startDocument();
        entities = new ArrayList<>();
        mappings = new ArrayList<>();
    }

    @Override
    public void endDocument() throws SAXException {
        super.endDocument();
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        super.startElement(uri, localName, qName, attributes);
        this.tag = qName;
        if("servlet".equals(qName)) {
            this.classTag = "servlet";
            this.entity = new Entity();
        } else if("servlet-mapping".equals(qName)) {
            this.classTag = "servlet-mapping";
            this.mapping = new Mapping();
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        super.endElement(uri, localName, qName);
        if("servlet".equals(qName)) {
            this.entities.add(this.entity);
            this.classTag = null;
        } else if("servlet-mapping".equals(qName)) {
            this.mappings.add(mapping);
            this.classTag = null;
        }
        this.tag = null;
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        super.characters(ch, start, length);
        String contents = new String(ch,start,length);
        if(contents.length()>0) {
            if("servlet".equals(classTag)) {
                if ("servlet-name".equals(this.tag)) {
                    this.entity.setServlet_name(contents);
                } else if ("servlet-class".equals(this.tag)) {
                    this.entity.setServlet_class(contents);
                }
            } else if("servlet-mapping".equals(this.classTag)) {
                if("servlet-name".equals(this.tag)) {
                    this.mapping.setServlet_name(contents);
                } else if("url-pattern".equals(this.tag)) {
                    this.mapping.getUrl_pattern().add(contents);
                }
            }
        }
    }

    public List<Entity> getEntities() {
        return entities;
    }

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

8.Entity类与Mapping类
作为记录xml文件内容的类

package com.lyz.server.core;

public class Entity {

    private String servlet_name;
    private String servlet_class;

    @Override
    public String toString() {
        return "Entity{" +
                "servlet_name='" + servlet_name + '\'' +
                ", servlet_class='" + servlet_class + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object obj) {
        if(obj instanceof Entity) {
            Entity tempEntity = (Entity)obj;
            return this.servlet_name.equals(tempEntity.servlet_name);
        } else {
            return false;
        }
    }
    public String getServlet_name() {
        return servlet_name;
    }

    public void setServlet_name(String servlet_name) {
        this.servlet_name = servlet_name;
    }

    public String getServlet_class() {
        return servlet_class;
    }

    public void setServlet_class(String servlet_class) {
        this.servlet_class = servlet_class;
    }

    public Entity() {
    }
}

package com.lyz.server.core;

import java.util.HashSet;

public class Mapping {
    private String servlet_name;
    private HashSet<String> url_pattern = new HashSet<>();

    @Override
    public String toString() {
        return "Mapping{" +
                "servlet_name='" + servlet_name + '\'' +
                ", url_pattern=" + url_pattern +
                '}';
    }

    @Override
    public boolean equals(Object obj) {
        if(obj instanceof Mapping) {
            Mapping obj1 = (Mapping) obj;
            return this.servlet_name.equals(obj1.servlet_name);
        } else {
            return false;
        }
    }

    public String getServlet_name() {
        return servlet_name;
    }

    public void setServlet_name(String servlet_name) {
        this.servlet_name = servlet_name;
    }

    public HashSet<String> getUrl_pattern() {
        return url_pattern;
    }

    public void setUrl_pattern(HashSet<String> url_pattern) {
        this.url_pattern = url_pattern;
    }

    public Mapping() {
    }

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

9.Servlet接口

package com.lyz.server.core;

public interface Servlet {
    void service(Request request,Response response);
}

10.LoginSrvlet与RegistServlet

package com.lyz.server.user;

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

public class LoginServlet implements Servlet {
    @Override
    public void service(Request request, Response response) {
        response.addContent("<html>");
        response.addContent("<head>");


        response.addContent("<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">");

        response.addContent("</head>");
        response.addContent("</html>");
        response.addContent("登录页面");
    }
}
package com.lyz.server.user;

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

public class RegisterServlet implements Servlet {
    @Override
    public void service(Request request, Response response) {
    	response.addContent("<html>");
        response.addContent("<head>");


        response.addContent("<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">");

        response.addContent("</head>");
        response.addContent("</html>");
        response.addContent("注册页面");
    }
}

前端页面可以采用更方便的jsp书写。

2.3 xml文件

<?xml version="1.0" encoding="UTF-8"?>  
 <web-app>
 <servlet>
  <servlet-name>login</servlet-name>
  <servlet-class>com.lyz.server.user.LoginServlet</servlet-class>
 </servlet>
   <servlet>
  <servlet-name>reg</servlet-name>
  <servlet-class>com.lyz.server.user.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>
 </web-app>

三、效果展示

Get请求

在这里插入图片描述

POST请求

在这里插入图片描述

四、收获与总结

通过这个小项目,熟悉了服务器的基本原理,融会贯通了java基础部分的知识

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值