我有一個在反向代理 nginx 上運行的 dockerized react-uvicorn asgi 應用程式。當我運行“docker compose up --build”時,一切都已連接,并且在頁面重新加載時重新連接成功。問題是反應不能發出事件或 uvicorn 沒有收到它們。該應用程式在本地沒有 nginx 的情況下成功測驗,一切正常,直到我添加 nginx 并部署在 digitalocean 上。我有一些不眠之夜試圖弄清楚,但仍然不知道問題是什么。有人可以幫我嗎...
根目錄層次結構:
App
|__client(react)
| |__conf
| |__conf.d
| |__default.conf
| |__gzip.conf
| |__Dockerfile
| |__public
| |__src
| |__withSocket.tsx
| |__App.tsx
|__server(uvicorn)
| |__server.py
| |__Dockerfile
|__another-service(python socket.io)
| |__main.py
|__docker-compose.yml
碼頭工人-compose.yml
version: '3.9'
services:
api:
build: server/
restart: unless-stopped
volumes:
- ./server:/app
ports:
- 8080:8080
client:
build: ./newclient
restart: unless-stopped
ports:
- 80:80
another-service:
build: ./another-service
restart: unless-stopped
volumes:
- ./another-service:/app
ports:
- 5004:5004
depends_on:
- api
uvicorn asgi server.py
import socketio
import asyncio
import json
sio = socketio.AsyncServer(
async_mode='asgi',
async_handlers=True,
cors_allowed_origins="*",
logger=True,
engineio_logger=True,
always_connect=True,
ping_timeout=60
)
app = socketio.ASGIApp(sio, socketio_path='/socket.io')
@sio.event
async def connect(sid, environ, auth=None):
print(Fore.GREEN 'connected ', sid)
@sio.event
def disconnect(sid):
print('disconnect ', sid)
@sio.on('example-event')
async def auth(sid, data):
data = json.loads(data)
print('received data: ' data['data'])
if await client.is_user_authorized():
response = {
'auth_log': 'Authenticated',
'logged_in': True
}
await sio.emit('auth', response)
if __name__ == '__main__':
import uvicorn
uvicorn.run("server:app", host='0.0.0.0', port=8080, log_level="info")
uvicorn 服務器 Dockerfile
FROM python:3.9
ENV PYTHONUNBUFFERED 1
WORKDIR /app
COPY requirements.txt /app/requirements.txt
RUN pip install -r requirements.txt
RUN pip freeze > requirements.txt
COPY . /app
EXPOSE 8080
CMD python server.py
客戶端 React socket.io 組件包裝器 withSocket.tsx
import React from "react";
import { io, Socket } from "socket.io-client";
interface ServerToClientEvents {
auth: (data: any) => void
}
interface ClientToServerEvents {
connect: (connected: string) => void
example-event: (data: any) => void
}
// component wrapper that allows to use socket globally
function withSocket (WrappedComponent: any) {
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(process.env.REACT_APP_SOCKET_URL!, {
transports: ["websocket"],
path: "/socket.io"
});
const WithSocket = (props: any) => {
// function to subscribe to events
const socketListen = async (queue: any, callback: any) => {
await socket.on(queue, (data?: any) => {
callback(data)
})
await socket.on('disconnect', () => socket.off());
}
const socketSend = async (queue?: any, data?: any) => {
await socket.emit(queue!, JSON.stringify(data))
}
return (
<WrappedComponent
{...props}
socketSend={socketSend}
socketListen={socketListen}
/>
)
}
return WithSocket
}
export default withSocket
客戶端 socket.io 組件 App.tsx
import React, {useState, useEffect} from 'react'
import withSocket from "../withSocket";
import "./AuthForm.css"
function AuthForm({socketListen, socketSend}: { socketListen: any; socketSend: any }) {
const [data, setData] = useState('');
const [message, setMessage] = useState('');
useEffect(() => {
socketListen('auth', (data: any) => {
setMessage(JSON.stringify(data.auth_log))
})
}, [socketListen])
let handleSubmitData = async (e: any) => {
e.preventDefault();
try {
socketSend('example-event', {'data': data})
} catch (err) {
console.log(err);
}
}
return(
<>
<div className="form-wrapper">
<form onSubmit={handleSubmitData}>
<label>
<input type="text" name="data" placeholder="Data" onChange={(e) => setData(e.target.value)}/>
</label>
<button type="submit">Send</button>
</form>
<div className="message">
{message ? <p>{message}</p> : null}
</div>
</div>
</>
)
}
export default withSocket(AuthForm)
客戶端 .env 檔案
REACT_APP_SOCKET_URL=ws://example-site.com
客戶端反應和 nginx Dockerfile
# build environment
FROM node:alpine as builder
WORKDIR /app
COPY package.json .
COPY yarn.lock .
RUN yarn
COPY . .
RUN yarn build
# production environment
FROM nginx:1.15.2-alpine
RUN rm -rf /etc/nginx/conf.d
COPY conf /etc/nginx
COPY --from=builder /app/build /usr/share/nginx/html
EXPOSE 80
# Copy .env file and shell script to container
WORKDIR /usr/share/nginx/html
COPY ./env.sh .
COPY .env .
# Add bash
RUN apk add --no-cache bash
# Make our shell script executable
RUN chmod x env.sh
# Start Nginx server
CMD ["/bin/bash", "-c", "/usr/share/nginx/html/env.sh && nginx -g \"daemon off;\""]
nginx配置default.conf
# Use site-specific access and error logs and don't log 2xx or 3xx status codes
map $status $loggable {
~^[23] 0;
default 1;
}
access_log /var/log/nginx/access.log combined buffer=512k flush=1m if=$loggable;
error_log /var/log/nginx/error.log;
upstream socket-server-upstream {
server api:8080;
keepalive 16;
}
server {
listen 80;
listen [::]:80;
server_name example-site.com www.example-site.com;
client_max_body_size 15M;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
# enable WebSockets
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
expires -1;
}
location /socket.io {
proxy_pass http://socket-server-upstream/socket.io;
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 "upgrade";
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers *;
proxy_redirect off;
proxy_buffering off;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
nginx MIME-Type 配置 gzip.conf
gzip on;
gzip_http_version 1.0;
gzip_comp_level 5; # 1-9
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
# MIME-types
gzip_types
application/atom xml
application/javascript
application/json
application/rss xml
application/vnd.ms-fontobject
application/x-font-ttf
application/x-web-app-manifest json
application/xhtml xml
application/xml
font/opentype
image/svg xml
image/x-icon
text/css
text/plain
text/x-component;
正如我在開始時已經寫的那樣,代碼在沒有 nginx 的情況下在本地作業,客戶端正在發出事件,uvicorn 服務器正在定期接收和發出。在 Ubuntu 服務器上部署 nginx 后,服務也已連接,但客戶端沒有發出。為什么?
uj5u.com熱心網友回復:
當您有 2 個 Docker 容器(1 個“前端”容器和 1 個“后端”容器)并且它們需要通過 HTTP 或 WebSocket 交換資訊時,請確保您的域指向您的“后端”容器,例如api.your-domain.com(并呼叫api.your-domain.com從您的“前端”呼叫" 容器,因為您正在從瀏覽器訪問您的 API 服務)。
如果您在 docker-comopose.yml 檔案中有兩個“后端”容器api,another-service并且需要交換資訊,請確保這兩個服務都在同一個 docker 網路上。看看這里:https ://docs.docker.com/compose/networking/#specify-custom-networks
如果他們在同一個網路上,那么他們可以通過他們的服務名稱相互呼叫,例如another-service進行 API 呼叫http://api:8080
(見這里:https ://stackoverflow.com/a/66588432/5734066 )
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/505162.html
上一篇:單個映射指令中的多個變數
