深度長文回顧web基礎元件
什麼是Serlvet ?
全稱 server applet 執行在服務端的小程式:
首先來說,這個servlet是java語言編寫的出來的應用程式,換句話說servlet擁有java語言全部的優點,比如跨越平臺,一次編譯到處執行
其次: 相對於CGI(common gateway interface)規範而言,CGI是針對每一個使用者的請求建立一個程序處理,而servlet所在的伺服器會對每一個請求建立一個執行緒來處理,雖然執行緒數量有上限,但是相對於建立程序來說,後者對系統資源的開銷更小
然後就是: 現在盛行javaWeb伺服器Tomcat也是java語言編寫的,畢竟Tomcat有Serlvet容器支援,所以servlet和web伺服器之間無縫連線
Servlet其實一個介面,一套規範,不同的廠家對它有不同的實現,tomcat也是如此,
web伺服器會把解析http協議資訊的邏輯封裝進他們的Servlet中,比如將使用者傳送的請求(request) HttpRequestServlet
,
把響應給使用者http報文的邏輯封裝進HttpResponseServlet
中, 然後web伺服器負責不同元件,不同servlet之間的排程關係,
什麼是排程呢? 比如說: 通過某個URL找到指定的Servlet,回撥Servlet的service()
方法處理請求
Servlet的體系結構
servlet介面的實現類如上圖
Servlet在java中是一個介面,封裝了被瀏覽器訪問到伺服器(tomcat)的規則
新增serlvet
通過web.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <display-name>Camel Routes</display-name> <!-- Camel servlet --> <servlet> <servlet-name>app1</servlet-name> <servlet-class>com.changwu.web.MyServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- Camel servlet mapping --> <servlet-mapping> <servlet-name>app1</servlet-name> <url-pattern>/app1</url-pattern> </servlet-mapping> </web-app>
通過註解
捨棄web.xml是serlet3.0新增全註解技術, 這個註解的屬性和需要在xml中配置的對應的
需要Tomcat7及以上才支援
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
/**
* servlet-name
*/
String name() default "";
/**
* The URL patterns of the servlet
*/
String[] value() default {};
/**
* servlet的資源路徑, 可以為一個servlet配置多個訪問路徑
*/
String[] urlPatterns() default {};
/**
* 啟動級別預設是-1,同樣意味著依然是第一次訪問時初始化
*/
int loadOnStartup() default -1;
/**
* The init parameters of the servlet
*/
WebInitParam [] initParams() default {};
/**
* Declares whether the servlet supports asynchronous operation mode.
*
* @see javax.servlet.ServletRequest#startAsync
* @see javax.servlet.ServletRequest#startAsync(ServletRequest,
* ServletResponse)
*/
boolean asyncSupported() default false;
/**
* The small-icon of the servlet
*/
String smallIcon() default "";
/**
* The large-icon of the servlet
*/
String largeIcon() default "";
/**
* The description of the servlet
*/
String description() default "";
/**
* The display name of the servlet
*/
String displayName() default "";
}
servlet的路徑定義規則
- /xxx
@WebServlet(urlPatterns = {"/app1","/app2"})
- /xxx/yyy
@WebServlet(urlPatterns = {"/app1/app2"})
- /xxx/*
@WebServlet(urlPatterns = {"/app1/*"})
- *.do
@WebServlet(urlPatterns = {"*.do"})
執行原理:
- tomcat讀取xml配置檔案中配置servlet,根據使用者配置的載入時機,通過反射技術創建出物件例項
- 使用者的請求報文經過tomcat的解析,分發到的Servlet下面,進行不同的回撥處理
Servlet介面的方法
- 初始化方法, 建立servlet時執行一次
- 什麼時候被建立: 預設情況下 第一次訪問時被建立
- 一般我們都在web.xml配置,讓Servlet在啟動時完成載入
<load-on-startup>1</load-on-startup>
預設這個值是-1, 表示第一次訪問時被建立, 整數表示啟動時初始化
- 一般我們都在web.xml配置,讓Servlet在啟動時完成載入
- 此外: Servlet的init()方法僅僅被執行一次,說明serlet是單例的,那麼在併發的情況的就可能出現執行緒安全問題 , 解決: 儘量不要在serlvet中定義成員變數,我們最好去成員方法中定義變數,即使定義了, 不要提供set()方法,僅僅提供的get()
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init.....");
}
- 獲取serlvet config 配置物件
//
@Override
public ServletConfig getServletConfig() {
return null;
}
- 提供服務的方法, 每次serlvet被訪問都會執行一次
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("--------------------service------------");
}
- 獲取serlvet 的資訊, 版本等
@Override
public String getServletInfo() {
return null;
}
- 伺服器正常關閉前, 銷燬servlet時 回撥
- 伺服器非正常關閉,不會執行
@Override
public void destroy() {
System.out.println("destroy");
}
Servlet3.0新特性
Servlet3.0中的重大升級是ServletContainerInitializer
,通過這個技術使我們可以為現有的元件寫出可插拔的元件,與之相對應的是Servlet的新規範如下:
在執行的路徑下面建立指定的檔案
/classpath:
--META-INF (目錄)
--services (目錄)
--javax.servlet.ServletContainerInitializer (檔案)
我們可以在上面的檔案中配置一個類的全類名,這個類是誰無所謂,但是隻要它實現了這個ServletContainnerInitializer
介面,並重寫它的onStart()
方法,於是當容器(tomcat)啟動的時候就會呼叫這個類的 onStart()
方法
這個規範帶來的革命決定是歷史性的,有了它我們的程式碼就有了可插拔的能力,不信可以回想一下傳統的配置檔案,如果想給專案進行升級,還不想改動xml檔案,那是不可能的,但是現在不同了,只要讓我們的類實現這個ServletContainnerInitializer
,重寫它的方法,它的onStart()
就會被回撥,而其他的功能不受響應,去掉這個類,專案整體也不受響應
示例:
容器啟動的時候,會把容器中,被@HandlerTypes(value={Test.class})
中指定的所有Test.class
實現類(子類,子介面)的例項傳遞進下面的set集合
@HandlesTypes(Test.class)
public class ChangWuInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
System.out.println(set);
}
}
通過上面onstart()
方法可以看到,第二個引數位置上是 ServletContext, 這個物件是什麼?有啥用? 在下文中單獨開一個模組說
ServletContext
tomcat會為每一個web專案建立一個全域性唯一的ServeltContext,這個物件裡面封裝著整個應用的資訊,常用的當作域物件,所有的servlet之間共享資料,同時他還可以獲取出web.xml檔案中的資料
功能:
- 獲取MIME型別
- MIME型別是網際網路通訊中定義的檔案資料型別
格式: 大型別/小型別 如: test/html
在tomcat的配置檔案目錄中存在web.xml ,裡面的存在大量的MEMI型別的資料,都可以從
ServletContex
t中獲取出來
String getMimeType(String file)
- 域物件(共享資料)
範圍: 類似於Session,通過ServletContext物件我們也可以實現資料共享,但值得注意的是,Session是隻能在一個客戶端中共享資料,而ServletContext中的資料是在所有客戶端中都可以實現資料共享的。
方法:
setAttribute(String name,Onject obj);
getAttribute(String name);
removeAttribute(String name);
- 獲取檔案真實的檔案路徑
方法
this.getServletContext().getRealPath("/"); // 現在訪問的目錄是tomcat中和WEB-INF同級目錄
- 實現請求轉發
// 方式1:
request.getRequestDispatcher("/url").forward(req,res);
// 方式2:
this.getServletContext().getRequestDispatcher("/url").forward(req,res);
- 獲取web應用的初始化引數
我們可以用
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>com.gavin.servlet.MyServlet</servlet-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</servlet>
獲取:
String encoding = this.getServletConfig().getInitParameter("encoding");
如何獲取:
ServletContext
在web應用上下文中以單例的形式存在,下面兩種獲取方式得到的ServletContext
是同一個物件
ServletContext servlet1 = request.getServletContext();
ServletContext servlet2 = this.getServletContext();
this.getServletConfig().getServletContext();
生命週期
伺服器一啟動就建立,伺服器關閉時才銷燬
註冊三大web元件(servlet filter listener)
- Servlet
addServlet、createServlet、getServletRegistration、getServletRegistrations
- Filter
addFilter、createFilter、getFilterRegistration、getFilterRegistrations
- 監聽器
addListener、createListener
Spring-web對Servlet3.0的應用
先上一張繼承體系圖,下面圍繞這張圖片展開
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList();
Iterator var4;
if (webAppInitializerClasses != null) {
var4 = webAppInitializerClasses.iterator();
while(var4.hasNext()) {
Class<?> waiClass = (Class)var4.next();
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
} catch (Throwable var7) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
}
}
}
}
...
}
可以看到,Spring應用一啟動就會載入WebApplicationInitializer
介面下的所有元件,並且,只要這些元件不是介面,不是抽象類,Spring就為它們建立例項
更進一步看一下上下文中WebApplicationInitializer
介面的實現類
AbstractContextLoaderInitializer
看他對onstart()
方法的重寫, 主要乾了什麼呢? 註冊了一個上下文的監聽器(藉助這個監聽器讀取SpringMvc的配置檔案),初始化應用的上下文
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
protected final Log logger = LogFactory.getLog(this.getClass());
public AbstractContextLoaderInitializer() {
}
public void onStartup(ServletContext servletContext) throws ServletException {
this.registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
WebApplicationContext rootAppContext = this.createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(this.getRootApplicationContextInitializers());
servletContext.addListener(listener);
} else {
this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
}
}
AbstractDispatcherServletInitializer
見名知意,他是DispatcherServlet
的初始化器,他主要做了什麼事呢?
- 上面看了,它的父類初始化上下文,於是它呼叫父類的構造,往上傳遞web環境的上下文
- 緊接著新增
DispatcherServlet
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
public static final String DEFAULT_SERVLET_NAME = "dispatcher";
public AbstractDispatcherServletInitializer() {
}
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
this.registerDispatcherServlet(servletContext);
}
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = this.getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
// 可以看一下,它建立的是web的容器
WebApplicationContext servletAppContext = this.createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
// 建立負責排程的 DispatcherServlet
FrameworkServlet dispatcherServlet = this.createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(this.getServletApplicationContextInitializers());
// 新增Servlet
Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. Check if there is another servlet registered under the same name.");
} else {
// 新增servlet的mapping資訊
registration.setLoadOnStartup(1);
registration.addMapping(this.getServletMappings());
registration.setAsyncSupported(this.isAsyncSupported());
Filter[] filters = this.getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
Filter[] var7 = filters;
int var8 = filters.length;
for(int var9 = 0; var9 < var8; ++var9) {
Filter filter = var7[var9];
this.registerServletFilter(servletContext, filter);
}
}
this.customizeRegistration(registration);
}
...
}
AbstractAnnotationConfigDispatcherServletInitializer
createRootApplicationContext
重寫了父類的建立上下文的方法,我覺得這算是一個高潮吧, 因為啥呢,AnnotationConfigWebApplicationContext是SpringMvc使用的應用的上下文,怎麼建立的原始碼在下面,其實我有在Spring原始碼閱讀中寫過這個方面的筆記,下面僅僅是將配置類傳遞給Spring的bean工廠,並沒有對配置類進行其他方面的解析,或者是掃描包啥的
createServletApplicationContext()
重寫了它父類的建立serlvet上下文的方法,
有個點,大家有沒有發現,SpringMvc的上下文和Servlet的上下文是同一個物件,都是AnnotationConfigWebApplicationContext
,不同點就是添加了if-else分支判斷,防止重複建立
public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer {
public AbstractAnnotationConfigDispatcherServletInitializer() {
}
@Nullable
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = this.getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext context = new 有沒有大神瞭解這個情況, SpringMvc的應用上下文和Servlet應用上下文竟然是同一個();
context.register(configClasses);
return context;
} else {
return null;
}
}
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = this.getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
context.register(configClasses);
}
return context;
}
對比官網推薦的啟動案例:
下面的是Spring官網推薦是通過註解的配置方法,仔細看看,其實和上面的Spring-Web模組的做法是一樣的
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletCxt) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
基於Servlet3.0全註解方式整合SpringMvc
經過前面的分析,第一個結論是:伺服器一啟動,經過自上而下的繼承體系AbstractAnnotationConfigDispacherServletInitializer
會被載入執行,所以,當我們想使用全註解方式完成繼承SpringMVC時,繼承AbstractAnnotationConfigDispacherServletInitializer
就好
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// 獲取Spring容器的配置類
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}
// 獲取web容器的配置類
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
/**
* 獲取DispatcherSerlvet的對映資訊
* / : 表示攔截所有請求(包含靜態資源 XXX.js XXX.jpg) 但是不包含 XXX.jsp
* /* : 表示攔截所有請求(包含靜態資源 XXX.js XXX.jpg) 包含 XXX.jsp
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
下面的兩個配置類, 按照他的意思,分成了兩個配置類,一個是web上下文中的配置類,另一個是Spring原生環境的配置類
但是吧,看看下面的配置真的是特別麻煩,一個得排除@Controller
,完事另一個得包含@Controller
,其實Spring原生上下文都認識這些通用註解,倒不如直接就一個配置類,還省事
@ComponentScan(value = "com.changwu",includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
public class WebConfig {
}
@ComponentScan(value = "com.changwu",excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
})
public class RootConfig {
}
Servlet3.0 非同步請求處理器
早前的前後端請求響應的模型是怎樣的呢? 使用者傳送的請求經過網路傳輸到Tomcat,Tomcat中存在一個執行緒池,這時Tomcat會從執行緒池中取出一條執行緒專門處理這個請求,一直到處理完畢,給了使用者響應之後才將此執行緒回收到執行緒池,但是執行緒池中的執行緒終究是有限的,一旦同時好幾百的連線進來,Tomcat的壓力驟然上升,難免會出現阻塞的現象
Serlet3.0引入的非同步處理,讓主執行緒擁有非阻塞的特性,這樣tomcat接收請求訪問的吞吐量就會增加
示例:
// 啟動非同步
@WebServlet(value = "/async",asyncSupported = true)
public class MyAsyncServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 開啟非同步處理
AsyncContext asyncContext = req.startAsync();
asyncContext.start(new Runnable() {
@Override
public void run() {
// do other things
// 結束
asyncContext.complete();
// 響應
ServletResponse response = asyncContext.getResponse();
try {
response.getWriter().write("123");
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
SpringMvc的非同步任務
針對Servlet3.0的非同步特性,SpringMvc相關的支援是提供了非同步執行緒池
DeferredResult
我覺得這個非同步的實現方式簡直是無與倫比!!!無法言表!!!
@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
DeferredResult<String> deferredResult = new DeferredResult<String>();
// Save the deferredResult somewhere..
return deferredResult;
}
// From some other thread...
deferredResult.setResult(result);
Callable
- 方法的最後將
Callable
返回 call()
方法中做寫需要非同步處理器的邏輯
執行流程:
- SpringMvc會將這個Callable放到一個叫TaskExcutor中執行
- DispatcherSerlvet和所有的Filter退出web容器,但是Response保持開啟狀態
- SpringMvc會將Callable的返回結果重寫派發給setlvet恢復之前的處理
- 根據Callable返回的結果SpringMvc進行渲染
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
public String call() throws Exception {
// ...
return "someView";
}
};
}
Request
請求方式與很多種,get post head trace options 還有put delete
其中get post put delete 是RestfulAPI中推薦,也是現在盛行使用的四種請求方法
get: 最為簡單的請求方式,一般資料新增在url後面一般這樣寫username?張三&password?123123
, 由於URL的長度有限制,故能傳輸的資料一般在1M左右, 而且資料明文傳輸,像上面那樣,村咋存在安全隱患
post的資料存放在請求體中,一般沒有大小限制,相對於get而言,post的安全性更好一點
繼承圖如下:
上圖中我們最常使用的HttpServletRequest
竟然是個介面,當時一開始學web的時候確實覺得很奇怪,但是現在想想其實也還好了,因為Tomcat提供了實現類org.apache.catalina.connector.RequestFacade
獲取請求行資料-GET
點選檢視文件
請求行: GET /test/app?name=zhangsan http/1.1
- 獲取請求方法: GET
String getMethod()
- 獲取虛擬路徑(專案路徑): /test
String getContextPath()
- 獲取Servlet路徑; /app
String getServletPath()
- 獲取get請求的請求引數: name=zhangsan
String getQueryString()
- 獲取URI : /test/app
String getRequestURI()
- 獲取URL : http:localhost/test/app
String getRequestURL()
- 獲取協議版本
String getProtocol()
- 獲取遠端主機地址
String getRemoteAddr()
獲取請求頭資料
- 根據名稱獲取請求頭
String getHeader(String name);
- 獲取所有的請求頭
Enumertion<String> getHeaderNames(); 獲取所有請求頭的名稱
獲取請求體資料
僅僅有post方式,才會有請求體:使用它分成兩步:
- 從request中獲取流物件
BufferReader getReader(); // 獲取字元輸入流
ServletInputStream getInputStrream(); // 獲取位元組輸入流
- 從流物件中獲取到需要的資料
通用的方法
- 根據引數名獲取引數值
String getParamter(String name); 根據引數名獲取引數值
- 根據引數名,獲取引數值陣列
String [] getParameterValues(String name)
- 獲取所有請求的引數名稱
Enumeration<String> getParamerterNames()
- 獲取所有引數鍵值對形式的map集合
Map<String,String[]) getParamerterMap()
有了通用的方法特性之後,我們就不跟針對doGet,doPost兩種方式寫兩份程式碼, 只要在doGet()或者doPost()中呼叫另外一個就ok,因為方法針對兩者通用
解決中文亂碼
首先: Tomcat8自身解決了中文亂碼問題
Post方式提交資料依然存在亂碼問題,像下面這樣先設定編碼再使用 req
request.setCharacterEncoding("utf-8")
request的請求轉發
當用戶的某一個請求需要通過多個Servlet協作完成時,請求在Servlet之間跳轉,這種資源跳轉的方式稱為請求轉發
使用方法:通過當前的request獲取出RequestDispacher物件,通過這個物件的forward(req,res)進行轉發的動作
RequestDispatcher getRequestDispacher(String path) // path是另一個Servlet的url-pattern
forward(currentReq,currentRes);
特點:
- 瀏覽器位址列路徑沒有發生變化
- 伺服器內部官網的資源跳轉,不能跳往別的站點
- 一次轉發,對瀏覽器來說,僅僅傳送了一次請求
因為我們沒讓瀏覽器傳送兩次請求,在服務端完成了請求轉發,所以上面的path僅僅是servlet-url-pattern,而不包含專案路徑
域物件-共享資料
域物件: request域, 既然是域物件,他就有自己的作用範圍,request的作用範圍是什麼呢? 就是一次請求,每次請求都是一個域, 換句話說,如果說客戶端的一次請求經過了AServlet,然後AServlet將請求轉發到了BServlet,name AServlet BServlet就在一個域中,也就可以共享彼此的資料
怎麼玩?
在AServlet
setAttribute(String name,Object obj);
請求轉發到BServlet
在BServlet
getAttribute(String name);
移除
removeAttribute(String name);
Reponse
響應資訊格式如下:
HTTP/1.1 200 OK //響應行
---------------------------------------
響應頭
// 伺服器的型別
Server: server-name
//服務端告訴客戶端,自己推送給它的資料的編碼格式(瀏覽器根據指定的型別進行解碼)
Content-Type: text/html;charset=utf-8
// 響應內容的長度
Content-Length: XXX
// 響應日期
Date: XXX
//伺服器告訴瀏覽器用什麼格式開啟響應體資料, 預設是in-line 表示在當前頁面中開啟, attachment(檔案下載)
Content-disposition: in-line
----------------------------------------
(響應空行)
----------------------------------------
XXX // 響應體
常用方法
Response物件就是用來設定響應訊息的物件
- 設定響應行
// 格式: HTTP/1.1 200 ok
setStatus(int status);
- 設定響應頭
setHeader(String name, String value);
- 設定響應體
// 0 在往客戶端寫中文前先設定編碼
response.setContentType("text/html;charset=utf-8");
// 1. 獲取到輸出流(位元組流/字元流)
// 2. 往客戶端寫
response.getWriter().write("XXXXXXX");
- 重定向
// 實現,從 AServlet 重定向到BServlet
// 兩步實現: 重定向
// 設定狀態碼 / 響應頭
reponse.setStatus(302);
response.setHeader("location","/專案路徑/serlet-url-pattern");
// 單行代理實現重定向
response.sendRedirect("/專案路徑/serlet-url-pattern");
特點:
- 重定向: 瀏覽器位址列路徑改變了
- 重定向: 可以請求其他伺服器
- 重定向: 實際上發起了兩次請求 (不能使用request域共享資料)
因為我們讓瀏覽器傳送了兩次請求, 因此重定向的路徑中包含 專案路徑
Cookie
常用api
客戶端會話技術,將資料儲存在瀏覽器本地, 下一次訪問時會攜帶著cookie
- 建立cookie,繫結資料
new Cookie(String name,String value)
- 傳送cookie
response.addCookie(Cookie cookie);
- 獲取解析cookie
Cookie [] cookies = request.getCookies();
- 一次傳送多個cookie
Cookie c1 = new Cookie(String name,String value)
Cookie c2 =new Cookie(String name,String value)
reponse.addCookie(c1);
reponse.addCookie(c2);
- cookie的生命週期
預設cookie儲存在瀏覽器記憶體中,一旦瀏覽器關閉,cookie銷燬
設定cookie的生命週期
setMaxAge(int seconds);
seconds 為存活時間,
正數: 表示以檔案的形式進行持久化,預設單位 s
負數: 表示僅僅存在於記憶體中
零: 表示讓瀏覽器刪除cookie
- cookie儲存中文
tomcat8之前,cookie不支援中文,Tomcat8之後cookie支援中文
tomcat8之前需要進行轉碼,一般採用URL編碼
- cookie獲取的範圍
預設情況下:在一個tomcat中的部署的多個web專案之間cookie是不能共享的
但是可以通過下面的方法設定更大路徑,實現想要的效果
setPath(String path); // 預設是當前的虛擬目錄
// 可以設定成下面這樣
setPath("/"); // 預設是當前的虛擬目錄
跨域tomcat之間cookie共享使用-- 根據域名劃分
setDomain(String path); // 只要一級域名相同,則多個伺服器之間共享 cookie
特點:
- 儲存在瀏覽器,不安全
- 瀏覽器對單個cookie大小(一般都在4kb),對同一個域名下的cookie總數也有限制(一般20個以內)
小場景:
伺服器,瀏覽器協作的流程: 比如登入: 使用者通過瀏覽器往服務端傳送登入請求,服務端驗證使用者名稱密碼,通過後往客戶端的傳送cookie, 其實是設定了響應頭set-cookie=XXX, 瀏覽器碰到這種響應頭,就把這個響應體快取在本地,再次請求這個網站時,會自動攜帶這個請求頭cookie=XXX , 後端通過解析使用者傳送過來的cookie,可以判斷當前請求的使用者是否是合法的
Session
服務端會話技術,在一次會話中多次請求共享資料,將資料儲存在服務端的物件--HttpSession
session也是域物件
常用API
- 獲取session
HttpSession session = request.getSession();
- 使用session
setAttribute(String name,Object obj);
getAttribute(String name);
romoveAttribute(String name);
如何確保多次會話中,多次獲取到的session是同一個
- 使用者通過瀏覽器向服務端傳送請求
- 服務端驗證使用者的資訊,通過驗證後,如果沒有當前的session就為使用者建立session(每個session都有唯一的id),建立cookie,給客戶端返回相應
set-coolkie:JSESSIONID=shdfiuhduifha
- 使用者再次訪問服務端,瀏覽器會自動攜帶上請求頭
cookie:JSESSIONID=shdfiuhduifha
- 伺服器解析cookie攜帶的
JSESSIONID=shdfiuhduifha
便可以找出唯一的session
細節
- 客戶端關閉,服務端不關閉,兩次獲取到的session一樣嗎?
- session是依賴cookie的,客戶端關閉,cookie被幹掉了,也就是說本次會話也就結束了,後端的session將被幹掉
- 但是如果我們傳送給瀏覽器一個可以存活很長時間的cookie,再次開啟瀏覽器訪問後端,session還是同一個
- 客戶端不關閉,服務端關閉,兩次獲取到的session一樣嗎?
- 伺服器都沒了,session肯定被幹掉了,session肯定不一樣
- 補救:鈍化 tomcat在伺服器正常關閉前,將session序列化持久化到磁碟上
補救:活化 tomcat在伺服器啟動時,將session重新載入進記憶體
idea中可以成功完成鈍化,但是不能完成活化
- session失效時間
- 伺服器關閉
- 使用api,自動關閉
invalidate()
- session預設的失效時間30分鐘
過期時間可以在Tomcat中的配置檔案目錄下的web.xml中配置
<session-config>
<session-timeout>30</session-timeout>
</session-config>
特點:
- session用於儲存一次會話的多次請求資料,儲存在服務端
- session可以儲存任意型別的資料沒有大小限制