Request Response

1 前言

1.1 内容概要

  1. 理解Request、Response和HTTP报文之间的关系
  2. 掌握通过Request能够获得的信息
  • 请求URL、URI、请求协议
  • 请求头、客户机和主机
  • 请求参数
  1. 掌握通过Response能够完成的设置
  • 响应中文乱码问题
  • 响应(Json)字符串、图片(文件)
  • 了解特殊的响应头
  1. 逐步通过反射能够解决一些通用问题

1.2 前置知识准备

  • HTTP 请求报文和响应报文
    • 请求报文和响应报文分别包含哪些部分
  • 反射reflect
    • 获得Class对象
    • 获得成员变量并给成员变量赋值
    • 获得方法并且能够调用指定方法
  • MyBatis的基本使用
  • 通过form表单分别构造get请求和post请求,能构造文件上传的请求
  • 解析字符串获得其中的信息

1.3 请求报文

请求报文的组成部分

  • 请求行
    • 请求方法
    • 请求的URL http://localhost:8080/hello
    • 请求协议
  • 请求头
    • 格式是key:value
    • 比较特殊的请求头:
      • Content-Type 由浏览器提供给服务器的正文类型
      • Accept 浏览器期望从服务器获得正文的类型( 服务器提供给浏览器的正文类型)
      • Host 主机ip
  • (空行)
  • 请求正文
    • 普通的Get请求和form表单提供的Get/Post请求 如果携带了参数它的格式是 key1=value1&key2=value2&key3=value3等
    • 携带Json数据的post请求 {}或[]
    • 图片/文件
POST http://101.43.69.31:8083/admin/auth/login HTTP/1.1
Host: 101.43.69.31:8083
Connection: keep-alive
Content-Length: 45
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.78
Content-Type: application/json;charset=UTF-8
Origin: http://101.43.69.31:8080
Referer: http://101.43.69.31:8080/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6{"username":"admin123","password":"admin123"}

分析一个请求体

对应的form表单如下

<form action="/hello" enctype="multipart/form-data" method="post"><input type="text" name="username"><br><input type="text" name="password"><br><input type="file" name="avatar"><br><input type="submit" value="Submit">
</form>

在这里插入图片描述

1.4 响应报文

响应报文的组成

  • 响应行
    • 协议
    • 状态码
  • 响应头
    • 格式是key=value
    • Content-Type 服务器提供给浏览器的正文类型,后面通常会跟charset,比如application/json;charset=utf-8
  • (空行)
  • 响应正文
    • 如果是json,Content-Type中通常是application/json
HTTP/1.1 200
Vary: accept-encoding,origin,access-control-request-headers,access-control-request-method,accept-encoding
Set-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0; Expires=Sun, 12-Feb-2023 06:51:56 GMT
Set-Cookie: JSESSIONID=24287278-5ebb-407d-a3f7-56b74782c4c7; Path=/; HttpOnly
Access-Control-Allow-Origin: *
Content-Type: application/json;charset=UTF-8
Date: Mon, 13 Feb 2023 06:51:56 GMT
Content-Length: 200{"errno":0,"data":{"adminInfo":{"nickName":"admin123","avatar":"https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif"},"token":"24287278-5ebb-407d-a3f7-56b74782c4c7"},"errmsg":"成功"}

1.5 反射

1.5.1 获得Class对象

获得Class对象的方式

定义了一个类,这个类,处于com.cskaoyan.service包下,类名为UserServiceImpl,我们想要获得这个Class对象,如何获得呢?

  • UserServiceImpl.class

UserServiceImpl userService = new UserServiceImpl();
useService.getClass();
  • Class.forName("com.cskaoyan.service.UserServiceImpl")

  • 类加载器

Class<UserServiceImpl> clazz1 = UserServiceImpl.class;UserServiceImpl userService = new UserServiceImpl();
Class<? extends UserServiceImpl> clazz2 = userService.getClass();Class<?> clazz3 = Class.forName("com.cskaoyan.service.UserServiceImpl");

如果我们想要使用一些通用性的代码,我们用哪种方式?

Class.forName

public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {InputStream inputStream = DriverDemo.class.getClassLoader().getResourceAsStream("parameter.properties");Properties properties = new Properties();properties.load(inputStream);String driver = (String) properties.get("driver");Class<?> driverClass = Class.forName(driver);Object instance = driverClass.newInstance();
}
# parameter.properties
# driver=com.cskaoyan.service.UserServiceImpl
driver=com.mysql.cj.jdbc.Driver

使用的mysql版本是5.7,对应的驱动是com.mysql.jdbc.Driver

如果使用mysql版本8,对应的驱动com.mysql.cj.jdbc.Driver

1.5.2 获得成员变量并给成员变量赋值

