通过前面的学习,我们实战过用tomcat作为服务器承载服务,为了更好地学习HTTP协议,决心插入一节:用代码实现一个简易的web服务器,满足接收客户端请求、处理和响应的功能。
本文我使用JAVA来实现,当然了,读者朋友可以使用其他的语言和技术,比如nodejs来实现只需要几行代码。
老规矩,代码仓库见文末。
一、接收客户端请求
如何通过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。
但是上面的写法是存在很多问题的,不过不重要,我们先完善下功能,让他给客户端返回点什么。
二、接收+响应客户端请求
由于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/
,就会显示欢迎的信息啦!
响应信息真的如我们代码设置的一样吗,我们抓包来验证下:
三、引入多线程
由于客户端往往是多个同时请求过来,服务端若只有一个线程在处理,整体会很慢,考虑引入多线程并行处理客户端请求,我们改进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}
四、丰富服务端的处理逻辑
若我想在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>
我希望得到的效果是:
但是当我启动代码,发现无法正常展示图片:
原因就在响应头中,所响应的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 ......
详细可见代码,通过这番调整后,我们终于顺利访问到了预期的页面:
五、结语
至此,我们基于JAVA完成了一个比较”乞丐版”的web服务器,读者朋友们,你们不妨也用自己擅长的语言来实现一下。
本文只是一个小demo,主要是为后续HTTP协议的深入学习做准备,本文后续行文也不会太突兀,若不实践也不影响后续学习。
代码仓库:https://github.com/sunweiguo/httpserver
原文始发于微信公众号(幕后哈土奇):五十五、应用层篇-实现一个简易的web服务器
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/113397.html