java仿前端开发工具HBuilder

前言
最近在看一些前端的知识,但是只是看了一些标签的用法,真正到了使用的时候,却又写不出来,学习的效率很低,但是我也发现了一个有意思的点,当我在HBuilder中启动项目时,我就可以在浏览器中访问它了——这说明HBuilder本身启动了一个静态web服务器。所以,本着对这个的兴趣,我准备来自己模仿实现一个简单的HBuilder——这里只是实现可以在浏览器中访问这个特性。

注:我这里只处理GET方式的无参请求。

浏览器实现效果
在这里插入图片描述

demo的目录结构
在这里插入图片描述
说明:
src目录下面是模拟的简单静态服务器的代码,然后提供了一个404.html页面,下面的resource目录是存放静态web文件的地方,所有与web相关的文件都在此文件夹中——html、css、js文件

demo介绍

先来理解一下HBuilder的工作模式:
HBuilder编写好代码后,直接选择允许,然后自动弹出浏览器窗口,页面已经可以访问了。对于页面中使用的静态资源,都是使用的相对路径。这些相对路径都是相对于当前文件夹所在的路径,即以 ./ 或者 / 开头的资源。例如:resource 目录下的图片nicai.jpg,在html中使用时,它的路径为:./nicai.jpg或者 /nicai.jpg。但我们在浏览器中使用此资源时,它的路径即为:http://ip:port/nicai.jpg。这即是统一定位符(URL)或者统一资源标识符(URI)的威力,但是我们也要明白它的定位,也是基于主机本身的定位的。通过URL可以对应一个确定的资源,这里就是 / 映射到了resource目录下面,所以我们可以通过网络访问到resource下面的文件。

所以,这里的思路就很清晰了。只要获取到浏览器发送的请求报文中的响应行,并且获取里面的请求路径,如:/ 或者 /index.html即表示请求resource目录下面的index.html文件,其它的情况类似。



StaticWebServer 类

启动一个ServerSocket对象,然后获取用户连接后发送的请求并返回响应数据。

package org.dragon;

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

public class StaticWebServer {
	
	private final static int PORT = 8000;   // 端口
	private final static int THREAD_NUM = 10; // 线程池的容量
	