// 通过class能够获得实例
// 可以直接使用newInstance方法
Object instance1 = clazz3.newInstance();
// 可以先获得构造器(构造方法),通过构造方法实例化
Constructor<?> constructor = clazz3.getDeclaredConstructor();
Object instance2 = constructor.newInstance();// 利用反射方式获得Field,通过反射的方式复制
Field usernameField = clazz3.getDeclaredField("username"); // user3.setUsername("")
usernameField.setAccessible(true);
usernameField.set(instance2,"zhangsan");// instance2的username这个成员变量赋值zhangsan// instance2.username = "zhangsan";// 并不建议直接通过反射的方式给成员变量赋值,我们建议使用set方法做赋值

1.5.3 获得方法并且能够调用方法

//UserServiceImpl userServiceImpl = (UserServiceImpl) instance1;
//userServiceImpl.setUsername("lisi");
// 通过反射的方式实现通用性的设置Properties properties = new Properties();
properties.load(ReflectExecution.class.getClassLoader().getResourceAsStream("parameter.properties"));
String className = (String) properties.get("className");
String method = (String)properties.get("method");
String value = (String)properties.get("value");Class<?> clazz = Class.forName(className);
Object instance = clazz.newInstance();//instance的username成员变量是否是lisi
Method declaredMethod = clazz.getDeclaredMethod(method, String.class);
declaredMethod.setAccessable("true");
Object invoke = declaredMethod.invoke(instance, value);
// method.invoke

Object invoke = method.invoke(instance,args)

变量名类型含义
methodMethod → clazz.getDeclaredMethod通过反射获得的Method
instanceObject 1. new XXX ; 2.反射,比如clazz.newInstance就是实例(对象)
argsObject[]参数,把所有的参数封装为数组
invokeObjectinstance对象执行method的返回值
/*** 通过反射的方式执行UserServiceImpl中的sayHello*/
public static void main(String[] args) throws Exception{Properties properties = new Properties();properties.load(ReflectExecution.class.getClassLoader().getResourceAsStream("parameter.properties"));String className = (String) properties.get("className");String method = (String)properties.get("method");String value = (String)properties.get("value");//String parameter1 = (String)properties.get("parameter1");//String parameter2 = (String)properties.get("parameter2");Object parameter1 = properties.get("parameter1");Object parameter2 = properties.get("parameter2");Class<?> clazz = Class.forName(className);Object instance = clazz.newInstance();Method setUsername = clazz.getDeclaredMethod("setUsername", String.class);setUsername.invoke(instance, value);//Method declaredMethod = clazz.getDeclaredMethod(method, String.class,String.class);Method declaredMethod = clazz.getDeclaredMethod(method, parameter1.getClass(),parameter2.getClass());//执行sayHello方法Object result = declaredMethod.invoke(instance, new Object[]{parameter1, parameter2});
}

1.6 MyBatis

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.xml"));
// 在整个应用程序中维护这个对象,想要在多个Servlet中共享这个对象
// ServletContext
// 提供一个Servlet,loadOnStartup为正数,且数值比较小,这个Servlet在应用程序启动过程中就会率先开始初始化,我们就可以利用生命周期的init方法去初始化SqlSessionFactory实例,并且将其放入到ServletContext中

2 概述

Request 请求

Response 响应

在Servlet中的service以及HttpServlet的doGet、doPost方法中的形参

public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {}

在Tomcat的时候就会产生Request,同时也会产生Response

可以这样子理解,当我们通过浏览器(或客户端)发送请求,在服务器应用中整个过程中的信息流通都是通过Request、Response流通的

Request:获得提供的信息,主要使用的是其getXXX方法

Response:设置信息提供给客户端(浏览器),主要使用的是其setXXX方法

image-20230313104005727

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

每次发起请求,其实产生一组新的Request和Response对象

记住一个点:使用Request主要为了输入,使用Response主要为了输出

服务器将请求报文封装为Request对象,方便我们通过Request提供的方法来直接获得请求报文中的信息;服务器将响应的信息封装为Response对象,当我们通过Response对象来完成各种设置之后,服务器响应给浏览器的时候将Response对象转换为响应报文

为了提供统一的规范,提供统一的规范接口ServletRequest和ServletResponse,为什么提供的是接口,接口能够提供统一的规范

3 Request

HttpServletRequest 获得HTTP请求报文中携带的信息

POST http://101.43.69.31:8083/admin/auth/login HTTP/1.1
Host: 101.43.69.31:8083
Connection: keep-alive
Content-Length: 45
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.78
Content-Type: application/json;charset=UTF-8
Origin: http://101.43.69.31:8080
Referer: http://101.43.69.31:8080/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6{"username":"admin123","password":"admin123"}

请求报文的组成

  • 请求行
  • 请求头
  • (空行)
  • 请求体

3.1 请求行

