本文共 8322 字,大约阅读时间需要 27 分钟。
推荐:
Java实现能获取静态资源的简易版服务器(类Tomcat)
我们平时使用Tomcat
做为服务器进行Web
开发,通常会输入http://localhost:8888/index.html
这种URL
,这种请求的Request Headers
如下:
GET /index.html HTTP/1.1Host: localhost:8888Connection: keep-aliveCache-Control: max-age=0Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36Sec-Fetch-User: ?1Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9Sec-Fetch-Site: noneSec-Fetch-Mode: navigateAccept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9,en;q=0.8
从Request Headers
可以得到请求想要访问的静态资源是index.html
(我们自己实现服务器,浏览器发起请求,服务器后台得到就是Request Headers
这种数据 )。
现在我们知道如何获取请求想要的资源了(从Request Headers
获取),这就好办了。
当用户在浏览器中输入URL
发起请求,其实就是与服务器先建立网络连接,再获取服务器上的资源。
- 服务器首先要打开
ServerSocket
,当有客户端(浏览器)进行连接,并且发起请求后,获得该请求的Request Headers
(你可以把浏览器当作我们之前实现的多人聊天室里面的客户端,因为浏览器向服务器发起请求,数据也是通过流Stream
或者类似的组件实现的)。 - 服务器获得用户请求的
Request Headers
后,然后从Request Headers
中分析出资源名称。 - 知道用户想要的资源后,服务器将资源与一些必要信息打包发送给客户端(协议、状态码、资源等),当资源不存在时,服务器返回
404.html
资源。
Bootstrap
类,启动服务器的模块。
import connector.Connector;public final class Bootstrap { public static void main(String[] args) { Connector connector = new Connector(); connector.start(); }}
Connector
类,创建ServerSocket
(用于客户端建立连接)、创建Request
(从Request Headers
中获取客户端请求的资源)、创建Response
(将协议、状态码、资源等数据打包写入流中)、创建StaticProcessor
(借助Response
将资源与一些必要信息发送给服务器),代码注释应该非常清楚。
package connector;import processor.StaticProcessor;import java.io.Closeable;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.ServerSocket;import java.net.Socket;public class Connector implements Runnable { private static final int DEFAULT_PORT = 8888; private ServerSocket server; private int port; public Connector(){ this(DEFAULT_PORT); } public Connector(int port) { this.port = port; } public void start(){ new Thread(this).start(); } @Override public void run() { try { // 创建ServerSocket,绑定、监听端口 server = new ServerSocket(port); System.out.println("启动服务器,监听端口:" + port); while(true){ // 等待客户端连接 Socket socket = server.accept(); // 获取输入流 InputStream input = socket.getInputStream(); // 获取输出流 OutputStream output = socket.getOutputStream(); // 创建请求request,并且传入输入流(有客户端请求的信息) Request request = new Request(input); // request通过输入流的信息,分析出客户端想要的资源 request.parse(); // 创建响应response,并且传入输出流(方便将获取的资源发送给客户端) Response response = new Response(output); // response需要request的uri(客户端请求的资源) response.setRequest(request); // 创建处理者processor StaticProcessor processor = new StaticProcessor(); // processor通过response把数据发送给客户端 processor.process(response); //关闭socket close(socket); } } catch (IOException e) { // 浏览器可以识别状态码,当状态码表示请求不成功时(如404),似乎会断开socket,所以这里不进行处理 } finally{ close(server); } } private void close(Closeable closeable){ if(closeable != null){ try { closeable.close(); } catch (IOException e) { e.printStackTrace(); } } }}
Request
类,从客户端与服务器建立连接的socket
的输入流InputStream
中读取信息,并且分析出客户端想要的资源。
package connector;import java.io.IOException;import java.io.InputStream;public class Request { private static final int BUFFER_SIZE = 1024; private InputStream input; private String uri; public Request(InputStream input) { this.input = input; } public String getUri() { return uri; } public void parse(){ int length = 0; byte[] buffer = new byte[BUFFER_SIZE]; try { // 读取流里面的数据,并且记录长度 length = input.read(buffer); } catch (IOException e) { e.printStackTrace(); } // 将数据转化成StringBuilder StringBuilder request = new StringBuilder(); for (int i = 0; i < length; i++) { request.append((char) buffer[i]); } // 分析出客户端想要的资源 uri = parseUri(request.toString()); } /** *从 “GET /index.html HTTP/1.1 * ...... * ”中获取index.html * 通过空格来分离出来 * */ public String parseUri(String request){ int index1 , index2; // 第一个空格的位置 index1 = request.indexOf(' '); if(index1 != -1){ // 第二个空格的位置 index2 = request.indexOf(' ', index1+1); if(index2 != -1){ // 分离出资源名称 return request.substring(index1 + 2 , index2); } } // 没有办法解析出uri return ""; }}
Response
类,将协议、状态码、资源等数据打包写入流中。
package connector;import java.io.*;public class Response { private static final int BUFFER_SIZE = 1024; Request request; OutputStream output; public Response(OutputStream output) { this.output = output; } public void setRequest(Request request) { this.request = request; } public void sendStaticResource() throws IOException { try{ // 通过request的uri,获取资源的路径 String filePath = getClass() .getClassLoader() .getResource(request.getUri()).getFile(); // 创建资源文件 File file = new File(filePath.substring(1 , filePath.length())); // 将资源写入流里面,HttpStatus.SC_OK是状态码 write(file , HttpStatus.SC_OK); } catch (Exception e) { // 当出现错误时,简单处理 ,发送404.html给客户端 String errorFilePath = getClass().getClassLoader().getResource("404.html").getFile(); // 将资源写入流里面,HttpStatus.SC_NOT_FOUND是状态码 write(new File(errorFilePath.substring(1 , errorFilePath.length())) , HttpStatus.SC_NOT_FOUND); } } private void write(File resource , HttpStatus status) throws IOException { try(FileInputStream fis = new FileInputStream(resource)){ // 先将协议、状态码等必要信息写入流中,ConnectorUtils是工具类 output.write(ConnectorUtils.renderStatus(status).getBytes()); byte[] buffer = new byte[BUFFER_SIZE]; int length = 0; // 把资源文件写入流中 while((length = fis.read(buffer , 0 , BUFFER_SIZE)) != -1){ output.write(buffer , 0 ,length); } } }}
HttpStatus
类,状态码枚举类。
package connector;public enum HttpStatus { SC_OK(200 , "OK"), SC_NOT_FOUND(404 , "File Not Found"); private int statusCode; private String reason; HttpStatus(int statusCode, String reason) { this.statusCode = statusCode; this.reason = reason; } public int getStatusCode() { return statusCode; } public String getReason() { return reason; }}
ConnectorUtils
类(工具类),将协议、状态码等信息组装成浏览器可以识别的信息。
package connector;public class ConnectorUtils { public static final String PROTOCOL = "HTTP/1.1"; public static final String CARRIAGE = "\r"; public static final String NEWLINE = "\n"; public static final String SPACE = " "; public static String renderStatus(HttpStatus status){ StringBuilder sb = new StringBuilder(PROTOCOL) .append(SPACE) .append(status.getStatusCode()) .append(SPACE) .append(status.getReason()) .append(CARRIAGE).append(NEWLINE) .append(CARRIAGE).append(NEWLINE); return sb.toString(); }}
StaticProcessor
类,做为静态资源处理者的身份,借助Response
进行处理。
package processor;import connector.Response;import java.io.IOException;public class StaticProcessor { public void process(Response response){ try { response.sendStaticResource(); } catch (IOException e) { // 不处理浏览器断开连接等错误 } }}
这里我们便完成了一个Java实现的能获取静态资源的简易版服务器。
项目结构
项目源码测试
请求成功,服务器有资源。
请求失败,服务器没有资源。
大家可以动手试一试,不用ServerSocket
来实现,用NIO模型或者AIO模型的组件来实现。
如果有说错的地方,请大家不吝赐教(记得留言哦~~~~)。
转载地址:https://kaven.blog.csdn.net/article/details/104216019 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!