一、认识web服务器
Web服务器的主要功能是和浏览器建立tcp连接,并不断接受浏览发送的请求,再向浏览器发送响应,下面是服务器运行的主要流程。

服务器要做的事情就是上面的四个过程。
二、创建连接,接受请求
Server类
想要获得一个服务器,那么我们就要创建一个服务器类,服务器类就是要重复上面的四个过程,我们可以在获取一个ServerSocket 类后不断再一个while循环里面获取Socket,建立连接获取请求和创建响应。此外,代码还要判断路由,我要去哪里找对应的资源给浏览器,以html文件和Servlet类为例子。
//接受请求
public void reciveing() throws Exception{
ServerSocket serverSocket = new ServerSocket(port);
while (true){
Socket socket = serverSocket.accept();
System.out.println("接收到请求");
//获取输入流
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
int t = inputStream.available();
System.out.println("请求长度是" + t);
if(myRequest.path.contains(".html")) {
//寻找html文件
}else {
//寻找Servlet文件
}
socket.close();
}
}
三、解析请求
Request类
我们可以通过InputStream获取浏览器传来的Http请求,但是在创建响应前我们还要读懂Http请求,并获取里面的数据。Request就是用来干这事的,Http请求分为请求行、请求头、请求体(Post方法)我们以Get方法为例子,我们的Request类先要获取InputStream,并保存里面的**请求方法、URI路径、传过来的数据(HashMap)、请求头的信息(HashMap)**并在创建是传入输入流
//请求方法
public String method;
//数据
public Map<String, String> datas = new HashMap<>();
//请求路径
public String path;
//请求头
public Map<String, String> heads;
//请求体
public String body;
public MyRequest(InputStream inputStream){
try {
parse(inputStream);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
解析请求行
GET /api/users?id=123 HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
Accept: application/json
上面是一个简单的Http请求的例子,最上面的一行是请求行里面包含了传递的数据(id=123),请求方法(GET),Http版本(HTTP/1.1),Http路径(/api/users)。这就是我们要从里面提取的信息,可以读取第一行数据,使用String的split方法(根据某个字符串把字符串分割),根据空格把他分为“GET”,“/api/users?id=123” , HTTP/1.1。里面的第一个是请求方法GET,第二个包含了路径和数据,第三个是版本号。获取数据和路径只要对第二个重复上面的步骤,先用“?”分割,在用“=”分割。代码如下
//解读请求行
public void parseLines(String line){
System.out.println(line);
//根据空格分割
String[] infore = line.split(" ");
//获取方法
method = infore[0];
if(method.equals("GET")){
//根据?分割
String[] strs = infore[1].split("\\?");
//获取路径
path = strs[0];
for(int i = 1; i < strs.length; i++){
//根据=分割,并存入HashMap
String[] keys = strs[i].split("=");
datas.put(keys[0], keys[1]);
}
}else {
path = infore[1];
}
}
解析请求头
Host: example.com
User-Agent: Mozilla/5.0
Accept: application/json
上面的部分就是请求头,只要一行一行的读,并根据“:”分割,存入HashMap就可以实现了
//解读请求头
public void parseHeads(BufferedReader br) throws Exception{
heads = new HashMap<>();
String line;
while ((line = br.readLine()) != null && !line.trim().isEmpty()){
String[] infore = line.split(": ");
System.out.println(line);
heads.put(infore[0], infore[1]);
}
}
上面就是Request类的核心代码,组装一下就好了。
//解读请求
public void parse(InputStream inputStream) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
//解读请求行
String line = br.readLine();
parseLines(line);
//解读请求头
parseHeads(br);
}
四、创建回响
Respone(一)
解读完之后,还要根据解读的信息组成对应的Http响应,发送给浏览器,Http响应主要是状态行,响应头,响应体。状态行表示响应的状态,当我们收到请求时,我们判断请求的资源是否存在,如果存在,那么我们返回一个200状态码,否则返回一个404的状态码。响应头里面存储响应的附带的信息。比如响应一个html文件,那么我们要传出的Http响应的头里面就包含text/html,文本信息就包含有text/plain,响应体包含要响应的数据。下面是一个简单的Http响应(传出的是JSON数据)
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"name": "John Doe",
"email": "john@example.com"
}
那么可以定义一个方法来拼接成Http响应格式,传入状态码,响应类型,响应的数据就可以自己组装成响应的方法。
//组装响应
public String getResponse(String code, String type, String message){
return "HTTP/1.1 " + code + "\r\n"
+ "Content-Type:" + type + ";charset=UTF-8\r\n"
+ "\r\n"
+ message;
}
还要分为两种方法传递数据,一种是传递html文件,另外一种是响应数据。先看文件的,先判断有没有这个文件,如果有这个文件就用IO流读取html文件里面的内容,传递给浏览器。判断可以使用字符串拼接,拼出一个路径,并获取文件对象,调用exsist方法来判断有没有这个文件,在读取文件里面的所有的内容。传入200, text/html使用上面的方法组装Http响应。否则返回404状态码。具体的代码如下。
public void sendRedirect(MyRequest request) throws Exception{
path = request.path;
//判断资源是否存在
String path = "C:\\Users\\wrui\\IdeaProjects\\test\\src\\main\\java\\webserver\\show" + this.path;
File file = new File(path);
if(file.exists()){
FileInputStream fin = new FileInputStream(file);
byte[] bytes = new byte[(int)file.length()];
fin.read(bytes);
String result = new String(bytes);
String infore = getResponse("200","text/html", result);
//System.out.println("我的响应是" + infore);
outputStream.write(infore.getBytes());
}else{
String error = getResponse("404", "text/html","404 not found");
outputStream.write(error.getBytes());
}
}
那如果我们需要的是一些业务处理呢?而不是返回一个html文件呢?。我们怎么判断给路由存不存在?怎么调用里面的方法执行相关的代码实现业务需求?这个时候我们需要Servlet。
Servlet
我们可以创建一个抽象类,在类里面定义一个doGet的抽象方法。执行业务需求时,我们创建对应的类,继承Servelt,重写doGet,在里面事项业务需求。在定义一个注解,存储当前的类的路由位置。接受请求后,根据里面的请求的路径,搜索匹配注解里面的信息。Servlet的代码如下很简单。
public abstract class Servlet {
//处理业务逻辑的规范
public abstract void doGet(MyRequest request, MyResponse response);
}
注解的代码如下
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface WebPath {
String value();
}
Response(二)
那么我们现在已经解决了复杂的业务处理的问题了,我们在获取路由时,判断是否存在该路由呢?可以把所以的Servlet的子类放在对应的一个包下面,在服务器启动时扫描获取所以类的字节文件Class,并获取注解WebPath里面的路径,把路径作为键,字节文件作为值存入HashMap里面。代码如下
public static void loadClassesFromJavaFiles(String folderPath, String packageName) throws Exception {
File dir = new File(folderPath);
// 检查文件夹是否存在
if (!dir.exists() || !dir.isDirectory()) {
throw new IllegalArgumentException("文件夹不存在或不是目录: " + folderPath);
}
// 遍历文件夹下的所有 .java 文件
for (File file : dir.listFiles((dir1, name) -> name.endsWith(".java"))) {
// 提取类名(去掉 .java 后缀)
String fileName = file.getName();
String className = packageName + "." + fileName.substring(0, fileName.length() - 5);
try {
// 使用 Class.forName 加载类(要求 .class 文件已在类路径中)
Class<?> clazz = Class.forName(className);
if(clazz.isAnnotationPresent(WebPath.class)){
WebPath an = clazz.getAnnotation(WebPath.class);
class_map.put(an.value(), clazz);
}
} catch (ClassNotFoundException e) {
System.err.println("无法加载类: " + className + "(请确保已编译并位于类路径中)");
}
}
}
获取文件夹的路径,遍历文件夹下面所有路径带有“.java”的文件,这是类文件。在从里面去掉“.java”的字符串,使用Class的forName方法,获取对应的Class类。使用isAnnotationPresent方法,获取里面的注解对象,把他存入HashMap里面。
如果我们获取了路由,只要在这个HashMap里面看看有没有对应的键就好了。并取出对应的Class对象,调用getConstructor方法获取构造器Constructor,在用构造器调用newInstance获取对应的对象。最后调用Class类的getMethod方法获取方法对象,调用该对象的invoke方法。
代码如下
public void sendData(MyRequest request) throws Exception{
//判断有没有该路径
if(Server.class_map.containsKey(request.path)){
System.out.println("找到了是" + request.path);
Class<?> clazz = Server.class_map.get(request.path);
//获取对象
Constructor<?> constructor = clazz.getConstructor();
Object servlet = constructor.newInstance();
//调用方法
Method method = clazz.getMethod("doGet", MyRequest.class, MyResponse.class);
method.invoke(servlet, request, this);
}else {
//响应404,没有找到
String error = getResponse("404", "text/html","404 not found");
outputStream.write(error.getBytes());
}
}
//输出数据,并拼接成Http响应格式
public void write(String str) throws IOException {
String data = getResponse("200", "text/plain", str);
outputStream.write(data.getBytes());
}
最后再整理一下把Server写完整
六、源码
下面是所有类的源码
Server类
public class Server {
//端口号
public int port;
public static Map<String, Class<?>> class_map = new HashMap<>();
public Server(int port){
this.port = port;
}
//接受请求
public void reciveing() throws Exception{
ServerSocket serverSocket = new ServerSocket(port);
getAllServlet();
System.out.println("已经扫描了所有的类!");
while (true){
Socket socket = serverSocket.accept();
System.out.println("接收到请求");
//获取输入流
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
int t = inputStream.available();
System.out.println("请求长度是" + t);
//创建Request对象 并解析请求
MyRequest myRequest = new MyRequest(inputStream);
System.out.println(myRequest.path);
//创建响应
MyResponse myResponse = new MyResponse(outputStream);
//判断响应数据还是响应页面
if(myRequest.path.contains(".html")) {
myResponse.sendRedirect(myRequest);
}else {
myResponse.sendData(myRequest);
}
socket.close();
}
}
public void getAllServlet() throws Exception {
loadClassesFromJavaFiles("C:\\Users\\wrui\\IdeaProjects\\test\\src\\main\\java\\webserver\\main"
, "webserver.main");
}
//获取含有注解的对象的Class
public static void loadClassesFromJavaFiles(String folderPath, String packageName) throws Exception {
File dir = new File(folderPath);
// 检查文件夹是否存在
if (!dir.exists() || !dir.isDirectory()) {
throw new IllegalArgumentException("文件夹不存在或不是目录: " + folderPath);
}
// 遍历文件夹下的所有 .java 文件
for (File file : dir.listFiles((dir1, name) -> name.endsWith(".java"))) {
// 提取类名(去掉 .java 后缀)
String fileName = file.getName();
String className = packageName + "." + fileName.substring(0, fileName.length() - 5);
try {
// 使用 Class.forName 加载类(要求 .class 文件已在类路径中)
Class<?> clazz = Class.forName(className);
if(clazz.isAnnotationPresent(WebPath.class)){
WebPath an = clazz.getAnnotation(WebPath.class);
class_map.put(an.value(), clazz);
}
} catch (ClassNotFoundException e) {
System.err.println("无法加载类: " + className + "(请确保已编译并位于类路径中)");
}
}
}
}
MyResponse类
public class MyResponse {
OutputStream outputStream;
String type = "text/html";
String path = "";
public MyResponse(OutputStream outputStream){
this.outputStream = outputStream;
}
//响应页面
public void sendRedirect(MyRequest request) throws Exception{
path = request.path;
//判断资源是否存在
String path = "C:\\Users\\wrui\\IdeaProjects\\test\\src\\main\\java\\webserver\\show" + this.path;
File file = new File(path);
if(file.exists()){
FileInputStream fin = new FileInputStream(file);
byte[] bytes = new byte[(int)file.length()];
fin.read(bytes);
String result = new String(bytes);
String infore = getResponse("200","text/html", result);
//System.out.println("我的响应是" + infore);
outputStream.write(infore.getBytes());
}else{
String error = getResponse("404", "text/html","404 not found");
outputStream.write(error.getBytes());
}
}
//组装响应
public String getResponse(String code, String type, String message){
return "HTTP/1.1 " + code + "\r\n"
+ "Content-Type:" + type + ";charset=UTF-8\r\n"
+ "\r\n"
+ message;
}
//响应字符串数据
public void sendData(MyRequest request) throws Exception{
//判断有没有该路径
if(Server.class_map.containsKey(request.path)){
System.out.println("找到了是" + request.path);
Class<?> clazz = Server.class_map.get(request.path);
//获取对象
Constructor<?> constructor = clazz.getConstructor();
Object servlet = constructor.newInstance();
//调用方法
Method method = clazz.getMethod("doGet", MyRequest.class, MyResponse.class);
method.invoke(servlet, request, this);
}else {
//响应404,没有找到
String error = getResponse("404", "text/html","404 not found");
outputStream.write(error.getBytes());
}
}
//输出数据,并拼接成Http响应格式
public void write(String str) throws IOException {
String data = getResponse("200", "text/plain", str);
outputStream.write(data.getBytes());
}
}
MyRequest类
public class MyRequest {
//请求方法
public String method;
//数据
public Map<String, String> datas = new HashMap<>();
//请求路径
public String path;
//请求头
public Map<String, String> heads;
//请求体
public String body;
public MyRequest(InputStream inputStream){
try {
parse(inputStream);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//解读请求
public void parse(InputStream inputStream) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
//解读请求行
String line = br.readLine();
parseLines(line);
//解读请求头
parseHeads(br);
}
//解读请求行
public void parseLines(String line){
System.out.println(line);
String[] infore = line.split(" ");
method = infore[0];
if(method.equals("GET")){
String[] strs = infore[1].split("\\?");
path = strs[0];
for(int i = 1; i < strs.length; i++){
String[] keys = strs[i].split("=");
datas.put(keys[0], keys[1]);
}
}else {
path = infore[1];
}
}
//解读请求头
public void parseHeads(BufferedReader br) throws Exception{
heads = new HashMap<>();
String line;
while ((line = br.readLine()) != null && !line.trim().isEmpty()){
String[] infore = line.split(": ");
System.out.println(line);
heads.put(infore[0], infore[1]);
}
}
}
Servlet类
public abstract class Servlet {
//处理业务逻辑的规范
public abstract void doGet(MyRequest request, MyResponse response);
}
WebPath注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface WebPath {
String value();
}
4436

被折叠的 条评论
为什么被折叠?