POST http://101.43.69.31:8083/admin/auth/login HTTP/1.1
GET http://localhost:8080/demo3/hello?username=zhangsan HTTP/1.1
信息内容方法返回值说明
请求方法POST、GETgetMethod()String获得请求方法
URLhttp://101.43.69.31:8083/admin/auth/logingetRequestURL()StringBuffer获得请求URL
URI/admin/auth/login、/demo3/hellogetRequestURI()String获得请求URI
context-path/demo3getContextPath()String获得上下文
服务器ip101.43.69.31、localhostgetLocalAddr()String获得ip
端口号8083、8080getLocalPort()int获得端口号
QueryStringusername=zhangsangetQueryString()String获得Get请求的查询字符串
协议HTTP/1.1getProtocol()String获得通讯协议

代码

@WebServlet("/line")
public class LineServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {// http://localhost:8080/demo3/line?username=zhangsan// http://192.168.0.180:8080/demo3/line?username=zhangsan// 192.168.0.180可以修改为自己的ip// 请求方法String method = request.getMethod();// 请求URL和URIString url = request.getRequestURL().toString();String uri = request.getRequestURI();// 服务器本地的IP和端口号String localAddr = request.getLocalAddr();int localPort = request.getLocalPort();// 应用的上下文路径String contextPath = request.getContextPath();// 请求协议String protocol = request.getProtocol();}
}

3.2 请求头

Host: 101.43.69.31:8083
Connection: keep-alive
Content-Length: 45
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.78
Content-Type: application/json;charset=UTF-8
Origin: http://101.43.69.31:8080
Referer: http://101.43.69.31:8080/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6

请求头中的信息,都是key:value的形式

  • 可以知道有哪一些请求头 Key → 获得所有的请求头
  • 也可以知道这些请求头当中的值是什么 Value → 获得特定Key对应的Value
方法返回值说明
getHeaderNames()Enumeration<String>获得所有的请求头,可以通过遍历的方式来使用,使用方式类似于Iterator
getHeader(String)String传入的是请求头的Key,返回的是请求头的value

构造一个请求,请求报文

GET http://192.168.0.180:8080/demo4/header HTTP/1.1
Host: 192.168.0.180:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.41
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6

代码

@WebServlet("/header")
public class HeaderServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {Enumeration<String> headerNames = request.getHeaderNames();System.out.println("所有的请求头");while (headerNames.hasMoreElements()) {String headerName = headerNames.nextElement();System.out.println(headerName);}// 想要获得Host请求头的值String hostValue = request.getHeader("Host");System.out.println("hostValue = " + hostValue);}
}

控制台输出的结果

所有的请求头
host
connection
cache-control
upgrade-insecure-requests
user-agent
accept
accept-encoding
accept-language
hostValue = 192.168.0.180:8080

可以直接输入对应的key-value

Enumeration<String> headerNames = request.getHeaderNames();
System.out.println("所有的请求头");
while (headerNames.hasMoreElements()) {String headerName = headerNames.nextElement();String headerValue = request.getHeader(headerName);System.out.println(headerName + ":" + headerValue);
}

注意事项:请求头大小写不敏感

// 想要获得Host请求头的值
String hostValue = request.getHeader("Host");
String hostValue2 = request.getHeader("host");
String hostValue3 = request.getHeader("HOST");

3.3 请求体

请求报文是

POST http://localhost:8080/demo5/body HTTP/1.1
User-Agent: PostmanRuntime/7.29.2
Accept: */*
Host: localhost:8080
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 17username=zhangsan

请求体的部分

username=zhangsan

字节流InputStream、字符流Reader

内容方法返回值描述
字节流getInputStream()ServletInputStream获得字节流
字符流getReader()BufferedReader获得字符流

字节流

@WebServlet("/body1")
public class BodyServlet1 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {ServletInputStream inputStream = request.getInputStream();File file = new File("D:\\tmp", "1.txt");FileOutputStream outputStream = new FileOutputStream(file);int length = 0;byte[] bytes = new byte[1024];while ((length = inputStream.read(bytes)) != -1) {outputStream.write(bytes,0,length);}outputStream.close();inputStream.close();}
}

字符流

@WebServlet("/body2")
public class BodyServlet2 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {BufferedReader reader = request.getReader();File file = new File("D:\\tmp", "2.txt");BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file));String str = null;while ((str = reader.readLine()) != null) {bufferedWriter.write(str);}bufferedWriter.flush();bufferedWriter.close();reader.close();}
}

3.4 特殊信息

3.4.1 客户机和服务器主机信息(了解)

请求是从客户机发到服务器的,在服务器中处理信息的获得,那么对于服务器,本地local是服务器,远程remote是客户机

我们在Servlet中可以获得本地的IP和Port,也可以获得远程IP和Port

信息方法返回值
本地(服务器)IPgetLocalAddr()String
本地(服务器)端口号getLocalPort()int
远程(客户机)IPgetRemoteAddr()String
远程(客户机)端口号getRemotePort()int
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 获得服务器端的信息String localAddr = req.getLocalAddr();int localPort = req.getLocalPort();// 获得客户机端的信息String remoteAddr = req.getRemoteAddr();int remotePort = req.getRemotePort();System.out.println("由" + remoteAddr + ":" + remotePort + "发送到" + localAddr + ":" + localPort);
}

3.5 ★请求参数

请求参数的场景:

  • 请求参数 在请求行中

    • getQueryString()
  • 请求参数 在请求体中

    • getInputStream()/getReader()

指的是key=value&key=value

@WebServlet("/origin")
public class OriginServlet extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doGet(req, resp);}@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {String parameterString = null;if ("GET".equals(request.getMethod())) {parameterString = request.getQueryString();}else {//ServletInputStream inputStream = request.getInputStream();//byte[] bytes = new byte[1024];//inputStream.read(bytes);//parameterString = new String(bytes);BufferedReader reader = request.getReader();parameterString = reader.readLine();}Map<String,String> parameterMap = executeMap(parameterString);String username = parameterMap.get("username");System.out.println(username);}private Map<String, String> executeMap(String parameterString) {HashMap<String, String> map = new HashMap<>();if (parameterString == null || "".equals(parameterString)) {return map;}//parameterArray[0] = "username=zhangsan"//parameterArray[1] = "password=lisi"String[] parameterArray = parameterString.split("\\&");for (String parameter : parameterArray) {int index = parameter.indexOf("=");String key = parameter.substring(0,index);String value = parameter.substring(index + 1, parameter.length());map.put(key, value);}return map;}
}

3.5.1 直接封装

但是实际开发中并不需要我们自己这么做,因为Request已经帮我们封装了可以直接使用的方法了

request.getParameterXXX这样的一些方法

方法返回值说明
getParameterNames()Enumeration<String>获得所有的key
getParameterMap()Map<String,String[]>获得所有的请求参数
getParameter(String)String获得第一个值
getParameterValues(String)String[]获得所有值

request对于请求参数的封装实际上,封装为一个Map<String,String[]>

在这里插入图片描述

/*** GET http://localhost:8080/demo7/parameter?key1=a1&key1=a2&key1=a3&key2=b1&key2=b2&key2=b3&key3=c1&key3=c2&key3=c3* key1: a1,a2,a3* key2: b1,b2,b3* key3: c1,c2,c3*/
@WebServlet("/parameter")
public class ParameterServlet extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doGet(req, resp);}@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {Map<String, String[]> parameterMap = request.getParameterMap();Enumeration<String> parameterNames = request.getParameterNames();while (parameterNames.hasMoreElements()) {String parameterName = parameterNames.nextElement();System.out.println("parameterName = " + parameterName);}String key1 = request.getParameter("key1");System.out.println("key1 = " + key1);String[] key1s = request.getParameterValues("key1");System.out.println("key1s = " + Arrays.asList(key1s));}
}

3.5.2 引用类型

GET http://localhost:8080/demo7/register?username=root&password=123456&age=20&birthday=2000-10-15 HTTP/1.1

