Bỏ qua, tới nội dung
Tài liệu APIv1

Tự dựng và vận hành

Reverse proxy Beta

Đặt nginx hoặc Traefik có TLS trước O3O, giữ WebSocket của trình soạn thảo, và các bẫy cấu hình hay gặp.

Trang này mô tả tính năng đang ở giai đoạn beta: đã chạy được nhưng có thể còn thay đổi.

Trong trang này

O3O luôn có HAI lớp proxy. Lớp trong là o3o-proxy, nằm sẵn trong bản Docker: nó hỏi gate trước khi mở phiên soạn thảo và chặn đường quản trị, số liệu và API chuyển đổi của máy chủ soạn thảo, nên KHÔNG được bỏ. Lớp ngoài do bạn đặt: nginx, Traefik hay bộ cân bằng tải, lo TLS và tên miền, rồi chuyển mọi thứ tới cổng 8080.

Proxy trong: o3o-proxy#

Đường dẫnTớiGhi chú
/browser/<hash>/cool.htmlo3o-onlineHỏi gate (giai đoạn page); bị từ chối thì gate trả trang chạm trần.
Mọi nâng cấp WebSocket tới máy chủ soạn thảo: /cool/<doc>/ws, /cool/ws?WOPISrc=…, và các dạng có dấu / cuối hay có đoạn đường dẫn phía sau wso3o-onlineHỏi gate (giai đoạn ws); WebSocket, proxy_read_timeout 36000s. Yêu cầu WebSocket mà gate không phân tích được là yêu cầu lạ: gate trả 403 và proxy từ chối, không cho qua.
/browser, /cool/, /hosting, /lool/o3o-onlineKhông qua gate. Header Upgrade bị xoá, nên không mở được WebSocket nào qua các đường dẫn này.
/browser/dist/admin, /browser/<hash>/admin, /cool/adminws, /cool/getMetrics404Quản trị và số liệu của máy chủ soạn thảo không mở ra ngoài. Chặn theo tiền tố, nên các dạng có dấu / cuối hay có đoạn phía sau (/cool/adminws/, /cool/getMetrics/x) cũng trả 404. Trang quản trị có cả ở đường dẫn kèm mã băm phiên bản, nên phải chặn cả hai.
/cool/convert-to, /lool/convert-to404API chuyển đổi của máy chủ soạn thảo không mở ra ngoài; hệ thống ngoài chuyển đổi qua POST /v1/convert để chịu hạn mức của gói.
/o3o/o3o-gateTrừ /o3o/auth/o3o/wopi/: 404 từ bên ngoài.
/v1/o3o-docbuilderproxy_read_timeout 660s, không đệm thân yêu cầu; giới hạn cỡ do DocBuilder tự áp.
/302 tới /o3o/
Mọi đường dẫn khác404Không chuyển tiếp những gì không có trong bảng này.
nginxCấu hình lõi của proxy trong (đã chạy thử với nginx 1.27)
# Hỏi gate; gate không trả lời hoặc lỗi 5xx thì cho qua
location = /_o3o_auth {
    internal;
    proxy_pass http://o3o-gate:8070/o3o/auth;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
    proxy_set_header X-Original-URI $request_uri;       # URI gốc, chưa giải mã
    proxy_set_header X-Original-Method $request_method;
    proxy_set_header X-O3O-Stage $o3o_stage;
    proxy_connect_timeout 2s;
    proxy_read_timeout 5s;
    proxy_intercept_errors on;
    error_page 500 502 503 504 = /_o3o_failopen;   # fail-open, chỉ khi gate hỏng
}
location = /_o3o_failopen { internal; return 204; }

# MỌI nâng cấp WebSocket của phiên soạn thảo đi qua đây
location ~ ^/cool/(.*/)?ws(/.*)?$ {                     # bắt /cool/ws, dấu / cuối và đoạn sau ws
    set $o3o_stage ws;
    auth_request /_o3o_auth;
    proxy_pass http://o3o-online:9980;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_set_header Host $http_host;
    proxy_http_version 1.1;
    proxy_read_timeout 36000s;
}

