将sax解析xml以及http的请求与响应协议结合的小项目:
项目结构图分析:
1.Server01类:
创建服务器serverSocket
多线程调用分发类Dispatcher,重复接收客户端请求协议并返回
关闭客户端
public class Server01 {
private ServerSocket serverSocket;
private boolean isrunning;//分发器控制标志
public static void main(String[] args) {
Server01 s = new Server01();
s.start();
}
public void start() {
try {
serverSocket = new ServerSocket(8888);
isrunning=true;
Serverrunning();//建立连接
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务器启动失败");
stop();
}
}
public void Serverrunning() {
while(isrunning){
try {
Socket client = serverSocket.accept();
System.out.println("一个客户端建立了连接");
new Thread(new Dispatcher(client)).start();
} catch (IOException e) {
e.printStackTrace();
System.out.println("客户端连接失败");
}
}}
public void stop(){
isrunning=false;
try {
serverSocket.close();
System.out.println("服务器关闭");
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.Dispatcher类
该类继承Runnable接口,实现多线程
该类里面创建好两个封装的类Request,Response类进行对接收请求协议,以及包装发送响应协议
再通过Webapp类解析xml协议,再利用反射机制,得到对应的Servlet类,进行响应协议正文的包装。
public class Dispatcher implements Runnable {
private Socket client;
private Request r1;
private Response r2;
public Dispatcher(Socket client){//传入服务器接收的插座
this.client=client;
try {
r1=new Request(client);
r2=new Response(client);//文件正文
} catch (IOException e) {
e.printStackTrace();
release();//如果创建请求协议失败,直接释放资源
}
}
@Override
public void run() {
try {
Servlet servlet = Webapp.getServletfromXML(r1.getUrl());
if (servlet != null) {//如果找到xml文件的类
servlet.service(r1, r2);//通过反射得到的servlet方法,调用指定函数,构建响应正文
r2.pushToBrowser(200);//构建响应协议成功
} else {
r2.pushToBrowser(404);//若在xml文件里找不到类,则404错误}
}
} catch (IOException e) {
try {
r2.pushToBrowser(505);//服务器错误
} catch (IOException ex) {
ex.printStackTrace();
}
}
release();//响应一次就释放一次资源(短连接),否则Webapp类被一个线程一直占用,无法连续接收客户端请求
}
private void release(){
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.封装的Request类
接收请求协议
/*封装请求信息
* 获取方法(GET/POST等),URL以及请求参数*/
public class Request {
private String requestinfo;
private String method;//方法
private String Url;
private String queryStr;//请求参数
private Map<String, List<String>> parameterMap;//封装请求参数,因为可能一个key值要有不同的value值,所有value是个容器
public Request(InputStream is){
parameterMap=new HashMap<String, List<String>>();
byte[] datas = new byte[1024 * 10 * 10];
try {
is.read(datas);
this.requestinfo = new String(datas, 0, datas.length);
System.out.println(requestinfo);
} catch (IOException e) {
e.printStackTrace();
}
}
public Request(Socket client) throws IOException {
this(client.getInputStream());//构造器相互调用
parseRequest();
}
private void parseRequest(){
System.out.println("获取请求方式-----------------------------");
int idx1=requestinfo.indexOf("/");
this.method=requestinfo.substring(0,idx1).trim();
System.out.println(method);
System.out.println("获取请求URL-----------------------------");
int idx2=requestinfo.indexOf("HTTP");
int idx3=requestinfo.indexOf("?");
if(idx3>0&&idx3<idx2){
Url=requestinfo.substring(idx1+1,idx3).trim();}else {Url=requestinfo.substring(idx1+1,idx2).trim();}
System.out.println(Url);
System.out.println("获取请求参数-----------------------------");
if(idx3>0&&idx3<idx2){
queryStr=requestinfo.substring(idx3+1,idx2).trim();}else{queryStr=null;}
if(method.equals("POST")){
queryStr=requestinfo.substring(requestinfo.lastIndexOf("\r\n")).trim();
}
System.out.println(queryStr);
if(queryStr!=null) convertMap();
}
private void convertMap(){//处理请求参数,将其转换为Map
//请求参数格式:name1=curry&name2=Ayesha
String[] keyValues=this.queryStr.split("&");//以&符号分割为name1=curry
for(String kv:keyValues){//以=号分割出key与value
String kv1[]=kv.split("=");
String key=kv1[0];//等号前面为key
String value=kv1[1];//等号后为value
//System.out.println(key+"----"+value);
if(!parameterMap.containsKey(key)){
parameterMap.put(key,new ArrayList<String>());//第一次出现当前key值
}
parameterMap.get(key).add(value);//get(key)得到容器v,往里面添值
}
}
public String[] getParameterValues(String key){
List<String> list=parameterMap.get(key);
if(list==null){return null;}
return list.toArray(new String[0]);
}
public String getParameterSingleValues(String key){
String[] s=getParameterValues(key);
return s==null?null:s[0];
}
public String getMethod() {//返回方法名(post/get)
return method;
}
public String getUrl() {//返回URL
return Url;
}
public String getQueryStr() {//返回请求参数
return queryStr;
}
}
4.封装的Response类:
对外界有个公共方法print,可以传入响应协议内容
内部构建好响应协议头
/*封装返回响应协议*/
public class Response {
private BufferedWriter bw;
private StringBuilder headInfo;
private final String BLANK=" ";
private final String CRLF="\r\n";
private long size=0;//协议正文大小
private StringBuilder content;//协议正文
private Response(){
headInfo=new StringBuilder();
content=new StringBuilder();
}
public Response(Socket client) {
this();//重载无参构造函数
try {
bw=new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
} catch (IOException e) {
e.printStackTrace();
}
}
public void createHeadInfo(int code){//构造响应协议头
headInfo.append("HTTP/1.1").append(BLANK);
headInfo.append(code).append(BLANK);
switch (code){
case 200: headInfo.append("OK").append(CRLF);
break;
case 404: headInfo.append("NOT FOUND").append(CRLF);
break;
case 505: headInfo.append("SERVER ERROR").append(CRLF);
break;
}
//响应头(最后一行有空行)
headInfo.append("Date:").append(new Date()).append(CRLF);
headInfo.append("Server:").append("WebServer02/0.0.1;charset=UTF8").append(CRLF);
headInfo.append("Content-type:text/html").append(CRLF);
headInfo.append("Content-length:").append(size).append(CRLF);
headInfo.append(CRLF);//与正文直接有个空行
}
public Response print(String info){//传入响应协议正文,流操作,返回自身则可以 print.print反复依次调用
content.append(info);
size+=info.getBytes().length;
return this;//返回本身实例
}
public Response println(String info){//换行的传入正文操作
content.append(info).append(CRLF);
size+=(info+CRLF).getBytes().length;
return this;//返回本身实例
}
public void pushToBrowser(int code) throws IOException {
createHeadInfo(code);//传入状态码,生产响应协议头
bw.append(headInfo);//Writer内的方法
bw.append(content);
bw.flush();
}
}
5.Webapp类
内部静态块通过sax分析xml配置文件,为了进行反射
与webcontext类关联,webcontext处理配置文件内的数据,封装成容器。之前的博客内有相关内容
public class Webapp {
private static Webcontext context;
static{
SAXParserFactory factory=SAXParserFactory.newInstance();
SAXParser Parse= null;
try {
Parse = factory.newSAXParser();
Handler1 handler=new Handler1();
Parse.parse(Thread.currentThread().getContextClassLoader().getResourceAsStream("cn/xjh3/Net/WebServer02/web.xml"),handler);
context=new Webcontext(handler.getEntitys(),handler.getMappings());
} catch (Exception e) {
e.printStackTrace();
}
}
public static Servlet getServletfromXML(String url) {//上下文类处理Handler得到的数据
if(context==null){
System.out.println("context is null");
}
String classname= context.getclz("/"+url);
Class clz= null;
try {
clz = Class.forName(classname);
Servlet servlet=(Servlet) clz.getConstructor().newInstance();//使用反射得到类名
return servlet;
} catch (Exception e) {
}
return null;
}
}
class Handler1 extends DefaultHandler {//继承DefaultHandler类,必要条件
private List<Entity> Entitys=new ArrayList<>();//定义对象容器
private List<Mapping> Mappings=new ArrayList<>();
private Mapping mapping;//将xml的内容转为对象型数据
private Entity entity;
private String tag;
private boolean ismapping=false;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
// System.out.println(qName+"---->解析开始");
tag=qName;
if(qName=="servlet"){
entity=new Entity();
}else if(qName=="servlet-mapping"){
mapping=new Mapping();
ismapping=true;//读取servlet-mapping
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
// System.out.println(qName+"---->解析结束");
if(qName.equals("servlet")){Entitys.add(entity);}//把类加入容器内
if(qName.equals("servlet-mapping")){Mappings.add(mapping);}
tag=null;
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if(tag!=null){//处理了,解析完一个标题后空内容的情况
String contents=new String(ch,start,length).trim();//trim去掉空
if(ismapping){//操作servlet-mapping
if(tag=="servlet-name"){mapping.setName(contents);}
else if(tag=="url-pattern"){mapping.addPatterns(contents);}
}else {//操作servlet
if(tag=="servlet-name"){entity.setName(contents);}
else if(tag=="servlet-class"){entity.setClz(contents);}
}
}}
@Override
public void startDocument() throws SAXException {
System.out.println("开始解析xml");
}
public List<Entity> getEntitys() {
return Entitys;
}
public List<Mapping> getMappings() {
return Mappings;
}
}
6.webcontext类
上下文类用于对webapp类解析xml得到的数据进行处理,将其一 一转为键值对的形式存储
public class Webcontext {
private List<Entity> E=null;
private List<Mapping> M=null;
private Map<String,String> EntityMap=new HashMap<>();//KEY->Entity里的name,VALUE-》Entity里的class
private Map<String,String> MappingMap=new HashMap<>();//KEY->Mapping里的pattern,VALUE-》Mapping里的name
public Webcontext(List<Entity> E, List<Mapping> M) {
this.E = E;
this.M = M;
for(Entity e:E){
EntityMap.put(e.getName(),e.getClz());//将Handler得到的entity list转换为map
}
for(Mapping m:M){
for(String pattern:m.getPatterns()){
MappingMap.put(pattern,m.getName());//将得到的mapping list转换为map,因为mapping内还有一个set容器,双重循环
}
}
}
//通过URL路径找到class:由url-pattern找到servlet-name再找到servlet-class
public String getclz(String pattern){//得到类名
String name=MappingMap.get(pattern);
return EntityMap.get(name);
}
}
7.Entity以及Mapping类
用于将xml内的文件分块,分为两种数据类型:
Entity:对应xml文件内的块
public class Entity {
private String name;
private String clz;
public Entity() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClz() {
return clz;
}
public void setClz(String clz) {
this.clz = clz;
}
}
Mapping:对应xml文件内的 块
public class Mapping {
private String name;
private Set<String > patterns;
public Mapping(){
patterns=new HashSet<String>();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<String> getPatterns() {
return patterns;
}
public void setPatterns(Set<String> patterns) {
this.patterns = patterns;
}
public void addPatterns(String pattern){
this.patterns.add(pattern);//便于操作
}
}
xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>login</servlet-name>
<servlet-class> cn.xjh3.Net.WebServer02.loginServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>reg</servlet-name>
<servlet-class> cn.xjh3.Net.WebServer02.registerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
<url-pattern>/g</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>reg</servlet-name>
<url-pattern>/reg</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>reg</servlet-name>
<url-pattern>/re</url-pattern>
</servlet-mapping>
</web-app>
关键的后端处理部分:
8.Servlet接口以及相关类
定义一个Servlet接口,每个业务有不同的类构成,这些类都继承Servlet接口。每个类根据请求协议的URL的不同,进行不同的操作实现。
例如:写一个登录操作类loginServlet,继承Servlet接口
请求协议的URL为login,解析xml,反射得到类loginServlet,调用。
/*服务器小脚本接口,解耦了业务代码(登录/注册等业务)*/
public interface Servlet {
void service(Request r1,Response r2) throws IOException;
}
继承Servlet接口的类:loginServlet
public class loginServlet implements Servlet{
/*登录小脚本*/
@Override
public void service(Request r1, Response r2) throws IOException {
r2.print("<html>");
r2.println("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />");//这一行很重要,可以强制让浏览器按UTF-8格式显示!
r2.print("<head>");
r2.print("<title>");
r2.print("服务器响应成功");
r2.print("服务器小脚本1");
r2.print("</title>");
r2.print("</head>");
r2.print("<body>");
r2.print("欢迎回来:"+r1.getParameterSingleValues("uname"));
r2.print("</body>");
r2.print("</html>");
System.out.println("-----------响应结束------------"+"\r\n");
}
}
继承Servlet接口的类:registerServlet
当请求协议的URL为register,反射得到该类,响应回注册的页面
public class registerServlet implements Servlet {
@Override
public void service(Request r1, Response r2) {
r2.print("注册成功");
}
}
最后一个小例子:
首先打开这个登录的html页面:
html代码:
<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<head>
<title>html登录</title>
</head>
<body>
<form method="post" action="http://localhost:8888/login">
用户名:<input type="text" name="uname" id="uname"/>
密码:<input type="password" name="pwd" id="pwd"/>
<input type="submit" value="登录"/>
</form>
</body>
</html>
点击登录,服务器得到其请求协议,得到该请求协议的URL值以及请求参数,URL值为为login,请求参数为输入的用户名与密码,调用loginServlet函数,返回响应协议。