将请求参数封装为一个引用类型的对象,比如User

  • 个性化(并不是一个褒义词)

    • User user = new User();
      user.setUsername(request.getParameter("username"));
      user.setPassword(request.getParameter("password"));
      
  • 反射

    • BeanUtils.transfer(instance,parameterMap);//通过自己写的工具类,进行转换
      
    • public class WdBeanUtils {@SneakyThrowspublic static <T> T transfer(Class<T> clazz, Map<String, String[]> parameterMap) {// 先创建clazz对应的实例T instance = clazz.newInstance();// 给其成员变量赋值 →// 反射直接给成员变量赋值 field.set(instance,value)// 反射通过set方法间接给成员变量赋值 setMethod.invoke(instance,value)Iterator<String> iterator = parameterMap.keySet().iterator();// 反射直接给field赋值// giveFieldValue(clazz, parameterMap, instance, iterator);// 反射直接调用set方法,给set方法传入形参,通过形参给field赋值giveMethodValue(clazz, parameterMap, instance, iterator);// 赋值完成之后给其返回去return instance;}private static <T> void giveMethodValue(Class<T> clazz, Map<String, String[]> parameterMap, T instance, Iterator<String> iterator) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {while (iterator.hasNext()) {String fieldName = iterator.next();// 拼接set方法名// username → setUsername// password → setPassword// fieldName 首字母大写char[] chars = fieldName.toCharArray();chars[0] -= 32;String upperFirst = new String(chars);String setMethodName = "set" + upperFirst;// 获得形参的类型 → 成员变量的类型Field field = clazz.getDeclaredField(fieldName);Class<?> type = field.getType();Method method = clazz.getDeclaredMethod(setMethodName, type);//set方法String[] value = parameterMap.get(fieldName);if (field.getType().isArray()) {method.invoke(instance, (Object) value);} else {//method.invoke(instance, value);method.invoke(instance, value[0]);}}}private static <T> void giveFieldValue(Class<T> clazz, Map<String, String[]> parameterMap, T instance, Iterator<String> iterator) throws NoSuchFieldException, IllegalAccessException {while (iterator.hasNext()) {// 请求参数名 → 成员变量名String fieldName = iterator.next();Field field = clazz.getDeclaredField(fieldName);field.setAccessible(true);// 判断field对应的成员变量的类型是否是数组,// 如果是数组,给其的值是value// 如果不是数组,给其的值是value[0]String[] value = parameterMap.get(fieldName);if (field.getType().isArray()) {field.set(instance, value);} else {field.set(instance, value[0]);}}}
      }
      
  • BeanUtils

    • 引入依赖commons-beanutils

    • BeanUtils.copyProperties(instance, parameterMap);//直接使用其提供的copyProperties方法
      

3.5.3 Post请求请求参数乱码

乱码问题:编解码不一致

构造一个form表单,一个Get请求,一个是Post请求,分别去获得请求参数,查看通过Get和Post请求获得的请求参数

<h1>GET请求</h1>
<form action="/demo8/parameter" method="get"><input name="username"><input type="submit">
</form>
<h1>POST请求</h1>
<form action="/demo8/parameter" method="post"><input name="username"><input type="submit">
</form>
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {request.setCharacterEncoding("utf-8");String username = request.getParameter("username");System.out.println("username = " + username);
}

3.5.4 文件上传

在实际的开发过程中,有些场景需要做文件上传,比如上传头像,上传商品的描述图片等。

我们首先来看文件上传的请求的form表单如何构造

然后来看其对应的请求报文是什么样子的,然后我们在做进一步的分析

创建了一个form表单如下

<form action="/demo9/upload" enctype="multipart/form-data" method="post">用户:<input type="text" name="username"><br>头像:<input type="file" name="avatar"><br><input type="submit" name="提交">
</form>

显示效果如下

在这里插入图片描述

然后构造请求数据

在这里插入图片描述

接下来发送请求,并且通过fiddler来抓取请求报文

POST http://localhost:8080/demo9/upload HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 1148487
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="110", "Not A(Brand";v="24", "Microsoft Edge";v="110"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://localhost:8080
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1pgACDbBGFGBPZYi
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost:8080/demo15/upload.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: Idea-dcbc35b9=2a0b80c2-0c01-4b8f-846d-d0915e770aa6------WebKitFormBoundary1pgACDbBGFGBPZYi
Content-Disposition: form-data; name="username"wangdao
------WebKitFormBoundary1pgACDbBGFGBPZYi
Content-Disposition: form-data; name="avatar"; filename="logo.png"
Content-Type: image/pngPNGIHDR             v    	pHYs  .#  .#x ?v  _iCCPPhotoshop ICC profile  xڭ ?( q   ?]  n0 ʟ 3  (ud  L   z _  7)  A   20$ 后面省略了很多很多内容

我们来分析一下上面的请求报文,首先能够进入眼帘的是,响应报文中出现了一堆未知的字符

这些内容其实包含了图片的字节数据

在这里插入图片描述

另外在请求信息之间出现了这样的一块内容,出现了两次

------WebKitFormBoundary1pgACDbBGFGBPZYi

这块内容其实就是分隔符,用来分割普通的请求数据和文件的请求数据的,在上面的请求头Content-Type的值中也可以看到。如果我们拿到请求体中的内容,通过分隔符,也就可以拿到图片的字节数据,拿到字节数据的话就可以通过OutputStream做写出了

但是呢,这个过程如果我们自己来完成的话,非常的复杂。在Servlet3.0之前,我们可以借助第三方工具来封装,比如commons-fileupload(FileUpload – Using FileUpload (apache.org)),但是这个过程仍然是非常繁琐。

Servlet3.0提供了对文件上传的支持,通过@MultipartConfig标注和HttpServletRequest提供的方法可以完成文件部分的获取,我们当前可以使用Request中的getPart方法直接拿到其图片部分

Part getPart(String var1) throws IOException, ServletException;

该方法的参数需要传入一个字符串值,该值是请求参数名,如果拿的是上面请求中图片文件的信息,则需要传入 avatar

<!--intput标签中的name属性值其实就是请求参数名,分隔符中name对应的值也是这个-->
头像:<input type="file" name="avatar"><br>

获得Part对象,通过Part中提供的方法可以获得其他具体的信息

方法返回值说明
getInputStream()InputStream获得字节输入流,可以读取字节数据将其保存下来
getContentType()String获得正文类型,比如png图片,其值为image/png
getSize()long获得文件字节大小
getName()String获得请求参数名(这里就是avatar)
getSubmittedFileName()String获得上传的原始文件名(这里是logo.png)
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {Part avatar = req.getPart("avatar");InputStream inputStream = avatar.getInputStream();String contentType = avatar.getContentType();long size = avatar.getSize();String name = avatar.getName();String submittedFileName = avatar.getSubmittedFileName();
}

如果要将图片以原始文件名保存在web资源根目录,我们可以写这样的代码

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {Part avatar = req.getPart("avatar");InputStream inputStream = avatar.getInputStream();String submittedFileName = avatar.getSubmittedFileName();byte[] bytes = new byte[1024];int length = 0;FileOutputStream outputStream = new FileOutputStream(new File(getServletContext().getRealPath(submittedFileName)));while ((length = inputStream.read(bytes)) != -1) {outputStream.write(bytes, 0, length);}inputStream.close();
}

3.6 Request做请求转发(了解)

实际开发过程中,基本不会再涉及到,当前基本上不再使用JSP技术,JSP技术使用过程中会做请求转发。

JSP,而JSP是一种特殊的Servlet

但是jsp目前已经没有什么使用场景了。目前架构主要是前后端分离。所以关于转发了解即可。

在这里插入图片描述

如果是jsp的话,jsp其实也是一个servlet

在这里插入图片描述

可以通过IDEA中的Tomcat的CATALINA_BASE看一下其生成的jsp 相关的java文件

在这里插入图片描述
请求转发,其实就是在一个Servlet处理业务,处理完业务,继续由另外一个Servlet处理业务。

其中一个关注点,转发的两个请求之间的数据共享

Request域,转发的请求之间数据共享

  • request.setAttribute
  • request.getAttribute

4 Response

响应报文的封装,设置响应报文

HTTP/1.1 200
Vary: accept-encoding,origin,access-control-request-headers,access-control-request-method,accept-encoding
Set-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0; Expires=Sun, 12-Feb-2023 06:51:56 GMT
Set-Cookie: JSESSIONID=24287278-5ebb-407d-a3f7-56b74782c4c7; Path=/; HttpOnly
Access-Control-Allow-Origin: *
Content-Type: application/json;charset=UTF-8
Date: Mon, 13 Feb 2023 06:51:56 GMT
Content-Length: 200{"errno":0,"data":{"adminInfo":{"nickName":"admin123","avatar":"https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif"},"token":"24287278-5ebb-407d-a3f7-56b74782c4c7"},"errmsg":"成功"}

响应报文的组成

  • 响应行
  • 响应头
  • (空行)
  • 响应体(正文)
    • 字符信息
    • 字节信息

4.1 响应行

协议就不设置了,设置一下响应状态码

方法名参数说明
setStatus(int)参数就是状态码设置响应状态码
@WebServlet("/line")
public class LineServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException {response.setStatus(302);}
}

