1. 如何為thinkjs靜態資源配置nginx反向代理
最近,用thinkjs寫了個小網站,把它放到了VPS上。
為了讓網站靜態資源載入更快,所以需要在VPS的nginx上配置一個反向代理來直接讓Nginx處理靜態資源,動態類的請求通過反向代理讓Node.js來處理:
?
server {
listen 80;
server_name abc.com www.abc.com;
index index.js index.html index.htm;
if
($host !=
'abc.com'
) {
rewrite ^/(.*)$ http:
//abc.com/$1 permanent;
}
root /www/web/myproject/public_html/www;
if
( -f $request_filename/index.html ){
rewrite (.*) $1/index.html
break
;
}
if
( !-f $request_filename ){
rewrite (.*) /index.js;
}
location = /index.js {
#proxy_http_version 1.1;
proxy_set_header Connection
""
;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy
true
;
proxy_pass http:
//127.0.0.1:8363$request_uri;
proxy_redirect off;
}
location ~ .*\.(js|css|gif|jpg|jpeg|png|bmp|swf|ico|svg|cur|ttf|woff)$ {
expires 1000d;
}
}
2. 新手問一個nginx動靜分離的問題,nginx相較於tomcat更適合處理靜態資源,他的原理是需要
需要將tomcat的靜態資源部署在nginx上
3. flask 藍圖nginx部署後怎麼訪問靜態資源
能共存,不需要聯系的話兩個用不一樣的埠就行 比如nginx用80,apache用8080 如果都想用8080訪問,那麼可以把nginx作為apache的前端 兩種方法 1、直接用nginx反代的方式 2、靜態交給nginx處理,PHP交給apache處理
4. 如何自動復制資源到nginx靜態資源伺服器
傳統的web項目,一般都將靜態資源存放在 webroot的目錄下,這樣做很方便獲取靜態資源,但是如果說web項目很大,用戶很多,靜態資源也很多時,伺服器的性能 或許就會很低下了。這種情況下一般都會需要一個靜態資源的伺服器。
搭建nginx伺服器首先得安裝nginx服務,關於nginx服務的安裝可以參考我的另一篇博客《nginx服務安裝》這里直接介紹靜態伺服器的配置
進入nginx安裝目錄的conf目錄下,修改nginx.conf文件,在一個server{}中添加 一個location 部分配置代碼如下
root@ubuntu:/usr/local/nginx/conf# vi nginx.conf
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
location /image/ {
root /usr/local/myImage/;
autoindex on;
}
}
從上面的配置可以看出來 埠為80,server_name為localhost(寫ip地址也可以)
location /image/ {
root /usr/local/myImage/;
autoindex on;
}
這個配置表示輸入 localhost:80/image/ 時會訪問本機的/usr/local/myImage/image/ 目錄。所以要新建/usr/local/myImage/image/ 目錄,同時還要在nginx安裝目錄的html目錄中新建一個 與 location中 image同名的image目錄,雖然該目錄裡面什麼也沒有,在/usr/local/my Image/image/ 中我們放一張圖片1.jpg上去,重啟nginx服務,就可以通過 localhost:80/image/1.jpg訪問了
root@ubuntu:/usr/local/nginx/html# mkdir image
root@ubuntu:/usr/local/nginx/html# mkdir /usr/local/myImage/image
#放一張照片上去#
root@ubuntu:/usr/local/nginx/html# cd /usr/local/myImage/image
root@ubuntu:/usr/local/myImage/image# ls
1.jpg
root@ubuntu:/usr/local/myImage/image#
重啟 nginx
root@ubuntu:/usr/local/nginx/sbin# ./nginx -s reload
root@ubuntu:/usr/local/nginx/sbin#
打開瀏覽器 輸入 server_name:80/image/1.jpg 就可以訪問該靜態圖片
5. 如何判斷 ngnix 走了 靜態文件路徑
Nginx以其消耗資源少,承受並發量大,配置文件簡潔等特點,深受廣大sa們的喜歡,但是網上傳播的nginx 配置並沒有對做過多的優化。那麼接下來,我就從某大型媒體網站的實際運維nginx優化角度,來給大家講解一下nginx主要優化的那些方面。
nginx的啟動命令是:/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf<br>-c制定配置文件的路徑,不加-nginx會自動載入默認路徑的配置文件。<br>
停止操作
停止操作是通過向nginx進程發送信號(什麼是信號請參閱linux文 章)來進行的
步驟1:查詢nginx主進程號
ps -ef | grep nginx
在進程列表裡 面找master進程,它的編號就是主進程號了。
步驟2:發送信號
從容停止Nginx:
kill -QUIT 主進程號
快速停止Nginx:
kill -TERM 主進程號
強制停止Nginx:
pkill -9 nginx
另外, 若在nginx.conf配置了pid文件存放路徑則該文件存放的就是Nginx主進程號,如果沒指定則放在nginx的logs目錄下。有了pid文 件,我們就不用先查詢Nginx的主進程號,而直接向Nginx發送信號了,命令如下:
kill -信號類型 '/usr/nginx/logs/nginx.pid'
平滑重啟
如果更改了配置就要重啟Nginx,要先關閉Nginx再打開?不是的,可以向Nginx 發送信號,平滑重啟。
平滑重啟命令:
kill -HUP 住進稱號或進程號文件路徑
或者使用
/usr/nginx/sbin/nginx -s reload
注意,修改了配置文件後最好先檢查一下修改過的配置文件是否正 確,以免重啟後Nginx出現錯誤影響伺服器穩定運行。判斷Nginx配置是否正確命令如下:
nginx -t -c /usr/nginx/conf/nginx.conf
或者
/usr/nginx/sbin/nginx -t
平滑升級
如果伺服器正在運行的Nginx要進行升級、添加或刪除模塊時,我們需 要停掉伺服器並做相應修改,這樣伺服器就要在一段時間內停止服務,Nginx可以在不停機的情況下進行各種升級動作而不影響伺服器運行。
步驟1:
如 果升級Nginx程序,先用新程序替換舊程序文件,編譯安裝的話新程序直接編譯到Nginx安裝目錄中。
步 驟2:執行命令
kill -USR2 舊版程序的主進程號或進程文件名
此時舊的Nginx主進程將會把自己的進程文件改名為.oldbin,然後執行新版 Nginx。新舊Nginx會同市運行,共同處理請求。
這時要逐步停止舊版 Nginx,輸入命令:
kill -WINCH 舊版主進程號
慢慢舊的工作進程就都會隨著任務執行完畢而退出,新版的Nginx的工作進程會逐漸取代舊版 工作進程。
此 時,我們可以決定使用新版還是恢復到舊版。
不重載配置啟動新/舊工作進程
kill -HUP 舊/新版主進程號
從容關閉舊/新進程
kill -QUIT 舊/新主進程號
如果此時報錯,提示還有進程沒有結束就用下面命令先關閉舊/新工作進程,再關閉主進程號:
kill -TERM 舊/新工作進程號
這樣下來,如果要恢復到舊版本,只需要上面的幾個步 驟都是操作新版主進程號,如果要用新版本就上面的幾個步驟都操作舊版主進程號就行了。
上面就是Nginx的一些基本的操作,希望以後Nginx能有更好的方法來處理這些操作, 最好是Nginx的命令而不是向Nginx進程發送系統信號。
一、編譯方面優化
1、首先就要從configure 參數分析,根據網上最常用的configure 參數來說,大都是:
# ./configure \
--prefix=/usr/local/nginx \
--user=www \
--group=www \
--with-http_stub_status_mole \
--with-http_ssl_mole
應該說這個參數是通用的,適用於各種環境的需要,比如php環境、純靜態文件環境、代理環境等等。編譯nginx程序文件大約有2M大小,跟全面優化的500多K,相差了不少。
下面我們修改一下參數,減少不必要的功能。
純靜態文件環境參數
# ./configure \
--prefix=/usr/local/nginx \
--user=www \
--group=www \
--with-http_stub_status_mole \
--without-http_fastcgi_mole \
--without-http_proxy_mole \
--without-http_upstream_ip_hash_mole \
--without-http_autoindex_mole \
--without-http_ssi_mole \
--without-http_proxy_mole \
--without-mail_pop3_mole \
--without-mail_imap_mole \
--without-mail_smtp_mole \
--without-http_uwsgi_mole \
--without-http_scgi_mole \
--without-http_memcached_mole
去掉了在mail模塊fastcgi模塊 代理模塊 ip_hash模塊等,在純靜態文件用不到的模塊,現在看看nginx程序文件是不是少了一些。Php環境的話,只需要去掉 --with-http_fastcgi_mole 重新編譯即可。代理環境的話,只需要去掉--with_proxy_mole重新編譯即可。
2、去掉nginx 默認的debug跟蹤設置。這一步需要修改nginx 源碼。
# cd nginx-1.0.x
# vim auto/cc/gcc
第175行:前面加#注釋掉改行。
#CFLAGS="$CFLAGS -g"
這樣的話,編譯的參數,就會減少到500多K的標准,這樣在大並發量的條件下,性能提升明顯。
二、利用google-perftools來優化高並發條件下的nginx
在32位系統下,可以直接安裝google-peftools,64位條件下,需要先安裝libunwind庫。然後再nginx configure 參數增加--with-google_perftools_mole 重新編譯安裝nginx。
這里以64位環境為准
1. 安裝libunwind庫
# wget http://download.savannah.gnu.org/releases/libunwind/libunwind-0.99.tar.gz
# tar zxvf libunwind-0.99.tar.gz
# cd libunwind-0.99/
# CFLAGS=-fPIC ./configure –prefix=/usr
# make CFLAGS=-fPIC
# make CFLAGS=-fPIC install
2. 安裝google-perftools
# wget http://google-perftools.googlecode.com/files/google-perftools-1.7.tar.gz
# tar xzvf google-perftools-1.7.tar.gz
# cd google-perftools-1.7
然後開始配置:
# ./configure --prefix=/usr --enable-frame-pointers (32位可以不添加--enable-frame-pointers)
# make --j4 && make installnginx
configure 參數加上--with-google-perftools 重新編譯nginx
# ./configure \
--prefix=/usr/local/nginx \
--user=www \
--group=www \
--with-http_stub_status_mole \
--without-http_fastcgi_mole \
--without-http_proxy_mole \
--without-http_upstream_ip_hash_mole \
--without-http_autoindex_mole \
--without-http_ssi_mole \
--without-http_proxy_mole \
--without-mail_pop3_mole \
--without-mail_imap_mole \
--without-mail_smtp_mole \
--without-http_uwsgi_mole \
--without-http_scgi_mole \
--without-http_memcached_mole \
--with-google_perftools_mole
# make && make install
3、在nginx.conf 的pid部分下,增加
google_perftools_profiles /data0/google_cache;
# service nginx restart 重啟即可生效。
4. 安裝
yum -y install gcc gcc-c++ autoconf automake
yum -y install zlib zlib-devel openssl openssl--devel pcre pcre-devel
wget http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm
rpm -ivh nginx-release-centos-6-0.el6.ngx.noarch.rpm
yum install nginx
三、nginx 工作進程優化
nginx指令中的優化(配置文件)
worker_processes 8;
nginx進程數,建議按照cpu數目來指定,一般為它的倍數。
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;
為每個進程分配cpu,上例中將8個進程分配到8個cpu,當然可以寫多個,或者將一個進程分配到多個cpu。
worker_rlimit_nofile 102400;
這個指令是指當一個nginx進程打開的最多文件描述符數目,理論值應該是最多打開文件數(ulimit -n)與nginx進程數相除,但是nginx分配請求並不是那麼均勻,所以最好與ulimit -n的值保持一致。
use epoll;
使用epoll的I/O模型,這個不用說了吧。
worker_connections 102400;
每個進程允許的最多連接數,理論上每台nginx伺服器的最大連接數為worker_processes*worker_connections。
keepalive_timeout 60;
keepalive超時時間。
client_header_buffer_size 4k;
客戶端請求頭部的緩沖區大小,這個可以根據你的系統分頁大小來設置,一般一個請求的頭部大小不會超過1k,不過由於一般系統分頁都要大於1k,所以這里設置為分頁大小。分頁大小可以用命令getconf PAGESIZE取得。
open_file_cache max=102400 inactive=20s;
這個將為打開文件指定緩存,默認是沒有啟用的,max指定緩存數量,建議和打開文件數一致,inactive是指經過多長時間文件沒被請求後刪除緩存。
open_file_cache_valid 30s;
這個是指多長時間檢查一次緩存的有效信息。
open_file_cache_min_uses 1;
open_file_cache指令中的inactive參數時間內文件的最少使用次數,如果超過這個數字,文件描述符一直是在緩存中打開的,如上例,如果有一個文件在inactive時間內一次沒被使用,它將被移除。
內核參數的優化
net.ipv4.tcp_max_tw_buckets = 6000
timewait的數量,默認是180000。
net.ipv4.ip_local_port_range = 1024 65000
允許系統打開的埠范圍。
net.ipv4.tcp_tw_recycle = 1
啟用timewait快速回收。
net.ipv4.tcp_tw_reuse = 1
開啟重用。允許將TIME-WAIT sockets重新用於新的TCP連接。
net.ipv4.tcp_syncookies = 1
開啟SYN Cookies,當出現SYN等待隊列溢出時,啟用cookies來處理。
net.core.somaxconn = 262144
web應用中listen函數的backlog默認會給我們內核參數的net.core.somaxconn限制到128,而nginx定義的NGX_LISTEN_BACKLOG默認為511,所以有必要調整這個值。
net.core.netdev_max_backlog = 262144
每個網路介面接收數據包的速率比內核處理這些包的速率快時,允許送到隊列的數據包的最大數目。
net.ipv4.tcp_max_orphans = 262144
系統中最多有多少個TCP套接字不被關聯到任何一個用戶文件句柄上。如果超過這個數字,孤兒連接將即刻被復位並列印出警告信息。這個限制僅僅是為了防止簡單的DoS攻擊,不能過分依靠它或者人為地減小這個值,更應該增加這個值(如果增加了內存之後)。
net.ipv4.tcp_max_syn_backlog = 262144
記錄的那些尚未收到客戶端確認信息的連接請求的最大值。對於有128M內存的系統而言,預設值是1024,小內存的系統則是128。
net.ipv4.tcp_timestamps = 0
時間戳可以避免序列號的卷繞。一個1Gbps的鏈路肯定會遇到以前用過的序列號。時間戳能夠讓內核接受這種「異常」的數據包。這里需要將其關掉。
net.ipv4.tcp_synack_retries = 1
為了打開對端的連接,內核需要發送一個SYN並附帶一個回應前面一個SYN的ACK。也就是所謂三次握手中的第二次握手。這個設置決定了內核放棄連接之前發送SYN+ACK包的數量。
net.ipv4.tcp_syn_retries = 1
在內核放棄建立連接之前發送SYN包的數量。
net.ipv4.tcp_fin_timeout = 1
如果套接字由本端要求關閉,這個參數決定了它保持在FIN-WAIT-2狀態的時間。對端可以出錯並永遠不關閉連接,甚至意外當機。預設值是60秒。2.2 內核的通常值是180秒,你可以按這個設置,但要記住的是,即使你的機器是一個輕載的WEB伺服器,也有因為大量的死套接字而內存溢出的風險,FIN- WAIT-2的危險性比FIN-WAIT-1要小,因為它最多隻能吃掉1.5K內存,但是它們的生存期長些。
net.ipv4.tcp_keepalive_time = 30
當keepalive起用的時候,TCP發送keepalive消息的頻度。預設是2小時。
一個完整的內核優化配置
net.ipv4.ip_forward = 0
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.default.accept_source_route = 0
kernel.sysrq = 0
kernel.core_uses_pid = 1
net.ipv4.tcp_syncookies = 1
kernel.msgmnb = 65536
kernel.msgmax = 65536
kernel.shmmax = 68719476736
kernel.shmall = 4294967296
net.ipv4.tcp_max_tw_buckets = 6000
net.ipv4.tcp_sack = 1
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_rmem = 4096 87380 4194304
net.ipv4.tcp_wmem = 4096 16384 4194304
net.core.wmem_default = 8388608
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.netdev_max_backlog = 262144
net.core.somaxconn = 262144
net.ipv4.tcp_max_orphans = 3276800
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_synack_retries = 1
net.ipv4.tcp_syn_retries = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_mem = 94500000 915000000 927000000
net.ipv4.tcp_fin_timeout = 1
net.ipv4.tcp_keepalive_time = 30
net.ipv4.ip_local_port_range = 1024 65000
一個簡單的nginx優化配置文件
6. nginx+tomcat 動靜態資源怎麼部署
http {
include mime.types;
default_type application/octet-stream;
charset utf-8;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
# 啟用內核復制模式,應該保持開啟達到最快IO效率
sendfile on;
#tcp_nopush on;
7. Spring Boot之如何配置靜態資源的地址與訪
靜態資源,例如HTML文件、JS文件,設計到的Spring Boot配置有兩項,一是「spring.mvc.static-path-pattern」,一是「spring.resources.static-locations」,很多人都難以分辨它們之間的差異,所以經常出現的結果就是404錯誤,無法找到靜態資源。
1. 「spring.mvc.static-path-pattern」
spring.mvc.static-path-pattern代表的含義是我們應該以什麼樣的路徑來訪問靜態資源,換句話說,只有靜態資源滿足什麼樣的匹配條件,Spring Boot才會處理靜態資源請求,以官方配置為例:
# 這表示只有靜態資源的訪問路徑為/resources/**時,才會處理請求
spring.mvc.static-path-pattern=/resources/**,
假定採用默認的配置埠,那麼只有請求地址類似於「http://localhost:8080/resources/jquery.js」時,Spring Boot才會處理此請求,處理方式是將根據模式匹配後的文件名查找本地文件,那麼應該在什麼地方查找本地文件呢?這就是「spring.resources.static-locations」的作用了。
2. 「spring.resources.static-locations」
「spring.resources.static-locations」用於告訴Spring Boot應該在何處查找靜態資源文件,這是一個列表性的配置,查找文件時會依賴於配置的先後順序依次進行,默認的官方配置如下:
spring.resources.static-locations=classpath:/static,classpath:/public,classpath:/resources,classpath:/META-INF/resources
繼續以上面的請求地址為例,「http://localhost:8080/resources/jquery.js」就會在上述的四個路徑中依次查找是否存在「jquery.js」文件,如果找到了,則返回此文件,否則返回404錯誤。
3. 靜態資源的Bean配置
從上面可以看出,「spring.mvc.static-path-pattern」與「spring.resources.static-locations」組合起來演繹了nginx的映射配置,如果熟悉Spring MVC,那麼理解起來更加簡單,它們的作用可以用Bean配置表示,如下:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/public-resources/")
.setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic());
}
}
或者等同與以下的XML。
<mvc:resources mapping="/resources/**" location="/public-resources/">
<mvc:cache-control max-age="3600" cache-public="true"/>
</mvc:resources>
結論
「spring.mvc.static-path-pattern」用於闡述HTTP請求地址,而「spring.resources.static-locations」則用於描述靜態資源的存放位置。
8. nginx訪問靜態資源文件,未指定靜態資源的名稱怎麼辦
今天在搭建nginx環境時出現一個奇怪問題,配置的靜態資源目錄下面文件無法訪問,瀏覽器訪問出現403 forbidden,環境是centos7 + nginx 1.6。nginx.conf中http配置如下:
[plain] view plain
……
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream tomcat_server {
server 10.10.100.52:8080;
}
server {
listen 80;
charset utf-8;
server_name localhost;
location /fcm/ {
index index.html index.htm;
proxy_pass http://tomcat_server;
proxy_set_header X-Real-IP $remote_addr;
client_max_body_size 100m;
}
location /static/ {
root /home/www/static;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
9. 如何在nginx中緩存靜態文件
首先確定配置文件httpd.conf中確已經載入mod_headers模塊。LoadMoleheaders_molemoles/mod_headers.so我們可以根據文件類型來讓瀏覽器每次都從伺服器讀取,這里測試用css、js、swf、php、html、htm這幾種文件。HeadersetCache-Control"private,no-cache,no-store,proxy-revalidate,no-transform"HeadersetPragma"no-cache"
10. 緩存靜態資源,不知怎麼解決
之前看過apach及nginx對於靜態資源(含js,圖片,css等)部分的緩存,用於加速並減輕後台實際web伺服器的壓力。
靜態資源緩存是WEB伺服器優化的一種手段,基本原理如下:
1.客戶端瀏覽器請求伺服器一個服務(該服務含有圖片,js等靜態資源),通常會對於每一個網頁中的獨立圖片或js文件發送一個http請求
2.WEB伺服器對於每個資源HTTP請求進行解析,並生成一個資源修改時間的唯一值(可以是etag或last_modified參數),放入伺服器端map,key為資源url,value為資源修改時間。最後將此資源修改時間的唯一值包含在http頭上返回,因為是首次請求,所以會將所有內容放在http body中一並返回給客戶瀏覽器端
3.客戶瀏覽器接收服伺服器響應,並將伺服器返回的資源修改時間作為key放入瀏覽器客戶端,value為http body中的實際資源內容
4.客戶瀏覽器再次請求靜態資源時,會將資源修改時間一並發送給伺服器
5.服務端會從最新的map中取出該資源url對應的修改時間,如果值晚於客戶端請求的資源修改時間,這時會返回最新的已經修改過的資源給客戶端。否則返回304 not modifed
這里記錄資源修改時間的方式有etag及last_modified。最先有的是last_modified,它的工作方式就是上述介紹的,但缺點是只能精確到秒級別。也就是說當你在一秒中修改資源兩次,而客戶端拿到的是第一次修改,那之後就算客戶端第二次再次請求也不會拿到最新的資源。
而etag的出現正是為了解決last_modified的秒級問題,於http 1.1被提出。
今天測試了下,在沒有nginx等前端反向代理伺服器時,tomcat竟然默認對靜態資源做了緩存。
tomcat默認運用etag及last_modifed。etag與if_no_match(客戶端瀏覽器上傳時在http head中應該放的屬性名)一起使用,last_modified與If-Modified-Since一起使用。
客戶端首次請求時,得到請求響應數據如下:
GET http://localhost:8080/webTest/jsp/index.jsp [HTTP/1.1 200 OK 1ms]
GET http://localhost:8080/webTest/js/hello.js [HTTP/1.1 200 OK 1ms]
GET http://localhost:8080/webTest/img/a.jpg [HTTP/1.1 200 OK 2ms]
我們看一下Hello.js這個請求響應具體信息:
server Apache-Coyote/1.1 (表明伺服器是tomcat)
Last-Modified: Sun, 11 May 2014 10:54:33 GMT
Etag: W/"175-1399805673000"
Date: Sun, 11 May 2014 10:59:23 GMT
Content-Type: application/javascript;charset=UTF-8
Content-Length: 175
Accept-Ranges: bytes
從上面可以看到tomcat即返回了last_modified也返回了etag。
客戶端再次請求時,請求數據如下:
If-None-Match: W/"175-1399805673000"
If-Modified-Since: Sun, 11 May 2014 10:54:33 GMT
響應如下:
GET http://localhost:8080/webTest/jsp/index.jsp [HTTP/1.1 200 OK 1ms]
GET http://localhost:8080/webTest/js/hello.js [HTTP/1.1 304 Not Modified 1ms]
GET http://localhost:8080/webTest/img/a.jpg [HTTP/1.1 304 Not Modified 1ms]
從中我們可以看到tomcat對於靜態數據作了緩存。
接著我們分析tomcat對於這部分靜態緩存的判斷處理,這部分邏輯是寫在DefaultServlet類中,
我們可以在doGet方法中進入ServiceContext方法中找到以下源碼:
// Check if the conditions specified in the optional If headers are
// satisfied.
if (cacheEntry.context == null) {
// Checking If headers
boolean included =
(request.getAttribute(Globals.INCLUDE_CONTEXT_PATH_ATTR) != null);
if (!included
&& !checkIfHeaders(request, response, cacheEntry.attributes)) { //這句判斷是否需要返回整個資源請求
return;
}
}
上面源碼的 if (!included
&& !checkIfHeaders(request, response, cacheEntry.attributes))
用於判斷是否需要返回整個資源,如果indcluded與checkIfHeaders方法返回的都是false,這時就直接返回,說明資源未修改,或者是緩存不支持的請求方式。
我們接著查看checkIfHeaders方法:
/**
* Check if the conditions specified in the optional If headers are
* satisfied.
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
* @param resourceAttributes The resource information
* @return boolean true if the resource meets all the specified conditions,
* and false if any of the conditions is not satisfied, in which case
* request processing is stopped
*/
protected boolean checkIfHeaders(HttpServletRequest request,
HttpServletResponse response,
ResourceAttributes resourceAttributes)
throws IOException {
return checkIfMatch(request, response, resourceAttributes)
&& checkIfModifiedSince(request, response, resourceAttributes)
&& checkIfNoneMatch(request, response, resourceAttributes)
&& checkIfUnmodifiedSince(request, response, resourceAttributes);
}
可以看到tomcat只有當這四個屬性全部返回true(也就是說全部認為資源已經改變)才會返回true,這樣最終會將整個資源(最新修改過的)返回客戶端。
在這里,我們從上面實際過程當中看到,瀏覽器第二次請求資源時在http請求header中放了
If-None-Match: W/"175-1399805673000"
If-Modified-Since: Sun, 11 May 2014 10:54:33 GMT
這兩個屬性。
因此我們查看
&& checkIfModifiedSince(request, response, resourceAttributes)
&& checkIfNoneMatch(request, response, resourceAttributes)
這兩個方法
checkIfModifiedSince源碼如下:
/**
* Check if the if-modified-since condition is satisfied.
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
* @param resourceInfo File object
* @return boolean true if the resource meets the specified condition,
* and false if the condition is not satisfied, in which case request
* processing is stopped
*/
protected boolean checkIfModifiedSince(HttpServletRequest request,
HttpServletResponse response,
ResourceAttributes resourceAttributes) {
try {
long headerValue = request.getDateHeader("If-Modified-Since");
long lastModified = resourceAttributes.getLastModified();
if (headerValue != -1) {
// If an If-None-Match header has been specified, if modified since
// is ignored.
if ((request.getHeader("If-None-Match") == null)
&& (lastModified < headerValue + 1000)) {
// The entity has not been modified since the date
// specified by the client. This is not an error case.
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
response.setHeader("ETag", resourceAttributes.getETag());
return false;
}
}
} catch (IllegalArgumentException illegalArgument) {
return true;
}
return true;
}
源碼中可以看到:
if ((request.getHeader("If-None-Match") == null)
&& (lastModified < headerValue + 1000)) {
這句話表明只有在客戶端瀏覽器發送的請求頭中不包含If-None-Match,IfModifiedSince才會生效。
我們接著看checkIfNoneMatch,源碼如下:
/**
* Check if the if-none-match condition is satisfied.
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
* @param resourceInfo File object
* @return boolean true if the resource meets the specified condition,
* and false if the condition is not satisfied, in which case request
* processing is stopped
*/
protected boolean checkIfNoneMatch(HttpServletRequest request,
HttpServletResponse response,
ResourceAttributes resourceAttributes)
throws IOException {
String eTag = resourceAttributes.getETag();
String headerValue = request.getHeader("If-None-Match");
if (headerValue != null) {
boolean conditionSatisfied = false;
if (!headerValue.equals("*")) {
StringTokenizer commaTokenizer =
new StringTokenizer(headerValue, ",");
while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
String currentToken = commaTokenizer.nextToken();
if (currentToken.trim().equals(eTag))
conditionSatisfied = true;
}
} else {
conditionSatisfied = true;
}
if (conditionSatisfied) {
// For GET and HEAD, we should respond with
// 304 Not Modified.
// For every other method, 412 Precondition Failed is sent
// back.
if ( ("GET".equals(request.getMethod()))
|| ("HEAD".equals(request.getMethod())) ) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
response.setHeader("ETag", eTag);
return false;
}
response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
return false;
}
}
return true;
}
這里:
String eTag = resourceAttributes.getETag();
String headerValue = request.getHeader("If-None-Match");
這兩句比較簡單,就是分別從伺服器緩存和http請求頭中中取出etag。
接著判斷這兩個etag如果相等,則conditionSatisfied為true,會執行到以下語句:
if (conditionSatisfied) {
// For GET and HEAD, we should respond with
// 304 Not Modified.
// For every other method, 412 Precondition Failed is sent
// back.
if ( ("GET".equals(request.getMethod()))
|| ("HEAD".equals(request.getMethod())) ) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
response.setHeader("ETag", eTag);
return false;
}
response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
return false;
}
這段語句中可以發現,如果資源未改變的情況下,並且請求方式為GET或者HEAD時,會返回304狀態碼。否則返回一個412狀態碼,同樣不會返回資源內容。
如果上述最終
if ((request.getHeader("If-None-Match") == null)
&& (lastModified < headerValue + 1000))
條件不成立,即資源更新了或者是第一次請求,這里會讀取當前請求資源文件,並最終放入http響應中。