手写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> parameterMap;

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

    public Request(InputStream is) {
        parameterMap = new HashMap>();
        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());
            }
            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 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("");
        response.print("");
        response.print("");
        response.print("<第一个小脚本>");
        response.print("");
        response.print("");
        response.print("");
        response.print("<...欢迎回来:"+request.getParameter("uname"));
        response.print("");
        response.print("");
    }
}

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



    
        login
        com.lijing.user.LoginServlet
    
    
        login
        /login
        /g
        /hhh
    
    
        reg
        com.lijing.user.RegisterServlet
    
    
        reg
        /reg
        /r
    
    
        others
        com.lijing.user.OthersServlet
    
    
        others
        /o
    

封装分发器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;
/*格式
    
        login
        com.lijing.LoginServlet
    
 */
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;

/*
    
        login
        /login
        /g
    
 */
public class Mapping {
     private String name;
     private Set patterns =new HashSet();
    public Mapping() {
    }

    public String getName() {
        return name;
    }

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

    public Set getPatterns() {
        return patterns;
    }

    public void setPatterns(Set 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 entitys = new ArrayList();
    private List mappings = new ArrayList();
    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 getEntitys() {
        return entitys;
    }

    public List 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 entitys = null;
    private List mappings = null;
    //key-->servlet-name  value-->servlet-class
    private Map entityMap = new HashMap();
    //key-->servlet-pattern  value-->servlet-name
    private Map mappingMap = new HashMap();
    public WebContext(List entitys, List mappings) {
        this.entitys = entitys;
        this.mappings = mappings;

        //将mapping的List转成对应的Map
        for(Mapping mapping:mappings){
          //  System.out.println(mapping.getName()+"nnnn");//问题出在这里!!!!!!!!!mapping正常
            Set 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文件



    
    用户登录


用户登录

用户名
密码

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

你可能感兴趣的:(Java)