发送请求后接收到的响应报文

HTTP/1.1 302
Content-Length: 0
Date: Wed, 15 Feb 2023 03:20:18 GMT
Keep-Alive: timeout=20
Connection: keep-alive

4.2 响应头

响应头也是key:value的格式,提供了通用的方法,可以设置响应头的key和value;也提供了一些特定的方法,特定的方法做的事情,就是设置特定响应头的值

方法参数说明
setHeader(String,String)参数1提供key,参数2提供value通用的方法
@WebServlet("/header")
public class HeaderServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException {// 第一个参数是key,第二个参数是valueresponse.setHeader("custom-header","abcdef");}
}
HTTP/1.1 200
custom-header: abcdef
Content-Length: 0
Date: Wed, 15 Feb 2023 03:28:04 GMT
Keep-Alive: timeout=20
Connection: keep-alive

4.3 响应体

响应体(正文)

可以使用字符流,也可以使用字节流。

方法返回值描述
getWriter()PrintWriter字符流
getOutputStream()ServletOutputStream字节流

字符流

@WebServlet("/body1")
public class BodyServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//resp.getWriter().println("hello world");//resp.getWriter().append("hello world");resp.getWriter().write("hello world");}
}
@WebServlet("/body2")
public class BodyServlet2 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {ServletOutputStream outputStream = resp.getOutputStream();InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("dlrb.jpg");byte[] bytes = new byte[1024];int length = 0;while ((length = inputStream.read(bytes)) != -1) {outputStream.write(bytes,0,length);}inputStream.close();}
}

场景:

字符流:响应文本数据,最主要的场景就是前后端分离之后,通过字符流响应Json数据

字节流:响应图片、文件,也通常在文件下载的场景下使用

4.4 特殊响应头

特殊的几个响应头

  1. refresh → 定时刷新、跳转
  2. content-type → 限定响应的正文(也可以解决中文乱码问题)
  3. content-disposition → 文件下载
  4. location → 重定向

4.4.1 refresh

private void refreshAndForward(HttpServletResponse resp) {resp.setHeader("refresh","3;url=/demo12/hello");
}private void refreshPerSecond(HttpServletResponse resp) throws IOException {resp.setHeader("refresh","1");Date date = new Date();String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);resp.getWriter().write(dateStr);
}

4.4.2 Content-Type

通常不需要设置

比如我们响应Json数据给前端,我们可以设置Content-Type:application/json

我们要在这里做字符集的设置,如果没有做有可能出现中文乱码

比如我们响应Json,想要设置字符集为utf-8

Content-Type:application/json;charset=utf-8

@WebServlet("/contenttype")
public class ContentTypeServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 这里不是json而是普通字符,就设置了text/html了resp.setHeader("content-type","text/html;charset=utf-8");resp.getWriter().write("你好");}
}

也可以直接使用Response来调用其setContentType方法

4.4.3 content-disposition

下载的场景会使用

content-disposition: attachment;filename=1.jpg

以1.jpg来下载正文

@WebServlet("/disposition")
public class ContentDispositionServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setHeader("content-disposition","attachment;filename=1.jpg");ServletOutputStream outputStream = resp.getOutputStream();InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("dlrb.jpg");byte[] bytes = new byte[1024];int length = 0;while ((length = inputStream.read(bytes)) != -1) {outputStream.write(bytes,0,length);}inputStream.close();outputStream.close();}
}

4.4.4 location

重定向

@WebServlet("/location")
public class LocationServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("访问到LocationServlet");resp.setStatus(302);resp.setHeader("location","http://localhost:8080/demo12/hello");}
}

5 注意事项

请求体和响应体分别要使用到输入流和输出流

  • 关闭流,如果是通过request和response获得流,不手动关闭也可以;如果是自己创建的流,要及时关闭
  • 同一个请求处理过程中,不能既获得字节流又获得字符流

6 附录 思维导图

请添加图片描述
请添加图片描述

请添加图片描述
请添加图片描述
请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/421967.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Leetcode 188. 买卖股票的最佳时机 Ⅳ 状态机dp C++实现

Leetcode 188.买卖股票的最佳时机 Ⅳ 问题&#xff1a;给你一个整数数组 prices 和一个整数 k &#xff0c;其中 prices[i] 是某支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说&#xff0c;你最多可以买 k 次&…

准备好了吗?JAVA从业AI开发的学习路线详解

作为一个拥有扎实 Java 基础的人&#xff0c;想要涉足人工智能&#xff08;AI&#xff09;应用开发&#xff0c;你已经在编程能力方面打下了很好的基础。Java 是一种通用的、强类型的语言&#xff0c;非常适合于开发高性能的应用程序&#xff0c;尤其是在后端服务和大规模分布式…

C++:IO流

目录 C语言的输入输出 流是什么 CIO流 C标准IO流 C文件IO流 stringstream的介绍 C语言的输入输出 C 语言中我们用到的最频繁的输入输出方式就是 scanf () 与 printf() 。 scanf(): 从标准输入设备 ( 键 盘 ) 读取数据&#xff0c;并将值存放在变量中 。 printf(): 将…

linux驱动之模块化编程

我们写的驱动程序&#xff0c;对linux操作系统而言&#xff0c;都是一个一个模块。 我们写应用程代码的时候是要有main函数入口&#xff0c;但是驱动模块有自己的入口。所以在编译驱动模块的时候就要使用到内核的makefile&#xff0c;来编译我们的模块。 我们在命令行敲&#x…

RS®FSWP 相位噪声分析仪和 VCO 测试仪信号源和组件的高端分析

FSWP 相位噪声分析仪和VCO测试仪 价格实惠&#xff0c;性能出众 R&SFSWP 相位噪声分析仪和 VCO 测试仪结合噪声极低的内部源与互相关技术&#xff0c;具备高灵敏度。它可在数秒内测量高度稳定的信号源的相位噪声。 R&SFSWP 还具备脉冲信号测量、加性相位噪声&…

C++初阶:string类的模拟实现

✨✨小新课堂开课了&#xff0c;欢迎欢迎~✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C&#xff1a;由浅入深篇 小新的主页&#xff1a;编程版小新-CSDN博客 前言&#xff1a; 前面已经对string类进行了…

[数据集][目标检测]井盖丢失未盖破损检测数据集VOC+YOLO格式2890张5类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2890 标注数量(xml文件个数)&#xff1a;2890 标注数量(txt文件个数)&#xff1a;2890 标注…

QGIS 如何连接空间库,并实时编辑空间表?编辑后库表如何刷新,保证是最新数据?

文章目录 一、什么是 qgis&#xff1f;二、qgis 如何连接数据库三、实时编辑空间表四、编辑后库表如何刷新&#xff0c;保证是最新数据&#xff1f;五、总结 一、什么是 qgis&#xff1f; QGIS&#xff08;原称Quantum GIS&#xff09;是一个用户界面友好的开源桌面端软件&…

