WebServerApplication
package com.webserver.core;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* WebServer主类
*/
public class WebServerApplication {
private ServerSocket serverSocket;
private String host;
public WebServerApplication() {
try {
System.out.println("............................................. ");
System.out.println("佛祖保佑 永无BUG ");
System.out.println(" 佛曰: ");
System.out.println("写字楼里写字间,写字间里程序员;");
System.out.println("程序人员写程序,又拿程序换酒钱。");
System.out.println("酒醒只在网上坐,酒醉还来网下眠;");
System.out.println("酒醉酒醒日复日,网上网下年复年。");
System.out.println("但愿老死电脑间,不愿鞠躬老板前;");
System.out.println("奔驰宝马贵者趣,公交自行程序员。");
System.out.println("别人笑我忒疯癫,我笑自己命太贱;");
System.out.println("不见满街漂亮妹,哪个归得程序员?");
System.out.print("正在启动服务端,");
serverSocket = new ServerSocket(8088);
System.out.println("服务端启动完毕!");
} catch (IOException e) {
e.printStackTrace();
}
}
public void start() {
try {
while (true) {
System.out.println("等待客户端链接...");
Socket socket = serverSocket.accept();
System.out.println("一个客户端链接了!");
//启动一个线程处理与该客户端的交互
ClientHandler handler = new ClientHandler(socket);
Thread t = new Thread(handler);
t.start();
host = socket.getInetAddress().getHostAddress();
System.out.println("用户" + host + "登录了");
}
} catch (IOException e) {
}
}
public static void main(String[] args) {
WebServerApplication application = new WebServerApplication();
application.start();
}
}
CilentHandler
package com.webserver.core;
import com.webserver.http.EmptyRequestException;
import com.webserver.http.HttpServletRequest;
import com.webserver.http.HttpServletResponse;
import java.io.*;
import java.net.Socket;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* 处理一次与客户端的HTTP交互操作
* 处理一次HTTP交互由三步完成:
* 1:解析请求
* 2:处理请求
* 3:发送响应
*/
public class ClientHandler implements Runnable {
private Socket socket;
public ClientHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
/**
*1解析请求
*/
HttpServletRequest request = new HttpServletRequest(socket);
//创建响应对象
HttpServletResponse response = new HttpServletResponse(socket);
/**
*2处理请求
*/
DispatcherServlet.getInstance().service(request, response);//单例模式
/**
*3发送响应
*/
response.response();
} catch (IOException e) {
} catch (EmptyRequestException e) {
} finally {
//按照HTTP协议要求,一次交互后断开TCP连接
try {
socket.close();
} catch (IOException e) {
}
}
}
}
DispatcherServlet
package com.webserver.core;
import com.webserver.ArticleController.ArticController;
import com.webserver.controller.UserController;
import com.webserver.http.HttpServletRequest;
import com.webserver.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
/**
* @Author Niukai
* @Date 2022/7/14 14:26
* DispatcherServlet是Spring MVC框架提供的一个核心类,用于和底层容器TOMACT整合使用。
* 通过他使得程序员在写业务类时,无需再关注请求是如何调用到对应的业务处理类(某Controller)中
* 对应的处理方法(被@RequestMapping注解标注的方法),其会根据请求自动调用。
* 这里忽略了Tomcat和SpringMVC框架整合的细节,直接使用该类完成核心业务逻辑的刨析
*/
public class DispatcherServlet {
private static DispatcherServlet dispatcherServlet;
static {
dispatcherServlet = new DispatcherServlet();
}
private DispatcherServlet(){}
private static File rootDir;
private static File staticDir;
static {
try {
//定位到:target/classes
rootDir = new File(ClientHandler.class.getClassLoader().getResource(".").toURI());
//定位static目录
staticDir = new File(rootDir, "static");
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
//不能直接使用uri作为请求路径处理了,因为可能包含参数,而参数内容不是固定信息
String path = request.getRequestURI();
/*
判断本次请求是否为请求某个业务
*/
if("/regUser".equals(path)){
UserController controller=new UserController();
controller.reg(request,response);
}
else if ("/loginUser".equals(path)){
UserController controller=new UserController();
controller.loginUser(request,response);
}
else if ("/userList".equals(path)){
UserController controller=new UserController();
controller.userList(request,response);
}else if ("/delete".equals(path)){
UserController controller=new UserController();
controller.delete(request,response);
}else if ("/deletearticle".equals(path)){
ArticController articController=new ArticController();
articController.deletearticle(request,response);
}
else if ("/writeArticle".equals(path)){
ArticController articController=new ArticController();
articController.writeArticle(request,response);
}
else if ("/showAllArticle".equals(path)){
ArticController articController=new ArticController();
articController.showAllArticle(request,response);
}
else {
File file = new File(staticDir, path);
System.out.println("是否存在:" + file.exists());
//判断是否存在,不存在则跳转到404.html
if (file.isFile()) {
response.setContentFile(file);
} else {
response.setStatusCode(404);
response.setStatusReason("NotFound");
response.setContentFile(new File(staticDir, "/root/404.html"));
}
}
}
public static DispatcherServlet getInstance() {
return dispatcherServlet;
}
}
HttpServletRequest
package com.webserver.http;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* @Author Niukai
* @Date 2022/7/13 14:27
* 请求对象
* 该类的每一个实例用于表示浏览器发送过来的一个HTTP协议
*/
public class HttpServletRequest {
private Socket socket;
private String method;//请求方式
private String uri;//抽象路径
private String protocol;//协议版本
private String requestURI;//保存uri中的请求部分("?"左侧的内容)
private String queryString;//保存uri中的参数部分("?"右侧的内容)
private Map<String, String> parameters = new HashMap<>();//保存每一组参数
private Map<String, String> headers = new HashMap<>();//消息头相关信息
public HttpServletRequest(Socket socket) throws IOException, EmptyRequestException {
this.socket = socket;
//1.1解析请求行
parseRequestLine();
//1.2解析消息头
parseHeaders();
// 1.3解析正文消息
parseContent();
}
//解析请求行
private void parseRequestLine() throws IOException, EmptyRequestException {
String line = readLine();
if (line.isEmpty()) {//如果请求行为空串,则说明本次为空请求
throw new EmptyRequestException();
}
System.out.println("请求行:" + line);
String[] data = line.split("\\s+");
method = data[0];
uri = data[1];
protocol = data[2];
parseURI();
}
//进一步解析uri
private void parseURI() {
String []data=uri.split("\\?");
requestURI=data[0];
if (data.length>1){
queryString=data[1];
parseParameters(queryString);
}
}
//解析参数
private void parseParameters(String line){
//先将参数转换为中文
try {
line=URLDecoder.decode(line,"UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String[] data=line.split("&");
for (String para: data){
String[] paras=para.split("=");
parameters.put(paras[0],paras.length>1?paras[1]:"");
}
}
//解析消息头
private void parseHeaders() throws IOException {
while (true) {
String line = readLine();
if (line.isEmpty()) {
break;
}
/*
将每一个消息头按照": "(冒号+空格拆)分为消息头的名字和消息头的值
并以key,value的形式存入到headers中
*/
String[] data = line.split(":\\s");
//将消息头的名字转换为全小写后存入headers,兼容性更好(浏览器发送的消息头无论大小写,只要拼写正确即可)
headers.put(data[0].toLowerCase(Locale.ROOT), data[1].toLowerCase(Locale.ROOT));
}
}
//解析消息正文
private void parseContent() throws IOException {
//判断本次请求方式是不是post请求
if ("POST".equalsIgnoreCase(method)){//"post"
//根据消息头Content-Length来确定正文的字节数量以便经行读取
String contentLength=getHeader("Content-Length");
if (contentLength!=null){
//判断不为null的目的是确保有消息头Content-Length
int length=Integer.parseInt(contentLength);
System.out.println("正文长度"+length);
byte []data=new byte[length];
InputStream in=socket.getInputStream();
in.read(data);
//根据Content-Type来判断正文类型,并进行对应的处理
String ContentType=getHeader("Content-Type");
//分支判断不同的类型进行不同的处理
if("application/x-www-form-urlencoded".equals(ContentType)){//判断类型是否为form表单不带附件的数据
String line=new String(data, StandardCharsets.ISO_8859_1);
System.out.println("正文内容"+line);
parseParameters(line);
}
// else if () {}扩展其他类型并进行对应的处理
}
}
}
private String readLine() throws IOException {
InputStream is = socket.getInputStream();
int d;
char cur = 'a', pre = 'a';
StringBuilder stringBuilder = new StringBuilder();
while ((d = is.read()) != -1) {
cur = (char) d;
if (pre == 13 && cur == 10) {
break;
}
stringBuilder.append(cur);
pre = cur;
}
return stringBuilder.toString().trim();
}
public String getMethod() {
return method;
}
public String getUri() {
return uri;
}
public String getProtocol() {
return protocol;
}
public String getHeader(String name) {
// headers:
return headers.get(name.toLowerCase(Locale.ROOT));
}
public String getRequestURI() {
return requestURI;
}
public String getQueryString() {
return queryString;
}
public String getParameter(String name) {
return parameters.get(name);
}
}
HttpServletResponse
package com.webserver.http;
import com.webserver.core.DispatcherServlet;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* 响应对象
* 该类的每一个实例表示HTTP协议规定的一个响应内容
* 每个响应由三部分构成:
* 状态行,响应头,响应正文
*/
public class HttpServletResponse {
private Socket socket;
//状态行的相关信息
private int statusCode = 200; //状态代码
private String statusReason = "OK"; //状态描述
//响应头相关信息
private Map<String,String> headers = new HashMap<>(); //响应头
//响应正文相关信息
private File contentFile; //正文对应的实体文件
private ByteArrayOutputStream baos;
public HttpServletResponse(Socket socket){
this.socket = socket;
}
/**
* 将当前响应对象内容以标准的HTTP响应格式,发送给客户端(浏览器)
*/
public void response() throws IOException {
//发送前的准备工作
sendBefore();
//3.1发送状态行
sendStatusLine();
//3.2发送响应头
sendHeaders();
//3.3发送响应正文
sendContent();
}
private void sendBefore(){
//判断是否有动态数据存在
if(baos!=null){
addHeader("Content-Length",baos.size()+"");
}
}
//发送状态行
private void sendStatusLine() throws IOException {
println("HTTP/1.1" + " " + statusCode + " " +statusReason);
}
//发送响应头
private void sendHeaders() throws IOException {
/*
当发送响应头时,所有待发送的都应当都作为键值对存入了headers中
headers:
key value
Content-Type text/html
Content-Length 245
Server WebServer
... ...
*/
Set<Map.Entry<String,String>> entrySet = headers.entrySet();
for(Map.Entry<String,String> e : entrySet){
String name = e.getKey();
String value = e.getValue();
println(name + ": " + value);
}
//单独发送一组回车+换行表示响应头部分发送完了!
println("");
}
//发送响应正文
private void sendContent() throws IOException {
if (baos!=null){//存在动态数据
byte[]data=baos.toByteArray();
OutputStream out=socket.getOutputStream();
out.write(data);
}
else if(contentFile!=null) {
try (
FileInputStream fis = new FileInputStream(contentFile);
) {
OutputStream out = socket.getOutputStream();
int len;
byte[] data = new byte[1024 * 10];
while ((len = fis.read(data)) != -1) {
out.write(data, 0, len);
}
}
}
}
public void sendRedirect(String path){//path="/reg_success.html"
statusCode = 302;
statusReason = "Moved Temporarily";
addHeader("Location",path);
}
/**
* 向浏览器发送一行字符串(自动补充CR+LF)
* @param line
*/
private void println(String line) throws IOException {
OutputStream out = socket.getOutputStream();
out.write(line.getBytes(StandardCharsets.ISO_8859_1));
out.write(13);//发送回车符
out.write(10);//发送换行符
}
public int getStatusCode() {
return statusCode;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
public String getStatusReason() {
return statusReason;
}
public void setStatusReason(String statusReason) {
this.statusReason = statusReason;
}
public File getContentFile() {
return contentFile;
}
public void setContentFile(File contentFile) throws IOException {
this.contentFile = contentFile;
String contentType = Files.probeContentType(contentFile.toPath());
//如果根据文件没有分析出Content-Type的值就不添加这个头了,HTTP协议规定服务端不发送这个头时由浏览器自行判断类型
if(contentType!=null){
addHeader("Content-Type",contentType);
}
addHeader("Content-Length",contentFile.length()+"");
}
public void addHeader(String name,String value){
headers.put(name,value);
}
public OutputStream getOutputStream(){
if(baos==null){
baos=new ByteArrayOutputStream();
}
return baos;
}
public PrintWriter getWriter(){
OutputStream out=getOutputStream();//低级流
OutputStreamWriter osw=new OutputStreamWriter(out,StandardCharsets.UTF_8);
BufferedWriter bw=new BufferedWriter(osw);
PrintWriter pw=new PrintWriter(bw,true);
return pw;
}
/**
*添加响应头ContentType
* @param mine
*/
public void setContentType(String mine){
addHeader("Content-Type",mine);
}
}
User
package com.webserver.entity;
import java.io.Serializable;
/**
* @Author Niukai
* @Date 2022/7/15 14:31
*/
public class User implements Serializable {
static final long serialVersionUID = 42L;
private String username;
private String password;
private String nickname;
private int age;
public User(){
}
public User(String username, String password, String nickname, int age) {
this.username = username;
this.password = password;
this.nickname = nickname;
this.age = age;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", nickname='" + nickname + '\'' +
", age=" + age +
'}';
}
}
UserController
package com.webserver.controller;
import com.webserver.entity.User;
import com.webserver.http.HttpServletRequest;
import com.webserver.http.HttpServletResponse;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* 当前类负责处理与用户相关的操作
*/
public class UserController {
private static File userDir;//该目录用于保存所有注册用户文件(一堆的.obj文件)
static {
userDir = new File("./users");
if (!userDir.exists()) {//如果该目录不存在
userDir.mkdirs();
}
}
public void reg(HttpServletRequest request, HttpServletResponse response) {
System.out.println("开始处理用户注册!!!!!!!!!!!!!!!!!!!");
//对应reg.html页面表单中<input name="username" type="text">
String username = request.getParameter("username");
String password = request.getParameter("password");
String nickname = request.getParameter("nickname");
String age = request.getParameter("age");
System.out.println(username + "," + password + "," + nickname + "," + age);
//对数据进行必要的验证工作
if (username.isEmpty() || password.isEmpty() || nickname.isEmpty() || age.isEmpty() || !age.matches("[0-9]+")) {
//如果如何上述情况,则直接响应给用户一个注册失败提示页面,告知信息输入有误。
response.sendRedirect("/reg_info_error.html");
return;
}
//处理注册
//将年龄转换为int值
int age_ = Integer.parseInt(age);
User user = new User(username, password, nickname, age_);
//参数1:当前File表示的文件所在的目录 参数2:当前文件的名字
File userFile = new File(userDir, username + ".obj");
if (userFile.exists()) {//文件已经存在说明该用户已经存在了!
response.sendRedirect("/have_user.html");
return;
}
try (
FileOutputStream fos = new FileOutputStream(userFile);
ObjectOutputStream oos = new ObjectOutputStream(fos);
) {
oos.writeObject(user);
//响应注册成功页面给浏览器
response.sendRedirect("/reg_success.html");
} catch (IOException e) {
e.printStackTrace();
}
}
public void loginUser(HttpServletRequest request, HttpServletResponse response) {
System.out.println("开始处理登录页面");
String username = request.getParameter("username");
String password = request.getParameter("password");
if (username.isEmpty() || password.isEmpty()) {
response.sendRedirect("/login_Error.html");
}
File userFile = new File(userDir, username + ".obj");
if (userFile.exists()) {
try
(
FileInputStream fis = new FileInputStream(userFile);
ObjectInputStream ois = new ObjectInputStream(fis);
) {
User user = (User) ois.readObject();
if (user.getUsername().equals(username) && user.getPassword().equals(password)) {
response.sendRedirect("/login_Success.html");
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
public void userList(HttpServletRequest request, HttpServletResponse response) {
System.out.println("开始处理动态页面");//含有动态数据的页面叫动态页面
//动态数据:由客户端新生成的数据
//静态数据,提前写好的数据
List<User> userList = new ArrayList<>();
File[] subs = userDir.listFiles(f -> f.getName().endsWith(".obj"));
for (File f : subs) {
try (FileInputStream fis = new FileInputStream(f);
ObjectInputStream ois = new ObjectInputStream(fis);) {
User user = (User) ois.readObject();
userList.add(user);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
response.setContentType("text/html;charset=utf-8");
PrintWriter pw =response.getWriter();
pw.println("<!DOCTYPE html>");
pw.println("<html lang=\"en\">");
pw.println("<head>");
pw.println(" <meta charset=\"UTF-8\">");
pw.println("<title>Title</title>");
pw.println("</head>");
pw.println("<body>");
pw.println("<center>");
pw.println("<h1>用户列表</h1>");
pw.println("<table border=\"1\">");
pw.println("<tr>");
pw.println("<td>用户名</td>");
pw.println("<td>密码</td>");
pw.println("<td>昵称</td>");
pw.println("<td>年龄</td>");
pw.println("<td>操作</td>");
pw.println("</tr>");
for (User user : userList) {
pw.println("<tr>");
pw.println("<td>" + user.getUsername() + "</td>");
pw.println("<td>" + user.getPassword() + "</td>");
pw.println("<td>" + user.getNickname() + "</td>");
pw.println("<td>" + user.getAge() + "</td>");
pw.println("<td><a href='/delete?username=" + user.getUsername() + "'>删除</a></td>");
pw.println("</tr>");
}
pw.println("</table>");
pw.println("</center>");
pw.println("</body>");
pw.println("<html");
}
public void delete(HttpServletRequest request,HttpServletResponse response){
String username=request.getParameter("username");
File userFile=new File(userDir,username+".obj");
if (userFile.exists()){
userFile.delete();
}
response.sendRedirect("/userList");
}
}
Article
package com.webserver.entity;
import java.io.Serializable;
/**
* @Author Niukai
* @Date 2022/7/19 9:46
*/
public class Article implements Serializable {
static final long serialVersionUID = 42L;
private String title;
private String author;
private String content;
public Article(){
}
public Article(String title, String author, String content) {
this.title = title;
this.author = author;
this.content = content;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Article{" +
"title='" + title + '\'' +
", author='" + author + '\'' +
", content='" + content + '\'' +
'}';
}
}
ArticController
package com.webserver.ArticleController;
import com.webserver.entity.Article;
import com.webserver.http.HttpServletRequest;
import com.webserver.http.HttpServletResponse;
import sun.security.tools.keytool.Main;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* @Author Niukai
* @Date 2022/7/19 9:42
* 发表文章的相关操作
*/
public class ArticController {
private static File articleDir;
static {
articleDir = new File("./articles");
if (!articleDir.exists()) {
articleDir.mkdirs();
}
}
public void writeArticle(HttpServletRequest request, HttpServletResponse response) {
String title = request.getParameter("title");
String author = request.getParameter("author");
String content = request.getParameter("content");
System.out.println(title + "," + author + "," + content);
if (title == null || author == null || content == null || title.isEmpty() || author.isEmpty() || content.isEmpty()) {
response.sendRedirect("/article_fail.html");
return;
}
File articleFile = new File(articleDir, title + ".obj");
if (articleFile.exists()) {
//文件存在,说明是重复标题
response.sendRedirect("/have_article.html");
return;
}
Article article = new Article(title, author, content);
try (
FileOutputStream fos = new FileOutputStream(articleFile);
ObjectOutputStream oos = new ObjectOutputStream(fos);
) {
oos.writeObject(article);
response.sendRedirect("/article_success.html");
} catch (IOException e) {
e.printStackTrace();
}
}
public void showAllArticle(HttpServletRequest request, HttpServletResponse response) {
System.out.println("处理文章列表请求");
List<Article> articleList = new ArrayList<>();
File[] subs = articleDir.listFiles(f -> f.getName().endsWith(".obj"));
for (File f : subs) {
try (
FileInputStream fis = new FileInputStream(f);
ObjectInputStream ois = new ObjectInputStream(fis);
) {
Article article = (Article) ois.readObject();
articleList.add(article);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
response.setContentType("text/html;charset=utf-8");
PrintWriter pw = response.getWriter();
pw.println("<!DOCTYPE html>");
pw.println("<html lang=\"en\">");
pw.println("<head>");
pw.println(" <meta charset=\"UTF-8\">");
pw.println("<title>Title</title>");
pw.println("</head>");
pw.println("<body>");
pw.println("<center>");
pw.println("<h1>文章列表</h1>");
pw.println("<table border=\"1\">");
pw.println("<tr>");
pw.println("<td>标题</td>");
pw.println("<td>作者</td>");
pw.println("<td>操作</td>");
pw.println("</tr>");
for (Article article : articleList) {
pw.println("<tr>");
pw.println("<td>" + article.getTitle() + "</td>");
pw.println("<td>" + article.getAuthor() + "</td>");
pw.println("<td><a href='/deletearticle?title=" + article.getTitle() + "'>删除</a></td>");
pw.println("</tr>");
}
pw.println("</table>");
pw.println("</center>");
pw.println("</body>");
pw.println("<html");
}
public void deletearticle(HttpServletRequest request, HttpServletResponse response) {
String title = request.getParameter("title");
File articleFile = new File(articleDir, title + ".obj");
if (articleFile.exists()) {
articleFile.delete();
}
response.sendRedirect("/showAllArticle");
}
}