实现一个最简易的静态Web资源服务器
1.基本元素与变量:
- 服务器,监听着某一端口号;
- 监听客户端的请求---------HTTP请求
- 识别出客户端的请求资源信息
- 根据资源存在与否做出对应的响应(如果请求的资源存在,那么应当将文件的内容返回给客户端,如果文件不存在,那么应当返回一个404)------HTTP响应
- 将客户端发送过来的网络路径,解析转换成本地的硬盘路径。
- 在硬盘上面去找某个文件,应该使用哪一套API?
File、FileInputStream、FileOutputStream
2.代码与实现:
Request.java(发送请求)
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
public class Request{
private Socket client;
private String requestString;
private String method;
private String requestURI;
private String protocol;
//成员变量里面用Map
private Map<String,String> requestHeaders;
public Request(Socket client) {
this.client = client;
this.requestHeaders = new HashMap<>();
parseRequest();
if (!StringUtils.isEmpty(requestString)){
//解析HTTP请求报文
parseRequestLine();
parseRequestHeaders();
}
}
private void parseRequestHeaders(){
//找到请求行,即请求头开始
int begin=requestString.indexOf("\r\n");
// 找到请求头结束位置
int end=requestString.indexOf("\r\n\r\n");
//begin + 2,排除\r与\n
String substring = requestString.substring(begin + 2, end);
//拿到请求头的每一行
String[] parts = substring.split("\r\n");
for (String part:parts){
int i=part.indexOf(":");
String key=part.substring(0,i);
String value=part.substring(i+1);
//将请求头每行以键值对的形式存储。trim()去掉两端空格
this.requestHeaders.put(key.trim(),value.trim());
}
}
//先解析HTTP请求报文的请求行 每一行结束都有一个\r\n
//空行这块本身也是一个\r\n
//利用\r\n可以把请求行分割出来
//利用\r\n和\r\n(最后一个请求行)\r\n(空行)可以把请求头部分割出来
private void parseRequestLine(){
//找到\r\n第一次出现的位置
int i=requestString.indexOf("\r\n");
//分割出请求行requestLine
String requestLine=requestString.substring(0,i);
//利用空格来分割出请求行的三元素method、requestURI、protocol
String[] parts = requestLine.split(" ");
this.method=parts[0];
this.requestURI=parts[1];
this.protocol=parts[2];
//还需要考虑到一点,如果在地址栏里面附带了请求参数呢?
int i1 = requestURI.indexOf("?");
if (i1!=-1){
this.requestURI=requestURI.substring(0,i1);
}
}
//处理请求信息
private void parseRequest(){
InputStream inputStream=null;
try {
//建立IO流中的输入流通道inputStream(目的是:从服务器中读取数据)
inputStream=client.getInputStream();
byte[] bytes = new byte[1024];
int read = inputStream.read(bytes);
if(read != -1){
this.requestString = new String(bytes, 0, read);
System.out.println(requestString);
}
}catch (IOException e) {
e.printStackTrace();
}
}
public String getMethod() {
return method;
}
public String getRequestURI() {
return requestURI;
}
public String getProtocol() {
return protocol;
}
public String getHeader(String headerName){
return requestHeaders.get(headerName);
}
}
Response.java(响应)
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class Response {
private Socket client;
private String requestURI;
public Response(Socket client, String requestURI){
this.requestURI=requestURI;
this.client=client;
handleResponse();
}
private void handleResponse() {
try {
OutputStream outputStream = client.getOutputStream();
//这一步的目的是为了将请求资源前面的/去掉,如果由/,那么file找不到的
File file = new File(requestURI.substring(1));
StringBuffer buffer = new StringBuffer();
if (file.exists() && file.isFile()) {
//如何拿到文件的输入流
FileInputStream fileInputStream = new FileInputStream(file);
byte[] bytes = new byte[1024];
int length = 0;
buffer.append("HTTP/1.1 200 OK\r\n");
buffer.append("Content-Type:text/html;charset=utf-8\r\n");
buffer.append("\r\n");
outputStream.write(buffer.toString().getBytes("utf-8"));
while ((length = fileInputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, length);
}
} else {
buffer.append("HTTP/1.1 404 Not Found\r\n");
buffer.append("Content-Type:text/html;charset=utf-8\r\n");
buffer.append("\r\n");
buffer.append("<div style='color:red'>File Not Found</div>");
outputStream.write(buffer.toString().getBytes("utf-8"));
}
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
WebServer.java(服务器端)
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class WebServer {
public static void main(String[] args) {
try {
//创建端口号为8090的服务器对象serverSocket
ServerSocket serverSocket = new ServerSocket(8090);
//继续监听8090端口号
while (true) {
//里面的client其实就是连接过来的每一个客户端
//这一步是阻塞的,若没有客户端连接过来,会一直阻塞无法执行
Socket client = serverSocket.accept();
//为防止客户端连接上服务器但没传输数据阻塞,影响其它客户端连接,故通过线程来解决。
//可实现多客户端同时连接
new Thread(new Runnable() {
@Override
public void run() {
//就是对请求信息的封装,客户端发送过来的信息都在这里面
try {
//对于响应信息的封装,只需要往这里面写入数据,最终就可以把数据返回给客户端
//OutputStream outputStream = client.getOutputStream();
//如何把inputStream里面的内容解析成为一个字符串呢?
//根据面向对象的思想,将请求响应设置为两个对象 request、response
Request request = new Request(client);
//请求的资源,请求意图 /1.html google----oracle android
//看这个文件是否存在,如果存在,则返回给客户端;如果不存在,则返回404 HTTP响应状态码
String requestURI = request.getRequestURI();
Response response = new Response(client, requestURI);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
StringUtils.java(工具类)
//工具类:这些代码在很多地方可能需要频繁使用到
public class StringUtils {
public static boolean isEmpty(String content){
if(content == null || content.isEmpty()){
return true;
}
return false;
}
}
test.html(服务器内文件,被访问)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<form action="http://localhost:8090/test2.html" method="post">
<input type="text" name="username">账号<br>
<input type="password" name="pasword">密码
<input type="submit">
</form>
</div>
</body>
</html>
test2.html(服务器内文件,被访问)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
test2
</div>
</body>
</html>
3.结果输出: