今天我们来写一个类似于Tomcat的简易服务器。可供大家深入理解一下tomcat的工作原理,本文仅供新手参考,请各位大神指正!
首先我们要准备的知识是:
  Socket编程
  HTML
  HTTP协议
  服务器编写
  反射
  XML解析
有了上面的知识,我们可以开始写我们的代码了~~
1、首先我们要应用Socket编程写一个简单的服务端用来接收服务器端发来的请求:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket; public class Server {
public static void main(String[] args) {
Server server = new Server();
//1、创建一个服务器端并开启
server.start();
}
public void start(){
try {
ServerSocket ss = new ServerSocket(8888);
//2、接收来自浏览器的请求
this.recevie(ss);
} catch (IOException e) {
e.printStackTrace();
}
}
private void recevie(ServerSocket ss){
try {
Socket client = ss.accept();
//3、将来自浏览器的信息打印出来
BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
StringBuilder httpMessage = new StringBuilder();
while(br.read()!=-1){
httpMessage.append(br.readLine());
httpMessage.append("\r\n");
}
System.out.println(httpMessage.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}

以下是浏览器访问上述服务器时产生的请求内容:HTTP知识补充:

  以下是浏览器访问上述服务器时产生的请求内容:
  POST请求内容:(远不止这些,大家可以通过wireshark来抓包分析请求协议格式
    POST / HTTP/1.1
    Host: localhost:8888
    Connection: keep-alive
    Content-Length: 25
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    Origin: null
    User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36
    Content-Type: application/x-www-form-urlencoded
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.8

    username=ss&password=aaaa
  GET请求内容:
    GET /?username=aa&password=ssss HTTP/1.1
    Host: localhost:8888
    Connection: keep-alive
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36
     Accept-Encoding: gzip, deflate, sdch
    Accept-Language: zh-CN,zh;q=0.8
  下面给出一个服务器响应内容:
    通用头域包含请求和响应消息都支持的头域,通用头域包含Cache-Control、 Connection、Date、Pragma、Transfer-Encoding、Upgrade、Via。
    HTTP/1.1 200 OK
    Cache-Control:private, max-age=0, no-cache:
    Control说明:
      Cache-Control指定请求和响应遵循的缓存机 制。
      Public指示响应可被任何缓存区缓存。
      Private指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效。
      no-cache指示请求或响应消息不能缓存
      no-store用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。
      max-age指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。
      min-fresh指示客户机可以接收响应时间小于当前时间加上指定时间的响应。
      max-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。
  

    Content-Type:image/gif
    Content-Type说明:
      Content-Type实体头用于向接收方指示实体的介质类型,指定HEAD方法送到接收方的实体介质类型,或GET方法发送的请求介质类型Content-Range实体头
    

    Date:Sat, 08 Aug 2015 03:23:23 GMT
    Date说明:
      Date头域表示消息发送的时间,时间的描述格式由rfc822定义。  

    Pragma:no-cache
    Pragma说明:
      Pragma头域用来包含实现特定的指令,最常用的是Pragma:no-cache。在HTTP/1.1协议中,它的含义和Cache-Control:no-cache相同。    

    Server:apache
    Server说明:
      Server响应头包含处理请求的原始服务器的软件信息。此域能包含多个产品标识和注释,产品标识一般按照重要性排序。

    Content-Length:43
  重点补充:
    Status-Code的第一个数字定义响应的类别,后两个数字没有分类的作用。第一个数字可能取5个不同的值:
    1xx:信息响应类,表示接收到请求并且继续处理
    2xx:处理成功响应类,表示动作被成功接收、理解和接受
    3xx:重定向响应类,为了完成指定的动作,必须接受进一步处理
    4xx:客户端错误,客户请求包含语法错误或者是不能正确执行
    5xx:服务端错误,服务器不能正确执行一个正确的请求

  以上补充有利于项目排错哦~~很重要哦~~

2、我们根据上面的服务器相应内容来写一个针对浏览器客户端的响应:

public class Server {
private static final String ENTER = "\r\n";
private static final String SPACE = " ";
public static void main(String[] args) {
Server server = new Server();
//1、创建一个服务器端并开启
server.start();
}
public void start(){
try {
ServerSocket ss = new ServerSocket(8888);
//2、接收来自浏览器的请求
this.recevie(ss);
} catch (IOException e) {
e.printStackTrace();
}
}
private void recevie(ServerSocket ss){
BufferedReader br = null;
try {
Socket client = ss.accept();
//3、将来自浏览器的信息打印出来
br = new BufferedReader(new InputStreamReader(client.getInputStream()));
StringBuilder httpRequest = new StringBuilder();
String meg = null;
while(!(meg = br.readLine().trim()).equals("")){
httpRequest.append(meg);
httpRequest.append("\r\n");
}
System.out.println(httpRequest);
this.httpResponse(client); } catch (IOException e) {
e.printStackTrace();
}finally{
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void httpResponse(Socket client){
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
StringBuilder contextText = new StringBuilder();
contextText.append("<html><head></head><body>This is my page</body></html>");
StringBuilder sb = new StringBuilder();
/*通用头域begin*/
sb.append("HTTP/1.1").append(SPACE).append("200").append(SPACE).append("OK").append(ENTER);
sb.append("Server:myServer").append(SPACE).append("0.0.1v").append(ENTER);
sb.append("Date:Sat,"+SPACE).append(new Date()).append(ENTER);
sb.append("Content-Type:text/html;charset=UTF-8").append(ENTER);
sb.append("Content-Length:").append(contextText.toString().getBytes().length).append(ENTER);
/*通用头域end*/
sb.append(ENTER);//空一行
sb.append(contextText);//正文部分
System.out.println(sb.toString());
bw.write(sb.toString());//写会
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

3、封装response和request
  3.1封装response
    步骤:
    A:构建报文头
    B:构建响应的HTML正文内容
    C:将报文头和HTML正文内容发送给客户端(浏览器)

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Date; public class Response { private static final String ENTER = "\r\n";
private static final String SPACE = " ";
//存储头信息
private StringBuilder headerInfo ;
//2、存储正文信息
private StringBuilder textContent;
//3、记录正文信息长度
private int contentLength ; //4、构建输出流
private BufferedWriter bw ; public Response() {
headerInfo = new StringBuilder();
textContent = new StringBuilder();
contentLength = 0;
} public Response(OutputStream os) {
this();
bw = new BufferedWriter(new OutputStreamWriter(os));
} /**
* 创建头部信息 html报文
* @param code
*/
private void createHeader(int code){
headerInfo.append("HTTP/1.1").append(SPACE).append(code).append(SPACE);
switch (code) {
case 200:
headerInfo.append("OK").append(ENTER);
break;
case 404:
headerInfo.append("NOT FOUND").append(ENTER);
break;
case 500:
headerInfo.append("SERVER ERROR").append(ENTER);
break;
default:
break;
}
headerInfo.append("Server:myServer").append(SPACE).append("0.0.1v").append(ENTER);
headerInfo.append("Date:Sat,"+SPACE).append(new Date()).append(ENTER);
headerInfo.append("Content-Type:text/html;charset=UTF-8").append(ENTER);
headerInfo.append("Content-Length:").append(contentLength).append(ENTER);
headerInfo.append(ENTER);
}
/**
* 响应给浏览器解析的内容(html正文)
* @param content
* @return
*/
public Response htmlContent(String content){
textContent.append(content).append(ENTER);
contentLength += (content+ENTER).toString().getBytes().length;
return this;
}
/**
* 发送给浏览器端
* @param code
*/
public void pushToClient(int code){
createHeader(code);
try {
bw.append(headerInfo.toString());
System.out.println(headerInfo.toString());
bw.append(textContent.toString());
System.out.println(textContent.toString());
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

  3.2封装request
    步骤:
    A:接受浏览器发送的请求
    B:解析浏览器发送来的请求

    import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
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; public class Request {
private static final String ENTER = "\r\n";
//接收请求
private BufferedReader request ;
//储存接受信息
private String requestHeader;
//通过解析头信息得到请求方法
private String method ;
//通过解析头信息得到请求url
private String action ;
//通过解析头信息得到传过来的请求参数 ,可能存在一Key多Value的情况所以用list
private Map<String, List<String>> parameter;
//得到浏览器发过来的头信息
public Request() {
requestHeader = "";
method = "";
action = "";
parameter = new HashMap<String, List<String>>();
}
public Request(InputStream inputStream) {
this();
request = new BufferedReader(new InputStreamReader(inputStream));
//接收到头部信息
try {
String temp;
while(!(temp=request.readLine()).equals("")){
requestHeader += (temp+ENTER);
}
System.out.println(requestHeader);
} catch (IOException e) {
e.printStackTrace();
}
//解析头部信息
parseRequestHeader();
}
/**
* 解析头信息
*/
public void parseRequestHeader(){ //声明一个字符串,来存放请求参数
String parameterString = "";
//读取都头信息的第一行
String firstLine = requestHeader.substring(0, requestHeader.indexOf(ENTER));
//开始分离第一行
//splitPoint分割点1
int splitPointOne = firstLine.indexOf("/");
method = firstLine.substring(0, splitPointOne).trim();
//splitPoint分割点2
int splitPointTwo = firstLine.indexOf("HTTP/");
String actionTemp = firstLine.substring(splitPointOne,splitPointTwo).trim();
if(method.equalsIgnoreCase("post")){
//此处代码为得到post请求的参数字符串,哈哈哈哈,读者自己想想该怎么写哦~~
this.action = actionTemp;
}else if(method.equalsIgnoreCase("get")){
if(actionTemp.contains("?")){
parameterString = actionTemp.substring((actionTemp.indexOf("?")+1)).trim();
this.action = actionTemp.substring(0, actionTemp.indexOf("?"));
}else{
this.action = actionTemp;
}
//将参数封装到Map中哦
parseParameterString(parameterString);
} }
/**
* 解析参数字符串,将参数封装到Map中
* @param parameterString
*/
private void parseParameterString(String parameterString) {
if("".equals(parameterString)){
return;
}else{
String[] parameterKeyValues = parameterString.split("&"); for (int i = 0; i < parameterKeyValues.length; i++) {
String[] KeyValues = parameterKeyValues[i].split("=");
//可能会出现有key没有value的情况
if(KeyValues.length == 1){
KeyValues = Arrays.copyOf(KeyValues, 2);
KeyValues[1] = null;
}
String key = KeyValues[0].trim();
String values = null == KeyValues[1] ? null : decode(KeyValues[1].trim(),"UTF-8");
//将key和values封装到Map中
if(!parameter.containsKey(key)){//如果不存在key,就创建一个
parameter.put(key, new ArrayList<String>());
}
List<String> value = parameter.get(key);
value.add(values);
}
}
}
/**
* 反解码:使用指定的编码机制对 application/x-www-form-urlencoded 字符串解码。
* @param string
* @param encoding
* @return
*/
public String decode(String string,String encoding){
try {
return URLDecoder.decode(string, encoding);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
/**
* 根据名字得到多个值
* @param name
* @return
*/
public String[] getParamterValues(String name){
List<String> values = parameter.get(name);
if(values == null){
return null;
}else{
return values.toArray(new String[0]);
}
}
/**
* 根据名字返回单个值
* @param name
* @return
*/
public String getParamter(String name){
String[] value = getParamterValues(name);
if(value == null){
return null;
}else{
return value[0];
}
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
}

  3.3 此时我们可以将代码优化一下
    新建Servlet类来转码处理请求和响应的业务

/**
* 专门处理请求和响应
* @author SNOOPY
*
*/
public class Servlet { public void service(Request request, Response response){
String username = request.getParamter("user");
response.htmlContent("<html><head></head><body>This is my page<br><br>");
response.htmlContent("欢迎:"+username+" 来到我的地盘</body></html>");
}
}

    此时我们的服务器端可以简化为:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket; /**
*
* @author SNOOPY
* @version 02
*/
public class Server02 {
public void start(){
try {
ServerSocket serverSocket = new ServerSocket(8888);
//2、接收来自浏览器的请求
this.recevie(serverSocket);
} catch (IOException e) {
e.printStackTrace();
}
}
private void recevie(ServerSocket serverSocket){
try {
Socket client = serverSocket.accept();
Servlet servlet = new Servlet(); Request request = new Request(client.getInputStream());
Response response = new Response(client.getOutputStream()); servlet.service(request, response);
response.pushToClient(200);
} catch (IOException e) {
e.printStackTrace();
}
} public static void main(String[] args) {
Server02 server = new Server02();
//1、创建一个服务器端并开启
server.start();
}
}

4、多线程实现
  写到这里我们会发现,此时的代码只能处理一个请求,而现实中的情况是往往会有很多很多的客户端发请求,所以这里我们要用多线程来实现
  因此我们把与客户端浏览器的通信封装到一个线程当中。

import java.io.IOException;
import java.net.Socket;
/**
* 分发
* @author SNOOPY
*
*/
public class Dispatch implements Runnable{ private Socket client;
private Request request;
private Response response;
private int code = 200;
public Dispatch(Socket client) {
this.client = client;
try {
request = new Request(client.getInputStream());
response = new Response(client.getOutputStream());
} catch (IOException e) {
//e.printStackTrace();
code = 500;
return ;
}
}
@Override
public void run() {
Servlet servlet = new Servlet();
servlet.service(request, response);
response.pushToClient(code);
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

  此时我们的服务器只能应对单一的请求与响应,结束后会自动关闭,而现实中的服务器是一直开启等待客户端的请求。
  所以我们的服务器端可以优化为:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket; /**
*
* @author SNOOPY
* @version 03
*/
public class Server03 { private boolean isShutDown = false;
/**
* 启动服务器
*/
public void start(){
start(8888);
}
/**
* 指定服务器端口
* @param port
*/
public void start(int port){
try {
ServerSocket serverSocket = new ServerSocket(port);
//2、接收来自浏览器的请求
this.recevie(serverSocket);
} catch (IOException e) {
//e.printStackTrace();
stop();
}
}
/**
* 关闭服务器
*/
private void stop() {
isShutDown = true;
}
/**
* 接受客户端信息
* @param serverSocket
*/
private void recevie(ServerSocket serverSocket){
try {
while(!isShutDown){ Socket client = serverSocket.accept(); new Thread(new Dispatch(client)).start();
}
} catch (IOException e) {
e.printStackTrace();
//如果这里面有问题直接关闭服务器
isShutDown = true;
}
} public static void main(String[] args) {
Server03 server = new Server03();
//1、创建一个服务器端并开启
server.start();
}
}

  到此时我们会发现我们发过来的请求会有很多,也就意味着我们应该会有很多的servlet,例如:RegisterServlet、LoginServlet等等还有很多其他的访问。
    那么我们要用到类似于工厂模式的方法处理,来随时产生很多的servlet,来满足不同的功能性的请求。
    那么我们要抽象servlet。
    在我们抽象servlet之前,我们先来思考一个问题:
    我们会写很多的servlet,那么我们怎么将请求与各种的servlet相匹配呢?
    接下来我们要先写一个关于servlet的上下文,来封装servlet与请求。说到这里你是不是想到了什么呢??

import java.util.HashMap;
import java.util.Map; /**
* servlet的上下文
* @author SNOOPY
*
*/
public class servletContext { //通过类名创建servlet对象
private Map<String, Servlet> servlet ;
//通过请求名找到对应的servlet类名
private Map<String , String> mapping ; public servletContext() {
servlet = new HashMap<String,Servlet>();
mapping = new HashMap<String,String>();
} public Map<String, Servlet> getServlet() {
return servlet;
}
public void setServlet(Map<String, Servlet> servlet) {
this.servlet = servlet;
}
public Map<String, String> getMapping() {
return mapping;
}
public void setMapping(Map<String, String> mapping) {
this.mapping = mapping;
}
}

    其实说白了servletContext这个类就是一个容器,用来存放请求与对应的Servlet的。
    那么接下来我们模拟一下,往这个容器里面存放请求与对应的Servlet,但是在这之前我们需要有不同的servlet,所以我们接下来要把Servlet进行抽象,
    以便于我们可以随意产生不同的servlet。

/**
* 专门处理请求和响应
* @author SNOOPY
*
*/
public abstract class Servlet { public void service(Request request, Response response) throws Exception{
String method = request.getMethod();
if(method.equalsIgnoreCase("post")){
this.doPost(request, response);
}else if(method.equalsIgnoreCase("get")){
this.doGet(request, response);
}
} public void doGet(Request request, Response response) throws Exception{ } public void doPost(Request request, Response response) throws Exception{ }
}

    上面的抽象其实很简单,那么当我们抽象结束后,我们写一个WebApp类来存储一些我们会用到的servlet上下文,并且提供获取他们的方法:

import java.util.HashMap;
import java.util.Map; /**
* servlet的上下文
* @author SNOOPY
*
*/
public class servletContext { //通过对应的servlet类名创建servlet对象
private Map<String, Servlet> servlet ;
//通过请求名(action)找到对应的servlet类名
private Map<String , String> mapping ; public servletContext() {
servlet = new HashMap<String,Servlet>();
mapping = new HashMap<String,String>();
} public Map<String, Servlet> getServlet() {
return servlet;
}
public void setServlet(Map<String, Servlet> servlet) {
this.servlet = servlet;
}
public Map<String, String> getMapping() {
return mapping;
}
public void setMapping(Map<String, String> mapping) {
this.mapping = mapping;
}
}

    接下来我们要修改一下分发的程序了:

import java.io.IOException;
import java.net.Socket;
/**
* 分发
* @author SNOOPY
*
*/
public class Dispatch implements Runnable{ private Socket client;
private Request request;
private Response response;
private int code = 200;
public Dispatch(Socket client) {
this.client = client;
try {
request = new Request(client.getInputStream());
response = new Response(client.getOutputStream());
} catch (IOException e) {
//e.printStackTrace();
code = 500;
return ;
}
}
@Override
public void run() {
try{
String action = request.getAction();
Servlet servlet = WebApp.getServlet(action);
if(servlet == null){
this.code = 404;
response.pushToClient(code);
return;
}
servlet.service(request, response);
} catch (Exception e) {
e.printStackTrace();
code = 500;
}
response.pushToClient(code);
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

    这样一个简易的原理过程就出来了,别急,还没有结束呢。
    我们现在写的代码用来存储servlet上下文的时候用的是Servlet抽象类吧?就是说我们每次存储的时候都要存储一个类,这样是不是有点消耗内存呢?如果换成字符串呢?
    还有一点就是如果我们每次修改这个类是不是都要重启一下服务器?是不是很麻烦??所以。。。你想到了什么?
    随你便啦~~其实如果你写过非框架的web应用,那么应该接触过配置文件。那么应该想到的就是web.xml
    接下来讲一下配置文件吧,哈哈哈哈哈哈
    一说到配置文件,肯定是要解析配置文件的,一解析配置又牵扯到类与对象,少不了的技术就是反射机制哦!!
    首先让我们稍微修改一下ServletContext类;

import java.util.HashMap;
import java.util.Map; /**
* servlet的上下文
* @author SNOOPY
*
*/
public class ServletContext { //通过对应的servlet类名创建servlet对象
//private Map<String, Servlet> servlet ;
private Map<String, String> servlet ;
//通过请求名(action)找到对应的servlet类名
private Map<String , String> mapping ; public ServletContext() {
servlet = new HashMap<String,String>();
mapping = new HashMap<String,String>();
} public Map<String, String> getServlet() {
return servlet;
}
public void setServlet(Map<String, String> servlet) {
this.servlet = servlet;
} public Map<String, String> getMapping() {
return mapping;
}
public void setMapping(Map<String, String> mapping) {
this.mapping = mapping;
}
}

    那么接下来我们要做的就是解析XML文件并且将解析好的值放入servlet上下文中。
    解析xml文件之前我们先要用实体类来封装xml文件

/**
*
* @author SNOOPY
*
*/
public class XmlServlet { private String servlet_name; private String servlet_class; public String getServlet_name() {
return servlet_name;
} public void setServlet_name(String servlet_name) {
this.servlet_name = servlet_name;
} public String getServlet_class() {
return servlet_class;
} public void setServlet_class(String servlet_class) {
this.servlet_class = servlet_class;
}
}
    import java.util.ArrayList;
import java.util.List; /**
* 一个servlet可以对应多个action
* @author SNOOPY
*
*/
public class XmlMapping { private String servlet_name; private List<String> url_pattern; public XmlMapping() {
url_pattern = new ArrayList<String>();
}
public String getServlet_name() {
return servlet_name;
}
public void setServlet_name(String servlet_name) {
this.servlet_name = servlet_name;
}
public List<String> getUrl_pattern() {
return url_pattern;
}
public void setUrl_pattern(List<String> url_pattern) {
this.url_pattern = url_pattern;
}
}

    然后再解析XML文件并且将解析好的值放入servlet上下文中。

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory; import org.xml.sax.SAXException; /**
* 存储一些我们会用到的servlet上下文,并且提供获取他们的方法
* @author SNOOPY
*
*/
public class WebApp { private static ServletContext context;
static {
context = new ServletContext();
//创建存放servlet上下文的容器
Map<String, String> mapping = context.getMapping();
Map<String, String> servlet = context.getServlet(); //解析配置文件,将对应的字符串存入里面
/*补充知识:
* 解析配置文件的方法有很多,最基本的是SAX解析和DOM解析:SAX解析式基于事件流的解析,DOM解析是基于XML文档树结构的解析
* 另外还有DOM4J和JDOM都可以解析。
* DOM和SAX的区别:
* DOM解析适合于对文件进行修改和随机存取的操作,但是不适合于大型文件的操作;
* SAX采用部分读取的方式,所以可以处理大型文件,而且只需要从文件中读取特定内容,SAX解析可以由用户自己建立自己的对象模型。
* 所以DOM解析适合于修改,SAX解析适合于读取大型文件,2者结合的话可以用JDOM
*
* 本次示例为了方便就选择SAX解析,步骤一共分三步:
* 1、获得解析工程类。
* 2、工程获取解析器
* 3、加载文档注册处理器
*/
//1、获得工厂类
SAXParserFactory factory = SAXParserFactory.newInstance();
try {
//2、从解析工程获取解析器
SAXParser parser = factory.newSAXParser();
//3、加载文档并注册处理器(handle)。注意:此处的文档可以用file的形式也可以用流的形式,随便,便于学习,下面提供两种。
//String filePath = "";
//parser.parse(new File(filePath), handler);
XMLHandler handler = new XMLHandler();
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("com/httpservlet/WEB-INF/web.xml");
parser.parse(is, handler); List<XmlServlet> serv = handler.getServlet();
for (XmlServlet xmlServlet : serv) {
servlet.put(xmlServlet.getServlet_name(), xmlServlet.getServlet_class());
}
List<XmlMapping> map = handler.getMapping();
for (XmlMapping maps : map) {
List<String> actions = maps.getUrl_pattern();
for (String action : actions) {
mapping.put(action, maps.getServlet_name());
}
} } catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} public static Servlet getServlet(String action){
if("".equals(action) || action == null){
return null;
}
//通过action找到servlet-name
String servlet_name = context.getMapping().get(action);
//通过反射,找到相应的类,创建其对象并返回
String classPath = context.getServlet().get(servlet_name);//通过action得到类路径 Servlet servlet = null;
if(classPath != null){
Class<?> clazz = null;
try {
clazz = Class.forName(classPath);
servlet = (Servlet)clazz.newInstance();//要确保空构造存在
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return servlet;
}
}

    这里还需要一个XMLHandler处理器来处理xml文件

import java.util.ArrayList;
import java.util.List; import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler; /**
* 存储对象
* @author SNOOPY
*
*/
public class XMLHandler extends DefaultHandler { private List<XmlServlet> servlet ; private List<XmlMapping> mapping ; public List<XmlServlet> getServlet() {
return servlet;
} public void setServlet(List<XmlServlet> servlet) {
this.servlet = servlet;
} public List<XmlMapping> getMapping() {
return mapping;
} public void setMapping(List<XmlMapping> mapping) {
this.mapping = mapping;
} private XmlServlet serv ; private XmlMapping map ; private String beginTag; private boolean isMap; @Override
public void startDocument() throws SAXException {
servlet = new ArrayList<XmlServlet>();
mapping = new ArrayList<XmlMapping>();
} @Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
if(null!=qName){
beginTag = qName;
if(qName.equals("servlet")){
serv = new XmlServlet();
isMap = false;
}else if(qName.equals("servlet-mapping")){
map = new XmlMapping();
isMap = true;
}
} } @Override
public void characters(char[] ch, int start, int length)
throws SAXException { if(beginTag != null){
String info = new String(ch, start, length);
if(isMap){
if(beginTag.equals("servlet-name")){
map.setServlet_name(info.trim());
}else if(beginTag.equals("url-pattern")){
map.getUrl_pattern().add(info);
}
}else{
if(beginTag.equals("servlet-name")){
serv.setServlet_name(info);
}else if(beginTag.equals("servlet-class")){
serv.setServlet_class(info);
}
}
}
} @Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
if(null!=qName){
if(qName.equals("servlet")){
servlet.add(serv);
}else if(qName.equals("servlet-mapping")){
mapping.add(map);
}
}
beginTag = null;
} @Override
public void endDocument() throws SAXException {
//文档结束
}
}

  万事具备只欠东风!---->处理一下分发的类:

import java.io.IOException;
import java.net.Socket;
/**
* 分发
* @author SNOOPY
*
*/
public class Dispatch implements Runnable{ private Socket client;
private Request request;
private Response response;
private int code = 200;
public Dispatch(Socket client) {
this.client = client;
try {
request = new Request(client.getInputStream());
response = new Response(client.getOutputStream());
} catch (IOException e) {
//e.printStackTrace();
code = 500;
return ;
}
}
@Override
public void run() {
try{
String action = request.getAction();
Servlet servlet = WebApp.getServlet(action);
if(servlet == null){
this.code = 404;
response.pushToClient(code);
return;
}
servlet.service(request, response);
/*String method = request.getMethod();
if(method.equalsIgnoreCase("post")){
servlet.doPost(request, response);
}else if(method.equalsIgnoreCase("get")){
servlet.doGet(request, response);
}*/
} catch (Exception e) {
e.printStackTrace();
code = 500;
}
response.pushToClient(code);
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

  经过以上的分析以及代码的书写,一个简易的web服务器就这样诞生了!
  各位看官可以动手尝试一下哦~~
  当然代码有很多的地方还很值得去优化和修正,希望有心的大神能指正错误,我会及时修正!!

    注意:此文仅适用于刚入门的同学,帮助理解服务器原理。

附:手写服务器包结构(最终整理)

手写简易WEB服务器的更多相关文章

  1. 【教程】手写简易web服务器

    package com.littlepage.testjdbc; import java.io.BufferedReader; import java.io.FileReader; import ja ...

  2. Tomcat源码分析 (一)----- 手写一个web服务器

    作为后端开发人员,在实际的工作中我们会非常高频地使用到web服务器.而tomcat作为web服务器领域中举足轻重的一个web框架,又是不能不学习和了解的. tomcat其实是一个web框架,那么其内部 ...

  3. 手写一个Web服务器,极简版Tomcat

    网络传输是通过遵守HTTP协议的数据格式来传输的. HTTP协议是由标准化组织W3C(World Wide Web Consortium,万维网联盟)和IETF(Internet Engineerin ...

  4. 写一个简易web服务器、ASP.NET核心知识(4)

    前言 昨天尝试了,基于对http协议的探究,我们用控制台写了一个简单的浏览器.尽管浏览器很low,但是对于http协议有个更好的理解. 说了上面这一段,诸位猜到我要干嘛了吗?(其实不用猜哈,标题里都有 ...

  5. java写的web服务器

    经常用Tomcat,不知道的以为Tomcat很牛,其实Tomcat就是用java写的,Tomcat对jsp的支持做的很好,那么今天我们用java来写一个web服务器 //首先得到一个server, S ...

  6. 手写简易SpringMVC

    手写简易SpringMVC 手写系列框架代码基于普通Maven构建,因此在手写SpringMVC的过程中,需要手动的集成Tomcat容器 必备知识: Servlet相关理解和使用,Maven,Java ...

  7. 转:C#写的WEB服务器

    转:http://www.cnblogs.com/x369/articles/79245.html 这只是一个简单的用C#写的WEB服务器,只实现了get方式的对html文件的请求,有兴趣的朋友可以在 ...

  8. 徒手用Java来写个Web服务器和框架吧<第三章:Service的实现和注册>

    徒手用Java来写个Web服务器和框架吧<第一章:NIO篇> 徒手用Java来写个Web服务器和框架吧<第二章:Request和Response> 这一章先把Web框架的功能说 ...

  9. 徒手用Java来写个Web服务器和框架吧<第二章:Request和Response>

    徒手用Java来写个Web服务器和框架吧<第一章:NIO篇> 接上一篇,说到接受了请求,接下来就是解析请求构建Request对象,以及创建Response对象返回. 多有纰漏还请指出.省略 ...

随机推荐

  1. # 20145118 《Java程序设计》第4周学习总结 ## 教材学习内容总结

    20145118 <Java程序设计>第4周学习总结 教材学习内容总结 本周内容为教材第六.七两张内容. 重点概念: 1.面向对象中,子类继承父类,避免重复的行为定义,是一种简化操作. 2 ...

  2. Java知识弥补-Android开发

    目录 数据结构 1. Map-HashMap 2. StringBuilder 3. List-ArrayList 4. Vector 5. Stack 6. Set 由于这学期开了android课程 ...

  3. Flask 3 程序的基本结构2

    NOTE 1.hello.py 通过修饰器的route方法添加动态路由: #!/usr/bin/env python from flask import Flask app = Flask(__nam ...

  4. Mininet 跑一个简单的ping测试

    安装地址:https://github.com/mininet/mininet/wiki/Mininet-VM-Images 登录用户:mininet 密码:mininet 执行命令:sudo mn ...

  5. .net core开发 (一)

    1..net core: 是微软开发的另外一个可以跨平台的.net 2..net framework,.net core, mono的关系三者都是.net在不同操作系统的实现 3. .net core ...

  6. 【源码学习之spark core 1.6.1 standalone模式下的作业提交】

    说明:个人原创,转载请说明出处 http://www.cnblogs.com/piaolingzxh/p/5656876.html 未完待续

  7. 在x86为arm 编译 httpd 2.2.31

    这个版本的httpd 已经自带 apr apr-util pcre , 不用额外下载源代码 1) 编写环境变量脚本,并执行 cross-env.sh : export ARMROOTFS=/h1roo ...

  8. APP推广运营经验总结

    这片文章来自于我在公司的分享会,主题是关于APP在渠道方面的推广,主要包括3个方面,下载量,留存率,日活跃用户. 首先,在应用市场中,一个APP有四个方面,简介,截图,下载量,评论.用户看这四个方面, ...

  9. c++ 中的 set

    set (集合) 中的元素是排序好的,而且是不重复的. 例题:hdu 4989 题目大意:求一组数列中任意两个不重复元素和,再求不重复和的和. #include <bits/stdc++.h&g ...

  10. Linux终端界面屏保

    Linux终端界面屏保   在很多Linux使用者的认知里,都认为终端下的Linux操作界面是没有屏保的,只有像windows那样的图形界面下才有屏保.但是其实Linux下也是有屏保的,只不过是ASC ...