写在前面:最近在学web前后端内容,其中web服务器搭建大多采用现成的tomcat以及serverlet,今天突发奇想想着能不呢自己写一个web服务器,省去大量的对我来讲没什么用的解析过程,这个简单项目由此展开(所以这个项目没有采用任何框架,都是从零开始写的web服务器)
这是第一次迭代的想法:
1.web文件都采用文件管理模式(后续维护或许会添加上数据库管理模式吧)
2.通过终端控制台控制。
3.对每一个web的html创建一个控制文件负责时间戳的202与304处理
问题探讨:
问题:keep-alive 生效?
我第一次思考到keep-alive时想法把一个html文件关联了几个资源文件全部写到控制文件中进行处理,就用一个tcp连接来完成所有资源的传输,后来发现是我年轻了,因为html一旦发送到客户端后不管是否keep-alive与否它全都给我发送新的tcp资源请求到服务器了,也就是说,原来的那个tcp连接线程一直就在空等,死也等不到,后来查了查资料,告诉我可能原因是浏览器在接受html时是便接受边解析,一旦解析到资源请求就发送tcp连接,后果就是原来的tcp的html资源还没全部过来就有了要求,浏览器只能重新发送tcp连接,(虽然我在服务器上添加了守护线程超时就把tcp关掉了),但多个tcp线程挂在服务器属实离谱,后来发现当css文件到了客户端后,css的资源请求会从之前html请求的tcp连接发出,查询的话主流浏览器服务器都是把tcp连接挂载在服务器上,用守护线程等超时再关掉,除非有客户端发来的close,我思考(难道我要用我那少得可怜的服务器资源来等客户端的反应么)于是我果断弃用了keep-alive,就是一个请求我就发送一个资源然后就把socket给close了。(可能是我对http的keep-alive的理解还不够到位,欢迎指正点评)
以下就是我的第一版运行效果了:(挂载在Linux上了)
客户端浏览器就可以通过端口号+“/”+文件夹名称就可以访问文件夹下的网页文件...
当然我这个服务器仅仅只是实现了get请求,以及实现了时间戳控制,后续会更新post请求等等吧
我有个大胆想法把post和get挂在不同服务器,然后用数据库活动密钥的方法来get和post,当然啦,仅仅还在初步构想。
如果不会配置源代码并需要源文件,欢迎私信我。。。
项目名:JavaWeb
包:user
JavaWeb.java:
package user;
import model.ServerBasis;
public class JavaWeb
{
/**
* 当前类是控制文件的标准类,用于控制文件的读取和写出。
* 第一次设计迭代时间:2022.11.8 //Name:Star_Tian 功能:实现了基本的web传输功能,完成了基本文件控制,并将一个文件夹下文件进行便于管理
* 第二次迭代时间:2022.11.15 //Name:Star_Tian 功能:修复了多个资源控制的bug,实现了时间戳控制,删除了KeepAlice,简化了控制文件,简要优化了控制台。
* 第三次迭代时间:2022.11.17 //Name:Star_Tian 功能:添加了服务器配置文件,用于动态配置服务器的各种配置数据,初始只有端口等
*/
static Integer OK=0;
public static void main(String args[])
{
System.out.println("启动中......");
ServerBasis.initialize();
new ListenProc(ServerBasis.ServerInfo.GetPort()).start();
}
}
ListenProc.java:
package user;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import lockbag.HeaderAnalysis;
public class ListenProc extends Thread
{
private int Port;
private ServerSocket server;
public ListenProc(int port)
{
this.Port=port;
}
public void run()
{
Socket client;
System.out.println("监听端口初始化中......");
try
{
server = new ServerSocket(this.Port);
new Console(this.server).start();
System.out.println("监听端口初始化建立成功");
while(true)
{
client=this.server.accept();
new HeaderAnalysis(client).start();
}
} catch (IOException e)
{
System.out.println("服务端接口已经失效,检查服务器端口占用情况");
}
}
}
Console.java:
package user;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.Scanner;
import model.LWebY;
import model.ServerBasis;
public class Console extends Thread {
ServerSocket Server;
static String HelpString="//Quit 操作说明:退出系统命令\n"
+ "//Create 操作说明:建立新的web控制文件\n"
+ "//Update + web文件夹名 操作说明:更新web文件夹控制文件的时间戳\n"
+ "//SetPort + 端口号 操作说明:更新web服务器的端口号(重启后生效)\n"
+ " ......其他";
static String CodeLeft="JavaWeb>>";
static String CreateTip="创建web控制文件中,请按以下格式输入相关信息,信息之间用空格隔开(注意,新文件会覆盖旧文件,请注意备份):\n"
+"主html文件名称+文件存储文件夹名称(当前目录下)";
static String ErrTip="指令识别失败,可以使用//WebHelp(//help)查看帮助";
static String TypeErrTip="输入信息不符合格式,创建失败";
static String CodeErrTip="指令格式错误,请遵照//WebHelp(//help)提供格式输入指令";
public Console(ServerSocket server)
{
this.Server=server;
}
public void run()
{
System.out.println("控制台程序初始化完成");
System.out.println("控制台使用注意:使用//WebHelp(//help)指令可以获取指令帮助,当前指令系统不区分大小写");
while(true)
{
System.out.print(CodeLeft);
Scanner GetCode = new Scanner(System.in);
String Code =GetCode.nextLine();
String[] deal=Code.split(" ");
deal[0]=deal[0].toLowerCase();
switch(deal[0])
{
case "//quit":
try
{
this.Server.close();
} catch (IOException e)
{
e.printStackTrace();
}
System.out.println("服务器配置保存中......");
ServerBasis.ServerInfo.FileWrite();
System.out.println("退出系统成功.");
GetCode.close();
System.exit(0);
break;
case "//webhelp":
System.out.println(HelpString);
break;
case "//help":
System.out.println(HelpString);
break;
case "//create":
System.out.println(CreateTip);
Code =GetCode.nextLine();
String Analysis[]=Code.split(" ");
if(Analysis.length!=2)
{
System.err.println(CodeErrTip);
break;
}
LWebY Create=new LWebY(Analysis[0],Analysis[1]);
Create.new WriteThread(Create).start();
break;
case "//update":
if(deal.length<2)
System.err.println(CodeErrTip);
else
{
new update(deal[1]).start();
}
break;
case "//setport":
{
if(deal.length<2)
System.err.println(CodeErrTip);
else
{
ServerBasis.ServerInfo.setPort(Integer.valueOf(deal[1]));
System.out.println("服务器端口更改成功,请//quit退出后重启服务器即可生效.");
}
break;
}
default:
System.out.println(ErrTip);
}
}
}
public class update extends Thread{
private String DirName;
public update(String DirName)
{
this.DirName=DirName;
}
public void run()
{
File updateDir=new File(DirName);
if(updateDir.exists()) {
try
{
LWebY old=LWebY.FileInput(DirName);
LWebY ControlNew=new LWebY(old.GetHtml(),old.GetDirName());
ControlNew.new WriteThread(ControlNew).start();
System.out.println("控制文件时间更新中...");
} catch (FileNotFoundException e)
{
System.err.println("文件检入错误,当前的网页文件不存在control文件,请使用//create指令创建");
}
}
else {
System.err.println("文件夹不存在,当前操作无效");
}
}
}
}
包:model
LWebY.java:
package model;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Date;
/**/
public class LWebY implements Serializable
{
/**
* 当前类是控制文件的标准类,用于控制文件的读取和写出。
* 第一次设计迭代时间:2022.11.8 //Name:Star_Tian
* 第二次迭代时间:2022.11.15 //Name:Star_Tian 功能:修复了多个资源控制的bug
*/
private static final long serialVersionUID = -5799700946989607148L;
private String titleHtml; //html文件名称
private String FileName; //控制文件名称
private String DirName; //文件夹名称
private Date LastNewDate; //最后更新日期
private String DateStr; //日期的格式
public LWebY(String titleHtml,String DirName)
{
this.FileName="Control.LWebY";
this.titleHtml=titleHtml;
this.DirName=DirName;
this.LastNewDate=new Date();
this.DateStr=this.LastNewDate.toString();
}
public class WriteThread extends Thread
{
private LWebY Focus;
public WriteThread(LWebY Focus)
{
this.Focus=Focus;
}
public void run()
{
try
{
Focus.FileWrite();
System.out.println("控制文件创建成功!");
System.out.print("JavaWeb>>");
} catch (FileNotFoundException e)
{
System.err.println("文件名称创建错误,检查LWebY.FileWrite的FileOutputStream");
} catch (IOException e)
{
System.err.println("文件流出现错误,检查LWebY.FileWrite的ObjectOutputStream");
} catch (FileNotFinded e)
{
System.err.println("输入参数错误,检查输入的路径以及html文件是否正确或尚未将文件放入指定文件夹");
}
}
}
public class FileNotFinded extends Exception
{
/**
*
*/
private static final long serialVersionUID = -5404731048838183395L;
}
public boolean FileWrite() throws FileNotFoundException,IOException, FileNotFinded
{
FileOutputStream Output;
try
{
File newControl=new File(this.DirName+"/"+this.titleHtml);
if(!newControl.exists())
throw new FileNotFinded();
Output = new FileOutputStream(DirName+"/"+FileName);
ObjectOutputStream ObjectOut=new ObjectOutputStream(Output);
Object ObjectContent=this;
ObjectOut.writeObject(ObjectContent);
ObjectOut.close();
} catch (FileNotFoundException e)
{
System.err.println("文件流创建失败,请检查LWebY.FileWrite的FileOutputStream");
throw e;
} catch (IOException e)
{
System.err.println("文件流输出失败,请检查LWebY.FileWrite的输出流样式");
throw e;
}
return true;
}
static public LWebY FileInput(String DirName) throws FileNotFoundException
{
try
{
DirName=DirName+"/Control.LWebY";
FileInputStream FileIn=new FileInputStream(DirName);
ObjectInputStream ObjectIn=new ObjectInputStream(FileIn);
Object Reading=ObjectIn.readObject();
ObjectIn.close();
return (LWebY)Reading;
} catch (FileNotFoundException e)
{
throw e;
} catch (ClassNotFoundException e)
{
System.err.println("对象类型转化失败,请检查对应LWebY类本地是否存在,如不存在,请联系设计者获取");
return null;
} catch (IOException e)
{
System.err.println("输入流过程中出现异常,请检查LWebY.FileInput输入流");
return null;
}
}
public String GetDirName()
{
return this.DirName;
}
public String GetHtml()
{
return this.titleHtml;
}
public String GetDate()
{
return this.DateStr;
}
public Date DateGet()
{
return this.LastNewDate;
}
}
ServerBasis.java:
package model;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Date;
/**/
public class LWebY implements Serializable
{
/**
* 当前类是控制文件的标准类,用于控制文件的读取和写出。
* 第一次设计迭代时间:2022.11.8 //Name:Star_Tian
* 第二次迭代时间:2022.11.15 //Name:Star_Tian 功能:修复了多个资源控制的bug
*/
private static final long serialVersionUID = -5799700946989607148L;
private String titleHtml; //html文件名称
private String FileName; //控制文件名称
private String DirName; //文件夹名称
private Date LastNewDate; //最后更新日期
private String DateStr; //日期的格式
public LWebY(String titleHtml,String DirName)
{
this.FileName="Control.LWebY";
this.titleHtml=titleHtml;
this.DirName=DirName;
this.LastNewDate=new Date();
this.DateStr=this.LastNewDate.toString();
}
public class WriteThread extends Thread
{
private LWebY Focus;
public WriteThread(LWebY Focus)
{
this.Focus=Focus;
}
public void run()
{
try
{
Focus.FileWrite();
System.out.println("控制文件创建成功!");
System.out.print("JavaWeb>>");
} catch (FileNotFoundException e)
{
System.err.println("文件名称创建错误,检查LWebY.FileWrite的FileOutputStream");
} catch (IOException e)
{
System.err.println("文件流出现错误,检查LWebY.FileWrite的ObjectOutputStream");
} catch (FileNotFinded e)
{
System.err.println("输入参数错误,检查输入的路径以及html文件是否正确或尚未将文件放入指定文件夹");
}
}
}
public class FileNotFinded extends Exception
{
/**
*
*/
private static final long serialVersionUID = -5404731048838183395L;
}
public boolean FileWrite() throws FileNotFoundException,IOException, FileNotFinded
{
FileOutputStream Output;
try
{
File newControl=new File(this.DirName+"/"+this.titleHtml);
if(!newControl.exists())
throw new FileNotFinded();
Output = new FileOutputStream(DirName+"/"+FileName);
ObjectOutputStream ObjectOut=new ObjectOutputStream(Output);
Object ObjectContent=this;
ObjectOut.writeObject(ObjectContent);
ObjectOut.close();
} catch (FileNotFoundException e)
{
System.err.println("文件流创建失败,请检查LWebY.FileWrite的FileOutputStream");
throw e;
} catch (IOException e)
{
System.err.println("文件流输出失败,请检查LWebY.FileWrite的输出流样式");
throw e;
}
return true;
}
static public LWebY FileInput(String DirName) throws FileNotFoundException
{
try
{
DirName=DirName+"/Control.LWebY";
FileInputStream FileIn=new FileInputStream(DirName);
ObjectInputStream ObjectIn=new ObjectInputStream(FileIn);
Object Reading=ObjectIn.readObject();
ObjectIn.close();
return (LWebY)Reading;
} catch (FileNotFoundException e)
{
throw e;
} catch (ClassNotFoundException e)
{
System.err.println("对象类型转化失败,请检查对应LWebY类本地是否存在,如不存在,请联系设计者获取");
return null;
} catch (IOException e)
{
System.err.println("输入流过程中出现异常,请检查LWebY.FileInput输入流");
return null;
}
}
public String GetDirName()
{
return this.DirName;
}
public String GetHtml()
{
return this.titleHtml;
}
public String GetDate()
{
return this.DateStr;
}
public Date DateGet()
{
return this.LastNewDate;
}
}
包:lockbag
HeaderAnalysis.java
package lockbag;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class HeaderAnalysis extends Thread {
Socket Client;
OutputStream Output;
BufferedReader reader;
boolean FirstOrNot;
String ConnectionState="Keep-Alive";
String DirName="";
Date ModifiedDate;
static DateFormat DateFor=new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy",Locale.ENGLISH);
public HeaderAnalysis(Socket client)
{
/**
* 当前类是控制文件的标准类,用于控制文件的读取和写出。
* 第一次设计迭代时间:2022.11.8 //Name:Star_Tian
* 第二次迭代时间:2022.11.15 //Name:Star_Tian 功能:添加了304时间戳测试,对时间戳进行判定,省略大量IO时间
*/
this.Client=client;
this.FirstOrNot=true;
}
public void run()
{
try {
byte[] header=new byte[1024*1024];
TimeCul TimeClose=new TimeCul(5000,this.Client);
TimeClose.start(); //启动守护线程
int len=Client.getInputStream().read(header);
TimeClose.interrupt();//收到信息关闭守护线程
if(len>0)
{
String Out=new String(header,"UTF-8");
String TargetFile="",Action="",Versions="",IPHost="",AccConnectionStates="";
String[] Analysis=Out.split("\r\n");
for(int i=0;i<Analysis.length;i++) {
String[] SecondAna=Analysis[i].split(" ",2);
switch(SecondAna[0])
{
case "GET":
Action=SecondAna[0];
String GetFocus[]=SecondAna[1].split(" ", 2);
TargetFile=GetFocus[0];
Versions=GetFocus[1];
break;
case "POST":
break;
case "Host:":
IPHost=SecondAna[1];
break;
case "Accept:":
AccConnectionStates=SecondAna[1];
break;
case "If-Modified-Since:":
try
{
ModifiedDate=HeaderAnalysis.DateFor.parse(SecondAna[1]);
} catch (ParseException e)
{
// TODO 自动生成的 catch 块
System.err.println("日期转化失败");
}
break;
default:
break;
}
}
if(!TargetFile.isEmpty())
{
new SendProc(TargetFile,Action,Versions,IPHost,AccConnectionStates,this.Client,ModifiedDate).start();
}
}
} catch (IOException e) {
}
}
}
SendProc.java:
package lockbag;
import java.io.FileNotFoundException;
import java.net.Socket;
import java.util.Date;
import data.MinSend;
import model.LWebY;
public class SendProc extends Thread
{
/**
* 当前类是头文件解析后发送基本文件的解释类,完成对头部解析的信息进行分析处理后调用数据层MinSend。
* 第一次设计迭代时间:2022.11.8 //Name:Star_Tian
* 第二次迭代时间:2022.11.15 //Name:Star_Tian 功能:添加了304时间戳测试,添加了来源请求预警与记录播报
*/
String TargetFile,Action,Versions,IPHost,AccConnecitonStates;
Socket Client;
Date ModifiedDate;
public SendProc(String TargetFile,String Action,String Versions,String IPHost,String AccConnectionStates,Socket client,Date ModifiedDate)
{
this.Client=client;
this.TargetFile=TargetFile;
this.Action=Action;
this.Versions=Versions;
this.IPHost=IPHost;
this.AccConnecitonStates=AccConnectionStates;
this.ModifiedDate=ModifiedDate;
}
public void run()
{
String FileName[]=this.TargetFile.substring(1).split("/");
try
{
LWebY ControlFile = LWebY.FileInput(FileName[0]);
if(FileName.length<2) {
if(this.ModifiedDate!=null&&ControlFile.DateGet().getTime()-this.ModifiedDate.getTime()<1000)
{
new MinSend("304 Not Modified",this.Action,this.Versions,this.IPHost,"close",this.Client,ControlFile).start();
}
else{
new MinSend(FileName[0],ControlFile.GetHtml(),this.Action,this.Versions,this.IPHost,"close",this.Client,ControlFile).start();
}
}
else {
if(this.ModifiedDate!=null&&ControlFile.DateGet().getTime()-this.ModifiedDate.getTime()<1000) {
new MinSend("304 Not Modified",this.Action,this.Versions,this.IPHost,"close",this.Client,ControlFile).start();
}
else {
new MinSend(FileName[0],FileName[1],this.Action,this.Versions,this.IPHost,"close",this.Client,ControlFile).start();
}
}
} catch (FileNotFoundException e)
{
System.err.println("警告,出现无效文件访问,访问来源:"+this.Client.toString()+" 请求目标:"+this.TargetFile+" 请求时间:"+new Date().toString());
System.out.print("JavaWeb>>");
}
System.out.println("来自"+this.Client.toString()+"的请求资源:"+this.TargetFile+" 请求时间:"+new Date().toString());
System.out.print("JavaWeb>>");
}
}
TimeCul.java
package lockbag;
import java.io.IOException;
import java.net.Socket;
public class TimeCul extends Thread
{
/**
* 当前类是等待TCP连接的守护线程,用于超时关闭线程避免占用资源。
* 第一次设计迭代时间:2022.11.9 //Name:Star_Tian
* 第二次迭代:2022.11.16 暂时取消本类使用,由于取消了keep-alive机制
*/
long timelong;
Socket Client;
public TimeCul(long timelong,Socket Client)
{
this.timelong=timelong;
this.Client=Client;
}
public void run()
{
try
{
Thread.sleep(timelong);
Client.close();
} catch (InterruptedException e)
{
} catch (IOException e)
{
}
}
}
包:data
MinSend.java:
package data;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.Date;
import model.LWebY;
public class MinSend extends Thread
{
/**
* 当前类数据层底层发送类,负责IO主要功能以及Socket的主要通信.
* 第一次设计迭代时间:2022.11.8 //Name:Star_Tian
* 第二次迭代时间:2022.11.15 //Name:Star_Tian 功能:添加了304时间戳测试,添加了来源请求预警与记录播报
*/
public final static String header="HTTP/1.1 %s\r\n"
+ "Server: LwebY\r\n"
+ "Accept-Ranges: bytes\r\n"
+ "Keep-Alive: timeout=5, max=100\r\n"
+ "Connection: %s\r\n"
+ "Date: %s\r\n"
+ "Last-Modified: %s\r\n\r\n";
@SuppressWarnings("unused")
private String Action,Versions,IPHost,ConnecitonStates;
private Socket Client;
private String DirName,FileName;
private LWebY Control;
private String State=null;
public MinSend(String DirName,String FileName,String Action,String Versions,String IPHost,String ConnectionStates,Socket client,LWebY Control)
{
this.DirName=DirName;
this.FileName=FileName;
this.Client=client;
this.Action=Action;
this.Versions=Versions;
this.IPHost=IPHost;
this.ConnecitonStates=ConnectionStates;
this.Control=Control;
}
public MinSend(String state,String Action,String Versions,String IPHost,String ConnectionStates,Socket client,LWebY Control)
{
this.State=state;
this.Client=client;
this.Action=Action;
this.Versions=Versions;
this.IPHost=IPHost;
this.ConnecitonStates=ConnectionStates;
this.Control=Control;
}
public void run()
{
Date DateNow=new Date();
if(State!=null)
{
String HeaderFormat=String.format(MinSend.header,this.State,"close",DateNow.toString(),this.Control.GetDate());
byte[] Header;
try
{
Header = HeaderFormat.getBytes("UTF-8");
Client.getOutputStream().write(Header,0,Header.length);
Client.getOutputStream().flush();
Client.close();
return;
} catch (UnsupportedEncodingException e)
{
// TODO 自动生成的 catch 块
e.printStackTrace();
} catch (IOException e)
{
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
try
{
File GetFile=new File(DirName+"/"+FileName);
byte[] Buffer=new byte[(int) GetFile.length()+1024];
FileInputStream Open=new FileInputStream(GetFile);
String HeaderFormat=String.format(MinSend.header,"200 OK",this.ConnecitonStates,DateNow.toString(),this.Control.GetDate());
byte[] Header = HeaderFormat.getBytes("UTF-8");
System.arraycopy(Header, 0, Buffer, 0, Header.length);
int FileLen=Open.read(Buffer, Header.length,Buffer.length-Header.length);
String End="\r\n";
byte[] end=End.getBytes("UTF-8");
System.arraycopy(end, 0, Buffer, Header.length+FileLen, end.length);
Client.getOutputStream().write(Buffer,0,Header.length+FileLen+end.length);
Client.getOutputStream().flush();
Open.close();
Client.close();
} catch (IOException e)
{
// TODO 自动生成的 catch 块
try {
String HeaderFormat=String.format(MinSend.header,"404 NOT FOUND","close",new Date().toString(),"");
byte[] Header=HeaderFormat.getBytes("UTF-8");
Client.getOutputStream().write(Header,0,Header.length);
Client.getOutputStream().flush();
Client.close();
} catch (UnsupportedEncodingException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}