一个基础需求的http服务器
项目的github地址
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); //从文件输入流中读取数据
}
}
}
标签:HTTP,请求,Tomcat,request,private,源码,output,服务器,String From: https://blog.51cto.com/u_11728118/8909779首先,我们有一个名为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服务器的功能。