Tomcat NGINX 選哪個?我全都要!


1,前言

以下內容僅僅是記錄個人配置 Springboot 運行在 Tomcat 和 NGINX 上的過程。內容混亂,容易引起強烈的不適。

SpringBoot 默認使用 Tomcat 作為 Web 服務器,但是如果使用 Tomcat 來提供靜態資源,速度和內存占用上會有問題。因為Tomcat 對靜態資源的處理也是需要通過 Servlet 來處理的[1],這時候,NGINX 就上場了,為了減少對 Servlet 的調用,我們可以使用 NGINX 來處理靜態資源請求。一般來說,可以使用 NGINX 來接收所有的 HTTP 請求,將動態請求轉發給 Tomcat 來進行處理,對於靜態資源的請求,則直接使用 NGINX 處理。

在我們日常的開發當中,有時候,我們還需要對用戶是否可以上傳下載資源進行鑒權。這篇博客還會介紹如何搭配 NGINX 和 Tomcat 來實現上傳下載的鑒權。

這篇博客的思路如下:先介紹如何安裝配置 NGINX,接下來介紹如何將一個使用嵌入 Tomcat 的 SpringBoot 項目轉為使用外部 Tomcat,接着配置 NGINX 來支持上傳下載。

總結:這篇博客介紹如何配置 NGINX 和 Tomcat 實現 SpringBoot 中帶有鑒權的上傳和下載靜態資源。

環境

系統:Windows 10。如果不需要上傳,使用 Windows 的 NGINX 即可。但是為了增加上傳功能,需要將上傳模塊編譯到 NGINX 中,所以我后面用了 WSL。Tomcat 使用 Windows 版本的,版本是 9.0.40。

2,NGINX

安裝

首先,第一件事情就是下載:http://nginx.org/en/download.html

直接到這個網站下載即可。如果這個鏈接失效了,那么請用自己喜歡的搜索引擎找下載地址即可。

由於筆者用的 Windows,直接下載 Windows 的包。之后找個地方解壓出來之后,雙擊運行。運行后,發現什么反應都沒有的樣子。其實,NGINX 運行在了后台。

接着用瀏覽器打開,localhost,就可以看到如下圖。這是打開 localhost 就不要用 8080 端口啦,用 80 端口。

靜態資源下載

配置 conf/nging.conf 這個文件。確保自己有 D:/2019/file 這個文件夾。

      server {
		listen 8888;
		server_name resource;
		charset utf-8;
		
		location /file/ {
			root D:/2019/;
			autoindex on;
		}
	}

一開始我在這里卡住了,訪問 /file/ 報 404。這里的問題是 “/file/” 的配置中用的是 root,查 StackOverflow,下面的答案說用 alias。alias 和 root 的區別在於,alias 不會將 location 配置的路徑名字放到 alias 配置的路徑后面,而 root 會。所以,對於上面的配置,實際上訪問的地址是 “D:/2019/file”,然而我並沒有這個文件夾,所以 404 了。

小問題

如果訪問文件名帶有中文的文件,會出 404。訪問文件夾,會出 500。

