Socket、多线程、反射、IO流通过类文件实现简易版http服务器

测试类

public class TestHttpServer {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        MyHttpServer server = new MiniHttpServer(80);
        server.startup();//启动服务
        server.shutdown();//关闭服务
    }
}

接口

public interface MyHttpServer {
    void startup() throws IOException, ClassNotFoundException;
    Request createRequest(ServletContext servletContext) throws IOException;
    String service(ServletContext servletContext) throws IOException, ClassNotFoundException;
    Response createResponse(ServletContext servletContext) throws IOException;
    void shutdown();
}

接口实现类

/**
 * 服务器
 */
public class MiniHttpServer implements MyHttpServer {
    //定义变量记录请求数量,线程安全的计数器
    private AtomicLong counter = new AtomicLong(0);
    private ServerSocket serverSocket;
    private Integer port;
    /** thread pool for connection handlers */
    private static final ExecutorService connectionThreadPool =
            Executors.newFixedThreadPool(3);
    public MiniHttpServer(int port) {
        this.port = port;
    }

    public void startup() throws IOException, ClassNotFoundException {
        //创建服务端socket通信对象
        System.out.println("The server is started on port 80 with IP 172.171.3.37");
        serverSocket = new ServerSocket(port);//构造参数传端口号。
        while (true) {
            Socket socket = serverSocket.accept();//程序阻塞等待客户端连接
            //获取客户端地址
            InetAddress clientAddr = socket.getInetAddress();
            String clientHost = (clientAddr != null
                    ? clientAddr.getHostAddress()
                    : "0.0.0.0");
            System.out.println("The Request [" + counter.incrementAndGet() + "]  client ip: " + clientHost);
            connectionThreadPool.execute(//该方法会自动执行传入线程池里线程的run方法
                    new ConnectionHandler(socket, clientHost));
        }
    }

    public void shutdown() {
        try {
            serverSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

ConnetionHandler类

public class ConnectionHandler implements Runnable {
    private Socket socket;
    private String remoteHost;//客户端地址
    public ConnectionHandler(Socket socket, String remoteHost) {
        this.socket = socket;
        this.remoteHost = remoteHost;
    }
    @Override
    public void run() {
        //创建一个web服务器也叫做web容器的上下文环境
        System.out.println("The server starts to init the context");
        ServletContext servletContext = new ServletContext();
        //封装request对象的目的是为了后边处理逻辑可以只使用该对象即可,方便携带请求过来的数据。
        //ServletContext
        try {
            //把socket套接字设置到上下文环境中
            servletContext.setSocket(socket);
            //该方法调用会在内部使用socket中的输入流读取客户端发过来的请求数据并且封装到Request对象里
            System.out.println("The server init a request entity Request");
            servletContext.setRequest(createRequest(servletContext));
            //根据uri找到对应的java文件,也就是servlet文件,然后获取该java文件的执行内容,最后封装成数据放到上下文环境中
            System.out.println("Server fetch user request data");
            servletContext.setData(service(servletContext));
            //使用上下文环境中的数据封装需要返回给用户的数据,这些数据大部分都是在service方法内部产生的。
            System.out.println("The server init a response entity Response");
            servletContext.setResponse(createResponse(servletContext));
            System.out.println("Server context init successfully..........");
            //最后返回给客户端它请求的具体数据。
            response(servletContext);
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private void response(ServletContext servletContext) throws IOException {
        Socket socket = servletContext.getSocket();
        Request request = servletContext.getRequest();
        Response response = servletContext.getResponse();
        OutputStream outputStream = socket.getOutputStream();
        PrintWriter out = new PrintWriter(outputStream);
        String resp = response.buildResponse(request, response);
        out.print(resp);
        out.flush();
        //关闭连接
        //socket.close();
    }

    public Request createRequest(ServletContext servletContext) throws IOException {
        InputStream inputStream = servletContext.getSocket().getInputStream();//获取输入流读取浏览器的请求数据
        InputStreamReader isr = new InputStreamReader(inputStream);//转换流,把字节流转成字符流
        //把转换后的字符流使用缓冲流包裹起来。因为我们要使用缓冲流的readline方法
        BufferedReader br = new BufferedReader(isr);
        //创建一个对象用于封请求的数据。此时创建出来的request对象引用里的各个变量参数都是空,需要我们读取请求过来的
        //数据并把这些数据解析后存入该request对象里。存完后该对象才会有值。
        Request request = new Request();

        //下面开始读取流中的数据,并且把数据放到request对象内。
        int count  = 1;
        //因为http请求的头部信息是key: value形式的字符串,所以我们可以使用键值对集合map来存储
        HashMap<String, String> header = new HashMap<>();

        String b = null;
        while (!(b=br.readLine()).equals("")) {
            System.out.println(b);
            //为什么需要设置这个count,因为请求的第一行的格式不是键值对形式。
            //GET /login HTTP/1.1
            if (count == 1) {
                String[] requests = b.split(" ");
                //初始化对象里的属性
                request.setMethod(requests[0]);
                if (requests[1].equals("/"))
                    request.setUri("/index");
                else {
                    request.setUri(requests[1]);
                }
                request.setVersion(requests[2]);
            } else {
                String[] split = b.split(":");
                if (split != null && split.length ==2) {
                    //因为浏览器请求的header头部是按照key:value的形式存放,所以我们可以使用集合的Hasmap存储这部分内容
                    header.put(split[0], split[1].trim());
                }
            }
            count++;
        }
        //把请求内容按照空格切割,切割后数组里的第二个元素就是路径名
        request.setHeaders(header);
        //流使用完成后需要手动关闭。
        /*br.close();
        isr.close();
        inputStream.close();*/
        //返回构建出来的request,此时浏览器请求过来的数据已经全部被封装进入request这个对象里了,以后使用请求数据
        //只需要使用该request对象即可。
        return request;
    }

    public String service(ServletContext servletContext) throws IOException, ClassNotFoundException {
        //获取java文件的class路径
        URL url = MyHttpServer.class.getResource("./");
        String classFilePath = url.toString().split(":")[1];
        String[] split = classFilePath.split("/");
        classFilePath = split[split.length-1];
        classFilePath = classFilePath + ".servlet";
        //获取某个路径下的所有文件,使用File类
        File file = new File(url.toString().split(":")[1] + "servlet/");// /Users/fcp/food/java/java2202/out/production/javase/socket/servlet/
        String webpage = null;
        //首先判断该file是否存在
        if(file.exists()) {
            File[] files = file.listFiles();
            for (File f : files) {
                if (f.getName().endsWith("Servlet.class")) {//只使用servlet文件
                    //通过字符串处理拼接类的全限定名
                    String classFileName = classFilePath;
                    classFileName = classFileName + "." + f.getName();
                    classFileName = classFileName.substring(0, classFileName.length()-6);
                    //通过反射获取该文件的Class对象。Class.forName("socket.servlet.xxxx");
                    Class<?> clazz = Class.forName(classFileName);
                    //查看该对象上的注解是否是WebServlet
                    if (clazz.isAnnotationPresent(WebServlet.class)) {
                        WebServlet annotation = clazz.getAnnotation(WebServlet.class);
                        //获取注解的value
                        String value = annotation.value();
                        //如果用户没有指定uri,那么就是访问网站的首页
                        if (value.equals("/")) value ="/index";
                        //判断该value是否和前端传入的地址相同
                        if (value.equals(servletContext.getRequest().getUri())) {
                            //通过反射创建该servlet对象,然后调用该对象的service方法
                           /* Object instance = clazz.getDeclaredConstructor().newInstance();
                            Method method = clazz.getDeclaredMethod("service");
                            method.invoke(instance);*/
                            //让服务器返回一个登录的html页面  login.html
                            //使用IO流读取html文件内容
                            FileInputStream fis = new FileInputStream("/Users/fcp/food/java/java2202/javase/src/html/" + value +".html");
                            byte[] bytes1 = new byte[1024*8];
                            fis.read(bytes1);
                            webpage = new String(bytes1);
                        }
                    }
                }
            }
        }
        return webpage;
        /*createResponse(request, socket, s);*/
    }

    public Response createResponse(ServletContext servletContext) throws IOException {
        Request request = servletContext.getRequest();
        String data = (String) servletContext.getData();

        //创建一个响应实体
        Response response = new Response();
        response.setCode(HttpConfig.HTTP_CODE);
        response.setStatus(HttpConfig.HTTP_STATUS);
        response.setVersion(request.getVersion());
        //构建响应头部数据
        Map<String,String> header = new HashMap<>();
        header.put(HttpConfig.CONTENT_TYPE_KEY, HttpConfig.CONTENT_TYPE_VALUE);
        response.setHeaders(header);

        //存入响应数据
        response.setMessage(data);
        return response;
    }
}

WebServlet注解类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface WebServlet {
    String value() default "";
}

html页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <style>
        input {
            width: 200px;
            height: 30px;
            border-radius: 10px;
        }
        .container {
            width: 800px;
            margin-left: auto;
            margin-right: auto;
            margin-top: auto;
            margin-bottom: auto;
        }
    </style>
</head>
<body style="background-color: cornflowerblue">
    <div class="container">
        <label>用户名:</label>
        <input type="text" name="username" placeholder="username"><br><br>
        <label>密码:</label>
        <input type="password" name="username" placeholder="password"><br><br>
        <label>确认密码:</label>
        <input type="password" name="username" placeholder="......."><br><br>
        <label>邮箱:</label>
        <input type="email" name="username" placeholder="eamil"><br><br>
        <label>电话:</label>
        <input type="phone" name="username" placeholder="phone">
    </div>
</body>
</html>

//首页html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
    <style>
        .container {
            display: flex;
            width: 800px;
            margin-left: auto;
            margin-right: auto;
        }
        .img{
            width: 300px;
            height: 500px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1 style="margin-left: auto; margin-right: auto;">欢迎来到英雄联盟</h1>
        <img class="img" src="./img/lol01.jpeg">
    </div>
</body>
</html>

Request类

public class Request {
    /**
     * 请求方法 GET/POST/PUT/DELETE/OPTION...
     */
    private String method;
    /**
     * 请求的uri
     */
    private String uri;
    /**
     * http版本
     */
    private String version;
    /**
     * 请求头
     */
    private Map<String, String> headers;
    /**
     * 请求参数相关
     */
    private String message;
}

Response类

public  class Response {
    private String version;
    private int code;
    private String status;
    private Map<String, String> headers;
    private String message;

    public static String buildResponse(Request request, Response response) {
        Response httpResponse = new Response();
        httpResponse.setCode(response.getCode());
        httpResponse.setStatus(response.getStatus());
        httpResponse.setVersion(request.getVersion());
        Map<String, String> headers = response.getHeaders();
        Optional<String> optional = Optional.ofNullable(response.getMessage());
        headers.put("Content-Length", String.valueOf(optional.orElseGet(() -> "").length()));
        httpResponse.setHeaders(headers);
        httpResponse.setMessage(response.getMessage());
        StringBuilder builder = new StringBuilder();
        buildResponseLine(httpResponse, builder);
        buildResponseHeaders(httpResponse, builder);
        buildResponseMessage(httpResponse, builder);
        return builder.toString();
    }
    private static void buildResponseLine(Response response, StringBuilder stringBuilder) {
        stringBuilder.append(response.getVersion()).append(" ").append(response.getCode()).append(" ")
                .append(response.getStatus()).append("\n");
    }
    private static void buildResponseHeaders(Response response, StringBuilder stringBuilder) {
        for (Map.Entry<String, String> entry : response.getHeaders().entrySet()) {
            stringBuilder.append(entry.getKey()).append(":").append(entry.getValue()).append("\n");
        }
        stringBuilder.append("\n");
    }
    private static void buildResponseMessage(Response response, StringBuilder stringBuilder) {
        stringBuilder.append(response.getMessage());
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public int getCode() {
        return code;
    }

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

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public Map<String, String> getHeaders() {
        return headers;
    }

    public void setHeaders(Map<String, String> headers) {
        this.headers = headers;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

ServletContext类

public class ServletContext<T> {
    private T data;
    private Socket socket;
    private Request request;
    private Response response;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public Socket getSocket() {
        return socket;
    }

    public void setSocket(Socket socket) {
        this.socket = socket;
    }

    public Request getRequest() {
        return request;
    }

    public void setRequest(Request request) {
        this.request = request;
    }

    public Response getResponse() {
        return response;
    }

    public void setResponse(Response response) {
        this.response = response;
    }
}

配置文件类HttpConfig

public class HttpConfig {
    public static final Integer HTTP_CODE = 200;
    public static final String HTTP_STATUS = "ok";
    public static final String CONTENT_TYPE_KEY = "Content-Type";
    public static final String CONTENT_TYPE_VALUE = "text/html;charset=UTF-8";
}

Servlet类如下

//浏览器输入地址   http://localhost   回车就可以访问
@WebServlet("/")//访问网站登录页
public class IndexServlet {
    public void service() {
        put();
    }
    public void put() {
        System.out.println("welcome!!!");
    }
}

//浏览器输入地址   http://localhost/login   回车就可以访问
@WebServlet("/login")//访问网站登录页
public class LoginServlet {
    public void service() {
        put();
    }
    public void put() {
        System.out.println("用户登录成功!!!");
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值