使用 Apollo Express、Nginx 和 docker-compose 保护 websocket

Posted

技术标签:

【中文标题】使用 Apollo Express、Nginx 和 docker-compose 保护 websocket【英文标题】:Secure websocket with Apollo Express, Nginx and docker-compose 【发布时间】:2020-11-20 11:21:50 【问题描述】:

我正在尝试使用 docker-compose 在 *** 上发布我的第一个 GraphQl 项目

它包含一个运行在 nodejs 上的 webapp 和一个也在 nodejs 上运行的 GraphQl API,带有 Apollo Express 和 Prisma

这个想法是让应用程序和 API 在不同的容器上运行,并使用 nginx 容器代理将请求传递给正确的容器(/ 进入 webapp,/api 进入 API)

我让它工作了,似乎没问题,但它需要在 https 上运行。所以我设置了一个letsencrypt证书并将其设置在nginx上并且也可以工作,除了一件事:订阅

如果我尝试使用 ws://mydomain/api 连接到 websocket,它会被拒绝,因为应用程序正在 https 上运行。但是如果我尝试在 wss://mydomain/api 上连接,我会得到:

WebSocket connection to 'wss://mydomain/api' failed: Error during WebSocket handshake: Unexpected response code: 400

我阅读了很多文档和教程,在我看来我做得对,但它就是行不通,我不知道该尝试什么了

这里是相关的 docker-compose.yml 代码:

version: "3"
services:
  api:
    build: 
      context: ./bin/api
    container_name: 'node10-api'
    restart: 'always'
    entrypoint: ["sh", "-c"]
    command: ["yarn && yarn prisma deploy && yarn prisma generate && yarn start"]
    restart: always
    ports:
      - "8383:8383"
    links: 
      - prisma
    volumes: 
      - /local/api:/api
  app:
    build: 
      context: ./bin/app
    container_name: 'node12-app'
    restart: 'always'
    entrypoint: ["sh", "-c"]
    command: ["yarn && yarn build && yarn express-start"]
    restart: always
    ports:
      - "3000:3000"
    links: 
      - api
    volumes: 
      - /local/app:/app
  nginx:
    container_name: 'nginx'
    restart: always
    image: nginx:1.15-alpine
    ports:
      - '80:80'  
      - '443:443'
    volumes:
      - ./data/nginx:/etc/nginx/conf.d
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot      
      - ./www:/etc/nginx/html

这里是 nginx 配置文件:

upstream app 
  ip_hash;
  server app:3000;


upstream api 
  server api:8383;


server 
    listen 80;


map $http_upgrade $connection_upgrade 
  default upgrade;
  ''      close;


server 
    listen 443 ssl;
    server_name mydomain;

    ssl_certificate /etc/letsencrypt/live/mydomain/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mydomain/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / 
      proxy_pass http://app;
     

    location /api 
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $host;

      proxy_pass http://api;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "Upgrade";      
       

最后,服务器初始化:

const app = express();
app.use(cookieParser());
app.use(process.env.URL_BASE_PATH + '/' + process.env.UPLOAD_URL_DIR, express.static(process.env.UPLOAD_PATH));
app.use(process.env.URL_BASE_PATH + '/assets', express.static('assets'));
app.use(process.env.URL_BASE_PATH + '/etc', router);
app.use(createLocaleMiddleware());

app.use(helmet());
app.disable('x-powered-by');

console.log(process.env.URL_BASE_PATH);
if(process.env.URL_BASE_PATH === '')server.applyMiddleware(app, cors:corsOptions);
    else server.applyMiddleware(app, cors:corsOptions, path:process.env.URL_BASE_PATH);

const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);

//STARTING
httpServer.listen(port: process.env.SERVER_PORT, () => 
        console.log(`???? Server ready`)
    
);

server 是 ApolloServer

除了 wss 连接之外一切正常:应用程序可以正常使用 https://mydomain/api 连接到 api,如果我在 http 上运行应用程序,常规 ws 连接也可以工作

只是我无法上班

有什么线索吗?我在这里做错了什么?

【问题讨论】:

【参考方案1】:

我找到了自己的解决方案:docker/nginx 配置是正确的,但 Apollo 期望 wss://mydomain/graphql 上的 websocket 连接,即使 graphql 服务器在 https://mydomain/api 上运行

我没有找到改变它的方法,所以我将它添加到了 nginx conf:

  location ^~/graphql 
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Frame-Options SAMEORIGIN;

    proxy_pass http://api; 
  

终于成功了

【讨论】:

以上是关于使用 Apollo Express、Nginx 和 docker-compose 保护 websocket的主要内容,如果未能解决你的问题,请参考以下文章

使用 ApolloClient 和 Apollo-Server-Express 处理 GraphQL 错误

使用 Express-GraphQL 和 React-Apollo 订阅 GraphQL

使用 express-graphql 和 react-apollo 处理错误

Apollo-Server-Express 未收到上传对象

是否可以将 apollo-server-express 部署到 lambda?

Mongoose 在 Apollo-Express 服务器上抛出错误 ECONNREFUSED