Authenticationerror翻译


介绍一下我是如何构建一个基于WebSocket的HTTP Tunnel工具的。

每天‬分享‬最新‬软件‬开发‬,Devops,敏捷‬,测试‬以及‬项目‬管理‬最新‬,最热门‬的‬文章‬,每天‬花‬3分钟‬学习‬何乐而不为‬,希望‬大家‬点赞‬,评论,加‬关注‬,你的‬支持‬是我‬最大‬的‬动力‬。下方抖音有我介绍自动化测试,以及google cloud 相关视频课程,欢迎观看。



当我们开发一些与第三方服务集成的应用程序时,我们需要让我们的本地开发服务器暴露在互联网上。为此,我们需要为本地服务器建立一个 HTTP 。HTTP Tunnel是如何工作的?在本文中,我将向您展示如何构建 HTTP Tunnel工具。

为什么我们需要部署自己的 HTTP Tunnel服务?

HTTP Tunnel有很多很棒的在线服务。例如,我们可以使用 获得付费的固定公共域来连接您的本地服务器。它也有一个免费的包。但是对于免费套餐,您无法获得固定域。重新启动客户端后,您将获得一个新的随机域。当您需要将域保存在第三方服务中时,这很不方便。 ngrok

为了得到一个固定的域,我们可以在我们自己的服务器上部署一个 HTTP Tunnel。 还提供了 用于服务器端部署的开源版本 。但它是一个旧的 1.x 版本,不建议在生产中部署,存在一些严重的可靠性问题。 ngrok

使用我们自己的服务器,它还可以保证数据的安全。

Lite HTTP Tunnel简介

Lite HTTP Tunnel 是我最近为自托管 HTTP Tunnel服务构建的。您可以在 Github 存储库中使用按钮部署它, 以快速获得免费的固定 Heroku 域。 Heroku

它是基于 并 仅使用一些代码构建的。它使用 WebSocket 将来自公共服务器的 HTTP/HTTPS 请求流式传输到本地服务器。 Express.js Socket.io

我如何实现它

步骤1:建立一个WebSocket服务器和客户端之间的连接

在服务器端支持WebSocket连接 socket . io :

JavaScript

