本文将从手写一个微型服务器,模拟实现TOMCAT服务器的运行过程及原理,废话不多说,先看整个项目的类架构。
本文需具有以下预备知识:
1.Socket编程 2.HTML基础知识 3.HTTP协议相关知识 4.反射及容器集合框架的相关知识
第一步:
我们先要按上图的包类关系,把整个服务器的项目框架搭建好,然后编写XML文档,最后写IOCloseUtil.java工具类。以下是XML文档和IOCloseUtil类的源码。
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>login</servlet-name>
<serlvet-class>com.bjsxt.servlet.LoginServlet</serlvet-class>
</servlet>
<servlet-mapping>
<serlvet-name>login</serlvet-name>
<url-pattern>/login</url-pattern>
<url-pattern>/log</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>register</servlet-name>
<serlvet-class>com.bjsxt.servlet.RegisterServlet</serlvet-class>
</servlet>
<servlet-mapping>
<serlvet-name>register</serlvet-name>
<url-pattern>/reg</url-pattern>
<url-pattern>/register</url-pattern>
<url-pattern>/regis</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>favicon</servlet-name>
<serlvet-class>com.bjsxt.servlet.FaviconServlet</serlvet-class>
</servlet>
<servlet-mapping>
<serlvet-name>favicon</serlvet-name>
<url-pattern>/favicon.ico</url-pattern>
</servlet-mapping>
</web-app>
IOCloseUtil.java:
package com.bjsxt.util;
import java.io.Closeable;
import java.io.IOException;
public class IOCloseUtil {//用于关闭流
public static void closeAll(Closeable...close){
for (Closeable closeable : close) {
if (closeable!=null) {
try {
closeable.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
第二步:
服务器端必须要有处理请求的功能和返回结果的功能,那我们就先完成Request.java和Response.java这两个类。
package com.bjsxt.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Request {/*请求*/
private InputStream is;//输入流
private String requestInfo;//请求字符串,请求方式,请求的路径,参数,协议,协议版本,请求的正文。。。
private String method;//请求的方式
private String url;//请求的url
public String getUrl() {
return url;
}
//输入框的name为key,值为value
/*
* key: username value :bjsxt
* key:pwd value:123
* key:hobby value :read,ball
* */
private Map<String,List<String>> parametermapValues;//参数
private static final String CRLF="rn";//换行
private static final String BLANK=" ";//空格
//构造方法,初始化属性
public Request() {
parametermapValues=new HashMap<String,List<String>>();
method="";
url="";
requestInfo="";
}
public Request(InputStream is){
this();//调用本类无参的构造方法
this.is=is;
try {
byte [] buf=new byte[20480];
int len=this.is.read(buf);
requestInfo=new String(buf,0,len);
} catch (IOException e) {
return;
}
//调用本类中的分解请求信息的方法
this.parseRequestInfo();
}
//分解请求信息的方法
/**
* 请求方式
* 请求路径
* 请求的参数
*
*/
private void parseRequestInfo(){
String paraString="";//用于存储请求参数
//获取请求参数的第一行
String firstLine=requestInfo.substring(0, requestInfo.indexOf(CRLF)).trim();//从0开始,到第一个换行的位置
//分解出请求方式
int index=firstLine.indexOf("/");
this.method=firstLine.substring(0, index).trim();
//分解url ,get可能包含参数,也可能不包含参数post
String urlString= firstLine.substring(index,firstLine.indexOf("HTTP/")).trim();
//判断请求方式是GET还 是POST
if("get".equalsIgnoreCase(this.method)){ //包含请求参数
if (urlString.contains("?")) {
String [] urlArray=urlString.split("?");
this.url=urlArray[0];
paraString=urlArray[1];
}else{
this.url=urlString;
}
}else{//post不包含请求参数
this.url=urlString;
paraString=requestInfo.substring(requestInfo.lastIndexOf(CRLF)).trim();
}
if (paraString.equals("")) {
return;
}
//请求参数
//System.out.println(paraString);
//调用本类的中的分析请求参数的方法
this.parseParam(paraString);
}
//用于测试
/*public void show(){
System.out.println(this.url);
System.out.println(this.method);
}*/
//username=fdsaf&pwd=fdasf&hobby=ball&hobby=read
/**
* username=bjsxt
* pwd=123
* hobby=ball
* hobby=paint
*
* username=
* @param prarString
*/
private void parseParam(String paramString){
String [] token=paramString.split("&");
for(int i=0;i<token.length;i++){
String keyValues=token[i]; //username=fasaf
//继续分割
String[] keyValue=keyValues.split("="); //username=
if (keyValue.length==1) {
keyValue=Arrays.copyOf(keyValue, 2);
keyValue[1]=null;
}
//转成Map集合
String key=keyValue[0].trim();
String value=keyValue[1]==null?null:decode(keyValue[1].trim(), "utf-8");
//放到参数的集合中存储
if (!parametermapValues.containsKey(key)) {
parametermapValues.put(key, new ArrayList<String>());
}
List<String> values=parametermapValues.get(key);
values.add(value);
}
}
/**
* 编写根据表单元素的name获取多个对应的值
*
*/
public String [] getParamterValues(String name){
List<String> values=parametermapValues.get(name);
if (values==null) {
return null;
}else{
return values.toArray(new String [0]);
}
}
/**
* 根据表单元素的name获取单个值
*
*/
public String getParameter(String name){
//调用根据名称获取多个值的方法
String [] values=getParamterValues(name);
if (values==null) {
return null;
}else{
return values[0];
}
}
//用于测试
public static void main(String[] args) {
Request req=new Request();
//调用分解参数的方法
req.parseParam("username=%E5%8C%97%E4%BA%AC%E5%B0%9A%E5%AD%A6%E5%A0%82&pwd=123&hobby=ball&hobby=paint");
System.out.println(req.parametermapValues);
//调用获取多个值的方法
String [] str=req.getParamterValues("hobby");
for (String string : str) {
System.out.println(string);
}
//调用获取单个值的方法
System.out.println(req.getParameter("pwd"));
}
//处理中文,因为浏览器对中文进行了编码,进行解码
private String decode(String value, String code){
try {
return URLDecoder.decode(value,code);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
Response.java:
package com.bjsxt.server;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import com.bjsxt.util.IOCloseUtil;
public class Response {//响应
private StringBuilder headInfo;//响应头
private StringBuilder content;//响应内容
private int length;//响应内容的长度
//流
private BufferedWriter bw;
//两个常量,换行和空格
private static final String CRLF="rn";//换行
private static final String BLANK=" ";//空格
//构造方法
public Response() {
headInfo=new StringBuilder();
content=new StringBuilder();
}
//带参构造方法
public Response(OutputStream os){
this();//调用本类的无参构造方法
try {
bw=new BufferedWriter(new OutputStreamWriter(os, "utf-8"));
} catch (UnsupportedEncodingException e) {
headInfo=null;
}
}
//构造正文部分
public Response print(String info){
content.append(info);
try {
length+=info.getBytes("utf-8").length;
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return this;
}
public Response println(String info){
content.append(info).append(CRLF);
try {
length+=(info+CRLF).getBytes("utf-8").length;
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return this;
}
//构造响应头
private void createHeadInfo(int code){
headInfo.append("HTTP/1.1").append(BLANK).append(code).append(BLANK);
switch (code) {
case 200:
headInfo.append("OK");
break;
case 500:
headInfo.append("SERVER ERROR");
break;
default:
headInfo.append("NOT FOUND");
break;
}
headInfo.append(CRLF);
headInfo.append("Content-Type:text/html;charset=utf-8").append(CRLF);
headInfo.append("Content-Length:"+length).append(CRLF);
headInfo.append(CRLF);
}
/**
* 推送到客户机的浏览器
* @param code
*/
public void pushToClient(int code){
if (headInfo==null) {
code=500;
}
try {
//调用本类中的构造响应头
this.createHeadInfo(code);
bw.write(headInfo.toString());
bw.write(content.toString());
bw.flush();
this.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void close(){
IOCloseUtil.closeAll(bw);
}
}
第三步:
实现Entity类,对应XML文件中的/*servlet-name和一个servlet-name所对应的*/一个实体类。
package com.bjsxt.server;
/**
* <servlet>
<servlet-name>login</servlet-name>
<serlvet-class>com.bjsxt.servlet.LoginServlet</serlvet-class>
</servlet>
* @author Administrator
*
*/
public class Entity { /**servlet-name和一个servlet-name所对应的一个实体类*/
private String name;//servlet-name
private String clazz;//servlet-class
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClazz() {
return clazz;
}
public void setClazz(String clazz) {
this.clazz = clazz;
}
public Entity(String name, String clazz) {
super();
this.name = name;
this.clazz = clazz;
}
public Entity() {
super();
}
}
鉴于篇幅有限,剩下的将于明天继续写出。