【尚学堂】手写WebServer


持续学习&持续更新中…

守破离


Web请求都是使用Reauest和Response式的交流

预备—反射

什么是反射(Reflect)

反射是框架设计的灵魂

在这里插入图片描述

获取Class的三种方式

		//三种方式
		//1、对象.getClass()
		Iphone iphone =new Iphone();
		Class clz = iphone.getClass();
		//2、类.class
		clz = Iphone.class;
		//3、Class.forName("包名.类名")
		clz = Class.forName("com.sxt.server.basic.Iphone");

利用反射创建对象的两种方式

		//创建对象
		// 方式一(不推荐)
		Iphone iphone1 =(Iphone)clz.newInstance();
		// 方式二(推荐)
		Iphone iphone2 =(Iphone)clz.getConstructor().newInstance();

预备—XML解析

简单解析

p.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<persons>
    <person>
        <name>至尊宝</name>
        <age>9000</age>
    </person>
    <person>
        <name>白晶晶</name>
        <age>7000</age>
    </person>
</persons>

解析:

        //SAX解析
        //1、获取解析工厂
        SAXParserFactory factory = SAXParserFactory.newInstance();
        //2、从解析工厂获取解析器
        SAXParser parse = factory.newSAXParser();
        //3、编写处理器
        //4、加载文档 Document 注册处理器
        PersonHandler handler = new PersonHandler();
        //5、解析
        parse.parse(Thread.currentThread().getContextClassLoader()
                        .getResourceAsStream("p.xml")
                , handler);
        final List<Person> persons = handler.getPersons();
        for (Person person : persons) {
    
    
            System.out.println(person);
        }

PersonHandler:

class PersonHandler extends DefaultHandler {
    
    

    private final List<Person> persons = new ArrayList<>();
    private Person person;
    private String currentTag;

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
    
    
        if (qName.equals("person")) {
    
    
            person = new Person();
        }
        currentTag = qName;
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
    
    
        if (qName.equals("person")) {
    
    
            persons.add(person);
        }
        currentTag = null;
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
    
    
        String content = new String(ch, start, length).trim();
        if (currentTag != null) {
    
    
            if ("name".equals(currentTag)) {
    
    
                person.setName(content);
            } else if ("age".equals(currentTag)) {
    
    
                if (!"".equals(content)) {
    
    
                    person.setAge(Integer.valueOf(content));
                }
            }
        }
    }

    public List<Person> getPersons() {
    
    
        return persons;
    }

}

解析web.xml

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
    <servlet>
        <servlet-name>login</servlet-name>
        <servlet-class>programmer.lp.webserver.servlet.LoginServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>login</servlet-name>
        <url-pattern>/login/*</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>register</servlet-name>
        <servlet-class>programmer.lp.webserver.servlet.RegisterServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>register</servlet-name>
        <url-pattern>/register/*</url-pattern>
    </servlet-mapping>
</web-app>

Entity :

package programmer.lp.webserver.webconfig;

/*
    <servlet>
        <servlet-name>login</servlet-name>
        <servlet-class>programmer.lp.basic.servlet.LoginServlet</servlet-class>
    </servlet>
 */

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 :

package programmer.lp.webserver.webconfig;

/*
    <servlet-mapping>
        <servlet-name>login</servlet-name>
        <url-pattern>/login</url-pattern>
        <url-pattern>/g</url-pattern>
    </servlet-mapping>
 */

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

public class Mapping {
    
    

    private String name;
    private Set<String> urlPatterns;

    public Mapping() {
    
    
        urlPatterns = new HashSet<>();
    }

    public String getName() {
    
    
        return name;
    }

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

    public Set<String> getUrlPatterns() {
    
    
        return urlPatterns;
    }

    public void setUrlPatterns(Set<String> urlPatterns) {
    
    
        this.urlPatterns = urlPatterns;
    }

    public void addUrlPattern(String urlPattern) {
    
    
        urlPatterns.add(urlPattern);
    }

}

WebHandler :

package programmer.lp.webserver.webconfig;

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

import java.util.ArrayList;
import java.util.List;

public class WebHandler extends DefaultHandler {
    
    

    private final List<Entity> entities = new ArrayList<>();
    private final List<Mapping> mappings = new ArrayList<>();
    private Entity entity;
    private Mapping mapping;
    private String currentTag;
    private boolean isMapping;

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

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

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

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

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
    
    
        String content = new String(ch, start, length).trim();
        if (currentTag != null) {
    
    
            if (isMapping) {
    
    
                if ("servlet-name".equals(currentTag)) {
    
    
                    mapping.setName(content);
                } else if ("url-pattern".equals(currentTag)) {
    
    
                    mapping.addUrlPattern(content);
                }
            } else {
    
    
                if ("servlet-name".equals(currentTag)) {
    
    
                    entity.setName(content);
                } else if ("servlet-class".equals(currentTag)) {
    
    
                    entity.setClz(content);
                }
            }
        }
    }

}

WebContext :

package programmer.lp.webserver.webconfig;


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

public class WebContext {
    
    