# Không mở quản trị, số liệu và API chuyển đổi của máy chủ soạn thảo
location ^~ /browser/dist/admin        { return 404; }
location ~ ^/browser/[^/]+/admin(/|$)  { return 404; }   # trang quản trị theo mã băm phiên bản
location ^~ /cool/adminws              { return 404; }   # tiền tố, không so khớp chính xác: chặn cả dạng có / cuối
location ^~ /cool/getMetrics           { return 404; }
location ^~ /cool/convert-to           { return 404; }   # chuyển đổi cho hệ thống ngoài đi qua POST /v1/convert
location ^~ /lool/convert-to           { return 404; }

# Phần còn lại của /cool/ (và /lool/ y hệt): không chuyển tiếp nâng cấp WebSocket
location /cool/ {
    proxy_pass http://o3o-online:9980;
    proxy_http_version 1.1;
    proxy_set_header Upgrade "";                        # xoá Upgrade và Connection
    proxy_set_header Connection "";
    proxy_set_header Host $http_host;
}

# Gốc chuyển tới gate; mọi đường dẫn khác trả 404
location = / { return 302 /o3o/; }
location /   { return 404; }

Tệp đầy đủ nằm ở online/nginx/o3o-proxy.conf. Nếu tự dựng proxy trong thay cho o3o-proxy, phải giữ ĐỦ các dòng chặn ở trên và đạt mọi mục của danh sách kiểm bên dưới: thiếu dòng nào là mở đường đó ra ngoài. o3o-proxy trả 404 cho /cool/convert-to/lool/convert-to; chuyển đổi cho hệ thống ngoài đi qua POST /v1/convert. Nextcloud dùng convert-to để dựng ảnh xem trước tài liệu: Nextcloud cùng mạng Docker đặt wopi_url = http://o3o-online:9980 nên vẫn có ảnh xem trước; Nextcloud ở máy khác đi qua địa chỉ công khai của proxy thì không có ảnh xem trước. Soạn thảo không bị ảnh hưởng.

Tự viết proxy trong: danh sách kiểm#

Máy chủ soạn thảo nhận nhiều dạng URL cho cùng một việc. Proxy trong phải bắt ĐỦ các dạng dưới đây, nếu không người dùng mở được phiên sửa mà gate không hề được hỏi (vượt trần kết nối), hoặc chạm được bảng quản trị và số liệu. Bảng này dành cho ai thay o3o-proxy bằng cấu hình nginx riêng hay proxy khác; o3o-proxy đi kèm bản Docker đã đạt đủ.

Dạng URLProxy trong phải
/cool/<doc>/ws?… (dạng chuẩn, tài liệu nằm trong đường dẫn)Hỏi gate qua auth_request, giai đoạn ws.
/cool/ws?WOPISrc=…&access_token=… (tài liệu nằm trong chuỗi truy vấn)Hỏi gate, giai đoạn ws, như dạng chuẩn.
/cool/<doc>/ws//cool/<doc>/ws/<đoạn bất kỳ>Hỏi gate, giai đoạn ws.
Phần tài liệu mã hoá hai lần (%252F…)Hỏi gate, và gửi NGUYÊN URI gốc ($request_uri) trong X-Original-URI, không giải mã hay dựng lại. Gate tự giải mã; không phân tích được thì từ chối.
Mọi đường dẫn khác dưới /cool/, /lool/, /browser, /hostingChuyển tiếp KHÔNG kèm header UpgradeConnection: upgrade. Bắt tay WebSocket ở các đường dẫn này không bao giờ được nhận 101.
/cool/adminws, /cool/adminws/, /cool/adminws/<x>Trả 404 (chặn theo tiền tố).
/cool/getMetrics, /cool/getMetrics/, /cool/getMetrics/<x>Trả 404 (chặn theo tiền tố).
/browser/dist/admin…, /browser/<hash>/admin…Trả 404.
/cool/convert-to…, /lool/convert-to…Trả 404; hệ thống ngoài chuyển đổi qua POST /v1/convert.
/o3o/auth, /o3o/wopi/…Trả 404 khi gọi từ bên ngoài.
/browser/<hash>/cool.htmlHỏi gate, giai đoạn page; gate trả 403 thì phục vụ trang chạm trần của gate. Giai đoạn này chỉ để báo sớm; chốt chặn thật là giai đoạn ws.
  • Chặn theo tiền tố (location ^~ /cool/adminws) hoặc biểu thức có (/|$), không bao giờ so khớp chính xác (location = …): so khớp chính xác để lọt dạng có dấu / cuối.
  • Proxy TỰ đặt X-Original-URIX-O3O-Stage cho mọi yêu cầu hỏi gate, ghi đè giá trị máy khách gửi lên; không bao giờ chuyển tiếp hai header này từ máy khách.
  • Fail-open CHỈ khi gate không trả lời, quá giờ hoặc lỗi 5xx. Mã 403 của gate là quyết định cuối, không đổi thành cho qua; yêu cầu WebSocket lạ bị từ chối chứ không fail-open. Gate chỉ từ chối phiên MỚI: phiên đang mở không bao giờ bị ngắt, việc lưu không bao giờ bị chặn.
  • Thử bằng curl --path-as-is: thiếu tuỳ chọn này, curl tự rút gọn đường dẫn và bạn không thử được đúng dạng URL cần chặn. Chạy lại danh sách kiểm mỗi lần nâng phiên bản máy chủ soạn thảo, vì bản mới có thể nhận thêm dạng URL.
