实现一个简单的WEB应用容器

我们只实现一个静态页面服务器。

 一、创建xml配置文件

配置文件中有对端口、访问根路径、实际路径的配置。

1.dtd约束文件

<!ELEMENT contain (listener+)>

        <!ELEMENT listener (context,path) >
        <!ELEMENT context (#PCDATA) >
        <!ELEMENT path (#PCDATA) >
        <!ATTLIST listener port ID #CDATA >

2.xml配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE contain SYSTEM ".\web-setting.dtd">
<contain>
    <listener port="60000">
        <context>/</context>
        <path>.</path>
    </listener>

    <listener port="50000">
        <context>/</context>
        <path>.</path>
    </listener>
</contain>

二、监听器类,监听端口

1.抽象类Environment

用于存储运行环境变量

public abstract class Environment {
    private static String classpath;

    public static String getClasspath() {
        return classpath;
    }

    public static void setClasspath(String classpath) {
        Environment.classpath = classpath;
    }
}

2.实体类Listener

package xyz.syyrjx.enitty;

public class Listener extends Environment{
    private Integer port;
    private String context;
    private String path;
    private Thread runThread;//运行线程
    private Integer failCount = 0;//启动线程失败次数

    public Listener() {
    }

    public Listener(Integer port, String context, String path) {
        this.port = port;
        this.context = context;
        this.path = path;
    }

    public Integer getFailCount() {
        return failCount;
    }

    public void setFailCount(Integer failCount) {
        this.failCount = failCount;
    }

    public Thread getRunThread() {
        return runThread;
    }

    public void setRunThread(Thread runThread) {
        this.runThread = runThread;
    }

    public Integer getPort() {
        return port;
    }

    public void setPort(Integer port) {
        this.port = port;
    }

    public String getContext() {
        return context;
    }

    public void setContext(String context) {
        this.context = context;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    @Override
    public String toString() {
        return "Listener{" +
                "port='" + port + '\'' +
                ", context='" + context + '\'' +
                ", path='" + path + '\'' +
                '}';
    }
}

三、请求对象和响应对象

1.请求对象

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;

public interface Request {
    int READ_SIZE = 1024;

    static <T> T parse(Class<T> clazz,InputStream inputStream) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            if (Objects.equals(method.getName(),"parse")){
                method.setAccessible(true);
                return (T)method.invoke(null,inputStream);
            }
        }
        throw new NoSuchMethodException();
    }

    String getParameter(String key);

    String getUri();
    String getMethod();


}
package xyz.syyrjx.enitty;


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

public class HttpRequest implements Request{

    private String uri;
    private String method;
    private Map<String,String> parameters = new HashMap<>();

    private HttpRequest(){}

    /**
     * 解析输入流的内容
     * @param inputStream socket输入流
     * @return 返回HttpRequest请求对象
     * @throws IOException
     */
    public static HttpRequest parse(InputStream inputStream) throws IOException {
        int readCount = 0;
        byte[] bytes = new byte[Request.READ_SIZE];
        StringBuilder builder = new StringBuilder();

//        //问题:在inputStream内容被读完后会卡死在read方法
//        while ((readCount = inputStream.read(bytes)) != -1){
//            builder.append(new String(bytes,0,readCount));
//        }

        while ((readCount = inputStream.read(bytes)) > 0){
            builder.append(new String(bytes,0,readCount));
            if (readCount < Request.READ_SIZE){
                break;
            }
        }
        String requestPackage = builder.toString();

        return parseRequestPackage(requestPackage);

    }

    /**
     * 解析请求包
     * @param requestPackage 请求包字符串
     * @return 返回HttpRequest对象
     */
    private static HttpRequest parseRequestPackage(String requestPackage){
//        System.out.println(requestPackage);
        HttpRequest res = new HttpRequest();

        int requestMethodSpace = requestPackage.indexOf(' ');
        int contextSpace = requestPackage.indexOf(' ',requestMethodSpace + 1);
        String requestMethod = requestPackage.substring(0,requestMethodSpace);
        String context = requestPackage.substring(requestMethodSpace + 1,contextSpace);
        String uri = context;
        int questionMarkIndex = context.indexOf('?');
        if (questionMarkIndex != -1){
            String parametersString = context.substring(questionMarkIndex + 1);
            uri = context.substring(0,questionMarkIndex);
            String[] pairs = parametersString.split("&");
            for (String pair : pairs) {
                String[] kv = pair.split("=");
                if (kv.length == 2){
                    res.parameters.put(kv[0],kv[1]);
                }else{
                    res.parameters.put(kv[0],"");
                }
            }
        }

        res.uri = uri;
        res.method = requestMethod;
        return res;
    }

    @Override
    public String getParameter(String key) {
        return parameters.get(key);
    }

    @Override
    public String getUri() {
        return this.uri;
    }

    @Override
    public String getMethod() {
        return this.method;
    }
}

2.响应对象

import java.io.IOException;

public interface Response {

    void response() throws IOException;

    void notFound() throws IOException;
}
import java.io.IOException;
import java.io.OutputStream;

public class HttpResponse implements Response {

    private String code = "200";
    private String content;
    private String contentType = "text/html";
    private OutputStream outputStream;

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getContentType() {
        return contentType;
    }

    public void setContentType(String contentType) {
        this.contentType = contentType;
    }

    public HttpResponse(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    @Override
    public void response() throws IOException {
        outputStream.write(this.toString().getBytes());
    }

    @Override
    public void notFound() throws IOException {
        this.content = "404 Not Found";
        response();
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("HTTP /1.1 ")
                .append(code)
                .append("\r\n")
                .append("Content-type: ")
                .append(contentType)
                .append("\r\n")
                .append("Content-length: ")
                .append(content.length())
                .append("\r\n")
                .append("\r\n")
                .append(content);
        return builder.toString();
    }
}

四、主类

在主类中完成Listener对象的创建,以及保证每个监听器对象都由一个线程在运行。

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import xyz.syyrjx.enitty.Environment;
import xyz.syyrjx.enitty.Listener;
import xyz.syyrjx.exception.SettingFileException;
import xyz.syyrjx.exception.SettingFileNotFoundException;
import xyz.syyrjx.listener.ListenerThread;

import java.io.*;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;

public class Start {
    static List<Listener> listenerList = new ArrayList<>();

    /**
     * 解析listener
     */
    static {
        try {
            Environment.setClasspath(URLDecoder.decode(Start.class.getClassLoader().getResource("").toString().substring(6),"utf-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        File setting = new File(Environment.getClasspath(),"setting.xml");
        if (!setting.exists()){
            throw new SettingFileNotFoundException("没有找到配置文件,请确认配置文件路径");
        }
        try {
            Document contains = Jsoup.parse(setting);
            Elements listeners = contains.getElementsByTag("listener");
            for (Element elementListener : listeners) {
                Integer port = null;
                try {
                    port = Integer.valueOf(elementListener.attr("port"));
                } catch (NumberFormatException e) {
                    throw new SettingFileException("请检查listener标签的port属性,以确保该属性为数字类型",e);
                }
                String context = elementListener.getElementsByTag("context").text();
                String path = elementListener.getElementsByTag("path").text();
                listenerList.add(new Listener(port,context,path));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        while (true){
            for (Listener listener : listenerList){
                if (listener.getFailCount() < 5 && //失败次数小于5
                        (listener.getRunThread() == null || !listener.getRunThread().isAlive())){ //线程未创建或线程不存活
                    try {
                        Thread t = new Thread(new ListenerThread(listener));
                        t.setName(String.valueOf(listener.getPort()));
                        listener.setRunThread(t);
                        t.start();
                        listener.setFailCount(0);
                    } catch (Exception e) {
                        e.printStackTrace();
                        listener.setFailCount(listener.getFailCount() + 1);
                    }
                }
            }
        }
    }
}

五、监听器工作线程类

解析获得请求对象、创建响应对象、调用服务类的service()方法

import xyz.syyrjx.enitty.HttpRequest;
import xyz.syyrjx.enitty.HttpResponse;
import xyz.syyrjx.enitty.Listener;
import xyz.syyrjx.enitty.Request;
import xyz.syyrjx.exception.StarterException;
import xyz.syyrjx.service.RunnableService;
import xyz.syyrjx.service.Service;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.ServerSocket;
import java.net.Socket;


public class ListenerThread implements Runnable{
    private Listener listener;

    public ListenerThread() {
    }

    public ListenerThread(Listener listener) {
        this.listener = listener;
    }

    @Override
    public void run() {
        ServerSocket serverSocket = null;
        Socket socket = null;
        try {
            serverSocket = new ServerSocket(listener.getPort());
            while (true){
                //获取socket连接
                socket = serverSocket.accept();

                //输入流
                InputStream inputStream = socket.getInputStream();
                HttpRequest request = Request.parse(HttpRequest.class,inputStream);

                //输出流
                OutputStream outputStream = socket.getOutputStream();
                HttpResponse response = new HttpResponse(outputStream);

                //服务调用
                if (!request.getUri().contains(listener.getContext())){
                    response.notFound();
                }
                Thread t = new Thread(new RunnableService(new Service(request,response,listener)));
                t.setName(listener.getRunThread() + listener.getContext());
                t.start();
            }

        }catch (IOException e) {
            throw new StarterException("应用启动异常,请检查端口是否被占用",e);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        } finally {
            if (null != socket){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != serverSocket){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

六、服务方法

1.服务类

import xyz.syyrjx.enitty.HttpResponse;
import xyz.syyrjx.enitty.Listener;
import xyz.syyrjx.enitty.Request;
import xyz.syyrjx.enitty.Response;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class Service {
    static final int READ_SIZE = 1024;

    private Request request;
    private Response response;
    private Listener listener;

    public Service() {
    }

    public Service(Request request, Response response,Listener listener) {
        this.request = request;
        this.response = response;
        this.listener = listener;
    }

    public void service() throws IOException {
        String uri = request.getUri();

        String path = listener.getPath();
        if (path.equals(".") || path.equals("./") || path.equals(".\\")){
            path  = Listener.getClasspath();
        }
        if (uri.equals("/")){
            uri = "/index.html";
        }
        File file = new File(path,uri);
        if (!file.exists()){
            response.notFound();
        }else{
            FileInputStream fileInputStream = new FileInputStream(file);
            byte[] bytes = new byte[Service.READ_SIZE];
            int readCount = 0;
            StringBuilder builder = new StringBuilder();
            while ((readCount = fileInputStream.read(bytes)) != -1){
                builder.append(new String(bytes,0,readCount));
            }
            ((HttpResponse)response).setContent(builder.toString());
            response.response();
        }
    }
}

2.适配器类,将Service类适配为Runnable类

import java.io.IOException;

public class RunnableService implements Runnable{
    private Service service;

    public RunnableService(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        try {
            service.service();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

七、启动工程,发送请求到60000端口

1.创建静态页面

欢迎页index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
index
</body>
</html>

测试页test.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
111111111
</body>
</html>

2.启动工程

3.访问

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值