使用java基础实现一个简陋的web服务器软件

使用java基础实现一个简陋的web服务器软件

1、写在前面

大学已经过了一年半了,从接触各种web服务器软件已经有一年多了,从大一上最开始折腾Windows电脑自带的IIS开始,上手了自己的第一个静态网站,从此开启了web方向学习的兴趣。到现在,从陪伴了javaweb阶段的Tomcat走来,也陆续接触了jetty,Nginx等web服务器软件。但是,这些web服务器的软件也一直都是开箱即用,从未探究过其背后的原理。今天,尽量用最简单的java代码,实现一个最简陋的web服务器软件,揭开web服务器软件的神秘面纱。

2、Tomcat的架构模式

由上图可以看出,Tomcat作为如今相对成熟的web服务器软件,有着相对较为复杂的架构,有着Server、Service、Engine、Connerctor、Host、Context等诸多组件。对于Tomcat的源码分析将在以后的博文中分篇讲解

,在此不在叙述。本节主要是实现一个自己的web服务器软件,其架构也超级简单。

3、编写一个简单的web服务器类

3.1、web服务器软件面向的浏览器客户,因此在同一时间肯定不止有一个http请求,因此肯定需要开启多线程来进行服务,对类上实现Runnable接口,并重写其中的run方法。

public class ServerThread implements Runnable {
    @Override
    public void run() {}
}

3.2、在本类中只有两个方法,其中构造方法用来初始化该web服务器需要的资源,run方法用来处理请求,开启服务。

3.3、首先,我们先需要定义一堆类级别的变量,如:

  • 浏览器发送Http请求时,需要有一个Socket来接受,并且需要或等输入、输出流。

        private Socket client;
        private InputStream in;
        private OutputStream out;
  • 在Tomcat中,有一个webapp文件夹用来存放静态资源,在此,我们也在D盘根路径下定义一个webroot文件夹,用来存储静态的资源。(该路径也可以通过获取当前j软件的相对路径来动态生成,但是为了简单起见,更好的揭示web服务器的工作流程,在此采用的是绝对路径)
        private static final String WEBROOT = "D:\\webroot\\";