對於這個問題,解決方法是處理好編碼問題。在 Windows 上,需要將系統的默認編碼改變 utf-8(參考鏈接:https://jingyan.baidu.com/article/25648fc1471e6a9191fd002e.html),同時設置 NGINX 的編碼為 utf-8。

3,tomcat 啟動 spring boot 項目

主要步驟參考鏈接:https://blog.csdn.net/u011768325/article/details/78018635

1, 修改打包方式為 war : https://blog.csdn.net/qq_46632322/article/details/105045502

2, 移除內嵌的 tomcat,再添加 tomcat 的包

        <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-servlet-api -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-servlet-api</artifactId>
            <version>9.0.40</version>
            <scope>provided</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-catalina</artifactId>
            <version>9.0.40</version>
        </dependency>

中間遇到的問題是:程序包org.apache.catalina不存在。最開始配置的時候,使用的是 tomcat 10,在 maven 中添加的版本也是 10。但是出現了問題,第一個問題就是,我沒有添加 tomcat-catalina,於是 build 的時候,找不到 tomcat 相關的包,比如 catalina 之類的。添加依賴,不僅僅要添加 servlet,還要添加 tomcat。接下來的問題就很麻煩了,這個問題就是包名的變化,從 javax 到 jakarta 的改變,可以將自己程序中的 javax 改成 jakarta。但是,如果是僅僅改改自己的包名那還簡單。問題是,很多其他的包,比如 shiro,依賴的是 javax,如果傳入一個 jakarta,shiro 可是不認的呢。

Fuck Oracle!

因此,最終是用 tomcat 9.0.40 這個版本,這樣就避免了代碼的更改,直接改 pom 依賴就好了。

3,配置 IDEA,這個參考了這里:https://blog.csdn.net/Goodbye_Youth/article/details/85640726

4,配置 tomcat 和 nginx

tomcat 不需要配置。

nginx 需要配置,將請求轉發到 8080 端口。注意下面將 “/file/” 配置在了 “/” 前面,因為 NGINX 的規則采用優先匹配,在前面的先匹配。對於靜態資源的請求,我們直接處理,如果不是,我們再轉發給 tomcat 處理。


worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    gzip  on;

    upstream tomcat {
      server 127.0.0.1:8080;
    }
	
    server {
        listen       80;
        server_name  localhost;
	charset utf-8;
        
	location /file/ {
      	      root D:/2019/;
	      autoindex on;
	}
		
        location / {
            proxy_set_header	Host $host;
	    proxy_set_header        X-Real-IP $remote_addr;
            proxy_pass http://tomcat;
        }
		
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

    }

}


5,NGINX 資源下載鑒權

在 NGINX 的靜態資源配置選項下面使用 internal 關鍵字,SpringBoot 在返回體中設置好 X-Accel-Redirect 頭部,參考:https://www.shuzhiduo.com/A/E35pRY4Kzv/

做的過程中遇到一個坑了我很久時間的問題是:編碼問題。如果文件名字沒有處理好,遇到了中文字符,那么下載下來的文件總是只有 4KB。真是個坑,把我坑了好久。

    @GetMapping("/download/{key}/{filename}")
    @ApiOperation(value = "下載文件", notes = "下載文件, 鑒權")
    public void downloadFile(@PathVariable("filename") String filename,
                             @PathVariable("key") String key,
                             HttpServletResponse response) {
        if ("233333".equals(key)) {
            try {
                response.setHeader("X-Accel-Redirect", "/resource/" +
                        URLEncoder.encode(filename, "UTF-8"));
                response.setHeader("Content-Disposition", "attachment; filename=" +
                        URLEncoder.encode(filename, "UTF-8"));
                response.setHeader("Content-Type", "application/octet-stream");
            }
            catch (UnsupportedEncodingException e) {
                LoggerUtil.getLogger().info("后端錯誤:使用了不支持的編碼");
            }
        }
        else {
            JSONObject responseJson = new JSONObject();
            responseJson.put("reason", "密碼錯誤");
            ResponseUtil.responseJson(response, responseJson);
        }
    }

6,NGINX 上傳鑒權

重新編譯 NGINX

使用 NGINX 的上傳模塊。這個模塊的原理是,先將上傳的東西存儲到臨時文件夾,再向 tomcat 發送請求。

主要參考:https://breeze2.github.io/blog/scheme-nginx-php-js-upload-process

對於上傳,需要添加上傳相關的模塊然后重新編譯 NGINX

看 NGINX 的官網,需要安裝依賴的庫。不過安裝的時候,各種下載不了,所以直接使用 ubuntu 的 apt 來安裝。

https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#dependencies

需要安裝三個庫,pcre, zlib, openssl,對於這每一個庫,在 ubuntu 上每個庫的報安裝的名字如下所示,每個都要谷歌一下才能查到...

sudo apt install libpcre3 libpcre3-dev
sudo apt install zlib1g zlib1g-dev
sudo apt install libssl-dev

顯示如圖,就表示配置成功了。

