五十五、应用层篇-实现一个简易的web服务器

通过前面的学习,我们实战过用tomcat作为服务器承载服务,为了更好地学习HTTP协议,决心插入一节:用代码实现一个简易的web服务器,满足接收客户端请求、处理和响应的功能

本文我使用JAVA来实现,当然了,读者朋友可以使用其他的语言和技术,比如nodejs来实现只需要几行代码。

老规矩,代码仓库见文末。

五十五、应用层篇-实现一个简易的web服务器

一、接收客户端请求

如何通过JAVA来实现一个最简易的web服务呢?并且我们打印下请求头等信息。代码如下:

 1public static void main(String[] args) throws IOException {
2    //服务端监听在8888端接口号
3    ServerSocket server = new ServerSocket(8888);
4    System.out.println("服务器已经启动...正在监听8888端口,随时等待客户端连接");
5    //服务端创建一个线程来处理客户端请求
6    while (!Thread.interrupted()){
7        //接收用户请求
8        Socket client = server.accept();
9        //获取输入输出流
10        InputStream ins = client.getInputStream();
11        OutputStream out = client.getOutputStream();
12        //打印获取到的请求内容
13        int len = 0;
14        byte[] b = new byte[1024];
15        while((len = ins.read(b)) != -1){
16            System.out.println(new String(b,0,len));
17        }
18    }
19}

此时我们访问地址: localhost:8888 打印出来的结果为:

