深入理解cookie和session
cookie和session在java web开发中扮演了十分重要的作用,本篇文章对其中的重要知识点做一些探究和总结。
1.cookie存在于浏览器
随意打开一个网址,用火狐的调试工具,随意选取一个链接,查看其请求头。你就会看到cookie的信息。如下图所示。
如上图所示,我们访问了新浪网,通过火狐浏览器的调试窗口可以看到cookie存在于请求头也就是httprequest中,并且是以键值对(数组)的形式存在。
只要有请求,就会在请求头携带一个cookie的数组(键值对)。cookie是浏览器层面的东西。
2.java中获取cookie
事实上,在java的servlet体系里,我们可以通过如下方式获取cookie.
HttpServletRequest req=ServletActionContext.getRequest();
Cookie[] cookies=req.getCookies();
for(int i=0;i<cookies.length;i++){
Cookie cookie=cookies[i];
System.out.println("name:"+cookie.getName()+",domain"+cookie.getDomain()+",value:"+cookie.getValue()+",maxage:"+cookie.getMaxAge());
}
可以看到,在servlet体系中,把cookie作为一个属性放到了HttpRequest对象里面。通过getCookies()方法得到一个cookie数组。
我们在一个action中加入上述代码,并且访问这个action,则可以看到控制台打印出如下信息。
servlet对cookie进行了封装,cookie对象有几个属性,如name,domain,value,maxage等,具体的意义可以参考servlet的api文档。
以上的请求的cookie是我首次访问某一个网站的链接时候产生的。可以看到cookie数组中只有一个元素。这边先注意一下,后续会有更进一步的说明。
3.java中向cookie中添加元素
说了获取cookie数组和cookie,我们一定也想知道如何把我们自己的一些信息放进cookie,其实很简单。http的一次请求总是伴随着一次响应,我们就将cookie信息放入到响应中,传递给浏览器。在java下代码是这样写的。
HttpServletResponse res=ServletActionContext.getResponse();
Cookie cookie=new Cookie("xdx", "i'm xdx");
res.addCookie(cookie);
可以看到当我们发起这个请求时,在响应头有下列信息。
也就是通过这次请求,我们把xdx=i'm xdx 这个cookie通过response放进了浏览器。
当我们再次访问该网站上的其他页面的时候,在请求头都将带有这个cookie。如下图所示。
而假如我们清除历史记录,包括cookie。
再次访问该网站的某一个地址。刚才我们加进去的cookie就不存在了。
总结来说就是:servlet通过response将cookie放入到cookie数组中,这样浏览器端就会拥有这一个cookie信息。浏览器会在以后的请求过程中把这个cookie信息放在请求头。
4.session是什么
session我们一般指的是HTTPSession,为了理解它,我们直接打开HttpSession的源码来一看究竟。
/**
*
* Provides a way to identify a user across more than one page
* request or visit to a Web site and to store information about that user.
*
* <p>The servlet container uses this interface to create a session
* between an HTTP client and an HTTP server. The session persists
* for a specified time period, across more than one connection or
* page request from the user. A session usually corresponds to one
* user, who may visit a site many times. The server can maintain a
* session in many ways such as using cookies or rewriting URLs.
*
* <p>This interface allows servlets to
* <ul>
* <li>View and manipulate information about a session, such as
* the session identifier, creation time, and last accessed time
* <li>Bind objects to sessions, allowing user information to persist
* across multiple user connections
* </ul>
*
* <p>When an application stores an object in or removes an object from a
* session, the session checks whether the object implements
* {@link HttpSessionBindingListener}. If it does,
* the servlet notifies the object that it has been bound to or unbound
* from the session. Notifications are sent after the binding methods complete.
* For session that are invalidated or expire, notifications are sent after
* the session has been invalidated or expired.
*
* <p> When container migrates a session between VMs in a distributed container
* setting, all session attributes implementing the {@link HttpSessionActivationListener}
* interface are notified.
*
* <p>A servlet should be able to handle cases in which
* the client does not choose to join a session, such as when cookies are
* intentionally turned off. Until the client joins the session,
* <code>isNew</code> returns <code>true</code>. If the client chooses
* not to join
* the session, <code>getSession</code> will return a different session
* on each request, and <code>isNew</code> will always return
* <code>true</code>.
*
* <p>Session information is scoped only to the current web application
* (<code>ServletContext</code>), so information stored in one context
* will not be directly visible in another.
*
* @author Various
*
* @see HttpSessionBindingListener
* @see HttpSessionContext
简单的翻译一下:
--提供一个在多页面请求切换的情况下用于验证、存储用户信息的手段。 --servlet容器使用Httpsession来创建连接客户端和服务端的一个会话(session)。这个会话能持续一段指定的时间(也就是我们常说的session过期时间),该会话能在多个请求之间共享。 --这个会话一般跟用户信息关联,由于这个用户可能多次访问网站,所以我们把他们存储在这个会话(也就是httpsession)里。 --服务端通常是通过cookies或者rewriting URLs来保持一个session。
我的理解,session是一种持久的会话,它的存在主要是为了克服http无状态的特点,关于http无状态,或者说没有记忆,这里不多阐述,涉及到计算机网络的知识,简单来说就是http一次请求对应一次响应,在这个过程中会携带一些信息,但这些信息也仅仅在这个过程中有效。当一个请求结束,我们进入下一个请求的时候,上一个请求里面的信息对当前的请求就没什么意义了,因为当前的请求根本不会知道上一个请求里面所包含的信息。
那么当我们需要一些在各个请求都能公用的信息的时候,该怎么办呢?有很多办法,可以把信息存在数据库,然后每次从数据库去取出来,当然io存取会浪费很多时间,它仅仅针对大数据量。还有一种就是将这些信息存在内存当中。没错session其实就是这样一种对象,他把项目当中一些常用的信息存在内存当中,这些常用的信息通常是跟用户相关的,比如用户名,用户昵称,用户角色等。因为他们需要经常用到,所以把这些信息存在session中进行管理再好不过了。
5.如何获取一个session
在servlet体系里,我们可以用如下代码来获取session。
HttpServletRequest request = ServletActionContext.getRequest();
HttpSession httpSession=request.getSession();
System.out.println(httpSession);
我们来查阅HttpServletRequest的源码,看看其getSession()方法。
/**
*
* Returns the current session associated with this request,
* or if the request does not have a session, creates one.
*
* @return the <code>HttpSession</code> associated
* with this request
*
* @see #getSession(boolean)
*
*/ public HttpSession getSession();
它的解释是返回当前与request关联的session,如果这个请求不存在session,就新建一个。
我们在两个请求中加入上述代码并运行,得到如下结果。
可以看到在整个项目内,这个session被共享着调用。
6.session和cookie到底有什么关系呢?
简单点说,每一个session对象都有一个sessionId,而在cookie数组中,又一个元素叫做JSESSIONID,服务器将session对象的sessionId,以名称叫做JSESSIONID,值为sessionId的形式存入cookie数组中,这样cookie和session就发生了关联。
上面的描述可以用下图来表示。
上述的过程可以用类似如下的代码来实现。
HttpServletRequest req=ServletActionContext.getRequest();
HttpServletResponse res=ServletActionContext.getResponse();
Cookie cookie=new Cookie("JSESSIONID", req.getSession().getId());
res.addCookie(cookie);
只不过我们并不需要写这个代码,servlet自动帮我们完成了如上的操作。
7.具体过程
具体的过程是这样的:
(1)当我们首次在某个请求中通过调用request.getSession去获取session的时候(这个调用不一定是显式的,很多框架把session封装成map等其他的类型,名称也不一定是session,但是本质都是在调用session),首先servlet通过getCookie的到本次请求的cookie信息,然后去寻找cookie数组中是否有否有一个名为JSESSIONID的cookie,没有的话就创建一个session,并且把sessionId做为JSESSIONID这个cookie的值,然后调用addCookie()方法把该cookie放入cookie数组。
(2)如果上一步中从cookie数组中取到的cookie数组已经包含了JSESSIONID这个cookie,这时候我我们取出JSESSIONID的值,然后去内存中的session(内存中有很多session)去寻找对应的sessionId为JSESSIONID的值的session,如果找得到的话,就使用这个session,找不到的话,就新建一个session,并且同样的调用addCookie()方法覆盖掉原来的JSESSIONID这个cookie的值。
上述的过程可以用类似如下的代码来表示。
HttpServletRequest req=ServletActionContext.getRequest();//具体获取request的情况可能有所不同
HttpSession session;
Cookie JSESSIONID=null;
Cookie[] cookies=req.getCookies();
for(int i=0;i<cookies.length;i++){
Cookie cookie=cookies[i];
if(cookie.getName().equals("JSESSIONID")){
JSESSIONID=cookie;
}
}
if(JSESSIONID==null){
session= createSession();//创建一个session
}else{
session=findSessionBySessionId(JSESSIONID.getValue());//通过sessionId获取session
if(session==null){
session= createSession();//创建一个session
}
HttpServletRequest req=ServletActionContext.getResponse();//具体情况可能有所不同
Cookie cookie=new Cookie("JSESSIONID", session.getId());
res.addCookie(cookie);
我们将浏览器缓存清除,这样cookie中就没有JSESSIONID了,然后我们访问一个action。如下。
8.当我们清空浏览器的时候,session会消失吗?
这个问题包含着一些陷阱。因为很多时候当我们清空浏览器以后,确实需要重新登录系统才可以操作,所以很多人自然而然认为清空浏览器缓存(包含cookie)以后。session就会消失。
其实这种结论是错误的。要知道,session是存在于服务器的,你清除浏览器缓存,只是清除了cookie,跟session一点关系都没有。那么为什么我们却不能访问网站,而需要重新登录了呢?
一般的web项目会通过session来判断用户是否有登录,常用的判断语句是if(session.get("userId")==null。如果为空,则表示需要重新登录。这时候其实隐式地调用了getSession()方法,这就回到了上一步我们所讲的session获取的具体步骤了。
因为清空了浏览器缓存,这时候cookie数组中必定不会有JSESSIONID这个cookie,所以必须得新建一个session,用新的sessionId来给JSESSIONID这个cookie赋值。由于是新建的session,session中必定没有userId这样的属性值,所以判断结果自然为空,所以需要重新登录。这次赋值以后,下一次再请求该网站的时候,由于cookie数组中已经有了JSESSIONID这个cookie,并且能通过该JSESSIONID的值找到相应的session,所以就不需要再重新登录了。
9.讨论几种极端情况
第一种:当项目正常运行,我们清空浏览器,cookie和session会发生什么变化。
因为清空了浏览器,所以不会存在JSESSIONID这个cookie,servlet无法找到对应的session,所以他会新建一个session,然后在本次请求的响应中将sessionId传入cookie中。
下一次请求,cookie数组中就带有JSESSIONID这个cookie了,servlet就可以找到对应的session,沿用即可。
第二种:浏览器正常,项目重启(可停止tomcat来模拟这种情况),cookie和session会发生什么变化。
因为项目重启,内存中的一切session都消失了,虽然访问一个action,请求头中有JSESSIONID这个cookie,但是通过它的值(sessionId)并不能找到session(因为根本没有任何session),所以还是得重新创建一个session,并且这个session的sessionId跟当前cookie数组中的JSESSIONID的值不一样,所以它会将新的sessionId覆盖掉cookie数组中原来的JSESSIONID,并且由于此时的session是崭新的,所以他不可能有userId这样的属性值,所以在拦截的时候依然会被截获,因此也是需要重新登录的。
有兴趣的同学可以去试验一下。
10.session的时限
可以通过setMaxInactiveInterval()方法来设置session的时限,比如可以设为半个小时。这个时间指的是session不活跃开始计算的时间。超过这个时间,session就失效了。此时若再getSession(),则会创建一个新的session,并且其sessionId为此时浏览器中JSESSIONID的值。
有三种方式来设置session的时限:
--通过在web容器中设置(以tomcat为例),在tomcat-7.0\conf\web.xml中设置,以下是tomcat7.0中默认配置:
<session-config>
<session-timeout>30</session-timeout>
</session-config>
--通过web项目的web.xml文件来设置。设置15分钟失效。
<session-config>
<session-timeout>15</session-timeout>
</session-config>
--直接在代码中设置
session.setMaxInactiveInterval(30*60);//以秒为单位,即在没有活动30分钟后,session将失效
上述三种方法的优先级:1<2<3.
我们来做个试验,在web.xml中设置session的过期时间为1分钟,然后观察session和cookie的变化。
第一次访问:
过了一分钟后,再次访问
发现还是没变,但马上再次访问。
结合我们之前所说的session和cookie的作用过程来解释一下:第二次访问的时候,因为过了一分钟,超过了session的过期时间,所以此时虽然cookie中还是原来的sessionID(这也是为什么第二次与第一次的请求头中SessionID相同的原因),但是通过此SessionID是无法再找到那个已经失效的session,所以服务端必须重新创建一个session,并且把新的sessionID放到cookie中,覆盖掉原来旧的。所以当我们马上再次访问的时候,这一次就是新的cookie了。
ps:其实这个过程跟服务端暂停服务的效果是一样的,只不过服务端暂停服务影响的是内存中的所有session,而session过期只是影响当前过期的这个session。
pss:cookie过期与session过期类似,只不过是发生在客户端,大家可以依照前面讲的作用过程试着推导cookie过期会发生什么事情。
psss:如果用了redis等内存数据库来管理session,那么设置过期时间将不起作用。
12.非浏览器平台使用session
cookie依赖于浏览器,session依赖于服务器。如果项目不在浏览器上面运行,那么cookie也就无用武之地,但是我们还是想要使用session。不巧的是,session依赖cookie进行管理,这时候要怎么办呢?
举个很常见的场景,我们想要在安卓,或者ios、或者微信小程序等非浏览器的项目中使用session。这时候这些客户端不会自动帮我们做管理cookie的工作。那么这时候我们需要自己来做。
前面讲到过,servlet底层帮我们自动使用cookie来管理session,大体过程是:从cookie中找寻sessionID,根据sessionID去找session,找到合适的就用,找不到的话就新建一个,并且用新的sessionID覆盖掉cookie中旧的。
而现在需要明白两点:
(1)我们没有浏览器了,所以不会在http的请求头中携带cookie信息,servlet后台收不到cookie信息,自然找不到JESSIONID,所以他会在每次请求都创建一个新的session,这对于服务端来说,是一笔严重的性能开销。
(2)因为没有浏览器了,servlet在更新完sessionID以后,不会自动执行set-cookie操作将新的sessionID去覆盖旧的cookie中的JESSIONID的值。
根据以上两点,我我们需要做如下几件事情。
(1):模拟http请求的时候在请求头中带上cookie,在cookie中塞入从JESSIONID。这样服务端就可以取到JESSIONID,从而获取sessionID,进行比对来获取session;
(2),在响应给客户端的时候,手动调用set-cookie方法将本次的sessionID放入JESSIONID中,这样客户端就可以得到最新的JESSIONID。
(3):然后客户端需要维护一个cookie的静态变量(或者用其他方法,总之就是维护一个cookie的内存变量)。将服务端响应回来的JESSIONID的值存在这个变量里面。作为下次请求的时候放入请求头。
(4):我们同样可以在服务端写一个拦截器,客户端的每个请求都必须先经过拦截器,这样就可以通过session来判断用户是否处于登录状态了。这个过程与在浏览器平台毫无二致。
下面我们来实现上面的功能。
由于本人不会写安卓和iOS的代码,所以以一个客户端程序来模拟安卓端。
首先,定义一个静态变量用于存储JESSIONID.初始值为空。
public static String J_SESSIONID="";
接下来我们编写模拟http请求的类,需要在这个类中的请求头中加入cookie,并且在响应的时候得到响应头中的cookie.具体代码如下。
public static String http4Cookie(String url, String param,String jsessionId) {
String result = "";
BufferedReader in = null;
try {
String urlNameString = url + "?" + param;
URL realUrl = new URL(urlNameString);
// 打开和URL之间的连接
HttpURLConnection connection = (HttpURLConnection) realUrl
.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
connection.setRequestProperty("Cookie", "JSESSIONID="+jsessionId);//请求头中加入JessionID
// 建立实际的连接
connection.connect();
// 获取所有响应头字段
Map<String, List<String>> map = connection.getHeaderFields();
// 遍历所有的响应头字段,获取响应头中的JESSIONID信息
for (String key : map.keySet()) {
// System.out.println(key + "--->" + map.get(key));
if("Set-Cookie".equals(key)){
String setCookie=map.get(key).toString();
String newJessionId=setCookie.substring(12,setCookie.lastIndexOf("]"));
J_SESSIONID=newJessionId;// 维护新的J_SESSIONID
System.out.println(setCookie.substring(12,setCookie.lastIndexOf("]")));
}
}
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(
connection.getInputStream(), "utf-8"));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
然后我们在服务端写一个Action让客户端来模拟请求。
@ResponseBody
@RequestMapping("httpCookieTest")
public String httpTest(HttpServletRequest req,HttpServletResponse res) {
Cookie[]cookies=req.getCookies();
for(Cookie cookie:cookies){
if(cookie.getName().equals("JSESSIONID"));
System.out.println(cookie.getValue());
}
OutPutMsg.outPutMsg(res, req, "httpCookieTest");
return null;
}
特别注意这里的OutPutMsg,它的代码如下。
1 public static void outPutMsg(HttpServletResponse response,HttpServletRequest request,String msg) {
2 response.setCharacterEncoding("utf-8");
3 String sessionId = request.getSession().getId();
4 response.setContentType("text/html; charset=UTF-8");
5 response.setHeader("Set-Cookie", "JSESSIONID=" + sessionId);
6 PrintWriter writer = null;
7 try {
8 writer = response.getWriter();
9 writer.print(msg);
10 writer.flush();
11 } catch (IOException e) {
12 e.printStackTrace();
13 } finally {
14 if (writer != null)
15 writer.close();
16 }
17 }
注意到第4和第5行代码,这两行代码往响应头中加入了此时的sessionId,作为JSESSIONID的值,放入到cookie中。这样客户端才能从响应头中获取最新的JSESSIONID,以更新静态变量J_SESSIONID的值,对应http4Cookie中的代码。
然后我们来看看客户端的主函数。
public static void main(String args[]){
HttpUtil.http4Cookie("http://192.168.1.185:8080/warrior/httpCookieTest","1=1",J_SESSIONID);
}
这样以后,其实这个过程已经跟浏览器的cookie运作机制并无二致了。
最后,我们在写一个拦截器,对所有客户端请求进行拦截。客户端每次请求之间都率先访问这个拦截器。
@ResponseBody
@RequestMapping("otherPlatformIntercept")
public String otherPlatformIntercept(HttpServletRequest req){
HttpSession httpSession=req.getSession();
if(httpSession.getAttribute("userId")!=null){
return "valid";
}
return "Invalid";
}
深入理解cookie和session的更多相关文章
- 转:理解Cookie和Session机制
原文: 理解Cookie和Session机制 摘要: Cookie工作原理 由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户身份.怎么办呢?就给客户端们颁发一个通行证吧,每人一个,无论 ...
- 理解Cookie和Session机制
转载: 理解Cookie和Session机制 会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话.常用的会话跟踪技术是Cookie与Session.Cookie通过在客户端记录 ...
- 形象地理解Cookie和Session
Cookie和Session的形象理解 通过实际生活中的银行卡来理解Cookie和Session间的关系: Cookie相当于银行卡 Session相当于银行账户 结合到银行存钱和取钱的过程来理解: ...
- 为什么你学不会递归?告别递归,谈谈我的一些经验 关于集合中一些常考的知识点总结 .net辗转java系列(一)视野 彻底理解cookie,session,token
为什么你学不会递归?告别递归,谈谈我的一些经验 可能很多人在大一的时候,就已经接触了递归了,不过,我敢保证很多人初学者刚开始接触递归的时候,是一脸懵逼的,我当初也是,给我的感觉就是,递归太神奇了! ...
- 基础知识《十二》一篇文章理解Cookie和Session
理解Cookie和Session机制 会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话.常用的会话跟踪技术是Cookie与Session.Cookie通过在客户端记录信息确定 ...
- 深入理解Cookie和Session机制
转载理解Cookie和Session机制 目录 Cookie机制什么是CookieCookie的不可跨域名性Unicode编码:保存中文BASE64编码:保存二进制图片设置Cookie的所有属性Coo ...
- 理解cookie,session,token
彻底理解cookie,session,token 发展史 1.很久很久以前,Web 基本上就是文档的浏览而已, 既然是浏览,作为服务器, 不需要记录谁在某一段时间里都浏览了什么文档,每次请求都是一个新 ...
- [转帖]彻底理解cookie,session,token
彻底理解cookie,session,token https://www.cnblogs.com/moyand/p/9047978.html 发展史 1.很久很久以前,Web 基本上就是文档的浏览而已 ...
- 一文理解Cookie、Session
一文理解Cookie.Session 1.什么是会话 用户打开浏览器,点击多个超链接,访问服务器的多个web资源,然后关闭浏览器,整个过程就称为一个会话: HTTP 是无状态,有会话的 HTTP 是无 ...
- 理解Cookie和Session机制(转)
目录[-] Cookie机制 什么是Cookie 记录用户访问次数 Cookie的不可跨域名性 Unicode编码:保存中文 BASE64编码:保存二进制图片 设置Cookie的所有属性 Cookie ...
随机推荐
- 【docker 一】入门实践、环境部署、基本操作指令、镜像库、数据卷
简述 `docker是如火如荼的容器技术,今后会陆续上传关于微服务技术的学习笔记,希望能和大家一起学习一起分享!` docker环境搭建 1.获取最新版的Docker安装包 $ wget -qO- h ...
- Python爬虫(十三)_案例:使用XPath的爬虫
本篇是使用XPath的案例,更多内容请参考:Python学习指南 案例:使用XPath的爬虫 现在我们用XPath来做一个简单的爬虫,我们尝试爬取某个贴吧里的所有帖子且将该帖子里每个楼层发布的图片下载 ...
- [Elasticsearch] 邻近匹配 (二) - 多值字段,邻近程度与相关度
多值字段(Multivalue Fields) 在多值字段上使用短语匹配会产生古怪的行为: PUT /my_index/groups/1 { "names": [ "Jo ...
- 《Android源代码设计模式解析与实战》读书笔记(十四)
第十四章.迭代器模式 迭代器模式,又叫做游标模式.是行为型设计模式之中的一个.我们知道对容器对象的訪问必定会涉及遍历算法.我们能够将遍历的方法封装在容器中,或者不提供遍历方法,让使用容器的人自己去实现 ...
- redis 简单安装使用
官方站点:http://redis.io/ 官方下载:http://redis.io/download 能够依据须要下载不同版本号 windows版:https://github.com/mythz/ ...
- 聚集索引VS非聚集索引
聚集索引VS非聚集索引 SQL Server 2014 发布日期: 2016年12月 索引是与表或视图关联的磁盘上结构,可以加快从表或视图中检索行的速度. 索引包含由表或视图中的一列或多列生成的键. ...
- 基于 HTML5 WebGL 的 3D 服务器与客户端的通信
这个例子的初衷是模拟服务器与客户端的通信,我把整个需求简化变成了今天的这个例子.3D 机房方面的模拟一般都是需要鹰眼来辅助的,这样找产品以及整个空间的概括会比较明确,在这个例子中我也加了,这篇文章就算 ...
- Lua 数组排序 table.sort的注意事项
1. table中不能有nil table.sort是排序函数,它要求要排序的目标table的必须是从1到n连续的,即中间不能有nil. 2. 重写的比较函数,两个值相等时不能return true ...
- 初识Avro
Avro是Hadoop生态圈的一部分,由Hadoop的创始人Doug Cutting牵头开发,当前最新版本1.8.2.Avro是一个数据序列化系统,设计用于支持大批量数据交换的应用.它的主要特点有: ...
- jemeter——badboy导入的jmx文件自带元件解析
线程组设置与解析 含义:1秒启动100个线程,每个线程循环调用20次请求 (包括FTP请求.Java请求.http请求,根据你提交的请求而定) delay thread creation until ...