资源执行架构……说的有一点儿玄了,实话实说,所谓的资源执行架构在最初仅仅是一群if……else语句而已,随着开发的进行它们变得越来越大,到最后实在是糟糕透顶。还记得上一篇的SocketProcess类么?最早它可是包含了执行方法的,很麻烦、很罗嗦、更要命的是不易扩展:
package server;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import jsp.JspConvertor;
import project.ProjectInfo;
import servlet.bean.ServletInfo;
import util.Constant;
import util.HttpError;
import util.LocalClassLoader;
import util.ServerMessage;
import webHttp.HttpRequestImpl;
import webHttp.HttpResponseImpl;
import webHttp.RequestHanler;
import webHttp.SessionPool;
/**
* 双线通信线程
* 解释: 双线通信指,既接收请求,又返回响应
* 相对应的还有 单向发送通讯 和 单向接受通讯
* 后两个可以用于jms等功能的实现
* 目前尚未考虑实现
* @author 刘宇航
*
*/
public class SocketProcess extends Thread {
ServerSocketChannel serverSocket = null;
/**
* 初始化一个线程
* @param serverSocket 必须保证双线通信
*/
public SocketProcess(ServerSocketChannel serverSocket) {
this.serverSocket = serverSocket;
}
/**
* 从serverSocket中获取数据
* 然后判断是资源文件还是servlet
* 最有发送正确的信息给客户端
*/
public void run() {
ByteBuffer buffer = ByteBuffer.allocate(1024 * 8);
SocketChannel channel = null;
try {
// 一些读取数据的工作
channel = serverSocket.accept();
if (channel != null && channel.read(buffer) > 0) {
String requestInfo = new String(buffer.array(), 0, buffer
.position());
HttpRequestImpl request = RequestHanler
.getHttpRequest(requestInfo);
AppRunTime runtime = AppRunTime.getInstance();
String[] uri = request.getContextPath().split(Constant.WEB_SP);
ProjectInfo project = runtime.getProjectMap().get(uri[1]);
if(project==null)return;
//绑定session
SessionPool sessionPool = SessionPool.getInstance(project.getId());
HttpSession session = sessionPool.getSession(channel.socket().getInetAddress().getHostAddress());
request.bind(session);
// 先尝试执行servlet
if(excuteServlet(channel, project, request)==false)
{
//判断是否是jsp
if(request.getServletPath().endsWith(Constant.FileType_SP_JSP))
excuteJsp(channel, project, request);
//否则的话按照普通资源进行解析
else
excuteResource(channel, project, request);
}
// 至少要反回空串
buffer.clear();
if (channel.isOpen()) {
buffer.put(" ".getBytes());
buffer.flip();
channel.write(buffer);
channel.finishConnect();
channel.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 编译并执行执行jsp资源
* @param channel 获得的非阻塞通道
* @param project 所属项目信息
* @param request 当前请求
*/
private void excuteJsp(SocketChannel channel, ProjectInfo project,
HttpServletRequest request) {
String fileName = RequestHanler.loadRealPath(request);
File jspFile = new File(fileName);
//如果jsp不存在的话
if(!jspFile.exists())
{
sendExceptionMessage(channel,HttpError.ERROR_404);
return;
}
JspConvertor convertor = new JspConvertor(jspFile,project);
try {
File servletFile = convertor.getCompiledServlet();
ClassLoader loader = LocalClassLoader.getInstance(servletFile.getParent());
HttpServlet servlet = (HttpServlet) loader.loadClass(servletFile.getName().replace(Constant.FileType_SP_Class, "")).newInstance();
HttpServletResponse response = new HttpResponseImpl(channel);
servlet.service(request, response);
} catch (Exception e) {
sendExceptionMessage(channel,HttpError.ERROR_404);
}
}
/**
* 向客户端浏览器报告错误,比如error404 page not found之类的
* @param channel
* @param message
*/
private void sendExceptionMessage(SocketChannel channel ,String message){
ByteBuffer buffer = ByteBuffer.allocate(1024 * 8);
buffer.put(HttpError.ERROR_404.getBytes());
buffer.flip();
try {
channel.write(buffer);
} catch (IOException e) {
System.err.println(ServerMessage.connection_exception);
}
buffer.clear();
}
/**
* 执行普通资源
*
* @param channel 获得的非阻塞通道
* @param project 所属项目信息
* @param request 当前请求
* @throws Exception 任何意外
*/
private void excuteResource(SocketChannel channel, ProjectInfo project, HttpServletRequest request) throws Exception {
ByteBuffer buffer = ByteBuffer.allocate(1024 * 8);
String fileName = RequestHanler.loadRealPath(request);
buffer.clear();
File file = new File(fileName);
if (file.exists() && file.canRead()) {
FileChannel fc = new FileInputStream(file).getChannel();
while (fc.read(buffer) > 0) {
buffer.flip();
channel.write(buffer);
buffer.clear();
}
} else {// 如果还是找不到资源,那么只能报404 page not found 错误了
sendExceptionMessage(channel,HttpError.ERROR_404);
}
}
/**
* 执行servlet资源
*
* @param channel 获得的非阻塞通道
* @param project 所属项目信息
* @param request 当前请求
* @return 是否已经有 servlet被执行
* @throws Exception if 任何意外
*/
private boolean excuteServlet(SocketChannel channel, ProjectInfo project,HttpServletRequest request) throws Exception {
for (ServletInfo info : project.getServletInfo()) {
//判断部分
Pattern p = Pattern.compile(Constant.WEB_SP+project.getWebPath()+info.getUrl_pattern());
Matcher m = p.matcher(request.getRequestURI());
if (m.matches()==false) continue;
//执行部分
ClassLoader loader = LocalClassLoader.getInstance(project .getPatch());
HttpServlet servlet = (HttpServlet) loader.loadClass( info.getServletClass()).newInstance();
HttpServletResponse response = new HttpResponseImpl(channel);
//先执行filter 然后是servlet本身
FilterChain chain = info.getFilterChain();
chain.doFilter(request, response);
servlet.init(info.getServletConfig());
servlet.service(request, response);
servlet.destroy();
return true;
}
return false;
}
}
这才是第一版的资源执行器,很凌乱吧
后来我把资源执行架构进行了重整,做了一个代理模式
这个架子由两个接口、一个代理类和若干实现类组成
第一个是执行器接口,实现了这个接口的类才能接受http请求并进行应答
public interface Executor {
/**
* 执行资源,这个方法只执行单向资源
* 如果日后需要扩展,比如添加返回值之类的
* 请另外写方法接口
* @param request 请求对象
* @param response 应答对象
*/
public void forward(HttpServletRequest request,HttpServletResponse response) throws Exception;
}
第二个接口是Executor 的子接口CheckableExecutor 实现了它的类可以进行判断是否可以执行
public interface CheckableExecutor extends Executor{
public void init(ProjectInfo project);
/**
* 判断是否可以执行该资源
* @return
*/
public boolean isExecutable(HttpServletRequest request);
}
再来是执行代理类,这个类用了个责任链模式,其实这个executorArray是可以配到配置文件里的,但是目前还没考虑那么远的事情,所以先这么放着了。不过想要扩展依然是比较容易的,实现一个CheckableExecutor 接口,再把类名填在executorArray数组中注册,就可以加入到执行序列里了
package executor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import project.ProjectInfo;
import executor.impl.JspExecutor;
import executor.impl.ResourceExecutor;
import executor.impl.ServletExecutor;
public class BaseExecutor implements Executor {
@SuppressWarnings("unchecked")
private Class<CheckableExecutor>[] executorArray = new Class[]{JspExecutor.class,ServletExecutor.class,ResourceExecutor.class};
//传给executor
private ProjectInfo project;
/**
* 构造方法
* @param project
*/
public BaseExecutor(ProjectInfo project){
this.project = project;
}
/**
* 获得相应的执行器实现
* 这个方法可以相应的扩展
* @param request
* @return
* @throws Exception
* @throws InstantiationException
*/
public Executor getExecutor(HttpServletRequest request) throws InstantiationException, Exception {
//选择执行器
for(Class<CheckableExecutor> exeClass:executorArray)
{
CheckableExecutor executor = exeClass.newInstance();
executor.init(project);
if(executor.isExecutable(request)) return executor;
}
return null;
}
/**
* 先找到执行器的实现,然后再执行资源
*/
@Override
public void forward(HttpServletRequest request,HttpServletResponse response) throws Exception{
Executor exe = getExecutor(request);
exe.forward(request, response);
}
}
接下来是三个实现类,由JspExecutor、ServletExecutor和ResourceExecutor组成
分别对应jsp资源、servlet资源和普通文件资源,但是前两个执行器涉及到环境外的类加载、jsp编译、系统数据结构等诸多问题,我会放到稍后的章节
下面是普通的资源执行器
package executor.impl;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import project.ProjectInfo;
import util.HttpError;
import webHttp.RequestHanler;
import exceptions.HttpException;
import executor.CheckableExecutor;
public class ResourceExecutor implements CheckableExecutor {
public void init(ProjectInfo project) {
//什么都不做
}
/**
* 执行普通资源
*/
@Override
public void forward(HttpServletRequest request, HttpServletResponse response)
throws Exception {
ByteBuffer buffer = ByteBuffer.allocate(1024 * 8);
String fileName = RequestHanler.loadRealPath(request);
buffer.clear();
File file = new File(fileName);
if (file.exists() && file.canRead()) {
FileChannel fc = new FileInputStream(file).getChannel();
while (fc.read(buffer) > 0) {
buffer.flip();
response.getOutputStream().write(buffer.array());
buffer.clear();
}
} else {// 如果还是找不到资源,那么只能报404 page not found 错误了
throw new HttpException(HttpError.ERROR_404);
}
}
@Override
/**
* 返回这个执行器是否可以执行
* ResourceExecutor,是所有执行器的最后一步,并且包含了容错,所以当然是可以执行的
*/
public boolean isExecutable(HttpServletRequest request) {
return true;
}
}
我review了一下,除了“String fileName = RequestHanler.loadRealPath(request);”这一句外,其他的应该都没有疑问。至于这一句,就是从request url到文件资源的映射,一开始的时候仅仅是做简单的文件夹路径映射就好,后来我把它跟项目装载器ProjectDeployer做了一些关联,不过这个实现不重要,大家应该看得懂。
那么,有了这几了类,我们的油炸糕应该就可以进行一些简单的应答了……
有兴趣的朋友可以自己写一个试试,其实这一步,并不难做到,难的是后面……
呵呵,下期会介绍远端classLoader的写法和server的数据结构,慢慢来