順便一提:WSL 在 Windows 上 Home 目錄在:C:\Users\zzk\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu20.04onWindows_79rhkp1fndgsc\LocalState\rootfs\home\zzk\web\nginx-1.18.0

WSL 開放 80 端口:https://mlog.club/article/4865246

配置 NGINX 和 Tomcat

讓 NGINX 運行在當前目錄: sudo ./nginx -p $(pwd)

參考這里 https://breeze2.github.io/blog/scheme-nginx-php-js-upload-process 編寫一個上傳頁面的 html,放到 nginx 工作目錄中的 html 文件夾下面。

問題1:403,這個好解決,問題來自於文件太大,修改 NGINX 配置就好。
問題2:有時候上傳一直在轉圈圈,這是為什么呢?

查看 NGINX 的日志,發現了 408 錯誤碼,304 的意思是內容沒有改變,客戶端可以使用緩存。

解決方案:https://blog.csdn.net/AmateurLee/article/details/111963288 ,添加一個 client_body_buffer_size 配置就好了。

    @PostMapping("/nginx-upload")
    @ApiOperation(value = "上傳文件", notes = "上傳文件, 鑒權")
    public void downloadFile(HttpServletRequest request, HttpServletResponse response) {
        JSONObject requestJson = RequestUtil.retrieveForm(request);
        if ("application/zip".equals(requestJson.get("file_content_type"))) {
            response.setStatus(200);
            response.setContentType("text/html");
            try {
                response.getWriter().println("upload success");
            }
            catch (IOException e) {
                LoggerUtil.getLogger().info("后端錯誤:輸出有問題");
            }
        }
        else {
            response.setStatus(403);
        }
    }

NGINX 配置


worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    gzip  on;
    charset utf-8;
    client_max_body_size 1024m;

    upstream tomcat {
	server 127.0.0.1:8080;
    }

    server {
        listen       80;
        server_name  localhost;

        location /file/ {
		root /home/zzk/web/nginx-1.18.0/upload_store;
		autoindex on;
	}
		
	location /resource/ {
	    # internal;
	    alias /home/zzk/web/nginx-1.18.0/upload_store;
	    autoindex on;
	}

        location = /file.html {
            root html;
        }
        
        location /upload {
            client_max_body_size 1024m;
            client_body_buffer_size 1024k;

            upload_pass /example/nginx-upload;
            upload_store /home/zzk/web/nginx-1.18.0/upload_store 1;
            upload_store_access user:r;

            # 設置請求體的字段
            upload_set_form_field "${upload_field_name}_name" "$upload_file_name";
            upload_set_form_field "${upload_field_name}_content_type" "$upload_content_type";
            upload_set_form_field "${upload_field_name}_path" "$upload_tmp_path";

            # 指示后端關於上傳文件的md5值和文件大小
            upload_aggregate_form_field "${upload_field_name}_md5" "$upload_file_md5";
            upload_aggregate_form_field "${upload_field_name}_size" "$upload_file_size";

            upload_pass_form_field "^submit$|^description$";

            # 若出現如下錯誤碼則刪除上傳的文件
            upload_cleanup 400 404 499 500-505;
        }

        location / {
            proxy_set_header	Host $host;
	    proxy_set_header        X-Real-IP $remote_addr;
            proxy_pass http://tomcat;
        }
        
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

    }
}


參考鏈接

Tomcat 是否使用 Servlet 來處理靜態資源請求 https://www.zhihu.com/question/57400909
Windows 下的靜態資源配置 https://blog.csdn.net/Yl12fh/article/details/81026391
tomcat 對比 NGINX,還有 Linux 下的靜態資源配置 https://juejin.cn/post/6844903875296641038
NGINX 常用命令 https://segmentfault.com/a/1190000012953480
設置 Windows 默認編碼為 utf-8 https://jingyan.baidu.com/article/25648fc1471e6a9191fd002e.html
nginx location 詳解,下面也講到了 alias 和 root 的區別 https://www.jianshu.com/p/a16936455018
關閉 nginx 進程,https://blog.csdn.net/weixin_38111957/article/details/81023124

Over


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM