首页 > 编程语言 >1. 学习Tomcat源码,写服务器

1. 学习Tomcat源码,写服务器

时间:2023-12-20 18:01:56浏览次数:45  
标签:HTTP 请求 Tomcat request private 源码 output 服务器 String

一个基础需求的http服务器

项目的github地址

small_http

git clone https://github.com/MrKrabXie/writeHttpServer.git

预备知识

  • 问: 什么是超文本传输协议(HTTP)? 答: HTTP是一种用于传输超媒体文档的协议,它使用请求-响应模式和TCP连接来实现通信。
  • 问: Java中的Socket和ServerSocket类有什么作用? 答: Socket类用于客户端和服务器之间的通信,而ServerSocket类用于服务器接受客户端的连接请求。
  • 问: HTTP请求和响应的格式是怎样的? 答: HTTP请求包括方法、URI、协议版本、头部和主体内容。HTTP响应则包括状态码、协议版本、头部和主体内容。
  • 问: Socket和ServerSocket类如何应用于创建Web服务器? 答: 使用Socket和ServerSocket类可以创建一个简单的Web服务器,并处理HTTP请求和响应。
  • 问: 什么是HTTP服务器? 答: HTTP服务器是一种用于接收和处理HTTP请求的软件或设备。它可以将请求发送到适当的资源,并返回相应的响应。
  • 问: 什么是Servlet容器? 答: Servlet容器是一个运行Java Servlet的环境,它提供了一种处理Web请求和响应的机制。
  • 问: Tomcat是什么? 它是如何工作的? 答: Tomcat是一个开源的Web服务器,它是基于Java Servlet和JavaServer Pages(JSP)技术的。Tomcat接收并处理客户端的HTTP请求,并将请求分发给适当的Servlet进行处理。

实现tomcat第一步, 实现一个简易的http服务器, 下面三个代码写好,启动后,浏览器输入

可以关闭服务: http://localhost:8080/SHUTDOWN

访问主页:http://localhost:8080/index.html

关于http协议标准:

HTTP协议的标准地址是指协议规范的正式文档,通常由互联网工程任务组(Internet Engineering Task Force,简称IETF)发布和维护。HTTP的最新标准是HTTP/3,它的正式文档可以在IETF的官方网站上找到。

你可以在以下网址找到HTTP/3的正式文档以及其他HTTP协议的相关信息:

https://datatracker.ietf.org/doc/html/rfc7540(HTTP/2的文档) https://datatracker.ietf.org/doc/html/rfc7541(HTTP/2的HPACK压缩算法文档) https://datatracker.ietf.org/doc/html/rfc7542(HTTP/2的HTTP交流文档) https://datatracker.ietf.org/doc/html/rfc7230(HTTP/1.1的文档) https://datatracker.ietf.org/doc/html/rfc7231(HTTP/1.1的方法和状态码文档)

这些文档包含了HTTP协议的详细规范,包括协议的工作原理、消息格式、状态码、头字段等内容。如果你需要深入了解HTTP协议的工作原理和细节,可以参考这些标准文档。如果需要更多解释和示例代码,请具体指明你需要哪方面的信息。

之所以要了解http协议,因为我们如果不按照协议走, 而浏览器是按照这些标准的,所以我们返回的数据,浏览器就不能接受, 比如我们把Content-Type: text/html这些的数值瞎改,页面就不能正常访问了。!!!!!

服务器的代码:

package crab;
import java.net.Socket;
import java.net.ServerSocket;
import java.net.InetAddress;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.File;
public class HttpServer {
  public static final String WEB_ROOT =
          System.getProperty("user.dir") + File.separator  + "webroot";
  private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
  private boolean serverShutdownRequested = false; // 重命名变量
  public static void main(String[] args) {
    HttpServer server = new HttpServer();
    server.await();
  }
  public void await() {
    int port = 8080;
    try (ServerSocket serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"))) {
      waitAndProcessRequestUntilShutdown(serverSocket);
    } catch (IOException e) {
      e.printStackTrace();
      System.exit(1);
    }
  }
  // 提取出来的方法,等待并处理请求直到收到关闭指令
  private void waitAndProcessRequestUntilShutdown(ServerSocket serverSocket) {
    while (!serverShutdownRequested) {
      processRequestWithExceptionHandler(serverSocket);
    }
  }
  // 处理异常的请求
  private void processRequestWithExceptionHandler(ServerSocket serverSocket) {
    try {
      processClientRequest(serverSocket);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  // 提取出的方法,处理客户端的请求
  private void processClientRequest(ServerSocket serverSocket) throws IOException {
    try (Socket socket = serverSocket.accept();
         InputStream input = socket.getInputStream();
         OutputStream output = socket.getOutputStream()) {

      // 创建 Request 对象并解析
      Request request = new Request(input);
      request.parse();

      // 创建 Response 对象
      Response response = new Response(output);
      response.setRequest(request);
      response.sendStaticResource();

      // 检查如果上一个 URI 是一个关闭指令
      serverShutdownRequested = request.getUri().equals(SHUTDOWN_COMMAND);
    }
  }
}

这个服务器程序主要包括创建服务器socket、接收和解析客户请求,以及生成并发送响应等功能。 注意,其中的Request 和 Response 类是我们自定义的,用来表示请求和响应。在真正处理请求和生成响应时,还会涉及到具体的HTTP信息(例如请求行、请求头、响应行、响应头、主体内容等)的解析、封装和发送。

package crab;
import java.io.InputStream;
import java.io.IOException;

// 请求类
public class Request {
  private InputStream input; // 输入流
  private String uri; // URI

  // 构造函数
  public Request(InputStream input) {
    this.input = input;
  }

  // 解析请求
  public void parse() {
    uri = parseUri(readFromInput());
  }

  // 从输入流中读取请求
  private String readFromInput() {
    StringBuilder request = new StringBuilder(); // 请求内容
    byte[] buffer = new byte[2048]; // 缓冲区
    int bytesRead; // 读取的字节
    try {
      bytesRead = input.read(buffer); // 读入缓冲区
      for (int i=0; i<bytesRead; i++) { // 构建请求
        request.append((char) buffer[i]);
      }
    } catch (IOException e) {
      e.printStackTrace(); // 输出错误
    }
    return request.toString(); // 返回请求内容
  }

  // 解析URI
  private String parseUri(String requestString) {
    int firstSpaceIndex = requestString.indexOf(' '); // 第一个空格的位置
    if (firstSpaceIndex != -1) {
      int secondSpaceIndex = requestString.indexOf(' ', firstSpaceIndex + 1); // 第二个空格的位置
      if (secondSpaceIndex != -1){
        return requestString.substring(firstSpaceIndex + 1, secondSpaceIndex); // 返回URI
      }
    }
    return null; // 没找到URI返回null
  }

  // 获取URI
  public String getUri() {
    return uri;
  }
}

这段Java代码定义了一个名为Request的类,用于处理与网络请求相关的操作。该类在crab包中。 首先,我们定义了两个私有成员变量:一个InputStream类型的input用于从套接字缓冲区读取数据,一个String类型的uri用于存储从HTTP请求中提取的请求URI。 然后,我们有一个构造函数,它接受一个InputStream参数并将其赋值给上面定义的input。 parse()函数用于读取输入流中的数据,并将其转换为一个字符串,然后调用parseUri()函数从字符串中提取出请求的URI。 parseUri(String requestString)函数是一个私有函数,用于解析并从请求字符串中提取出URI。方法是通过找到第一个和第二个空格字符的位置,然后提取这两个位置之间的子字符串,这个子字符串就是URI。如果没有找到第二个空格,那么返回null。 最后,getUri()函数用于获取uri成员变量的值。

package crab;
import java.io.*;
public class Response {
  private static final int BUFFER_SIZE = 1024;  //缓冲区大小
  private static final String CONTENT_TYPE = "Content-Type: text/html\r\n";  //内容类型头信息
  private static final String HTTP_OK = "HTTP/1.1 200 OK\r\n";  //HTTP 200 OK响应
  private static final String HTTP_NOT_FOUND = "HTTP/1.1 404 File Not Found\r\n";  //HTTP 404 Not Found响应
  private static final String FILE_NOT_FOUND = "<h1>File Not Found</h1>";  //文件未找到的消息
  private static final int FILE_NOT_FOUND_LENGTH = FILE_NOT_FOUND.length();  //文件未找到消息的长度
  private Request request;  //用户的请求
  private OutputStream output; // 输出流

  public Response(OutputStream output) {  //构造函数
    this.output = output;  //设置输出流
  }

  public void setRequest(Request request) {  //设置用户的请求
    this.request = request;  //存储用户的请求
  }

  public void sendStaticResource() throws IOException {  //发送静态资源
    byte[] bytes = new byte[BUFFER_SIZE];  //创建缓冲区
    FileInputStream fis = null;  //文件输入流
    try {
      File file = new File(getFilePath());  //获取文件路径
      if (file.exists()) {  //如果文件存在
        output.write(getHeader(HTTP_OK, file.length()).getBytes());  //发送HTTP 200 OK头信息
        fis = new FileInputStream(file);  //创建文件输入流
        writeFileToOutput(fis, bytes);  //将文件写入到输出流
      } else {  //如果文件不存在
        output.write(getHeader(HTTP_NOT_FOUND, FILE_NOT_FOUND_LENGTH).getBytes());  //发送HTTP 404 Not Found头信息
        output.write(FILE_NOT_FOUND.getBytes());  //发送文件未找到的消息
      }
    } catch (Exception ex) {  //捕获并处理异常
      System.out.println(ex.toString());  //打印异常信息
    } finally {  //最终
      if (fis != null)
        fis.close();  //关闭文件输入流
    }
  }

  private String getFilePath() {  //获取文件路径
    return new File(HttpServer.WEB_ROOT, request.getUri()).getPath().replace("\\", "/");  //返回文件路径
  }

  private String getHeader(String httpResponse, long contentLength) {  //获取头信息
    return httpResponse +
            CONTENT_TYPE +
            "Content-Length: " + contentLength + "\r\n" +
            "\r\n";  //返回头信息
  }

  private void writeFileToOutput(FileInputStream fis, byte[] bytes) throws IOException {  //将文件写入到输出流
    int ch = fis.read(bytes, 0, BUFFER_SIZE);  //从文件输入流中读取数据
    while (ch != -1) {  //当还有数据可读时
      output.write(bytes, 0, ch);  //写入数据到输出流
      ch = fis.read(bytes, 0, BUFFER_SIZE);  //从文件输入流中读取数据
    }
  }
}

首先,我们有一个名为Response的公共类。这个类有四个字段,其中一个是静态常量BUFFER_SIZE,用于在读取文件时设置缓冲区的大小。另外两个非静态字段分别是request,指的是客户端的请求,和output,指的是发送到客户端的输出流。 Response类有一个构造函数,需要一个输出流作为参数,用来初始化output字段。这个构造函数让Response对象知道应该向哪个输出流发送数据。 setRequest方法则是用于设置request字段。传递给这个方法的参数将被用来给request字段赋值。 接下来的方法是sendStaticResource,这个方法的功能是发送静态资源。在这个方法中,我们首先创建了一个字节数组bytes,大小和BUFFER_SIZE相同,然后创建了一个FileInputStream对象fis用来读取文件。接下来,我们尝试创建一个指向要发送的文件的File对象。如果文件存在,我们就从该文件读取数据,并且把数据写入到output流中,直到文件全部被读取完毕;如果文件不存在,则我们发送一个HTTP 404错误消息,报告文件未找到。 在try块的最后,有一个finally子句,这里面会检查fis是否为null,如果不是,就关闭fis流。 代码最后,有相关的Request类和HttpServer类,Request类处理解析HTTP请求,而HttpServer类则是用于启动ServerSocket,监听新的客户端连接请求。 以上就是这段Java代码的总体解释,它基本上实现了一个简易的HTTP服务器的功能。

标签:HTTP,请求,Tomcat,request,private,源码,output,服务器,String
From: https://blog.51cto.com/u_11728118/8909779

相关文章

  • iOS GCDWebServer 搭建本地服务器
    需求场景:H5页面读取系统相册,把选中的图片上传给前端H5.(H5不能直接读取沙盒的路径)方案1:读取到的二进制baseEncode字符串形式交互 弊端:安全性问题:JavaScript在浏览器中运行,可能存在潜在的安全风险,需要谨慎处理用户照片,以免导致隐私泄露或安全问题。性能问题:读取大型......
  • Nginx 服务器的基本原理和配置指南
    什么是Nginx?Nginx(EngineX)是一个轻量级的Web服务器、反向代理服务器及电子邮件(IMAP/POP3)代理服务器、高性能的HTTP服务器,它以高稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。什么是反向代理?反向代理(ReverseProxy)方式是指以代理服务器来接受inter......
  • 恒创科技:高防服务器防御靠谱吗?
    随着互联网的普及和信息技术的不断发展,网络安全问题日益突出。高防服务器作为一种专业的网络安全设备,在防御网络攻|击方面扮演着越来越重要的角色。然而,高防服务器是否靠谱,是否能够有效地防御各种网络攻|击,一直是人们关心的问题。较高的防御能力。它通常配备了高性能的防火墙和入......
  • 基于FastGPT和芋道源码挑战一句话生成代码
    芋道源码相信很多朋友都很了解了,今天我们试着基于FastGPT实现芋道框架的代码生成。芋道的代码生成,是基于数据库表字段实现的,那我们的思路就是看看如何使用GPT帮我们生成数据库表结构,只要数据库表字段有了,代码也就生成好了。实现这个需求我们就需要用到FastGPT的高级编排功能。编排......
  • APIView源码分析
    1.和CBV源码执行流程相似,请求来了先走路由层:path('books/',views.BookView.as_view()) 2.走APIView的as_view方法,代码如下:@classmethoddefas_view(cls,**initkwargs):view=super().as_view(**initkwargs)#调用父类的as_view,view还是View的as_view......
  • 搭建sftp服务器及ftp服务器
    一.搭建sftp服务器实现目标:实现两个用户admin和usr的sftp传输,其中admin可以上传下载,usr只可以下载。sftp端口改为22222sftp是一种安全的文件传送协议,是ssh内含协议,也就是说只要sshd服务器启动了,sftp就可使用,不需要额外安装,它的默认端口和SSH一样为221.创建sftp用户组groupad......
  • 给apollo源码中添加第三方库
    一、为什么需要引用外部库bazel工程期望所有库都在工作空间中,但是实际上有些库Ubuntu提供了非常简单的安装方式,这就涉及到引入外部库步骤1:在apollo/third_party中添加包,需要如下几个文件,写法参考proj的内容,其他涉及包管理的文件可以不要。步骤2:在apollo/tools/workspace.bzl......
  • 基于SaaS模式的云HIS信息管理系统源码
    云HIS全称为基于云计算的医疗卫生信息系统,是运用云计算大数据、物联网等新兴信息技术,按照现代医疗卫生管理要求,在一定区域范围内以数字化形式提供医疗卫生行业数据收集、存储、传递处理的业务和技术亚台云HIS系统采用主流成熟技术开发,软件结构简洁、代码规范易阅读,SaaS应用,全......
  • C++聊天集群服务器解决客户端注销登录问题
    客户端如何处理注销登录问题?问题描述:​ 在客户端登录后进行注销选择,然后重新登录刚才注销的账号,直接卡死。注意这是概率发生,因为是主线程和子线程抢服务器发送的信息,只有子线程抢到才会发生卡死问题产生原因分析:​ 前置条件:主线程循环等待用户输入选择(第一张图是死循环,send......
  • DNS服务器搭建小记
    环境准备主机IPDNS服务器:Rocky_Linux9192.168.3.1/24web服务器:Rocky_Linux9192.168.3.2/24客户端:windows11192.168.3.10/24安装软件包#安装yuminstallbind-libsbind-utilsbind-chroot-y#卸载yumremovebind-libsbind-utilsbind-chroot-y修改配置文件#修改配置文件,使其......