BashTự kiểm proxy trong từ bên ngoài
BASE=https://office.example.com

# Mọi dòng phải trả 404
for path in /cool/adminws /cool/adminws/ /cool/adminws/x /cool/getMetrics /cool/getMetrics/ /cool/getMetrics/x \
            /browser/dist/admin/admin.html /cool/convert-to /cool/convert-to/pdf /lool/convert-to /o3o/auth /o3o/wopi/files/x; do
  printf '%-32s %s\n' "$path" "$(curl -s -o /dev/null --path-as-is -w '%{http_code}' "$BASE$path")"
done

# Bắt tay WebSocket: không dòng nào được trả 101
WS=(--http1.1 -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==')
for path in /cool/khong-phai-url/ws /cool/khong-phai-url/ws/ /cool/%252Fx/ws /cool/adminws/ /cool/getMetrics/ /cool/clipboard; do
  printf '%-32s %s\n' "$path" "$(curl -s -o /dev/null --path-as-is --max-time 5 -w '%{http_code}' "${WS[@]}" "$BASE$path")"
done

Kết quả đúng: vòng đầu toàn 404. Vòng hai, ba dòng đầu trả 403 vì gate từ chối yêu cầu WebSocket nó không phân tích được; /cool/adminws//cool/getMetrics/ trả 404; /cool/clipboard trả mã lỗi của máy chủ soạn thảo vì header Upgrade đã bị xoá; không dòng nào trả 101. Để thử các dạng hợp lệ khi đã chạm trần, trên máy thử đặt O3O_GATE_CONNECTION_CAP=1, giữ một phiên sửa, rồi mở phiên thứ hai bằng từng dạng URL trong bảng: mọi dạng phải bị từ chối (403) hoặc chỉ mở ở chế độ chỉ đọc.

nginx ở lớp ngoài#

TLS và WebSocket trước o3o-proxy
# /etc/nginx/conf.d/o3o.conf
map $http_upgrade $connection_upgrade { default upgrade; '' close; }

server {
    listen 80;
    server_name office.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name office.example.com;
    ssl_certificate     /etc/letsencrypt/live/office.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/office.example.com/privkey.pem;

    client_max_body_size 0;          # không giới hạn ở đây; O3O tự áp cỡ tệp theo gói

    location / {
        proxy_pass http://127.0.0.1:8080;   # cổng O3O_PROXY_PORT của o3o-proxy
        proxy_http_version 1.1;
        proxy_set_header Host $http_host;   # giữ nguyên cả cổng, không dùng $host
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 36000s;       # WebSocket rảnh không bị cắt sau 60 giây
        proxy_send_timeout 36000s;
        proxy_request_buffering off;
        proxy_buffering off;
    }
}
O3O_PUBLIC_URL=https://office.example.com
O3O_ONLINE_SERVER_NAME=office.example.com
O3O_ONLINE_SSL_TERMINATION=true

Traefik ở lớp ngoài#

YAMLCấu hình tĩnh và động của Traefik v3
# traefik.yml (cấu hình tĩnh)
entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"
    transport:
      respondingTimeouts:
        readTimeout: 0s      # mặc định 60 giây sẽ cắt WebSocket và tải lên dài
        idleTimeout: 3600s

providers:
  file:
    filename: /etc/traefik/dynamic.yml

certificatesResolvers:
  le:
    acme:
      email: admin@example.com
      storage: /acme/acme.json
      httpChallenge:
        entryPoint: web
---
# dynamic.yml (cấu hình động)
http:
  routers:
    o3o:
      rule: Host(`office.example.com`)
      entryPoints: [websecure]
      service: o3o
      tls:
        certResolver: le
  services:
    o3o:
      loadBalancer:
        passHostHeader: true
        servers:
          - url: http://127.0.0.1:8080   # cổng O3O_PROXY_PORT của o3o-proxy
  • Traefik tự chuyển tiếp WebSocket và tự thêm X-Forwarded-Proto; không cần middleware tự chế cho header Upgrade.
  • Thử thách HTTP của ACME cần cổng 80 mở thật ra Internet; sau CDN hoặc tường lửa hãy dùng thử thách DNS.
  • Với nhà cung cấp Docker của Traefik, đặt exposedByDefault: false để không vô tình mở CSDL hay Nextcloud ra ngoài.

Bẫy hay gặp#

Triệu chứngNguyên nhânCách sửa
Bắt tay WebSocket trả 400, gate không bao giờ được hỏiTrong cấu hình proxy trong có location ^~ /cool/ hoặc ^~ /browser: ^~ làm nginx bỏ qua mọi location biểu thức chính quy.Dùng location tiền tố thường, không ^~ (đã kiểm chứng).
Đã chạm trần mà vẫn mở thêm được phiên sửaLocation WebSocket chỉ bắt một dạng URL (ví dụ ^/cool/(.*)/ws$), còn location chung /cool/ vẫn chuyển tiếp Upgrade mà không hỏi gate; hoặc X-Original-URI không phải URI gốc.Bắt mọi dạng bằng ~ ^/cool/(.*/)?ws(/.*)?$, xoá Upgrade ở location chung, gửi $request_uri; chạy danh sách kiểm ở trên.
/cool/getMetrics/ hay /cool/adminws/ không trả 404Chặn bằng so khớp chính xác (location = /cool/getMetrics): dạng có dấu / cuối rơi vào location chung.Chặn theo tiền tố: location ^~ /cool/getMetrics, location ^~ /cool/adminws.
Người dùng bị văng ra sau khoảng 60 giây không gõThời gian chờ đọc mặc định của proxy ngoài.nginx: proxy_read_timeout 36000s; Traefik: readTimeout: 0s.
Khung soạn thảo tải từ sai cổng hoặc sai tên máyHeader Host mất cổng ($host thay vì $http_host), hoặc O3O_ONLINE_SERVER_NAME sai.Giữ Host $http_host; đặt đúng tên công khai.
Màn hình trắng, trình duyệt báo nội dung hỗn hợpNgười dùng vào https nhưng O3O_ONLINE_SSL_TERMINATION=false.Đặt trueO3O_PUBLIC_URL bằng https.
Tải tệp lên hoặc chuyển đổi báo 413client_max_body_size của proxy ngoài nhỏ hơn giới hạn của O3O.Đặt 0, hoặc ít nhất 300 MB cho /v1/ ở bản doanh nghiệp.
Chuyển đổi đồng bộ báo 504 ở proxy ngoàiThời gian chờ của proxy ngoài ngắn hơn của O3O (/v1/ dùng 660 giây).Tăng thời gian chờ; tệp nặng thì dùng async = true.
Trang nhúng không hiện khung soạn thảoTrang nằm ở origin khác mà O3O_ONLINE_FRAME_ANCESTORS trống, hoặc proxy ngoài tự thêm frame-ancestors 'none'.Đặt biến đó; không để proxy ngoài ghi đè Content-Security-Policy.
WebSocket bị cắt định kỳ dù cấu hình đúngMột lớp CDN hoặc tường lửa ứng dụng đứng trước có thời gian chờ riêng.Kiểm hỗ trợ WebSocket và thời gian chờ của lớp đó.