Servlet原理解析:手写一个web简易服务器(思路及代码实现)(一)
思路
1、创建一个服务器
用ServerSocket绑定一个端口创建服务器
/**
*在本地创建一个服务器
*main函数入口
*/
public class servSocket {
private ServerSocket serSocket;
private boolean isRunniang = true;
public static void main(String[] args) {
servSocket s = new servSocket();
s.start();
s.receive();
}
//启动服务器
public void start(){
try {
serSocket = new ServerSocket(8888);
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务器启动失败");
}
}
public void receive(){
while(isRunniang){
try {
Socket client = serSocket.accept();
System.out.println("客户端建立了链接");
} catch (IOException e) {
e.printStackTrace();
System.out.println("客户端出错");
end();
}
}
}
//关闭服务器
public void end(){
isRunniang = false;
try {
serSocket.close();
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务器关闭出错");
}
}
}
2、思考如何通信
得到了一个服务器之后,怎么得到浏览器发来的消息呢,又如何给浏览器发送消息呢?
根据网络编程的知识,我们知道,服务器的accept函数会返回一个Socket对象,封装了客户端的所有信息,这样的话我们根据得到的client引用,获得输入流和输出流就可以和浏览器进行消息的传递。
得到浏览器的请求信息,给浏览器发送信息
public void receive(){
while(isRunniang){
try {
Socket client = serSocket.accept();
System.out.println("客户端建立了链接");
//根据client得到输入输出流
InputStream is = client.getInputStream();//输入流
OutputStream os = client.getOutStream();//输出流
//获得浏览器的请求信息
byte[] datas = new bytes[1024*1024];//为方便起见,直接创建一个足够大的容器来接收请求信息
int len = is.read(datas);//请求信息的字节长度
String requestInfo = new String(datas,0,len);//转换成字符串,requestInfo即为浏览器发送的请求信息
System.out.println(requestInfo); //打印查看浏览器发送的请求信息
//给浏览器发送信息,-------->比较复杂,要写响应头和一个html页面
StringBuffer header = new StringBuffer(); ///构建响应头
StringBuffer content= new StringBuffer(); ///构建响应体(静态HTML页面)
//响应体
content.append("<html>");
content.append("<head>");
content.append("<title>");
content.append("构建的响应信息");
content.append("</title>");
content.append("</head>");
content.append("<body>");
content.append("你好,浏览器");
content.append("</body>");
content.append("</html>");
int size = content.toString().getBytes().length; //得到字节的长度(content是字符型的,字符长度和字节是不同的,所以不能直接用content.length())
//响应头
String BLANK = " "; //空格
String CRLF = "\r\n"; //换行
header.append("HTTP/1.1").append(BLANK);
header.append(200).append(BLANK);
header.append("OK").append(CRLF);
header.append("Date:").append(new Date()).append(CRLF);
header.append("Server:").append("shsxt Server/0.01;charset=GBK").append(CRLF);
header.append("Content-type:").append("text/html").append(CRLF);
header.append("Content-length:").append(size).append(CRLF);
header.append(CRLF);
//构成完整的响应协议
header.append(content);
//发送给浏览器
os.write(header.toString().getBytes());
os.flush();
} catch (Exception e) { //捕捉所有异常
e.printStackTrace();
System.out.println("客户端出错");
end();
}
}
}
3、封装请求信息和响应信息
从上面的代码我们知道了服务器和浏览器如何进行通信,但是我们可以发现,请求信息和响应信息可以被封装在对象里面,我们通过管理对象就可以管理请求信息和响应信息。
封装Request对象
//封装的request对象
package HttpSever.core;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
/**
* 用于封装HttpRrquest请求
* @author Administrator
*
*/
public class Request {
private Socket client;
private String METHOD;
private String URL;
private String query;
private String requestInfo;
private InputStream is;
public Request(){
}
public Request(Socket client){
this.client = client;
getRequestInfo();
parseInfo();
}
//分解参数
private void parseInfo(){
//获取请求方法
this.METHOD = this.requestInfo.substring(0,this.requestInfo.indexOf("/")).trim().toLowerCase();
//获取虚拟URL
//1、url不带参数
int startIndex = this.requestInfo.indexOf("/")+1;
int endIndex = this.requestInfo.indexOf("HTTP/");
this.URL = this.requestInfo.substring(startIndex,endIndex).trim();
//2、url带参数
if(this.URL.contains("?")){
String[] urlAndPar = this.URL.split("\\?");
this.URL = urlAndPar[0].length()==0?null:urlAndPar[0]; //null或有值
if(urlAndPar.length==1)
{
this.query=null;
}else
this.query = urlAndPar[1].length()==0?null:urlAndPar[1]; //null或有值
}
//post方法中请求体的参数
if(this.METHOD.equals("post")){
int idx = this.requestInfo.lastIndexOf("\r\n");
String postQuery = this.requestInfo.substring(idx).trim().length()==0?null:this.requestInfo.substring(idx).trim(); //null或有值
if(postQuery!=null){
if(this.query==null)
this.query = postQuery;
else
this.query+="&"+postQuery;
}
}
}
private void getRequestInfo(){
try {
is = this.client.getInputStream();
} catch (IOException e) {
e.printStackTrace();
System.out.println("reques输入流获取失败。。。。。。");
}
byte[] datas = new byte[1024*1024];//为方便起见,直接创建一个够大的数组
int len =-1;
try {
len = is.read(datas);
} catch (IOException e) {
e.printStackTrace();
System.out.println("request请求数据接收异常。。。。。。");
}
requestInfo = new String(datas,0,len);
}
public String getMETHOD() {
return METHOD;
}
public String getURL() {
return URL;
}
public String getQuery() {
return query;
}
}
封装Response对象
//封装Response
package HttpSever.core;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Date;
/**
* 用于封装HttpResponse请求
* 通过该对象,我们不用再关心响应头,只用关心想要输出的响应体(HTML)和状态码即可
* @author Administrator
*
*/
public class Response {
private int size;
private StringBuilder header;
private StringBuilder content;
private Socket client;
private OutputStream os;
private final String BLANK = " ";
private final String CRLF = "\r\n";
public Response(){
size = 0;
header = new StringBuilder();
content = new StringBuilder();
}
public Response(Socket client){
this();
this.client = client;
try {
os = this.client.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
System.out.println("response输出了获取失败。。。。。。");
}
}
/**
*该方法用于向响应体中动态添加内容
*/
public Response print(String info){
content.append(info);
this.size+=info.getBytes().length;
return this;
}
/**
*该方法用于向浏览器输出封装好的response
*/
public void pushToBro(int status){
createHeader(status);
header.append(content.toString());
try {
os.write(header.toString().getBytes());
os.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("response输出出错。。。。。。。");
}
}
/**
*私有方法,用来构建头信息
*/
private void createHeader(int status){
header.append("HTTP/1.1").append(BLANK);
header.append(status).append(BLANK);
switch(status){
case 200:header.append("OK").append(CRLF);break;
case 404:header.append("NOT FOUND").append(CRLF);break;
case 505:header.append("SERVER ERROR").append(CRLF);break;
}
header.append("Date:").append(new Date()).append(CRLF);
header.append("Server:").append("shsxt Server/0.01;charset=GBK").append(CRLF);
header.append("Content-type:").append("text/html").append(CRLF);
header.append("Content-length:").append(size).append(CRLF);
header.append(CRLF);
}
}