Servlets

What are servlets?

Servlet(Server Applet)是用Java编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。

Servlets are Java’s answer to CGI

Java Servlet 通常情况下与使用 CGI(Common Gateway Interface,公共网关接口)实现的程序可以达到异曲同工的效果

– Used to generate dynamic web pages in response to client

– Used for client requests that cannot be satisfied using pre-built (static) documents.

– They are programs that run on a web server acting as middle layer between HTTP request and databases or other applications.是运行在web服务器上的程序,充当HTTP请求和数据库或其他应用程序之间的中间层。

web服务器只是提供静态的资源:从目录中找到它们直接发给client,无法区分client

而CGI(Java 中servlet)可以进行Dynamic response,辅助web服务器做出响应

tomcat是其容器

image-20221114215446221 image-20221114215511196

Advantages of Servlets over CGI

  • 性能明显更好Efficient
  • Convenient:提供解析和解码HTML表单的基础结构
  • Powerful: 可以直接与web服务器通信。多个servlet可以共享数据库连接。简化会话跟踪
  • Portable– Written in Java and follows standard API
  • Secure服务器上的 Java 安全管理器执行了一系列限制,以保护服务器计算机上资源
  • Inexpensive

servlet 一个例子:

servlet标准格式

import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.*;

public class HelloServlet extends HttpServlet{
    public void doGet(HttpServletRequest   request,HttpServletResponse response)throws IOException, ServletExcpetion {
   response.setContentType("text/html");//响应格式
   PrintWriter out = response.getWriter();
   out.println(“返回的HTML”);
   out.close;
  }
}

另一个例子:echo

image-20221114214745977
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.*;

public class GetEchoServlet extends HttpServlet {
  public void doGet(HttpServletRequest request,
                    HttpServletResponse response) 
                       throws IOException, ServletException {
  String userName = request.getParameter(“fname”);
  response.setContentType("text/html");
  PrintWriter out = response.getWriter();
  out.println(“<html><body>Hello”);
  if (userName != null) out.println(userName);
  else out.println(“mystery person”);
  out.println(“</body></html>”);
  out.close(); //记得close!!!!!
 }
}

值得注意的点:

  1. 导入的包:
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.*;
  1. HTTP报文类型是get则这里使用doGet

    继承该类时至少重写一个方法

  2. 获取HTML form中的参数值value(是字符串类型,需要转换)

String request.getParameter(attribute name )//其中要打双引号

attribute name 是HTML表单提交时的名字,把变量名提交到HTTP请求中

– returns null if unknown parameter;

– returns “” (i.e. empty string) if parameter has no value

可以用枚举获取全部参数:

String[] Enumeration getParameterNames(String)
    然后用这个获取每个的值构成的数组
String[] getParameterValues(String)

• returns null if unknown parameter;

• returns a single string (“”) if parameter has no values.

  1. 写入response报文:
PrintWriter out = response.getWriter();

通常在HTML中

中action指向servlet

<FORM action=“servlet/myServlet” …> 这个指向是由Deployment Descriptor实现的

一个例子:获取html发送的报文中的键值对并打印在表格中返回

image-20221121212755031

html

<form action="http://localhost:8080/servlet/elem004.ProcessBigForm" 
method="post">
Please enter: <br><br>
Your login: <input type="text" name="Login"> <br><br>
Your favourite colour:
<input type="radio" name="Colour" value="blue">Blue
<input type="radio" name="Colour" value="red">Red
<input type="radio" name="Colour" value="green">Green <br><br>
Which of these courses you are taking: <br>
<input type="checkbox" name="Course" value="elem001">ELEM001 <br>
<input type="checkbox" name="Course" value="elem002">ELEM002 <br>
<input type="checkbox" name="Course" value="elem003">ELEM003 <br>
<input type="checkbox" name="Course" value="elem004">ELEM004 <br>
<input type="submit" value="Send to Servlet">
</form>

servlet

// More code here ...
out.println("<table border=1>");
// 从请求对象中获取表单的所有参数
Enumeration paramNames = req.getParameterNames();

while (paramNames.hasMoreElements()) {
    
String paramName = (String) paramNames.nextElement();
    
// 获取该参数的值并检查有多少个。
String[] paramValues = req.getParameterValues(paramName);
    
if (paramValues.length == 1) { // 单值
String paramVal = req.getParameter(paramName);
out.println("<tr><td>" + paramName +"</td><td>" 
+ paramVal + "</td></tr>");
}
else { // 如果多个值在表中打印一个列表。 
out.println("<tr><td>" + paramName +"</td><td><ul>");
for (int i = 0; i < paramValues.length; i++)
out.println("<li>" + paramValues[i] + "</li>");
out.println("</ul></td></tr>");
}
}
out.println("</table>");
out.close();

image-20221121213002075

Servlets

  1. 其中没有 NO method public static void main(String[] args)

  2. NO constructor: There is one (default) constructor but the developer should never write an explicit constructor.

    没有main()方法在,只有默认constructor

  3. Two key (service) methods:

    doGet()

    doPost()

What servlets do

  • 读取客户端(浏览器)发送的数据(Read any data sent by the user)。这包括网页上的 HTML 表单,或者也可以是来自 applet 或自定义的 HTTP 客户端程序的表单。
  • 读取客户端(浏览器)发送的隐式的 HTTP 请求数据(Look up information embedded in HTTP request)。这包括 cookies、媒体类型和浏览器能理解的压缩格式等等。
  • 处理数据并生成结果(Generate results)。这个过程可能需要访问数据库,执行 RMI 或 CORBA 调用,调用 Web 服务,或者直接计算得出对应的响应。
  • 发送显式的数据(即文档)到客户端(浏览器)(Format results inside a document,Send the document back to the client)。该文档的格式可以是多种多样的,包括文本文件(HTML 或 XML)、二进制文件(GIF 图像)、Excel 等。
  • 发送隐式的 HTTP 响应到客户端(浏览器)(Set appropriate HTTP response parameters)。这包括告诉浏览器或其他客户端被返回的文档类型(例如 HTML),设置 cookies 和缓存参数,以及其他类似的任务。

Typical generic servlet code

通用的servlet: extends GenericServlet

网路编程中继承其子类:extends HttpServlet

GenericServlet类:实现了Servlet定义的方法

HttpServlet类:继承了GenericServlet 扩展了doGet, doPost, doDelete, doPut, doTrace等方法

import java.io.*;
import jakarta.servlet.*;
public class AnyServlet extends GenericServlet {
public AnyServlet() {} 
 // constructor – BUT USE THE DEFAULT
// NEVER ANY NEED TO WRITE ONE
public void init(ServletConfig config) throws ServletException;
// 传入一个servletconfig对象用其中参数初始化
public void service(ServletRequest req, ServletResponse res)throws ServletException, IOException;
// Called by a new thread (in the container) each time a 
// request is received.
public void destroy();
// Called when servlet is destroyed or removed.
}
  • Servlet 通过调用 init () 方法进行初始化(配置文件和Servlet关联)。

  • Servlet 调用 service() 方法来处理客户端的请求会根据需求调用doGet()/doPut()。所以只需重写这两个方法即可

  • Servlet 通过调用 destroy() 方法终止(结束)。

A Servlet is “deployed” in a container

A program that receives (e.g. HTTP) requests to servlets from a web server application:

(1)当服务器收到某一个Servlet请求时,它会检查该Servlet类的实例是否存在

  • 如果不存在就会创建这个Servlet的实例(创建该类的对象),同时启动一个线程,这个过程称为载入(load) Servlet

  • 如果存在就会直接调用该Servlet的实例

(2)Servlet对象创建之后,服务器就可以调用该实例响应客户的请求了,当多个客户请求一个Servlet时,服务器为每一个客户启动一个线程而不是进程,这个线程调用内存中的Servlet实例的service()方法响应客户的请求。

(3)当服务器关闭或者卸载应用程序时,关闭该Servlet实例,释放Servlet所占用的资源

manages the life cycle of its servlets

tomcat:Calls constructor, init(), service(), destroy()

image-20221116140206763

每建立一个连接就新建一个线程运行servlet,destroy调用后servlet对象仍然存在,直到服务器关闭才被GC

image-20221116141258010
Constructor

called by the container

不能重写

init() 方法

init 方法被设计成只调用一次。它在第一次创建 Servlet 时被调用,在后续每次用户请求时不再调用。

called after constructor

public void init()
public void service(…)

几乎不重写,没意义(会调用doGet/doPost)

public void doGet() / public void doPost()

根据需求重写

public void destroy()

一般不用写新内容

https://www.w3cschool.cn/servlet/servlet-life-cycle.html)

TOMCAT中配置servlet

Deployment Descriptor (DD)

利用tomcat中web.xml文件将servlet名字和URL挂钩

关联 servlet 和 url 的一对标签(由两个标签共同决定),如下代码所示:

<web-app>
<servlet>
    <servlet-name>helloServlet</servlet-name> 名字
    <servlet-class>com.example.demo.HelloServlet</servlet-class>
</servlet>
maps internal name to fully qualified class name
<servlet-mapping>
    <servlet-name>helloServlet</servlet-name>
    <url-pattern>/hello</url-pattern>
</servlet-mapping>
maps internal name to public URL name
</web-app>

servlet标签下有两个子标签,一个定义了一个servlet的名字,另一个将这个servlet的名字与一个具体的servlet Java的class文件(位于WEB-INF/classes/下直接写名字即可, 否则写包.类名(如上))关联起来

接着就是servlet-mapping标签了,这个标签下面同样有两个标签,一个标签指向之前定义的 servlet 部署名,另一个标签指向一个url(form表单中action后面的url)

Can use absolute or relative URLs or pre-configured names

比如:

image-20221130121100454

Tomcat文件结构:

Level 1: WEB-INF (folder) and .html, .jsp(第一级)

image-20221121214037245

Level 2: (inside WEB-INF folder): web.xml and classes (folder)

image-20221121214316410

Level 3 (inside classes folder): servlet .class files (and other “business” class files e.g. JavaBeans)

Servlet Configuration

Prior to initialisation, the ServletConfig object is created 初始化前会创建一个servletConfig对象, 包含 init params

servletConfig对象:用于封装servlet的配置信息。从一个servlet被实例化后,对任何客户端在任何时候访问有效,但仅对servlet自身有效,一个servlet的ServletConfig对象不能被另一个servlet访问。

all requests made to a servlet can access this object

用于加载servlet的初始化参数,容器使用它将部署时信息(DD)传递给servlet。

运行过程中参数不会改。

在一个web应用可以存在多个ServletConfig对象(一个Servlet对应一个ServletConfig对象one ServletConfig object per servlet

• Step 1: container reads the deployment descriptor

• Step 2: container creates new ServletConfig object

• Step 3: container creates name/value pair (Strings) for each servlet init-param

tomcat服务器把这些参数会在加载web应用的时候,封装到ServletConfig对象

• Step 4: container passes references to these to the ServletConfig object

• Step 5: container creates new instance of the servlet class

• Step 6: container calls servlet’s init() method passing in reference to the ServletConfig object

写在Web. xml中的servlet标签中!:

<web-app xmlns=“http://java.sun.com/xml/ns/j2ee”
xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=“http//java.sun.com/xml/ns/j2ee/web-app_2.4.xsd”
version=“2.4”>
<servlet> 
<servlet-name>Hello World Servlet</servlet-name>
<servlet-class>S1</servlet-class>
    
<init-param> 
<param-name>lecturersEmail</param-name>
<param-value>paula.fonseca@qmul.ac.uk</param-value>
</init-param>
    
</servlet>
</web-app>

These are parameter name-value pairs,写在servlet标签中

调用ServletConfig的初始参数
getServletConfig().getInitParameter(“lecturersEmail”)
设置ServletConfigAttribute的方法

属性是键值对:name是string,但是属性值为对象

获取属性:
getAttribute(String);
设置属性:
setAttribute(String,Object);

在调用servlet之前设置属性对象

ServletContext

one per web application, 通常Web applications normally have several servlets/JSPs

ServletContext对象:servlet容器在启动时会加载web应用,并为每个web应用创建唯一的servlet context对象,可以把ServletContext看成是一个Web应用的服务器端组件的共享内存,在ServletContext中可以存放共享数据。

Used to access web application parameters that need to be seen by all servlets/JSPs in the application.

ServletContext object created and set up when web application is deployed.当web应用被部署时,servletcontext被创建

写在Web. xml中标签外面

指定context parameters的例子

<web-app ...> ...
<servlet>
<servlet-name>...</servlet-name>
<servlet-class>...</servlet-class>
<init-param><param-name>...</param-name>
<param-value>...</param-value></init-param>
</servlet> ... + other servlets
    
<context-param>
<param-name>HOP_Email</param-name>
<param-value>g.tyson@qmul.ac.uk</param-value>
</context-param>
...
</web-app>

These are parameter name-value pairs,并且不在servlet标签中

调用ServletContext的初始参数(web app parameters

Same name for get method as when accessing ServletConfig object.

  • 两种方法访问ServletContext

直接访问

getServletContext().getInitParameter()

from ServletConfig(只有ServletConfig对象作为参数传递):

getServletConfig().getServletContext().getInitParameter()

Advanced Servlets

REDIRECTING AND FORWARDING

REDIRECTING

重定向(Redirect)就是通过各种方法将各种网络请求重新定个方向转到其它位置

使用respond对象

其中URL可以为jsp文件、HTML文件

response.sendRedirect("http://localhost:8080/");//参数是String,不是object
response.sendRedirect(Retrieve?+request.getQueryString());//如果请求中带参数

Container sends response “302” status code with a location header containing the new URL as value

Browser reads response and location header, and makes new request using the URL.

调用这个方法后会向client发一个状态码为302 的响应,这个响应中包含新的URL,一旦客户端接收就会立刻像新URL进行请求(这个请求是原始的请求的重发)

特点就是:重定向其实就是两个请求,而且两个请求还是连续的,因为速度很快,所以你看不出这是两个请求。

客户端的浏览器地址栏中的URL会发生改变

Use a redirect if other file (servlet, JSPs, POJ) is in another web application.

如果其他文件不在当前webapp中就要使用重定向。

返回的状态码可能为301或者302(不做区分)

Status codes sent back by the container:

301 moved permanently

302 moved temporarily

forward (Request Dispatch)

Servlet wants request to go to a different servlet or JSP in the same web application.

登录验证Servlet在完成了验证之后会把请求转发给显示主页的Servlet。

RequestDispatcher view = request.getRequestDispatcher(“display.jsp”);//获取转发器
//request.getRequestDispatcher(“其他servlet的URL地址”);//获取转发器
view.forward(request, response);//进行转发

客户端的浏览器地址栏中的URL不会发生改变

二者区别:

重定向:两个web-app,不能共享;请求转发:一个web-app;

重定向:两个请求;请求转发:一个请求;

重定向:不能共享request和response;请求转发:可以共享request和response;

重定向:浏览器地址栏发生变化;请求转发:浏览器地址栏不发生变化;

重定向:调用目标servlet的doGet()方法;请求转发:由发出请求的方法决定到底调用目标servlet的哪个方法;

重定向:可以跳转到百度;请求转发:只能在当前上下文内容跳转。

HTTP REQUEST AND RESPONSE HEADERS

HTTP Request Headers

As with form data, HTTP request headers can be extracted from the HttpServletRequest object.

String getHeader(String)

getContentLength()

getContentType()

getMethod()

getRequestURI()

getProtocol()

access all headers using:

Enumeration getHeaderNames().

Server Response

– a status line (containing version, status code + message);

– some response headers;

– a blank line;

– the document

image-20221130160111105

servlet可以通过操作状态行和响应头来执行各种任务。

Status Codes

HTTP Status Codes: 由服务器返回给客户端软件,以指示请求的结果。

200–OK : The request sent by the client was successful.

301–Moved Permanently : The resource has permanently moved to a different URI.

303–See Other : The requested response is at a different URI and should be accessed using a GET command at the given URI.

400–Bad Request : The syntax of the request was not understood by the server.

403–Forbidden : The server has refused to fulfill the request.

servlet只需要设置状态码,因为版本是由服务器决定的,消息与状态码相关联。

最简单的方法:

response.setStatus(int)

如果响应包含特殊的状态码和文档,则必须在通过PrintWriter返回任何内容之前调用setStatus()

使用HttpServletResponse类中定义的常量:HttpServletResponse.SC_NOT_FOUND(404)

两种状态码对应独特的方法:

  • public void sendError(int sc, String message)

    • This sets the status code plus a short message.

  • public void sendRedirect(String url)

    • Generates a 301 response, along with a Location Header giving the URL of the new document that the browser should now request.

Setting Response Headers
res.setHeader(String header, String value);

Allow, Content-Encoding, Content-Language, Content-Length, Content-Type, Date, Expires, Last-Modified, Location, **Refresh, Set-Cookie, **WWW-Authenticate

一些常见头部可以直接设置:

setContentType(String type)
setContentLength(int length)
addCookie(String cookie)
sendRedirect(String encodedURL)
Setting the Refresh header

指示浏览器请求更新页面所需的时间,表示浏览器应该在多少时间之后刷新文档,以秒计,浏览器会重新请求一次页面

或者每多少秒,重定向到别的页面一次。

res.setHeader(Refresh,30);
res.setHeader(Refresh,5; URL=http://host/path”);

Sending status code 204(SC_NO_CONTENT) stops browser reloading further:

 res.setStatus(204);
res.setStatus(response.SC_NO_CONTENT);

204 – No Content : The request was successful but does not require the return of an entity-body.

Setting the Content–Type header

Content-Type tells browser what sort of document is being sent.

通常就用这俩text/html text/plain(显示源代码)

必须在写入到OutputStream之前设置

text/plain, text/html, text/css

image/gif, image/png, image/jpeg, image/tiff

application/pdf, application/msword

application/vnd.ms-excel

video/mpeg, video/quicktime

根据客户端希望得到的响应数据类型,设置数据类型

获取数据类型:String req.getHeader(“Accept”)

Accept属于http请求头,描述客户端希望接收的响应body 数据类型。就是希望服务器返回什么类型的数据。

String types = req.getHeader(Accept);
if (types.toLowerCase().contains(“image/jpeg”)) {
res.setContentType(“image/jpeg”);
// Send a jpeg file.
} 
else {
res.setContentType(“image/gif”);
// Send a gif file.
}

SESSION HANDLING

HTTP 是一种”无状态”协议,这意味着每次客户端检索网页时,客户端打开一个单独的连接到 Web 服务器,服务器会自动不保留之前客户端请求的任何记录

Keeping the state of a user over a sequence of requests

有以下三种方式来维持 Web 客户端和 Web 服务器之间的 session 会话

  1. Cookies:
  1. check for cookie in request header;

  2. create a cookie and send back to client

一个 Web 服务器可以分配一个唯一的 session 会话 ID 作为每个 Web 客户端的 cookie,对于客户端的后续请求可以使用接收到的 cookie 来识别。

jakarta.servlet.http API contains a Cookie class to get and set cookies and their attributes.

  1. URL-rewriting

​ attach a session id to the end of URLs

http://host/path/file.html;sessionid=1234

  1. The HttpSession API is a high-level interface built on top of cookies or URL-rewriting:

Session tracking in servlets

Session Tracker uses a session ID to match users up with Session objects on the server side.

会话ID是用户第一次访问服务器时创建的字符串,然后作为cookie发送给浏览器

JSESSIONID=0ABC5019DE56

session依赖Cookie,但如果浏览器关闭了对Cookie的支持。这时可以使用URL重写的方式

Tracks the users’ session by including the session ID in all URLs the users will communicate with the server application.

HttpSession objects

servlet引擎保持一个HttpSession对象表:

使用“session id”作为键从表中查找对象。

cookie or rewritten URL中提取session:

Extract or create a new session object.

HttpSession session = request.getSession();

HttpSession对象本身就是哈希表,用于在与用户的“会话”期间存储数据。

image-20221130165419111

当客户端第一次访问服务器时,服务器会为客户端创建一个session对象,然后把session对象放到session池中,在响应时把sessionId通过Cookie响应给客户端。注意,只有在第一次访问时,服务器才会创建session,给客户端响应sessionId。从此以后就不会了!

当客户端再次访问服务器时,会在请求中带着sessionId给服务器,服务器通过sessionId到session池中找到session对象,这就可以完成会话跟踪了。也就是说,服务器端保存的是session对象,而客户端只有sessionId。每次访问都需要通过客户端的sessionId来匹配服务器端的session对象!这样用户在session中保存的数据就可以再次被使用了。

sessionId是服务器通过Cookie发送给客户端浏览器的,这个Cookie的maxAge为-1,即只在浏览器内存中存在。如果你关闭所有浏览器窗口,那么这个Cookie就会消失了

  • 通常会判断是不是第一次创建session:
HttpSession session = request.getSession();
if(session.isNew()) { // e.g., add username to it }
else {
// e.g., add new information, extract previous
// stored information from the session object
}
HttpSession session = request.getSession();
if (session.isNew()) {
String uName = request.getParameter(“fullName”);
session.setAttribute(“userName”,uName);
// [session.setAttribute(String key, Object value)]//第一次建立session,设置name
}
HttpSession session = request.getSession();
if (session.isNew()) { ... }
else {
int uAge = Integer.parseInt(request.getParameter(“age”));
session.setAttribute(“userAge”, uAge);
String name = (String) session.getAttribute(“userName”);//再次访问时,获取session中属性
...
out.println(“hello ” + name + “ your age is ” + uAge);
}
Can a session track over many pages?

memory is held in the Session object:

The session objects are held by the container (Tomcat)

设置最长session持续时间

When a session object has not been accessed for some time, when can it be discarded from the table of session objects?

HttpSession session = request.getSession();
session.setMaxInactiveInterval(2*60*60); // two hours(秒)
//设置为赋值代表session永远不会超时

服务器记录上一次访问的时间,如果时间间隔超过getMaxInactiveInterval()的值,则丢弃该session。

invalidate() and **logout() **explicitly delete the session object

We can also set this (in minutes) in the deployment descriptor:(分钟)

<session-config>
<session-timeout>30</session-timeout>
</session-config>

URL Rewriting

查找将被写回浏览器的所有链接,并将其写入,使其包含会话ID。

<a href=“/store/catalog”> could become
<a href=“/store/catalog”;jsessionid$DA334rsg23>

URL重写很好理解,就是在用户点击的超链接和提交的表单上添加参数。例如:If the user clicks on this link, the rewritten form of the URL is sent to the server

The session ID is then used to obtain the session object

用**response.**encodeURL()实现URL rewriting,而不是直接加

image-20221130183850621

这个方法有两个功能(在需要rewrite时候自动加 sessionID)

  • Checks if the URL needs to be rewritten:

    If the server detects that the browser is supporting cookies, then the URL is not rewritten

  • If URL needs to be rewritten, then the session ID is inserted into the URL and the rewritten URL returned.

两种情况使用这个方法:

  1. When URLs are embedded in a generated web page(URL在生成的网站中,如上)
String originalURL = someRelativeOrAbsoluteURL;
String encodedURL = res.encodeURL(originalURL);
out.println(<A HREF=\”” + encodedURL + “\”>...</A>);
  1. When using sendRedirect( when placed in location header))
String originalURL = someURL; 
String encodedURL = res.encodeRedirectURL(originalURL);
res.sendRedirect(encodedURL);

如果你不重写,且不支持cookie,会造成user’s session丢失