//CilentHandler 类
public class CilentHandler implements Runnable{
private Socket socket ;
public CilentHandler(Socket socket) {
this.socket =socket;
}
@Override
public void run() {
try {
/*1.解析请求*/
HttpRequest request = new HttpRequest(socket);
//创建响应对象
HttpResponse response = new HttpResponse(socket);
//2、处理请求
//2.1 根据request获取资源的抽象路径
String path = request.getRequestURL();
//2.2根据请求路径判断是否为请求一个业务操作
HttpServlet servlet = ServletContext.getServlet(path);
if(servlet!=null){
servlet.service(request, response);
}else{
//2.2 根据资源路径去webapps目录中寻找该资源
File file = new File("webapps"+path);
if (file.exists()) {
System.out.println("找到该资源");
//响应客户端资源
//发送一个标准的HTTP响应给客户端,响应该资源
//将响应资源设置到response中
response.setEntity(file);
}else {
System.out.println("该资源不存在");
response.setStatusCode(404);
response.setStatusReasom("NOT FOUND");
File fileNotFound = new File("webapps/root/404.html");
response.setEntity(fileNotFound);
}
}
//3.发送响应
//1.发送状态行
//2.发送响应头
//3.发送响应正文
response.flush();
System.out.println("响应完毕");
}catch (EmptyRequestexception e){
System.out.println("空请求");
}catch (Exception e) {
e.printStackTrace();
}finally{
//处理完请求并响应浏览器后断开连接
try {
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
webServer 类
//
public class WebServer {
private ServerSocket server;
public WebServer() {
try {
System.out.println("正在启动服务器");
server = new ServerSocket(8088);
System.out.println("服务器启动完毕");
} catch (IOException e) {
e.printStackTrace();
}
}
public void start(){
try {
while(true){
System.out.println("等待客户端连接");
Socket socket = server.accept();
System.out.println("一个客户端已连接");
CilentHandler handler = new CilentHandler(socket);
Thread t = new Thread(handler);
t.start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
WebServer webserver = new WebServer();
webserver.start();
}
}
HttpRequest 类
public class HttpRequest {
/*
* 请求行相关信息定义
*/
// 请求方式
private String method;
// 请求的抽象路径部分
private String url;
// 请求使用的HTTP协议版本
private String protocol;
/*
* 由于请求行中抽象部分因客户端请求方式不同会有不同的内容 1:不带参数抽象路径 /myweb/index.xml
* 2:form表单GET形式提交,抽象路径就带参数 /myweb/reg?username=***&passworld=**
*
*/
// 抽象路径中的请求部分。 ?左侧内容
private String requestURL;
// 抽象路径的参数部分。 ?右侧内容
private String queryString;
// 保存具体一组参数
private Map<String, String> parameters = new HashMap<>();
public String getMethod() {
return method;
}
public String getUrl() {
return url;
}
public String getProtocol() {
return protocol;
}
/*
* 根据消息头的名字获取消息头对应的值
*/
public String getHeaders(String key) {
/*
* 这里的设计没有直接将headers这个Map对外返回, 避免外界拿到这个Map之后可以任意操作, 这样破坏了当前属性的封装性。
* 实际开发中,很多这种集合,Map的属性都不对外返回
*/
return headers.get(key);
}
/*
* 消息头相关信息定义
*/
// key:消息头的名字 value:消息头对应的值
private Map<String, String> headers = new HashMap<String, String>();
/*
* 消息正文相关信息定义
*/
/*
* 与连接相关的属性
*/
private Socket socket;
private InputStream in;
/**
* 构造方法,用来初始化请求对象 初始化的过程就是解析请求的过程
*/
public HttpRequest(Socket socket) throws EmptyRequestexception {
try {
this.socket = socket;
/*
* 通过socket获取输入流,用于读取客户端(浏览器) 发送过来的请求内容
*/
this.in = socket.getInputStream();
/*
* 解析请求分为三步: 1、解析请求内容 2、解析消息头内容 3、解析消息正文内容
*
*/
parseRequestLine();
parseHeaders();
parseContent();
} catch (EmptyRequestexception e) {
throw e;
} catch (Exception e) {
e.printStackTrace();
}
}
/* 解析请求行 */
private void parseRequestLine() throws EmptyRequestexception {
System.out.println("开始解析请求行...");
try {
String line = readLine();
if ("".equals(line)) {
throw new EmptyRequestexception();
}
System.out.println("请求行:" + line);
/*
* 将请求行的内容按空格拆分为三部分 分别设置到属性:method,url,protocol
*/
// 以空格为分割
String[] array = line.split("\\s+");
method = array[0];
url = array[1];// 这里可能出现下标越界,原因是空请求引起的
protocol = array[2];
// 进一步解析抽象部分
parseURL();
System.out.println("method:" + method);
System.out.println("URL:" + url);
System.out.println("protocol:" + protocol);
} catch (EmptyRequestexception e) {
throw e;
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("解析请求行完毕");
}
/*
* 解析参数
* 参数的格式应当为:username=zhangsan&nickname=fffff&age=23&
*/
private void parseParameters(String line ){
String [] data = line.split("&");
for (String para : data) {
String[] arr = para.split("=");
if (arr.length > 1) {
parameters.put(arr[0], arr[1]);
} else {
parameters.put(arr[0], null);
}
}
}
/*
* 进一步解析请求行中的抽象路径
*/
private void parseURL() {
System.out.println("进一步解析抽象路径部分.......");
/*
* 在解析url时,首先判断该抽象路径是否含有参数 标志就是有没有"?" 如果没有则说明是没有参数,那么直接将当前抽象路径
* url的值赋给requestURL即可
*
* 如果有参数,则首先按照"?"将抽象路径拆分为两个部分: 第一部分:请求部分,赋值给requestURL,第二部分是
* 参数部分,就是把参数赋值给querString 然后对querString部分进行进一步拆分 按照&先拆分出每一个参数,然后每个参数再按照
* =拆分为参数名与参数值,然后将参数名设为key,将参数值设为value 保存到parameters这个Map周完成解析工作
*/
if (url.contains("?")) {
// 有参数
String[] data = url.split("\\?");
requestURL = data[0];
// 判断“?”右侧确实包含参数部分
if (data.length > 1) {
queryString = data[1];
try {
//对queryString解锁
/*
* String decode(String str,String enc)
* 将给定的字符串中含有“%xx”的内容按照指定的字符集还原对应字符串并替换这些“%xx”
* 然后将替换后的字符串返回
*/
queryString =URLDecoder.decode(queryString,"utf-8");
} catch (UnsupportedEncodingException e) {
// TODO: handle exception
e.printStackTrace();
}
// 拆分每一个参数
parseParameters(queryString);
}
} else {
requestURL = url;
}
/*
* String[] urllist = url.split("\\?"); requestURL=urllist[0];
* queryString=urllist[1]; String [] querstring =
* queryString.split("&"); for(String e:querstring){ String key =
* querstring[0]; String value = querstring[1]; parameters.put(key,
* value);
*
* }
*/
System.out.println("解析抽象路径完毕");
}
/* 解析消息头 */
private void parseHeaders() {
System.out.println("开始解析消息头...");
try {
/*
* 消息头有若干行,因此我们应当循环调用 readLine方法读取消息头,但是如果 调用readLine方法返回值为""
* (空字符串)时 则说明单独读到了CRLF,此时表示消息头全部 解析完毕,应当停止读取工作。
* 每个消息头读取到以后,按照冒号空格(:) 进行拆分,并将拆分的第一项作为消息头的
* 名字,第二项作为消息头的值,并以key,value 形式保存到属性headers这个Map中完成消息头 的解析工作。
*/
while (true) {
String line = null;
if (!"".equals(line = readLine())) {// 读到消息头末尾
/*
* 将请求行的内容按空格拆分为三部分 分别设置到属性:method,url,protocol
*/
// 以冒号为分割
// String[] array = line.split(":\\s");//将消息头每条数据切分
String[] array = line.split(":\\s", 2);// 将消息头每条数据切分
headers.put(array[0], array[1]);// 存入map
} else {
break;
}
}
headers.forEach((k, v) -> System.out.println(k + ":" + v));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("解析消息头完毕");
}
/* 解析消息正文 */
private void parseContent() {
/*
* 一个请求是否包含消息正文,可以根据这个请求发过来的消息头中看是否包含
* Content-Length来判断,
* 如果包含这个头,则说明是包含消息正文的,并且指定了正文长度
*
* Content-Type头是客户端告知服务器消息正文的内容是什么类型的数据
* Content-Type的值若为:
* application/x-www-form-urlencoded
* 则表明该正文是一个字符串,内容是页面表单提交上来的用户输入数据的内容
* 格式与GET请求中 url的“?”右侧内容一致 例如:username=zhangsan&password=2133231
*
* 1.首先解析消息正文时,先判断本次请求的消息头中是否含有Centent-Length
* 如果包含则说明包含消息正文,没有则忽略解析消息正文工作
* 2.获取消息头Content-Length的值,这是一个数组,用来得知消息正文的长度(字节量)
* 3.通过输入流读取Content-Length指定的字节量,将消息正文数据读取出来
* 4.在获取Content-Type头的值,根据这个头来判断数据类型,这里只判断一种
* application/x-www-form-urlencoded
* 如果是上述值,则说明内容是页面表单提交上的用户数据,将其转换为字符串,字符集使用ISO8869-1
* 5.转换后的字符串就可以进行拆分参数了,将拆分后的参数再次存入parameters这几个Map中
*/
System.out.println("开始解析消息正文...");
if(headers.containsKey("Content-Length")){
int length =Integer.parseInt( headers.get("Content-Length").trim());
System.out.println("=================="+length);
byte[] data = new byte[length];
try {
in.read(data);
if("application/x-www-form-urlencoded".equals(headers.get("Content-Type"))){
String line =new String(data,"ISO8859-1");
//转码
line = URLDecoder.decode(line,"utf-8");
parseParameters(line);
System.out.println("parameters:"+parameters);
}
} catch (Exception e) {
// TODO: handle exception
}
}else{
System.out.println("解析消息正文失败,没有消息正文");
}
System.out.println("解析消息正文完毕...");
}
/*
* 通过输入流读取客户端(浏览器)发送过来的一行字符串 一行结束的标志为CR,LF
*/
private String readLine() throws IOException {
// 单线程,对字符串进行修改
StringBuilder builder = new StringBuilder();
// c1上次读到的数据
int c1 = -1;
// c2本次读到的数据
int c2 = -1;
while ((c2 = in.read()) != -1) {
// 是否连续读到CR(回车符),LF(换行符)
if (c1 == 13 && c2 == 10) {
break;
}
// 将读到的字符进行拼接成字符串
builder.append((char) c2);
c1 = c2;
}
return builder.toString().trim();
}
public String getRequestURL() {
return requestURL;
}
public String getQueryString() {
return queryString;
}
/*
* 获取指定参数对应的值
*
*/
public String getParameters(String name) {
return parameters.get(name);
}
}
/*
* 响应类
* 该类的每一个实例用于表示服务器端发送给客户端的具体内容
* 每个响应由三个部分构成:状态行、响应头、响应正文
*/
public class HttpResponse {
/*
* 状态行相关信息定义
*/
//状态码
private int statusCode =200;
//状态描述
private String statusReason ="OK";
/*
* 响应头相关信息定义
*/
//key:响应头名字 value :响应头对应的值
private Map<String ,String> headers = new HashMap<>();
/*
* 响应正文相关信息
*/
//响应正文的数据
private byte[] data;
//响应正文的实体文件
private File entity;
/*
* 连接相关的定义
*/
private Socket socket;
private OutputStream out;
public HttpResponse (Socket socket){
try {
this.socket =socket;
out = socket.getOutputStream();
} catch (Exception e) {
e.printStackTrace();
}
}
/*
* 将当前响应对象内容以一个标准的HTTP响应格式响应 发送给客户端
*/
public void flush() {
sendStatusLine();
sendHeaders();
sendContent();
}
/*
* 发送状态行
*/
private void sendStatusLine() {
System.out.println("开始发送状态行");
try {
//发送状态行
String line = "HTTP/1.1"+" "+statusCode+" "+statusReason;
out.write(line.getBytes("ISO8859-1"));
out.write(13);//CR
out.write(10);//LF
} catch (Exception e) {
// TODO: handle exception
}
System.out.println("发送状态行完毕");
}
/*
* 发送响应头
*/
//
private void sendHeaders() {
System.out.println("开始发送响应头");
try {
//发送响应头
Set<Entry<String,String>> set = headers.entrySet();
for(Entry<String,String> headers :set){
String key = headers.getKey();
String value = headers.getValue();
String line = key+":"+value;
out.write(line.getBytes("ISO8859-1"));
out.write(13);
out.write(10);
}
//单独发CRLF,表示响应头部分结束
out.write(13);//CR
out.write(10);//LF
} catch (Exception e) {
// TODO: handle exception
}
System.out.println("发送响应头完毕");
}
/*
* 发送响应正文
*/
private void sendContent() {
System.out.println("开始发送响应正文");
if(entity!=null){
//将文件作为正文内容发送给客户端
try (FileInputStream fis = new FileInputStream(entity);){
//发送文件长度
//读取文件数据
byte[] data = new byte[1024*10];
int len = -1;
while((len = fis.read(data)) != -1) {
out.write(data, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
}
}else if(data!=null){
//将这组字节数组作为正文内容发送到客户端
try {
out.write(data);
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("发送响应正文完毕");
}
/*
* 向当前响应对象中设置正文的实体文件设置的同时会自动根据实体文件添加两个对应的响应头:Content-Type与Content-Lenght
*/
public void setEntity(File entity) {
this.entity = entity;
//获取响应正文实体文件名
String fileName = entity.getName();
String [] ext = fileName.split("\\.");
System.out.println("_----------------------------------------");
String fileType = HttpContext.getMimeType(ext[1]);
headers.put("Content-Type",fileType);
headers.put("Content-Length", entity.length()+"");
}
public int getStatusCode() {
return statusCode;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
public String getStatusReasom() {
return statusReason;
}
public void setStatusReasom(String statusReason) {
this.statusReason = statusReason;
}
//设置响应内容
public void setContentDate(byte[] data){
this.data=data;
headers.put("Content-Length", data.length+"");
}
public byte[] getContentData(){
return data;
}
/*
* 响应对象中添加一个响应头
* key
* value
*/
public void putHeader(String key, String value){
headers.put(key, value);
}
}
package com.webserver.http;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/*
* HTTP协议相关定义
*/
public class HttpContext {
/*
* Context-Type的值与资源后缀对应关系
* key:资源后缀名
* value:对应的Content-Type的值
*/
private static Map<String, String> mime_mapping = new HashMap<String, String>();
static {
initMineMapping();
}
/*
* 初始化静态资源
* 读取conf/web.xml中资源类型列表,添加到mime-mapping中
* 可以解决大部分市面上常见文件类型与资源类中的映射关系
*/
private static void initMineMapping() {
SAXReader reader = new SAXReader();
try {
Document doc = reader.read(new File("conf/web.xml"));
Element root = doc.getRootElement();
List<Element> list = root.elements("mime-mapping");
for(Element e: list) {
String key = e.elementTextTrim("extension");
String value = e.elementTextTrim("mime-type");
mime_mapping.put(key, value);
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
}
/*
* 根据资源的后缀名获取对应的Content-type的值
* ext:文件后缀名
*/
public static String getMimeType(String ext) {
return mime_mapping.get(ext);
}
}
package com.webserver.http;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import com.webserver.servlet.HttpServlet;
/*
* 服务端业务匹配信息
*/
public class ServletContext {
/*
* 请求路径与对应Servlet对应关系 key:请求路径 value:对应的Servlet实例
*/
private static Map<String, HttpServlet> servletMapping = new HashMap<>();
static{
servletMineMapping();
}
/*
* 读取Servlet.xml文件
* 将servlet元素的属性path作为key,
* 基于属性ClassName 获取类对象
* 再进一步获取实例对象,实例对象为value
* 将key和value存入servletMapping
*/
private static void servletMineMapping() {
SAXReader reader = new SAXReader();
try {
Document doc = reader.read("conf/servlets.xml");
Element root = doc.getRootElement();
List<Element> list = root.elements("servlet");
for(Element e :list){
String key = e.attributeValue("path");
//attributeValue获取属性值
String className = e.attributeValue("className");
Class cls = Class.forName(className);
HttpServlet servlet = (HttpServlet)cls.newInstance();
servletMapping.put(key, servlet);
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
throw new RuntimeException();
}
}
public static HttpServlet getServlet(String path){
return servletMapping.get(path);
}
}
业务超类
package com.webserver.servlet;
import java.io.File;
import com.webserver.http.HttpRequest;
import com.webserver.http.HttpResponse;
/*
* 所有Servlet 的超类
*/
public abstract class HttpServlet {
/*
* 用于处理请求的方法,ClientHander在调用
* 某个请求对应的处理类(某servlet)时,会调用
* service方法
*/
public abstract void service(HttpRequest request,HttpResponse response);
/*跳转到指定页面
* path:从webapps之后开始指定路径:如果"/myweb/xxx.html"
* request
* response
*
*/
public void forward(String path,HttpRequest request,HttpResponse response){
File file = new File("webapps/"+path);
response.setEntity(file);
}
}
登录业务
package com.webserver.servlet;
import java.io.File;
import java.io.RandomAccessFile;
import com.webserver.http.HttpRequest;
import com.webserver.http.HttpResponse;
/*
* 处理登录业务
*/
public class LoginServelt extends HttpServlet{
public void service( HttpRequest request,HttpResponse response) {
String username = request.getParameters("username");
String password = request.getParameters("password");
try (RandomAccessFile raf = new RandomAccessFile("user.dat","rw");){
for(int i=0;i<raf.length()/100;i++){
raf.seek(i*100);
//读取用户名
byte[] data = new byte[32];
raf.read(data);
String name = new String(data,"utf-8").trim();
//读取密码
if(name.equals(username)){
raf.read(data);
String pwd = new String (data,"utf-8").trim();
if(pwd.equals(password)){
forward("myweb/loginsuccess.html", request, response);
return;
}
break;
}
}
forward("myweb/login_fail.html", request, response);
return;
} catch (Exception e) {
// TODO: handle exception
}
}
}
注册业务
package com.webserver.servlet;
import java.io.File;
import java.io.RandomAccessFile;
import java.util.Arrays;
import com.webserver.http.HttpRequest;
import com.webserver.http.HttpResponse;
/*
* 处理用户注册业务
*/
public class RegServlet extends HttpServlet{
public void service(HttpRequest request,HttpResponse response){
System.out.println("RegServlet:开始处理注册");
//1.通过request获取用户注册信息
String username = request.getParameters("username");
String password = request.getParameters("password");
String nickname = request.getParameters("nickname");
int age = Integer.parseInt(request.getParameters("age"));
System.out.println("username:"+username);
System.out.println("password:"+password);
System.out.println("nickname:"+nickname);
System.out.println("age:"+age);
//2.将注册信息写入文件user.dat
try (RandomAccessFile raf = new RandomAccessFile("user.dat","rw");){
/*
* 首先判断user.dat文件中是否已经存在该用户,若存在则响应用户注册提示页面
* 提示用户已存在,否则才将该用户写入user.bat文件中
*
* 注册提示网页:reg_have_user.html
* 提示也是显示一行字,内容为:该用户已存在,请重新注册。
*/
for(int i=0;i<raf.length()/100;i++){
//移动指针到第一条记录的开头
raf.seek(i*100);
//读取该条记录的开始位置
byte [] data = new byte[32];
raf.read(data);
//将获取的用户名还原
String name = new String(data,"utf-8").trim();
if(name.equals(username)){
forward("myweb/reg_have_user.html", request, response);
return;
}
}
//先将指针移动到文件末尾
raf.seek(raf.length());
//写用户名,将用户名转为字节数组
byte[] data =username.getBytes("utf-8");
//将字节数组扩容到32字节
data = Arrays.copyOf(data, 32);
raf.write(data);
System.out.println("pos" + raf.getFilePointer());
data = password.getBytes("utf-8");
data = Arrays.copyOf(data, 32);
raf.write(data);
System.out.println("pos" + raf.getFilePointer());
data = nickname.getBytes("utf-8");
data = Arrays.copyOf(data, 32);
raf.write(data);
raf.writeInt(age);
System.out.println("注册完毕");
} catch (Exception e) {
e.printStackTrace();
}
//3.response响应客户端注册结果
forward("myweb/reg_success.html", request, response);
System.out.println("RegServlet:处理注册完毕");
}
}
用户信息展示在浏览器上
package com.webserver.servlet;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.templateresolver.FileTemplateResolver;
import com.webserver.http.HttpRequest;
import com.webserver.http.HttpResponse;
/*
* 展示用户列表
*/
public class ShowAllUserServlet extends HttpServlet{
@Override
public void service(HttpRequest request, HttpResponse response) {
/*
* 1.读取user.dat文件,将所有用户读取出来,
* 其中每个用户用Map存,key保存属性名,value保存属性值
* 2.利用Thymeleaf将集合的数据绑定到showAlluser.html页面上,
* 页面要对应的添加thymeleaf需要的属性,否则thymeleaf不知道如何绑定
*
*/
List<Map<String,String >> userlist = new ArrayList<>();
try(RandomAccessFile raf = new RandomAccessFile("user.dat", "r");) {
for(int i=0;i<raf.length()/100;i++){
//读取用户名
byte[] data = new byte[32];
raf.read(data);
String username = new String(data,"utf-8").trim();
//读取密码
raf.read(data);
String password = new String (data,"utf-8").trim();
//读取昵称
raf.read(data);
String nickname = new String(data,"utf-8").trim();
//读取年龄
int age = raf.readInt();
//用一个Map保存一个用户信息
Map<String ,String > map = new HashMap<>();
map.put("username", username);
map.put("password", password);
map.put("nickname", nickname);
map.put("age", age+"");
//将用户信息存入list集合
userlist.add(map);
}
/*TemplateEngine:模板引擎
* 用来将数据绑定到静态页面的核心组件
*
*/
TemplateEngine engine = new TemplateEngine();
//文件解析器对象
FileTemplateResolver resolver = new FileTemplateResolver();
//设置字符集,告知引擎静态页面的字符集
resolver.setCharacterEncoding("utf-8");
//将解析器对象设置到引擎上,使得引擎知道如何处理静态页面
engine.setTemplateResolver(resolver);
//Context用来存储所有要在页面显示的数据
Context context = new Context();
context.setVariable("list", userlist);
/*
* 调用引擎的process处理方法,该方法就是将指定的数据和指定的页面进行绑定
* 参数1:静态页面路径
* 参数2:需要静态页面上显示的动态数据
* 该方法返回一个字符串,内容就是绑定了动态数据的静态页面所对应的html代码
*/
String html = engine.process("./webapps/myweb/showAlluser.html", context);
byte[] data = html.getBytes("utf-8");
response.setContentDate(data);
response.putHeader("Content-Type", "text/html");
} catch (Exception e) {
e.printStackTrace();
}
}
}
自定义异常
package com.webserver.exception;
public class EmptyRequestexception extends Exception{
/**
* 空指针异常
*/
private static final long serialVersionUID = 1L;
public EmptyRequestexception(){
super();
}
public EmptyRequestexception(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
// TODO Auto-generated constructor stub
}
public EmptyRequestexception(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
public EmptyRequestexception(String message) {
super(message);
// TODO Auto-generated constructor stub
}
public EmptyRequestexception(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
}
}