目录
Servlet概念
Servlet 主要做的工作
创建一个Servlet项目
1.创建一个Maven项目
2.引入依赖
3.创建目录
1) 创建 webapp 目录
2) 创建 web.xml
3) 编写 web.xml
5.打包程序
6.程序部署
7.验证程序
更方便的部署方式-Smart TomCat
安装 Smart Tomcat 插件
1. 菜单 -> 文件 -> Settings
2. 选择 Plugins(插件), 选择 Marketplace, 搜索 "tomcat", 点击 "Install".
3. 安装完毕之后, 会提示 "重启 IDEA“
配置 Smart TomCat 插件
5. 访问页面.
区分IDEA和TomCat之间的关系
常见的访问错误
出现404
出现405
出现500
出现”空白页面“
出现无法访问此网站
小结
Servlet 运行原理
TomCat的定位
详细的交互过程
用户发送请求-封装
通过网络设备,直至抵达目标主机;
服务器接受请求-分用
TomCat的伪代码
TomCat初始化流程
TomCat处理请求流程
Servlet 的 service 方法的实现
Servlet API 详解
HttpServlet
核心方法
处理GET/POST请求
HttpServletRequest
核心方法
HttpServletResponse
核心方法
Cookie 和 Session
Cookie:
Session :会话
网页应用的登录流程
Cookie 和 Session 的区别
核心方法:
HttpServletRequest 类
HttpServletResponse 类
HttpSession 类
Cookie 类
上传文件
核心方法
HttpServletRequest 类方法
Part 类方法
Servlet 是一种实现动态页面的技术.
是一组 Tomcat 提供给程序猿的 API, 帮助程序猿简单高效的开发一个 web app
使用idea 创建一个maven项目
1.菜单-》文件-》新建项目-》Maven
2.选择存放目录
3.项目创建完毕后,一般右下角会弹出一下对话框,选择enable auto-import
Maven 项目创建完毕后, 会自动生成一个 pom.xml 文件.
我们需要在 pom.xml 中引入 Servlet API 依赖的 jar 包
1. 在中央仓库 https://mvnrepository.com/ 中搜索 "servlet", 一般第一个结果就是
2. 选择版本. 一般我们使用 3.1.0 版本
Servlet 的版本要和 Tomcat 匹配.
如果我们使用 Tomcat 8.5, 那么就需要使用 Servlet 3.1.0
可以在 Apache Tomcat® - Which Version Do I Want? 查询版本对应关系
3. 把中央仓库中提供的 xml 复制到项目的 pom.xml 中
修改后的 pom.xml 形如
4.0.0
org.example
serlet
1.0-SNAPSHOT
8
8
//引入包
javax.servlet
javax.servlet-api
3.1.0
//此标签表示,当前jar包只是在开发阶段使用,不需要打包到最终的发布包中
provided
标签内部放置项目依赖的 jar 包. maven 会自动下载依赖到本地 关于 groupId, artifactId, version
这几个东西暂时我们不关注.啥时候需要关注呢?
如果我们要把这个写的代码发布到中央仓库上, 那么就需要设定好这几个 ID 了.
groupId: 表示组织名称
artifactId: 表示项目名称
version: 表示版本号
中央仓库就是按照这三个字段来确定唯一一个包的
红色方框圈出来的部分, 就是这个 jar 包的 groupId, artifactId, version
当项目创建好了之后, IDEA 会帮我们自动创建出一些目录. 形如
这些目录中:
这些目录还不够, 我们还需要创建一些新的目录/文件
在 main 目录下, 和 java 目录并列, 创建一个 webapp 目录 (注意, 不是 webapps).
然后在 webapp 目录内部创建一个 WEB-INF 目录, 并创建一个 web.xml 文件
注意单词拼写,严格相同
往 web.xml 中拷贝以下代码. 具体细节内容我们暂时不关注
Archetype Created Web Application
webapp 目录就是未来部署到 Tomcat 中的一个重要的目录. 当前我们可以往 webapp 中放一些静 态资源, 比如 html , css 等.
在这个目录中还有一个重要的文件 web.xml. Tomcat 找到这个文件才能正确处理 webapp 中的动 态资源.
4.编写简单代码
在 java 目录中创建一个类 HelloServlet, 代码如下:
/**
* @WebServlet() Servlet提供的注解;
* 功能:将 类 和 HTTP特定请求进行关联
* 根据HTTP请求的URL的路径进行关联
* 若收到路径为/hello 的请求,就会调用到HelloServlet的代码
* 若为GET请求,调用doGet方法
* 若为POST请求,调用doPost方法
*/
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
/**
* 重写doGet方法,若不重写,调用父类方法,报错
* 根据GET请求,生成响应
* @param req :收到的请求
* HttpServletRequest 表示 HTTP 请求.
* Tomcat 按照 HTTP 请求的格式把 字符串 格式的请求转成 HttpServletRequest 对象.
* 后续想获取请求中的信息(方法, url, header, body 等) 都是通过这个对象来获取.
* @param resp :要构造的响应
* HttpServletResponse 表示 HTTP 响应.
* 代码中把响应对象构造好(构造响应的状态码, header,body 等)
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//干掉父类代码
//super.doGet(req, resp);
/**
* resp.getWriter():获取一个流对象
* 通过这个流对象就可以写入一些数据, 写入的数据会被构造成一个 HTTP 响应的 body 部分
*
* Tomcat 会把整个响应转成字符串, 通过 socket 写回给浏览器
*/
//实现hello world,不管请求,直接返回字符串
resp.getWriter().write("hello world");
}
}
1.我们的代码不是通过 main 方法作为入口了. main 方法已经被包含在 Tomcat 里, 我们写的代码会 被 Tomcat 在合适的时机调用起来.(方法调用,实例化,有TomCat在合适的时机自主调用)
此时我们写的代码并不是一个完整的程序, 而是 Tomcat 这个程序的一小部分逻辑.
2. 我们随便写个类都能被 Tomcat 调用嘛? 满足啥样条件才能被调用呢?
主要满足三个条件:
当这三个条件都满足之后, Tomcat 就可以找到这个类, 并且在合适的时机进行调用
即Servlet编程,三大起手式
使用 maven 进行打包. 打开 maven 窗口 (一般在 IDEA 右侧就可以看到 Maven 窗口, 如果看不到的话,
可以通过 菜单 -> View -> Tool Window -> Maven 打开)
看到SUCCESS字样打包成功
打包成功后, 可以看到在 target 目录下, 生成了一个 jar 包
这样的 jar 包并不是我们需要的, Tomcat 需要识别的是另外一种 war 包格式.
另外这个 jar 包的名字太复杂了, 我们也希望这个名字能更简单一点
war 包和 jar 包的区别
jar 包 :是普通的 java 程序打包的结果. 里面会包含一些 .class 文件.
war 包:是 java web 的程序, 里面除了会包含 .class 文件之外, 还会包含 HTML, CSS, JavaScript, 图 片, 以及其他的 jar 包. 打成 war 包格式才能被 Tomcat 识别
ServletHelloWorld-1.0-SNAPSHOT.jar 的由来
这个名字来源于
相当于把 artifactId 和 version 拼接起来了
通过pom.xml文件设置包名,及类型
通过packing 标签改变包名
表示打包的方式是打一个 war 包
war
通过 build 标签 设置,内置一个 finalName 标签,
表示打出的 war 包的名字是
ServletHelloWorld
此时通过浏览器访问 http://127.0.0.1:8080/ServletHelloWorld/hello
即可看到结果
http://127.0.0.1:8080/路径
注意: URL 中的 PATH 分成两个部分, 其中 HelloServlet 为 Context Path, hello 为 Servlet Path
注意: 安装过程必须要联网.
1. 点击右上角的 "Add Configuration
2. 选择左侧的 "Smart Tomcat"
3. 在 Name 这一栏填写一个名字(可以随便写)
在 Tomcat Server 这一栏选择 Tomcat 所在的目录(设置一次以后就不用设置了).
其他的选项不必做出修改
其中 Context Path 默认填写的值是项目名称.
这会影响到后面咱们的访问页面.
4. 点击 OK 之后, 右上角变成了
点击绿色的三角号, IDEA 就会自动进行编译, 部署, 启动 Tomcat 的过程
此时 Tomcat 日志就会输出在 IDEA 的控制台中, 可以看到现在就不再乱码了
在浏览器中使用 http://127.0.0.1:8080/ServletHelloWorld/hello 访问页面.
注意路径的对应关系
部署方式
使用 Smart Tomcat 部署的时候, 我们发现 Tomcat 的 webapps 内部并没有被拷贝一个 war 包, 也没有看到解压缩的内容.
会发现给当前项目创建了一个单独的目录,把当前正在运行的Tomcat,搞一个副本,来运行当前正在编辑的代码
IDEA 和 TomCat 之间是独立的两个进程,通过Smart Tomcat插件
让idea进程 调用 tomcat进程;
404表示用户访问的资源不存在,大概率是URL路径写的不正确
正确路径:https://127.0.0.1:8080/ServletHellowworld/hello
405表示对应http请求方法没有实现
请求为GET,提供的实现却是POST就会引发405
往往是Servlet代码抛出异常引起
比较好处理,日志或页面上就会明确提示除异常的调用栈等详细信息
实际开发中异常调用栈直接显示在页面上十分危险
响应数据的body中没有提供数据
错误实例:
修改代码, 去掉 resp.getWritter().write() 操作
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("hello");
}
}
抓包观察,响应body中的内容就是空数据
一般为Tomcat 启动失败
应该写作 "/hello", Tomcat 在启动的时候已经提示了相关的错误.
Tomcat 启动的日志里面报错信息可能比较多, 需要耐心观察, 找到关键的提示.
熟悉 HTTP 协议能够让我们调试问题事半功倍.
浏览器发送http请求
当TomCat拿到请求后,对请求进行解析,返回一个HttpServletRequest对象,调用Servlet类,执行程序员写好的代码
应用层:用户在浏览器输入一个URL,浏览器就会构造出一个HTTP请求,通过HTTP协议封装,若url中ip地址位域名,通过DNS协议转换为对应IP地址;
传输层:基于TCP协议,封装数据,包括源端口号,目的端口号
网络层:基于IP协议,封装数据,包括源IP地址,目的IP地址
数据链路层:基于以太网帧,封装数据,通过ARP转换目的MAC包括源MAC地址,目的MAC地址
物理成:传输数据
详细过程可复习网路部分笔记
1.接受数据
服务器从下到上分用解析数据
数据链路层:解析数据,检查MAC地址
网路层:解析数据,检查IP地址
传输层:解析数据,锁定目的端口
应用层:解析数据,TomCat通过Socket读取请求,按照HTTP请求格式解析,创建HTTPServletRequest对象,
根据 请求中的 Context Path 确定一个 webapp, 再通过 Servlet Path 确定一个具体的 类.
再根据当前请 求的方法 (GET/POST/...), 决定调用这个类的 doGet 或者 doPost 等方法.
此时我们的代码中的 doGet / doPost 方法的第一个参数 HttpServletRequest 就包含了这个 HTTP 请求的详细信息.
2.根据请求计算响应:
根据我们的代码,给HttpServletReponse对象设置属性
3.返回响应:
对应的请求方法执行结束后,TomCat自动将HttpServletResponse对象,转换为符合HTTP协议的字符串,通过Socket发送响应
通过网络协议,逐层封装数据,通过物理层发送数据
通过网络设备,传输至目标主机
目标主机,逐层分用解析,还原为HTTP响应,交由浏览器处理
destroy()有坑,不一定执行得到,若直接kill掉tomcat进程,此时destroy就不会执行
我们写 Servlet 代码的时候, 首先第一步就是先创建类, 继承自 HttpServlet, 并重写其中的某些方法
方法名称 |
调用时机 |
init |
在 HttpServlet 实例化之后被调用一次 |
destory |
在 HttpServlet 实例不再使用的时候调用一次 |
service |
收到 HTTP 请求的时候调用 |
doGet |
收到 GET 请求的时候调用(由 service 方法调用) |
doPost |
收到 POST 请求的时候调用(由 service 方法调用) |
doPut/doDelete/doOptions/... |
收到其他请求的时候调用(由 service 方法调用) |
实际开发的时候主要重写 doXXX 方法, 很少会重写 init / destory / service .
创建Servlet 类继承HttpServlet
@WebServlet("/method")
public class MethodServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//必须在添加body之前设置,否则无用
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().write("GET 响应");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().write("POST 响应");
}
}
点击发送请求,可能会发现乱码(控制台)
编码方式不统一;
我们使用UTF-8,浏览器通常随windows系统使用GBK编码
我们可以在header中加入Content-Type,在其中注明编码格式
一个 Servlet 程序中可以同时部署静态文件. 静态文件就放到 webapp 目录中即可.
MethodServlet
当 Tomcat 通过 Socket API 读取 HTTP 请求(字符串), 并且按照 HTTP 协议的格式把字符串解析成HttpServletRequest 对象
HTTP请求报文:
首行:(方法 url 版本号) url:path,query string
header:键值对
空行:
body:
方法 |
描述 |
String getProtocol() |
返回请求协议的名称和版本。 |
String getMethod() |
返回请求的 HTTP 方法的名称, 例如,GET、POST 或 PUT。 |
String getRequestURI() |
从协议名称直到 HTTP 请求的第一行的查询字符串中, 返回该请求的 URL 的一部分。 |
URL:Location,资源位置;URI:Identify,资源表示符;使用场景类似 |
|
String getContextPath() |
返回指示请求上下文的请求 URI 部分。 |
String getQueryString() |
返回包含在路径后的请求 URL 中的查询字符串。 得到完整query string |
Enumeration getParameterNames() |
返回一个 String 对象的枚举, 包含在该请求中包含的参数的名称。 |
String getParameter(String name) |
以字符串形式返回请求参数的值, 或者如果参数不存在则返回null。 |
String[] getParameterValues(String name) |
返回一个字符串对象的数组, 包含所有给定的请求参数的值, 如果参数不存在则返回 null。 |
这几个方法,不光可以操作query string 还可以操作 x-www-form-urlencoded格式的body数据 允许多个键值对,键相同 |
|
Enumeration getHeaderNames() |
返回一个枚举, 包含在该请求中包含的所有的头名。 |
String getHeader(String name) |
以字符串形式返回指定的请求头的值。 |
String getCharacterEncoding() |
返回请求主体中使用的字符编码的名称。 |
String getContentType() |
返回请求主体的 MIME 类型, 如果不知道类型则返回 null。 |
int getContentLength() |
以字节为单位返回请求主体的长度, 并提供输入流, 或者如果长度未知则返回 -1。 |
InputStream getInputStream() |
用于读取请求的 body 内容. 返回一个 InputStream 对象 |
通过这些方法可以获取到一个请求中的各个方面的信息.
注意: 请求对象是服务器收到的内容, 不应该修改.
因此上面的方法也都只是 "读" 方法, 而不是 "写" 方法
方法 |
描述 |
void setStatus(int sc) |
为该响应设置状态码。 |
void setHeader(String name,String value) |
设置一个带有给定的名称和值的 header. 如果 name 已经存在, 则覆盖旧的值. |
void addHeader(String name, String value) |
添加一个带有给定的名称和值的 header. 如果 name 已经存在, 不覆盖旧的值, 并列添加新的键值对 |
对于http响应来说,header中的key并不是要求唯一的,有些key可以重复出现。 典型的就是 Set-Cookie 属性 |
|
void setContentType(String type) |
设置被发送到客户端的响应的内容类型。 |
void setCharacterEncoding(String charset) |
设置被发送到客户端的响应的字符编码(MIME 字符集) 例如,UTF-8。 |
void sendRedirect(String location) |
使用指定的重定向位置 URL 发送临时重定向响应到客户端。 |
PrintWriter getWriter() |
用于往 body 中写入文本格式数据. |
OutputStream getOutputStream() |
用于往 body 中写入二进制格式数据 |
是HTTP协议中的一个字段,同时是浏览器在客户端保存数码的一种方式;
Cookie从哪来?
Cookie怎么存?
Cookie 到哪去?
Cookie 存的是什么?
服务器同一时刻收到的请求是很多的. 服务器需要清除的区分清楚每个请求是从属于哪个用户, 就需要在服务器这边记录每个用户令牌以及用户的信息的对应关系.
Session 本质就是 hash表;key就是SesionId,value 就是程序员自定义的数据(可以存储用户身份信息)
Cookie 和 Session 之间要相互配合
方法 |
描述 |
HttpSession getSession() |
在服务器中获取会话. 参数如果为 true, 则当不存在会话时新建会话; 根据请求中SessionId获取当前的session;
参数如果为 false, 则当不存在会话时返回 null |
Cookie[] getCookies() |
返回一个数组, 包含客户端发送该请求的所有的 Cookie 对象. 会自动把Cookie 中的格式解析成键值对. |
方法 |
描述 |
void addCookie(Cookie cookie) |
把指定的 cookie 添加到响应中 |
一个 HttpSession 对象里面包含多个键值对. 我们可以往 HttpSession 中存任何我们需要的信息
方法 |
描述 |
Object getAttribute(String name) |
该方法返回在该 session 会话中具有指定名称的对象, 如果没有指定名称的对象,则返回 null. |
void setAttribute(String name, Object value) |
该方法使用指定的名称绑定一个对象到该 session 会话 |
boolean isNew() |
判定当前是否是新创建出的会话 |
每个 Cookie 对象就是一个键值对
方法 |
描述 |
String getName() |
该方法返回 cookie 的名称。 名称在创建后不能改变。 (这个值是 SetCooke 字段设置给浏览器的) |
String getValue() |
该方法获取与 cookie 关联的值 |
void setValue(String newValue) |
该方法设置与 cookie 关联的值。 |
上传文件在Servlet中的支持
方法 |
描述 |
Part getPart(String name) |
获取请求中给定 name 的文件 |
Collection getParts() |
获取所有的文件 |
前端,选择文件上传,一次可以选择多个文件,每个文件就是一个Part
每个文件都有自己的 name(input 标签中 name属性)
在服务器中通过 这个name 找到对应的 Part(part中包含文件内容)
方法 |
描述 |
String getSubmittedFileName() |
获取提交的文件名 |
String getContentType() |
获取提交的文件类型 |
long getSize() |
获取文件的大小 |
void write(String path) |
把提交的文件数据写入磁盘文件 |
最难不过坚持!