1服务器已经启动...正在监听8888端口,随时等待客户端连接
2GET / HTTP/1.1
3Host: localhost:8888
4User-Agent: curl/7.55.1
5Accept: */*

关注下第一行,由于我其实请求的是根路径,所以是/,如果我在这里请求 localhost:8888/index.html 那么就会显示 GET /index.html HTTP/1.1 这样的信息,使用的HTTP协议是HTTP/1.1。

但是上面的写法是存在很多问题的,不过不重要,我们先完善下功能,让他给客户端返回点什么。

五十五、应用层篇-实现一个简易的web服务器

二、接收+响应客户端请求

由于HTTP是超文本传输协议,我们本次返回一个典型的HTML页面。

首先我们得有资源才能展示,假设我们要展示index.html,我们将其暂时放在F:/webroot下。

里面的内容十分简单,就是借助HTML的标签显示一些文字,就像我们初次学习编程时打印”hello world”一样,我在此HTML中放置的内容是:

1<!DOCTYPE html>
2<html>
3<head>
4    <title>httpserver test page</title>
5</head>
6<body>
7<h1><font color="green">hello world!</font></h1>
8</body>
9</html>

好了,素材准备了,下面就是升级代码,想办法将html的内容返回给浏览器。

服务端需要读取这个文件,然后以流的形式发送给客户端的浏览器上,浏览器再解析展示。

 1public static void main(String[] args) throws IOException {
2    ServerSocket server = new ServerSocket(8888);
3    System.out.println("服务器已经启动...正在监听8888端口,随时等待客户端连接");
4    //服务端创建一个线程来处理客户端请求
5    while (!Thread.interrupted()){
6        //接收用户请求
7        Socket client = server.accept();
8        //获取输入输出流
9        InputStream ins = client.getInputStream();
10        OutputStream out = client.getOutputStream();
11        //给用户响应
12        //首先读取html文件流,准备返回给浏览器
13        PrintWriter pw = new PrintWriter(out);
14        InputStream i = new FileInputStream("f:\webroot\index.html");
15        BufferedReader br = new BufferedReader(new InputStreamReader(i));
16        //配置http协议需要的响应头信息,注意响应头结束后有一行空行
17        pw.println("HTTP/1.1 200 OK");
18        pw.println("Content-Type: text/html;charset=utf-8");
19        pw.println("Content-Length:" + i.available());
20        pw.println("Server:hello-server");
21        pw.println("Date:"+new Date());
22        pw.println("");
23        //在空行之后就是返回响应体,即html给浏览器渲染展示
24        String c = null;
25        while ((c = br.readLine()) != null){
26            pw.println(c);
27        }
28        pw.flush();
29        System.out.println(" 本次请求处理结束");
30    }
31}

此时再在浏览器上去访问http://localhost:8888/,就会显示欢迎的信息啦!

五十五、应用层篇-实现一个简易的web服务器

响应信息真的如我们代码设置的一样吗,我们抓包来验证下:

五十五、应用层篇-实现一个简易的web服务器

五十五、应用层篇-实现一个简易的web服务器

三、引入多线程

由于客户端往往是多个同时请求过来,服务端若只有一个线程在处理,整体会很慢,考虑引入多线程并行处理客户端请求,我们改进demo。

 1public static void main(String[] args) throws IOException {
2    ServerSocket server = new ServerSocket(8888);
3    System.out.println("服务器已经启动...正在监听8888端口,随时等待客户端连接");
4    //服务端创建一个线程来处理客户端请求
5    while (!Thread.interrupted()){
6        //接收用户请求
7        Socket client = server.accept();
8        //新开一个线程去处理请求
9        new Thread(new ServerThread(client)).start();
10    }
11    server.close();
12}

更进一步地,可以使用线程池来分配线程去处理:

 1public static void main(String[] args) throws IOException {
2    ExecutorService pool = Executors.newCachedThreadPool();
3
4    ServerSocket server = new ServerSocket(8888);
5    System.out.println("服务器已经启动...正在监听8888端口,随时等待客户端连接");
6    //服务端创建一个线程来处理客户端请求
7    while (!Thread.interrupted()){
8        //接收用户请求
9        Socket client = server.accept();
10
11        pool.execute(new ServerThread(client));
12    }
13    server.close();
14}

下面核心的就是这个线程类的处理,其实跟之前是一样的:

 1public class ServerThread implements Runnable {
2    private Socket client;
3    InputStream ins;
4    OutputStream out;
5
6    public ServerThread(Socket client){
7        this.client = client;
8        init();
9    }
10
11    private void init(){
12        try {
13            ins = client.getInputStream();
14            out = client.getOutputStream();
15        } catch (IOException e) {
16            e.printStackTrace();
17        }
18    }
19
20    @Override
21    public void run() {
22        try {
23            go();
24        } catch (IOException e) {
25            e.printStackTrace();
26        }
27    }
28
29    private void go() throws IOException {
30        //给用户响应
31        PrintWriter pw = new PrintWriter(out);
32        InputStream i = new FileInputStream("f:\webroot\index.html");
33        BufferedReader br = new BufferedReader(new InputStreamReader(i));
34        pw.println("HTTP/1.1 200 OK");
35        pw.println("Content-Type: text/html;charset=utf-8");
36        pw.println("Content-Length:" + i.available());
37        pw.println("Server:hello-server");
38        pw.println("Date:"+new Date());
39        pw.println("");
40        pw.flush();
41
42        String c = null;
43        while ((c = br.readLine()) != null){
44            pw.println(c);
45        }
46        pw.flush();
47
48        pw.close();
49        br.close();
50        i.close();
51        client.close();
52    }
53}

五十五、应用层篇-实现一个简易的web服务器

四、丰富服务端的处理逻辑

若我想在html中显示一张图片呢?

于是我快速修改了html代码:

 1<!DOCTYPE html>
2<html>
3<head>
4    <title>httpserver test page</title>
5</head>
6<body>
7<div align="center">
8
9<img src="./favicon.png" width="100px" height="100px">
10</br>
11<h1><font color="green">hello world!</font></h1>
12
13</div>
14
15</body>
16</html>

我希望得到的效果是:

五十五、应用层篇-实现一个简易的web服务器

但是当我启动代码,发现无法正常展示图片:

五十五、应用层篇-实现一个简易的web服务器

原因就在响应头中,所响应的content-type是text/html;charset=utf-8,这显然不是图片对应的类型,一看代码,原来我们是将content-type的类型写死了,导致浏览器侧无法正常渲染。

那么就需要优化代码,让其更加聪明,由于我们这里demo比较简单,可根据请求资源的后缀进行判断,如果是图片类型,则将响应头调整为图片的,我们设置了一个MIME type池子:

1//存放类型,比如jpg对应的是image/jpeg,这是http协议规定的每种类型的响应格式
2private static Map<String,String> contentMap =  new HashMap<>();
3static {
4    contentMap.put("html","text/html;charset=utf-8");
5    contentMap.put("jpg","image/jpeg");
6    contentMap.put("png","image/jpeg");
7}

接下来就是根据资源的后缀类型去动态返回,核心代码是:

 1//1、获取请求资源名称
2BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
3String requestPath = reader.readLine().split(" ")[1].replace("/","\");
4if(requestPath.equals("\")){
5    requestPath += "index.html";
6}
7
8//2、拼接起来就是资源的完整路径
9File file = new File(webroot + requestPath);
10
11//3、给用户响应
12if (file.exists()) {
13    //给用户响应
14    PrintWriter pw = new PrintWriter(out);
15    InputStream i = new FileInputStream(webroot + requestPath);
16    //由于需要将图片也要传给前端,再用这个就不好办了,得用普通的文件输入流
17    pw.println("HTTP/1.1 200 OK");
18    //返回的类型是动态判断的,图片用图片的类型,文本用文本的类型
19    String s = contentMap.get(requestPath.substring(requestPath.lastIndexOf(".")+1,requestPath.length()));
20    System.out.println("返回的类型为:"+ s);
21    pw.println("Content-Type: " + s);
22    ......

详细可见代码,通过这番调整后,我们终于顺利访问到了预期的页面:

五十五、应用层篇-实现一个简易的web服务器

五十五、应用层篇-实现一个简易的web服务器

五、结语

至此,我们基于JAVA完成了一个比较”乞丐版”的web服务器,读者朋友们,你们不妨也用自己擅长的语言来实现一下。

本文只是一个小demo,主要是为后续HTTP协议的深入学习做准备,本文后续行文也不会太突兀,若不实践也不影响后续学习。

代码仓库:https://github.com/sunweiguo/httpserver

原文始发于微信公众号(幕后哈土奇):五十五、应用层篇-实现一个简易的web服务器

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/113397.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!