Nginx反向代理Odoo後導致日誌中Werkzeug記錄的IP地址不正確的問題

使用環境

  • 主機(win7 192.168.1.78) 虛機 (ubuntu 192.168.1.102)
  • ubuntu 16.04
  • nginx 1.10.0
  • odoo 9.0c

問題描述

  1. 在odoo不使用代理的情況下,日誌中記錄的ip地址是正確的
    這裏寫圖片描述

  2. 配置nginx

    $ sudo vim /etc/nginx/sites-available/odoo.conf
    
    # odoo.conf 配置如下
    
    
    # 也可以先刪除/etc/nginx/sites-available/default, 因爲監聽80端口衝突了
    
    --- odoo.conf ---
    server {
        listen 80 default;
        server_name _;
    
        location / {
            proxy_pass http://127.0.0.1:8069;
            proxy_next_upstream error timeout invalid_header http_500 http_502 http_504;
    
            proxy_buffer_size 128k;
            proxy_buffers 16 64k;
            proxy_redirect off;
    
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;  # 這一行必須要有
            proxy_set_header X-Forwarded-HOST $host;  # 這一行必須要有
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    
        location ~* /[a-zA-Z0-9_]*/static/ {
            proxy_cache_valid 200 60m;
            proxy_buffering    on;
            expires 864000;
            proxy_pass http://127.0.0.1:8069;
        }
    
    }
    ---
    
    $ sudo ln /etc/nginx/sites-available/odoo.conf /etc/nginx/sites-enabled/odoo.conf
    $ sudo service nginx start
  3. 驗證
    重新啓動odoo, 在 ~/.openerp_serverrc的文件中,我並沒有設定 xmlrpc_interface,但是 proxy_mode = True 一定要設置。odoo開啓後,我們有兩種方式可以從宿主機訪問:

這裏寫圖片描述

此時,werkzeug 的 log 就出現了錯誤,他並沒有處理正確的 IP 地址。
查看源碼,我們可以在
odoo-9.0/openerp/service/wsgi_server.py 中看到
這裏寫圖片描述
這也是爲什麼在前面在nginx配置中一定要 set header X-Forwarded-Host的原因。但是,即使調用了ProxyFix, Werkzeug也並沒有 log 出正確的 IP地址, 所以我們有理由懷疑這個是Werkzeug的鍋

測試 Werkzeug

拋開odoo, 我們使用最簡單的wsgi app來測試werkzeug
在home目錄下,創建 app.py 文件,內容如下

#!/usr/bin/python
# coding: utf-8

import logging
from werkzeug.serving import run_simple, WSGIRequestHandler
from werkzeug.contrib.fixers import ProxyFix

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return 'Hello World\n'

# 這個是按照wsgi_server.py中源碼的樣式寫的
def dispatch_app(environ, start_response):
    # 去掉了對 config 的要求,因爲我們沒有config
    if 'HTTP_X_FORWARDED_HOST' in environ:
        return ProxyFix(application)(environ, start_response)
    else:
        return application(environ, start_response)


if __name__ == "__main__":
    logging.basicConfig(level=10)
    run_simple('0.0.0.0', 8080, dispatch_app)

我們讓他跑起來, 並用宿主機進行訪問 http://192.168.1.102:8080

$ python ~/app.py 

這裏寫圖片描述
可以看到地址是正確的,

我們再來配置 Nginx, 使用反向代理來測試這個app

$ sudo vim /etc/nginx/sites-available/app.conf
# ----app.conf ------
server {
    listen 8044 default;
    server_name __;
    location / {
        proxy_pass http://127.0.0.1:8080;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;  # 這一行必須要有
        proxy_set_header X-Forwarded-HOST $host;  # 這一行必須要有
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
# -------------------
$ sudo ln /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/app.conf
$ sudo service nginx restart

此時宿主機可以通過兩種方式訪問虛擬機

這裏寫圖片描述

可以看出,拋出Odoo這個干擾項之後,Werkzeug還是不能記錄出正確的 IP 地址, 所以,我們決定肯定這鍋就是werkzeug的了

解決方法

在google搜索一大堆之後,終於在搜索引擎中將odoo關鍵詞去掉,進而專注搜索werkzeug的問題,鄙人終於找到了解決方法, 重寫 WSGIRequestHandler.address_string方法

在app.py中添加

# 添加此函數
def fix_werkzeug_logging():
    from werkzeug.serving import WSGIRequestHandler

    def address_string(self):
        # 這就是在nginx的config中,爲什麼一定要有X-Real-IP啦
        return self.headers.get('x-real-ip', self.client_address[0])
    WSGIRequestHandler.address_string = address_string

if __name__ == "__main__":
    fix_werkzeug_logging()  # 這是新增的行
    logging.basicConfig(level=10)
    run_simple('0.0.0.0', 8080, dispatch_app)

重新啓動 python ~/app.py, 用宿主機訪問,發現問題 解決啦!
這裏寫圖片描述

那麼在odoo中修改 odoo-9.0/openerp/service/wsgi_server.py 的 application 函數,新增此行即可!

def application(environ, start_response):
    if config['proxy_mode'] and 'HTTP_X_FORWARDED_HOST' in environ:
        # 增加此行
        werkzeug.serving.WSGIRequestHandler.address_string = lambda self: self.headers.get('x-real-ip', self.client_address[0])
        return werkzeug.contrib.fixers.ProxyFix(application_unproxied)(environ, start_response)
    else:
        return application_unproxied(environ, start_response)

至此,問題解決!

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