3.4、通过构造函数来初始化全局变量

    /**
     * 构造函数初始化客户端
     */
    public ServerThread(Socket client) {
        this.client = client;
        //其他初始化信息
        try {
            //获取客户端连接的流对象
            in = client.getInputStream();
            out = client.getOutputStream();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

该构造函数相当的简单,就是获取浏览器发来的Socket,并拿到其中的输入、输出流,然后赋值给全局变量。

3.5、run()方法方法体的编写

  1. 通过输入流获得请求的内容
            //读取请求的内容
            reader = new BufferedReader(new InputStreamReader(in));
  1. 解析获取的内容,并且放回网站得首页(index.html)

                //取得:后面得内容
                String line = reader.readLine().split(" ")[1].replace("/","\\");
                if("\\".equals(line)) {
                    line += "index.html";
                }
    
                System.out.println(line);
                //获取文件的后缀名
                String strType = line.substring(line.lastIndexOf(".")+1, line.length());
                System.out.println("strType = " + strType);
  2. 给浏览器进行响应(用浏览器打开任意一个网站,调出控制台观查其响应头,因此我们的web服务器也应该把响应头给浏览器写出)

所以我们的代码应该为:

            //给用户响应
            pw = new PrintWriter(out);
            input = new FileInputStream(WEBROOT + line);

            //BufferedReader buffer = new BufferedReader(new InputStreamReader(input));
            //写响应头
            pw.println("HTTP/1.1 200 ok");
            pw.println("Content-Type: "+ contentMap.get(strType)  +";charset=utf-8");
            pw.println("Content-Length: " + input.available());
            pw.println("Server: hello");
            pw.println("Date: " + new Date());
            pw.println();
            pw.flush();

因为放返回数据的类型有多样,所以我们可以用一个map集合来存储,并在类加载前将数据存入。

    /**
     * 静态资源的集合(对应的文本类型)
     */
    private static Map<String,String> contentMap = new HashMap<>();

    //初始化静态资源的集合
    static {
        contentMap.put("html", "text/html");
        contentMap.put("htm", "text/html");
        contentMap.put("jpg", "image/jpeg");
        contentMap.put("jpeg", "image/jpeg");
        contentMap.put("gif", "image/gif");
        contentMap.put("js", "application/javascript");
        contentMap.put("css", "text/css");
        contentMap.put("json", "application/json");
        contentMap.put("mp3", "audio/mpeg");
        contentMap.put("mp4", "video/mp4");
    }

3.6、向浏览器写回数据,并写完后进行刷新

            //向浏览器写数据
            byte[] bytes = new byte[1024];
            int len = 0;
            while ((len = input.read(bytes)) != -1){
                out.write(bytes, 0, len);
            }
            pw.flush();

3.7、关闭流、释放资源

                if(input != null) {
                    input.close();
                }

                if(pw != null) {
                    pw.close();
                }

                if(reader != null) {
                    reader.close();
                }
                if(out != null) {
                    out.close();
                }

                if(client != null) {
                    client.close();

                }

3.8、该类完整的代码为:

package com.xgp.company;

import java.io.*;
import java.net.Socket;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 服务线程
 * @author 薛国鹏
 */
public class ServerThread implements Runnable {

    /**
     * 静态资源的集合(对应的文本类型)
     */
    private static Map<String,String> contentMap = new HashMap<>();

    //初始化静态资源的集合
    static {
        contentMap.put("html", "text/html");
        contentMap.put("htm", "text/html");
        contentMap.put("jpg", "image/jpeg");
        contentMap.put("jpeg", "image/jpeg");
        contentMap.put("gif", "image/gif");
        contentMap.put("js", "application/javascript");
        contentMap.put("css", "text/css");
        contentMap.put("json", "application/json");
        contentMap.put("mp3", "audio/mpeg");
        contentMap.put("mp4", "video/mp4");
    }

    private Socket client;
    private InputStream in;
    private OutputStream out;

    private static final String WEBROOT = "D:\\webroot\\";

    /**
     * 构造函数初始化客户端
     */
    public ServerThread(Socket client) {
        this.client = client;
        //其他初始化信息
        try {
            //获取客户端连接的流对象
            in = client.getInputStream();
            out = client.getOutputStream();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 解析信息,给用户响应
     */
    @Override
    public void run() {
        PrintWriter pw = null;
        BufferedReader reader = null;
        FileInputStream input = null;
        try {
            //读取请求的内容
            reader = new BufferedReader(new InputStreamReader(in));

            /**
             * //请求的资源
             * //解析请求头
             * Host: static.zhihu.com
             * User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0
             * Accept: text/css,
             */
            //取得:后面得内容
            String line = reader.readLine().split(" ")[1].replace("/","\\");
            if("\\".equals(line)) {
                line += "index.html";
            }

            System.out.println(line);
            //获取文件的后缀名
            String strType = line.substring(line.lastIndexOf(".")+1, line.length());
            System.out.println("strType = " + strType);

            //给用户响应
            pw = new PrintWriter(out);
            input = new FileInputStream(WEBROOT + line);

            //BufferedReader buffer = new BufferedReader(new InputStreamReader(input));
            //写响应头
            pw.println("HTTP/1.1 200 ok");
            pw.println("Content-Type: "+ contentMap.get(strType)  +";charset=utf-8");
            pw.println("Content-Length: " + input.available());
            pw.println("Server: hello");
            pw.println("Date: " + new Date());
            pw.println();
            pw.flush();

            //向浏览器写数据
            byte[] bytes = new byte[1024];
            int len = 0;
            while ((len = input.read(bytes)) != -1){
                out.write(bytes, 0, len);
            }
            pw.flush();

        }catch (Exception e) {
            throw new RuntimeException(e.getMessage() + "服务端的run方法出错");
        }finally {
            try {
                if(input != null) {
                    input.close();
                }

                if(pw != null) {
                    pw.close();
                }

                if(reader != null) {
                    reader.close();
                }
                if(out != null) {
                    out.close();
                }

                if(client != null) {
                    client.close();

                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}

4、编写启动类

4.1、一般连接性行为会采用池化技术,这里使用一个可以弹性伸缩的线程池。(如果想要跟为专业化,最好是使用一个默认的线程数量的线程池,并且可以让开发者自行设定)

            //创建一个可伸缩的连接池
            pool = Executors.newCachedThreadPool();

4.2、监听端口。(这里监听的是80端口,其实监听端口的权力应该交给使用者指定)

            //启动服务器,监听8080端口
            server = new ServerSocket(80);
            System.out.println("服务器启动,当前端口为80");

4.3、启动服务器,处理来自于浏览器的请求

while (!Thread.interrupted()){
    //不停接收客户端请求
    Socket client = server.accept();
    //向线程池中提交任务
    pool.execute(new ServerThread(client));
}

4.4、关闭连接,释放资源

if(server != null) {
    server.close();
}

if(pool != null) {
    pool.shutdown();
}

4.5、本类完整的代码为:

package com.xgp.company;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 服务端
 * @author 薛国鹏
 */
public class MyHttpServer {
    public static void main(String[] args) {
        ServerSocket server = null;
        ExecutorService pool = null;
        try {
            //创建一个可伸缩的连接池
            pool = Executors.newCachedThreadPool();
            //启动服务器,监听8080端口
            server = new ServerSocket(80);
            System.out.println("服务器启动,当前端口为80");
            while (!Thread.interrupted()){
                //不停接收客户端请求
                Socket client = server.accept();
                //向线程池中提交任务
                pool.execute(new ServerThread(client));
            }
        }catch (Exception e) {
            throw new RuntimeException(e.getMessage() + "服务端异常");
        }finally {
            try {
                if(server != null) {
                    server.close();
                }

                if(pool != null) {
                    pool.shutdown();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

5、进行测试

5.1、将测试的静态文件放在D:\webroot目录下,如图是一个使用Vue编写的一个静态的前端项目

5.2、启动自己编写的web服务器软件,看到控制台出现了"服务器启动,当前端口为80"则服务启动成功

5.3、输入域名,进行访问

调出浏览器控制台,看请求的资源是否正常解析:

可以看到,页面正确渲染了,请求的资源也没有发生问题,因此我们自己编写的简陋版本的web服务器软件编写成功。

原文地址:https://www.cnblogs.com/xgp123/p/12345028.html

时间: 2024-10-08 22:41:44

使用java基础实现一个简陋的web服务器软件的相关文章

探秘Tomcat——从一个简陋的Web服务器开始

前言: 无论是之前所在实习单位小到一个三五个人做的项目,还是如今一个在做的百人以上的产品,一直都能看到tomcat的身影.工作中经常遇到的操作就是启动和关闭tomcat服务,或者修改了摸个java文件,编译该文件,将生成的class文件塞到tomcat目录下相应的jar包中去,以使其生效,但是也可以热部署,不需要这么繁琐的操作. 总之,一直以来都是习惯了tomcat的存在,没有深究tomcat的运行机制和原理,上一次对于tomcat源码的跃跃欲试还是去年的事儿了——<探秘Tomcat(一)——M

自己动手模拟开发一个简单的Web服务器

开篇:每当我们将开发好的ASP.NET网站部署到IIS服务器中,在浏览器正常浏览页面时,可曾想过Web服务器是怎么工作的,其原理是什么?“纸上得来终觉浅,绝知此事要躬行”,于是我们自己模拟一个简单的Web服务器来体会一下. 一.请求-处理-响应模型 1.1 基本过程介绍 每一个HTTP请求都会经历三个步凑:请求-处理-响应:每当我们在浏览器中输入一个URL时都会被封装为一个HTTP请求报文发送到Web服务器,而Web服务器则接收并解析HTTP请求报文,然后针对请求进行处理(返回指定的HTML页面

Java中常见的5种WEB服务器介绍

这篇文章主要介绍了Java中常见的5种WEB服务器介绍,它们分别是Tomcat.Resin.JBoss.WebSphere.WebLogic,需要的朋友可以参考下 Web服务器是运行及发布Web应用的容器,只有将开发的Web项目放置到该容器中,才能使网络中的所有用户通过浏览器进行访问.开发Java Web应用所采用的服务器主要是与JSP/Servlet兼容的Web服务器,比较常用的有Tomcat.Resin.JBoss.WebSphere 和 WebLogic 等,下面将分别进行介绍. Tomc

(一)一个简单的Web服务器

万丈高楼平地起,首先我们必须了解 超文本传输协议(HTTP) 以后才能够比较清晰的明白web服务器是怎么回事. 1. 浅析Http协议 HTTP是一种协议,允许web服务器和浏览器通过互联网进行来发送和接受数据.它是一种请求和响应协议.客户端请求一个文件而服务器响应请求.HTTP使用可靠的TCP连接--TCP默认使用80端口.第一个HTTP版是HTTP/0.9,然后被HTTP/1.0所替代.正在取代HTTP/1.0的是当前版本HTTP/1.1,它定义于征求意见文档(RFC) 2616,可以从ht

自己模拟的一个简单的web服务器

首先我为大家推荐一本书:How Tomcat Works.这本书讲的很详细的,虽然实际开发中我们并不会自己去写一个tomcat,但是对于了解Tomcat是如何工作的还是很有必要的. Servlet容器是如何工作的 servlet容器是一个复杂的系统.不过,一个servlet容器要为一个servlet的请求提供服务,基本上有三件事要做: 1,创建一个request对象并填充那些有可能被所引用的servlet使用的信息,如参数.头部. cookies.查询字符串. URI 等等.一个 request

使用 Nodejs 搭建一个简单的Web服务器

使用Nodejs搭建Web服务器是学习Node.js比较全面的入门教程,因为要完成一个简单的Web服务器,你需要学习Nodejs中几个比较重要的模块,比如:http协议模块.文件系统.url解析模块.路径解析模块.以及301重定向问题,下面我们就简单讲一下如何来搭建一个简单的Web服务器. 作为一个Web服务器应具备以下几个功能: 1.能显示以.html/.htm结尾的Web页面 2.能直接打开以.js/.css/.json/.text结尾的文件内容 3.显示图片资源 4.自动下载以.apk/.

如何用PHP/MySQL为 iOS App 写一个简单的web服务器(译) PART1

原文:http://www.raywenderlich.com/2941/how-to-write-a-simple-phpmysql-web-service-for-an-ios-app 作为一个iPhone/iPad开发者,能够自己写一个简单的web服务器将是很有用的. 例如,你可能希望在软件启动时显示一些来自服务器的更新,或者在服务器端保存一些用户数据.除了你的想象力,没有什么能限制你了. 在第一篇中,我们将会一步一步的建立一个web服务器,基于promo code system(促销码系

一个简单的web服务器

static void Main(string[] args) { IPAddress localAddress = IPAddress.Loopback;//获取本机的ip地址 IPEndPoint endPoint =new IPEndPoint(localAddress, 49155); Socket socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); socket.Bind

一个简单的Web服务器-支持Servlet请求

上接 一个简单的Web服务器-支持静态资源请求,这个服务器可以处理静态资源的请求,那么如何处理Servlet请求的呢? 判断是否是Servlet请求 首先Web服务器需要判断当前请求是否是Servlet请求. 像Tomcat,通过解析HTTP报文拿到请求url后,就可以根据web.xml来查找是否有匹配的Servlet,如果有匹配则认定为是一个有效的Servlet请求,然后将request,response传给对应的servlet的service()方法. 这里既然要实现一个简单的Web服务器,