	public void start() {
		System.out.println("服务启动中。。。");
		ExecutorService pool = Executors.newFixedThreadPool(THREAD_NUM);
		try (ServerSocket server = new ServerSocket(PORT)) {
			while (true) {
				System.out.println("等待连接。。。");
				Socket con = server.accept();
				pool.submit(new Connection(con));  // 使用线程处理每一个连接,这里的连接均为短连接,即模拟 HTTP/1.0 协议的部分功能。
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		new StaticWebServer().start();
	}
}


Connection 类

获取到请求路径,然后处理它。注意,这里如果使用GET方式的含参请求,程序就会报错。不过,这不是主要的,这里只是一个简单的验证性的demo。这个类的主要功能是:获取请求报文中的第一行,即请求行,然后其余的直接读取输出在控制台。
请求行包括:请求方法 请求路径 协议版本\r\n

这里解析出来请求行即可,其它的不做处理。然后针对请求路径进行处理,请求路径直接映射到 resource目录下,使用路径进行拼接,最终是通过file协议来定位到资源的位置。这个也可以算是file协议和http协议之间的交互吧。如果不存在该路径的话,即没有找到该URL对应的资源,这就是我们的老朋友——404 Not Found了,返回一个固定的404页面即可。

package org.dragon;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Connection implements Runnable {
	private Socket connection;
	private InputStream in;
	private OutputStream out;
	
	public Connection(Socket connection) {
		this.connection = connection;
	}
	
	@Override
	public void run() {
		try {
			in = new BufferedInputStream(connection.getInputStream());
			out = new BufferedOutputStream(connection.getOutputStream());
			
			// 读取请求行,剩下的不处理。
			StringBuilder sb = new StringBuilder();
			while (true) {
				int c = in.read();
				if (c == '\r' || c == '\n' || c == -1 ) {
					break;
				}
				sb.append((char)c);
			}
			
			// 这种情况有问题,不处理它。
			if (sb.length() == 0) {
				return ;
			}
			
			String requestLine = sb.toString();
			String[] requests = requestLine.split(" ");
			System.out.println("请求报文:\n" + requestLine);
			// 对于剩下的请求报文,只是简单的打印处理,1024字节已经足够了。
			byte[] b = new byte[1024];
			int len = in.read(b);
			System.out.println(new String(b, 0, len, StandardCharsets.UTF_8));
			
			// 只处理GET方式的无参请求,也即只对请求路径进行响应,忽略其它参数。
			String requestPath = requests[1];
			
			requestPath = "/".equals(requestPath) ? "/index.html" : requestPath;
			
			// 开始生成响应报文
			int statusCode = 0;   // 状态码
			String phrase = null; // 短语
			String contentType = null; // MIME类型
			long length;       // 文件的长度
			Path path = Paths.get("./", "resource", requestPath);
			
			// 请求路径存在且为一个文件
			if (Files.exists(path) && !Files.isDirectory(path)) {
				// 200 OK
				statusCode = 200;
				phrase = "OK";
				contentType = Files.probeContentType(path);
				length = Files.size(path);
			} else {
				// 404 Not Found!
				statusCode = 404;
				phrase = "Not Found";
				path = Paths.get("./src", "org", "dragon", "404.html");
				contentType = Files.probeContentType(path);
				// 如果找不到格式,就默认它为通用MIME类型的 application/octet-stream
				contentType = contentType == null ? "application/octet-stream" : contentType;
				length = Files.size(path);
			}
			
			byte[] header = this.getHeader(statusCode, phrase, contentType, length);
			byte[] body = this.getBody(path);
			
			// 响应
			out.write(header);
			out.write(body);
			out.flush();  // 手动刷新输出流,因为我用的是缓冲流
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 这里不使用StringBuilder拼接了,所以要注意空格,不要遗漏。
	 * (实际上,它应该还是会被优化成StringBuilder的。)
	 * */
	public byte[] getHeader(int statusCode, String phrase, String contentType, long length) {
		String header = "HTTP/1.0 " + statusCode + " " + phrase +"\r\n"
				+ "Content-Type: " + contentType + "\r\n"
				+ "Content-Length: " + length + "\r\n"
				+ "\r\n";
		return header.getBytes(StandardCharsets.UTF_8);
	}
	
	public byte[] getBody(Path path) throws IOException {
		// 读取小文件,大文件不合适!
		return Files.readAllBytes(path);
	}
}

运行时的输出(部分)

浏览器的网络调试窗口
在这里插入图片描述

eclipse的输出

服务启动中。。。
等待连接。。。
等待连接。。。
等待连接。。。
请求报文:
GET / HTTP/1.1

Host: localhost:8000
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36 Edg/85.0.564.51
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6


请求报文:
GET /my.css HTTP/1.1
等待连接。。。

Host: localhost:8000
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36 Edg/85.0.564.51
Accept: text/css,*/*;q=0.1
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: style
Referer: http://localhost:8000/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6


请求报文:
GET /my.js HTTP/1.1

Host: localhost:8000
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36 Edg/85.0.564.51
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: script
Referer: http://localhost:8000/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6

404页面
在这里插入图片描述

进一步可改进的点

如果觉得太乱的话,可以把不同类的资源放入不同的文件夹中,然后修改相对路径的位置即可。但是要注意,index.html和favico.ico必须是处于根目录下面,这个一般是约定的处理,不要破坏它。

注意: 我这里使用到的静态资源就不提供了,因为我是学习web前端的,但是写的不好,如果你想要验证的话,可以将自己的web前端静态资源(html、css、js)直接复制到resource目录下,启动StaticWebServer类即可。

说明

这个小demo挺有趣的,虽然前端没有学习到什么东西,但是也算是复习了网络的知识吧。感兴趣的朋友们,可以自己尝试一下,对于网络的理解和学习都是有一点帮助的。

©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页