编写一个HTTP服务器,要先编写基于TCP协议的服务,然后在一个TCP连接中读取HTTP请求,发送HTTP响应即可。要编写一个完善的HTTP
服务器,以HTTP/1.1
为例,需要考虑的包括:
HTTP
请求;HTTP
头;TCP
连接;IO
异常处理;这些基础工作需要耗费大量的时间,并且经过长期测试才能稳定运行。如果我们只需要输出一个简单的HTML
页面,就不得不编写上千行底层代码,那就根本无法做到高效而可靠地开发。
在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("
Hello, world!
"); - // 最后使用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服务器)。常用的服务器有:
还有一些收费的商用服务器,如Oracle的WebLogic,IBM的WebSphere。
在Servlet
容器中运行的Servlet
具有如下特点:
new
创建Servlet
实例,必须由Servlet
容器自动创建Servlet
实例;Servlet
容器只会给每个Servlet
类创建唯一实例;Servlet
容器会使用多线程执行doGet()
或doPost()
方法。 在通过一个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("
Hello, world!
"); - 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实例。
从一个客户端(浏览器)发起请求,到服务器做出响应的过程如图: