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”)
设置ServletConfig中Attribute的方法

属性是键值对: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丢失