htop、free -h对于可用内存显示不同的区别

htop中Mem包含了缓存和缓存区&#xff0c; free -h查看 used free buff/cache 上面htop显示的mem&#xff0c; 1、我看我还能用多少内存&#xff0c;看哪里 看free -h 中的free 2、buff/cache 是啥 缓存缓存区占用&#xff0c;htop显示的效果是把这个也算在一块了&#…

TIDB的整体架构和主要功能

1. 基础架构 PD &#xff1a;负责集群管理和调度。TiDB Server &#xff1a;负责 SQL 查询处理。TiKV/TiFlash&#xff1a;负责数据存储和事务处理。 1.1 PD (Placement Driver) Server 1.1.1 基础介绍 整个 TiDB 集群的元信息管理模块&#xff0c;负责存储每个 TiKV 节点实时…

哪款骨传导耳机适合运动?健身党无广安利五款有用的骨传导耳机!

作为一名耳机爱好者&#xff0c;我的耳机收藏可以说是丰富多样&#xff0c;从追求极致音质的头戴式&#xff0c;到便于携带的入耳式&#xff0c;再到近年来兴起的骨传导耳机&#xff0c;我都有所体验。在众多选择中&#xff0c;我最终偏爱上了骨传导耳机&#xff0c;它以其独特…

vue3 使用 codemirror 实现yaml文件的在线编辑

vue3 使用 codemirror 实现yaml文件的在线编辑 1. 使用情形2. 插件下载3. 封装yaml编辑器组件4. 父组件使用5. js-yaml 使用6. 备注 1. 使用情形 需要对yaml文件进行在线编辑&#xff0c;并且进行基础格式验证 2. 插件下载 vue-codemirror 在线代码编辑器插件 js-yaml 用于转…

容联云容犀Copilot&Agent入选《中国 AI Agent 产品罗盘》

近日&#xff0c;InfoQ研究中心推出《中国AI Agent应用研究报告》&#xff0c;并在报告中对现行的中国AI Agent产品进行梳理总结&#xff0c;并形成《中国AI Agent产品罗盘》。 作为“营销服”领域垂直类Agent&#xff0c;容联云容犀Copilot&#xff06;Agent入选2024中国AI A…

java8+springboot2.3升级jdk17+springboot2.7.9踩坑

一.问题: java.lang.ExceptionInInitializerError: nullat java.base/java.lang.Class.forName0(Native Method)at java.base/java.lang.Class.forName(Class.java:375)。。。。。。内部保密。。。。at org.springframework.context.annotation.ParserStrategyUtils.invokeAwa…

封装智能指针 qt实现登录界面

1.封装独占智能指针——unique_ptr #include <iostream> #include <utility> // For std::move// 命名空间 namespace custom_memory { template <typename T> class myPtr { public:// 使用初始化列表进行初始化explicit myPtr(T* p nullptr) noexcept : …

ThinkPHP8出租屋管理系统

有需要请加文章底部Q哦 可远程调试 ThinkPHP8出租屋管理系统 一 介绍 此出租屋管理系统基于ThinkPHP8框架开发&#xff0c;数据库mysql&#xff0c;前端Vue3&#xff0c;前后端不分离&#xff0c;系统主要角色为管理员。房租计算器&#xff0c;房东记账收租管理&#xff0c;房…

NX二次开发—柱面中心线工具

设计一个柱面中心线工具,可以实现选择对象,画出圆柱的中心线,可以更改中心的线的颜色、线型、线宽和图层,是否延长,是否关联。 先在NX上进行界面设计 添加选择对象,并设置标题,选择设置为多选 添加组,在组里添加线条颜色/线型/线宽,设置颜色ColorValue和线型Value 这…

C++详解string(全面解析)

目录 string的概念&#xff1a; string的框架&#xff1a; 1、成员函数 2、迭代器&#xff08;Iterators&#xff09;​编辑 3、容量 4、元素访问 5、修改 6、非成员函数重载 string的构造和拷贝构造&#xff1a; string的析构&#xff1a; string的访问&#xff1a;…

单片机,传感器等低功耗管理

**有些客户需求&#xff0c;把设备做成低功耗管理&#xff0c;这样就可以节省电池的电量&#xff0c;也可以增加传感器的使用寿命 HCLK为CPU提供时钟&#xff0c;内核执行代码。当CPU不需要继续运行时&#xff0c;可以利用多种低功耗模式&#xff0c;等待某个事件触发 ① 睡眠…

单链表的实现(C语言)

目录 1.单链表 1.1 实现单链表 1.1.1 文件创建 1.1.2 链表功能了解 1.1.3 链表的结点 1.1.4 链表的函数声明 1.1.5 链表功能的实现 链表是一种链式结构&#xff0c;物理结构不连续&#xff0c;逻辑结构是连续的&#xff0c;在计算机中链表的实际存储是按照一个结点内存放…