目录
1 Socket
2 软件结构
3 Servlet
4 HTTP
5 单点登录SSO
6 常见问题总结参考资料
· 《深入分析 Java Web》
1 Socket
1.1 概念
套接字(socket),是描述计算机之间完成相互网络通信的抽象功能,没有对应的实体。通讯的两端都有Sokcet,数据在两个Sokcet间通过IO传输。
格式为:IP:端口号
1.2 分类
· TCP和UDP
· 两种方式传输数据都是通过序列化java对象后,通过二进制协议传输,故Socket通信和编程语言没有关系。
2 软件结构
2.1 C/S体系
Client-Server 客户端-服务器端。属于桌面应用程序。
· 弊端:
·需要安装软件
·维护难,占空间
·服务端升级,客户端也需要升级
· 优点:
·体验效果好
·占宽带小
·减轻服务器端压力
2.2 B/S体系
Browser-Server浏览器端-服务器端,属于网站应用程序。
· 弊端:
·需要使用浏览器访问
·兼容性差、安全性差
·服务器端压力大
· 优点:
·不需要特定客户端
·服务端升级,浏览器不需要升级。
3 Servlet
3.1 资源的分类
· 静态资源:当用户多次访问这个资源,资源的源代码永远不会改变的资源。
· 动态资源:当用户多次访问这个资源,资源的源代码可能会发送改变。
3.2 实质
Servlet本质是Server Applet 服务连接器。是服务器端的程序,具有独立平台和协议的特性,用于交互式地浏览和生成数据,生成动态Web内容。
3.3 使用
编写类继承HttpServlet类,并覆盖doGet和doPost方法,并在web.xml文件或者使用@WebServlet注解配置访问路径。
public class HelloServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//解决中文乱码问题
resp.setCharacterEncoding("utf-8");//内容编码,防止出现中文乱码
resp.setContentType("text/html;charset=utf-8"); //向浏览器输出内容
resp.getWriter().write("这是第一个servlet程序。当前时间为:"+new Date());
}
}
3.4 工作原理
3.4.1 Servlet容器
servlet容器的产生是为了技术上的解耦,通过标准化接口来相互协作。
图中看出,真正管理servlet的容器是Context容器,且一个Context对应一个Web工程。
3.4.2 Servlet容器的启动
Tomcat启动代码
Tomcat tomcat=getTomcatInstance();
File appDir= new File(getBuildDirectory(), "webapps/examples") ;
tomcat. addWebapp(null, "/examplesappDir. getAbsolutePath());
tomcat. start();
ByteChunk res = geturl( "http://localhost:"+getport()+" /examples/servlets/servlet/HelloWorldExample");
assertTrue(res tostring().indexOf(Hello World!
")>0);
public Context addwebapp (Hosthost, String url, String path){
silence(url);
Context ctx = new StandardContext();
ctx.setPath( url);
ctx. setDocBase(path);
if(defaultRealm== null){
initsimpleAuth();
}
ctx. setRealm(defaultRealm);
ctx. addLifecycleListener(new DefaultWebXmlListener());
Contextconfig ctxCfg = new ContextConfig();
ctx. addLifecycleListener(ctxCfg);
ctxcfg. setDefaultwebXml ("org/apache/catalin/startup/DEFAULT_XMI ") ;
if(host == null){
getHost(). addchild (ctx);
} else{
host.addchild(ctx);
}
return ctx;
(1)Tomcat启动过程
·getTomcatInstance()获取Tomcat实例
·新建StandardContext()容器,并设置访问URl和项目文件访问地址
·添加LifecycleListener
·新建ContextConfig。解析Web应用的配置文件web.xml等
·tomcat实例执行addWebapp()
·tomcat实例执行start(),启动整个Tomcat容器。
(2)tomcat所有的容器都集成Lifecycle接口,Lifecycle接口管理着容器的整个生命周期,所有容器的修改和状态的改变都会由它去通知已经注册的观察者(Listener)。这是基于观察者模式设计的。
(3)Context容器的启动
在tomcat容器启动后,当Context容器处于init初始化状态时,其中的Listener将会被调用。
首先执行ContextConfig的Init方法:
· 创建用于解析XML配置文件的contextDigester对象
· 读取默认的 context. xm配置文件,如果存在则解析它。
· 读取默认的Hos配置文件,如果存在则解析它
· 读取默认的 Context自身的配置文件,如果存在则解析它
· 设置 Context的 DocBase
ContextConfig的init方法完成后, Context容器就会执行startInternal()方法,这个方法的启动逻辑比较复杂,主要包括如下几部分:
· 创建读取资源文件的对象
· 创建 ClassLoader对象
· 设置应用的工作目录
· 启动相关的辅助类,如 logger、 realm、 resources等。
· 修改启动状态,通知感兴趣的观察者(Web应用的配置)
· 子容器的初始化
· 获取 ServletContext并设置必要的参数。
· 初始化“ load on startup”的 Servlet. 其他Servlet在第一次被调用的时候初始化。
在初始化时,会将Servlet包装成StandardWrapper。因为StandardWrapper是Tomcat容器中的一部分,它具有容器的特性,而Servlet作为一个独立的Web开发标准,不应该强耦合在Tomcat中。
3.4.3 Servlet实例的建立和初始化
(1)创建实例
web.xml中的配置项,“load-on-startup”如果大于0,则在Tomcat启动时Servlet就会被启动。调用Wrapper.loadServlet方法获取servletClass,并交给InstanceManager去创建对象。
(2)初始化实例
调用StandardWrapper的InitServlet方法初始化对象。
3.4.4 Servlet体系结构
· ServletConfig,在Servlet初始化时就传到Servlet,以StandardWrapperFacade对象的形式调用,可以防止暴露不必要的数据。
· ServletContext,获取Context容器的信息。
· ServletRquest
· ServletResponse
体系设计过程中用到门面设计模式。
3.4.5 Servlet调用
(1)访问URL:http://hostname:port/contextpath/servletPath
(2)Tomcat中的org.apache.tomcat.util.http.mapper完成URL到一个Servlet子容器的映射工作。Mapper类保存了Tomcat的Container容器中的所有子容器的信息,根据传入Servlet容器的请求的hostname和contextpath设置到request的mappingData属性中。
(3)Mapper子类MapperListener作为监听这监听容器的变化,这样其中的mapper属性也相应修改。
(4)执行Servlet接口的service(ServletRequest req, ServletResponse resp)方法。
3.4.6 Filter
3.4.6.1 简介
Filter也称之为过滤器,它是 Servlet 技术中最实用的技术,Web 开发人员通过 Filter 技术,对 web 服务器管理的所有 web 资源:例如 Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现 URL 级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
它主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理。使用 Filter 的完整流程:Filter 对用户请求进行预处理,接着将请求交给 Servlet 进行处理并生成响应,最后 Filter 再对服务器响应进行后处理。
3.4.6.2 Demo
/**
* 使用Filter 打印参数
* @author Administrator
*
*/
public class FilterDemo implements Filter {
public FilterDemo(){
System.out.println("FilterDemo 构造函数被执行...");
}
/**
* 销毁
*/
public void destroy() {
System.out.println("destroy");
}
/*
用户在每个请求进来时,访问doFilter方法,在Servlet的service方法之前调用。*/
public void doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse,
FilterChain paramFilterChain) throws IOException, ServletException {
System.out.println("doFilter");
HttpServletRequest request = (HttpServletRequest) paramServletRequest;
HttpServletResponse response = (HttpServletResponse) paramServletResponse;
// 请求地址
String requestURI = request.getRequestURI();
System.out.println("requestURI:"+requestURI);
// 参数
Map
parameterMap = request.getParameterMap(); for (String key : parameterMap.keySet()) {
String[] arr=parameterMap.get(key);
}
}
/**
* 初始化
*/
public void init(FilterConfig paramFilterConfig) throws ServletException {
System.out.println("init");
}
}
FilterDemo
com.qian.servlet.FilterDemo
FilterDemo
/*
3.4.6.3 相关类
(1)FilterConfig
可以通过其获取ServletContext对象。
(2)FilterChain
责任链设计模式。FilterChain是doFilter方法的传入参数,保存了当前整个请求链。通过调用FilterChain.doFilter方法,可以将请求继续传递下去,但是如果要拦截请求,则不调用。
当FilterChain上所有的Filter对象执行完成后,才会执行最终的Servlet。
3.4.6.4 注意事项
(1)Filter常用于登录、XSS攻击、权限方面。
(2)Filter是单例的,与Servlet类似。
3.5 Servlet生命周期
3.5.1 Servlet接口方法
public class TestServletService implements Servlet{
@Override
public void destroy() {
// TODO 自动生成的方法存根
}
@Override
public ServletConfig getServletConfig() {
// TODO 自动生成的方法存根
return null;
}
@Override
public String getServletInfo() {
// TODO 自动生成的方法存根
return null;
}
@Override
public void init(ServletConfig arg0) throws ServletException {
// TODO 自动生成的方法存根
}
@Override
public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException {
// TODO 自动生成的方法存根
}
}
其中,与生命周期相关的有四个方法:
(1)构造方法:创建servlet对象的时候调用。默认情况下,第一次访问(拥有load-on-startup设置的除外)servlet的时候创建servlet对象只调用1次。证明servlet对象在tomcat是单实例的。
(2)init方法: 创建完servlet对象的时候调用。只调用1次。
(3)service方法: 每次发出请求时调用。调用n次。
(4)destroy方法: 销毁servlet对象的时候调用。停止服务器或者重新部署web应用时销毁servlet对象。只调用1次。
3.5.2 Tomcat内部代码运行
(1)通过配置文件的映射关系,找到ServletClass内容。
(2)通过反射构造Servlet对象
(3)创建ServletConfig对象,反射调用init方法。
(4)创建request、response对象,反射调用service方法。
(5)销毁servlet时,反射调用destroy方法。
3.5.3 时序图
3.6 Servlet多线程问题
(1)servlet对象在tomcat服务器是单实例多线程的,为每个用户请求分配一个线程,可以通过线程池来管理。
(2)因为servlet是多线程的,所以当多个servlet的线程同时访问了servlet的共享数据,如成员变量,可能会引发线程安全问题。
解决办法:
· 把使用到共享数据的代码块进行同步(使用synchronized关键字进行同步)
· 建议在servlet类中尽量不要使用成员变量。如果确实要使用成员,必须同步。而且尽量缩小同步代码块的范围。(哪里使用到了成员变量,就同步哪里!!),以避免因为同步而导致并发效率降低。
3.7 域对象
(1)作用: 用于保存数据、获取数据,可以在不同的动态资源之间共享数据。
(2)使用:
· 保存数据 setAttribute(String,Object)
· 获取数据 Object getAttribute(String)
· 删除数据 removeAttribute(String)
(3)分类:
· HttpServletRequest
· HttpSession 会话对象
· PageContext
· ServletContext 作用范围为整个Web应用
(4)可以通过getContextPath()方法获取上下文路径
3.8 转发与重定向
(1)转发:
· 地址栏不会改变
· 只能转发到当前web应用
· 可以把数据保存到request域
· request.getRequestDispatcher(String).forward(request,response)
(2)重定向:
· 地址栏改变
· 可以跳转到其他web应用
· 不能使用request数据(涉及到2次浏览器请求)
· response.sendRedirect(String)
3.9 Cookie
会话数据保存在客户端。浏览器在每次访问服务端时,都会带着cookie信息。
关于Cookie的跨域问题:cookie 跨域问题_chou_out_man的博客-CSDN博客_cookie跨域
3.9.1 使用
(1)构造Cookie对象
Cookie(java.lang.String name, java.lang.String value)
(2)设置cookie
void setPath(java.lang.String uri) :设置cookie的有效访问路径
void setMaxAge(int expiry):设置cookie的有效时间
· 不设置,则随浏览器关闭而消失
· 整数(正负均可),cookie保存到浏览器,缓存在硬盘中
· 零,不保存cookie
void setValue(java.lang.String newValue) :设置cookie的值
(3)发送cookie到浏览器端保存
void response.addCookie(Cookie cookie) :发送cookie
(4)服务器接收cookie
Cookie[] request.getCookies() :接收cookie
3.9.2 Demo
@WebServlet("/LastAccessTime")
public class LastAccessTime extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");// 防止浏览器显示乱码
String lastAccessTime = null;
Cookie[] cookies = req.getCookies();
for (Cookie cookie : cookies) {
String name = cookie.getName();
if (name.equals("lastAccessTime")) {
astAccessTime = cookie.getValue();
break;
}
}
if (StringUtils.isEmpty(lastAccessTime)) {
resp.getWriter().print("您是首次访问!");
} else {
resp.getWriter().print("你上次访问时间:" + lastAccessTime);
}
// 保存访问时间
// 创建cookie 将当前时间作为cookie保存到浏览器
String currenttime = new SimpleDateFormat("yyyy-mm-dd hh:mm:ss").format(new Date());
Cookie cookie = new Cookie("lastAccessTime", currenttime);
cookie.setMaxAge(60 * 60 * 24);
// 发送cookie
resp.addCookie(cookie);
}
}
3.9.3 工作原理
真正构建Cookie是在org.apache.catalina.connector.Response类中,调用generateCookieString方法将Cookie对象构造成一个String字符串,并将字符串命名为Set-Cookie添加到Header中。
3.9.4 利弊分析
弊端
· 只能存字符串类型,不能保存对象
· 只能存非中文
· 1个Cookie的容量不超过4KB,最多300个
· 安全性差
3.9.5 压缩cookie
(1)采用文本压缩方式。可采用gzip或者deflate算法
(2)压缩后进行转码,采用Base32或者Base64。因为Cookie中不能包含控制字符,只能包含ASCII码中34~126的可见字符。
3.10 Session
会话数据保存在服务器端(内存中)。
3.10.1 使用
HttpSession类:用于保存会话数据
(1)创建或得到session对象
HttpSession getSession()
HttpSession getSession(boolean create)
(2)设置session对象
void setMaxInactiveInterval(int interval) : 设置session的有效时间
void invalidate() : 销毁session对象
java.lang.String getId() : 得到session编号
(3)保存会话数据到session对象
void setAttribute(java.lang.String name, java.lang.Object value) : 保存数据
java.lang.Object getAttribute(java.lang.String name) : 获取数据
void removeAttribute(java.lang.String name) : 清除数据
3.10.2 demo
@WebServlet("/TestSession")
public class TestSession extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public TestSession() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.setContentType("text/html;charset=utf-8");// 防止浏览器显示乱码
HttpSession session = request.getSession();
String lastTime = (String) session.getAttribute("lastTime");
if(lastTime == null) {
response.getWriter().write("这是第一次访问");
}else {
response.getWriter().write("上次访问时间:" + lastTime + "现在: " + new Date());
}
session.setMaxInactiveInterval(60 * 60 * 24);
session.setAttribute("lastTime", new Date()+ "");
}
}
3.10.3 与Cookie关系
(1)服务器生成唯一对应session对象的JSESSIONID,并作为cookie发送到浏览器端保存。
(2)如果浏览器禁用cookie可以将JSESSIONID写到用户请求的URL中。
(3)如何避免浏览器的JSESSIONID的cookie随着浏览器关闭而丢失的问题
/**
* 手动发送一个硬盘保存的cookie给浏览器
*/
Cookie c = new Cookie("JSESSIONID",session.getId());
c.setMaxAge(60*60);
response.addCookie(c);
3.10.4 工作原理
(1)request.getSession()方法,触发创建session对象,并加入到org.apache.catalina.Manager的session容器中保存。
(2)Manager负责servlet容器中所有session的生命周期管理。当servlet容器重启或者关闭时,Manager负责持久化(调用upload方法)没有过期的session对象到“SESSIONS.ser”文件中,也会定期检测过期。
3.10.5 分布式session
(1)服务订阅服务器,集中管理资源和配置,统一通过它来推送配置。可以使用Zookeeper实现。
(2)分布式缓存,存储共享集群中每台集群的session。
(3)存取方式。
· 自定义InnerHttpSession类重新实现HttpSession接口。
· 通过Filter拦截用户请求,将自己设置的InnerHttpSession对象设置到request和response对象中。
· 应用创建的所有Session对象都保存在InnerHttpSession对象中,访问完成后将InnerHttpSession内容更新到分布式缓存中。
3.10.6 跨域名共享Cookie
(1)利用跳转应用支持多个域名的访问,并将同一个sessionID作为cookie写到多个域名下。
(2)多个域名,根据sessionID在分布缓存中拿取session信息。
3.10.7 表单重复提交问题
(1)原因
· 网络延时
· 浏览器重新刷新按钮
· 浏览器“回退”按钮,再提交。
(2)解决方案
· 利用javaScript代码进行标识
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
Form表单 var isFlag = false; //表单是否已经提交标识,默认为false
function submitFlag() {
if (isFlag == false) {
isFlag = true;
return true;
} else {
return false;
}
}
·利用js让按钮在提交一次后不可用
function dosubmit(){
//获取表单提交按钮
var btnSubmit = document.getElementById("submit");
//将表单提交按钮设置为不可用,这样就可以避免用户再次点击提交按钮
btnSubmit.disabled= "disabled";
//返回true让表单可以正常提交
return true;
}
· 在session域中生成并保存唯一token,在页面中加入隐藏域存储token,提交时进行检验
//用户访问服务器
@WebServlet("/ForwardServlet")
public class ForwardServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getSession().setAttribute("sesionToken", TokenUtils.getToken());
req.getRequestDispatcher("form.jsp").forward(req, resp);
}
}
//跳转页面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
Form表单
//服务器后端处理
HttpSession session = request.getSession();
String sesionToken = (String) session.getAttribute("sesionToken");
String token = request.getParameter("token");
if (!(token.equals(sesionToken))) {
return false;
}
session.removeAttribute("sesionToken");
3.11 Token
随机性令牌,唯一不重复字符串。
生成方式:
· 自定义唯一识别码
· UUID.randomUUID().toString()
3.12 Web安全与攻防
(1)XSS 跨站脚本注入
利用Filter进行拦截,例如将