[問題記錄]按url context path設置nginx反向代理禪道和Jenkins

問題
在公司同一臺服務器上分別裝了禪道的開源版(linux一鍵安裝)和Jenkins(docker),覺得帶端口號的鏈接給上頭leader等使用有點難看……就想改成用二級url區別的方法,即目標是:

由 dev.server.com:8001 改成用 dev.server.com/zentao   訪問禪道
由 dev.server.com:8002 改成用 dev.server.com/jenkins  訪問Jenkins

然後簡單搜索了下、寫出了下面的(有錯誤的)nginx配置:

 server {
    listen 80;
    server_name dev.server.com;  
    location /zentao {
        proxy_pass http://localhost:8001;
    }
    location /jenkins {
        proxy_pass http://localhost:8002;
    }
}

按上面/zentao、/jenkins這樣的新路徑,禪道一下子就打開了用戶登錄界面,但是Jenkins卻是404。


原因
原本以爲Jenkins不能用很奇怪,最後發現禪道能用只是碰巧……

爲什麼打不開Jenkins?
首先發現如果使用端口號訪問的方式、瀏覽器會自動跳轉至Jenkins登錄界面、url發生了變化,於是聯想到比較下不同的url訪問,整理下是:

(1) GET http://dev.server.com/jenkins 跳轉至 http://dev.server.com/login?from=%2Fjenkins 404 NOT FOUND
(2) GET http://dev.server.com:8002 跳轉至 http://dev.server.com:8002/login?from=%2F 200 OK 顯示登錄界面
(3) GET http://dev.server.com:8002/login 直接 200 OK 顯示登錄界面
(4) GET http://dev.server.com:8002/jenkins 跳轉至 http://dev.server.com:8002/login?from=%2Fjenkins 200 OK 顯示登錄界面

於是可以知道Jenkins登錄界面的訪問url是ip:port/login,後面的from query string只是將請求url中的path貼在了後面(%2F/)。所以nginx配置不能用的原因也很明顯,Jenkins監聽的是8002端口,加了nginx proxy_pass後變成了請求默認的80端口,自然是找不到/找不對的。

爲什麼能打開禪道?
這個是幾方面的巧合:
首先如果用最上面的請求鏈接http://dev.server.com/zentao、沒有以/結尾,按前面的配置nginx會返回301重定向http://dev.server.com/zentao/

In response to a request with URI equal to this string, but without the trailing slash, a permanent redirect with the code 301 will be returned to the requested URI with the slash appended

然後proxy_pass的目標url http://localhost:8001也沒有以/結尾,nginx會將請求路徑path拼至目標url的後面、即變成請求http://localhost:8001/zentao/

If proxy_pass is specified without a URI, the request URI is passed to the server in the same form as sent by a client when the original request is processed, or the full normalized request URI is passed when processing the changed URI

然而使用dev.server.com:8001訪問時、其實顯示的是禪道的歡迎界面、可以選“開源版”或“專業版 試用”;選開源版、用戶登陸界面的url樣子是:http://dev.server.com:8001/zentao/user-login.html;登錄之後、所有的頁面操作也都在http://dev.server.com:8001/zentao/的子級url下。

所以這個似乎上來就能用的nginx配置、其實是經過一些默認轉換後、恰好符合了禪道開源版的url mapping規則。如果將nginx的map規則改成location /zbox,以http://dev.server.com/zbox訪問時、轉換出來的路徑應該是http://localhost:8001/zbox/,那結果就應該是404(這裏有問題、測試下來以http://dev.server.com/zbox訪問並不會自動重定向、而是直接404=>其他1)。


解決方法:
比較一下正常使用時的jenkins頁面和禪道頁面,禪道自身的url、a標籤href、script src、image src等都是在/zentao目錄下,但jenkins/docker就沒有這樣的設計。所以禪道可以很方便的達到dev.server.com/zentao這樣的反向代理,但Jenkins就需要另外的考慮。

Jenkins:
一種思路是使用nginx在jenkins返回的所有資源鏈接前面都加上/jenkins字段,這樣用戶接下來產生的GET請求都可以map到location /jenkins區塊。要實現這個功能需要用到nginx的ngx_http_sub_module。參考Example,可以寫出類似配置:

location /jenkins {
    sub_filter '<a href="/'  '<a href="/jenkins/';
    sub_filter '<img src="/' '<img src="/jenkins/';
    # ...其他替換規則...
    sub_filter_once off;  # 查找並替換多次
    proxy_pass http://localhost:8002;
}

但是這樣做效率很低、且filter遺漏和出錯可能性大(比如=>其他2),所以最後還是妥協、使用了重定向:

location /jenkins {
    return 302 http://dev.server.com:8002; 
}

這樣至少使http://dev.server.com/jenkins這個(間接)訪問鏈接可用。

如果條件允許的話,應該是分配一個子域名比如jenkins.server.com會更合理和方便,這時nginx設置可以參考jenkins官方的wiki:Jenkins behind an NGinX reverse proxy
再或者按官方wiki的提醒、直接使用jenkins.war放到web容器裏跑,這樣也就能保證有/jenkins context path。

禪道:
雖然開頭寫成那樣也可以用、考慮了下還是寫的更明確一點:

location /zentao/ {
    proxy_pass http://localhost:8001/zentao/;
}

其他:

  1. nginx location的匹配string最後要不要加斜槓/
    這裏還是很搞不清楚。
    實測下來,Jenkins使用重定向時,nginx location的匹配字段如果是/jenkins/,則http://dev.server.com/jenkins的請求結果是404;然而禪道配置這邊,nginx location和http請求裏、似乎/zentao/zentao/任意組合使用都是可以的,但是換成/zbox/aaa等其他不存在的path、則連301重定向都沒有、而是直接報404…有點混亂就先不管了,有空看看文檔再說吧。

  2. Jenkins的“跳轉”顯示登錄頁面是怎麼發生的?
    同時用postman GET jenkins測試例子裏(3)以外的url,可以看到返回的http狀態是403 FORBIDDEN,消息體html裏有類似的:

    <head>
        <meta http-equiv='refresh' content='1;url=/login?from=%2F'/>
        <script>window.location.replace('/login?from=%2F');</script>
    </head>

    location.replace引用MDN

    The Location.replace() method replaces the current resource with the one at the provided URL. The difference from the assign() method is that after using replace() the current page will not be saved in session History, meaning the user won’t be able to use the back button to navigate to it.

    Chrome console的話要勾選preserve log才能抓到403的返回狀態,但html還是看不到…

  3. context path?
    contextPath是java servlet的說法,一般就是指http://example.com/aaa/bbb/ccc.html/aaa這個字段。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章