引言
编写一个HTTP服务器,要先编写基于TCP协议的服务,然后在一个TCP连接中读取HTTP请求,发送HTTP响应即可。要编写一个完善的
HTTP
服务器,以
HTTP/1.1
为例,需要考虑的包括:
- 识别正确和错误的
HTTP
请求; - 识别正确和错误的
HTTP
头; - 复用
TCP
连接; - 复用线程;
IO
异常处理;
这些基础工作需要耗费大量的时间,并且经过长期测试才能稳定运行。如果我们只需要输出一个简单的
HTML
页面,就不得不编写上千行底层代码,那就根本无法做到高效而可靠地开发。
Servlet
在
JavaEE
平台上,处理
TCP
连接,解析
HTTP
协议这些底层工作统统扔给现成的
Web
服务器去做,我们只需要把自己的应用程序跑在
Web
服务器上。为了实现这一目的,
JavaEE
提供了
Servlet
API
,我们使用
Servlet API
编写自己的
Servlet
来处理
HTTP
请求,
Web
服务器实现
Servlet
API
接口,实现底层功能:
┌───────────┐
│My Servlet │
├───────────┤
│Servlet API│
┌───────┐ HTTP ├───────────┤
│Browser│<──────>│Web Server │
└───────┘ └───────────┘
简单来讲,一个Web App就是由一个或多个
Servlet
组成的,每个
Servlet
通过注解说明自己能处理的路径。一个
Webapp
完全可以有多个
Servlet
,分别映射不同的路径。例如:
HelloServlet
能处理
/hello.do
这个路径的请求。
**一个简单的Servlet: **
// WebServlet注解表示这是一个Servlet,并映射到地址 hello.do
@WebServlet(urlPatterns = "/hello.do")
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 设置响应类型:
resp.setContentType("text/html");
// 获取输出流:
PrintWriter pw = resp.getWriter();
// 写入响应:
pw.write("<h1>Hello, world!</h1>");
// 最后使用flush强制输出:
pw.flush();
}
}
在浏览器输入
http://localhost:8080/项目名称/hello.do
,即可看到
HelloServlet
的输出:
一个
Servlet
总是继承自
HttpServlet
,然后覆写
doGet()
或
doPost()
方法。注意到
doGet()
方法传入了
HttpServletRequest
和
HttpServletResponse
两个对象,分别代表
HTTP
请求和响应。我们使用
Servlet API
时,并不直接与底层
TCP
交互,也不需要解析
HTTP
协议,因为
HttpServletRequest
和
HttpServletResponse
就已经封装好了请求和响应。以发送响应为例,我们只需要设置正确的响应类型,然后获取
PrintWriter
,写入响应即可。
要想运行Servlet,就要使用支持
Servlet
API
的Web容器(Web服务器)。常用的服务器有:
- Tomcat:由Apache开发的开源免费服务器;
- Jetty:由Eclipse开发的开源免费服务器;
- GlassFish:一个开源的全功能JavaEE服务器。
还有一些收费的商用服务器,如Oracle的WebLogic,IBM的WebSphere。
**在
Servlet
容器中运行的
Servlet
具有如下特点**:
- 无法在代码中直接通过
new
创建Servlet
实例,必须由Servlet
容器自动创建Servlet
实例; Servlet
容器只会给每个Servlet
类创建唯一实例;Servlet
容器会使用多线程执行doGet()
或doPost()
方法。
Servlet生命周期
在通过一个
URL
路径发起对一个
Servlet
请求的过程中,其本质是在调用执行
Servlet
实例的
doXXX()
方法。该
Servlet
实例创建和使用的过程,被称为Servlet的生命周期。整个生命周期包括:实例化、初始化、服务、销毁。
实例化
根据
Servlet
请求的路径(例如:
home.do
),查找该
Servlet
的实例。如果实例不存在,则通过调用构造方法,完成
Servlet
实例的创建。
//WebServlet注解表示这是一个Servlet,并映射到地址 hello.do
@WebServlet(urlPatterns = "/hello.do")
public class HelloServlet extends HttpServlet {
//记录客户端(服务器)发起请求的次数
int count ;
//定义其无参构造方法
public HelloServlet(){
System.out.println("Servlect实例化开始,对象被创建!");
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("收到客户端的请求:"+(++count)+"次");
PrintWriter pw = resp.getWriter();
// 写入响应:
pw.write("<h1>Hello, world!</h1>");
pw.flush();
}
}
客户端(服务器)第一次发起请求时Tomcat没有找到Servlet实例,所以就调用构造方法,完成对Servlect的实例化,当再次客户端再次发起请求时,由于Servlet对象已经存在,故不再调用构造方法!
初始化
通过该
Servlet
的实例,调用
init()
方法,执行初始化的逻辑。
还是上面HelloServlet类中的代码,在此类中定义无参的构造方法并且重写init()方法,当有客户端发起请求,运行结果如下:
服务
通过该
Servlet
的实例,调用
service()
方法,如果子类没有重写该方法,则调用HttpServlet父类的
service()
方法,在父类的该方法中进行请求方式的判断,如果是
GET
请求,则调用
doGet()
方法;如果是
POST
请求,则调用
doPost()
方法;如果子类重写
doXXX()
方法,则调用子类重写后的
doXXX()
方法;如果子类没有重写
doXXX()
方法,则调用父类的
doXXX()
方法,在父类的方法实现中,返回一个
405
状态码的错误页面。
源代码:
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// Invalid date header - proceed as if none was set
ifModifiedSince = -1;
}
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
如果我们在HelloServlet类中简单重写service()方法,那么当有客户端发起请求,服务器执行service()方法,就不会做出响应。
** 运行结果:**
销毁
服务器关闭或重启时,会调用Servlet实例的
destroy()
方法,会销毁所有的Servlet实例。
从一个客户端(浏览器)发起请求,到服务器做出响应的过程如图:
版权归原作者 磨剑斩秋招 所有, 如有侵权,请联系我们删除。