一.Servlet
1.RequestDispatcher
RequestDispatcher是一个接口,它包含两个方法:forward(request, response)和
include(request, response)。RequestDispatcher 有一个特点,就是浏览器上显示
的URL是最先请求的目标资源的URL,不会因为使用了forward、include方法而改
变。因此forward和include的调用对于用户来说是透明的。
(1)forward
这个方法将请求从一个 Servlet or JSP目标资源 上 转发到服务器上的另一个
资源(servlet、JSP 文件或 HTML 文件,这些资源必须是当前Web上下文中
的),让其它的资源去生成响应数据。如下图中的,用户请求的是目标资源
servlet1,servlet1接受到请求后,转发到servlet2,真正产生响应数据是
servlet2,而servlet1只是起个引导转发作用。浏览器的地址栏不会变,依然
是servlet1的URL。
注意点:
(a)在目标资源中调用forward方法时,必须保证此响应没有提交。也就是不
要使用 ServletResponse 对象的输出流对象,因为即便你写入了数据到
响应缓冲区,最后也会被清空,如果缓冲区数据被刷新提交(out.flush),
还会抛出IllegalStateException异常。
(b)对于forward方法传递的request对象:虽然我们从调用上看,好像是将
request对象传递给转动的资源上去了,但是我发现目标资源使用的
request对象和转发的资源使用的request对象不是同一个request对象,
因为分别从这2个request中获取RequestURL,发现是不一样的。但是
在目标资源request提取的Paramter 和 Attribute ,在转发后的资源的
request对象中,依然都可以提取到,且是相同的。所以,二者只是在
请求路径相关的属性上不同,其它API调用返回的都是一样的。
(c)在forward语句的前后,都不应该有响应输出的语句,应该会被忽略。
ServletRequest接口的getRequestDispatcher()方法返回
RequestDispatcher的对象。句法:
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/sessionLoginDemo/login.jsp");//得到转发器
requestDispatcher.forward(request, response);//转发(调度)请求给/sessionLoginDemo/login.jsp 并由login.jsp发送response给客户端。
(2)include
此方法用于包含响应中某个资源(servlet、JSP 页面和 HTML 文件)的内
容。调用者指定一个被包含的资源,将这个包含的资源(JSP,Servlet,
HTML)的响应数据包含到自己的响应体中。被包含的数据是在服务器上经
过运行产生的,因此是动态包含,而不同于JSP中的include指令,它是
JSP转译期的静态包含。这个过程实质是用一个相同的Request再请求
一次被包含的资源,将被包含的资源的响应数据包含到原本的资源中去,
构成它的响应数据的一部分。
2.response.sendRedirect
示例:response.sendRedirect("login.jsp");
使用response.sendRedirect传递参数:
response.sendRedirect("/UsersManager/MainFrame?uname="+username+"&pwd="+password);
3.RequestDispatcher.forward和response.sendRedirect的区别:
(1)response.sendRedirect相当于浏览器接收到了响应之后又向服务器发送
了一次请求,所以相当于两次请求。RequestDispatcher.forward相当于
方法调用,在执行当前文件的过程中转向执行目标文件,两个文件(当
前文件和目标文件)属于同一次请求,最本质的特点就是两次请求共享
了reques对象和response对象。
(2)地址栏不同:response.sendRedirect方式下用户在浏览器地址栏中看
到的是目标文件的地址,RequestDispatcher.forward方式下用户在浏览
器地址栏中看到的是当前文件的地址。
4.java web的四大作用域
(1)PageContext域:作用范围是整个JSP页面,是四大作用域中最小的一
个;生命周期是当对JSP的请求时开始,当响应结束
时销毁。
(2)ServletRequest域:作用范围是整个请求链(请求转发也存在);生命
周期是在service方法调用前由服务器创建,传入
service方法。整个请求结束,request生命结束。
线程安全
(3)HttpSession域:作用范围是一次会话。生命周期是在第一次调用
request.getSession()方法时,服务器会检查是否已经
有对应的session,如果没有就在内存中创建一个
session并返回。当一段时间内session没有被使用
(默认为30分钟),则服务器会销毁该session。如果
服务器非正常关闭(强行关闭),没有到期的session
也会跟着销毁。如果调用session提供的invalidate(),
可以立即销毁session。线程不安全
session销毁的方式:
(a)超时(一般服务器设置超时时间为30分钟)服务器会销毁
session;
(b)点击控制台的红色按钮异常关闭服务器要销毁session
(c)手动调用session的invalidate方法session.invalidate();
注意:服务器正常关闭不销毁session,session会存到我
们的硬盘中,也就是我们正常的点击stop server()会在
tomcat的work的Catalina\localhost\项目名称下面生成
一个文件SESSIONS(执行序列化),当服务器再次启动
的时候会加载此文件(反序列化),倘若没有实现序列化
接口(Serializable)可能会报错因为序列化和反序列化会
依据一个id。
问:浏览器关闭后session会销毁吗?
答:不会。
我们知道Session是存在于服务器端的,当把浏览器关闭
时,浏览器并没有向服务器发送任何请求来关闭Session,
自然Session也不会被销毁,但是可以做一点努力,在所
有的客户端页面里使用js的window.onclose来监视浏览器
的关闭动作,然后向服务器发送一个请求来关闭Session,
但是这种做法在实际的开发中也是不推荐使用的,最正常
的办法。
问:那么为什么当我们关闭浏览器后,就再也访问不到之前的
session了呢?
答:其实之前的Session一直都在服务器端,而当我们关闭浏
览器时,此时的Cookie是存在于浏览器的进程中的,当浏
览器关闭时,Cookie也就不存在了。其实Cookie有两种:
一种是存在于浏览器的进程中;一种是存在于硬盘上。而
session的Cookie是存在于浏览器的进程中,那么这种
Cookie我们称为会话Cookie,当我们重新打开浏览器窗口
时,之前的Cookie中存放的Sessionid已经不存在了,此时
服务器从HttpServletRequest对象中没有检查到sessionid,
服务器会再发送一个新的存有Sessionid的Cookie到客户端
的浏览器中,此时对应的是一个新的会话,而服务器上原先
的session等到它的默认时间到之后,便会自动销毁。就是
不去管它,让它等到默认的时间后,自动销毁。
注:当在同一个浏览器中同时打开多个标签,发送同一个请
求或不同的请求,仍是同一个session;当不在同一个窗口
中打开相同的浏览器时,发送请求,仍是同一个session;
当使用不同的浏览器时,发送请求,即使发送相同的请
求,是不同的session;当把当前某个浏览器的窗口全关闭,
再打开,发起相同的请求时,就是本文所阐述的,是不同
的session,但是它和session的生命周期是没有关系的。
(4)ServletContext(application)域:作用范围是整个Web应用。当Web应用
被加载进容器时创建代表整个web应用
的ServletContext对象,当服务器关闭或
Web应用被移除时,ServletContext对象
跟着销毁。线程不安全
关于作用域的线程安全的详细内容,查看:
http://www.itkeyword.com/doc/903165062469129x507/Web-ServletContex-SessionRequest
6.web应用中的对象
(1)Servlet
Servlet通常称为服务器端小程序,是运行在服务器端的程序,用于处理及响
应客户的请求。Servlet是个特殊的java类,继承于HttpServlet。客户端通常
只有GET和POST两种请求方式,Servlet为了响应则两种请求,必须重写
doGet()和doPost()方法。大部分时候,Servlet对于所有的请求响应都是完全
一样的,此时只需要重写service()方法即可响应客户端的所有请求。
(a)HttpServlet有三个方法:
init(ServletConfig config):创建Servlet实例时,调用该方法的初始化
Servlet资源。在 Servlet 的生命期中,仅
执行一次 init()方法。它是在服务器装入
Servlet 时执行的。可以对它进行覆盖。
service() 方法:service() 方法是 Servlet 的核心。每当一个客户请求一
个HttpServlet对象,该对象的service() 方法就要被调用,
而且传递给这个方法一个"请求"(ServletRequest)对象和
一个"响应"(ServletResponse)对象作为参数。 在
HttpServlet中已存在service() 方法。缺省的服务功能是
调用与 HTTP 请求的方法相应的 do 功能。例如, 如果
HTTP 请求方法为 GET,则缺省情况下调用 doGet() ;
HTTP请求方法为POST,则缺省情况下调用doPost()。
Servlet 应该为 Servlet 支持的 HTTP方法覆盖 do 功能。
因为 HttpServlet.service() 方法会检查请求方法是否调用
了适当的处理方法,不必要覆盖 service() 方法。只需覆
盖相应的 do 方法就可以了。
destroy() 方法:destroy() 方法仅执行一次,即在服务器停止且卸装
Servlet时执行该方法。典型的,将 Servlet 作为服务器
进程的一部分来关闭。可以对它进行覆盖。
(b)Servlet的响应
Servlet的响应可以是下列几种类型:
一个输出流,浏览器根据它的内容类型(如text/HTML)进行解释。
一个HTTP错误响应, 重定向到另一个URL、servlet、JSP。
(c)Servlet的生命周期
(i)创建Servlet实例。
创建Servlet实例有两个时机:
第一个时机:客户端第一次请求某个Servlet时,系统创建该
Servlet的实例。
第二个时机:Web应用启动时立即创建Servlet实例,即
load-on-start Servlet。
load-on-start:
load-on-startup标记容器是否在启动的时候实例化并调用其
init()方法的优先级。它的值表示 servlet应该被载入的顺序。
当值为0或者大于0时,表示容器在应用启动时就加载并初始
化这个servlet。如果值小于0或未指定时,则表示只有在第一
次请求的容器才在该servlet调用初始化函数。正值越小
servlet的优先级越高,应用启动时就越先加载。值相同时,
容器就会自己选择顺序来加载。
(ii)Web容器调用Servlet的init()方法,对Servlet进行初始化。
(iii)Servlet初始化后,将一直存在于容器中,用于响应客户端请求,如
果客户端发送GET请求,容器调用Servlet的doGet()方法处理并响应
请求;如果客户端发送POST请求,容器调用Servlet的doPost()方法
处理并响应请求。或者统一使用service()方法处理来响应用户请求。
(iv)Web容器决定销毁Servlet时,先调用Servlet的destory()方法,通常
在关闭Web应用时销毁Servlet实例。
(d)Servlet的线程安全
(i)对象分为有状态的对象和无状态的对象
有状态的对象:有数据存储功能,即有实例变量;
无状态的对象:无数据存储功能,即无实例变量。
Servlet是一个单例模式,是否线程安全即取决于它的实现是有状态的
Servlet还是无状态的Servlet。传统的实现方式,即实现HttpServlet为
有状态的Servlet,所以非线程安全,它的线程安全问题如下:
静态变量:非线程安全
实例变量:非线程安全
局部变量:线程安全
如果Servlet是实现SingleThreadModel接口,它就是无状态的,即线
程安全。
(1)ServletContext(application)
官方叫servlet上下文,ServletContext是一个web应用的上下文,服务器会为
每一个工程创建一个对象,这个对象就是ServletContext对象。这个对象全局
唯一,而且工程内部的所有servlet都共享这个对象。所以叫全局应用程序共
享对象。它是一个全局信息的存储空间,代表当前web应用。
(a)创建和销毁
ServletContext在web应用(服务器)启动时创建,ServletContext在Web
应用(服务器)关闭时释放。
(b)如何获取ServletContext
(i)调用ServletConfig类的getServletContext()方法
在init(ServletConfig config)方法中,使用
ServletContext context=config.getServletContext();
(ii)调用GenericServlet类的getServletContext()方法
其实这种方式也是调用ServletConfig类的getServletContext()方法,
因为GenericServlet类实现了ServletConfig接口;
(iii)调用HttpSession类的getServletContext()方法
(iv)ServletContextEvent类只有一个方法,就是getServletContext()
(2)ServletContextListener和ServletContextEvent
当Servlet 容器启动或终止Web 应用时,会触发ServletContextEvent 事件,该
事件由ServletContextListener 来处理。在 ServletContextListener 接口中定义
了处理ServletContextEvent 事件的两个方法。
/**
* 当Servlet 容器启动Web 应用时调用该方法。在调用完该方法之后,容器再对Filter 初始化,
* 并且对那些在Web 应用启动时就需要被初始化的Servlet 进行初始化。
*/
contextInitialized(ServletContextEvent sce)
/**
* 当Servlet 容器终止Web 应用时调用该方法。在调用该方法之前,容器会先销毁所有的Servlet 和Filter 过滤器。
*/
contextDestroyed(ServletContextEvent sce)
下面展示三个例子:
例一,在服务启动时,将数据库中的数据加载进内存,并将其赋值给一个属性
名,其它的 Servlet 就可以通过 getAttribute 进行属性值的访问。包括两
个步骤:
(i)实现 servletContextListerner 接口 并将要共享的通过
setAttribute ( name,data )方法提交到内存中去;
(ii)应用项目通过 getAttribute(name) 将数据取到 。
Java代码:
public class ServletContextLTest implements ServletContextListener{
// 实现其中的销毁函数
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("this is last destroyeed");
}
// 实现其中的初始化函数,当有事件发生时即触发
public void contextInitialized(ServletContextEvent sce) {
ServletContext sct=sce.getServletContext();
Map<Integer,String> depts=new HashMap<Integer,String>();
Connection connection=null;
PreparedStatement pstm=null;
ResultSet rs=null;
try{
connection=ConnectTool.getConnection();
String sql="select deptNo,dname from dept";
pstm=connection.prepareStatement(sql);
rs=pstm.executeQuery();
while(rs.next()){
depts.put(rs.getInt(1), rs.getString(2));
}
// 将所取到的值存放到一个属性键值对中
sct.setAttribute("dept", depts);
System.out.println("======listener test is beginning=========");
}catch(Exception e){
e.printStackTrace();
}finally{
ConnectTool.releasersc(rs, pstm, connection);
}
}
}
在完成上述编码后,仍需在 web.xml 中进行如下配置,以使得该监听器可以起作用。
web.xml代码:
<listener>
<listener-class>ServletContextTest.ServletContextLTest</listener-class>
</listener>
在完成上述配置后, web 服务器在启动时,会直接加载该监听器,通过以下的应用
程序就可以进行数据的访问。
例二,启动线程
public class DSAction extends Thread implements ServletContextListener {
public void contextInitialized(ServletContextEvent arg0) {
super.start();// 启动一个线程
}
public void zdfs() throws IOException {
Huoquzhuye u = new Huoquzhuye();// 爬虫方法类
Htmlneirong h = new Htmlneirong();// 存入数据库类
List<String> list = u.seturl("http://xxxxxxx");
for (int i = 0; i < list.size(); i++) {
String txt = list.get(i).substring(0, 22);
String start = list.get(i).substring(4, 14);
String end = list.get(i).substring(22, list.get(i).length());
try {
h.seturl(txt, start, end);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
while (true) {
try {
this.zdfs();
super.sleep(1000 * 60 * 10);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/*
* (non-Javadoc)
*
* @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.
* ServletContextEvent)
*/
/*
* (non-Javadoc)
*
* @see
* javax.servlet.ServletContextListener#contextInitialized(javax.servlet
* .ServletContextEvent)
*/
public void contextDestroyed(ServletContextEvent arg0) {
super.stop();// 停止线程
}
}
web.xml代码
<listener>
<listener-class>bj.hbj.dingshi.DSAction</listener-class>
</listener>
(i)调用super.start()开启线程。
(ii)最后关闭线程super.stop()。
7.web.xml
(1)web.xml的加载顺序:
ServletContext -> context-param(无顺序)-> listener(无顺序)-> filter(书写顺序)
-> servlet(load-on-startup优先级)
详细步骤如下:
(i)启动一个web项目,web容器(如tomcat)读取web.xml文件,读取其中的配置信
息。
(ii)容器创建一个servlet上下文(servletContext),这个web项目所有部分共享这个
上下文。
(iii)容器将<context-param>转换为键值对,交给servletContext
(iv)容器创建<listener>中的监听器实例
(v)触发contextInitialized方法,listener被调用(当Servlet 容器启动或终止Web 应
用时,会触发ServletContextEvent 事件,该事件由ServletContextListener 来
处理。在 ServletContextListener 接口中定义了处理ServletContextEvent 事件
的两个方法contextInitialized和contextDestroyed,web.xml有
contextLoaderListener监听器,spring等框架实现了本监听器的接口方法)调
用完contextInitialized方法后,容器再对filter初始化。
ContextLoaderListener:Spring实现的类,它继承自ContextLoader,并且实
现ServletContextListener接口(实现了
contextInitialized和contextDestroyed,这是它的核
心功能)。详细查看:
https://blog.csdn.net/qq_15037231/article/details/78743765
(vi)容器对web.xml中的指定load-on-startup的值为正数Servlet初始化
(优先级1,2,3...->递减),负数或不指定则在该Servlet调用时初始化
(springMVC的初始化为此阶段)
(2)web.xml文件中配置<context-param>和<init-param>的区别
<context-param>和<init-param>都是上下文参数,但它们的范围和使用方式不同。
<context-param>是application范围内的初始化参数,用于向servlet-context提供键
值对,即应用程序的上下文信息,listener、filter等初始化时会用到这些信息。
<init-param>是servlet范围内的参数,只能在servlet类的init()方法中取得。
示例如下:
<context-param>
<param-name>context/param</param-name>
<param-value>avalible during application</param-value>
</context-param>
<servlet>
<servlet-name>ReadContext</servlet-name>
<servlet-class>file.ReadContext</servlet-class>
<init-param>
<param-name>user1</param-name>
<param-value>user1-ps</param-value>
</init-param>
</servlet>
(3)<session-config></session-config>
<session-config> 用于设置容器的session参数,比如:<session-timeout> 用于指
定http session的失效时间,-1 代表session永远不会过期。
(4)<listener></listener>
(a)listener介绍
<listener>为web应用程序定义监听器,监听器用来监听各种事件,比如:
application和session事件,所有的监听器按照相同的方式定义,功能取决去
它们各自实现的接口,常用的Web事件接口有如下几个:
(i)ServletContextListener:用于监听Web应用的启动和关闭;
(ii)ServletContextAttributeListener:用于监听ServletContext范围
(application)内属性的改变;
(iii)ServletRequestListener:用于监听用户的请求;
(iv)ServletRequestAttributeListener:用于监听ServletRequest范围
(request)内属性的改变;
(v)HttpSessionListener:用于监听用户session的开始和结束;
(vi)HttpSessionAttributeListener:用于监听HttpSession范围(session)
内属性的改变。
<listener>主要用于监听Web应用事件,其中有两个比较重要的WEB应用
事件:应用的启动和停止(starting up or shutting down)和Session的创
建和失效(created or destroyed)。应用启动事件发生在应用第一次被
Servlet容器装载和启动的时候;停止事件发生在Web应用停止的时候。
Session创建事件发生在每次一个新的session创建的时候,类似地
Session失效事件发生在每次一个Session失效的时候。为了使用这些
Web应用事件做些有用的事情,我们必须创建和使用一些特殊的“监听
类”。它们是实现了以下两个接口中任何一个接口的简单java类:
javax.servlet.ServletContextListener或
javax.servlet.http.HttpSessionListener,如果想让你的类监听应用的启动
和停止事件,你就得实现ServletContextListener接口;想让你的类去监
听Session的创建和失效事件,那你就得实现HttpSessionListener接口。
(b)Listener配置
配置Listener只要向Web应用注册Listener实现类即可,无序配置参数之
类的东西,因为Listener获取的是Web应用ServletContext(application)
的配置参数。为Web应用配置Listener的两种方式:
(i)使用@WebListener修饰Listener实现类即可。
(ii)在web.xml文档中使用<listener>进行配置。
注意:6中的(2)是只针对ServletContextEvent配置的一个Listener。
web.xml的方式如下所示:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
(5)<filter></filter>
二.Jsp
1.Forward和Redirect
Forward和Redirect代表了两种请求转发方式:直接转发和间接转发。
直接转发方式(Forward):客户端和浏览器只发出一次请求,Servlet、
HTML、JSP或其它信息资源,由第二个信息资
源响应该请求,在请求对象request中,保存的
对象对于每个信息资源是共享的。
间接转发方式(Redirect):实际是两次HTTP请求,服务器端在响应第一
次请求的时候,让浏览器再向另外一个URL发
出请求,从而达到转发的目的。
2.Jsp的九个隐藏变量
(1)Request
(2)Response
(3)Session
(4)application
(5)out
(6)config
javax.servlet.ServletConfig实例化对象,我们也可以在web.xml文件中配置
JSP,只是很少用。
(7)page
翻译成Servlet后相当于this
(8)PageContext
参看:http://www.cnblogs.com/fjdingsd/p/5117303.html
(9)exception
exception 对象的作用是显示异常信息,只有在包含 isErrorPage="true" 的页
面中才可以被使用,在一般的JSP页面中使用该对象将无法编译JSP文件。
excepation对象和Java的所有对象一样,都具有系统提供的继承结构。
exception 对象几乎定义了所有异常情况。在Java程序中,可以使用try/catch
关键字来处理异常情况; 如果在JSP页面中出现没有捕获到的异常,就会生
成 exception 对象,并把 exception 对象传送到在page指令中设定的错误页
面中,然后在错误页面中处理相应的 exception 对象。
3.<%@include%>和<jsp:include>的区别
<%@include file="文件的URL"%> **静态引入**
页面请求之前预编译,所有代码包含进来之后,一起进行处理,把所有代码合在
一起,编译成一个servlet
<jsp:include page="文件的URL"/>**动态引入**
所有代码分别处理,在页面被请求的时候才编译,被编译成多个servlet,页面语
法相对独立,处理完成之后再将代码的显示结果(处理结果)组合进来。
三.Web应用的编码问题
1.先了解字符集和编码
(1)字符集和编码
概念:简单的说字符集就规定了某个文字对应的二进制数字存放方式
(编码)和某串二进制数值代表了哪个文字(解 码)的转换关系
我们在计算机屏幕上看到的是实体化的文字,而在计算机存储介质中存放的
实际是二进制的比特流。那 么在这两者之间的转换规则就需要一个统一的标
准,否则把我们的U盘插到老板的电脑上,文档就乱码了;小伙伴QQ上传过
来的文件,在我们本地打开又乱码了。 于是为了实现转换标准,各种字符集
标准就出现了。
例如,“屌”这个字,对应的编码如下:
字符集 | 16进制编码 | 对应的二进制数据 |
---|---|---|
UTF-8 | 0xE5B18C | 1110 0101 1011 0001 1000 1100 |
UTF-16 | 0x5C4C | 1011 1000 1001 1000 |
GBK | 0x8CC5 | 1000 1100 1100 0101 |
(a)ASCII
ASCII 码使用指定的 7 位或 8 位二进制数组合来表示 128 或 256 种可能
的字符。标准 ASCII 码也叫基础ASCII码,使用 7 位二进制数来表示所有
的大写和小写字母,数字 0 到 9、标点符号, 以及在美式英语中使用的
特殊控制字符。
(b)非ASCII编码
英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够
的。比如,在法语中,字母上方有注音符号,它就无法用 ASCII 码表示。
于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比
如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲
国家使用的编码体系,可以表示最多256个符号。但是,这里又出现了新
的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的
编码方式,代表的字母却不一样。比如,130在法语编码中代表了é,在希
伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。
但是不管怎样,所有这些编码方式中,0--127表示的符号是一样的,不一
样的只是128--255的这一段。至于亚洲国家的文字,使用的符号就更多了,
汉字就多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必
须使用多个字节表达一个符号。比如,简体中文常见的编码方式是GB2312,
使用两个字节表示一个汉字,所以理论上最多可以表示 256 x 256 = 65536
个符号。
(c)UNICODE
正如上一节所说,世界上存在着多种编码方式,同一个二进制数字可以被
解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码
方式,否则用错误的编码方式解读,就会出现乱码。为什么电子邮件常常
出现乱码?就是因为发信人和收信人使用的编码方式不一样。以想象,如
果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个
独一无二的编码,那么乱码问题就会消失。这就是 Unicode,就像它的名
字都表示的,这是一种所有符号的编码。Unicode 当然是一个很大的集合,
现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,
U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E25表
示汉字严。具体的符号对应表,可以查询unicode.org,或者专门的汉字对
应表。
Unicode的问题:
需要注意的是,Unicode 只是一个符号集,它只规定了符号的二进制
代码,却没有规定这个二进制代码应该如何存储。
比如,汉字严的 Unicode 是十六进制数4E25,转换成二进制数足足有15
位(100111000100101),也就是说,这个符号的表示至少需要2个字节。
表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。这里就
有两个严重的问题,第一个问题是,如何才能区别 Unicode 和 ASCII ?
计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第
二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果
Unicode 统一规定,每个符号用三个或四个字节表示,那么每个英文字母
前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件
的大小会因此大出二三倍,这是无法接受的。它们造成的结果是:一是出
现了 Unicode 的多种存储方式,也就是说有许多种不同的二进制格式,可
以用来表示 Unicode。二是Unicode 在很长一段时间内无法推广,直到互
联网的出现。
(i)UTF-8
互联网的普及,强烈要求出现一种统一的编码方式。UTF-8 就是在互
联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括
UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个
字节表示),不过在互联网上基本不用。重复一遍,这里的关系是,
UTF-8 是 Unicode 的实现方式之一。UTF-8 最大的一个特点,就是它
是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不
同的符号而变化字节长度。UTF-8 的编码规则很简单,只有二条:
第一条:对于单字节的符号,字节的第一位设为0,后面7位为这个符
号的Unicode码。因此对于英语字母,UTF-8 编码和 ASCII
码是相同的。
第二条:对于n字节的符号(n > 1),第一个字节的前n位都设为1,第
n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提
及的二进制位,全部为这个符号的 Unicode 码。下表总结了编
码规则,字母x表示可用编码的位。
总结:
UNICODE和UTF-8的区别:
UNICODE是字符集(编码),UTF-8是编码格式,编码格式是用来序列化或
存储字符集编码的一种“格式”。。
关于UNICODE和UTF-8参考自:
https://blog.csdn.net/Deft_MKJing/article/details/79460485
(ii)UTF-16
暂时略
(iii)UTF-32
暂时略
3.关于windows平台的记事本编码说明
具体参看:http://www.cnblogs.com/WestGarden/archive/2012/09/02/3138331.html
4.Java中的编码
首先强调一下“编码和编码格式”(这里有些和上面的内容重复)
Unicode是一种“编码”,所谓编码就是一个编号(数字)到字符的一种映射关系,就仅仅是一
种一对一的映射而已;GBK、UTF-8是一种“编码格式”,是用来序列化或存储1中提到的那
个“编号(数字)”的一种“格式”。
GBK和UTF-8都是用来序列化或存储Unicode编码的数据的,但是分别是2种不同的格式,
他们都是Unicode编码的实现方式;他们俩除了格式不一样之外,他们所关心的Unicode编
码范围也不一样。UTF-8考虑了很多种不同国家的字符;而GBK只考虑中文——在
Unicode中的一小部分的字符的编码。
(1)J2SE中的编码
java的String使用的编码是Unicode,当String存在于内存中时(在代码中用string类型
的引用对它进行操作时),是"只有编码而没有编码格式的",所以java程序中的任何
String对象,说它是gbk还是utf-8都是错的,String在内存中不需要“编码格式”,它只
是一个Unicode的字符串而已。
当字符串需要在网络中传输或要被写入文件时,就需要使用编码格式了。乱码问题
也因此出现。
(a)Java文件编译后形成class
这里Java文件的编码可能有多种多样,但Java编译器会自动将这些编码按照
Java文件的编码格式正确读取后产生class文件,这里的class文件编码是
Unicode编码(具体说是UTF-16编码)。
因此,在Java代码中定义一个字符串:String s="汉字";不管在编译前java文件
使用何种编码,在编译后成class后,他们都是一样的----Unicode编码表示。
(b)JVM中的编码
JVM加载class文件读取时候使用Unicode编码方式正确读取class文件,那么
原来定义的String s="汉字";在内存中的表现形式是Unicode
(2)Java Web中的编码
假定浏览器就是ie,WEB服务器是tomcat,页面是jsp,应用服务器用的是servlet
(a)浏览器中打开了一个页面,这个页面映射应用服务的某个jsp。此时浏览器中
的解码格式是什么呢?
浏览器的解码格式是在jsp中指定的,比如在jsp文件中经常可以看到这样两
行代码。
<!--
这一句是和Tomcat说的:保存在硬盘上的jsp文件在被Tomcat翻译成servlet的时候,使用utf-8解码jsp文件的内容。
如果不指定,会默认使用iso-8859-1来解码。
-->
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!-- 这一行是和浏览器说的:浏览器解码的时候请使用utf-8解码哦 -->
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
这样浏览在解析这个jsp的时候,就是使用utf-8编码来解码。
浏览器的解码格式也是可以在浏览器上设置的:
(b)response中也可以设置内容的编码格式和指定浏览器的解码格式。
开发人员应该都知道,jsp就是一个特殊一点的servlet。在servlet中, response
有两个设置编码的方法,和jsp中的那两行编码配置有着相似的功能。
// 表示response的内容会以utf-8的编码方式编码后发送给浏览器。
response.setCharacterEncoding("UTF-8");
// 告诉浏览器,解码的时候也要使用utf-8解码哦。
response.setContentType("text/html;charset=UTF-8");
(c)通过request对象可以指定应用服务用哪种编码格式来解码接收到的数据
// 通过这句话,可以指定用utf-8编码格式来解码浏览器传来的数据。不过只对post方式传来的数据有效。如果是get方法传来的数据,还是会以默认的iso-8859-1来解码。
request.setCharacterEncoding("UTF-8");
(d)设置tomcat服务器配置文件server.xml,指定以何种编码解码浏览器传来的参
数。
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" URIEncoding="UTF-8" />
'URIEncoding="UTF-8"', 这个属性的配置, 和"
request.setCharacterEncoding("UTF-8");"这句代码的功能大致相同。都是指
定tomcat服务器接受到收据后如何解码。如果不指定,tomcat服务器会默认
使用iso-8859-1来解码。如果一个tomcat服务器里有多个应用,应该考虑该处
修改是否会对其它应用造成伤害。
(e)get和post传中文
(i)get
方法一:在客户端使用 URLEncoder.encode(“中文”,”UTF-8”)对中文参
数进行编码,在服务器端需要进行解码
this.setName(java.net.URLDecoder.decode(name, “UTF-8”));
方法二:修改tomcat的server.xml文件
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" URIEncoding="UTF-8" useBodyEncodingForURI="true"/>
其它和web中乱码的解决方案差不多。
(ii)post
和web中乱码的解决方案差不多。
乱码问题的解决来源网络,是否好使,不知道,需要自己实验一下,实验暂
时略。
5.URL重写和重定向(这是两个比较古老的技术)
(1)URL重写
(a)概念
URL 重写是拦截客户端传入 Web 请求URL并自动将其定向到到规则指定的 URL
的过程。比如浏览器发来请求 http://blog.mocoder.com/hello.html ,服务器自动
(b)URL重写的好处
(i)搜索引擎比较喜欢.html,.htm的(与.jsp,.php,.aspx,.cff相比),因为.html,
.htm是静态的,更容易让引擎了解你网页的内容。而动态网页的内容是根
据用户,来输出不同的内容,不容易让引擎吸收具体HTML内容。
(ii)如果不用URL Rewriting将拓展名隐藏或改成.html,那么假如这个网站要
换个技术或把动态页面换成静态,则需要寻找所有含有拓展名的连接,
把连接所含URL进行拓展名修改(如从JSP换到PHP技术,则要寻找所有
含有.jsp的页面,并把所有含.jsp的URL改成.php,费时费力)。
URL Rewriting正好避免了这点,因为好的URL是能做到“不变应完变”的。
(iii)防止某些黑客恶意攻击。
有些大网站采用不同的技术开发不同功能的页面。而把拓展名改掉,让黑
客无法确认此页面用的技术是什么,从而就无从下手。
(iv)方便访问者使用。访问者不是程序员,他们不明白什么是.jsp,.php.aspx,
他们只知道URL。所以统一把拓展名拿掉,或者同意把拓展名换为html,
htm,有利于用户的使用。用户可以知道现在在你网站的位置,如何通过
输入URL到某一页面。
url重写在php开发的web程序中应用十分广泛,当然,大多数的javaweb框架
如springMVC、struts都有指定访问url的配置,但是不够灵活。
(2)URL重定向
URL重定向,是指当使用者浏览某个网址时,将他导向到另一个网址的技术。
(3)重写和重定向的区别
重定向 :浏览器知道页面位置发生变化,从而改变地址栏显示的地址。
搜索引擎意识到页面被移动了,从而更新搜索引擎索引,将原来失效的
链接从搜索结果中移除。
临时重定向(R=302)和永久重定向(R=301)都是亲搜索引擎的,是SEO
的重要技术
重写:用于将页面映射到本站另一页面,若重写到另一网络主机(域名),则按
重定向处理。
浏览器的URL地址不会发生变化。
四:Web应用的安全
1.CSS攻击,跨站脚本攻击
跨站脚本,顾名思义,就是恶意攻击者利用网站漏洞往Web页面里插入恶意代码,
一般需要以下几个条件:
(a)客户端访问的网站是一个有漏洞的网站,但是他没有意识到;
(b)在这个网站中通过一些手段放入一段可以执行的代码,吸引客户执行(通过鼠标
点击等);
(c)客户点击后,代码执行,可以达到攻击目的。
解决方案:对用户输入的数据进行HTML转移处理。如今很多开源框架默认就支持
HTML的转义。
2.CSRF攻击(Cross Site Request Forgery, 跨站域请求伪造)
你这可以这么理解 CSRF
攻击:攻击者盗用了你的身份,伪装成你发送恶意请求。
CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购
买商品,虚拟货币转账......造成的问题包括:个人隐私泄露以及财产安全。
具体攻击过程如下:
(1)用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
(2)在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登
录网站A成功,可以正常发送请求到网站A;
(3)用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
(4)网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第
三方站点A
解决方案:
(1)将cookie设置成HttpOnly
(2)增加token
其原理是在请求中放入攻击者所不能伪造的信息,并且该新信息不存在于cookie
中。鉴于此,系统开发人员可以在HTTP请求中以参数的形式加入一个随机产生
的token,并在服务端进行token验证,如果请求中没有token或者token内容不正
确,则认为CSRF攻击而拒绝该请求。例如,token可以存放在表单中的隐藏域
中:<input type="hidden" name="_token" value="tokenvalue"/>。token的值通
过服务端生成,表单提交后token的值通过POST请求与参数一同带到服务端,
每次回话可以使用相同的token,会话过期,则token失效,攻击者因无法获取
token,也就无法伪造请求。
HttpSession session=request.getSession()
Object token=session.getAttribute("_token")
if(token==null||"".equals(token))
{
session.setAttribute("_token",UUID.randomUUID().toString());
}
3.SQL注入攻击
解决方案:
(1)使用预编译语句(PreparedStatement)
预编译语句使用参数占位符来替代需要动态传入的参数,这样攻击者无法改变
SQL语句的结构,SQL语句的语义不会发生变化,即便用户输入sql,它会将其
转义。
例如,原语句:
select * from hhuser where nick=nickname and password=password
例如用户输入 'or '1'='1时,不使用预编译,生成语句为:
select * from hhuser where nick='zhangsan' and password='' or '1'='1'
使用预编译:
select * from hhuser where nick='zhangsan' and password='\' or \'1\'=\'1'
可见发生了转义。
(2)使用ORM框架,如IBATIS和Hibernate等都支持输入变量的转义
实现办法时通过#配置变量
4.文件上传漏洞
(1)一般处理方法
为了防止用户上传恶意的可执行文件和脚本,以及将文件服务器当做免费的文
件存储服务器使用,我们需要对上传的文件进行白名单校验并限制上传文件的
大小,上传文件需要重新命名,使共攻者无法猜测上传文件的访问路径。对于
上传的文件来说,不能简单地通过后缀名称判断文件的类型,因为恶意攻击可
以将可执行文件的后缀名称改成图片或者其它后缀类型,诱导用户执行。因
此,判断文件类型需要使用更安全的方式,很多类型的文件起使得几个字节内
容使固定得,根据这几个字节的内容,就可以确定文件的类型,这几个字节被
称为魔数。
(2)配合使用imagemaglck
对于图片类型的文件,可以上传后对图片进行相应的缩放,破坏恶意用户上传
的二进制可执行文件的结构。imagemaglck是一套功能强大、稳定并且开源的
对图片进行处理的开发工具包,能处理多种格式的图片文件,可以利用
imagemaglck来对图片进行缩放。
魔数枚举类型:
public enum FileType {
/** JPEG */
JPEG("FFD8FF"),
/** PNG */
PNG("89504E47"),
/** GIF */
GIF("47494638"),
/** TIFF */
TIFF("49492A00"),
/** Windows bitmap */
BMP("424D"),
/** CAD */
DWG("41433130"),
/** Adobe photoshop */
PSD("38425053"),
/** Rich Text Format */
RTF("7B5C727466"),
/** XML */
XML("3C3F786D6C"),
/** HTML */
HTML("68746D6C3E"),
/** Outlook Express */
DBX("CFAD12FEC5FD746F "),
/** Outlook */
PST("2142444E"),
/** doc;xls;dot;ppt;xla;ppa;pps;pot;msi;sdw;db */
OLE2("0xD0CF11E0A1B11AE1"),
/** Microsoft Word/Excel */
XLS_DOC("D0CF11E0"),
/** Microsoft Access */
MDB("5374616E64617264204A"),
/** Word Perfect */
WPB("FF575043"),
/** Postscript */
EPS_PS("252150532D41646F6265"),
/** Adobe Acrobat */
PDF("255044462D312E"),
/** Windows Password */
PWL("E3828596"),
/** ZIP Archive */
ZIP("504B0304"),
/** ARAR Archive */
RAR("52617221"),
/** WAVE */
WAV("57415645"),
/** AVI */
AVI("41564920"),
/** Real Audio */
RAM("2E7261FD"),
/** Real Media */
RM("2E524D46"),
/** Quicktime */
MOV("6D6F6F76"),
/** Windows Media */
ASF("3026B2758E66CF11"),
/** MIDI */
MID("4D546864");
private String value = "";
private FileType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}