const http = require(\'http\');
const express = require(\'express\');
const { Server } = require(\'socket.io\');

const app = express();
const httpServer = http.createServer(app);
const io = new Server(httpServer);

let connectedSocket = null;

io.on(\'connection\', (socket) => {
  console.log(\'client connected\');
  connectedSocket = socket;
  const onMessage = (message) => {
    if (message === \'ping\') {
      socket.send(\'pong\');
    }
  }
  const onDisconnect = (reason) => {
    console.log(\'client disconnected: \', reason);
    connectedSocket = null;
    socket.off(\'message\', onMessage);
    socket.off(\'error\', onError);
  };
  const onError = (e) => {
    connectedSocket = null;
    socket.off(\'message\', onMessage);
    socket.off(\'disconnect\', onDisconnect);
  };
  socket.on(\'message\', onMessage);
  socket.once(\'disconnect\', onDisconnect);
  socket.once(\'error\', onError);
});

httpServer.listen(process.env.PORT);

WebSocket连接客户端:

JavaScript

const { io } = require(\'socket.io-client\');

let socket = null;

function initClient(options) {
  socket = io(options.server, {
    transports: ["websocket"],
    auth: {
      token: options.jwtToken,
    },
  });

  socket.on(\'connect\', () => {
    if (socket.connected) {
      console.log(\'client connect to server successfully\');
    }
  });

  socket.on(\'connect_error\', (e) => {
    console.log(\'connect error\', e && e.message);
  });

  socket.on(\'disconnect\', () => {
    console.log(\'client disconnected\');
  });
}

第 2 步:使用 Jwt 令牌保护 Websocket 连接

在服务器端,我们使用套接字。io 中间件拒绝无效连接:

JavaScript

const jwt = require(\'jsonwebtoken\');

io.use((socket, next) => {
  if (connectedSocket) {
    return next(new Error(\'Connected error\'));
  }
  if (!socket.handshake.auth || !socket.handshake.auth.token){
    next(new Error(\'Authentication error\'));
  }
  jwt.verify(socket.handshake.auth.token, process.env.SECRET_KEY, function(err, decoded) {
    if (err) {
      return next(new Error(\'Authentication error\'));
    }
    if (decoded.token !== process.env.VERIFY_TOKEN) {
      return next(new Error(\'Authentication error\'));
    }
    next();
  });  
});

步骤3:流请求从服务器到客户端

我们实现一个可写流Tunnel客户端发送请求数据:

JavaScript

const { Writable } = require(\'stream\');

class SocketRequest extends Writable {
  constructor({ socket, requestId, request }) {
    super();
    this._socket = socket;
    this._requestId = requestId;
    this._socket.emit(\'request\', requestId, request);
  }

  _write(chunk, encoding, callback) {
    this._socket.emit(\'request-pipe\', this._requestId, chunk);
    this._socket.conn.once(\'drain\', () => {
      callback();
    });
  }

  _writev(chunks, callback) {
    this._socket.emit(\'request-pipes\', this._requestId, chunks);
    this._socket.conn.once(\'drain\', () => {
      callback();
    });
  }

  _final(callback) {
    this._socket.emit(\'request-pipe-end\', this._requestId);
    this._socket.conn.once(\'drain\', () => {
      callback();
    });
  }

  _destroy(e, callback) {
    if (e) {
      this._socket.emit(\'request-pipe-error\', this._requestId, e && e.message);
      this._socket.conn.once(\'drain\', () => {
        callback();
      });
      return;
    }
    callback();
  }
}

app.use(\'/\', (req, res) => {
  if (!connectedSocket) {
    res.status(404);
    res.send(\'Not Found\');
    return;
  }
  const requestId = uuidV4();
  const socketRequest = new SocketRequest({
    socket: connectedSocket,
    requestId,
    request: {
      method: req.method,
      headers: { ...req.headers },
      path: req.url,
    },
  });
  const onReqError = (e) => {
    socketRequest.destroy(new Error(e || \'Aborted\'));
  }
  req.once(\'aborted\', onReqError);
  req.once(\'error\', onReqError);
  req.pipe(socketRequest);
  req.once(\'finish\', () => {
    req.off(\'aborted\', onReqError);
    req.off(\'error\', onReqError);
  });
  // ...
});

实现一个流可读的get请求数据在客户端:

JavaScript

const stream = require(\'stream\');

class SocketRequest extends stream.Readable {
  constructor({ socket, requestId }) {
    super();
    this._socket = socket;
    this._requestId = requestId;
    const onRequestPipe = (requestId, data) => {
      if (this._requestId === requestId) {
        this.push(data);
      }
    };
    const onRequestPipes = (requestId, data) => {
      if (this._requestId === requestId) {
        data.forEach((chunk) => {
          this.push(chunk);
        });
      }
    };
    const onRequestPipeError = (requestId, error) => {
      if (this._requestId === requestId) {
        this._socket.off(\'request-pipe\', onRequestPipe);
        this._socket.off(\'request-pipes\', onRequestPipes);
        this._socket.off(\'request-pipe-error\', onRequestPipeError);
        this._socket.off(\'request-pipe-end\', onRequestPipeEnd);
        this.destroy(new Error(error));
      }
    };
    const onRequestPipeEnd = (requestId, data) => {
      if (this._requestId === requestId) {
        this._socket.off(\'request-pipe\', onRequestPipe);
        this._socket.off(\'request-pipes\', onRequestPipes);
        this._socket.off(\'request-pipe-error\', onRequestPipeError);
        this._socket.off(\'request-pipe-end\', onRequestPipeEnd);
        if (data) {
          this.push(data);
        }
        this.push(null);
      }
    };
    this._socket.on(\'request-pipe\', onRequestPipe);
    this._socket.on(\'request-pipes\', onRequestPipes);
    this._socket.on(\'request-pipe-error\', onRequestPipeError);
    this._socket.on(\'request-pipe-end\', onRequestPipeEnd);
  }

  _read() {}
}

socket.on(\'request\', (requestId, request) => {
  console.log(`${request.method}: `, request.path);
  request.port = options.port;
  request.hostname = options.host;
  const socketRequest = new SocketRequest({
    requestId,
    socket: socket,
  });
  const localReq = http.request(request);
  socketRequest.pipe(localReq);
  const onSocketRequestError = (e) => {
    socketRequest.off(\'end\', onSocketRequestEnd);
    localReq.destroy(e);
  };
  const onSocketRequestEnd = () => {
    socketRequest.off(\'error\', onSocketRequestError);
  };
  socketRequest.once(\'error\', onSocketRequestError);
  socketRequest.once(\'end\', onSocketRequestEnd);
  // ...

步骤4:流响应从客户端到服务器

实现一个可写流将响应数据发送到Tunnel服务器:

JavaScript

const stream = require(\'stream\');

class SocketResponse extends stream.Writable {
  constructor({ socket, responseId }) {
    super();
    this._socket = socket;
    this._responseId = responseId;
  }

  _write(chunk, encoding, callback) {
    this._socket.emit(\'response-pipe\', this._responseId, chunk);
    this._socket.io.engine.once(\'drain\', () => {
      callback();
    });
  }

  _writev(chunks, callback) {
    this._socket.emit(\'response-pipes\', this._responseId, chunks);
    this._socket.io.engine.once(\'drain\', () => {
      callback();
    });
  }

  _final(callback) {
    this._socket.emit(\'response-pipe-end\', this._responseId);
    this._socket.io.engine.once(\'drain\', () => {
      callback();
    });
  }

  _destroy(e, callback) {
    if (e) {
      this._socket.emit(\'response-pipe-error\', this._responseId, e && e.message);
      this._socket.io.engine.once(\'drain\', () => {
        callback();
      });
      return;
    }
    callback();
  }

  writeHead(statusCode, statusMessage, headers) {
    this._socket.emit(\'response\', this._responseId, {
      statusCode,
      statusMessage,
      headers,
    });
  }
}

socket.on(\'request\', (requestId, request) => {
    // ...stream request and send request to local server...
    const onLocalResponse = (localRes) => {
      localReq.off(\'error\', onLocalError);
      const socketResponse = new SocketResponse({
        responseId: requestId,
        socket: socket,
      });
      socketResponse.writeHead(
        localRes.statusCode,
        localRes.statusMessage,
        localRes.headers
      );
      localRes.pipe(socketResponse);
    };
    const onLocalError = (error) => {
      console.log(error);
      localReq.off(\'response\', onLocalResponse);
      socket.emit(\'request-error\', requestId, error && error.message);
      socketRequest.destroy(error);
    };
    localReq.once(\'error\', onLocalError);
    localReq.once(\'response\', onLocalResponse);
  });

实现一个可读的流在Tunnel响应数据服务器:

JavaScript

class SocketResponse extends Readable {
  constructor({ socket, responseId }) {
    super();
    this._socket = socket;
    this._responseId = responseId;
    const onResponse = (responseId, data) => {
      if (this._responseId === responseId) {
        this._socket.off(\'response\', onResponse);
        this._socket.off(\'request-error\', onRequestError);
        this.emit(\'response\', data.statusCode, data.statusMessage, data.headers);
      }
    }
    const onResponsePipe = (responseId, data) => {
      if (this._responseId === responseId) {
        this.push(data);
      }
    };
    const onResponsePipes = (responseId, data) => {
      if (this._responseId === responseId) {
        data.forEach((chunk) => {
          this.push(chunk);
        });
      }
    };
    const onResponsePipeError = (responseId, error) => {
      if (this._responseId !== responseId) {
        return;
      }
      this._socket.off(\'response-pipe\', onResponsePipe);
      this._socket.off(\'response-pipes\', onResponsePipes);
      this._socket.off(\'response-pipe-error\', onResponsePipeError);
      this._socket.off(\'response-pipe-end\', onResponsePipeEnd);
      this.destroy(new Error(error));
    };
    const onResponsePipeEnd = (responseId, data) => {
      if (this._responseId !== responseId) {
        return;
      }
      if (data) {
        this.push(data);
      }
      this._socket.off(\'response-pipe\', onResponsePipe);
      this._socket.off(\'response-pipes\', onResponsePipes);
      this._socket.off(\'response-pipe-error\', onResponsePipeError);
      this._socket.off(\'response-pipe-end\', onResponsePipeEnd);
      this.push(null);
    };
    const onRequestError = (requestId, error) => {
      if (requestId === this._responseId) {
        this._socket.off(\'request-error\', onRequestError);
        this._socket.off(\'response\', onResponse);
        this._socket.off(\'response-pipe\', onResponsePipe);
        this._socket.off(\'response-pipes\', onResponsePipes);
        this._socket.off(\'response-pipe-error\', onResponsePipeError);
        this._socket.off(\'response-pipe-end\', onResponsePipeEnd);
        this.emit(\'requestError\', error);
      }
    };
    this._socket.on(\'response\', onResponse);
    this._socket.on(\'response-pipe\', onResponsePipe);
    this._socket.on(\'response-pipes\', onResponsePipes);
    this._socket.on(\'response-pipe-error\', onResponsePipeError);
    this._socket.on(\'response-pipe-end\', onResponsePipeEnd);
    this._socket.on(\'request-error\', onRequestError);
  }

  _read(size) {}
}

app.use(\'/\', (req, res) => {
  // ... stream request to tunnel client
  const onResponse = (statusCode, statusMessage, headers) => {
    socketRequest.off(\'requestError\', onRequestError)
    res.writeHead(statusCode, statusMessage, headers);
  };
  socketResponse.once(\'requestError\', onRequestError)
  socketResponse.once(\'response\', onResponse);
  socketResponse.pipe(res);
  const onSocketError = () => {
    res.end(500);
  };
  socketResponse.once(\'error\', onSocketError);
  connectedSocket.once(\'close\', onSocketError)
  res.once(\'close\', () => {
    connectedSocket.off(\'close\', onSocketError);
    socketResponse.off(\'error\', onSocketError);
  });
});

经过所有步骤,我们已经支持将 HTTP 请求流式传输到本地计算机,并将本地服务器的响应发送到原始请求。它是一个精简的解决方案,但在任何环境 下都稳定且易于部署 。node . js

更多的

如果你只是想找一个免费的固定域名的 HTTP Tunnel服务,可以尝试将 Lite HTTP Tunnel 项目部署
https://github.com/web-tunnel/lite-http-tunnel

到 与 在 Github README 中。希望你能从这篇文章中有所收获。


免责声明
    以上文章转载自互联网,文章内容仅供参考,不构成建议,也不代表百科学社赞同其观点。如有侵权请联系755934052@qq.com,提供原文链接地址以及资料原创证明,本站将会立即删除

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请通知我们,一经查实,本站将立刻删除。