    private final Map<String, String> entityMap = new HashMap<>();
    private final Map<String, String> mappingMap = new HashMap<>();
    private final Map<String, String> mappingALlMap = new HashMap<>();

    public WebContext(List<Entity> entities, List<Mapping> mappings) {
    
    
        for (Entity entity : entities) {
    
    
            entityMap.put(entity.getName(), entity.getClz());
        }
        for (Mapping mapping : mappings) {
    
    
            final Set<String> urlPatterns = mapping.getUrlPatterns();
            for (String urlPattern : urlPatterns) {
    
    
                if (urlPattern.endsWith("*")) {
    
    
                    urlPattern = urlPattern.substring(0, urlPattern.lastIndexOf("/*"));
                    mappingALlMap.put(urlPattern, mapping.getName());
                } else {
    
    
                    mappingMap.put(urlPattern, mapping.getName());
                }
            }
        }
    }

    public String getServletClass(String urlPattern) {
    
    
        final Set<Map.Entry<String, String>> entries = mappingALlMap.entrySet();
        for (Map.Entry<String, String> entry : entries) {
    
    
            final String key = entry.getKey();
            if (urlPattern.startsWith(key)) {
    
    
                return entityMap.get(mappingALlMap.get(key));
            }
        }
        return entityMap.get(mappingMap.get(urlPattern));
    }

}

结合反射与XML配置文件

package programmer.lp.webserver.webconfig;

import programmer.lp.webserver.servlet.Servlet;

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

public class WebApp {
    
    

    private static WebContext webContext;

    static {
    
    
        try {
    
    
            //SAX解析
            //1、获取解析工厂
            SAXParserFactory factory = SAXParserFactory.newInstance();
            //2、从解析工厂获取解析器
            SAXParser parse = factory.newSAXParser();
            //3、编写处理器
            //4、加载文档 Document 注册处理器
            WebHandler handler = new WebHandler();
            //5、解析
            parse.parse(Thread.currentThread()
                            .getContextClassLoader()
                            .getResourceAsStream("web.xml")
                    , handler);
            webContext = new WebContext(handler.getEntities(), handler.getMappings());
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    public static Servlet getServletFromUri(String uri) {
    
    
//        final String uri = "/register";
        final String servletClass = webContext.getServletClass(uri);
        try {
    
    
            return (Servlet) Class.forName(servletClass).getConstructor().newInstance();
        } catch (Exception e) {
    
    
            return null;
        }
    }

}

预备—HTTP协议

HTTP协议是应用层协议,底层基于TCP协议。

Request—GET

GET / HTTP/1.1
Host: localhost:8888
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3878.400 QQBrowser/10.8.4518.400
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.9
GET /?username=lp&password=1234 HTTP/1.1
Host: localhost:8888
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.9 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

Request—POST

Request方法是POST时,也可以在浏览器地址栏中加上请求参数,会将这些参数与请求BODY中的参数一块解析。

POST /index.html HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Content-Length: 15
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.9 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

uname=lp&pwd=12

Response

响应格式:

HTTP/1.1 200 OK
Date: Tue, 16 Nov 2021 14:48:29 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 44543

响应正文

手写WebServer(简易)

以后有时间的话,再仔细研究研究。

封装Request

package programmer.lp.webserver.server;

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

public class Request {
    
    

    private static final String CRLF = "\r\n";

    private String requestInfo;
    private String method; // 小写
    private String uri; // 前面带/
    private String params = ""; // 浏览器地址栏可以写参数
    private Map<String, List<String>> paramsMap;


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

    public Request(InputStream is) {
    
    
        paramsMap = new HashMap<>();
        try {
    
    
            byte[] buf = new byte[1024 * 1024];
            int len = is.read(buf);
            requestInfo = new String(buf, 0, len);
            parseRequest();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    private void parseRequest() {
    
    
        method = requestInfo.substring(0, requestInfo.indexOf(" /")).toLowerCase();
        uri = requestInfo.substring(requestInfo.indexOf("/"), requestInfo.indexOf(" HTTP/"));
        int indexOfParam = uri.indexOf("?");
        if (indexOfParam >= 0) {
    
    
            params += uri.substring(indexOfParam + 1);
            uri = uri.substring(0, indexOfParam);
        }
        if ("post".equals(method)) {
    
    
            String param = requestInfo.substring(requestInfo.lastIndexOf(CRLF)).trim();
            if (params.equals("")) {
    
    
                params += param;
            } else {
    
    
                params += "&";
                params += param;
            }
        }
//        System.out.println(method);
//        System.out.println(uri);
//        System.out.println(params);

        parseParams();

    }

    private void parseParams() {
    
    
        if (params.length() <= 0) return;
        final String[] keyValues = params.split("&");
        for (String keyValue : keyValues) {
    
    
            String[] kvArr = keyValue.split("=");
            kvArr = Arrays.copyOf(kvArr, 2);
            String key = kvArr[0];
            String value = kvArr[1];
            if (!paramsMap.containsKey(key)) {
    
    
                paramsMap.put(key, new ArrayList<>());
            }
            value = value == null ? "" : value;
            if (method.equals("get")) {
    
    
                value = decodeGetParam(value, "UTF-8");
            }
            paramsMap.get(key).add(value);
        }
    }

    private String decodeGetParam(String value, String code) {
    
    
        try {
    
    
            return java.net.URLDecoder.decode(value, code);
        } catch (UnsupportedEncodingException e) {
    
    
            return value;
        }
    }

    public String[] getParameters(String name) {
    
    
        if (!paramsMap.containsKey(name)) return null;
        return paramsMap.get(name).toArray(new String[]{
    
    });
    }

    public String getParameter(String name) {
    
    
        try {
    
    
            return getParameters(name)[0];
        } catch (Exception e) {
    
    
            return "";
        }
    }


    public String getMethod() {
    
    
        return method;
    }

    public String getUri() {
    
    
        return uri;
    }
    
}

封装Response

package programmer.lp.webserver.server;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Date;

public class Response {
    
    

    private static final String CRLF = "\r\n";
    private static final String BLANK = " ";

    private BufferedWriter writer;
    private final StringBuilder responseContent = new StringBuilder();
    private final StringBuilder responseHeaders = new StringBuilder();

    public Response(Socket client) throws IOException {
    
    
        this(client.getOutputStream());
    }

    public Response(OutputStream outputStream) {
    
    
        try {
    
    
            writer = new BufferedWriter(new OutputStreamWriter(outputStream));
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    public Response print(String content) {
    
    
        responseContent.append(content);
        return this;
    }

    public Response println(String content) {
    
    
        responseContent.append(content).append(CRLF);
        return this;
    }

    public void createHeader(int code) {
    
    
        //1、响应行: HTTP/1.1 200 OK
        responseHeaders.append("HTTP/1.1").append(BLANK);
        responseHeaders.append(code).append(BLANK);
        String status = "OK";
        switch (code) {
    
    
            case 404:
                status = "NOT FOUND";
                break;
            case 500:
                status = "SERVER ERROR";
                break;
        }
        responseHeaders.append(status).append(CRLF);
        //2、响应头(最后一行存在空行):
        /*
        Date: Mon,31Dec209904:25:57GMT
        Content-Type: text/html
        Content-Length: 39725426
         */
        responseHeaders.append("Date: ").append(new Date()).append(CRLF);
        responseHeaders.append("Content-Type: text/html").append(CRLF);
        responseHeaders.append("Content-Length: ")
                .append(responseContent.toString().getBytes(StandardCharsets.UTF_8).length)
                .append(CRLF);
        //3、空行
        responseHeaders.append(CRLF);
    }

    public void pushToBrowser(int code) {
    
    
        // 响应正文
        try {
    
    
            createHeader(code);
            writer.append(responseHeaders.toString());
            writer.append(responseContent.toString());
            writer.flush();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

}

封装Dispatcher

package programmer.lp.webserver.server;

import programmer.lp.webserver.servlet.Servlet;
import programmer.lp.webserver.webconfig.WebApp;

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

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 (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
    
    
        try {
    
    
            final String requestUri = request.getUri();
//            if (requestUri.equals("") || requestUri.equals("/")) {
    
    
//                goIndex();
//                release();
//                return;
//            }
            final Servlet servlet = WebApp.getServletFromUri(requestUri);
            if (null != servlet) {
    
    
                servlet.service(request, response);
                response.pushToBrowser(200);
            } else {
    
    
                goError(404);
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
            goError(500);
        }
        release();
    }

    private void goError(int code) {
    
    
        // 读取error.html页面
        String info = readFromResource("error.html");
        info = info.replace("#error#", code + "");
        response.print(info);
        response.pushToBrowser(code);
    }

//    private void goIndex() {
    
    
//        // 读取index.html页面
//        String info = readFromResource("index.html");
//        response.print(info);
//        response.pushToBrowser(200);
//    }

    private String readFromResource(String fileName) {
    
    
        StringBuilder info = new StringBuilder();
        try (final InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);) {
    
    
            int len;
            byte[] buf = new byte[1024];
            while ((len = resourceAsStream.read(buf)) != -1) {
    
    
                info.append(new String(buf, 0, len));
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return info.toString();
    }

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

}

封装Server

package programmer.lp.webserver.server;

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

public class Server {
    
    

    private ServerSocket serverSocket;
    private boolean isRunning;

    //启动服务
    public void start() {
    
    
        try {
    
    
            serverSocket = new ServerSocket(8888);
            isRunning = true;
            receive();
        } catch (IOException e) {
    
    
            e.printStackTrace();
            stop();
        }
    }

    //接受连接处理
    public void receive() {
    
    
        while (isRunning) {
    
    
            try {
    
    
                Socket client = serverSocket.accept();
                new Thread(new Dispatcher(client)).start();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

    //停止服务
    public void stop() {
    
    
        isRunning = false;
        try {
    
    
            serverSocket.close();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
    
    
        Server server = new Server();
        server.start();
    }

}

参考

尚学堂: JAVA课程.


本文完,感谢您的关注支持!


猜你喜欢

转载自blog.csdn.net/weixin_44018671/article/details/121360821